diff options
Diffstat (limited to 'modules')
194 files changed, 9566 insertions, 2726 deletions
diff --git a/modules/arkit/arkit_interface.h b/modules/arkit/arkit_interface.h index 8129611287..e1dbca1488 100644 --- a/modules/arkit/arkit_interface.h +++ b/modules/arkit/arkit_interface.h @@ -3,10 +3,10 @@ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ -/* http://www.godotengine.org */ +/* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm index 68844c54c2..9614f775a5 100644 --- a/modules/arkit/arkit_interface.mm +++ b/modules/arkit/arkit_interface.mm @@ -3,10 +3,10 @@ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ -/* http://www.godotengine.org */ +/* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/arkit/arkit_session_delegate.h b/modules/arkit/arkit_session_delegate.h index afe093656b..9303552ca6 100644 --- a/modules/arkit/arkit_session_delegate.h +++ b/modules/arkit/arkit_session_delegate.h @@ -3,10 +3,10 @@ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ -/* http://www.godotengine.org */ +/* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/arkit/arkit_session_delegate.mm b/modules/arkit/arkit_session_delegate.mm index 56485c987c..d4072fc391 100644 --- a/modules/arkit/arkit_session_delegate.mm +++ b/modules/arkit/arkit_session_delegate.mm @@ -3,10 +3,10 @@ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ -/* http://www.godotengine.org */ +/* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/assimp/SCsub b/modules/assimp/SCsub index d8ef866bec..275f1ff5e9 100644 --- a/modules/assimp/SCsub +++ b/modules/assimp/SCsub @@ -9,6 +9,7 @@ env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/include']) env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/code/Importer/IFC']) env_assimp.Prepend(CPPPATH=['#thirdparty/misc']) env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/code']) +env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/common']) env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/contrib/irrXML/']) env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/contrib/unzip/']) env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/code/Importer/STEPParser']) @@ -65,11 +66,11 @@ env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_STEP_IMPORTER']) env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_IFC_IMPORTER']) env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_XGL_IMPORTER']) env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_ASSBIN_IMPORTER']) -env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_GLTF_IMPORTER']) env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_C4D_IMPORTER']) env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_3MF_IMPORTER']) env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_X3D_IMPORTER']) - +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_GLTF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_GLTF2_IMPORTER']) env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_SINGLETHREADED']) if(env['platform'] == 'windows'): @@ -84,7 +85,13 @@ elif(env['platform'] == 'osx'): env_thirdparty = env_assimp.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, Glob('#thirdparty/assimp/code/*.cpp')) +env_thirdparty.add_source_files(env.modules_sources, Glob('#thirdparty/assimp/code/Common/*.cpp')) +env_thirdparty.add_source_files(env.modules_sources, Glob('#thirdparty/assimp/code/PostProcessing/*.cpp')) +env_thirdparty.add_source_files(env.modules_sources, Glob('#thirdparty/assimp/code/Material/*.cpp')) +env_thirdparty.add_source_files(env.modules_sources, Glob('#thirdparty/assimp/code/FBX/*.cpp')) +env_thirdparty.add_source_files(env.modules_sources, Glob('#thirdparty/assimp/code/MMD/*.cpp')) +env_thirdparty.add_source_files(env.modules_sources, Glob('#thirdparty/assimp/code/glTF/*.cpp')) +env_thirdparty.add_source_files(env.modules_sources, Glob('#thirdparty/assimp/code/glTF2/*.cpp')) # Godot's own source files env_assimp.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp index 65fa8b6459..6a95d355eb 100644 --- a/modules/assimp/editor_scene_importer_assimp.cpp +++ b/modules/assimp/editor_scene_importer_assimp.cpp @@ -28,24 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "assimp/DefaultLogger.hpp" -#include "assimp/Importer.hpp" -#include "assimp/LogStream.hpp" -#include "assimp/Logger.hpp" -#include "assimp/SceneCombiner.h" -#include "assimp/cexport.h" -#include "assimp/cimport.h" -#include "assimp/matrix4x4.h" -#include "assimp/pbrmaterial.h" -#include "assimp/postprocess.h" -#include "assimp/scene.h" - +#include "editor_scene_importer_assimp.h" #include "core/bind/core_bind.h" #include "core/io/image_loader.h" #include "editor/editor_file_system.h" #include "editor/import/resource_importer_scene.h" -#include "editor_scene_importer_assimp.h" #include "editor_settings.h" +#include "import_utils.h" #include "scene/3d/camera.h" #include "scene/3d/light.h" #include "scene/3d/mesh_instance.h" @@ -53,7 +42,19 @@ #include "scene/main/node.h" #include "scene/resources/material.h" #include "scene/resources/surface_tool.h" -#include "zutil.h" + +#include <assimp/SceneCombiner.h> +#include <assimp/cexport.h> +#include <assimp/cimport.h> +#include <assimp/matrix4x4.h> +#include <assimp/pbrmaterial.h> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <zutil.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <assimp/LogStream.hpp> +#include <assimp/Logger.hpp> #include <string> void EditorSceneImporterAssimp::get_extensions(List<String> *r_extensions) const { @@ -92,18 +93,6 @@ uint32_t EditorSceneImporterAssimp::get_import_flags() const { return IMPORT_SCENE; } -AssimpStream::AssimpStream() { - // empty -} - -AssimpStream::~AssimpStream() { - // empty -} - -void AssimpStream::write(const char *message) { - print_verbose(String("Open Asset Import: ") + String(message).strip_edges()); -} - void EditorSceneImporterAssimp::_bind_methods() { } @@ -122,34 +111,34 @@ Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_f //} importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT); + //importer.SetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD, 1.0f); int32_t post_process_Steps = aiProcess_CalcTangentSpace | + aiProcess_GlobalScale | // imports models and listens to their file scale for CM to M conversions //aiProcess_FlipUVs | - //aiProcess_FlipWindingOrder | + aiProcess_FlipWindingOrder | // very important for culling so that it is done in the correct order. //aiProcess_DropNormals | //aiProcess_GenSmoothNormals | - aiProcess_JoinIdenticalVertices | + //aiProcess_JoinIdenticalVertices | aiProcess_ImproveCacheLocality | - aiProcess_LimitBoneWeights | //aiProcess_RemoveRedundantMaterials | // Causes a crash - aiProcess_SplitLargeMeshes | + //aiProcess_SplitLargeMeshes | aiProcess_Triangulate | aiProcess_GenUVCoords | //aiProcess_FindDegenerates | - aiProcess_SortByPType | - aiProcess_FindInvalidData | + //aiProcess_SortByPType | + // aiProcess_FindInvalidData | aiProcess_TransformUVCoords | aiProcess_FindInstances | //aiProcess_FixInfacingNormals | - //aiProcess_ValidateDataStructure | + aiProcess_ValidateDataStructure | aiProcess_OptimizeMeshes | //aiProcess_OptimizeGraph | //aiProcess_Debone | - aiProcess_EmbedTextures | - aiProcess_SplitByBoneCount | + // aiProcess_EmbedTextures | + //aiProcess_SplitByBoneCount | 0; - const aiScene *scene = importer.ReadFile(s_path.c_str(), - post_process_Steps); + aiScene *scene = (aiScene *)importer.ReadFile(s_path.c_str(), post_process_Steps); ERR_EXPLAIN(String("Open Asset Import failed to open: ") + String(importer.GetErrorString())); ERR_FAIL_COND_V(scene == NULL, NULL); return _generate_scene(p_path, scene, p_flags, p_bake_fps, max_bone_weights); @@ -282,159 +271,7 @@ T EditorSceneImporterAssimp::_interpolate_track(const Vector<float> &p_times, co ERR_FAIL_V(p_values[0]); } -void EditorSceneImporterAssimp::_generate_bone_groups(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<String, Transform> &bind_xforms) { - - Transform mesh_offset = _get_global_assimp_node_transform(p_assimp_node); - //mesh_offset.basis = Basis(); - for (uint32_t i = 0; i < p_assimp_node->mNumMeshes; i++) { - const aiMesh *mesh = state.assimp_scene->mMeshes[i]; - int owned_by = -1; - for (uint32_t j = 0; j < mesh->mNumBones; j++) { - const aiBone *bone = mesh->mBones[j]; - String name = _assimp_get_string(bone->mName); - - if (ownership.has(name)) { - owned_by = ownership[name]; - break; - } - } - - if (owned_by == -1) { //no owned, create new unique id - owned_by = 1; - for (Map<String, int>::Element *E = ownership.front(); E; E = E->next()) { - owned_by = MAX(E->get() + 1, owned_by); - } - } - - for (uint32_t j = 0; j < mesh->mNumBones; j++) { - const aiBone *bone = mesh->mBones[j]; - String name = _assimp_get_string(bone->mName); - ownership[name] = owned_by; - //store the actual full path for the bone transform - //when skeleton finds its place in the tree, it will be restored - bind_xforms[name] = mesh_offset * _assimp_matrix_transform(bone->mOffsetMatrix); - } - } - - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - _generate_bone_groups(state, p_assimp_node->mChildren[i], ownership, bind_xforms); - } -} - -void EditorSceneImporterAssimp::_fill_node_relationships(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, int p_skeleton_id, Skeleton *p_skeleton, const String &p_parent_name, int &holecount, const Vector<SkeletonHole> &p_holes, const Map<String, Transform> &bind_xforms) { - - String name = _assimp_get_string(p_assimp_node->mName); - if (name == String()) { - name = "AuxiliaryBone" + itos(holecount++); - } - - Transform pose = _assimp_matrix_transform(p_assimp_node->mTransformation); - - if (!ownership.has(name)) { - //not a bone, it's a hole - Vector<SkeletonHole> holes = p_holes; - SkeletonHole hole; //add a new one - hole.name = name; - hole.pose = pose; - hole.node = p_assimp_node; - hole.parent = p_parent_name; - holes.push_back(hole); - - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - _fill_node_relationships(state, p_assimp_node->mChildren[i], ownership, skeleton_map, p_skeleton_id, p_skeleton, name, holecount, holes, bind_xforms); - } - - return; - } else if (ownership[name] != p_skeleton_id) { - //oh, it's from another skeleton? fine.. reparent all bones to this skeleton. - int prev_owner = ownership[name]; - ERR_EXPLAIN("A previous skeleton exists for bone '" + name + "', this type of skeleton layout is unsupported."); - ERR_FAIL_COND(skeleton_map.has(prev_owner)); - for (Map<String, int>::Element *E = ownership.front(); E; E = E->next()) { - if (E->get() == prev_owner) { - E->get() = p_skeleton_id; - } - } - } - - //valid bone, first fill holes if needed - for (int i = 0; i < p_holes.size(); i++) { - - int bone_idx = p_skeleton->get_bone_count(); - p_skeleton->add_bone(p_holes[i].name); - int parent_idx = p_skeleton->find_bone(p_holes[i].parent); - if (parent_idx >= 0) { - p_skeleton->set_bone_parent(bone_idx, parent_idx); - } - - Transform pose_transform = _get_global_assimp_node_transform(p_holes[i].node); - p_skeleton->set_bone_rest(bone_idx, pose_transform); - - state.bone_owners[p_holes[i].name] = skeleton_map[p_skeleton_id]; - } - - //finally fill bone - - int bone_idx = p_skeleton->get_bone_count(); - p_skeleton->add_bone(name); - int parent_idx = p_skeleton->find_bone(p_parent_name); - if (parent_idx >= 0) { - p_skeleton->set_bone_parent(bone_idx, parent_idx); - } - //p_skeleton->set_bone_pose(bone_idx, pose); - if (bind_xforms.has(name)) { - //for now this is the full path to the bone in rest pose - //when skeleton finds it's place in the tree, it will get fixed - p_skeleton->set_bone_rest(bone_idx, bind_xforms[name]); - } - state.bone_owners[name] = skeleton_map[p_skeleton_id]; - //go to children - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - _fill_node_relationships(state, p_assimp_node->mChildren[i], ownership, skeleton_map, p_skeleton_id, p_skeleton, name, holecount, Vector<SkeletonHole>(), bind_xforms); - } -} - -void EditorSceneImporterAssimp::_generate_skeletons(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, const Map<String, Transform> &bind_xforms) { - - //find skeletons at this level, there may be multiple root nodes for each - Map<int, List<aiNode *> > skeletons_found; - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - String name = _assimp_get_string(p_assimp_node->mChildren[i]->mName); - if (ownership.has(name)) { - int skeleton = ownership[name]; - if (!skeletons_found.has(skeleton)) { - skeletons_found[skeleton] = List<aiNode *>(); - } - skeletons_found[skeleton].push_back(p_assimp_node->mChildren[i]); - } - } - - //go via the potential skeletons found and generate the actual skeleton - for (Map<int, List<aiNode *> >::Element *E = skeletons_found.front(); E; E = E->next()) { - ERR_CONTINUE(skeleton_map.has(E->key())); //skeleton already exists? this can't be.. skip - Skeleton *skeleton = memnew(Skeleton); - //this the only way to reliably use multiple meshes with one skeleton, at the cost of less precision - skeleton->set_use_bones_in_world_transform(true); - skeleton_map[E->key()] = state.skeletons.size(); - state.skeletons.push_back(skeleton); - int holecount = 1; - //fill the bones and their relationships - for (List<aiNode *>::Element *F = E->get().front(); F; F = F->next()) { - _fill_node_relationships(state, F->get(), ownership, skeleton_map, E->key(), skeleton, "", holecount, Vector<SkeletonHole>(), bind_xforms); - } - } - - //go to the children - for (uint32_t i = 0; i < p_assimp_node->mNumChildren; i++) { - String name = _assimp_get_string(p_assimp_node->mChildren[i]->mName); - if (ownership.has(name)) { - continue; //a bone, so don't bother with this - } - _generate_skeletons(state, p_assimp_node->mChildren[i], ownership, skeleton_map, bind_xforms); - } -} - -Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights) { +Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights) { ERR_FAIL_COND_V(scene == NULL, NULL); ImportState state; @@ -445,60 +282,37 @@ Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, const state.fbx = false; state.animation_player = NULL; - real_t scale_factor = 1.0f; - { - //handle scale - String ext = p_path.get_file().get_extension().to_lower(); - if (ext == "fbx") { - if (scene->mMetaData != NULL) { - float factor = 1.0; - scene->mMetaData->Get("UnitScaleFactor", factor); - scale_factor = factor * 0.01f; - } - state.fbx = true; - } - } - - state.root->set_scale(Vector3(scale_factor, scale_factor, scale_factor)); - //fill light map cache for (size_t l = 0; l < scene->mNumLights; l++) { aiLight *ai_light = scene->mLights[l]; ERR_CONTINUE(ai_light == NULL); - state.light_cache[_assimp_get_string(ai_light->mName)] = l; + state.light_cache[AssimpUtils::get_assimp_string(ai_light->mName)] = l; } //fill camera cache for (size_t c = 0; c < scene->mNumCameras; c++) { aiCamera *ai_camera = scene->mCameras[c]; ERR_CONTINUE(ai_camera == NULL); - state.camera_cache[_assimp_get_string(ai_camera->mName)] = c; + state.camera_cache[AssimpUtils::get_assimp_string(ai_camera->mName)] = c; } if (scene->mRootNode) { - Map<String, Transform> bind_xforms; //temporary map to store bind transforms - //guess the skeletons, since assimp does not really support them directly - Map<String, int> ownership; //bone names to groups - //fill this map with bone names and which group where they detected to, going mesh by mesh - _generate_bone_groups(state, state.assimp_scene->mRootNode, ownership, bind_xforms); - Map<int, int> skeleton_map; //maps previously created groups to actual skeletons - //generates the skeletons when bones are found in the hierarchy, and follows them (including gaps/holes). - _generate_skeletons(state, state.assimp_scene->mRootNode, ownership, skeleton_map, bind_xforms); //generate nodes for (uint32_t i = 0; i < scene->mRootNode->mNumChildren; i++) { - _generate_node(state, scene->mRootNode->mChildren[i], state.root); + _generate_node(state, NULL, scene->mRootNode->mChildren[i], state.root); } - //assign skeletons to nodes - - for (Map<MeshInstance *, Skeleton *>::Element *E = state.mesh_skeletons.front(); E; E = E->next()) { - MeshInstance *mesh = E->key(); - Skeleton *skeleton = E->get(); - NodePath skeleton_path = mesh->get_path_to(skeleton); - mesh->set_skeleton_path(skeleton_path); + // finalize skeleton + for (Map<Skeleton *, const Spatial *>::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) { + Skeleton *skeleton = key_value_pair->key(); + // convert world to local for skeleton bone rests + skeleton->localize_rests(); } + + print_verbose("generating mesh phase from skeletal mesh"); + generate_mesh_phase_from_skeletal_mesh(state); } if (p_flags & IMPORT_ANIMATION && scene->mNumAnimations) { @@ -603,12 +417,14 @@ void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, cons } } +// animation tracks are per bone + void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_animation_index, int p_bake_fps) { ERR_FAIL_INDEX(p_animation_index, (int)state.assimp_scene->mNumAnimations); const aiAnimation *anim = state.assimp_scene->mAnimations[p_animation_index]; - String name = _assimp_anim_string_to_string(anim->mName); + String name = AssimpUtils::get_anim_string_from_assimp(anim->mName); if (name == String()) { name = "Animation " + itos(p_animation_index + 1); } @@ -618,7 +434,7 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim if (state.assimp_scene->mMetaData != NULL && Math::is_equal_approx(ticks_per_second, 0.0f)) { int32_t time_mode = 0; state.assimp_scene->mMetaData->Get("TimeMode", time_mode); - ticks_per_second = _get_fbx_fps(time_mode, state.assimp_scene); + ticks_per_second = AssimpUtils::get_fbx_fps(time_mode, state.assimp_scene); } //? @@ -639,36 +455,31 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim for (size_t i = 0; i < anim->mNumChannels; i++) { const aiNodeAnim *track = anim->mChannels[i]; - String node_name = _assimp_get_string(track->mNodeName); - /* - if (node_name.find(ASSIMP_FBX_KEY) != -1) { - String p_track_type = node_name.get_slice(ASSIMP_FBX_KEY, 1); - if (p_track_type == "_Translation" || p_track_type == "_Rotation" || p_track_type == "_Scaling") { - continue; - } - } -*/ + String node_name = AssimpUtils::get_assimp_string(track->mNodeName); + if (track->mNumRotationKeys == 0 && track->mNumPositionKeys == 0 && track->mNumScalingKeys == 0) { continue; //do not bother } - bool is_bone = state.bone_owners.has(node_name); - NodePath node_path; - Skeleton *skeleton = NULL; + for (Map<Skeleton *, const Spatial *>::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) { + Skeleton *skeleton = key_value_pair->key(); - if (is_bone) { - skeleton = state.skeletons[state.bone_owners[node_name]]; - String path = state.root->get_path_to(skeleton); - path += ":" + node_name; - node_path = path; - } else { + bool is_bone = skeleton->find_bone(node_name) != -1; + //print_verbose("Bone " + node_name + " is bone? " + (is_bone ? "Yes" : "No")); + NodePath node_path; - ERR_CONTINUE(!state.node_map.has(node_name)); - Node *node = state.node_map[node_name]; - node_path = state.root->get_path_to(node); - } + if (is_bone) { + String path = state.root->get_path_to(skeleton); + path += ":" + node_name; + node_path = path; + } else { + ERR_CONTINUE(!state.node_map.has(node_name)); + Node *node = state.node_map[node_name]; + node_path = state.root->get_path_to(node); + } - _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, node_path, node_name); + _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, node_path, node_name); + } } //blend shape tracks @@ -677,7 +488,7 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim const aiMeshMorphAnim *anim_mesh = anim->mMorphMeshChannels[i]; - const String prop_name = _assimp_get_string(anim_mesh->mName); + const String prop_name = AssimpUtils::get_assimp_string(anim_mesh->mName); const String mesh_name = prop_name.split("*")[0]; ERR_CONTINUE(prop_name.split("*").size() != 2); @@ -717,514 +528,41 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim } } -float EditorSceneImporterAssimp::_get_fbx_fps(int32_t time_mode, const aiScene *p_scene) { - switch (time_mode) { - case AssetImportFbx::TIME_MODE_DEFAULT: return 24; //hack - case AssetImportFbx::TIME_MODE_120: return 120; - case AssetImportFbx::TIME_MODE_100: return 100; - case AssetImportFbx::TIME_MODE_60: return 60; - case AssetImportFbx::TIME_MODE_50: return 50; - case AssetImportFbx::TIME_MODE_48: return 48; - case AssetImportFbx::TIME_MODE_30: return 30; - case AssetImportFbx::TIME_MODE_30_DROP: return 30; - case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: return 29.9700262f; - case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: return 29.9700262f; - case AssetImportFbx::TIME_MODE_PAL: return 25; - case AssetImportFbx::TIME_MODE_CINEMA: return 24; - case AssetImportFbx::TIME_MODE_1000: return 1000; - case AssetImportFbx::TIME_MODE_CINEMA_ND: return 23.976f; - case AssetImportFbx::TIME_MODE_CUSTOM: - int32_t frame_rate; - p_scene->mMetaData->Get("FrameRate", frame_rate); - return frame_rate; - } - return 0; -} - -Transform EditorSceneImporterAssimp::_get_global_assimp_node_transform(const aiNode *p_current_node) { - aiNode const *current_node = p_current_node; - Transform xform; - while (current_node != NULL) { - xform = _assimp_matrix_transform(current_node->mTransformation) * xform; - current_node = current_node->mParent; - } - return xform; -} - -Ref<Texture> EditorSceneImporterAssimp::_load_texture(ImportState &state, String p_path) { - Vector<String> split_path = p_path.get_basename().split("*"); - if (split_path.size() == 2) { - size_t texture_idx = split_path[1].to_int(); - ERR_FAIL_COND_V(texture_idx >= state.assimp_scene->mNumTextures, Ref<Texture>()); - aiTexture *tex = state.assimp_scene->mTextures[texture_idx]; - String filename = _assimp_raw_string_to_string(tex->mFilename); - filename = filename.get_file(); - print_verbose("Open Asset Import: Loading embedded texture " + filename); - if (tex->mHeight == 0) { - if (tex->CheckFormat("png")) { - Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - - Ref<ImageTexture> t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; - } else if (tex->CheckFormat("jpg")) { - Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - Ref<ImageTexture> t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; - } else if (tex->CheckFormat("dds")) { - ERR_EXPLAIN("Open Asset Import: Embedded dds not implemented"); - ERR_FAIL_COND_V(true, Ref<Texture>()); - //Ref<Image> img = Image::_dds_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - //ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - //Ref<ImageTexture> t; - //t.instance(); - //t->create_from_image(img); - //t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - //return t; - } - } else { - Ref<Image> img; - img.instance(); - PoolByteArray arr; - uint32_t size = tex->mWidth * tex->mHeight; - arr.resize(size); - memcpy(arr.write().ptr(), tex->pcData, size); - ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Texture>()); - //ARGB8888 to RGBA8888 - for (int32_t i = 0; i < arr.size() / 4; i++) { - arr.write().ptr()[(4 * i) + 3] = arr[(4 * i) + 0]; - arr.write().ptr()[(4 * i) + 0] = arr[(4 * i) + 1]; - arr.write().ptr()[(4 * i) + 1] = arr[(4 * i) + 2]; - arr.write().ptr()[(4 * i) + 2] = arr[(4 * i) + 3]; - } - img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr); - ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - - Ref<ImageTexture> t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; - } - return Ref<Texture>(); - } - Ref<Texture> p_texture = ResourceLoader::load(p_path, "Texture"); - return p_texture; -} - -Ref<Material> EditorSceneImporterAssimp::_generate_material_from_index(ImportState &state, int p_index, bool p_double_sided) { - - ERR_FAIL_INDEX_V(p_index, (int)state.assimp_scene->mNumMaterials, Ref<Material>()); - - aiMaterial *ai_material = state.assimp_scene->mMaterials[p_index]; - Ref<SpatialMaterial> mat; - mat.instance(); - - int32_t mat_two_sided = 0; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_TWOSIDED, mat_two_sided)) { - if (mat_two_sided > 0) { - mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); - } - } - - //const String mesh_name = _assimp_get_string(ai_mesh->mName); - aiString mat_name; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_NAME, mat_name)) { - mat->set_name(_assimp_get_string(mat_name)); - } +// +// Mesh Generation from indices ? why do we need so much mesh code +// [debt needs looked into] +Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( + ImportState &state, + const Vector<int> &p_surface_indices, + const aiNode *assimp_node, + Skeleton *p_skeleton) { - aiTextureType tex_normal = aiTextureType_NORMALS; - { - aiString ai_filename = aiString(); - String filename = ""; - aiTextureMapMode map_mode[2]; - - if (AI_SUCCESS == ai_material->GetTexture(tex_normal, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { - filename = _assimp_raw_string_to_string(ai_filename); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - - if (texture.is_valid()) { - _set_texture_mapping_mode(map_mode, texture); - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); - } - } - } - } - - { - aiString ai_filename = aiString(); - String filename = ""; - - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_NORMAL_TEXTURE, ai_filename)) { - filename = _assimp_raw_string_to_string(ai_filename); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); - } - } - } - } - - aiTextureType tex_emissive = aiTextureType_EMISSIVE; - - if (ai_material->GetTextureCount(tex_emissive) > 0) { - - aiString ai_filename = aiString(); - String filename = ""; - aiTextureMapMode map_mode[2]; - - if (AI_SUCCESS == ai_material->GetTexture(tex_emissive, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { - filename = _assimp_raw_string_to_string(ai_filename); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - if (texture != NULL) { - _set_texture_mapping_mode(map_mode, texture); - mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true); - mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, texture); - } - } - } - } - - aiTextureType tex_albedo = aiTextureType_DIFFUSE; - if (ai_material->GetTextureCount(tex_albedo) > 0) { - - aiString ai_filename = aiString(); - String filename = ""; - aiTextureMapMode map_mode[2]; - if (AI_SUCCESS == ai_material->GetTexture(tex_albedo, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { - filename = _assimp_raw_string_to_string(ai_filename); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() != Image::ALPHA_NONE) { - _set_texture_mapping_mode(map_mode, texture); - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } - } else { - aiColor4D clr_diffuse; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, clr_diffuse)) { - if (Math::is_equal_approx(clr_diffuse.a, 1.0f) == false) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_albedo(Color(clr_diffuse.r, clr_diffuse.g, clr_diffuse.b, clr_diffuse.a)); - } - } - - aiString tex_gltf_base_color_path = aiString(); - aiTextureMapMode map_mode[2]; - if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, &tex_gltf_base_color_path, NULL, NULL, NULL, NULL, map_mode)) { - String filename = _assimp_raw_string_to_string(tex_gltf_base_color_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - _set_texture_mapping_mode(map_mode, texture); - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_base_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, pbr_base_color)) { - if (Math::is_equal_approx(pbr_base_color.a, 1.0f) == false) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); - } - } - { - aiString tex_fbx_pbs_base_color_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_base_color_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_base_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR, pbr_base_color)) { - mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); - } - } - - aiUVTransform pbr_base_color_uv_xform; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM, pbr_base_color_uv_xform)) { - mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f)); - mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f)); - } - } - - { - aiString tex_fbx_pbs_normal_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE, tex_fbx_pbs_normal_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_normal_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); - } - } - } - } - - if (p_double_sided) { - mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); - } - - { - aiString tex_fbx_stingray_normal_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE, tex_fbx_stingray_normal_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_stingray_normal_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); - } - } - } - } - - { - aiString tex_fbx_pbs_base_color_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_base_color_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_base_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR, pbr_base_color)) { - mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); - } - } - - aiUVTransform pbr_base_color_uv_xform; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM, pbr_base_color_uv_xform)) { - mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f)); - mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f)); - } - } - - { - aiString tex_fbx_pbs_emissive_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE, tex_fbx_pbs_emissive_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_emissive_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_emmissive_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR, pbr_emmissive_color)) { - mat->set_emission(Color(pbr_emmissive_color.r, pbr_emmissive_color.g, pbr_emmissive_color.b, pbr_emmissive_color.a)); - } - } - - real_t pbr_emission_intensity; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR, pbr_emission_intensity)) { - mat->set_emission_energy(pbr_emission_intensity); - } - } - - aiString tex_gltf_pbr_metallicroughness_path; - if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &tex_gltf_pbr_metallicroughness_path)) { - String filename = _assimp_raw_string_to_string(tex_gltf_pbr_metallicroughness_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); - mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE); - mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); - mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN); - } - } - } else { - float pbr_roughness = 0.0f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, pbr_roughness)) { - mat->set_roughness(pbr_roughness); - } - float pbr_metallic = 0.0f; - - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, pbr_metallic)) { - mat->set_metallic(pbr_metallic); - } - } - { - aiString tex_fbx_pbs_metallic_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE, tex_fbx_pbs_metallic_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_metallic_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); - mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); - } - } - } else { - float pbr_metallic = 0.0f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR, pbr_metallic)) { - mat->set_metallic(pbr_metallic); - } - } - - aiString tex_fbx_pbs_rough_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_rough_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); - mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); - } - } - } else { - float pbr_roughness = 0.04f; + Ref<ArrayMesh> mesh; + mesh.instance(); + bool has_uvs = false; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR, pbr_roughness)) { - mat->set_roughness(pbr_roughness); - } - } - } + Map<String, uint32_t> morph_mesh_string_lookup; - { - aiString tex_fbx_pbs_metallic_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE, tex_fbx_pbs_metallic_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_metallic_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); - mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); - } - } - } else { - float pbr_metallic = 0.0f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_FACTOR, pbr_metallic)) { - mat->set_metallic(pbr_metallic); - } - } + for (int i = 0; i < p_surface_indices.size(); i++) { + const unsigned int mesh_idx = p_surface_indices[0]; + const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_idx]; + for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) { - aiString tex_fbx_pbs_rough_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_rough_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); - mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); + String ai_anim_mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mAnimMeshes[j]->mName); + if (!morph_mesh_string_lookup.has(ai_anim_mesh_name)) { + morph_mesh_string_lookup.insert(ai_anim_mesh_name, j); + mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); + if (ai_anim_mesh_name.empty()) { + ai_anim_mesh_name = String("morph_") + itos(j); } - } - } else { - float pbr_roughness = 0.04f; - - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR, pbr_roughness)) { - mat->set_roughness(pbr_roughness); + mesh->add_blend_shape(ai_anim_mesh_name); } } } - return mat; -} - -Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, Skeleton *p_skeleton, bool p_double_sided_material) { - - Ref<ArrayMesh> mesh; - mesh.instance(); - bool has_uvs = false; - + // + // Process Vertex Weights + // for (int i = 0; i < p_surface_indices.size(); i++) { const unsigned int mesh_idx = p_surface_indices[i]; const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_idx]; @@ -1234,7 +572,7 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS if (p_skeleton) { for (size_t b = 0; b < ai_mesh->mNumBones; b++) { aiBone *bone = ai_mesh->mBones[b]; - String bone_name = _assimp_get_string(bone->mName); + String bone_name = AssimpUtils::get_assimp_string(bone->mName); int bone_index = p_skeleton->find_bone(bone_name); ERR_CONTINUE(bone_index == -1); //bone refers to an unexisting index, wtf. @@ -1247,7 +585,6 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS uint32_t vertex_index = ai_weights.mVertexId; bi.bone = bone_index; bi.weight = ai_weights.mWeight; - ; if (!vertex_weights.has(vertex_index)) { vertex_weights[vertex_index] = Vector<BoneInfo>(); @@ -1258,23 +595,34 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS } } + // + // Create mesh from data from assimp + // + Ref<SurfaceTool> st; st.instance(); st->begin(Mesh::PRIMITIVE_TRIANGLES); for (size_t j = 0; j < ai_mesh->mNumVertices; j++) { + + // Get the texture coordinates if they exist if (ai_mesh->HasTextureCoords(0)) { has_uvs = true; st->add_uv(Vector2(ai_mesh->mTextureCoords[0][j].x, 1.0f - ai_mesh->mTextureCoords[0][j].y)); } + if (ai_mesh->HasTextureCoords(1)) { has_uvs = true; st->add_uv2(Vector2(ai_mesh->mTextureCoords[1][j].x, 1.0f - ai_mesh->mTextureCoords[1][j].y)); } + + // Assign vertex colors if (ai_mesh->HasVertexColors(0)) { Color color = Color(ai_mesh->mColors[0]->r, ai_mesh->mColors[0]->g, ai_mesh->mColors[0]->b, ai_mesh->mColors[0]->a); st->add_color(color); } + + // Work out normal calculations? - this needs work it doesn't work properly on huestos if (ai_mesh->mNormals != NULL) { const aiVector3D normals = ai_mesh->mNormals[j]; const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z); @@ -1289,6 +637,7 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS } } + // We have vertex weights right? if (vertex_weights.has(j)) { Vector<BoneInfo> bone_info = vertex_weights[j]; @@ -1296,6 +645,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS bones.resize(bone_info.size()); Vector<float> weights; weights.resize(bone_info.size()); + + // todo? do we really need to loop over all bones? - assimp may have helper to find all influences on this vertex. for (int k = 0; k < bone_info.size(); k++) { bones.write[k] = bone_info[k].bone; weights.write[k] = bone_info[k].weight; @@ -1305,47 +656,239 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS st->add_weights(weights); } + // Assign vertex const aiVector3D pos = ai_mesh->mVertices[j]; + + // note we must include node offset transform as this is relative to world space not local space. Vector3 godot_pos = Vector3(pos.x, pos.y, pos.z); st->add_vertex(godot_pos); } + // fire replacement for face handling for (size_t j = 0; j < ai_mesh->mNumFaces; j++) { const aiFace face = ai_mesh->mFaces[j]; - ERR_CONTINUE(face.mNumIndices != 3); - Vector<size_t> order; - order.push_back(2); - order.push_back(1); - order.push_back(0); - for (int32_t k = 0; k < order.size(); k++) { - st->add_index(face.mIndices[order[k]]); + for (unsigned int k = 0; k < face.mNumIndices; k++) { + st->add_index(face.mIndices[k]); } } + if (ai_mesh->HasTangentsAndBitangents() == false && has_uvs) { st->generate_tangents(); } - Ref<Material> material; + aiMaterial *ai_material = state.assimp_scene->mMaterials[ai_mesh->mMaterialIndex]; + Ref<SpatialMaterial> mat; + mat.instance(); + + int32_t mat_two_sided = 0; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_TWOSIDED, mat_two_sided)) { + if (mat_two_sided > 0) { + mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); + } + } + + const String mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mName); + aiString mat_name; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_NAME, mat_name)) { + mat->set_name(AssimpUtils::get_assimp_string(mat_name)); + } + + // Culling handling for meshes + + // cull all back faces + mat->set_cull_mode(SpatialMaterial::CULL_BACK); + + // Now process materials + aiTextureType base_color = aiTextureType_BASE_COLOR; + { + String filename, path; + AssimpImageData image_data; + + if (AssimpUtils::GetAssimpTexture(state, ai_material, base_color, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + + // anything transparent must be culled + if (image_data.raw_image->detect_alpha() != Image::ALPHA_NONE) { + mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); + mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode + } + + mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, image_data.texture); + } + } + + aiTextureType tex_diffuse = aiTextureType_DIFFUSE; + { + String filename, path; + AssimpImageData image_data; + + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_diffuse, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + + // anything transparent must be culled + if (image_data.raw_image->detect_alpha() != Image::ALPHA_NONE) { + mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); + mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode + } + + mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, image_data.texture); + } + + aiColor4D clr_diffuse; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, clr_diffuse)) { + if (Math::is_equal_approx(clr_diffuse.a, 1.0f) == false) { + mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); + mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode + } + mat->set_albedo(Color(clr_diffuse.r, clr_diffuse.g, clr_diffuse.b, clr_diffuse.a)); + } + } + + aiTextureType tex_normal = aiTextureType_NORMALS; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_normal, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture); + } else { + aiString texture_path; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_NORMAL_TEXTURE, AI_PROPERTIES, texture_path)) { + if (AssimpUtils::CreateAssimpTexture(state, texture_path, filename, path, image_data)) { + mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture); + } + } + } + } + + aiTextureType tex_normal_camera = aiTextureType_NORMAL_CAMERA; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_normal_camera, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture); + } + } + + aiTextureType tex_emission_color = aiTextureType_EMISSION_COLOR; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_emission_color, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture); + } + } + + aiTextureType tex_metalness = aiTextureType_METALNESS; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_metalness, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, image_data.texture); + } + } + + aiTextureType tex_roughness = aiTextureType_DIFFUSE_ROUGHNESS; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_roughness, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, image_data.texture); + } + } + + aiTextureType tex_emissive = aiTextureType_EMISSIVE; + { + String filename = ""; + String path = ""; + Ref<Image> texture; + AssimpImageData image_data; + + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_emissive, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true); + mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, image_data.texture); + } else { + // Process emission textures + aiString texture_emissive_path; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_EMISSION_TEXTURE, AI_PROPERTIES, texture_emissive_path)) { + if (AssimpUtils::CreateAssimpTexture(state, texture_emissive_path, filename, path, image_data)) { + mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true); + mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, image_data.texture); + } + } else { + float pbr_emission = 0.0f; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_EMISSIVE_FACTOR, AI_NULL, pbr_emission)) { + mat->set_emission(Color(pbr_emission, pbr_emission, pbr_emission, 1.0f)); + } + } + } + } + + aiTextureType tex_specular = aiTextureType_SPECULAR; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_specular, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, image_data.texture); + } + } - if (!state.material_cache.has(ai_mesh->mMaterialIndex)) { - material = _generate_material_from_index(state, ai_mesh->mMaterialIndex, p_double_sided_material); + aiTextureType tex_ao_map = aiTextureType_AMBIENT_OCCLUSION; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_ao_map, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_feature(SpatialMaterial::FEATURE_AMBIENT_OCCLUSION, true); + mat->set_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION, image_data.texture); + } } Array array_mesh = st->commit_to_arrays(); Array morphs; morphs.resize(ai_mesh->mNumAnimMeshes); Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES; - Map<uint32_t, String> morph_mesh_idx_names; + for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) { - if (i == 0) { - //only do this the first time - String ai_anim_mesh_name = _assimp_get_string(ai_mesh->mAnimMeshes[j]->mName); - mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); - if (ai_anim_mesh_name.empty()) { - ai_anim_mesh_name = String("morph_") + itos(j); - } - mesh->add_blend_shape(ai_anim_mesh_name); + String ai_anim_mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mAnimMeshes[j]->mName); + + if (ai_anim_mesh_name.empty()) { + ai_anim_mesh_name = String("morph_") + itos(j); } Array array_copy; @@ -1366,12 +909,11 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS vertices.write()[l] = position; } PoolVector3Array new_vertices = array_copy[VisualServer::ARRAY_VERTEX].duplicate(true); - - for (int32_t l = 0; l < vertices.size(); l++) { + ERR_CONTINUE(vertices.size() != new_vertices.size()); + for (int32_t l = 0; l < new_vertices.size(); l++) { PoolVector3Array::Write w = new_vertices.write(); w[l] = vertices[l]; } - ERR_CONTINUE(vertices.size() != new_vertices.size()); array_copy[VisualServer::ARRAY_VERTEX] = new_vertices; } @@ -1385,7 +927,7 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS colors.write()[l] = color; } PoolColorArray new_colors = array_copy[VisualServer::ARRAY_COLOR].duplicate(true); - + ERR_CONTINUE(colors.size() != new_colors.size()); for (int32_t l = 0; l < colors.size(); l++) { PoolColorArray::Write w = new_colors.write(); w[l] = colors[l]; @@ -1397,12 +939,12 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS PoolVector3Array normals; normals.resize(num_vertices); for (size_t l = 0; l < num_vertices; l++) { - const aiVector3D ai_normal = ai_mesh->mAnimMeshes[i]->mNormals[l]; + const aiVector3D ai_normal = ai_mesh->mAnimMeshes[j]->mNormals[l]; Vector3 normal = Vector3(ai_normal.x, ai_normal.y, ai_normal.z); normals.write()[l] = normal; } PoolVector3Array new_normals = array_copy[VisualServer::ARRAY_NORMAL].duplicate(true); - + ERR_CONTINUE(normals.size() != new_normals.size()); for (int l = 0; l < normals.size(); l++) { PoolVector3Array::Write w = new_normals.write(); w[l] = normals[l]; @@ -1415,7 +957,7 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS tangents.resize(num_vertices); PoolColorArray::Write w = tangents.write(); for (size_t l = 0; l < num_vertices; l++) { - _calc_tangent_from_mesh(ai_mesh, j, l, l, w); + AssimpUtils::calc_tangent_from_mesh(ai_mesh, j, l, l, w); } PoolRealArray new_tangents = array_copy[VisualServer::ARRAY_TANGENT].duplicate(true); ERR_CONTINUE(new_tangents.size() != tangents.size() * 4); @@ -1425,340 +967,388 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS new_tangents.write()[l + 2] = tangents[l].b; new_tangents.write()[l + 3] = tangents[l].a; } - array_copy[VisualServer::ARRAY_TANGENT] = new_tangents; } morphs[j] = array_copy; } - mesh->add_surface_from_arrays(primitive, array_mesh, morphs); - mesh->surface_set_material(i, material); - mesh->surface_set_name(i, _assimp_get_string(ai_mesh->mName)); + mesh->surface_set_material(i, mat); + mesh->surface_set_name(i, AssimpUtils::get_assimp_string(ai_mesh->mName)); } return mesh; } -void EditorSceneImporterAssimp::_generate_node(ImportState &state, const aiNode *p_assimp_node, Node *p_parent) { - - Spatial *new_node = NULL; - String node_name = _assimp_get_string(p_assimp_node->mName); - Transform node_transform = _assimp_matrix_transform(p_assimp_node->mTransformation); +/* to be moved into assimp */ +aiBone *get_bone_by_name(const aiScene *scene, aiString bone_name) { + for (unsigned int mesh_id = 0; mesh_id < scene->mNumMeshes; ++mesh_id) { + aiMesh *mesh = scene->mMeshes[mesh_id]; - if (p_assimp_node->mNumMeshes > 0) { - /* MESH NODE */ - Ref<Mesh> mesh; - Skeleton *skeleton = NULL; - { + // iterate over all the bones on the mesh for this node only! + for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) { - //see if we have mesh cache for this. - Vector<int> surface_indices; - for (uint32_t i = 0; i < p_assimp_node->mNumMeshes; i++) { - int mesh_index = p_assimp_node->mMeshes[i]; - surface_indices.push_back(mesh_index); - - //take the chance and attempt to find the skeleton from the bones - if (!skeleton) { - aiMesh *ai_mesh = state.assimp_scene->mMeshes[p_assimp_node->mMeshes[i]]; - for (uint32_t j = 0; j < ai_mesh->mNumBones; j++) { - aiBone *bone = ai_mesh->mBones[j]; - String bone_name = _assimp_get_string(bone->mName); - if (state.bone_owners.has(bone_name)) { - skeleton = state.skeletons[state.bone_owners[bone_name]]; - break; - } - } - } - } - surface_indices.sort(); - String mesh_key; - for (int i = 0; i < surface_indices.size(); i++) { - if (i > 0) { - mesh_key += ":"; - } - mesh_key += itos(surface_indices[i]); + aiBone *bone = mesh->mBones[boneIndex]; + if (bone->mName == bone_name) { + printf("matched bone by name: %s\n", bone->mName.C_Str()); + return bone; } + } + } - if (!state.mesh_cache.has(mesh_key)) { - //adding cache - aiString cull_mode; //cull is on mesh, which is kind of stupid tbh - bool double_sided_material = false; - if (p_assimp_node->mMetaData) { - p_assimp_node->mMetaData->Get("Culling", cull_mode); - } - if (cull_mode.length != 0 && cull_mode == aiString("CullingOff")) { - double_sided_material = true; - } + return NULL; +} - mesh = _generate_mesh_from_surface_indices(state, surface_indices, skeleton, double_sided_material); - state.mesh_cache[mesh_key] = mesh; +/** + * Create a new mesh for the node supplied + */ +void EditorSceneImporterAssimp::create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *current_node, Node *parent_node, Transform node_transform) { + /* MESH NODE */ + Ref<Mesh> mesh; + Skeleton *skeleton = NULL; + // see if we have mesh cache for this. + Vector<int> surface_indices; + for (uint32_t i = 0; i < assimp_node->mNumMeshes; i++) { + int mesh_index = assimp_node->mMeshes[i]; + aiMesh *ai_mesh = state.assimp_scene->mMeshes[assimp_node->mMeshes[i]]; + + // Map<aiBone*, Skeleton*> // this is what we need + if (ai_mesh->mNumBones > 0) { + // we only need the first bone to retrieve the skeleton + const aiBone *first = ai_mesh->mBones[0]; + + ERR_FAIL_COND(first == NULL); + + Map<const aiBone *, Skeleton *>::Element *match = state.bone_to_skeleton_lookup.find(first); + if (match != NULL) { + skeleton = match->value(); + + if (skeleton == NULL) { + print_error("failed to find bone skeleton for bone: " + AssimpUtils::get_assimp_string(first->mName)); + } else { + print_verbose("successfully found skeleton for first bone on mesh, can properly handle animations now!"); + } + // I really need the skeleton and bone to be known as this is something flaky in model exporters. + ERR_FAIL_COND(skeleton == NULL); // should not happen if bone was successfully created in previous step. } - - mesh = state.mesh_cache[mesh_key]; } + surface_indices.push_back(mesh_index); + } - MeshInstance *mesh_node = memnew(MeshInstance); - if (skeleton) { - state.mesh_skeletons[mesh_node] = skeleton; + surface_indices.sort(); + String mesh_key; + for (int i = 0; i < surface_indices.size(); i++) { + if (i > 0) { + mesh_key += ":"; } - mesh_node->set_mesh(mesh); - new_node = mesh_node; - - } else if (state.light_cache.has(node_name)) { - - Light *light = NULL; - aiLight *ai_light = state.assimp_scene->mLights[state.light_cache[node_name]]; - ERR_FAIL_COND(!ai_light); + mesh_key += itos(surface_indices[i]); + } - if (ai_light->mType == aiLightSource_DIRECTIONAL) { - light = memnew(DirectionalLight); - Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); - dir.normalize(); - Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); - Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z); - up.normalize(); + if (!state.mesh_cache.has(mesh_key)) { + mesh = _generate_mesh_from_surface_indices(state, surface_indices, assimp_node, skeleton); + state.mesh_cache[mesh_key] = mesh; + } - Transform light_transform; - light_transform.set_look_at(pos, pos + dir, up); + //Transform transform = recursive_state.node_transform; - node_transform *= light_transform; + // we must unfortunately overwrite mesh and skeleton transform with armature data + if (skeleton != NULL) { + print_verbose("Applying mesh and skeleton to armature"); + // required for blender, maya etc + Map<Skeleton *, const Spatial *>::Element *match = state.armature_skeletons.find(skeleton); + node_transform = match->value()->get_transform(); + } - } else if (ai_light->mType == aiLightSource_POINT) { - light = memnew(OmniLight); - Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); - Transform xform; - xform.origin = pos; + MeshInstance *mesh_node = memnew(MeshInstance); + mesh = state.mesh_cache[mesh_key]; + mesh_node->set_mesh(mesh); - node_transform *= xform; + attach_new_node(state, + mesh_node, + assimp_node, + parent_node, + node_name, + node_transform); - light->set_transform(xform); + // set this once and for all + if (skeleton != NULL) { + // root must be informed of its new child + parent_node->add_child(skeleton); - //light->set_param(Light::PARAM_ATTENUATION, 1); - } else if (ai_light->mType == aiLightSource_SPOT) { - light = memnew(SpotLight); + // owner must be set after adding to tree + skeleton->set_owner(state.root); - Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); - dir.normalize(); - Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); - Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z); - up.normalize(); + skeleton->set_transform(node_transform); - Transform light_transform; - light_transform.set_look_at(pos, pos + dir, up); - node_transform *= light_transform; + // must be done after added to tree + mesh_node->set_skeleton_path(mesh_node->get_path_to(skeleton)); + } +} - //light->set_param(Light::PARAM_ATTENUATION, 0.0f); - } - ERR_FAIL_COND(light == NULL); - light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b)); - new_node = light; - } else if (state.camera_cache.has(node_name)) { +/** generate_mesh_phase_from_skeletal_mesh + * This must be executed after generate_nodes because the skeleton doesn't exist until that has completed the first pass + */ +void EditorSceneImporterAssimp::generate_mesh_phase_from_skeletal_mesh(ImportState &state) { + // prevent more than one skeleton existing per mesh + // * multiple root bones have this + // * this simply filters the node out if it has already been added then references the skeleton so we know the actual skeleton for this node + for (Map<const aiNode *, const Node *>::Element *key_value_pair = state.assimp_node_map.front(); key_value_pair; key_value_pair = key_value_pair->next()) { + const aiNode *assimp_node = key_value_pair->key(); + Node *current_node = (Node *)key_value_pair->value(); + Node *parent_node = current_node->get_parent(); - aiCamera *ai_camera = state.assimp_scene->mCameras[state.camera_cache[node_name]]; - ERR_FAIL_COND(!ai_camera); + ERR_CONTINUE(assimp_node == NULL); + ERR_CONTINUE(parent_node == NULL); - Camera *camera = memnew(Camera); + String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); + Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation); - float near = ai_camera->mClipPlaneNear; - if (Math::is_equal_approx(near, 0.0f)) { - near = 0.1f; + if (assimp_node->mNumMeshes > 0) { + create_mesh(state, assimp_node, node_name, current_node, parent_node, node_transform); } - camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar); + } +} - Vector3 pos = Vector3(ai_camera->mPosition.x, ai_camera->mPosition.y, ai_camera->mPosition.z); - Vector3 look_at = Vector3(ai_camera->mLookAt.y, ai_camera->mLookAt.x, ai_camera->mLookAt.z).normalized(); - Vector3 up = Vector3(ai_camera->mUp.x, ai_camera->mUp.y, ai_camera->mUp.z); +/** + * attach_new_node + * configures node, assigns parent node +**/ +void EditorSceneImporterAssimp::attach_new_node(ImportState &state, Spatial *new_node, const aiNode *node, Node *parent_node, String Name, Transform &transform) { + ERR_FAIL_COND(new_node == NULL); + ERR_FAIL_COND(node == NULL); + ERR_FAIL_COND(parent_node == NULL); + ERR_FAIL_COND(state.root == NULL); + + // assign properties to new godot note + new_node->set_name(Name); + new_node->set_transform(transform); + + // add element as child to parent + parent_node->add_child(new_node); + + // owner must be set after + new_node->set_owner(state.root); + + // cache node mapping results by name and then by aiNode* + state.node_map[Name] = new_node; + state.assimp_node_map[node] = new_node; +} +/** + * Create a light for the scene + * Automatically caches lights for lookup later + */ +void EditorSceneImporterAssimp::create_light(ImportState &state, RecursiveState &recursive_state) { + Light *light = NULL; + aiLight *ai_light = state.assimp_scene->mLights[state.light_cache[recursive_state.node_name]]; + ERR_FAIL_COND(!ai_light); + + if (ai_light->mType == aiLightSource_DIRECTIONAL) { + light = memnew(DirectionalLight); + Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); + dir.normalize(); + Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); + Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z); + up.normalize(); + + Transform light_transform; + light_transform.set_look_at(pos, pos + dir, up); + + recursive_state.node_transform *= light_transform; + + } else if (ai_light->mType == aiLightSource_POINT) { + light = memnew(OmniLight); + Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); Transform xform; - xform.set_look_at(pos, look_at, up); - - new_node = camera; - } else if (state.bone_owners.has(node_name)) { - - //have to actually put the skeleton somewhere, you know. - Skeleton *skeleton = state.skeletons[state.bone_owners[node_name]]; - if (skeleton->get_parent()) { - //a bone for a skeleton already added.. - //could go downwards here to add meshes children of skeleton bones - //but let's not support it for now. - return; - } - //restore rest poses to local, now that we know where the skeleton finally is - Transform skeleton_transform; - if (p_assimp_node->mParent) { - skeleton_transform = _get_global_assimp_node_transform(p_assimp_node->mParent); - } - for (int i = 0; i < skeleton->get_bone_count(); i++) { - Transform rest = skeleton_transform.affine_inverse() * skeleton->get_bone_rest(i); - skeleton->set_bone_rest(i, rest.affine_inverse()); - } + xform.origin = pos; - skeleton->localize_rests(); - node_name = "Skeleton"; //don't use the bone root name - node_transform = Transform(); //don't transform + recursive_state.node_transform *= xform; - new_node = skeleton; - } else { - //generic node - new_node = memnew(Spatial); - } + light->set_transform(xform); - { + //light->set_param(Light::PARAM_ATTENUATION, 1); + } else if (ai_light->mType == aiLightSource_SPOT) { + light = memnew(SpotLight); - new_node->set_name(node_name); - new_node->set_transform(node_transform); - p_parent->add_child(new_node); - new_node->set_owner(state.root); - } + Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); + dir.normalize(); + Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); + Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z); + up.normalize(); - state.node_map[node_name] = new_node; + Transform light_transform; + light_transform.set_look_at(pos, pos + dir, up); + recursive_state.node_transform *= light_transform; - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - _generate_node(state, p_assimp_node->mChildren[i], new_node); + //light->set_param(Light::PARAM_ATTENUATION, 0.0f); } -} + ERR_FAIL_COND(light == NULL); + + light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b)); + recursive_state.new_node = light; -void EditorSceneImporterAssimp::_calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w) { - const aiVector3D normals = ai_mesh->mAnimMeshes[i]->mNormals[tri_index]; - const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z); - const aiVector3D tangent = ai_mesh->mAnimMeshes[i]->mTangents[tri_index]; - const Vector3 godot_tangent = Vector3(tangent.x, tangent.y, tangent.z); - const aiVector3D bitangent = ai_mesh->mAnimMeshes[i]->mBitangents[tri_index]; - const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z); - float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f; - Color plane_tangent = Color(tangent.x, tangent.y, tangent.z, d); - w[index] = plane_tangent; + attach_new_node(state, + recursive_state.new_node, + recursive_state.assimp_node, + recursive_state.parent_node, + recursive_state.node_name, + recursive_state.node_transform); } -void EditorSceneImporterAssimp::_set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<Texture> texture) { - ERR_FAIL_COND(map_mode == NULL); - aiTextureMapMode tex_mode = aiTextureMapMode::aiTextureMapMode_Wrap; - //for (size_t i = 0; i < 3; i++) { - tex_mode = map_mode[0]; - //} - int32_t flags = Texture::FLAGS_DEFAULT; - if (tex_mode == aiTextureMapMode_Wrap) { - //Default - } else if (tex_mode == aiTextureMapMode_Clamp) { - flags = flags & ~Texture::FLAG_REPEAT; - } else if (tex_mode == aiTextureMapMode_Mirror) { - flags = flags | Texture::FLAG_MIRRORED_REPEAT; +/** + * Create camera for the scene + */ +void EditorSceneImporterAssimp::create_camera(ImportState &state, RecursiveState &recursive_state) { + aiCamera *ai_camera = state.assimp_scene->mCameras[state.camera_cache[recursive_state.node_name]]; + ERR_FAIL_COND(!ai_camera); + + Camera *camera = memnew(Camera); + + float near = ai_camera->mClipPlaneNear; + if (Math::is_equal_approx(near, 0.0f)) { + near = 0.1f; } - texture->set_flags(flags); -} + camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar); -void EditorSceneImporterAssimp::_find_texture_path(const String &r_p_path, String &r_path, bool &r_found) { + Vector3 pos = Vector3(ai_camera->mPosition.x, ai_camera->mPosition.y, ai_camera->mPosition.z); + Vector3 look_at = Vector3(ai_camera->mLookAt.y, ai_camera->mLookAt.x, ai_camera->mLookAt.z).normalized(); + Vector3 up = Vector3(ai_camera->mUp.x, ai_camera->mUp.y, ai_camera->mUp.z); - _Directory dir; + Transform xform; + xform.set_look_at(pos, look_at, up); - List<String> exts; - ImageLoader::get_recognized_extensions(&exts); + recursive_state.new_node = camera; - Vector<String> split_path = r_path.get_basename().split("*"); - if (split_path.size() == 2) { - r_found = true; - return; - } + attach_new_node(state, + recursive_state.new_node, + recursive_state.assimp_node, + recursive_state.parent_node, + recursive_state.node_name, + recursive_state.node_transform); +} - if (dir.file_exists(r_p_path.get_base_dir() + r_path.get_file())) { - r_path = r_p_path.get_base_dir() + r_path.get_file(); - r_found = true; - return; - } +/** + * Create Bone + * Create a bone in the scene + */ +void EditorSceneImporterAssimp::create_bone(ImportState &state, RecursiveState &recursive_state) { + // for each armature node we must make a new skeleton but ensure it + // has a bone in the child to ensure we don't make too many + // the reason you must do this is because a skeleton exists per mesh? + // and duplicate bone names are very bad for determining what is going on. + aiBone *parent_bone_assimp = get_bone_by_name(state.assimp_scene, recursive_state.assimp_node->mParent->mName); - for (int32_t i = 0; i < exts.size(); i++) { - if (r_found) { - return; - } - if (r_found == false) { - _find_texture_path(r_p_path, dir, r_path, r_found, "." + exts[i]); + // set to true when you want to use skeleton reference from cache. + bool do_not_create_armature = false; + + // prevent more than one skeleton existing per mesh + // * multiple root bones have this + // * this simply filters the node out if it has already been added then references the skeleton so we know the actual skeleton for this node + for (Map<Skeleton *, const Spatial *>::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) { + if (key_value_pair->value() == recursive_state.parent_node) { + // apply the skeleton for this mesh + recursive_state.skeleton = key_value_pair->key(); + + // force this off + do_not_create_armature = true; } } -} -void EditorSceneImporterAssimp::_find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension) { - String name = path.get_basename() + extension; - if (dir.file_exists(name)) { - found = true; - path = name; - return; - } - String name_ignore_sub_directory = p_path.get_base_dir().plus_file(path.get_file().get_basename()) + extension; - if (dir.file_exists(name_ignore_sub_directory)) { - found = true; - path = name_ignore_sub_directory; - return; - } + // check if parent was a bone + // if parent was not a bone this is the first bone. + // therefore parent is the 'armature'? + // also for multi root bone support make sure we don't already have the skeleton cached. + // if we do we must merge them - as this is all godot supports right now. + if (!parent_bone_assimp && recursive_state.skeleton == NULL && !do_not_create_armature) { + // create new skeleton on the root. + recursive_state.skeleton = memnew(Skeleton); - String name_find_texture_sub_directory = p_path.get_base_dir() + "/textures/" + path.get_file().get_basename() + extension; - if (dir.file_exists(name_find_texture_sub_directory)) { - found = true; - path = name_find_texture_sub_directory; - return; - } - String name_find_texture_upper_sub_directory = p_path.get_base_dir() + "/Textures/" + path.get_file().get_basename() + extension; - if (dir.file_exists(name_find_texture_upper_sub_directory)) { - found = true; - path = name_find_texture_upper_sub_directory; - return; - } - String name_find_texture_outside_sub_directory = p_path.get_base_dir() + "/../textures/" + path.get_file().get_basename() + extension; - if (dir.file_exists(name_find_texture_outside_sub_directory)) { - found = true; - path = name_find_texture_outside_sub_directory; - return; - } + ERR_FAIL_COND(state.root == NULL); + ERR_FAIL_COND(recursive_state.skeleton == NULL); - String name_find_upper_texture_outside_sub_directory = p_path.get_base_dir() + "/../Textures/" + path.get_file().get_basename() + extension; - if (dir.file_exists(name_find_upper_texture_outside_sub_directory)) { - found = true; - path = name_find_upper_texture_outside_sub_directory; - return; - } -} + print_verbose("Parent armature node is called " + recursive_state.parent_node->get_name()); + // store root node for this skeleton / used in animation playback and bone detection. -String EditorSceneImporterAssimp::_assimp_get_string(const aiString &p_string) const { - //convert an assimp String to a Godot String - String name; - name.parse_utf8(p_string.C_Str() /*,p_string.length*/); - if (name.find(":") != -1) { - String replaced_name = name.split(":")[1]; - print_verbose("Replacing " + name + " containing : with " + replaced_name); - name = replaced_name; + state.armature_skeletons.insert(recursive_state.skeleton, Object::cast_to<Spatial>(recursive_state.parent_node)); + + //skeleton->set_use_bones_in_world_transform(true); + print_verbose("Created new FBX skeleton for armature node"); } - name = name.replace(".", ""); //can break things, specially bone names + ERR_FAIL_COND_MSG(recursive_state.skeleton == NULL, "Mesh has invalid armature detection - report this"); - return name; -} + // this transform is a bone + recursive_state.skeleton->add_bone(recursive_state.node_name); + + ERR_FAIL_COND(recursive_state.skeleton == NULL); // serious bug we must now exit. + //ERR_FAIL_COND(recursive_state.skeleton->get_name() == ""); + print_verbose("Bone added to lookup: " + AssimpUtils::get_assimp_string(recursive_state.bone->mName)); + print_verbose("Skeleton attached to: " + recursive_state.skeleton->get_name()); + // make sure to write the bone lookup inverse so we can retrieve the mesh for this bone later + state.bone_to_skeleton_lookup.insert(recursive_state.bone, recursive_state.skeleton); -String EditorSceneImporterAssimp::_assimp_anim_string_to_string(const aiString &p_string) const { + Transform xform = AssimpUtils::assimp_matrix_transform(recursive_state.bone->mOffsetMatrix); + recursive_state.skeleton->set_bone_rest(recursive_state.skeleton->get_bone_count() - 1, xform.affine_inverse()); - String name; - name.parse_utf8(p_string.C_Str() /*,p_string.length*/); - if (name.find(":") != -1) { - String replaced_name = name.split(":")[1]; - print_verbose("Replacing " + name + " containing : with " + replaced_name); - name = replaced_name; + // get parent node of assimp node + const aiNode *parent_node_assimp = recursive_state.assimp_node->mParent; + + // ensure we have a parent + if (parent_node_assimp != NULL) { + int parent_bone_id = recursive_state.skeleton->find_bone(AssimpUtils::get_assimp_string(parent_node_assimp->mName)); + int current_bone_id = recursive_state.skeleton->find_bone(recursive_state.node_name); + print_verbose("Parent bone id " + itos(parent_bone_id) + " current bone id" + itos(current_bone_id)); + print_verbose("Bone debug: " + AssimpUtils::get_assimp_string(parent_node_assimp->mName)); + recursive_state.skeleton->set_bone_parent(current_bone_id, parent_bone_id); } - return name; } -String EditorSceneImporterAssimp::_assimp_raw_string_to_string(const aiString &p_string) const { - String name; - name.parse_utf8(p_string.C_Str() /*,p_string.length*/); - return name; -} +/** + * Generate node + * Recursive call to iterate over all nodes + */ +void EditorSceneImporterAssimp::_generate_node( + ImportState &state, + Skeleton *skeleton, + const aiNode *assimp_node, Node *parent_node) { + + // sanity check + ERR_FAIL_COND(state.root == NULL); + ERR_FAIL_COND(state.assimp_scene == NULL); + ERR_FAIL_COND(assimp_node == NULL); + ERR_FAIL_COND(parent_node == NULL); -Ref<Animation> EditorSceneImporterAssimp::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { - return Ref<Animation>(); -} + Spatial *new_node = NULL; + String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); + Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation); -const Transform EditorSceneImporterAssimp::_assimp_matrix_transform(const aiMatrix4x4 p_matrix) { - aiMatrix4x4 matrix = p_matrix; - Transform xform; - //xform.set(matrix.a1, matrix.b1, matrix.c1, matrix.a2, matrix.b2, matrix.c2, matrix.a3, matrix.b3, matrix.c3, matrix.a4, matrix.b4, matrix.c4); - xform.set(matrix.a1, matrix.a2, matrix.a3, matrix.b1, matrix.b2, matrix.b3, matrix.c1, matrix.c2, matrix.c3, matrix.a4, matrix.b4, matrix.c4); - return xform; -} + // can safely return null - is this node a bone? + aiBone *bone = get_bone_by_name(state.assimp_scene, assimp_node->mName); + + // out arguments helper - for pushing state down into creation functions + RecursiveState recursive_state(node_transform, skeleton, new_node, node_name, assimp_node, parent_node, bone); + + // Creation code + if (state.light_cache.has(node_name)) { + create_light(state, recursive_state); + } else if (state.camera_cache.has(node_name)) { + create_camera(state, recursive_state); + } else if (bone != NULL) { + create_bone(state, recursive_state); + } else { + //generic node + recursive_state.new_node = memnew(Spatial); + attach_new_node(state, + recursive_state.new_node, + recursive_state.assimp_node, + recursive_state.parent_node, + recursive_state.node_name, + recursive_state.node_transform); + } + + // recurse into all child elements + for (size_t i = 0; i < recursive_state.assimp_node->mNumChildren; i++) { + _generate_node(state, recursive_state.skeleton, recursive_state.assimp_node->mChildren[i], + recursive_state.new_node != NULL ? recursive_state.new_node : recursive_state.parent_node); + } +}
\ No newline at end of file diff --git a/modules/assimp/editor_scene_importer_assimp.h b/modules/assimp/editor_scene_importer_assimp.h index 7a30816e3b..787376c9af 100644 --- a/modules/assimp/editor_scene_importer_assimp.h +++ b/modules/assimp/editor_scene_importer_assimp.h @@ -44,60 +44,31 @@ #include "scene/resources/animation.h" #include "scene/resources/surface_tool.h" -#include "assimp/DefaultLogger.hpp" -#include "assimp/LogStream.hpp" -#include "assimp/Logger.hpp" -#include "assimp/matrix4x4.h" -#include "assimp/scene.h" -#include "assimp/types.h" +#include <assimp/matrix4x4.h> +#include <assimp/scene.h> +#include <assimp/types.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/LogStream.hpp> +#include <assimp/Logger.hpp> + +#include "import_state.h" +#include "import_utils.h" + +using namespace AssimpImporter; class AssimpStream : public Assimp::LogStream { public: // Constructor - AssimpStream(); + AssimpStream() {} // Destructor - ~AssimpStream(); + ~AssimpStream() {} // Write something using your own functionality - void write(const char *message); + void write(const char *message) { + print_verbose(String("Open Asset Import: ") + String(message).strip_edges()); + } }; -#define AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR "$raw.Maya|baseColor", 0, 0 -#define AI_MATKEY_FBX_MAYA_METALNESS_FACTOR "$raw.Maya|metalness", 0, 0 -#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR "$raw.Maya|diffuseRoughness", 0, 0 - -#define AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE "$raw.Maya|metalness|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_METALNESS_UV_XFORM "$raw.Maya|metalness|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE "$raw.Maya|diffuseRoughness|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_UV_XFORM "$raw.Maya|diffuseRoughness|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE "$raw.Maya|baseColor|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM "$raw.Maya|baseColor|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE "$raw.Maya|normalCamera|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo", aiTextureType_UNKNOWN, 0 - -#define AI_MATKEY_FBX_NORMAL_TEXTURE "$raw.Maya|normalCamera|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo", aiTextureType_UNKNOWN, 0 - -#define AI_MATKEY_FBX_MAYA_STINGRAY_DISPLACEMENT_SCALING_FACTOR "$raw.Maya|displacementscaling", 0, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR "$raw.Maya|base_color", 0, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR "$raw.Maya|emissive", 0, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR "$raw.Maya|metallic", 0, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR "$raw.Maya|roughness", 0, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR "$raw.Maya|emissive_intensity", 0, 0 - -#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE "$raw.Maya|TEX_normal_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_UV_XFORM "$raw.Maya|TEX_normal_map|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE "$raw.Maya|TEX_color_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM "$raw.Maya|TEX_color_map|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE "$raw.Maya|TEX_metallic_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_UV_XFORM "$raw.Maya|TEX_metallic_map|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE "$raw.Maya|TEX_roughness_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_UV_XFORM "$raw.Maya|TEX_roughness_map|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE "$raw.Maya|TEX_emissive_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_UV_XFORM "$raw.Maya|TEX_emissive_map|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_TEXTURE "$raw.Maya|TEX_ao_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_UV_XFORM "$raw.Maya|TEX_ao_map|uvtrafo", aiTextureType_UNKNOWN, 0 - class EditorSceneImporterAssimp : public EditorSceneImporter { private: GDCLASS(EditorSceneImporterAssimp, EditorSceneImporter); @@ -112,59 +83,6 @@ private: }; }; - struct AssetImportFbx { - enum ETimeMode { - TIME_MODE_DEFAULT = 0, - TIME_MODE_120 = 1, - TIME_MODE_100 = 2, - TIME_MODE_60 = 3, - TIME_MODE_50 = 4, - TIME_MODE_48 = 5, - TIME_MODE_30 = 6, - TIME_MODE_30_DROP = 7, - TIME_MODE_NTSC_DROP_FRAME = 8, - TIME_MODE_NTSC_FULL_FRAME = 9, - TIME_MODE_PAL = 10, - TIME_MODE_CINEMA = 11, - TIME_MODE_1000 = 12, - TIME_MODE_CINEMA_ND = 13, - TIME_MODE_CUSTOM = 14, - TIME_MODE_TIME_MODE_COUNT = 15 - }; - enum UpAxis { - UP_VECTOR_AXIS_X = 1, - UP_VECTOR_AXIS_Y = 2, - UP_VECTOR_AXIS_Z = 3 - }; - enum FrontAxis { - FRONT_PARITY_EVEN = 1, - FRONT_PARITY_ODD = 2, - }; - - enum CoordAxis { - COORD_RIGHT = 0, - COORD_LEFT = 1 - }; - }; - - struct ImportState { - - String path; - const aiScene *assimp_scene; - uint32_t max_bone_weights; - Spatial *root; - Map<String, Ref<Mesh> > mesh_cache; - Map<int, Ref<Material> > material_cache; - Map<String, int> light_cache; - Map<String, int> camera_cache; - Vector<Skeleton *> skeletons; - Map<String, int> bone_owners; //maps bones to skeleton index owned by - Map<String, Node *> node_map; - Map<MeshInstance *, Skeleton *> mesh_skeletons; - bool fbx; //for some reason assimp does some things different for FBX - AnimationPlayer *animation_player; - }; - struct BoneInfo { uint32_t bone; float weight; @@ -177,28 +95,29 @@ private: const aiNode *node; }; - const Transform _assimp_matrix_transform(const aiMatrix4x4 p_matrix); - String _assimp_get_string(const aiString &p_string) const; - Transform _get_global_assimp_node_transform(const aiNode *p_current_node); - void _calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w); void _set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<Texture> texture); - void _find_texture_path(const String &p_path, String &path, bool &r_found); - void _find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension); - - Ref<Texture> _load_texture(ImportState &state, String p_path); - Ref<Material> _generate_material_from_index(ImportState &state, int p_index, bool p_double_sided); - Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, Skeleton *p_skeleton = NULL, bool p_double_sided_material = false); - void _generate_node(ImportState &state, const aiNode *p_assimp_node, Node *p_parent); - void _generate_bone_groups(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<String, Transform> &bind_xforms); - void _fill_node_relationships(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, int p_skeleton_id, Skeleton *p_skeleton, const String &p_parent_name, int &holecount, const Vector<SkeletonHole> &p_holes, const Map<String, Transform> &bind_xforms); - void _generate_skeletons(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, const Map<String, Transform> &bind_xforms); + Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, const aiNode *assimp_node, Skeleton *p_skeleton = NULL); + + // utility for node creation + void attach_new_node(ImportState &state, Spatial *new_node, const aiNode *node, Node *parent_node, String Name, Transform &transform); + // simple object creation functions + void create_light(ImportState &state, RecursiveState &recursive_state); + void create_camera(ImportState &state, RecursiveState &recursive_state); + void create_bone(ImportState &state, RecursiveState &recursive_state); + // non recursive - linear so must not use recursive arguments + void create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *current_node, Node *parent_node, Transform node_transform); + + // recursive node generator + void _generate_node(ImportState &state, Skeleton *skeleton, const aiNode *assimp_node, Node *parent_node); + // runs after _generate_node as it must then use pre-created godot skeleton. + void generate_mesh_phase_from_skeletal_mesh(ImportState &state); void _insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int p_track, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, Skeleton *p_skeleton, const NodePath &p_path, const String &p_name); void _import_animation(ImportState &state, int p_animation_index, int p_bake_fps); - Spatial *_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights); + Spatial *_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights); String _assimp_anim_string_to_string(const aiString &p_string) const; String _assimp_raw_string_to_string(const aiString &p_string) const; @@ -228,7 +147,7 @@ public: virtual void get_extensions(List<String> *r_extensions) const; virtual uint32_t get_import_flags() const; virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = NULL); - virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps); + Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path); }; #endif #endif diff --git a/modules/assimp/godot_update_assimp.sh b/modules/assimp/godot_update_assimp.sh index dcf1e6d4a2..ff8ff59e97 100644..100755 --- a/modules/assimp/godot_update_assimp.sh +++ b/modules/assimp/godot_update_assimp.sh @@ -254,8 +254,9 @@ rm -rf contrib/irrXML rm -rf contrib/Open3DGC rm -rf contrib/openddlparser rm -rf contrib/poly2tri -rm -rf contrib/rapidjson +#rm -rf contrib/rapidjson rm -rf contrib/unzip rm -rf contrib/zip rm -rf contrib/stb_image rm .travis* + diff --git a/modules/assimp/import_state.h b/modules/assimp/import_state.h new file mode 100644 index 0000000000..56d89ffea7 --- /dev/null +++ b/modules/assimp/import_state.h @@ -0,0 +1,115 @@ +/*************************************************************************/ +/* import_state.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_SCENE_IMPORT_STATE_H +#define EDITOR_SCENE_IMPORT_STATE_H + +#include "core/bind/core_bind.h" +#include "core/io/resource_importer.h" +#include "core/vector.h" +#include "editor/import/resource_importer_scene.h" +#include "editor/project_settings_editor.h" +#include "scene/3d/mesh_instance.h" +#include "scene/3d/skeleton.h" +#include "scene/3d/spatial.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/animation.h" +#include "scene/resources/surface_tool.h" + +#include <assimp/matrix4x4.h> +#include <assimp/scene.h> +#include <assimp/types.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/LogStream.hpp> +#include <assimp/Logger.hpp> + +namespace AssimpImporter { +/** Import state is for global scene import data + * This makes the code simpler and contains useful lookups. + */ +struct ImportState { + + String path; + const aiScene *assimp_scene; + uint32_t max_bone_weights; + + Spatial *root; + Map<String, Ref<Mesh> > mesh_cache; + Map<int, Ref<Material> > material_cache; + Map<String, int> light_cache; + Map<String, int> camera_cache; + //Vector<Skeleton *> skeletons; + Map<Skeleton *, const Spatial *> armature_skeletons; // maps skeletons based on their armature nodes. + Map<const aiBone *, Skeleton *> bone_to_skeleton_lookup; // maps bones back into their skeleton + // very useful for when you need to ask assimp for the bone mesh + Map<String, Node *> node_map; + Map<const aiNode *, const Node *> assimp_node_map; + Map<String, Ref<Image> > path_to_image_cache; + bool fbx; //for some reason assimp does some things different for FBX + AnimationPlayer *animation_player; +}; + +struct AssimpImageData { + Ref<Image> raw_image; + Ref<ImageTexture> texture; + aiTextureMapMode map_mode[2]; +}; + +/** Recursive state is used to push state into functions instead of specifying them + * This makes the code easier to handle too and add extra arguments without breaking things + */ +struct RecursiveState { + RecursiveState( + Transform &_node_transform, + Skeleton *_skeleton, + Spatial *_new_node, + const String &_node_name, + const aiNode *_assimp_node, + Node *_parent_node, + const aiBone *_bone) : + node_transform(_node_transform), + skeleton(_skeleton), + new_node(_new_node), + node_name(_node_name), + assimp_node(_assimp_node), + parent_node(_parent_node), + bone(_bone) {} + + Transform &node_transform; + Skeleton *skeleton; + Spatial *new_node; + const String &node_name; + const aiNode *assimp_node; + Node *parent_node; + const aiBone *bone; +}; +} // namespace AssimpImporter + +#endif // EDITOR_SCENE_IMPORT_STATE_H diff --git a/modules/assimp/import_utils.h b/modules/assimp/import_utils.h new file mode 100644 index 0000000000..4be76ade0f --- /dev/null +++ b/modules/assimp/import_utils.h @@ -0,0 +1,448 @@ +/*************************************************************************/ +/* import_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef IMPORT_UTILS_IMPORTER_ASSIMP_H +#define IMPORT_UTILS_IMPORTER_ASSIMP_H + +#include "core/io/image_loader.h" +#include "import_state.h" + +#include <assimp/SceneCombiner.h> +#include <assimp/cexport.h> +#include <assimp/cimport.h> +#include <assimp/matrix4x4.h> +#include <assimp/pbrmaterial.h> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <assimp/LogStream.hpp> +#include <assimp/Logger.hpp> +#include <string> + +using namespace AssimpImporter; + +#define AI_PROPERTIES aiTextureType_UNKNOWN, 0 +#define AI_NULL 0, 0 +#define AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR "$raw.Maya|baseColor" +#define AI_MATKEY_FBX_MAYA_METALNESS_FACTOR "$raw.Maya|metalness" +#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR "$raw.Maya|diffuseRoughness" + +#define AI_MATKEY_FBX_MAYA_EMISSION_TEXTURE "$raw.Maya|emissionColor|file" +#define AI_MATKEY_FBX_MAYA_EMISSIVE_FACTOR "$raw.Maya|emission" +#define AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE "$raw.Maya|metalness|file" +#define AI_MATKEY_FBX_MAYA_METALNESS_UV_XFORM "$raw.Maya|metalness|uvtrafo" +#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE "$raw.Maya|diffuseRoughness|file" +#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_UV_XFORM "$raw.Maya|diffuseRoughness|uvtrafo" +#define AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE "$raw.Maya|baseColor|file" +#define AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM "$raw.Maya|baseColor|uvtrafo" +#define AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE "$raw.Maya|normalCamera|file" +#define AI_MATKEY_FBX_MAYA_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo" + +#define AI_MATKEY_FBX_NORMAL_TEXTURE "$raw.Maya|normalCamera|file" +#define AI_MATKEY_FBX_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo" + +#define AI_MATKEY_FBX_MAYA_STINGRAY_DISPLACEMENT_SCALING_FACTOR "$raw.Maya|displacementscaling" +#define AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR "$raw.Maya|base_color" +#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR "$raw.Maya|emissive" +#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR "$raw.Maya|metallic" +#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR "$raw.Maya|roughness" +#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR "$raw.Maya|emissive_intensity" + +#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE "$raw.Maya|TEX_normal_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_UV_XFORM "$raw.Maya|TEX_normal_map|uvtrafo" +#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE "$raw.Maya|TEX_color_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM "$raw.Maya|TEX_color_map|uvtrafo" +#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE "$raw.Maya|TEX_metallic_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_UV_XFORM "$raw.Maya|TEX_metallic_map|uvtrafo" +#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE "$raw.Maya|TEX_roughness_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_UV_XFORM "$raw.Maya|TEX_roughness_map|uvtrafo" +#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE "$raw.Maya|TEX_emissive_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_UV_XFORM "$raw.Maya|TEX_emissive_map|uvtrafo" +#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_TEXTURE "$raw.Maya|TEX_ao_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_UV_XFORM "$raw.Maya|TEX_ao_map|uvtrafo" + +/** + * Assimp Utils + * Conversion tools / glue code to convert from assimp to godot +*/ +class AssimpUtils { +public: + /** + * calculate tangents for mesh data from assimp data + */ + static void calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w) { + const aiVector3D normals = ai_mesh->mAnimMeshes[i]->mNormals[tri_index]; + const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z); + const aiVector3D tangent = ai_mesh->mAnimMeshes[i]->mTangents[tri_index]; + const Vector3 godot_tangent = Vector3(tangent.x, tangent.y, tangent.z); + const aiVector3D bitangent = ai_mesh->mAnimMeshes[i]->mBitangents[tri_index]; + const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z); + float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f; + Color plane_tangent = Color(tangent.x, tangent.y, tangent.z, d); + w[index] = plane_tangent; + } + + struct AssetImportFbx { + enum ETimeMode { + TIME_MODE_DEFAULT = 0, + TIME_MODE_120 = 1, + TIME_MODE_100 = 2, + TIME_MODE_60 = 3, + TIME_MODE_50 = 4, + TIME_MODE_48 = 5, + TIME_MODE_30 = 6, + TIME_MODE_30_DROP = 7, + TIME_MODE_NTSC_DROP_FRAME = 8, + TIME_MODE_NTSC_FULL_FRAME = 9, + TIME_MODE_PAL = 10, + TIME_MODE_CINEMA = 11, + TIME_MODE_1000 = 12, + TIME_MODE_CINEMA_ND = 13, + TIME_MODE_CUSTOM = 14, + TIME_MODE_TIME_MODE_COUNT = 15 + }; + enum UpAxis { + UP_VECTOR_AXIS_X = 1, + UP_VECTOR_AXIS_Y = 2, + UP_VECTOR_AXIS_Z = 3 + }; + enum FrontAxis { + FRONT_PARITY_EVEN = 1, + FRONT_PARITY_ODD = 2, + }; + + enum CoordAxis { + COORD_RIGHT = 0, + COORD_LEFT = 1 + }; + }; + + /** Get assimp string + * automatically filters the string data + */ + static String get_assimp_string(const aiString &p_string) { + //convert an assimp String to a Godot String + String name; + name.parse_utf8(p_string.C_Str() /*,p_string.length*/); + if (name.find(":") != -1) { + String replaced_name = name.split(":")[1]; + print_verbose("Replacing " + name + " containing : with " + replaced_name); + name = replaced_name; + } + + return name; + } + + static String get_anim_string_from_assimp(const aiString &p_string) { + + String name; + name.parse_utf8(p_string.C_Str() /*,p_string.length*/); + if (name.find(":") != -1) { + String replaced_name = name.split(":")[1]; + print_verbose("Replacing " + name + " containing : with " + replaced_name); + name = replaced_name; + } + return name; + } + + /** + * No filter logic get_raw_string_from_assimp + * This just convers the aiString to a parsed utf8 string + * Without removing special chars etc + */ + static String get_raw_string_from_assimp(const aiString &p_string) { + String name; + name.parse_utf8(p_string.C_Str() /*,p_string.length*/); + return name; + } + + static Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { + return Ref<Animation>(); + } + + /** + * Converts aiMatrix4x4 to godot Transform + */ + static const Transform assimp_matrix_transform(const aiMatrix4x4 p_matrix) { + aiMatrix4x4 matrix = p_matrix; + Transform xform; + xform.set(matrix.a1, matrix.a2, matrix.a3, matrix.b1, matrix.b2, matrix.b3, matrix.c1, matrix.c2, matrix.c3, matrix.a4, matrix.b4, matrix.c4); + return xform; + } + + /** Get fbx fps for time mode meta data + */ + static float get_fbx_fps(int32_t time_mode, const aiScene *p_scene) { + switch (time_mode) { + case AssetImportFbx::TIME_MODE_DEFAULT: return 24; //hack + case AssetImportFbx::TIME_MODE_120: return 120; + case AssetImportFbx::TIME_MODE_100: return 100; + case AssetImportFbx::TIME_MODE_60: return 60; + case AssetImportFbx::TIME_MODE_50: return 50; + case AssetImportFbx::TIME_MODE_48: return 48; + case AssetImportFbx::TIME_MODE_30: return 30; + case AssetImportFbx::TIME_MODE_30_DROP: return 30; + case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: return 29.9700262f; + case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: return 29.9700262f; + case AssetImportFbx::TIME_MODE_PAL: return 25; + case AssetImportFbx::TIME_MODE_CINEMA: return 24; + case AssetImportFbx::TIME_MODE_1000: return 1000; + case AssetImportFbx::TIME_MODE_CINEMA_ND: return 23.976f; + case AssetImportFbx::TIME_MODE_CUSTOM: + int32_t frame_rate = -1; + p_scene->mMetaData->Get("FrameRate", frame_rate); + return frame_rate; + } + return 0; + } + + /** + * Get global transform for the current node - so we can use world space rather than + * local space coordinates + * useful if you need global - although recommend using local wherever possible over global + * as you could break fbx scaling :) + */ + static Transform _get_global_assimp_node_transform(const aiNode *p_current_node) { + aiNode const *current_node = p_current_node; + Transform xform; + while (current_node != NULL) { + xform = assimp_matrix_transform(current_node->mTransformation) * xform; + current_node = current_node->mParent; + } + return xform; + } + + /** + * Find hardcoded textures from assimp which could be in many different directories + */ + static void find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension) { + Vector<String> paths; + paths.push_back(path.get_basename() + extension); + paths.push_back(path + extension); + paths.push_back(path); + paths.push_back(p_path.get_base_dir().plus_file(path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file(path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file(path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file())); + for (int i = 0; i < paths.size(); i++) { + if (dir.file_exists(paths[i])) { + found = true; + path = paths[i]; + return; + } + } + } + + /** find the texture path for the supplied fbx path inside godot + * very simple lookup for subfolders etc for a texture which may or may not be in a directory + */ + static void find_texture_path(const String &r_p_path, String &r_path, bool &r_found) { + _Directory dir; + + List<String> exts; + ImageLoader::get_recognized_extensions(&exts); + + Vector<String> split_path = r_path.get_basename().split("*"); + if (split_path.size() == 2) { + r_found = true; + return; + } + + if (dir.file_exists(r_p_path.get_base_dir() + r_path.get_file())) { + r_path = r_p_path.get_base_dir() + r_path.get_file(); + r_found = true; + return; + } + + for (int32_t i = 0; i < exts.size(); i++) { + if (r_found) { + return; + } + if (r_found == false) { + find_texture_path(r_p_path, dir, r_path, r_found, "." + exts[i]); + } + } + } + + /** + * set_texture_mapping_mode + * Helper to check the mapping mode of the texture (repeat, clamp and mirror) + */ + static void set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<ImageTexture> texture) { + ERR_FAIL_COND(texture.is_null()); + ERR_FAIL_COND(map_mode == NULL); + aiTextureMapMode tex_mode = aiTextureMapMode::aiTextureMapMode_Wrap; + + tex_mode = map_mode[0]; + + int32_t flags = Texture::FLAGS_DEFAULT; + if (tex_mode == aiTextureMapMode_Wrap) { + //Default + } else if (tex_mode == aiTextureMapMode_Clamp) { + flags = flags & ~Texture::FLAG_REPEAT; + } else if (tex_mode == aiTextureMapMode_Mirror) { + flags = flags | Texture::FLAG_MIRRORED_REPEAT; + } + texture->set_flags(flags); + } + + /** + * Load or load from cache image :) + */ + static Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path) { + + Map<String, Ref<Image> >::Element *match = state.path_to_image_cache.find(p_path); + + // if our cache contains this image then don't bother + if (match) { + return match->get(); + } + + Vector<String> split_path = p_path.get_basename().split("*"); + if (split_path.size() == 2) { + size_t texture_idx = split_path[1].to_int(); + ERR_FAIL_COND_V(texture_idx >= p_scene->mNumTextures, Ref<Image>()); + aiTexture *tex = p_scene->mTextures[texture_idx]; + String filename = AssimpUtils::get_raw_string_from_assimp(tex->mFilename); + filename = filename.get_file(); + print_verbose("Open Asset Import: Loading embedded texture " + filename); + if (tex->mHeight == 0) { + if (tex->CheckFormat("png")) { + Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); + state.path_to_image_cache.insert(p_path, img); + return img; + } else if (tex->CheckFormat("jpg")) { + Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); + state.path_to_image_cache.insert(p_path, img); + return img; + } else if (tex->CheckFormat("dds")) { + ERR_EXPLAIN("Open Asset Import: Embedded dds not implemented"); + ERR_FAIL_COND_V(true, Ref<Image>()); + } + } else { + Ref<Image> img; + img.instance(); + PoolByteArray arr; + uint32_t size = tex->mWidth * tex->mHeight; + arr.resize(size); + memcpy(arr.write().ptr(), tex->pcData, size); + ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Image>()); + //ARGB8888 to RGBA8888 + for (int32_t i = 0; i < arr.size() / 4; i++) { + arr.write().ptr()[(4 * i) + 3] = arr[(4 * i) + 0]; + arr.write().ptr()[(4 * i) + 0] = arr[(4 * i) + 1]; + arr.write().ptr()[(4 * i) + 1] = arr[(4 * i) + 2]; + arr.write().ptr()[(4 * i) + 2] = arr[(4 * i) + 3]; + } + img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr); + ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); + state.path_to_image_cache.insert(p_path, img); + return img; + } + return Ref<Image>(); + } else { + Ref<Texture> texture = ResourceLoader::load(p_path); + Ref<Image> image = texture->get_data(); + state.path_to_image_cache.insert(p_path, image); + return image; + } + + return Ref<Image>(); + } + + /* create texture from assimp data, if found in path */ + static bool CreateAssimpTexture( + AssimpImporter::ImportState &state, + aiString texture_path, + String &filename, + String &path, + AssimpImageData &image_state) { + filename = get_raw_string_from_assimp(texture_path); + path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); + bool found = false; + find_texture_path(state.path, path, found); + if (found) { + image_state.raw_image = AssimpUtils::load_image(state, state.assimp_scene, path); + if (image_state.raw_image.is_valid()) { + image_state.texture.instance(); + image_state.texture->create_from_image(image_state.raw_image); + image_state.texture->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); + return true; + } + } + + return false; + } + /** GetAssimpTexture + * Designed to retrieve textures for you + */ + static bool GetAssimpTexture( + AssimpImporter::ImportState &state, + aiMaterial *ai_material, + aiTextureType texture_type, + String &filename, + String &path, + AssimpImageData &image_state) { + aiString ai_filename = aiString(); + if (AI_SUCCESS == ai_material->GetTexture(texture_type, 0, &ai_filename, NULL, NULL, NULL, NULL, image_state.map_mode)) { + return CreateAssimpTexture(state, ai_filename, filename, path, image_state); + } + + return false; + } +}; + +#endif // IMPORT_UTILS_IMPORTER_ASSIMP_H diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index 88732dff33..5a32fa1c2c 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -241,9 +241,9 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f, case BI_CMYKRLE8: case BI_CMYKRLE4: { // Stop parsing - ERR_EXPLAIN("Compressed BMP files are not supported: " + f->get_path()); + String bmp_path = f->get_path(); f->close(); - ERR_FAIL_V(ERR_UNAVAILABLE); + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "Compressed BMP files are not supported: " + bmp_path + "."); } break; } // Don't rely on sizeof(bmp_file_header) as structure padding diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp index 038001996d..e01928191a 100644 --- a/modules/bullet/bullet_physics_server.cpp +++ b/modules/bullet/bullet_physics_server.cpp @@ -1548,8 +1548,7 @@ void BulletPhysicsServer::free(RID p_rid) { bulletdelete(space); } else { - ERR_EXPLAIN("Invalid ID"); - ERR_FAIL(); + ERR_FAIL_MSG("Invalid ID."); } } diff --git a/modules/bullet/cone_twist_joint_bullet.cpp b/modules/bullet/cone_twist_joint_bullet.cpp index bc7fd52cf6..97b9a81f77 100644 --- a/modules/bullet/cone_twist_joint_bullet.cpp +++ b/modules/bullet/cone_twist_joint_bullet.cpp @@ -83,8 +83,7 @@ void ConeTwistJointBullet::set_param(PhysicsServer::ConeTwistJointParam p_param, coneConstraint->setLimit(coneConstraint->getSwingSpan1(), coneConstraint->getSwingSpan2(), coneConstraint->getTwistSpan(), coneConstraint->getLimitSoftness(), coneConstraint->getBiasFactor(), p_value); break; default: - ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The parameter " + itos(p_param) + " is deprecated."); break; } } @@ -102,8 +101,7 @@ real_t ConeTwistJointBullet::get_param(PhysicsServer::ConeTwistJointParam p_para case PhysicsServer::CONE_TWIST_JOINT_RELAXATION: return coneConstraint->getRelaxationFactor(); default: - ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The parameter " + itos(p_param) + " is deprecated."); return 0; } } diff --git a/modules/bullet/generic_6dof_joint_bullet.cpp b/modules/bullet/generic_6dof_joint_bullet.cpp index 0d2c46c579..4aae87c220 100644 --- a/modules/bullet/generic_6dof_joint_bullet.cpp +++ b/modules/bullet/generic_6dof_joint_bullet.cpp @@ -174,8 +174,7 @@ void Generic6DOFJointBullet::set_param(Vector3::Axis p_axis, PhysicsServer::G6DO sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_equilibriumPoint = p_value; break; default: - ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The parameter " + itos(p_param) + " is deprecated."); break; } } @@ -216,8 +215,7 @@ real_t Generic6DOFJointBullet::get_param(Vector3::Axis p_axis, PhysicsServer::G6 case PhysicsServer::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT: return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_equilibriumPoint; default: - ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The parameter " + itos(p_param) + " is deprecated."); return 0; } } @@ -255,8 +253,7 @@ void Generic6DOFJointBullet::set_flag(Vector3::Axis p_axis, PhysicsServer::G6DOF sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_enableSpring = p_value; break; default: - ERR_EXPLAIN("This flag " + itos(p_flag) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The flag " + itos(p_flag) + " is deprecated."); break; } } diff --git a/modules/bullet/hinge_joint_bullet.cpp b/modules/bullet/hinge_joint_bullet.cpp index b7e1e1a4c2..4d26e729db 100644 --- a/modules/bullet/hinge_joint_bullet.cpp +++ b/modules/bullet/hinge_joint_bullet.cpp @@ -117,8 +117,7 @@ void HingeJointBullet::set_param(PhysicsServer::HingeJointParam p_param, real_t hingeConstraint->setMaxMotorImpulse(p_value); break; default: - ERR_EXPLAIN("The HingeJoint parameter " + itos(p_param) + " is deprecated."); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The HingeJoint parameter " + itos(p_param) + " is deprecated."); break; } } @@ -143,8 +142,7 @@ real_t HingeJointBullet::get_param(PhysicsServer::HingeJointParam p_param) const case PhysicsServer::HINGE_JOINT_MOTOR_MAX_IMPULSE: return hingeConstraint->getMaxMotorImpulse(); default: - ERR_EXPLAIN("The HingeJoint parameter " + itos(p_param) + " is deprecated."); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The HingeJoint parameter " + itos(p_param) + " is deprecated."); return 0; } } diff --git a/modules/bullet/pin_joint_bullet.cpp b/modules/bullet/pin_joint_bullet.cpp index c9c4d1af7e..8d404e7f04 100644 --- a/modules/bullet/pin_joint_bullet.cpp +++ b/modules/bullet/pin_joint_bullet.cpp @@ -85,8 +85,7 @@ real_t PinJointBullet::get_param(PhysicsServer::PinJointParam p_param) const { case PhysicsServer::PIN_JOINT_IMPULSE_CLAMP: return p2pConstraint->m_setting.m_impulseClamp; default: - ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The parameter " + itos(p_param) + " is deprecated."); return 0; } } diff --git a/modules/bullet/shape_bullet.cpp b/modules/bullet/shape_bullet.cpp index f15bcec914..85f47c3bbb 100644 --- a/modules/bullet/shape_bullet.cpp +++ b/modules/bullet/shape_bullet.cpp @@ -505,8 +505,7 @@ void HeightMapShapeBullet::set_data(const Variant &p_data) { } } else { - ERR_EXPLAIN("Expected PoolRealArray or float Image."); - ERR_FAIL(); + ERR_FAIL_MSG("Expected PoolRealArray or float Image."); } ERR_FAIL_COND(l_width <= 0); diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index 9d632aaf83..d2b16b0fd1 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -581,10 +581,8 @@ void SpaceBullet::create_empty_world(bool p_create_soft_world) { } else { world_mem = malloc(sizeof(btDiscreteDynamicsWorld)); } - if (!world_mem) { - ERR_EXPLAIN("Out of memory"); - ERR_FAIL(); - } + + ERR_FAIL_COND_MSG(!world_mem, "Out of memory."); if (p_create_soft_world) { collisionConfiguration = bulletnew(GodotSoftCollisionConfiguration(static_cast<btDiscreteDynamicsWorld *>(world_mem))); @@ -1234,7 +1232,7 @@ bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTran ERR_FAIL_COND_V(shape_idx < 0 || shape_idx >= cs->getNumChildShapes(), false); if (cs->getChildShape(shape_idx)->isConvex()) { - if (RFP_convex_convex_test(kin_shape.shape, static_cast<const btConvexShape *>(cs->getChildShape(shape_idx)), otherObject, shape_idx, shape_transform, otherObject->getWorldTransform() * cs->getChildTransform(shape_idx), p_recover_movement_scale, r_delta_recover_movement, r_recover_result)) { + if (RFP_convex_convex_test(kin_shape.shape, static_cast<const btConvexShape *>(cs->getChildShape(shape_idx)), otherObject, kinIndex, shape_idx, shape_transform, otherObject->getWorldTransform() * cs->getChildTransform(shape_idx), p_recover_movement_scale, r_delta_recover_movement, r_recover_result)) { penetration = true; } @@ -1245,7 +1243,7 @@ bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTran } } } else if (otherObject->getCollisionShape()->isConvex()) { /// Execute GJK test against object shape - if (RFP_convex_convex_test(kin_shape.shape, static_cast<const btConvexShape *>(otherObject->getCollisionShape()), otherObject, 0, shape_transform, otherObject->getWorldTransform(), p_recover_movement_scale, r_delta_recover_movement, r_recover_result)) { + if (RFP_convex_convex_test(kin_shape.shape, static_cast<const btConvexShape *>(otherObject->getCollisionShape()), otherObject, kinIndex, 0, shape_transform, otherObject->getWorldTransform(), p_recover_movement_scale, r_delta_recover_movement, r_recover_result)) { penetration = true; } @@ -1261,7 +1259,7 @@ bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTran return penetration; } -bool SpaceBullet::RFP_convex_convex_test(const btConvexShape *p_shapeA, const btConvexShape *p_shapeB, btCollisionObject *p_objectB, int p_shapeId_B, const btTransform &p_transformA, const btTransform &p_transformB, btScalar p_recover_movement_scale, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result) { +bool SpaceBullet::RFP_convex_convex_test(const btConvexShape *p_shapeA, const btConvexShape *p_shapeB, btCollisionObject *p_objectB, int p_shapeId_A, int p_shapeId_B, const btTransform &p_transformA, const btTransform &p_transformB, btScalar p_recover_movement_scale, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result) { // Initialize GJK input btGjkPairDetector::ClosestPointInput gjk_input; @@ -1279,6 +1277,7 @@ bool SpaceBullet::RFP_convex_convex_test(const btConvexShape *p_shapeA, const bt if (r_recover_result) { if (result.m_distance < r_recover_result->penetration_distance) { r_recover_result->hasPenetration = true; + r_recover_result->local_shape_most_recovered = p_shapeId_A; r_recover_result->other_collision_object = p_objectB; r_recover_result->other_compound_shape_index = p_shapeId_B; r_recover_result->penetration_distance = result.m_distance; @@ -1314,6 +1313,7 @@ bool SpaceBullet::RFP_convex_world_test(const btConvexShape *p_shapeA, const btC if (r_recover_result) { if (contactPointResult.m_penetration_distance < r_recover_result->penetration_distance) { r_recover_result->hasPenetration = true; + r_recover_result->local_shape_most_recovered = p_shapeId_A; r_recover_result->other_collision_object = p_objectB; r_recover_result->other_compound_shape_index = p_shapeId_B; r_recover_result->penetration_distance = contactPointResult.m_penetration_distance; diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h index eb4a065e54..ecf8a2db9d 100644 --- a/modules/bullet/space_bullet.h +++ b/modules/bullet/space_bullet.h @@ -208,7 +208,7 @@ private: bool recover_from_penetration(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = NULL); /// This is an API that recover a kinematic object from penetration /// This allow only Convex Convex test and it always use GJK algorithm, With this API we don't benefit of Bullet special accelerated functions - bool RFP_convex_convex_test(const btConvexShape *p_shapeA, const btConvexShape *p_shapeB, btCollisionObject *p_objectB, int p_shapeId_B, const btTransform &p_transformA, const btTransform &p_transformB, btScalar p_recover_movement_scale, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = NULL); + bool RFP_convex_convex_test(const btConvexShape *p_shapeA, const btConvexShape *p_shapeB, btCollisionObject *p_objectB, int p_shapeId_A, int p_shapeId_B, const btTransform &p_transformA, const btTransform &p_transformB, btScalar p_recover_movement_scale, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = NULL); /// This is an API that recover a kinematic object from penetration /// Using this we leave Bullet to select the best algorithm, For example GJK in case we have Convex Convex, or a Bullet accelerated algorithm bool RFP_convex_world_test(const btConvexShape *p_shapeA, const btCollisionShape *p_shapeB, btCollisionObject *p_objectA, btCollisionObject *p_objectB, int p_shapeId_A, int p_shapeId_B, const btTransform &p_transformA, const btTransform &p_transformB, btScalar p_recover_movement_scale, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = NULL); diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp index fd0d36eddf..5a76f32977 100644 --- a/modules/csg/csg.cpp +++ b/modules/csg/csg.cpp @@ -114,7 +114,7 @@ void CSGBrush::_regen_face_aabbs() { faces.write[i].aabb.position = faces[i].vertices[0]; faces.write[i].aabb.expand_to(faces[i].vertices[1]); faces.write[i].aabb.expand_to(faces[i].vertices[2]); - faces.write[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision erros + faces.write[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision errors } } @@ -242,7 +242,7 @@ void CSGBrushOperation::BuildPoly::_clip_segment(const CSGBrush *p_brush, int p_ //check if edge and poly share a vertex, of so, assign it to segment_idx for (int i = 0; i < points.size(); i++) { for (int j = 0; j < 2; j++) { - if (Math::is_zero_approx(segment[j].distance_to(points[i].point))) { + if (segment[j] == points[i].point) { segment_idx[j] = i; inserted_points.push_back(i); break; @@ -310,7 +310,7 @@ void CSGBrushOperation::BuildPoly::_clip_segment(const CSGBrush *p_brush, int p_ Vector2 edgeseg[2] = { points[edges[i].points[0]].point, points[edges[i].points[1]].point }; Vector2 closest = Geometry::get_closest_point_to_segment_2d(segment[j], edgeseg); - if (Math::is_zero_approx(closest.distance_to(segment[j]))) { + if (closest == segment[j]) { //point rest of this edge res = closest; found = true; @@ -439,7 +439,7 @@ void CSGBrushOperation::BuildPoly::clip(const CSGBrush *p_brush, int p_face, Mes //transform A points to 2D - if (Math::is_zero_approx(segment[0].distance_to(segment[1]))) + if (segment[0] == segment[1]) return; //too small _clip_segment(p_brush, p_face, segment, mesh_merge, p_for_B); @@ -461,10 +461,10 @@ void CSGBrushOperation::_collision_callback(const CSGBrush *A, int p_face_a, Map { //check if either is a degenerate - if (Math::is_zero_approx(va[0].distance_to(va[1])) || Math::is_zero_approx(va[0].distance_to(va[2])) || Math::is_zero_approx(va[1].distance_to(va[2]))) + if (va[0] == va[1] || va[0] == va[2] || va[1] == va[2]) return; - if (Math::is_zero_approx(vb[0].distance_to(vb[1])) || Math::is_zero_approx(vb[0].distance_to(vb[2])) || Math::is_zero_approx(vb[1].distance_to(vb[2]))) + if (vb[0] == vb[1] || vb[0] == vb[2] || vb[1] == vb[2]) return; } diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp index 4628bd9a5b..0220dcae4a 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -108,8 +108,7 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, if (r_error) *r_error = ERR_FILE_CORRUPT; - ERR_EXPLAIN("Unable to open DDS texture file: " + p_path); - ERR_FAIL_COND_V(err != OK, RES()); + ERR_FAIL_COND_V_MSG(err != OK, RES(), "Unable to open DDS texture file: " + p_path + "."); uint32_t magic = f->get_32(); uint32_t hsize = f->get_32(); @@ -128,8 +127,7 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, if (magic != DDS_MAGIC || hsize != 124 || !(flags & DDSD_PIXELFORMAT) || !(flags & DDSD_CAPS)) { - ERR_EXPLAIN("Invalid or Unsupported DDS texture file: " + p_path); - ERR_FAIL_V(RES()); + ERR_FAIL_V_MSG(RES(), "Invalid or unsupported DDS texture file: " + p_path + "."); } /* uint32_t format_size = */ f->get_32(); @@ -218,9 +216,7 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, } else { printf("unrecognized fourcc %x format_flags: %x - rgbbits %i - red_mask %x green mask %x blue mask %x alpha mask %x\n", format_fourcc, format_flags, format_rgb_bits, format_red_mask, format_green_mask, format_blue_mask, format_alpha_mask); - ERR_EXPLAIN("Unrecognized or Unsupported color layout in DDS: " + p_path); - - ERR_FAIL_V(RES()); + ERR_FAIL_V_MSG(RES(), "Unrecognized or unsupported color layout in DDS: " + p_path + "."); } if (!(flags & DDSD_MIPMAPCOUNT)) diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp index a5a356ced2..44f69ca261 100644 --- a/modules/enet/networked_multiplayer_enet.cpp +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -543,10 +543,7 @@ Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer, int p_buffer if (target_peer != 0) { E = peer_map.find(ABS(target_peer)); - if (!E) { - ERR_EXPLAIN("Invalid Target Peer: " + itos(target_peer)); - ERR_FAIL_V(ERR_INVALID_PARAMETER); - } + ERR_FAIL_COND_V_MSG(!E, ERR_INVALID_PARAMETER, "Invalid target peer: " + itos(target_peer) + "."); } ENetPacket *packet = enet_packet_create(NULL, p_buffer_size + 8, packet_flags); @@ -794,11 +791,7 @@ int NetworkedMultiplayerENet::get_peer_port(int p_peer_id) const { void NetworkedMultiplayerENet::set_transfer_channel(int p_channel) { ERR_FAIL_COND(p_channel < -1 || p_channel >= channel_count); - - if (p_channel == SYSCH_CONFIG) { - ERR_EXPLAIN("Channel " + itos(SYSCH_CONFIG) + " is reserved"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(p_channel == SYSCH_CONFIG, "Channel " + itos(SYSCH_CONFIG) + " is reserved."); transfer_channel = p_channel; } diff --git a/modules/etc/texture_loader_pkm.cpp b/modules/etc/texture_loader_pkm.cpp index ff925480b8..dd61d816d4 100644 --- a/modules/etc/texture_loader_pkm.cpp +++ b/modules/etc/texture_loader_pkm.cpp @@ -56,17 +56,14 @@ RES ResourceFormatPKM::load(const String &p_path, const String &p_original_path, if (r_error) *r_error = ERR_FILE_CORRUPT; - ERR_EXPLAIN("Unable to open PKM texture file: " + p_path); - ERR_FAIL_COND_V(err != OK, RES()); + ERR_FAIL_COND_V_MSG(err != OK, RES(), "Unable to open PKM texture file: " + p_path + "."); // big endian f->set_endian_swap(true); ETC1Header h; - ERR_EXPLAIN("Invalid or Unsupported PKM texture file: " + p_path); f->get_buffer((uint8_t *)&h.tag, sizeof(h.tag)); - if (strncmp(h.tag, "PKM 10", sizeof(h.tag))) - ERR_FAIL_V(RES()); + ERR_FAIL_COND_V_MSG(strncmp(h.tag, "PKM 10", sizeof(h.tag)), RES(), "Invalid or unsupported PKM texture file: " + p_path + "."); h.format = f->get_16(); h.texWidth = f->get_16(); diff --git a/modules/gdnative/arvr/arvr_interface_gdnative.cpp b/modules/gdnative/arvr/arvr_interface_gdnative.cpp index 64e2c362b2..bd6eb575d0 100644 --- a/modules/gdnative/arvr/arvr_interface_gdnative.cpp +++ b/modules/gdnative/arvr/arvr_interface_gdnative.cpp @@ -245,8 +245,7 @@ extern "C" { void GDAPI godot_arvr_register_interface(const godot_arvr_interface_gdnative *p_interface) { // If our major version is 0 or bigger then 10, we're likely looking at our constructor pointer from an older plugin - ERR_EXPLAINC("GDNative ARVR interfaces build for Godot 3.0 are not supported"); - ERR_FAIL_COND((p_interface->version.major == 0) || (p_interface->version.major > 10)); + ERR_FAIL_COND_MSG((p_interface->version.major == 0) || (p_interface->version.major > 10), "GDNative ARVR interfaces build for Godot 3.0 are not supported."); Ref<ARVRInterfaceGDNative> new_interface; new_interface.instance(); diff --git a/modules/gdnative/doc_classes/NativeScript.xml b/modules/gdnative/doc_classes/NativeScript.xml index e34e209374..dc735546e3 100644 --- a/modules/gdnative/doc_classes/NativeScript.xml +++ b/modules/gdnative/doc_classes/NativeScript.xml @@ -42,7 +42,7 @@ </description> </method> <method name="new" qualifiers="vararg"> - <return type="Object"> + <return type="Variant"> </return> <description> Constructs a new object of the base type with a script of this type already attached. diff --git a/modules/gdnative/doc_classes/PluginScript.xml b/modules/gdnative/doc_classes/PluginScript.xml index b07122bbdf..33b5f02bd4 100644 --- a/modules/gdnative/doc_classes/PluginScript.xml +++ b/modules/gdnative/doc_classes/PluginScript.xml @@ -8,7 +8,7 @@ </tutorials> <methods> <method name="new" qualifiers="vararg"> - <return type="Object"> + <return type="Variant"> </return> <description> Returns a new instance of the script. diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index 4eb9a2a0a3..783ad4e147 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -268,8 +268,7 @@ void GDNative::_bind_methods() { } void GDNative::set_library(Ref<GDNativeLibrary> p_library) { - ERR_EXPLAIN("Tried to change library of GDNative when it is already set"); - ERR_FAIL_COND(library.is_valid()); + ERR_FAIL_COND_MSG(library.is_valid(), "Tried to change library of GDNative when it is already set."); library = p_library; } diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 8dcea26a7b..03258584ce 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -44,7 +44,7 @@ ["const godot_vector2 *", "p_to"], ["const godot_real", "p_delta"] ] - }, + }, { "name": "godot_string_count", "return_type": "godot_int", @@ -54,7 +54,7 @@ ["godot_int", "p_from"], ["godot_int", "p_to"] ] - }, + }, { "name": "godot_string_countn", "return_type": "godot_int", @@ -6548,24 +6548,24 @@ "name": "godot_net_bind_stream_peer", "return_type": "void", "arguments": [ - ["godot_object *", "p_obj"], - ["const godot_net_stream_peer *", "p_interface"] + ["godot_object *", "p_obj"], + ["const godot_net_stream_peer *", "p_interface"] ] }, { "name": "godot_net_bind_packet_peer", "return_type": "void", "arguments": [ - ["godot_object *", "p_obj"], - ["const godot_net_packet_peer *", "p_interface"] + ["godot_object *", "p_obj"], + ["const godot_net_packet_peer *", "p_interface"] ] }, { "name": "godot_net_bind_multiplayer_peer", "return_type": "void", "arguments": [ - ["godot_object *", "p_obj"], - ["const godot_net_multiplayer_peer *", "p_interface"] + ["godot_object *", "p_obj"], + ["const godot_net_multiplayer_peer *", "p_interface"] ] } ] diff --git a/modules/gdnative/gdnative_builders.py b/modules/gdnative/gdnative_builders.py index 7ab0e01108..20c1a2233c 100644 --- a/modules/gdnative/gdnative_builders.py +++ b/modules/gdnative/gdnative_builders.py @@ -185,7 +185,7 @@ def _build_gdnative_api_struct_source(api): 'extern const godot_gdnative_core_' + ('{0}_{1}_api_struct api_{0}_{1}'.format(core['version']['major'], core['version']['minor'])) + ' = {', '\tGDNATIVE_' + core['type'] + ',', '\t{' + str(core['version']['major']) + ', ' + str(core['version']['minor']) + '},', - '\t' + ('NULL' if not core['next'] else ('(const godot_gdnative_api_struct *)& api_{0}_{1}'.format(core['version']['major'], core['version']['minor']))) + ',' + '\t' + ('NULL' if not core['next'] else ('(const godot_gdnative_api_struct *)& api_{0}_{1}'.format(core['next']['version']['major'], core['next']['version']['minor']))) + ',' ] for funcdef in core['api']: diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index e2a69b1635..5d272a6cdc 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -66,10 +66,18 @@ void GDNativeLibraryEditor::_update_tree() { tree->clear(); TreeItem *root = tree->create_item(); - for (Map<String, NativePlatformConfig>::Element *E = platforms.front(); E; E = E->next()) { + PopupMenu *filter_list = filter->get_popup(); + String text = ""; + for (int i = 0; i < filter_list->get_item_count(); i++) { - if (showing_platform != E->key() && showing_platform != "All") + if (!filter_list->is_item_checked(i)) { continue; + } + Map<String, NativePlatformConfig>::Element *E = platforms.find(filter_list->get_item_metadata(i)); + if (!text.empty()) { + text += ", "; + } + text += E->get().name; TreeItem *platform = tree->create_item(root); platform->set_text(0, E->get().name); @@ -119,6 +127,7 @@ void GDNativeLibraryEditor::_update_tree() { platform->set_collapsed(collapsed_items.find(E->get().name) != NULL); } + filter->set_text(text); } void GDNativeLibraryEditor::_on_item_button(Object *item, int column, int id) { @@ -162,9 +171,10 @@ void GDNativeLibraryEditor::_on_dependencies_selected(const PoolStringArray &fil _set_target_value(file_dialog->get_meta("section"), file_dialog->get_meta("target"), files); } -void GDNativeLibraryEditor::_on_filter_selected(int id) { +void GDNativeLibraryEditor::_on_filter_selected(int index) { - showing_platform = filter->get_item_metadata(id); + PopupMenu *filter_list = filter->get_popup(); + filter_list->set_item_checked(index, !filter_list->is_item_checked(index)); _update_tree(); } @@ -265,8 +275,6 @@ void GDNativeLibraryEditor::_translate_to_config_file() { GDNativeLibraryEditor::GDNativeLibraryEditor() { - showing_platform = "All"; - { // Define platforms NativePlatformConfig platform_windows; platform_windows.name = "Windows"; @@ -336,20 +344,21 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { Label *label = memnew(Label); label->set_text(TTR("Platform:")); hbox->add_child(label); - filter = memnew(OptionButton); - hbox->add_child(filter); + filter = memnew(MenuButton); filter->set_h_size_flags(SIZE_EXPAND_FILL); + filter->set_text_align(filter->ALIGN_LEFT); + hbox->add_child(filter); + PopupMenu *filter_list = filter->get_popup(); + filter_list->set_hide_on_checkable_item_selection(false); int idx = 0; - filter->add_item(TTR("All"), idx); - filter->set_item_metadata(idx, "All"); - idx += 1; for (Map<String, NativePlatformConfig>::Element *E = platforms.front(); E; E = E->next()) { - filter->add_item(E->get().name, idx); - filter->set_item_metadata(idx, E->key()); + filter_list->add_check_item(E->get().name, idx); + filter_list->set_item_metadata(idx, E->key()); + filter_list->set_item_checked(idx, true); idx += 1; } - filter->connect("item_selected", this, "_on_filter_selected"); + filter_list->connect("index_pressed", this, "_on_filter_selected"); tree = memnew(Tree); container->add_child(tree); @@ -387,11 +396,9 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { void GDNativeLibraryEditorPlugin::edit(Object *p_node) { - if (Object::cast_to<GDNativeLibrary>(p_node)) { - library_editor->edit(Object::cast_to<GDNativeLibrary>(p_node)); - library_editor->show(); - } else - library_editor->hide(); + Ref<GDNativeLibrary> new_library = Object::cast_to<GDNativeLibrary>(p_node); + if (new_library.is_valid()) + library_editor->edit(new_library); } bool GDNativeLibraryEditorPlugin::handles(Object *p_node) const { diff --git a/modules/gdnative/gdnative_library_editor_plugin.h b/modules/gdnative/gdnative_library_editor_plugin.h index e7d50ba29f..8c1449f55a 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.h +++ b/modules/gdnative/gdnative_library_editor_plugin.h @@ -61,7 +61,7 @@ class GDNativeLibraryEditor : public Control { }; Tree *tree; - OptionButton *filter; + MenuButton *filter; EditorFileDialog *file_dialog; ConfirmationDialog *new_architecture_dialog; LineEdit *new_architecture_input; diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index 7f52f5736c..8a05b6cfa3 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -64,9 +64,9 @@ typedef enum { GODOT_PROPERTY_HINT_LAYERS_3D_RENDER, GODOT_PROPERTY_HINT_LAYERS_3D_PHYSICS, GODOT_PROPERTY_HINT_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," - GODOT_PROPERTY_HINT_DIR, ///< a directort path must be passed + GODOT_PROPERTY_HINT_DIR, ///< a directory path must be passed GODOT_PROPERTY_HINT_GLOBAL_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," - GODOT_PROPERTY_HINT_GLOBAL_DIR, ///< a directort path must be passed + GODOT_PROPERTY_HINT_GLOBAL_DIR, ///< a directory path must be passed GODOT_PROPERTY_HINT_RESOURCE_TYPE, ///< a resource object type GODOT_PROPERTY_HINT_MULTILINE_TEXT, ///< used for string properties that can contain multiple lines GODOT_PROPERTY_HINT_PLACEHOLDER_TEXT, ///< used to set a placeholder text for string properties diff --git a/modules/gdnative/nativescript/godot_nativescript.cpp b/modules/gdnative/nativescript/godot_nativescript.cpp index 863999d6d4..979e47f7b9 100644 --- a/modules/gdnative/nativescript/godot_nativescript.cpp +++ b/modules/gdnative/nativescript/godot_nativescript.cpp @@ -104,11 +104,7 @@ void GDAPI godot_nativescript_register_method(void *p_gdnative_handle, const cha String *s = (String *)p_gdnative_handle; Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); - - if (!E) { - ERR_EXPLAIN("Attempted to register method on non-existent class!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!E, "Attempted to register method on non-existent class."); NativeScriptDesc::Method method; method.method = p_method; @@ -123,11 +119,7 @@ void GDAPI godot_nativescript_register_property(void *p_gdnative_handle, const c String *s = (String *)p_gdnative_handle; Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); - - if (!E) { - ERR_EXPLAIN("Attempted to register method on non-existent class!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!E, "Attempted to register method on non-existent class."); NativeScriptDesc::Property property; property.default_value = *(Variant *)&p_attr->default_value; @@ -148,11 +140,7 @@ void GDAPI godot_nativescript_register_signal(void *p_gdnative_handle, const cha String *s = (String *)p_gdnative_handle; Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); - - if (!E) { - ERR_EXPLAIN("Attempted to register method on non-existent class!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!E, "Attempted to register method on non-existent class."); List<PropertyInfo> args; Vector<Variant> default_args; @@ -213,17 +201,10 @@ void GDAPI godot_nativescript_set_method_argument_information(void *p_gdnative_h String *s = (String *)p_gdnative_handle; Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); - - if (!E) { - ERR_EXPLAIN("Attempted to add argument information for a method on a non-existent class!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!E, "Attempted to add argument information for a method on a non-existent class."); Map<StringName, NativeScriptDesc::Method>::Element *method = E->get().methods.find(p_function_name); - if (!method) { - ERR_EXPLAIN("Attempted to add argument information to non-existent method!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!method, "Attempted to add argument information to non-existent method."); MethodInfo *method_information = &method->get().info; @@ -247,11 +228,7 @@ void GDAPI godot_nativescript_set_class_documentation(void *p_gdnative_handle, c String *s = (String *)p_gdnative_handle; Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); - - if (!E) { - ERR_EXPLAIN("Attempted to add documentation to a non-existent class!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!E, "Attempted to add documentation to a non-existent class."); E->get().documentation = *(String *)&p_documentation; } @@ -260,17 +237,10 @@ void GDAPI godot_nativescript_set_method_documentation(void *p_gdnative_handle, String *s = (String *)p_gdnative_handle; Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); - - if (!E) { - ERR_EXPLAIN("Attempted to add documentation to a method on a non-existent class!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!E, "Attempted to add documentation to a method on a non-existent class."); Map<StringName, NativeScriptDesc::Method>::Element *method = E->get().methods.find(p_function_name); - if (!method) { - ERR_EXPLAIN("Attempted to add documentatino to non-existent method!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!method, "Attempted to add documentation to non-existent method."); method->get().documentation = *(String *)&p_documentation; } @@ -279,17 +249,10 @@ void GDAPI godot_nativescript_set_property_documentation(void *p_gdnative_handle String *s = (String *)p_gdnative_handle; Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); - - if (!E) { - ERR_EXPLAIN("Attempted to add documentation to a property on a non-existent class!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!E, "Attempted to add documentation to a property on a non-existent class."); OrderedHashMap<StringName, NativeScriptDesc::Property>::Element property = E->get().properties.find(p_path); - if (!property) { - ERR_EXPLAIN("Attempted to add documentation to non-existent property!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!property, "Attempted to add documentation to non-existent property."); property.get().documentation = *(String *)&p_documentation; } @@ -298,17 +261,10 @@ void GDAPI godot_nativescript_set_signal_documentation(void *p_gdnative_handle, String *s = (String *)p_gdnative_handle; Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); - - if (!E) { - ERR_EXPLAIN("Attempted to add documentation to a signal on a non-existent class!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!E, "Attempted to add documentation to a signal on a non-existent class."); Map<StringName, NativeScriptDesc::Signal>::Element *signal = E->get().signals_.find(p_signal_name); - if (!signal) { - ERR_EXPLAIN("Attempted to add documentation to non-existent signal!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!signal, "Attempted to add documentation to non-existent signal."); signal->get().documentation = *(String *)&p_documentation; } @@ -325,11 +281,7 @@ void GDAPI godot_nativescript_set_type_tag(void *p_gdnative_handle, const char * String *s = (String *)p_gdnative_handle; Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); - - if (!E) { - ERR_EXPLAIN("Attempted to set type tag on a non-existent class!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!E, "Attempted to set type tag on a non-existent class."); E->get().type_tag = p_type_tag; } diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index f30c9da4c1..7c313c983f 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -79,7 +79,7 @@ void NativeScript::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "script_class_name"), "set_script_class_name", "get_script_class_name"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "script_class_icon_path", PROPERTY_HINT_FILE), "set_script_class_icon_path", "get_script_class_icon_path"); - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &NativeScript::_new, MethodInfo(Variant::OBJECT, "new")); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &NativeScript::_new, MethodInfo("new")); } #define NSL NativeScriptLanguage::get_singleton() @@ -402,10 +402,7 @@ void NativeScript::get_script_property_list(List<PropertyInfo> *p_list) const { String NativeScript::get_class_documentation() const { NativeScriptDesc *script_data = get_script_desc(); - if (!script_data) { - ERR_EXPLAIN("Attempt to get class documentation on invalid NativeScript"); - ERR_FAIL_V(""); - } + ERR_FAIL_COND_V_MSG(!script_data, "", "Attempt to get class documentation on invalid NativeScript."); return script_data->documentation; } @@ -413,10 +410,7 @@ String NativeScript::get_class_documentation() const { String NativeScript::get_method_documentation(const StringName &p_method) const { NativeScriptDesc *script_data = get_script_desc(); - if (!script_data) { - ERR_EXPLAIN("Attempt to get method documentation on invalid NativeScript"); - ERR_FAIL_V(""); - } + ERR_FAIL_COND_V_MSG(!script_data, "", "Attempt to get method documentation on invalid NativeScript."); while (script_data) { @@ -429,17 +423,13 @@ String NativeScript::get_method_documentation(const StringName &p_method) const script_data = script_data->base_data; } - ERR_EXPLAIN("Attempt to get method documentation for non-existent method"); - ERR_FAIL_V(""); + ERR_FAIL_V_MSG("", "Attempt to get method documentation for non-existent method."); } String NativeScript::get_signal_documentation(const StringName &p_signal_name) const { NativeScriptDesc *script_data = get_script_desc(); - if (!script_data) { - ERR_EXPLAIN("Attempt to get signal documentation on invalid NativeScript"); - ERR_FAIL_V(""); - } + ERR_FAIL_COND_V_MSG(!script_data, "", "Attempt to get signal documentation on invalid NativeScript."); while (script_data) { @@ -452,17 +442,13 @@ String NativeScript::get_signal_documentation(const StringName &p_signal_name) c script_data = script_data->base_data; } - ERR_EXPLAIN("Attempt to get signal documentation for non-existent signal"); - ERR_FAIL_V(""); + ERR_FAIL_V_MSG("", "Attempt to get signal documentation for non-existent signal."); } String NativeScript::get_property_documentation(const StringName &p_path) const { NativeScriptDesc *script_data = get_script_desc(); - if (!script_data) { - ERR_EXPLAIN("Attempt to get property documentation on invalid NativeScript"); - ERR_FAIL_V(""); - } + ERR_FAIL_COND_V_MSG(!script_data, "", "Attempt to get property documentation on invalid NativeScript."); while (script_data) { @@ -475,8 +461,7 @@ String NativeScript::get_property_documentation(const StringName &p_path) const script_data = script_data->base_data; } - ERR_EXPLAIN("Attempt to get property documentation for non-existent signal"); - ERR_FAIL_V(""); + ERR_FAIL_V_MSG("", "Attempt to get property documentation for non-existent signal."); } Variant NativeScript::_new(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { @@ -655,10 +640,7 @@ void NativeScriptInstance::get_property_list(List<PropertyInfo> *p_properties) c Variant res = *(Variant *)&result; godot_variant_destroy(&result); - if (res.get_type() != Variant::ARRAY) { - ERR_EXPLAIN("_get_property_list must return an array of dictionaries"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(res.get_type() != Variant::ARRAY, "_get_property_list must return an array of dictionaries."); Array arr = res; for (int i = 0; i < arr.size(); i++) { @@ -780,8 +762,7 @@ String NativeScriptInstance::to_string(bool *r_valid) { if (ret.get_type() != Variant::STRING) { if (r_valid) *r_valid = false; - ERR_EXPLAIN("Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String."); - ERR_FAIL_V(String()); + ERR_FAIL_V_MSG(String(), "Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String."); } if (r_valid) *r_valid = true; @@ -1344,10 +1325,7 @@ void NativeScriptLanguage::unregister_binding_functions(int p_idx) { void *NativeScriptLanguage::get_instance_binding_data(int p_idx, Object *p_object) { ERR_FAIL_INDEX_V(p_idx, binding_functions.size(), NULL); - if (!binding_functions[p_idx].first) { - ERR_EXPLAIN("Tried to get binding data for a nativescript binding that does not exist"); - ERR_FAIL_V(NULL); - } + ERR_FAIL_COND_V_MSG(!binding_functions[p_idx].first, NULL, "Tried to get binding data for a nativescript binding that does not exist."); Vector<void *> *binding_data = (Vector<void *> *)p_object->get_script_instance_binding(lang_idx); @@ -1499,8 +1477,7 @@ void NativeScriptLanguage::init_library(const Ref<GDNativeLibrary> &lib) { #endif // See if this library was "registered" already. const String &lib_path = lib->get_current_library_path(); - ERR_EXPLAIN(lib->get_name() + " does not have a library for the current platform"); - ERR_FAIL_COND(lib_path.length() == 0); + ERR_FAIL_COND_MSG(lib_path.length() == 0, lib->get_name() + " does not have a library for the current platform."); Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path); if (!E) { diff --git a/modules/gdnative/pluginscript/pluginscript_language.cpp b/modules/gdnative/pluginscript/pluginscript_language.cpp index 9de073fc8e..22898a73ce 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.cpp +++ b/modules/gdnative/pluginscript/pluginscript_language.cpp @@ -200,7 +200,7 @@ void PluginScriptLanguage::get_recognized_extensions(List<String> *p_extensions) } void PluginScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const { - // TODO: provid this statically in `godot_pluginscript_language_desc` ? + // TODO: provide this statically in `godot_pluginscript_language_desc` ? if (_desc.get_public_functions) { Array functions; _desc.get_public_functions(_data, (godot_array *)&functions); @@ -212,7 +212,7 @@ void PluginScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) c } void PluginScriptLanguage::get_public_constants(List<Pair<String, Variant> > *p_constants) const { - // TODO: provid this statically in `godot_pluginscript_language_desc` ? + // TODO: provide this statically in `godot_pluginscript_language_desc` ? if (_desc.get_public_constants) { Dictionary constants; _desc.get_public_constants(_data, (godot_dictionary *)&constants); diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp index 3ecb29404a..94d38e1be1 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.cpp +++ b/modules/gdnative/pluginscript/pluginscript_script.cpp @@ -35,16 +35,14 @@ #include "pluginscript_script.h" #ifdef DEBUG_ENABLED -#define __ASSERT_SCRIPT_REASON "Cannot retrieve pluginscript class for this script, is you code correct ?" -#define ASSERT_SCRIPT_VALID() \ - { \ - ERR_EXPLAIN(__ASSERT_SCRIPT_REASON); \ - ERR_FAIL_COND(!can_instance()); \ +#define __ASSERT_SCRIPT_REASON "Cannot retrieve PluginScript class for this script, is your code correct?" +#define ASSERT_SCRIPT_VALID() \ + { \ + ERR_FAIL_COND_MSG(!can_instance(), __ASSERT_SCRIPT_REASON); \ } -#define ASSERT_SCRIPT_VALID_V(ret) \ - { \ - ERR_EXPLAIN(__ASSERT_SCRIPT_REASON); \ - ERR_FAIL_COND_V(!can_instance(), ret); \ +#define ASSERT_SCRIPT_VALID_V(ret) \ + { \ + ERR_FAIL_COND_V_MSG(!can_instance(), ret, __ASSERT_SCRIPT_REASON); \ } #else #define ASSERT_SCRIPT_VALID() @@ -52,7 +50,7 @@ #endif void PluginScript::_bind_methods() { - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &PluginScript::_new, MethodInfo(Variant::OBJECT, "new")); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &PluginScript::_new, MethodInfo("new")); } PluginScriptInstance *PluginScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, Variant::CallError &r_error) { @@ -197,8 +195,7 @@ ScriptInstance *PluginScript::instance_create(Object *p_this) { // if (ScriptDebugger::get_singleton()) { // _language->debug_break_parse(get_path(), 0, msg); // } - ERR_EXPLAIN(msg); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, msg); } } @@ -272,8 +269,7 @@ Error PluginScript::reload(bool p_keep_state) { _ref_base_parent = res; } else { String name = *(StringName *)&manifest.name; - ERR_EXPLAIN(_path + ": Script '" + name + "' has an invalid parent '" + *base_name + "'."); - ERR_FAIL_V(ERR_PARSE_ERROR); + ERR_FAIL_V_MSG(ERR_PARSE_ERROR, _path + ": Script '" + name + "' has an invalid parent '" + *base_name + "'."); } } } @@ -420,8 +416,7 @@ Error PluginScript::load_source_code(const String &p_path) { String s; if (s.parse_utf8((const char *)w.ptr())) { - ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); - ERR_FAIL_V(ERR_INVALID_DATA); + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode."); } _source = s; diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index 6904154953..74e653ce43 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -8,4 +8,12 @@ env_gdscript = env_modules.Clone() env_gdscript.add_source_files(env.modules_sources, "*.cpp") if env['tools']: - env_gdscript.add_source_files(env.modules_sources, "./editor/*.cpp") + env_gdscript.add_source_files(env.modules_sources, "./editor/*.cpp") + + # Those two modules are required for the language server protocol + if env['module_jsonrpc_enabled'] and env['module_websocket_enabled']: + env_gdscript.add_source_files(env.modules_sources, "./language_server/*.cpp") + else: + # Using a define in the disabled case, to avoid having an extra define + # in regular builds where all modules are enabled. + env_gdscript.Append(CPPDEFINES=['GDSCRIPT_NO_LSP']) diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index f65f2a8935..4efa90fd86 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -322,6 +322,7 @@ <description> The natural exponential function. It raises the mathematical constant [b]e[/b] to the power of [code]s[/code] and returns it. [b]e[/b] has an approximate value of 2.71828. + For exponents to other bases use the method [method pow]. [codeblock] a = exp(2) # Approximately 7.39 [/codeblock] @@ -1114,7 +1115,11 @@ <argument index="1" name="step" type="float"> </argument> <description> - Snaps float value [code]s[/code] to a given [code]step[/code]. + Snaps float value [code]s[/code] to a given [code]step[/code]. This can also be used to round a floating point number to an arbitrary number of decimals. + [codeblock] + stepify(100, 32) # Returns 96 + stepify(3.14159, 0.01) # Returns 3.14 + [/codeblock] </description> </method> <method name="str" qualifiers="vararg"> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index d606a41fab..6f43361914 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -19,7 +19,7 @@ </description> </method> <method name="new" qualifiers="vararg"> - <return type="Object"> + <return type="Variant"> </return> <description> Returns a new instance of the script. diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index bc28f7009e..c8ec80c101 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -67,10 +67,7 @@ void GDScriptNativeClass::_bind_methods() { Variant GDScriptNativeClass::_new() { Object *o = instance(); - if (!o) { - ERR_EXPLAIN("Class type: '" + String(name) + "' is not instantiable."); - ERR_FAIL_V(Variant()); - } + ERR_FAIL_COND_V_MSG(!o, Variant(), "Class type: '" + String(name) + "' is not instantiable."); Reference *ref = Object::cast_to<Reference>(o); if (ref) { @@ -102,7 +99,7 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco #endif instance->owner->set_script_instance(instance); - /* STEP 2, INITIALIZE AND CONSRTUCT */ + /* STEP 2, INITIALIZE AND CONSTRUCT */ #ifndef NO_THREADS GDScriptLanguage::singleton->lock->lock(); @@ -158,8 +155,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Variant::CallErro } else { owner = memnew(Reference); //by default, no base means use reference } - ERR_EXPLAIN("Can't inherit from a virtual class"); - ERR_FAIL_COND_V(!owner, Variant()); + ERR_FAIL_COND_V_MSG(!owner, Variant(), "Can't inherit from a virtual class."); Reference *r = Object::cast_to<Reference>(owner); if (r) { @@ -326,8 +322,7 @@ ScriptInstance *GDScript::instance_create(Object *p_this) { if (ScriptDebugger::get_singleton()) { GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); } - ERR_EXPLAIN("Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type '" + p_this->get_class() + "'" + "."); } } @@ -648,10 +643,7 @@ Variant GDScript::call(const StringName &p_method, const Variant **p_args, int p Map<StringName, GDScriptFunction *>::Element *E = top->member_functions.find(p_method); if (E) { - if (!E->get()->is_static()) { - ERR_EXPLAIN("Can't call non-static function: '" + String(p_method) + "' in script."); - ERR_FAIL_V(Variant()); - } + ERR_FAIL_COND_V_MSG(!E->get()->is_static(), Variant(), "Can't call non-static function '" + String(p_method) + "' in script."); return E->get()->call(NULL, p_args, p_argcount, r_error); } @@ -718,7 +710,7 @@ void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { void GDScript::_bind_methods() { - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo(Variant::OBJECT, "new")); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new")); ClassDB::bind_method(D_METHOD("get_as_byte_code"), &GDScript::get_as_byte_code); } @@ -826,8 +818,7 @@ Error GDScript::load_source_code(const String &p_path) { String s; if (s.parse_utf8((const char *)w.ptr())) { - ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); - ERR_FAIL_V(ERR_INVALID_DATA); + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode."); } source = s; @@ -1079,11 +1070,8 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const Variant ret = const_cast<GDScriptFunction *>(E->get())->call(const_cast<GDScriptInstance *>(this), NULL, 0, err); if (err.error == Variant::CallError::CALL_OK) { - if (ret.get_type() != Variant::ARRAY) { + ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries."); - ERR_EXPLAIN("Wrong type for _get_property list, must be an array of dictionaries."); - ERR_FAIL(); - } Array arr = ret; for (int i = 0; i < arr.size(); i++) { @@ -1243,8 +1231,7 @@ String GDScriptInstance::to_string(bool *r_valid) { if (ret.get_type() != Variant::STRING) { if (r_valid) *r_valid = false; - ERR_EXPLAIN("Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String."); - ERR_FAIL_V(String()); + ERR_FAIL_V_MSG(String(), "Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String."); } if (r_valid) *r_valid = true; @@ -2057,8 +2044,7 @@ String GDScriptWarning::get_message() const { } break; case WARNING_MAX: break; // Can't happen, but silences warning } - ERR_EXPLAIN("Invalid GDScript warning code: " + get_name_from_code(code)); - ERR_FAIL_V(String()); + ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + "."); #undef CHECK_SYMBOLS } @@ -2110,8 +2096,7 @@ GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) } } - ERR_EXPLAIN("Invalid GDScript warning name: " + p_name); - ERR_FAIL_V(WARNING_MAX); + ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name); } #endif // DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 4c976bd2e0..dea2225e91 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1241,8 +1241,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: } break; default: { - ERR_EXPLAIN("Bug in bytecode compiler, unexpected operator #" + itos(on->op) + " in parse tree while parsing expression."); - ERR_FAIL_V(0); //unreachable code + ERR_FAIL_V_MSG(0, "Bug in bytecode compiler, unexpected operator #" + itos(on->op) + " in parse tree while parsing expression."); //unreachable code } break; } @@ -1255,8 +1254,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: //TYPE_TYPE, default: { - ERR_EXPLAIN("Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); - ERR_FAIL_V(-1); //unreachable code + ERR_FAIL_V_MSG(-1, "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); //unreachable code } break; } } @@ -1522,8 +1520,16 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo if (ret2 < 0) return ERR_PARSE_ERROR; + int message_ret = 0; + if (as->message) { + message_ret = _parse_expression(codegen, as->message, p_stack_level + 1, false); + if (message_ret < 0) + return ERR_PARSE_ERROR; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSERT); codegen.opcodes.push_back(ret2); + codegen.opcodes.push_back(message_ret); #endif } break; case GDScriptParser::Node::TYPE_BREAKPOINT: { @@ -2066,7 +2072,7 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa } instance->owner->set_script_instance(instance); - /* STEP 2, INITIALIZE AND CONSRTUCT */ + /* STEP 2, INITIALIZE AND CONSTRUCT */ Variant::CallError ce; p_script->initializer->call(instance, NULL, 0, ce); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 9f65a9fff1..9b3bf8ad5b 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -223,7 +223,7 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const _debug_parse_err_line = p_line; _debug_parse_err_file = p_file; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, false); + ScriptDebugger::get_singleton()->debug(this, false, true); return true; } else { return false; @@ -237,7 +237,8 @@ bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) _debug_parse_err_line = -1; _debug_parse_err_file = ""; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, p_allow_continue); + bool is_error_breakpoint = p_error != "Breakpoint"; + ScriptDebugger::get_singleton()->debug(this, p_allow_continue, is_error_breakpoint); return true; } else { return false; @@ -634,7 +635,7 @@ static GDScriptCompletionIdentifier _type_from_gdtype(const GDScriptDataType &p_ switch (p_gdtype.kind) { case GDScriptDataType::UNINITIALIZED: { - ERR_EXPLAIN("Uninitialized completion. Please report a bug."); + ERR_PRINT("Uninitialized completion. Please report a bug."); } break; case GDScriptDataType::BUILTIN: { ci.type.kind = GDScriptParser::DataType::BUILTIN; @@ -1936,9 +1937,18 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context Ref<GDScript> script = base_type.script_type; if (script.is_valid()) { if (!_static && !p_only_functions) { - for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); + if (p_context.base && p_context.base->get_script_instance()) { + List<PropertyInfo> members; + p_context.base->get_script_instance()->get_property_list(&members); + for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); + } + } else { + for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); + } } } if (!p_only_functions) { @@ -2826,6 +2836,16 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path ScriptCodeCompletionOption option(Variant::get_type_name((Variant::Type)i), ScriptCodeCompletionOption::KIND_CLASS); options.insert(option.display, option); } + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); + } } List<StringName> native_classes; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 42f349ffc0..bdeea9cef3 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -86,8 +86,7 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta o = o->_owner; } - ERR_EXPLAIN("GDScriptCompiler bug..."); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "GDScriptCompiler bug."); } break; case ADDR_TYPE_LOCAL_CONSTANT: { #ifdef DEBUG_ENABLED @@ -128,8 +127,7 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta } break; } - ERR_EXPLAIN("Bad Code! (Addressing Mode)"); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Bad code! (unknown addressing mode)."); return NULL; } @@ -433,6 +431,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a profile.frame_call_count++; } bool exit_ok = false; + bool yielded = false; #endif #ifdef DEBUG_ENABLED @@ -1325,6 +1324,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED exit_ok = true; + yielded = true; #endif OPCODE_BREAK; } @@ -1475,20 +1475,25 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ASSERT) { - CHECK_SPACE(2); + CHECK_SPACE(3); #ifdef DEBUG_ENABLED GET_VARIANT_PTR(test, 1); + GET_VARIANT_PTR(message, 2); bool result = test->booleanize(); if (!result) { - - err_text = "Assertion failed."; + const String &message_str = *message; + if (message_str.empty()) { + err_text = "Assertion failed."; + } else { + err_text = "Assertion failed: " + message_str; + } OPCODE_BREAK; } #endif - ip += 2; + ip += 3; } DISPATCH_OPCODE; @@ -1591,8 +1596,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time; } - bool yielded = retvalue.is_ref() && Object::cast_to<GDScriptFunctionState>(retvalue); - // Check if this is the last time the function is resuming from yield // Will be true if never yielded as well // When it's the last resume it will postpone the exit from stack, @@ -1834,8 +1837,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) { ERR_FAIL_COND_V(!function, Variant()); if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { #ifdef DEBUG_ENABLED - ERR_EXPLAIN("Resumed function '" + String(function->get_name()) + "()' after yield, but class instance is gone. At script: " + state.script->get_path() + ":" + itos(state.line)); - ERR_FAIL_V(Variant()); + ERR_FAIL_V_MSG(Variant(), "Resumed function '" + String(function->get_name()) + "()' after yield, but class instance is gone. At script: " + state.script->get_path() + ":" + itos(state.line)); #else return Variant(); #endif diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index f5f245b25f..97790e00bb 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -106,6 +106,7 @@ const char *GDScriptFunctions::get_func_name(Function p_func) { "typeof", "type_exists", "char", + "ord", "str", "print", "printt", @@ -349,8 +350,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ VALIDATE_ARG_COUNT(1); VALIDATE_ARG_NUM(0); r_ret = Math::step_decimals((double)*p_args[0]); - ERR_EXPLAIN("GDScript method 'decimals' is deprecated and has been renamed to 'step_decimals', please update your code accordingly."); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("GDScript method 'decimals' is deprecated and has been renamed to 'step_decimals', please update your code accordingly."); } break; case MATH_STEP_DECIMALS: { VALIDATE_ARG_COUNT(1); @@ -666,6 +666,33 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ CharType result[2] = { *p_args[0], 0 }; r_ret = String(result); } break; + case TEXT_ORD: { + + VALIDATE_ARG_COUNT(1); + + if (p_args[0]->get_type() != Variant::STRING) { + + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + r_ret = Variant(); + return; + } + + String str = p_args[0]->operator String(); + + if (str.length() != 1) { + + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + r_ret = RTR("Expected a string of length 1 (a character)."); + return; + } + + r_ret = str.get(0); + + } break; case TEXT_STR: { if (p_arg_count < 1) { r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; @@ -1508,6 +1535,7 @@ bool GDScriptFunctions::is_deterministic(Function p_func) { case TYPE_OF: case TYPE_EXISTS: case TEXT_CHAR: + case TEXT_ORD: case TEXT_STR: case COLOR8: case LEN: @@ -1850,6 +1878,13 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; + case TEXT_ORD: { + + MethodInfo mi("ord", PropertyInfo(Variant::STRING, "char")); + mi.return_val.type = Variant::INT; + return mi; + + } break; case TEXT_STR: { MethodInfo mi("str"); diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_functions.h index 8f7ba76d2c..9ea5dd46cf 100644 --- a/modules/gdscript/gdscript_functions.h +++ b/modules/gdscript/gdscript_functions.h @@ -97,6 +97,7 @@ public: TYPE_OF, TYPE_EXISTS, TEXT_CHAR, + TEXT_ORD, TEXT_STR, TEXT_PRINT, TEXT_PRINT_TABBED, diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index f006d50a83..8bb053b2bc 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -252,6 +252,16 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } } + // Check that the next token is not TK_CURSOR and if it is, the offset should be incremented. + int next_valid_offset = 1; + if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_CURSOR) { + next_valid_offset++; + // There is a chunk of the identifier that also needs to be ignored (not always there!) + if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_IDENTIFIER) { + next_valid_offset++; + } + } + if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { //subexpression () tokenizer->advance(); @@ -504,7 +514,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s Ref<GDScript> gds = res; if (gds.is_valid() && !gds->is_valid()) { - _set_error("Could not fully preload the script, possible cyclic reference or compilation error. Use 'load()' instead if a cyclic reference is intended."); + _set_error("Couldn't fully preload the script, possible cyclic reference or compilation error. Use \"load()\" instead if a cyclic reference is intended."); return NULL; } @@ -518,7 +528,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_YIELD) { if (!current_function) { - _set_error("yield() can only be used inside function blocks."); + _set_error("\"yield()\" can only be used inside function blocks."); return NULL; } @@ -526,7 +536,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - _set_error("Expected '(' after 'yield'"); + _set_error("Expected \"(\" after \"yield\"."); return NULL; } @@ -552,7 +562,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s yield->arguments.push_back(object); if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - _set_error("Expected ',' after first argument of 'yield'"); + _set_error("Expected \",\" after the first argument of \"yield\"."); return NULL; } @@ -578,7 +588,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s yield->arguments.push_back(signal); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' after second argument of 'yield'"); + _set_error("Expected \")\" after the second argument of \"yield\"."); return NULL; } @@ -592,7 +602,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } else if (tokenizer->get_token() == GDScriptTokenizer::TK_SELF) { if (p_static) { - _set_error("'self'' not allowed in static function or constant expression"); + _set_error("\"self\" isn't allowed in a static function or constant expression."); return NULL; } //constant defined by tokenizer @@ -613,7 +623,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s if (identifier == StringName()) { - _set_error("Built-in type constant or static function expected after '.'"); + _set_error("Built-in type constant or static function expected after \".\"."); return NULL; } if (!Variant::has_constant(bi_type, identifier)) { @@ -668,7 +678,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s expr = cn; } - } else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) { + } else if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) { // We check with is_token_literal, as this allows us to use match/sync/etc. as a name //function or constructor @@ -898,6 +908,10 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_IS && tokenizer->get_token(1) == GDScriptTokenizer::TK_BUILT_IN_TYPE) { // 'is' operator with built-in type + if (!expr) { + _set_error("Expected identifier before 'is' operator"); + return NULL; + } OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_IS_BUILTIN; op->arguments.push_back(expr); @@ -1136,10 +1150,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s return NULL; //nothing } - if (!expr) { - ERR_EXPLAIN("GDScriptParser bug, couldn't figure out what expression is..."); - ERR_FAIL_V(NULL); - } + ERR_FAIL_COND_V_MSG(!expr, NULL, "GDScriptParser bug, couldn't figure out what expression is."); /******************/ /* Parse Indexing */ @@ -1196,7 +1207,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s if (_get_completable_identifier(COMPLETION_INDEX, identifier)) { if (identifier == StringName()) { - identifier = "@temp"; //so it parses allright + identifier = "@temp"; //so it parses alright } completion_node = op; @@ -2308,7 +2319,7 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m // static type check if possible if (pattern_type.has_type && to_match_type.has_type) { if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) { - _set_error("Pattern type (" + pattern_type.to_string() + ") is not compatible with the type of the value to match (" + to_match_type.to_string() + ").", + _set_error("The pattern type (" + pattern_type.to_string() + ") isn't compatible with the type of the value to match (" + to_match_type.to_string() + ").", p_pattern->line); return; } @@ -2760,24 +2771,24 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { case GDScriptTokenizer::TK_CF_PASS: { if (tokenizer->get_token(1) != GDScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE && tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF) { - _set_error("Expected ';' or <NewLine>."); + _set_error("Expected \";\" or a line break."); return; } _mark_line_as_safe(tokenizer->get_token_line()); tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) { - // Ignore semicolon after 'pass' + // Ignore semicolon after 'pass'. tokenizer->advance(); } } break; case GDScriptTokenizer::TK_PR_VAR: { - //variale declaration and (eventual) initialization + // Variable declaration and (eventual) initialization. tokenizer->advance(); int var_line = tokenizer->get_token_line(); if (!tokenizer->is_token_literal(0, true)) { - _set_error("Expected identifier for local variable name."); + _set_error("Expected an identifier for the local variable name."); return; } StringName n = tokenizer->get_token_literal(); @@ -2785,7 +2796,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { if (current_function) { for (int i = 0; i < current_function->arguments.size(); i++) { if (n == current_function->arguments[i]) { - _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(current_function->line) + ")."); + _set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(current_function->line) + ")."); return; } } @@ -2793,7 +2804,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { BlockNode *check_block = p_block; while (check_block) { if (check_block->variables.has(n)) { - _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variables[n]->line) + ")."); + _set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(check_block->variables[n]->line) + ")."); return; } check_block = check_block->parent_block; @@ -2815,7 +2826,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { #endif tokenizer->advance(); } else if (!_parse_type(lv->datatype)) { - _set_error("Expected type for variable."); + _set_error("Expected a type for the variable."); return; } } @@ -2835,15 +2846,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { assigned = subexpr; } else { - ConstantNode *c = alloc_node<ConstantNode>(); - if (lv->datatype.has_type && lv->datatype.kind == DataType::BUILTIN) { - Variant::CallError err; - c->value = Variant::construct(lv->datatype.builtin_type, NULL, 0, err); - } else { - c->value = Variant(); - } - c->line = var_line; - assigned = c; + assigned = _get_default_value_for_type(lv->datatype, var_line); } lv->assign = assigned; //must be added later, to avoid self-referencing. @@ -2864,7 +2867,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { lv->assign = assigned; if (!_end_statement()) { - _set_error("Expected end of statement (var)"); + _set_error("Expected end of statement (\"var\")."); return; } @@ -2893,7 +2896,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { p_block->sub_blocks.push_back(cf_if->body); if (!_enter_indent_block(cf_if->body)) { - _set_error("Expected indented block after 'if'"); + _set_error("Expected an indented block after \"if\"."); p_block->end_line = tokenizer->get_token_line(); return; } @@ -2923,7 +2926,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { if (tab_level.back()->get() > indent_level) { - _set_error("Invalid indent"); + _set_error("Invalid indentation."); return; } @@ -2954,7 +2957,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { p_block->sub_blocks.push_back(cf_if->body); if (!_enter_indent_block(cf_if->body)) { - _set_error("Expected indented block after 'elif'"); + _set_error("Expected an indented block after \"elif\"."); p_block->end_line = tokenizer->get_token_line(); return; } @@ -2970,7 +2973,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) { if (tab_level.back()->get() > indent_level) { - _set_error("Invalid indent"); + _set_error("Invalid indentation."); return; } @@ -2980,7 +2983,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { p_block->sub_blocks.push_back(cf_if->body_else); if (!_enter_indent_block(cf_if->body_else)) { - _set_error("Expected indented block after 'else'"); + _set_error("Expected an indented block after \"else\"."); p_block->end_line = tokenizer->get_token_line(); return; } @@ -3025,7 +3028,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { p_block->sub_blocks.push_back(cf_while->body); if (!_enter_indent_block(cf_while->body)) { - _set_error("Expected indented block after 'while'"); + _set_error("Expected an indented block after \"while\"."); p_block->end_line = tokenizer->get_token_line(); return; } @@ -3044,7 +3047,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { if (!tokenizer->is_token_literal(0, true)) { - _set_error("identifier expected after 'for'"); + _set_error("Identifier expected after \"for\"."); } IdentifierNode *id = alloc_node<IdentifierNode>(); @@ -3053,7 +3056,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_IN) { - _set_error("'in' expected after identifier"); + _set_error("\"in\" expected after identifier."); return; } @@ -3143,7 +3146,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { p_block->sub_blocks.push_back(cf_for->body); if (!_enter_indent_block(cf_for->body)) { - _set_error("Expected indented block after 'for'"); + _set_error("Expected indented block after \"for\"."); p_block->end_line = tokenizer->get_token_line(); return; } @@ -3170,23 +3173,25 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { } break; case GDScriptTokenizer::TK_CF_CONTINUE: { + _mark_line_as_safe(tokenizer->get_token_line()); tokenizer->advance(); ControlFlowNode *cf_continue = alloc_node<ControlFlowNode>(); cf_continue->cf_type = ControlFlowNode::CF_CONTINUE; p_block->statements.push_back(cf_continue); if (!_end_statement()) { - _set_error("Expected end of statement (continue)"); + _set_error("Expected end of statement (\"continue\")."); return; } } break; case GDScriptTokenizer::TK_CF_BREAK: { + _mark_line_as_safe(tokenizer->get_token_line()); tokenizer->advance(); ControlFlowNode *cf_break = alloc_node<ControlFlowNode>(); cf_break->cf_type = ControlFlowNode::CF_BREAK; p_block->statements.push_back(cf_break); if (!_end_statement()) { - _set_error("Expected end of statement (break)"); + _set_error("Expected end of statement (\"break\")."); return; } } break; @@ -3240,7 +3245,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { match_node->val_to_match = val_to_match; if (!_enter_indent_block()) { - _set_error("Expected indented pattern matching block after 'match'"); + _set_error("Expected indented pattern matching block after \"match\"."); return; } @@ -3267,19 +3272,40 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { case GDScriptTokenizer::TK_PR_ASSERT: { tokenizer->advance(); - Node *condition = _parse_and_reduce_expression(p_block, p_static); - if (!condition) { - if (_recover_from_completion()) { - break; - } + + if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { + _set_error("Expected '(' after assert"); + return; + } + + tokenizer->advance(); + + Vector<Node *> args; + const bool result = _parse_arguments(p_block, args, p_static); + if (!result) { return; } + + if (args.empty() || args.size() > 2) { + _set_error("Wrong number of arguments, expected 1 or 2"); + return; + } + AssertNode *an = alloc_node<AssertNode>(); - an->condition = condition; + an->condition = _reduce_expression(args[0], p_static); + + if (args.size() == 2) { + an->message = _reduce_expression(args[1], p_static); + } else { + ConstantNode *message_node = alloc_node<ConstantNode>(); + message_node->value = String(); + an->message = message_node; + } + p_block->statements.push_back(an); if (!_end_statement()) { - _set_error("Expected end of statement after assert."); + _set_error("Expected end of statement after \"assert\"."); return; } } break; @@ -3290,7 +3316,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { p_block->statements.push_back(bn); if (!_end_statement()) { - _set_error("Expected end of statement after breakpoint."); + _set_error("Expected end of statement after \"breakpoint\"."); return; } } break; @@ -3322,7 +3348,7 @@ bool GDScriptParser::_parse_newline() { int current_indent = tab_level.back()->get(); if (indent > current_indent) { - _set_error("Unexpected indent."); + _set_error("Unexpected indentation."); return false; } @@ -3332,7 +3358,7 @@ bool GDScriptParser::_parse_newline() { //exit block if (tab_level.size() == 1) { - _set_error("Invalid indent. BUG?"); + _set_error("Invalid indentation. Bug?"); return false; } @@ -3359,13 +3385,13 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) { if (p_class->extends_used) { - _set_error("'extends' already used for this class."); + _set_error("\"extends\" can only be present once per script."); return; } - if (!p_class->constant_expressions.empty() || !p_class->subclasses.empty() || !p_class->functions.empty() || !p_class->variables.empty() || p_class->classname_used) { + if (!p_class->constant_expressions.empty() || !p_class->subclasses.empty() || !p_class->functions.empty() || !p_class->variables.empty()) { - _set_error("'extends' must be used before anything else."); + _set_error("\"extends\" must be used before anything else."); return; } @@ -3385,7 +3411,7 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) { Variant constant = tokenizer->get_token_constant(); if (constant.get_type() != Variant::STRING) { - _set_error("'extends' constant must be a string."); + _set_error("\"extends\" constant must be a string."); return; } @@ -3420,7 +3446,7 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) { default: { - _set_error("Invalid 'extends' syntax, expected string constant (path) and/or identifier (parent class)."); + _set_error("Invalid \"extends\" syntax, expected string constant (path) and/or identifier (parent class)."); return; } } @@ -3480,28 +3506,29 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (error_set) return; if (!_end_statement()) { - _set_error("Expected end of statement after extends"); + _set_error("Expected end of statement after \"extends\"."); return; } } break; case GDScriptTokenizer::TK_PR_CLASS_NAME: { + _mark_line_as_safe(tokenizer->get_token_line()); if (p_class->owner) { - _set_error("'class_name' is only valid for the main class namespace."); + _set_error("\"class_name\" is only valid for the main class namespace."); return; } if (self_path.begins_with("res://") && self_path.find("::") != -1) { - _set_error("'class_name' not allowed in built-in scripts."); + _set_error("\"class_name\" isn't allowed in built-in scripts."); return; } if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) { - _set_error("'class_name' syntax: 'class_name <UniqueName>'"); + _set_error("\"class_name\" syntax: \"class_name <UniqueName>\""); return; } if (p_class->classname_used) { - _set_error("'class_name' already used for this class."); + _set_error("\"class_name\" can only be present once per script."); return; } @@ -3510,12 +3537,21 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { p_class->name = tokenizer->get_token_identifier(1); if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) { - _set_error("Unique global class '" + p_class->name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name)); + _set_error("Unique global class \"" + p_class->name + "\" already exists at path: " + ScriptServer::get_global_class_path(p_class->name)); return; } if (ClassDB::class_exists(p_class->name)) { - _set_error("Class '" + p_class->name + "' shadows a native class."); + _set_error("The class \"" + p_class->name + "\" shadows a native class."); + return; + } + + if (p_class->classname_used && ProjectSettings::get_singleton()->has_setting("autoload/" + p_class->name)) { + const String autoload_path = ProjectSettings::get_singleton()->get_setting("autoload/" + p_class->name); + if (autoload_path.begins_with("*")) { + // It's a singleton, and not just a regular AutoLoad script. + _set_error("The class \"" + p_class->name + "\" conflicts with the AutoLoad singleton of the same name, and is therefore redundant. Remove the class_name declaration to fix this error."); + } return; } @@ -3542,12 +3578,12 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); } else { - _set_error("Optional parameter after 'class_name' must be a string constant file path to an icon."); + _set_error("The optional parameter after \"class_name\" must be a string constant file path to an icon."); return; } } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) { - _set_error("Class icon must be separated by a comma."); + _set_error("The class icon must be separated by a comma."); return; } @@ -3556,7 +3592,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (p_class->tool) { - _set_error("tool used more than once"); + _set_error("The \"tool\" keyword can only be present once per script."); return; } @@ -3571,7 +3607,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) { - _set_error("'class' syntax: 'class <Name>:' or 'class <Name> extends <BaseClass>:'"); + _set_error("\"class\" syntax: \"class <Name>:\" or \"class <Name> extends <BaseClass>:\""); return; } name = tokenizer->get_token_identifier(1); @@ -3579,23 +3615,23 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { // Check if name is shadowing something else if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) { - _set_error("Class '" + String(name) + "' shadows a native class."); + _set_error("The class \"" + String(name) + "\" shadows a native class."); return; } if (ScriptServer::is_global_class(name)) { - _set_error("Can't override name of unique global class '" + name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name)); + _set_error("Can't override name of the unique global class \"" + name + "\". It already exists at: " + ScriptServer::get_global_class_path(p_class->name)); return; } ClassNode *outer_class = p_class; while (outer_class) { for (int i = 0; i < outer_class->subclasses.size(); i++) { if (outer_class->subclasses[i]->name == name) { - _set_error("Another class named '" + String(name) + "' already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ")."); + _set_error("Another class named \"" + String(name) + "\" already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ")."); return; } } if (outer_class->constant_expressions.has(name)) { - _set_error("A constant named '" + String(name) + "' already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ")."); + _set_error("A constant named \"" + String(name) + "\" already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ")."); return; } @@ -3639,7 +3675,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - _set_error("Expected 'func'."); + _set_error("Expected \"func\"."); return; } @@ -3663,18 +3699,18 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (name == StringName()) { - _set_error("Expected identifier after 'func' (syntax: 'func <identifier>([arguments]):' )."); + _set_error("Expected an identifier after \"func\" (syntax: \"func <identifier>([arguments]):\")."); return; } for (int i = 0; i < p_class->functions.size(); i++) { if (p_class->functions[i]->name == name) { - _set_error("Function '" + String(name) + "' already exists in this class (at line: " + itos(p_class->functions[i]->line) + ")."); + _set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->functions[i]->line) + ")."); } } for (int i = 0; i < p_class->static_functions.size(); i++) { if (p_class->static_functions[i]->name == name) { - _set_error("Function '" + String(name) + "' already exists in this class (at line: " + itos(p_class->static_functions[i]->line) + ")."); + _set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->static_functions[i]->line) + ")."); } } @@ -3696,7 +3732,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - _set_error("Expected '(' after identifier (syntax: 'func <identifier>([arguments]):' )."); + _set_error("Expected \"(\" after the identifier (syntax: \"func <identifier>([arguments]):\" )."); return; } @@ -3728,7 +3764,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (!tokenizer->is_token_literal(0, true)) { - _set_error("Expected identifier for argument."); + _set_error("Expected an identifier for an argument."); return; } @@ -3746,7 +3782,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { argtype.infer_type = true; tokenizer->advance(); } else if (!_parse_type(argtype)) { - _set_error("Expected type for argument."); + _set_error("Expected a type for an argument."); return; } } @@ -3795,7 +3831,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { continue; } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ',' or ')'."); + _set_error("Expected \",\" or \")\"."); return; } @@ -3824,7 +3860,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (name == "_init") { if (_static) { - _set_error("Constructor cannot be static."); + _set_error("The constructor cannot be static."); return; } @@ -3841,7 +3877,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - _set_error("expected '(' for parent constructor arguments."); + _set_error("Expected \"(\" for parent constructor arguments."); return; } tokenizer->advance(); @@ -3861,7 +3897,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { continue; } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ',' or ')'."); + _set_error("Expected \",\" or \")\"."); return; } @@ -3886,7 +3922,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() == GDScriptTokenizer::TK_FORWARD_ARROW) { if (!_parse_type(return_type, true)) { - _set_error("Expected return type for function."); + _set_error("Expected a return type for the function."); return; } } @@ -3916,7 +3952,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (!tokenizer->is_token_literal()) { - _set_error("Expected identifier after 'signal'."); + _set_error("Expected an identifier after \"signal\"."); return; } @@ -3940,7 +3976,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } if (!tokenizer->is_token_literal(0, true)) { - _set_error("Expected identifier in signal argument."); + _set_error("Expected an identifier in a \"signal\" argument."); return; } @@ -3954,7 +3990,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ',' or ')' after signal parameter identifier."); + _set_error("Expected \",\" or \")\" after a \"signal\" parameter identifier."); return; } } @@ -3963,7 +3999,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { p_class->_signals.push_back(sig); if (!_end_statement()) { - _set_error("Expected end of statement (signal)"); + _set_error("Expected end of statement (\"signal\")."); return; } } break; @@ -4018,12 +4054,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - ERR_EXPLAIN("Exporting bit flags hint requires string constants."); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("Exporting bit flags hint requires string constants."); break; } if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - _set_error("Expected ',' in bit flags hint."); + _set_error("Expected \",\" in the bit flags hint."); return; } @@ -4035,7 +4070,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { current_export = PropertyInfo(); - _set_error("Expected a string constant in named bit flags hint."); + _set_error("Expected a string constant in the named bit flags hint."); return; } @@ -4053,7 +4088,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { current_export = PropertyInfo(); - _set_error("Expected ')' or ',' in named bit flags hint."); + _set_error("Expected \")\" or \",\" in the named bit flags hint."); return; } tokenizer->advance(); @@ -4066,7 +4101,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' in layers 2D render hint."); + _set_error("Expected \")\" in the layers 2D render hint."); return; } current_export.hint = PROPERTY_HINT_LAYERS_2D_RENDER; @@ -4077,7 +4112,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' in layers 2D physics hint."); + _set_error("Expected \")\" in the layers 2D physics hint."); return; } current_export.hint = PROPERTY_HINT_LAYERS_2D_PHYSICS; @@ -4088,7 +4123,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' in layers 3D render hint."); + _set_error("Expected \")\" in the layers 3D render hint."); return; } current_export.hint = PROPERTY_HINT_LAYERS_3D_RENDER; @@ -4099,7 +4134,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' in layers 3D physics hint."); + _set_error("Expected \")\" in the layers 3D physics hint."); return; } current_export.hint = PROPERTY_HINT_LAYERS_3D_PHYSICS; @@ -4115,7 +4150,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { current_export = PropertyInfo(); - _set_error("Expected a string constant in enumeration hint."); + _set_error("Expected a string constant in the enumeration hint."); return; } @@ -4133,7 +4168,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { current_export = PropertyInfo(); - _set_error("Expected ')' or ',' in enumeration hint."); + _set_error("Expected \")\" or \",\" in the enumeration hint."); return; } @@ -4151,7 +4186,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { current_export.hint = PROPERTY_HINT_EXP_EASING; tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' in hint."); + _set_error("Expected \")\" in the hint."); return; } break; @@ -4166,7 +4201,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) break; else if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - _set_error("Expected ')' or ',' in exponential range hint."); + _set_error("Expected \")\" or \",\" in the exponential range hint."); return; } tokenizer->advance(); @@ -4182,7 +4217,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) { current_export = PropertyInfo(); - _set_error("Expected a range in numeric hint."); + _set_error("Expected a range in the numeric hint."); return; } @@ -4197,7 +4232,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { current_export = PropertyInfo(); - _set_error("Expected ',' or ')' in numeric range hint."); + _set_error("Expected \",\" or \")\" in the numeric range hint."); return; } @@ -4212,7 +4247,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) { current_export = PropertyInfo(); - _set_error("Expected a number as upper bound in numeric range hint."); + _set_error("Expected a number as upper bound in the numeric range hint."); return; } @@ -4225,7 +4260,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { current_export = PropertyInfo(); - _set_error("Expected ',' or ')' in numeric range hint."); + _set_error("Expected \",\" or \")\" in the numeric range hint."); return; } @@ -4239,7 +4274,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) { current_export = PropertyInfo(); - _set_error("Expected a number as step in numeric range hint."); + _set_error("Expected a number as step in the numeric range hint."); return; } @@ -4258,7 +4293,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { current_export = PropertyInfo(); - _set_error("Expected a string constant in enumeration hint."); + _set_error("Expected a string constant in the enumeration hint."); return; } @@ -4275,7 +4310,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { current_export = PropertyInfo(); - _set_error("Expected ')' or ',' in enumeration hint."); + _set_error("Expected \")\" or \",\" in the enumeration hint."); return; } tokenizer->advance(); @@ -4295,7 +4330,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER || !(tokenizer->get_token_identifier() == "GLOBAL")) { - _set_error("Expected 'GLOBAL' after comma in directory hint."); + _set_error("Expected \"GLOBAL\" after comma in the directory hint."); return; } if (!p_class->tool) { @@ -4306,11 +4341,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' in hint."); + _set_error("Expected \")\" in the hint."); return; } } else { - _set_error("Expected ')' or ',' in hint."); + _set_error("Expected \")\" or \",\" in the hint."); return; } break; @@ -4339,7 +4374,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) tokenizer->advance(); else { - _set_error("Expected ')' or ',' in hint."); + _set_error("Expected \")\" or \",\" in the hint."); return; } } @@ -4347,9 +4382,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { if (current_export.hint == PROPERTY_HINT_GLOBAL_FILE) - _set_error("Expected string constant with filter"); + _set_error("Expected string constant with filter."); else - _set_error("Expected 'GLOBAL' or string constant with filter"); + _set_error("Expected \"GLOBAL\" or string constant with filter."); return; } current_export.hint_string = tokenizer->get_token_constant(); @@ -4357,7 +4392,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' in hint."); + _set_error("Expected \")\" in the hint."); return; } break; @@ -4368,7 +4403,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { current_export.hint = PROPERTY_HINT_MULTILINE_TEXT; tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' in hint."); + _set_error("Expected \")\" in the hint."); return; } break; @@ -4379,7 +4414,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER) { current_export = PropertyInfo(); - _set_error("Color type hint expects RGB or RGBA as hints"); + _set_error("Color type hint expects RGB or RGBA as hints."); return; } @@ -4390,7 +4425,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { //none } else { current_export = PropertyInfo(); - _set_error("Color type hint expects RGB or RGBA as hints"); + _set_error("Color type hint expects RGB or RGBA as hints."); return; } tokenizer->advance(); @@ -4399,7 +4434,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { default: { current_export = PropertyInfo(); - _set_error("Type '" + Variant::get_type_name(type) + "' can't take hints."); + _set_error("Type \"" + Variant::get_type_name(type) + "\" can't take hints."); return; } break; } @@ -4437,7 +4472,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } else { current_export = PropertyInfo(); - _set_error("Export hint not a resource type."); + _set_error("The export hint isn't a resource type."); } } else if (constant.get_type() == Variant::DICTIONARY) { // Enumeration @@ -4451,7 +4486,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); } else { current_export = PropertyInfo(); - _set_error("Expected 'FLAGS' after comma."); + _set_error("Expected \"FLAGS\" after comma."); } } @@ -4488,7 +4523,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { current_export = PropertyInfo(); - _set_error("Expected ')' or ',' after export hint."); + _set_error("Expected \")\" or \",\" after the export hint."); return; } @@ -4508,7 +4543,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPET && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTESYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTERSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPETSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SLAVE) { current_export = PropertyInfo(); - _set_error("Expected 'var', 'onready', 'remote', 'master', 'puppet', 'sync', 'remotesync', 'mastersync', 'puppetsync'."); + _set_error("Expected \"var\", \"onready\", \"remote\", \"master\", \"puppet\", \"sync\", \"remotesync\", \"mastersync\", \"puppetsync\"."); return; } @@ -4519,7 +4554,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { //may be fallthrough from export, ignore if so tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { - _set_error("Expected 'var'."); + _set_error("Expected \"var\"."); return; } @@ -4531,13 +4566,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (current_export.type) { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { - _set_error("Expected 'var'."); + _set_error("Expected \"var\"."); return; } } else { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - _set_error("Expected 'var' or 'func'."); + _set_error("Expected \"var\" or \"func\"."); return; } } @@ -4551,13 +4586,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (current_export.type) { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { - _set_error("Expected 'var'."); + _set_error("Expected \"var\"."); return; } } else { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - _set_error("Expected 'var' or 'func'."); + _set_error("Expected \"var\" or \"func\"."); return; } } @@ -4576,13 +4611,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (current_export.type) { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { - _set_error("Expected 'var'."); + _set_error("Expected \"var\"."); return; } } else { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - _set_error("Expected 'var' or 'func'."); + _set_error("Expected \"var\" or \"func\"."); return; } } @@ -4597,9 +4632,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { if (current_export.type) - _set_error("Expected 'var'."); + _set_error("Expected \"var\"."); else - _set_error("Expected 'var' or 'func'."); + _set_error("Expected \"var\" or \"func\"."); return; } @@ -4612,9 +4647,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { if (current_export.type) - _set_error("Expected 'var'."); + _set_error("Expected \"var\"."); else - _set_error("Expected 'var' or 'func'."); + _set_error("Expected \"var\" or \"func\"."); return; } @@ -4627,9 +4662,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { if (current_export.type) - _set_error("Expected 'var'."); + _set_error("Expected \"var\"."); else - _set_error("Expected 'var' or 'func'."); + _set_error("Expected \"var\" or \"func\"."); return; } @@ -4652,7 +4687,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (!tokenizer->is_token_literal(0, true)) { - _set_error("Expected identifier for member variable name."); + _set_error("Expected an identifier for the member variable name."); return; } @@ -4668,14 +4703,14 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { #endif if (current_class->constant_expressions.has(member.identifier)) { - _set_error("A constant named '" + String(member.identifier) + "' already exists in this class (at line: " + + _set_error("A constant named \"" + String(member.identifier) + "\" already exists in this class (at line: " + itos(current_class->constant_expressions[member.identifier].expression->line) + ")."); return; } for (int i = 0; i < current_class->variables.size(); i++) { if (current_class->variables[i].identifier == member.identifier) { - _set_error("Variable '" + String(member.identifier) + "' already exists in this class (at line: " + + _set_error("Variable \"" + String(member.identifier) + "\" already exists in this class (at line: " + itos(current_class->variables[i].line) + ")."); return; } @@ -4683,7 +4718,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { for (int i = 0; i < current_class->subclasses.size(); i++) { if (current_class->subclasses[i]->name == member.identifier) { - _set_error("A class named '" + String(member.identifier) + "' already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); + _set_error("A class named \"" + String(member.identifier) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); return; } } @@ -4713,7 +4748,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { #endif tokenizer->advance(); } else if (!_parse_type(member.data_type)) { - _set_error("Expected type for class variable."); + _set_error("Expected a type for the class variable."); return; } } @@ -4741,7 +4776,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { IdentifierNode *id = static_cast<IdentifierNode *>(op->arguments[1]); if (id->name == "get_node") { - _set_error("Use 'onready var " + String(member.identifier) + " = get_node(..)' instead"); + _set_error("Use \"onready var " + String(member.identifier) + " = get_node(...)\" instead."); return; } } @@ -4769,7 +4804,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { Object *obj = cn->value; Resource *res = Object::cast_to<Resource>(obj); if (res == NULL) { - _set_error("Exported constant not a type or resource."); + _set_error("The exported constant isn't a type or resource."); return; } member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; @@ -4787,7 +4822,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { const Variant *args = &cn->value; cn->value = Variant::construct(member._export.type, &args, 1, err); } else { - _set_error("Cannot convert the provided value to the export type."); + _set_error("Can't convert the provided value to the export type."); return; } } @@ -4826,35 +4861,29 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } - Variant::Type initial_type = member.data_type.has_type ? member.data_type.builtin_type : member._export.type; - - if (initial_type != Variant::NIL && initial_type != Variant::OBJECT) { - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = member.identifier; + Node *expr; - Node *expr; + if (member.data_type.has_type) { + expr = _get_default_value_for_type(member.data_type); + } else { + DataType exported_type; + exported_type.has_type = true; + exported_type.kind = DataType::BUILTIN; + exported_type.builtin_type = member._export.type; + expr = _get_default_value_for_type(exported_type); + } - // Make sure arrays and dictionaries are not shared - if (initial_type == Variant::ARRAY) { - expr = alloc_node<ArrayNode>(); - } else if (initial_type == Variant::DICTIONARY) { - expr = alloc_node<DictionaryNode>(); - } else { - ConstantNode *cn = alloc_node<ConstantNode>(); - Variant::CallError ce2; - cn->value = Variant::construct(initial_type, NULL, 0, ce2); - expr = cn; - } + IdentifierNode *id = alloc_node<IdentifierNode>(); + id->name = member.identifier; - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_INIT_ASSIGN; - op->arguments.push_back(id); - op->arguments.push_back(expr); + OperatorNode *op = alloc_node<OperatorNode>(); + op->op = OperatorNode::OP_INIT_ASSIGN; + op->arguments.push_back(id); + op->arguments.push_back(expr); - p_class->initializer->statements.push_back(op); + p_class->initializer->statements.push_back(op); - member.initial_assignment = op; - } + member.initial_assignment = op; } if (autoexport && member.data_type.has_type) { @@ -4885,7 +4914,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { //just comma means using only getter if (!tokenizer->is_token_literal()) { - _set_error("Expected identifier for setter function after 'setget'."); + _set_error("Expected an identifier for the setter function after \"setget\"."); } member.setter = tokenizer->get_token_literal(); @@ -4898,7 +4927,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (!tokenizer->is_token_literal()) { - _set_error("Expected identifier for getter function after ','."); + _set_error("Expected an identifier for the getter function after \",\"."); } member.getter = tokenizer->get_token_literal(); @@ -4909,7 +4938,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { p_class->variables.push_back(member); if (!_end_statement()) { - _set_error("Expected end of statement (continue)"); + _set_error("Expected end of statement (\"continue\")."); return; } } break; @@ -4921,7 +4950,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (!tokenizer->is_token_literal(0, true)) { - _set_error("Expected name (identifier) for constant."); + _set_error("Expected an identifier for the constant."); return; } @@ -4929,14 +4958,14 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { int line = tokenizer->get_token_line(); if (current_class->constant_expressions.has(const_id)) { - _set_error("Constant '" + String(const_id) + "' already exists in this class (at line: " + + _set_error("Constant \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->constant_expressions[const_id].expression->line) + ")."); return; } for (int i = 0; i < current_class->variables.size(); i++) { if (current_class->variables[i].identifier == const_id) { - _set_error("A variable named '" + String(const_id) + "' already exists in this class (at line: " + + _set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->variables[i].line) + ")."); return; } @@ -4944,7 +4973,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { for (int i = 0; i < current_class->subclasses.size(); i++) { if (current_class->subclasses[i]->name == const_id) { - _set_error("A class named '" + String(const_id) + "' already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); + _set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); return; } } @@ -4959,13 +4988,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { #endif tokenizer->advance(); } else if (!_parse_type(constant.type)) { - _set_error("Expected type for class constant."); + _set_error("Expected a type for the class constant."); return; } } if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) { - _set_error("Constant expects assignment."); + _set_error("Constants must be assigned immediately."); return; } @@ -4980,7 +5009,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } if (subexpr->type != Node::TYPE_CONSTANT) { - _set_error("Expected constant expression", line); + _set_error("Expected a constant expression.", line); return; } subexpr->line = line; @@ -4989,7 +5018,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { p_class->constant_expressions.insert(const_id, constant); if (!_end_statement()) { - _set_error("Expected end of statement (constant)", line); + _set_error("Expected end of statement (constant).", line); return; } @@ -5006,14 +5035,14 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { enum_name = tokenizer->get_token_literal(); if (current_class->constant_expressions.has(enum_name)) { - _set_error("A constant named '" + String(enum_name) + "' already exists in this class (at line: " + + _set_error("A constant named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->constant_expressions[enum_name].expression->line) + ")."); return; } for (int i = 0; i < current_class->variables.size(); i++) { if (current_class->variables[i].identifier == enum_name) { - _set_error("A variable named '" + String(enum_name) + "' already exists in this class (at line: " + + _set_error("A variable named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->variables[i].line) + ")."); return; } @@ -5021,7 +5050,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { for (int i = 0; i < current_class->subclasses.size(); i++) { if (current_class->subclasses[i]->name == enum_name) { - _set_error("A class named '" + String(enum_name) + "' already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); + _set_error("A class named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); return; } } @@ -5029,7 +5058,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); } if (tokenizer->get_token() != GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) { - _set_error("Expected '{' in enum declaration"); + _set_error("Expected \"{\" in the enum declaration."); return; } tokenizer->advance(); @@ -5047,7 +5076,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { _set_error("Unexpected end of file."); } else { - _set_error(String("Unexpected ") + GDScriptTokenizer::get_token_name(tokenizer->get_token()) + ", expected identifier"); + _set_error(String("Unexpected ") + GDScriptTokenizer::get_token_name(tokenizer->get_token()) + ", expected an identifier."); } return; @@ -5070,14 +5099,14 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } if (subexpr->type != Node::TYPE_CONSTANT) { - _set_error("Expected constant expression"); + _set_error("Expected a constant expression."); return; } enum_value_expr = static_cast<ConstantNode *>(subexpr); if (enum_value_expr->value.get_type() != Variant::INT) { - _set_error("Expected an int value for enum"); + _set_error("Expected an integer value for \"enum\"."); return; } @@ -5093,7 +5122,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); } else if (tokenizer->is_token_literal(0, true)) { - _set_error("Unexpected identifier"); + _set_error("Unexpected identifier."); return; } @@ -5101,14 +5130,14 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { enum_dict[const_id] = enum_value_expr->value; } else { if (current_class->constant_expressions.has(const_id)) { - _set_error("A constant named '" + String(const_id) + "' already exists in this class (at line: " + + _set_error("A constant named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->constant_expressions[const_id].expression->line) + ")."); return; } for (int i = 0; i < current_class->variables.size(); i++) { if (current_class->variables[i].identifier == const_id) { - _set_error("A variable named '" + String(const_id) + "' already exists in this class (at line: " + + _set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->variables[i].line) + ")."); return; } @@ -5116,7 +5145,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { for (int i = 0; i < current_class->subclasses.size(); i++) { if (current_class->subclasses[i]->name == const_id) { - _set_error("A class named '" + String(const_id) + "' already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); + _set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); return; } } @@ -5143,7 +5172,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } if (!_end_statement()) { - _set_error("Expected end of statement (enum)"); + _set_error("Expected end of statement (\"enum\")."); return; } @@ -5189,19 +5218,19 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive String base = base_path; if (base == "" || base.is_rel_path()) { - _set_error("Could not resolve relative path for parent class: " + path, p_class->line); + _set_error("Couldn't resolve relative path for the parent class: " + path, p_class->line); return; } path = base.plus_file(path).simplify_path(); } script = ResourceLoader::load(path); if (script.is_null()) { - _set_error("Could not load base class: " + path, p_class->line); + _set_error("Couldn't load the base class: " + path, p_class->line); return; } if (!script->is_valid()) { - _set_error("Script not fully loaded (cyclic preload?): " + path, p_class->line); + _set_error("Script isn't fully loaded (cyclic preload?): " + path, p_class->line); return; } @@ -5216,7 +5245,7 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive script = subclass; } else { - _set_error("Could not find subclass: " + sub, p_class->line); + _set_error("Couldn't find the subclass: " + sub, p_class->line); return; } } @@ -5238,10 +5267,35 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive if (ScriptServer::is_global_class(base)) { base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base)); if (!base_script.is_valid()) { - _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic dependency).", p_class->line); + _set_error("The class \"" + base + "\" couldn't be fully loaded (script error or cyclic dependency).", p_class->line); return; } p = NULL; + } else { + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + String name = s.get_slice("/", 1); + if (name == base) { + String singleton_path = ProjectSettings::get_singleton()->get(s); + if (singleton_path.begins_with("*")) { + singleton_path = singleton_path.right(1); + } + if (!singleton_path.begins_with("res://")) { + singleton_path = "res://" + singleton_path; + } + base_script = ResourceLoader::load(singleton_path); + if (!base_script.is_valid()) { + _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line); + return; + } + p = NULL; + } + } } while (p) { @@ -5279,13 +5333,13 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive if (p->constant_expressions.has(base)) { if (p->constant_expressions[base].expression->type != Node::TYPE_CONSTANT) { - _set_error("Could not resolve constant '" + base + "'.", p_class->line); + _set_error("Couldn't resolve the constant \"" + base + "\".", p_class->line); return; } const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression); base_script = cn->value; if (base_script.is_null()) { - _set_error("Constant is not a class: " + base, p_class->line); + _set_error("Constant isn't a class: " + base, p_class->line); return; } break; @@ -5312,13 +5366,13 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive Ref<GDScript> new_base_class = base_script->get_constants()[subclass]; if (new_base_class.is_null()) { - _set_error("Constant is not a class: " + ident, p_class->line); + _set_error("Constant isn't a class: " + ident, p_class->line); return; } find_subclass = new_base_class; } else { - _set_error("Could not find subclass: " + ident, p_class->line); + _set_error("Couldn't find the subclass: " + ident, p_class->line); return; } } @@ -5329,13 +5383,13 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive if (p_class->extends_class.size() > 1) { - _set_error("Invalid inheritance (unknown class + subclasses)", p_class->line); + _set_error("Invalid inheritance (unknown class + subclasses).", p_class->line); return; } //if not found, try engine classes if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { - _set_error("Unknown class: '" + base + "'", p_class->line); + _set_error("Unknown class: \"" + base + "\"", p_class->line); return; } @@ -5357,7 +5411,7 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive p_class->base_type.kind = DataType::NATIVE; p_class->base_type.native_type = native; } else { - _set_error("Could not determine inheritance", p_class->line); + _set_error("Couldn't determine inheritance.", p_class->line); return; } @@ -5502,7 +5556,7 @@ bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) { switch (tokenizer->get_token()) { case GDScriptTokenizer::TK_PERIOD: { if (!can_index) { - _set_error("Unexpected '.'."); + _set_error("Unexpected \".\"."); return false; } can_index = false; @@ -5533,7 +5587,7 @@ bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) { } if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) { - _set_error("Expected subclass identifier."); + _set_error("Expected a subclass identifier."); return false; } @@ -5571,7 +5625,7 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, Ref<GDScript> gds = script; if (gds.is_valid()) { if (!gds->is_valid()) { - _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic dependency).", p_line); + _set_error("The class \"" + id + "\" couldn't be fully loaded (script error or cyclic dependency).", p_line); return DataType(); } result.kind = DataType::GDSCRIPT; @@ -5580,15 +5634,55 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, result.kind = DataType::SCRIPT; result.script_type = script; } else { - _set_error("Class '" + id + "' was found in global scope but its script could not be loaded.", p_line); + _set_error("The class \"" + id + "\" was found in global scope, but its script couldn't be loaded.", p_line); return DataType(); } } name_part++; continue; - } else { - p = current_class; } + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + String singleton_path; + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + String name = s.get_slice("/", 1); + if (name == id) { + singleton_path = ProjectSettings::get_singleton()->get(s); + if (singleton_path.begins_with("*")) { + singleton_path = singleton_path.right(1); + } + if (!singleton_path.begins_with("res://")) { + singleton_path = "res://" + singleton_path; + } + break; + } + } + if (!singleton_path.empty()) { + Ref<Script> script = ResourceLoader::load(singleton_path); + Ref<GDScript> gds = script; + if (gds.is_valid()) { + if (!gds->is_valid()) { + _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line); + return DataType(); + } + result.kind = DataType::GDSCRIPT; + result.script_type = gds; + } else if (script.is_valid()) { + result.kind = DataType::SCRIPT; + result.script_type = script; + } else { + _set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line); + return DataType(); + } + name_part++; + continue; + } + + p = current_class; } else if (base_type.kind == DataType::CLASS) { p = base_type.class_type; } @@ -5649,28 +5743,35 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, } } - // Still look for class constants in parent script + // Still look for class constants in parent scripts if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) { Ref<Script> scr = base_type.script_type; ERR_FAIL_COND_V(scr.is_null(), result); - Map<StringName, Variant> constants; - scr->get_constants(&constants); + while (scr.is_valid()) { + Map<StringName, Variant> constants; + scr->get_constants(&constants); - if (constants.has(id)) { - Ref<GDScript> gds = constants[id]; + if (constants.has(id)) { + Ref<GDScript> gds = constants[id]; - if (gds.is_valid()) { - result.kind = DataType::GDSCRIPT; - result.script_type = gds; - found = true; - } else { - Ref<Script> scr2 = constants[id]; - if (scr2.is_valid()) { - result.kind = DataType::SCRIPT; - result.script_type = scr2; + if (gds.is_valid()) { + result.kind = DataType::GDSCRIPT; + result.script_type = gds; found = true; + } else { + Ref<Script> scr2 = constants[id]; + if (scr2.is_valid()) { + result.kind = DataType::SCRIPT; + result.script_type = scr2; + found = true; + } } } + if (found) { + break; + } else { + scr = scr->get_base_script(); + } } } @@ -5681,8 +5782,8 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, } else { base = result.to_string(); } - _set_error("Identifier '" + String(id) + "' is not a valid type (not a script or class), or could not be found on base '" + - base + "'.", + _set_error("The identifier \"" + String(id) + "\" isn't a valid type (not a script or class), or couldn't be found on base \"" + + base + "\".", p_line); return DataType(); } @@ -5760,7 +5861,7 @@ GDScriptParser::DataType GDScriptParser::_type_from_gdtype(const GDScriptDataTyp switch (p_gdtype.kind) { case GDScriptDataType::UNINITIALIZED: { - ERR_EXPLAIN("Uninitialized datatype. Please report a bug."); + ERR_PRINT("Uninitialized datatype. Please report a bug."); } break; case GDScriptDataType::BUILTIN: { result.kind = DataType::BUILTIN; @@ -6062,6 +6163,31 @@ bool GDScriptParser::_is_type_compatible(const DataType &p_container, const Data return false; } +GDScriptParser::Node *GDScriptParser::_get_default_value_for_type(const DataType &p_type, int p_line) { + Node *result; + + if (p_type.has_type && p_type.kind == DataType::BUILTIN && p_type.builtin_type != Variant::NIL && p_type.builtin_type != Variant::OBJECT) { + if (p_type.builtin_type == Variant::ARRAY) { + result = alloc_node<ArrayNode>(); + } else if (p_type.builtin_type == Variant::DICTIONARY) { + result = alloc_node<DictionaryNode>(); + } else { + ConstantNode *c = alloc_node<ConstantNode>(); + Variant::CallError err; + c->value = Variant::construct(p_type.builtin_type, NULL, 0, err); + result = c; + } + } else { + ConstantNode *c = alloc_node<ConstantNode>(); + c->value = Variant(); + result = c; + } + + result->line = p_line; + + return result; +} + GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { #ifdef DEBUG_ENABLED if (p_node->get_datatype().has_type && p_node->type != Node::TYPE_ARRAY && p_node->type != Node::TYPE_DICTIONARY) { @@ -6146,8 +6272,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { } if (!valid) { - _set_error("Invalid cast. Cannot convert from '" + source_type.to_string() + - "' to '" + cn->cast_type.to_string() + "'.", + _set_error("Invalid cast. Cannot convert from \"" + source_type.to_string() + + "\" to \"" + cn->cast_type.to_string() + "\".", cn->line); return DataType(); } @@ -6176,11 +6302,11 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { DataType signal_type = _reduce_node_type(op->arguments[1]); // TODO: Check if signal exists when it's a constant if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) { - _set_error("First argument of 'yield()' must be an object.", op->line); + _set_error("The first argument of \"yield()\" must be an object.", op->line); return DataType(); } if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) { - _set_error("Second argument of 'yield()' must be a string.", op->line); + _set_error("The second argument of \"yield()\" must be a string.", op->line); return DataType(); } } @@ -6200,15 +6326,15 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { if (check_types && type_type.has_type) { if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) { - _set_error("Invalid 'is' test: right operand is not a type (not a native type nor a script).", op->line); + _set_error("Invalid \"is\" test: the right operand isn't a type (neither a native type nor a script).", op->line); return DataType(); } type_type.is_meta_type = false; // Test the actual type if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) { if (op->op == OperatorNode::OP_IS) { - _set_error("A value of type '" + value_type.to_string() + "' will never be an instance of '" + type_type.to_string() + "'.", op->line); + _set_error("A value of type \"" + value_type.to_string() + "\" will never be an instance of \"" + type_type.to_string() + "\".", op->line); } else { - _set_error("A value of type '" + value_type.to_string() + "' will never be of type '" + type_type.to_string() + "'.", op->line); + _set_error("A value of type \"" + value_type.to_string() + "\" will never be of type \"" + type_type.to_string() + "\".", op->line); } return DataType(); } @@ -6236,8 +6362,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { node_type = _get_operation_type(var_op, argument_type, argument_type, valid); if (check_types && !valid) { - _set_error("Invalid operand type ('" + argument_type.to_string() + - "') to unary operator '" + Variant::get_operator_name(var_op) + "'.", + _set_error("Invalid operand type (\"" + argument_type.to_string() + + "\") to unary operator \"" + Variant::get_operator_name(var_op) + "\".", op->line, op->column); return DataType(); } @@ -6281,8 +6407,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid); if (check_types && !valid) { - _set_error("Invalid operand types ('" + argument_a_type.to_string() + "' and '" + - argument_b_type.to_string() + "') to operator '" + Variant::get_operator_name(var_op) + "'.", + _set_error("Invalid operand types (\"" + argument_a_type.to_string() + "\" and \"" + + argument_b_type.to_string() + "\") to operator \"" + Variant::get_operator_name(var_op) + "\".", op->line, op->column); return DataType(); } @@ -6297,7 +6423,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { // Ternary operators case OperatorNode::OP_TERNARY_IF: { if (op->arguments.size() != 3) { - _set_error("Parser bug: ternary operation without 3 arguments"); + _set_error("Parser bug: ternary operation without 3 arguments."); ERR_FAIL_V(DataType()); } @@ -6330,7 +6456,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { case OperatorNode::OP_ASSIGN_BIT_XOR: case OperatorNode::OP_INIT_ASSIGN: { - _set_error("Assignment inside expression is not allowed (parser bug?).", op->line); + _set_error("Assignment inside an expression isn't allowed (parser bug?).", op->line); return DataType(); } break; @@ -6366,8 +6492,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { if (valid) { result = _type_from_variant(res); } else if (check_types) { - _set_error("Can't get index '" + String(member_id->name.operator String()) + "' on base '" + - base_type.to_string() + "'.", + _set_error("Can't get index \"" + String(member_id->name.operator String()) + "\" on base \"" + + base_type.to_string() + "\".", op->line); return DataType(); } @@ -6459,7 +6585,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { } } if (error) { - _set_error("Invalid index type (" + index_type.to_string() + ") for base '" + base_type.to_string() + "'.", + _set_error("Invalid index type (" + index_type.to_string() + ") for base \"" + base_type.to_string() + "\".", op->line); return DataType(); } @@ -6492,8 +6618,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { node_type = _type_from_variant(res); node_type.is_constant = false; } else if (check_types) { - _set_error("Can't get index '" + String(cn->value) + "' on base '" + - base_type.to_string() + "'.", + _set_error("Can't get index \"" + String(cn->value) + "\" on base \"" + + base_type.to_string() + "\".", op->line); return DataType(); } @@ -6503,11 +6629,11 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { _mark_line_as_unsafe(op->line); } } else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) { - _set_error("Only strings can be used as index in the base type '" + base_type.to_string() + "'.", op->line); + _set_error("Only strings can be used as an index in the base type \"" + base_type.to_string() + "\".", op->line); return DataType(); } } - if (check_types && !node_type.has_type) { + if (check_types && !node_type.has_type && base_type.kind == DataType::BUILTIN) { // Can infer indexing type for some variant types DataType result; result.has_type = true; @@ -6520,7 +6646,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { case Variant::REAL: case Variant::NODE_PATH: case Variant::_RID: { - _set_error("Can't index on a value of type '" + base_type.to_string() + "'.", op->line); + _set_error("Can't index on a value of type \"" + base_type.to_string() + "\".", op->line); return DataType(); } break; // Return int @@ -6694,8 +6820,7 @@ bool GDScriptParser::_get_function_signature(DataType &p_base_type, const String } if (!ClassDB::class_exists(native)) { if (!check_types) return false; - ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found."); - ERR_FAIL_V(false); + ERR_FAIL_V_MSG(false, "Parser bug: Class '" + String(native) + "' not found."); } MethodBind *method = ClassDB::get_method(native, p_function); @@ -6919,7 +7044,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat if (check_types) { if (!tmp.has_method(callee_name)) { - _set_error("Method '" + callee_name + "' is not declared on base '" + base_type.to_string() + "'.", p_call->line); + _set_error("The method \"" + callee_name + "\" isn't declared on base \"" + base_type.to_string() + "\".", p_call->line); return DataType(); } @@ -6979,7 +7104,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat if (!valid) { #ifdef DEBUG_ENABLED if (p_call->arguments[0]->type == Node::TYPE_SELF) { - _set_error("Method '" + callee_name + "' is not declared in the current class.", p_call->line); + _set_error("The method \"" + callee_name + "\" isn't declared in the current class.", p_call->line); return DataType(); } DataType tmp_type; @@ -7004,7 +7129,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat } if (check_types && !is_static && !is_initializer && base_type.is_meta_type) { - _set_error("Non-static function '" + String(callee_name) + "' can only be called from an instance.", p_call->line); + _set_error("Non-static function \"" + String(callee_name) + "\" can only be called from an instance.", p_call->line); return DataType(); } @@ -7029,11 +7154,11 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat } if (arg_count < arg_types.size() - default_args_count) { - _set_error("Too few arguments for '" + callee_name + "()' call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line); + _set_error("Too few arguments for \"" + callee_name + "()\" call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line); return return_type; } if (!is_vararg && arg_count > arg_types.size()) { - _set_error("Too many arguments for '" + callee_name + "()' call. Expected at most " + itos(arg_types.size()) + ".", p_call->line); + _set_error("Too many arguments for \"" + callee_name + "()\" call. Expected at most " + itos(arg_types.size()) + ".", p_call->line); return return_type; } @@ -7055,7 +7180,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat } else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) { // Supertypes are acceptable for dynamic compliance if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) { - _set_error("At '" + callee_name + "()' call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" + + _set_error("At \"" + callee_name + "()\" call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" + par_type.to_string() + ") doesn't match the function argument's type (" + arg_types[i - arg_diff].to_string() + ").", p_call->line); @@ -7203,8 +7328,7 @@ bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringN } if (!ClassDB::class_exists(native)) { if (!check_types) return false; - ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found."); - ERR_FAIL_V(false); + ERR_FAIL_V_MSG(false, "Parser bug: Class \"" + String(native) + "\" not found."); } bool valid = false; @@ -7374,7 +7498,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType Ref<GDScript> gds = scr; if (gds.is_valid()) { if (!gds->is_valid()) { - _set_error("Class '" + p_identifier + "' could not be fully loaded (script error or cyclic dependency)."); + _set_error("The class \"" + p_identifier + "\" couldn't be fully loaded (script error or cyclic dependency)."); return DataType(); } result.kind = DataType::GDSCRIPT; @@ -7383,7 +7507,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType } return result; } - _set_error("Class '" + p_identifier + "' was found in global scope but its script could not be loaded."); + _set_error("The class \"" + p_identifier + "\" was found in global scope, but its script couldn't be loaded."); return DataType(); } @@ -7426,7 +7550,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType Ref<GDScript> gds = singleton; if (gds.is_valid()) { if (!gds->is_valid()) { - _set_error("Couldn't fully load singleton script '" + p_identifier + "' (possible cyclic reference or parse error).", p_line); + _set_error("Couldn't fully load the singleton script \"" + p_identifier + "\" (possible cyclic reference or parse error).", p_line); return DataType(); } result.kind = DataType::GDSCRIPT; @@ -7438,7 +7562,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType } // This means looking in the current class, which type is always known - _set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line); + _set_error("The identifier \"" + p_identifier.operator String() + "\" isn't declared in the current scope.", p_line); } #ifdef DEBUG_ENABLED @@ -7470,7 +7594,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line); if (check_types && !_is_type_compatible(cont, expr)) { - _set_error("Constant value type (" + expr.to_string() + ") is not compatible with declared type (" + cont.to_string() + ").", + _set_error("The constant value type (" + expr.to_string() + ") isn't compatible with declared type (" + cont.to_string() + ").", c.expression->line); return; } @@ -7481,7 +7605,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { DataType tmp; if (_get_member_type(p_class->base_type, E->key(), tmp)) { - _set_error("Member '" + String(E->key()) + "' already exists in parent class.", c.expression->line); + _set_error("The member \"" + String(E->key()) + "\" already exists in a parent class.", c.expression->line); return; } } @@ -7503,7 +7627,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { DataType tmp; if (_get_member_type(p_class->base_type, v.identifier, tmp)) { - _set_error("Member '" + String(v.identifier) + "' already exists in parent class.", v.line); + _set_error("The member \"" + String(v.identifier) + "\" already exists in a parent class.", v.line); return; } @@ -7520,7 +7644,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { } else { // Try with implicit conversion if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) { - _set_error("Assigned expression type (" + expr_type.to_string() + ") doesn't match the variable's type (" + + _set_error("The assigned expression's type (" + expr_type.to_string() + ") doesn't match the variable's type (" + v.data_type.to_string() + ").", v.line); return; @@ -7549,7 +7673,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { if (v.data_type.infer_type) { if (!expr_type.has_type) { - _set_error("Assigned value does not have a set type, variable type cannot be inferred.", v.line); + _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", v.line); return; } v.data_type = expr_type; @@ -7561,7 +7685,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { if (v.data_type.has_type && v._export.type != Variant::NIL) { DataType export_type = _type_from_property(v._export); if (!_is_type_compatible(v.data_type, export_type, true)) { - _set_error("Export hint type (" + export_type.to_string() + ") doesn't match the variable's type (" + + _set_error("The export hint's type (" + export_type.to_string() + ") doesn't match the variable's type (" + v.data_type.to_string() + ").", v.line); return; @@ -7580,15 +7704,15 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { if (setter->get_required_argument_count() != 1 && !(setter->get_required_argument_count() == 0 && setter->default_values.size() > 0)) { - _set_error("Setter function needs to receive exactly 1 argument. See '" + setter->name + - "()' definition at line " + itos(setter->line) + ".", + _set_error("The setter function needs to receive exactly 1 argument. See \"" + setter->name + + "()\" definition at line " + itos(setter->line) + ".", v.line); return; } if (!_is_type_compatible(v.data_type, setter->argument_types[0])) { - _set_error("Setter argument type (" + setter->argument_types[0].to_string() + - ") doesn't match the variable's type (" + v.data_type.to_string() + "). See '" + - setter->name + "()' definition at line " + itos(setter->line) + ".", + _set_error("The setter argument's type (" + setter->argument_types[0].to_string() + + ") doesn't match the variable's type (" + v.data_type.to_string() + "). See \"" + + setter->name + "()\" definition at line " + itos(setter->line) + ".", v.line); return; } @@ -7599,15 +7723,15 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { FunctionNode *getter = p_class->functions[j]; if (getter->get_required_argument_count() != 0) { - _set_error("Getter function can't receive arguments. See '" + getter->name + - "()' definition at line " + itos(getter->line) + ".", + _set_error("The getter function can't receive arguments. See \"" + getter->name + + "()\" definition at line " + itos(getter->line) + ".", v.line); return; } if (!_is_type_compatible(v.data_type, getter->get_datatype())) { - _set_error("Getter return type (" + getter->get_datatype().to_string() + + _set_error("The getter return type (" + getter->get_datatype().to_string() + ") doesn't match the variable's type (" + v.data_type.to_string() + - "). See '" + getter->name + "()' definition at line " + itos(getter->line) + ".", + "). See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line); return; } @@ -7621,23 +7745,23 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { for (int j = 0; j < p_class->static_functions.size(); j++) { if (v.setter == p_class->static_functions[j]->name) { FunctionNode *setter = p_class->static_functions[j]; - _set_error("Setter can't be a static function. See '" + setter->name + "()' definition at line " + itos(setter->line) + ".", v.line); + _set_error("The setter can't be a static function. See \"" + setter->name + "()\" definition at line " + itos(setter->line) + ".", v.line); return; } if (v.getter == p_class->static_functions[j]->name) { FunctionNode *getter = p_class->static_functions[j]; - _set_error("Getter can't be a static function. See '" + getter->name + "()' definition at line " + itos(getter->line) + ".", v.line); + _set_error("The getter can't be a static function. See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line); return; } } if (!found_setter && v.setter != StringName()) { - _set_error("Setter function is not defined.", v.line); + _set_error("The setter function isn't defined.", v.line); return; } if (!found_getter && v.getter != StringName()) { - _set_error("Getter function is not defined.", v.line); + _set_error("The getter function isn't defined.", v.line); return; } } @@ -7684,7 +7808,7 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) { if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) { String arg_name = p_function->arguments[i]; _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" + - arg_name + "' (" + p_function->arguments[i] + ")", + arg_name + "' (" + p_function->arguments[i] + ").", p_function->line); } } @@ -7747,21 +7871,21 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) { } } parent_signature += ")"; - _set_error("Function signature doesn't match the parent. Parent signature is: '" + parent_signature + "'.", p_function->line); + _set_error("The function signature doesn't match the parent. Parent signature is: \"" + parent_signature + "\".", p_function->line); return; } } #endif // DEBUG_ENABLED } else { if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { - _set_error("Constructor cannot return a value.", p_function->line); + _set_error("The constructor can't return a value.", p_function->line); return; } } if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { if (!p_function->body->has_return) { - _set_error("Non-void function must return a value in all possible paths.", p_function->line); + _set_error("A non-void function must return a value in all possible paths.", p_function->line); return; } } @@ -7892,7 +8016,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { } else { // Try implicit conversion if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) { - _set_error("Assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" + + _set_error("The assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" + lv->datatype.to_string() + ").", lv->line); return; @@ -7924,7 +8048,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { } if (lv->datatype.infer_type) { if (!assign_type.has_type) { - _set_error("Assigned value does not have a set type, variable type cannot be inferred.", lv->line); + _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", lv->line); return; } lv->datatype = assign_type; @@ -7975,7 +8099,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { } } if (lh_type.is_constant) { - _set_error("Cannot assign a new value to a constant.", op->line); + _set_error("Can't assign a new value to a constant.", op->line); return; } } @@ -7994,8 +8118,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { rh_type = _get_operation_type(oper, lh_type, arg_type, valid); if (check_types && !valid) { - _set_error("Invalid operand types ('" + lh_type.to_string() + "' and '" + arg_type.to_string() + - "') to assignment operator '" + Variant::get_operator_name(oper) + "'.", + _set_error("Invalid operand types (\"" + lh_type.to_string() + "\" and \"" + arg_type.to_string() + + "\") to assignment operator \"" + Variant::get_operator_name(oper) + "\".", op->line); return; } @@ -8023,7 +8147,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { } else { // Try implicit conversion if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) { - _set_error("Assigned value type (" + rh_type.to_string() + ") doesn't match the variable's type (" + + _set_error("The assigned value's type (" + rh_type.to_string() + ") doesn't match the variable's type (" + lh_type.to_string() + ").", op->line); return; @@ -8108,18 +8232,18 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) { // Return void, should not have arguments if (cf->arguments.size() > 0) { - _set_error("Void function cannot return a value.", cf->line, cf->column); + _set_error("A void function cannot return a value.", cf->line, cf->column); return; } } else { // Return something, cannot be empty if (cf->arguments.size() == 0) { - _set_error("Non-void function must return a value.", cf->line, cf->column); + _set_error("A non-void function must return a value.", cf->line, cf->column); return; } if (!_is_type_compatible(function_type, ret_type)) { - _set_error("Returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" + + _set_error("The returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" + function_type.to_string() + ").", cf->line, cf->column); return; @@ -8256,6 +8380,10 @@ int GDScriptParser::get_error_column() const { return error_column; } +bool GDScriptParser::has_error() const { + return error_set; +} + Error GDScriptParser::_parse(const String &p_base_path) { base_path = p_base_path; @@ -8272,7 +8400,7 @@ Error GDScriptParser::_parse(const String &p_base_path) { if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) { error_set = false; - _set_error("Parse Error: " + tokenizer->get_token_error()); + _set_error("Parse error: " + tokenizer->get_token_error()); } if (error_set && !for_completion) { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 62d7bdb393..04ce9cf4c6 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -481,7 +481,12 @@ public: struct AssertNode : public Node { Node *condition; - AssertNode() { type = TYPE_ASSERT; } + Node *message; + AssertNode() : + condition(0), + message(0) { + type = TYPE_ASSERT; + } }; struct BreakpointNode : public Node { @@ -610,6 +615,7 @@ private: bool _get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const; bool _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const; bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const; + Node *_get_default_value_for_type(const DataType &p_type, int p_line = -1); DataType _reduce_node_type(Node *p_node); DataType _reduce_function_call_type(const OperatorNode *p_call); @@ -632,6 +638,7 @@ private: Error _parse(const String &p_base_path); public: + bool has_error() const; String get_error() const; int get_error_line() const; int get_error_column() const; diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 95715ab648..64b354bdb8 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -357,8 +357,7 @@ StringName GDScriptTokenizer::get_token_literal(int p_offset) const { } } } - ERR_EXPLAIN("Failed to get token literal"); - ERR_FAIL_V(""); + ERR_FAIL_V_MSG("", "Failed to get token literal."); } static bool _is_text_char(CharType c) { @@ -517,7 +516,22 @@ void GDScriptTokenizerText::_advance() { INCPOS(1); column = 1; int i = 0; - while (GETCHAR(i) == ' ' || GETCHAR(i) == '\t') { + while (true) { + if (GETCHAR(i) == ' ') { + if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_SPACES; + if (file_indent_type != INDENT_SPACES) { + _make_error("Spaces used for indentation in tab-indented file!"); + return; + } + } else if (GETCHAR(i) == '\t') { + if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_TABS; + if (file_indent_type != INDENT_TABS) { + _make_error("Tabs used for indentation in space-indented file!"); + return; + } + } else { + break; // not indentation anymore + } i++; } @@ -555,9 +569,25 @@ void GDScriptTokenizerText::_advance() { column = 1; line++; int i = 0; - while (GETCHAR(i) == ' ' || GETCHAR(i) == '\t') { + while (true) { + if (GETCHAR(i) == ' ') { + if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_SPACES; + if (file_indent_type != INDENT_SPACES) { + _make_error("Spaces used for indentation in tab-indented file!"); + return; + } + } else if (GETCHAR(i) == '\t') { + if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_TABS; + if (file_indent_type != INDENT_TABS) { + _make_error("Tabs used for indentation in space-indented file!"); + return; + } + } else { + break; // not indentation anymore + } i++; } + _make_newline(i); return; @@ -1082,6 +1112,7 @@ void GDScriptTokenizerText::set_code(const String &p_code) { ignore_warnings = false; #endif // DEBUG_ENABLED last_error = ""; + file_indent_type = INDENT_NONE; for (int i = 0; i < MAX_LOOKAHEAD + 1; i++) _advance(); } @@ -1187,10 +1218,8 @@ Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) ERR_FAIL_COND_V(p_buffer.size() < 24 || p_buffer[0] != 'G' || p_buffer[1] != 'D' || p_buffer[2] != 'S' || p_buffer[3] != 'C', ERR_INVALID_DATA); int version = decode_uint32(&buf[4]); - if (version > BYTECODE_VERSION) { - ERR_EXPLAIN("Bytecode is too New! Please use a newer engine version."); - ERR_FAIL_V(ERR_INVALID_DATA); - } + ERR_FAIL_COND_V_MSG(version > BYTECODE_VERSION, ERR_INVALID_DATA, "Bytecode is too recent! Please use a newer engine version."); + int identifier_count = decode_uint32(&buf[8]); int constant_count = decode_uint32(&buf[12]); int line_count = decode_uint32(&buf[16]); diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 7b977ff67c..89d586b912 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -222,6 +222,12 @@ class GDScriptTokenizerText : public GDScriptTokenizer { int tk_rb_pos; String last_error; bool error_flag; + enum { + INDENT_NONE, + INDENT_SPACES, + INDENT_TABS, + } file_indent_type; + #ifdef DEBUG_ENABLED Vector<Pair<int, String> > warning_skips; Set<String> warning_global_skips; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp new file mode 100644 index 0000000000..45f9ec9c6a --- /dev/null +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -0,0 +1,759 @@ +/*************************************************************************/ +/* gdscript_extend_parser.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_extend_parser.h" +#include "../gdscript.h" +#include "core/io/json.h" +#include "gdscript_language_protocol.h" +#include "gdscript_workspace.h" + +void ExtendGDScriptParser::update_diagnostics() { + + diagnostics.clear(); + + if (has_error()) { + lsp::Diagnostic diagnostic; + diagnostic.severity = lsp::DiagnosticSeverity::Error; + diagnostic.message = get_error(); + diagnostic.source = "gdscript"; + diagnostic.code = -1; + lsp::Range range; + lsp::Position pos; + int line = LINE_NUMBER_TO_INDEX(get_error_line()); + const String &line_text = get_lines()[line]; + pos.line = line; + pos.character = line_text.length() - line_text.strip_edges(true, false).length(); + range.start = pos; + range.end = range.start; + range.end.character = line_text.strip_edges(false).length(); + diagnostic.range = range; + diagnostics.push_back(diagnostic); + } + + const List<GDScriptWarning> &warnings = get_warnings(); + for (const List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { + const GDScriptWarning &warning = E->get(); + lsp::Diagnostic diagnostic; + diagnostic.severity = lsp::DiagnosticSeverity::Warning; + diagnostic.message = warning.get_message(); + diagnostic.source = "gdscript"; + diagnostic.code = warning.code; + lsp::Range range; + lsp::Position pos; + int line = LINE_NUMBER_TO_INDEX(warning.line); + const String &line_text = get_lines()[line]; + pos.line = line; + pos.character = line_text.length() - line_text.strip_edges(true, false).length(); + range.start = pos; + range.end = pos; + range.end.character = line_text.strip_edges(false).length(); + diagnostic.range = range; + diagnostics.push_back(diagnostic); + } +} + +void ExtendGDScriptParser::update_symbols() { + + members.clear(); + + const GDScriptParser::Node *head = get_parse_tree(); + if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) { + + parse_class_symbol(gdclass, class_symbol); + + for (int i = 0; i < class_symbol.children.size(); i++) { + const lsp::DocumentSymbol &symbol = class_symbol.children[i]; + members.set(symbol.name, &symbol); + + // cache level one inner classes + if (symbol.kind == lsp::SymbolKind::Class) { + ClassMembers inner_class; + for (int j = 0; j < symbol.children.size(); j++) { + const lsp::DocumentSymbol &s = symbol.children[j]; + inner_class.set(s.name, &s); + } + inner_classes.set(symbol.name, inner_class); + } + } + } +} + +void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) { + + const String uri = get_uri(); + + r_symbol.uri = uri; + r_symbol.script_path = path; + r_symbol.children.clear(); + r_symbol.name = p_class->name; + if (r_symbol.name.empty()) + r_symbol.name = path.get_file(); + r_symbol.kind = lsp::SymbolKind::Class; + r_symbol.deprecated = false; + r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->line); + r_symbol.range.start.character = p_class->column; + r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line); + r_symbol.selectionRange.start.line = r_symbol.range.start.line; + r_symbol.detail = "class " + r_symbol.name; + bool is_root_class = &r_symbol == &class_symbol; + r_symbol.documentation = parse_documentation_as_markdown(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class); + + for (int i = 0; i < p_class->variables.size(); ++i) { + + const GDScriptParser::ClassNode::Member &m = p_class->variables[i]; + + lsp::DocumentSymbol symbol; + symbol.name = m.identifier; + symbol.kind = lsp::SymbolKind::Variable; + symbol.deprecated = false; + const int line = LINE_NUMBER_TO_INDEX(m.line); + symbol.range.start.line = line; + symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length(); + symbol.range.end.line = line; + symbol.range.end.character = lines[line].length(); + symbol.selectionRange.start.line = symbol.range.start.line; + if (m._export.type != Variant::NIL) { + symbol.detail += "export "; + } + symbol.detail += "var " + m.identifier; + if (m.data_type.kind != GDScriptParser::DataType::UNRESOLVED) { + symbol.detail += ": " + m.data_type.to_string(); + } + if (m.default_value.get_type() != Variant::NIL) { + symbol.detail += " = " + JSON::print(m.default_value); + } + + symbol.documentation = parse_documentation_as_markdown(line); + symbol.uri = uri; + symbol.script_path = path; + + r_symbol.children.push_back(symbol); + } + + for (int i = 0; i < p_class->_signals.size(); ++i) { + const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i]; + + lsp::DocumentSymbol symbol; + symbol.name = signal.name; + symbol.kind = lsp::SymbolKind::Event; + symbol.deprecated = false; + const int line = LINE_NUMBER_TO_INDEX(signal.line); + symbol.range.start.line = line; + symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length(); + symbol.range.end.line = symbol.range.start.line; + symbol.range.end.character = lines[line].length(); + symbol.selectionRange.start.line = symbol.range.start.line; + symbol.documentation = parse_documentation_as_markdown(line); + symbol.uri = uri; + symbol.script_path = path; + symbol.detail = "signal " + signal.name + "("; + for (int j = 0; j < signal.arguments.size(); j++) { + if (j > 0) { + symbol.detail += ", "; + } + symbol.detail += signal.arguments[j]; + } + symbol.detail += ")"; + + r_symbol.children.push_back(symbol); + } + + for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { + lsp::DocumentSymbol symbol; + const GDScriptParser::ClassNode::Constant &c = E->value(); + const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression); + symbol.name = E->key(); + symbol.kind = lsp::SymbolKind::Constant; + symbol.deprecated = false; + const int line = LINE_NUMBER_TO_INDEX(E->get().expression->line); + symbol.range.start.line = line; + symbol.range.start.character = E->get().expression->column; + symbol.range.end.line = symbol.range.start.line; + symbol.range.end.character = lines[line].length(); + symbol.selectionRange.start.line = symbol.range.start.line; + symbol.documentation = parse_documentation_as_markdown(line); + symbol.uri = uri; + symbol.script_path = path; + + symbol.detail = "const " + symbol.name; + if (c.type.kind != GDScriptParser::DataType::UNRESOLVED) { + symbol.detail += ": " + c.type.to_string(); + } + + String value_text; + if (node->value.get_type() == Variant::OBJECT) { + RES res = node->value; + if (res.is_valid() && !res->get_path().empty()) { + value_text = "preload(\"" + res->get_path() + "\")"; + if (symbol.documentation.empty()) { + if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) { + symbol.documentation = S->get()->class_symbol.documentation; + } + } + } else { + value_text = JSON::print(node->value); + } + } else { + value_text = JSON::print(node->value); + } + if (!value_text.empty()) { + symbol.detail += " = " + value_text; + } + + r_symbol.children.push_back(symbol); + } + + for (int i = 0; i < p_class->functions.size(); ++i) { + const GDScriptParser::FunctionNode *func = p_class->functions[i]; + lsp::DocumentSymbol symbol; + parse_function_symbol(func, symbol); + r_symbol.children.push_back(symbol); + } + + for (int i = 0; i < p_class->static_functions.size(); ++i) { + const GDScriptParser::FunctionNode *func = p_class->static_functions[i]; + lsp::DocumentSymbol symbol; + parse_function_symbol(func, symbol); + r_symbol.children.push_back(symbol); + } + + for (int i = 0; i < p_class->subclasses.size(); ++i) { + const GDScriptParser::ClassNode *subclass = p_class->subclasses[i]; + lsp::DocumentSymbol symbol; + parse_class_symbol(subclass, symbol); + r_symbol.children.push_back(symbol); + } +} + +void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) { + + const String uri = get_uri(); + + r_symbol.name = p_func->name; + r_symbol.kind = lsp::SymbolKind::Function; + r_symbol.detail = "func " + p_func->name + "("; + r_symbol.deprecated = false; + const int line = LINE_NUMBER_TO_INDEX(p_func->line); + r_symbol.range.start.line = line; + r_symbol.range.start.character = p_func->column; + r_symbol.range.end.line = MAX(p_func->body->end_line - 2, p_func->body->line); + r_symbol.range.end.character = lines[r_symbol.range.end.line].length(); + r_symbol.selectionRange.start.line = r_symbol.range.start.line; + r_symbol.documentation = parse_documentation_as_markdown(line); + r_symbol.uri = uri; + r_symbol.script_path = path; + + String arguments; + for (int i = 0; i < p_func->arguments.size(); i++) { + lsp::DocumentSymbol symbol; + symbol.kind = lsp::SymbolKind::Variable; + symbol.name = p_func->arguments[i]; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->body->line); + symbol.range.start.character = p_func->body->column; + symbol.range.end = symbol.range.start; + symbol.uri = uri; + symbol.script_path = path; + r_symbol.children.push_back(symbol); + if (i > 0) { + arguments += ", "; + } + arguments += String(p_func->arguments[i]); + if (p_func->argument_types[i].kind != GDScriptParser::DataType::UNRESOLVED) { + arguments += ": " + p_func->argument_types[i].to_string(); + } + int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size()); + if (default_value_idx >= 0) { + const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]); + if (const_node == NULL) { + const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]); + if (operator_node) { + const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next); + } + } + + if (const_node) { + String value = JSON::print(const_node->value); + arguments += " = " + value; + } + } + } + r_symbol.detail += arguments + ")"; + if (p_func->return_type.kind != GDScriptParser::DataType::UNRESOLVED) { + r_symbol.detail += " -> " + p_func->return_type.to_string(); + } + + for (const Map<StringName, LocalVarNode *>::Element *E = p_func->body->variables.front(); E; E = E->next()) { + lsp::DocumentSymbol symbol; + const GDScriptParser::LocalVarNode *var = E->value(); + symbol.name = E->key(); + symbol.kind = lsp::SymbolKind::Variable; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(E->get()->line); + symbol.range.start.character = E->get()->column; + symbol.range.end.line = symbol.range.start.line; + symbol.range.end.character = lines[symbol.range.end.line].length(); + symbol.uri = uri; + symbol.script_path = path; + symbol.detail = "var " + symbol.name; + if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) { + symbol.detail += ": " + var->datatype.to_string(); + } + symbol.documentation = parse_documentation_as_markdown(line); + r_symbol.children.push_back(symbol); + } +} + +String ExtendGDScriptParser::marked_documentation(const String &p_bbcode) { + + String markdown = p_bbcode.strip_edges(); + + Vector<String> lines = markdown.split("\n"); + bool in_code_block = false; + int code_block_indent = -1; + + markdown = ""; + for (int i = 0; i < lines.size(); i++) { + String line = lines[i]; + int block_start = line.find("[codeblock]"); + if (block_start != -1) { + code_block_indent = block_start; + in_code_block = true; + line = "'''gdscript"; + line = "\n"; + } else if (in_code_block) { + line = "\t" + line.substr(code_block_indent, line.length()); + } + + if (in_code_block && line.find("[/codeblock]") != -1) { + line = "'''\n"; + line = "\n"; + in_code_block = false; + } + + if (!in_code_block) { + line = line.strip_edges(); + line = line.replace("[code]", "`"); + line = line.replace("[/code]", "`"); + line = line.replace("[i]", "*"); + line = line.replace("[/i]", "*"); + line = line.replace("[b]", "**"); + line = line.replace("[/b]", "**"); + line = line.replace("[u]", "__"); + line = line.replace("[/u]", "__"); + line = line.replace("[method ", "`"); + line = line.replace("[member ", "`"); + line = line.replace("[signal ", "`"); + line = line.replace("[enum ", "`"); + line = line.replace("[constant ", "`"); + line = line.replace("[", "`"); + line = line.replace("]", "`"); + } + + if (!in_code_block && i < lines.size() - 1) { + line += "\n\n"; + } else if (i < lines.size() - 1) { + line += "\n"; + } + markdown += line; + } + return markdown; +} + +String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { + ERR_FAIL_INDEX_V(p_line, lines.size(), String()); + + List<String> doc_lines; + + if (!p_docs_down) { // inline comment + String inline_comment = lines[p_line]; + int comment_start = inline_comment.find("#"); + if (comment_start != -1) { + inline_comment = inline_comment.substr(comment_start, inline_comment.length()).strip_edges(); + if (inline_comment.length() > 1) { + doc_lines.push_back(inline_comment.substr(1, inline_comment.length())); + } + } + } + + int step = p_docs_down ? 1 : -1; + int start_line = p_docs_down ? p_line : p_line - 1; + for (int i = start_line; true; i += step) { + + if (i < 0 || i >= lines.size()) break; + + String line_comment = lines[i].strip_edges(true, false); + if (line_comment.begins_with("#")) { + line_comment = line_comment.substr(1, line_comment.length()); + if (p_docs_down) { + doc_lines.push_back(line_comment); + } else { + doc_lines.push_front(line_comment); + } + } else { + break; + } + } + + String doc; + for (List<String>::Element *E = doc_lines.front(); E; E = E->next()) { + doc += E->get() + "\n"; + } + return doc; +} + +String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_cursor) const { + + String longthing; + int len = lines.size(); + for (int i = 0; i < len; i++) { + + if (i == p_cursor.line) { + longthing += lines[i].substr(0, p_cursor.character); + longthing += String::chr(0xFFFF); //not unicode, represents the cursor + longthing += lines[i].substr(p_cursor.character, lines[i].size()); + } else { + + longthing += lines[i]; + } + + if (i != len - 1) + longthing += "\n"; + } + + return longthing; +} + +String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_requred) const { + String longthing; + int len = lines.size(); + for (int i = 0; i < len; i++) { + + if (i == p_cursor.line) { + String line = lines[i]; + String first_part = line.substr(0, p_cursor.character); + String last_part = line.substr(p_cursor.character + 1, lines[i].length()); + if (!p_symbol.empty()) { + String left_cursor_text; + for (int c = p_cursor.character - 1; c >= 0; c--) { + left_cursor_text = line.substr(c, p_cursor.character - c); + if (p_symbol.begins_with(left_cursor_text)) { + first_part = line.substr(0, c); + first_part += p_symbol; + break; + } + } + } + + longthing += first_part; + longthing += String::chr(0xFFFF); //not unicode, represents the cursor + if (p_func_requred) { + longthing += "("; // tell the parser this is a function call + } + longthing += last_part; + } else { + + longthing += lines[i]; + } + + if (i != len - 1) + longthing += "\n"; + } + + return longthing; +} + +String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const { + + ERR_FAIL_INDEX_V(p_position.line, lines.size(), ""); + String line = lines[p_position.line]; + ERR_FAIL_INDEX_V(p_position.character, line.size(), ""); + + int start_pos = p_position.character; + for (int c = p_position.character; c >= 0; c--) { + start_pos = c; + CharType ch = line[c]; + bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; + if (!valid_char) { + break; + } + } + + int end_pos = p_position.character; + for (int c = p_position.character; c < line.length(); c++) { + CharType ch = line[c]; + bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; + if (!valid_char) { + break; + } + end_pos = c; + } + if (start_pos < end_pos) { + p_offset.x = start_pos - p_position.character; + p_offset.y = end_pos - p_position.character; + return line.substr(start_pos + 1, end_pos - start_pos); + } + + return ""; +} + +String ExtendGDScriptParser::get_uri() const { + return GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path); +} + +const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const { + const lsp::DocumentSymbol *ret = NULL; + if (p_line < p_parent.range.start.line) { + return ret; + } else if (p_parent.range.start.line == p_line) { + return &p_parent; + } else { + for (int i = 0; i < p_parent.children.size(); i++) { + ret = search_symbol_defined_at_line(p_line, p_parent.children[i]); + if (ret) { + break; + } + } + } + return ret; +} + +String ExtendGDScriptParser::parse_documentation_as_markdown(int p_line, bool p_docs_down) { + return marked_documentation(parse_documentation(p_line, p_docs_down)); +} + +const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const { + if (p_line <= 0) { + return &class_symbol; + } + return search_symbol_defined_at_line(p_line, class_symbol); +} + +const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const { + + if (p_subclass.empty()) { + const lsp::DocumentSymbol *const *ptr = members.getptr(p_name); + if (ptr) { + return *ptr; + } + } else { + if (const ClassMembers *_class = inner_classes.getptr(p_subclass)) { + const lsp::DocumentSymbol *const *ptr = _class->getptr(p_name); + if (ptr) { + return *ptr; + } + } + } + + return NULL; +} + +const Array &ExtendGDScriptParser::get_member_completions() { + + if (member_completions.empty()) { + + const String *name = members.next(NULL); + while (name) { + + const lsp::DocumentSymbol *symbol = members.get(*name); + lsp::CompletionItem item = symbol->make_completion_item(); + item.data = JOIN_SYMBOLS(path, *name); + member_completions.push_back(item.to_json()); + + name = members.next(name); + } + + const String *_class = inner_classes.next(NULL); + while (_class) { + + const ClassMembers *inner_class = inner_classes.getptr(*_class); + const String *member_name = inner_class->next(NULL); + while (member_name) { + const lsp::DocumentSymbol *symbol = inner_class->get(*member_name); + lsp::CompletionItem item = symbol->make_completion_item(); + item.data = JOIN_SYMBOLS(path, JOIN_SYMBOLS(*_class, *member_name)); + member_completions.push_back(item.to_json()); + + member_name = inner_class->next(member_name); + } + + _class = inner_classes.next(_class); + } + } + + return member_completions; +} + +Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const { + Dictionary func; + ERR_FAIL_NULL_V(p_func, func); + func["name"] = p_func->name; + func["return_type"] = p_func->return_type.to_string(); + func["rpc_mode"] = p_func->rpc_mode; + Array arguments; + for (int i = 0; i < p_func->arguments.size(); i++) { + Dictionary arg; + arg["name"] = p_func->arguments[i]; + arg["type"] = p_func->argument_types[i].to_string(); + int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size()); + if (default_value_idx >= 0) { + const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]); + if (const_node == NULL) { + const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]); + if (operator_node) { + const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next); + } + } + if (const_node) { + arg["default_value"] = const_node->value; + } + } + arguments.push_back(arg); + } + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->line))) { + func["signature"] = symbol->detail; + func["description"] = symbol->documentation; + } + func["arguments"] = arguments; + return func; +} + +Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode *p_class) const { + Dictionary class_api; + + ERR_FAIL_NULL_V(p_class, class_api); + + class_api["name"] = String(p_class->name); + class_api["path"] = path; + Array extends_class; + for (int i = 0; i < p_class->extends_class.size(); i++) { + extends_class.append(String(p_class->extends_class[i])); + } + class_api["extends_class"] = extends_class; + class_api["extends_file"] = String(p_class->extends_file); + class_api["icon"] = String(p_class->icon_path); + + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->line))) { + class_api["signature"] = symbol->detail; + class_api["description"] = symbol->documentation; + } + + Array subclasses; + for (int i = 0; i < p_class->subclasses.size(); i++) { + subclasses.push_back(dump_class_api(p_class->subclasses[i])); + } + class_api["sub_classes"] = subclasses; + + Array constants; + for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { + + const GDScriptParser::ClassNode::Constant &c = E->value(); + const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression); + + Dictionary api; + api["name"] = E->key(); + api["value"] = node->value; + api["data_type"] = node->datatype.to_string(); + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(node->line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + constants.push_back(api); + } + class_api["constants"] = constants; + + Array members; + for (int i = 0; i < p_class->variables.size(); ++i) { + const GDScriptParser::ClassNode::Member &m = p_class->variables[i]; + Dictionary api; + api["name"] = m.identifier; + api["data_type"] = m.data_type.to_string(); + api["default_value"] = m.default_value; + api["setter"] = String(m.setter); + api["getter"] = String(m.getter); + api["export"] = m._export.type != Variant::NIL; + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + members.push_back(api); + } + class_api["members"] = members; + + Array signals; + for (int i = 0; i < p_class->_signals.size(); ++i) { + const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i]; + Dictionary api; + api["name"] = signal.name; + Array args; + for (int j = 0; j < signal.arguments.size(); j++) { + args.append(signal.arguments[j]); + } + api["arguments"] = args; + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(signal.line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + signals.push_back(api); + } + class_api["signals"] = signals; + + Array methods; + for (int i = 0; i < p_class->functions.size(); ++i) { + methods.append(dump_function_api(p_class->functions[i])); + } + class_api["methods"] = methods; + + Array static_functions; + for (int i = 0; i < p_class->static_functions.size(); ++i) { + static_functions.append(dump_function_api(p_class->functions[i])); + } + class_api["static_functions"] = static_functions; + + return class_api; +} + +Dictionary ExtendGDScriptParser::generate_api() const { + + Dictionary api; + const GDScriptParser::Node *head = get_parse_tree(); + if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) { + api = dump_class_api(gdclass); + } + return api; +} + +Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { + path = p_path; + lines = p_code.split("\n"); + + Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, NULL, false); + update_diagnostics(); + update_symbols(); + return err; +} diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h new file mode 100644 index 0000000000..dd0453d3ff --- /dev/null +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -0,0 +1,103 @@ +/*************************************************************************/ +/* gdscript_extend_parser.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_EXTEND_PARSER_H +#define GDSCRIPT_EXTEND_PARSER_H + +#include "../gdscript_parser.h" +#include "core/variant.h" +#include "lsp.hpp" + +#ifndef LINE_NUMBER_TO_INDEX +#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1) +#endif + +#ifndef SYMBOL_SEPERATOR +#define SYMBOL_SEPERATOR "::" +#endif + +#ifndef JOIN_SYMBOLS +#define JOIN_SYMBOLS(p_path, name) ((p_path) + SYMBOL_SEPERATOR + (name)) +#endif + +typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers; + +class ExtendGDScriptParser : public GDScriptParser { + + String path; + Vector<String> lines; + + lsp::DocumentSymbol class_symbol; + Vector<lsp::Diagnostic> diagnostics; + ClassMembers members; + HashMap<String, ClassMembers> inner_classes; + + void update_diagnostics(); + + void update_symbols(); + void parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol); + void parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol); + + Dictionary dump_function_api(const GDScriptParser::FunctionNode *p_func) const; + Dictionary dump_class_api(const GDScriptParser::ClassNode *p_class) const; + + String parse_documentation(int p_line, bool p_docs_down = false); + const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const; + + Array member_completions; + + String parse_documentation_as_markdown(int p_line, bool p_docs_down = false); + +public: + static String marked_documentation(const String &p_bbcode); + +public: + _FORCE_INLINE_ const String &get_path() const { return path; } + _FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; } + _FORCE_INLINE_ const lsp::DocumentSymbol &get_symbols() const { return class_symbol; } + _FORCE_INLINE_ const Vector<lsp::Diagnostic> &get_diagnostics() const { return diagnostics; } + _FORCE_INLINE_ const ClassMembers &get_members() const { return members; } + _FORCE_INLINE_ const HashMap<String, ClassMembers> &get_inner_classes() const { return inner_classes; } + + String get_text_for_completion(const lsp::Position &p_cursor) const; + String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_requred = false) const; + String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const; + String get_uri() const; + + const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const; + const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const; + + const Array &get_member_completions(); + Dictionary generate_api() const; + + Error parse(const String &p_code, const String &p_path); +}; + +#endif diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp new file mode 100644 index 0000000000..afe461b68e --- /dev/null +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -0,0 +1,211 @@ +/*************************************************************************/ +/* gdscript_language_protocol.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_language_protocol.h" +#include "core/io/json.h" +#include "core/os/copymem.h" +#include "core/project_settings.h" +#include "editor/editor_node.h" + +GDScriptLanguageProtocol *GDScriptLanguageProtocol::singleton = NULL; + +void GDScriptLanguageProtocol::on_data_received(int p_id) { + lastest_client_id = p_id; + Ref<WebSocketPeer> peer = server->get_peer(p_id); + PoolByteArray data; + if (OK == peer->get_packet_buffer(data)) { + String message; + message.parse_utf8((const char *)data.read().ptr(), data.size()); + if (message.begins_with("Content-Length:")) return; + String output = process_message(message); + if (!output.empty()) { + CharString charstr = output.utf8(); + peer->put_packet((const uint8_t *)charstr.ptr(), charstr.length()); + } + } +} + +void GDScriptLanguageProtocol::on_client_connected(int p_id, const String &p_protocal) { + clients.set(p_id, server->get_peer(p_id)); +} + +void GDScriptLanguageProtocol::on_client_disconnected(int p_id, bool p_was_clean_close) { + clients.erase(p_id); +} + +String GDScriptLanguageProtocol::process_message(const String &p_text) { + String ret = process_string(p_text); + if (ret.empty()) { + return ret; + } else { + return format_output(ret); + } +} + +String GDScriptLanguageProtocol::format_output(const String &p_text) { + + String header = "Content-Length: "; + CharString charstr = p_text.utf8(); + size_t len = charstr.length(); + header += itos(len); + header += "\r\n\r\n"; + + return header + p_text; +} + +void GDScriptLanguageProtocol::_bind_methods() { + ClassDB::bind_method(D_METHOD("initialize", "params"), &GDScriptLanguageProtocol::initialize); + ClassDB::bind_method(D_METHOD("initialized", "params"), &GDScriptLanguageProtocol::initialized); + ClassDB::bind_method(D_METHOD("on_data_received"), &GDScriptLanguageProtocol::on_data_received); + ClassDB::bind_method(D_METHOD("on_client_connected"), &GDScriptLanguageProtocol::on_client_connected); + ClassDB::bind_method(D_METHOD("on_client_disconnected"), &GDScriptLanguageProtocol::on_client_disconnected); + ClassDB::bind_method(D_METHOD("notify_all_clients", "p_method", "p_params"), &GDScriptLanguageProtocol::notify_all_clients, DEFVAL(Variant())); + ClassDB::bind_method(D_METHOD("notify_client", "p_method", "p_params", "p_client"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("is_smart_resolve_enabled"), &GDScriptLanguageProtocol::is_smart_resolve_enabled); + ClassDB::bind_method(D_METHOD("get_text_document"), &GDScriptLanguageProtocol::get_text_document); + ClassDB::bind_method(D_METHOD("get_workspace"), &GDScriptLanguageProtocol::get_workspace); + ClassDB::bind_method(D_METHOD("is_initialized"), &GDScriptLanguageProtocol::is_initialized); +} + +Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { + + lsp::InitializeResult ret; + + String root_uri = p_params["rootUri"]; + String root = p_params["rootPath"]; + bool is_same_workspace = root == workspace->root; + is_same_workspace = root.to_lower() == workspace->root.to_lower(); +#ifdef WINDOWS_ENABLED + is_same_workspace = root.replace("\\", "/").to_lower() == workspace->root.to_lower(); +#endif + + if (root_uri.length() && is_same_workspace) { + workspace->root_uri = root_uri; + } else { + + workspace->root_uri = "file://" + workspace->root; + + Dictionary params; + params["path"] = workspace->root; + Dictionary request = make_notification("gdscrip_client/changeWorkspace", params); + if (Ref<WebSocketPeer> *peer = clients.getptr(lastest_client_id)) { + String msg = JSON::print(request); + msg = format_output(msg); + CharString charstr = msg.utf8(); + (*peer)->put_packet((const uint8_t *)charstr.ptr(), charstr.length()); + } + } + + if (!_initialized) { + workspace->initialize(); + text_document->initialize(); + _initialized = true; + } + + return ret.to_json(); +} + +void GDScriptLanguageProtocol::initialized(const Variant &p_params) { +} + +void GDScriptLanguageProtocol::poll() { + server->poll(); +} + +Error GDScriptLanguageProtocol::start(int p_port) { + if (server == NULL) { + server = dynamic_cast<WebSocketServer *>(ClassDB::instance("WebSocketServer")); + server->set_buffers(8192, 1024, 8192, 1024); // 8mb should be way more than enough + server->connect("data_received", this, "on_data_received"); + server->connect("client_connected", this, "on_client_connected"); + server->connect("client_disconnected", this, "on_client_disconnected"); + } + return server->listen(p_port); +} + +void GDScriptLanguageProtocol::stop() { + server->stop(); +} + +void GDScriptLanguageProtocol::notify_all_clients(const String &p_method, const Variant &p_params) { + + Dictionary message = make_notification(p_method, p_params); + String msg = JSON::print(message); + msg = format_output(msg); + CharString charstr = msg.utf8(); + const int *p_id = clients.next(NULL); + while (p_id != NULL) { + Ref<WebSocketPeer> peer = clients.get(*p_id); + (*peer)->put_packet((const uint8_t *)charstr.ptr(), charstr.length()); + p_id = clients.next(p_id); + } +} + +void GDScriptLanguageProtocol::notify_client(const String &p_method, const Variant &p_params, int p_client) { + + if (p_client == -1) { + p_client = lastest_client_id; + } + + Ref<WebSocketPeer> *peer = clients.getptr(p_client); + ERR_FAIL_COND(peer == NULL); + + Dictionary message = make_notification(p_method, p_params); + String msg = JSON::print(message); + msg = format_output(msg); + CharString charstr = msg.utf8(); + + (*peer)->put_packet((const uint8_t *)charstr.ptr(), charstr.length()); +} + +bool GDScriptLanguageProtocol::is_smart_resolve_enabled() const { + return bool(_EDITOR_GET("network/language_server/enable_smart_resolve")); +} + +bool GDScriptLanguageProtocol::is_goto_native_symbols_enabled() const { + return bool(_EDITOR_GET("network/language_server/show_native_symbols_in_editor")); +} + +GDScriptLanguageProtocol::GDScriptLanguageProtocol() { + server = NULL; + singleton = this; + _initialized = false; + workspace.instance(); + text_document.instance(); + set_scope("textDocument", text_document.ptr()); + set_scope("completionItem", text_document.ptr()); + set_scope("workspace", workspace.ptr()); + workspace->root = ProjectSettings::get_singleton()->get_resource_path(); +} + +GDScriptLanguageProtocol::~GDScriptLanguageProtocol() { + memdelete(server); + server = NULL; +} diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h new file mode 100644 index 0000000000..136b45fd78 --- /dev/null +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* gdscript_language_protocol.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_PROTOCAL_SERVER_H +#define GDSCRIPT_PROTOCAL_SERVER_H + +#include "gdscript_text_document.h" +#include "gdscript_workspace.h" +#include "lsp.hpp" +#include "modules/jsonrpc/jsonrpc.h" +#include "modules/websocket/websocket_peer.h" +#include "modules/websocket/websocket_server.h" + +class GDScriptLanguageProtocol : public JSONRPC { + GDCLASS(GDScriptLanguageProtocol, JSONRPC) + + enum LSPErrorCode { + RequestCancelled = -32800, + ContentModified = -32801, + }; + + static GDScriptLanguageProtocol *singleton; + + HashMap<int, Ref<WebSocketPeer> > clients; + WebSocketServer *server; + int lastest_client_id; + + Ref<GDScriptTextDocument> text_document; + Ref<GDScriptWorkspace> workspace; + + void on_data_received(int p_id); + void on_client_connected(int p_id, const String &p_protocal); + void on_client_disconnected(int p_id, bool p_was_clean_close); + + String process_message(const String &p_text); + String format_output(const String &p_text); + + bool _initialized; + +protected: + static void _bind_methods(); + + Dictionary initialize(const Dictionary &p_params); + void initialized(const Variant &p_params); + +public: + _FORCE_INLINE_ static GDScriptLanguageProtocol *get_singleton() { return singleton; } + _FORCE_INLINE_ Ref<GDScriptWorkspace> get_workspace() { return workspace; } + _FORCE_INLINE_ Ref<GDScriptTextDocument> get_text_document() { return text_document; } + _FORCE_INLINE_ bool is_initialized() const { return _initialized; } + + void poll(); + Error start(int p_port); + void stop(); + + void notify_all_clients(const String &p_method, const Variant &p_params = Variant()); + void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client = -1); + + bool is_smart_resolve_enabled() const; + bool is_goto_native_symbols_enabled() const; + + GDScriptLanguageProtocol(); + ~GDScriptLanguageProtocol(); +}; + +#endif diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp new file mode 100644 index 0000000000..62c212a2bd --- /dev/null +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* gdscript_language_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_language_server.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "editor/editor_node.h" + +GDScriptLanguageServer::GDScriptLanguageServer() { + thread = NULL; + thread_exit = false; + _EDITOR_DEF("network/language_server/remote_port", 6008); + _EDITOR_DEF("network/language_server/enable_smart_resolve", false); + _EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false); +} + +void GDScriptLanguageServer::_notification(int p_what) { + + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + start(); + break; + case NOTIFICATION_EXIT_TREE: + stop(); + break; + } +} + +void GDScriptLanguageServer::thread_main(void *p_userdata) { + GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata); + while (!self->thread_exit) { + self->protocol.poll(); + OS::get_singleton()->delay_usec(10); + } +} + +void GDScriptLanguageServer::start() { + int port = (int)_EDITOR_GET("network/language_server/remote_port"); + if (protocol.start(port) == OK) { + EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); + ERR_FAIL_COND(thread != NULL || thread_exit); + thread_exit = false; + thread = Thread::create(GDScriptLanguageServer::thread_main, this); + } +} + +void GDScriptLanguageServer::stop() { + ERR_FAIL_COND(NULL == thread || thread_exit); + thread_exit = true; + Thread::wait_to_finish(thread); + memdelete(thread); + thread = NULL; + protocol.stop(); + EditorNode::get_log()->add_message("--- GDScript language server stopped ---", EditorLog::MSG_TYPE_EDITOR); +} + +void register_lsp_types() { + ClassDB::register_class<GDScriptLanguageProtocol>(); + ClassDB::register_class<GDScriptTextDocument>(); + ClassDB::register_class<GDScriptWorkspace>(); +} diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h new file mode 100644 index 0000000000..83c2320d98 --- /dev/null +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -0,0 +1,60 @@ +/*************************************************************************/ +/* gdscript_language_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_LANGUAGE_SERVER_H +#define GDSCRIPT_LANGUAGE_SERVER_H + +#include "../gdscript_parser.h" +#include "editor/editor_plugin.h" +#include "gdscript_language_protocol.h" + +class GDScriptLanguageServer : public EditorPlugin { + GDCLASS(GDScriptLanguageServer, EditorPlugin); + + GDScriptLanguageProtocol protocol; + + Thread *thread; + bool thread_exit; + static void thread_main(void *p_userdata); + +private: + void _notification(int p_what); + void _iteration(); + +public: + Error parse_script_file(const String &p_path); + GDScriptLanguageServer(); + void start(); + void stop(); +}; + +void register_lsp_types(); + +#endif // GDSCRIPT_LANGUAGE_SERVER_H diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp new file mode 100644 index 0000000000..f211fae526 --- /dev/null +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -0,0 +1,391 @@ +/*************************************************************************/ +/* gdscript_text_document.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_text_document.h" +#include "../gdscript.h" +#include "core/os/os.h" +#include "editor/editor_settings.h" +#include "editor/plugins/script_text_editor.h" +#include "gdscript_extend_parser.h" +#include "gdscript_language_protocol.h" + +void GDScriptTextDocument::_bind_methods() { + ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen); + ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange); + ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol); + ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion); + ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve); + ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange); + ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens); + ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink); + ClassDB::bind_method(D_METHOD("colorPresentation"), &GDScriptTextDocument::colorPresentation); + ClassDB::bind_method(D_METHOD("hover"), &GDScriptTextDocument::hover); + ClassDB::bind_method(D_METHOD("definition"), &GDScriptTextDocument::definition); + ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor"), &GDScriptTextDocument::show_native_symbol_in_editor); +} + +void GDScriptTextDocument::didOpen(const Variant &p_param) { + lsp::TextDocumentItem doc = load_document_item(p_param); + sync_script_content(doc.uri, doc.text); +} + +void GDScriptTextDocument::didChange(const Variant &p_param) { + lsp::TextDocumentItem doc = load_document_item(p_param); + Dictionary dict = p_param; + Array contentChanges = dict["contentChanges"]; + for (int i = 0; i < contentChanges.size(); ++i) { + lsp::TextDocumentContentChangeEvent evt; + evt.load(contentChanges[i]); + doc.text = evt.text; + } + sync_script_content(doc.uri, doc.text); +} + +lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) { + lsp::TextDocumentItem doc; + Dictionary params = p_param; + doc.load(params["textDocument"]); + return doc; +} + +void GDScriptTextDocument::initialize() { + + if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + + const HashMap<StringName, ClassMembers> &native_members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members; + + const StringName *class_ptr = native_members.next(NULL); + while (class_ptr) { + + const ClassMembers &members = native_members.get(*class_ptr); + + const String *name = members.next(NULL); + while (name) { + + const lsp::DocumentSymbol *symbol = members.get(*name); + lsp::CompletionItem item = symbol->make_completion_item(); + item.data = JOIN_SYMBOLS(String(*class_ptr), *name); + native_member_completions.push_back(item.to_json()); + + name = members.next(name); + } + + class_ptr = native_members.next(class_ptr); + } + } +} + +Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) { + Dictionary params = p_params["textDocument"]; + String uri = params["uri"]; + String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri); + Array arr; + if (const Map<String, ExtendGDScriptParser *>::Element *parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) { + Vector<lsp::DocumentedSymbolInformation> list; + parser->get()->get_symbols().symbol_tree_as_list(uri, list); + for (int i = 0; i < list.size(); i++) { + arr.push_back(list[i].to_json()); + } + } + return arr; +} + +Array GDScriptTextDocument::completion(const Dictionary &p_params) { + + Array arr; + + lsp::CompletionParams params; + params.load(p_params); + Dictionary request_data = params.to_json(); + + List<ScriptCodeCompletionOption> options; + GDScriptLanguageProtocol::get_singleton()->get_workspace()->completion(params, &options); + + if (!options.empty()) { + + int i = 0; + arr.resize(options.size()); + + for (const List<ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) { + + const ScriptCodeCompletionOption &option = E->get(); + lsp::CompletionItem item; + item.label = option.display; + item.data = request_data; + + switch (option.kind) { + case ScriptCodeCompletionOption::KIND_ENUM: + item.kind = lsp::CompletionItemKind::Enum; + break; + case ScriptCodeCompletionOption::KIND_CLASS: + item.kind = lsp::CompletionItemKind::Class; + break; + case ScriptCodeCompletionOption::KIND_MEMBER: + item.kind = lsp::CompletionItemKind::Property; + break; + case ScriptCodeCompletionOption::KIND_FUNCTION: + item.kind = lsp::CompletionItemKind::Method; + break; + case ScriptCodeCompletionOption::KIND_SIGNAL: + item.kind = lsp::CompletionItemKind::Event; + break; + case ScriptCodeCompletionOption::KIND_CONSTANT: + item.kind = lsp::CompletionItemKind::Constant; + break; + case ScriptCodeCompletionOption::KIND_VARIABLE: + item.kind = lsp::CompletionItemKind::Variable; + break; + case ScriptCodeCompletionOption::KIND_FILE_PATH: + item.kind = lsp::CompletionItemKind::File; + break; + case ScriptCodeCompletionOption::KIND_NODE_PATH: + item.kind = lsp::CompletionItemKind::Snippet; + break; + case ScriptCodeCompletionOption::KIND_PLAIN_TEXT: + item.kind = lsp::CompletionItemKind::Text; + break; + } + + arr[i] = item.to_json(); + i++; + } + } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + + arr = native_member_completions.duplicate(); + + for (Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.front(); E; E = E->next()) { + + ExtendGDScriptParser *script = E->get(); + const Array &items = script->get_member_completions(); + + const int start_size = arr.size(); + arr.resize(start_size + items.size()); + for (int i = start_size; i < arr.size(); i++) { + arr[i] = items[i - start_size]; + } + } + } + return arr; +} + +Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { + + lsp::CompletionItem item; + item.load(p_params); + + lsp::CompletionParams params; + Variant data = p_params["data"]; + + const lsp::DocumentSymbol *symbol = NULL; + + if (data.get_type() == Variant::DICTIONARY) { + + params.load(p_params["data"]); + symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function); + + } else if (data.get_type() == Variant::STRING) { + + String query = data; + + Vector<String> param_symbols = query.split(SYMBOL_SEPERATOR, false); + + if (param_symbols.size() >= 2) { + + String class_ = param_symbols[0]; + StringName class_name = class_; + String member_name = param_symbols[param_symbols.size() - 1]; + String inner_class_name; + if (param_symbols.size() >= 3) { + inner_class_name = param_symbols[1]; + } + + if (const ClassMembers *members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members.getptr(class_name)) { + if (const lsp::DocumentSymbol *const *member = members->getptr(member_name)) { + symbol = *member; + } + } + + if (!symbol) { + if (const Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(class_name)) { + symbol = E->get()->get_member_symbol(member_name, inner_class_name); + } + } + } + } + + if (symbol) { + item.documentation = symbol->render(); + } + + if ((item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function) && !item.label.ends_with("):")) { + item.insertText = item.label + "("; + if (symbol && symbol->detail.find(",") == -1) { + item.insertText += ")"; + } + } else if (item.kind == lsp::CompletionItemKind::Event) { + if (params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter && (params.context.triggerCharacter == "(")) { + const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; + item.insertText = quote_style + item.label + quote_style; + } + } + + return item.to_json(true); +} + +Array GDScriptTextDocument::foldingRange(const Dictionary &p_params) { + Dictionary params = p_params["textDocument"]; + String path = params["uri"]; + Array arr; + return arr; +} + +Array GDScriptTextDocument::codeLens(const Dictionary &p_params) { + Array arr; + return arr; +} + +Variant GDScriptTextDocument::documentLink(const Dictionary &p_params) { + Variant ret; + return ret; +} + +Array GDScriptTextDocument::colorPresentation(const Dictionary &p_params) { + Array arr; + return arr; +} + +Variant GDScriptTextDocument::hover(const Dictionary &p_params) { + + lsp::TextDocumentPositionParams params; + params.load(p_params); + + const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params); + if (symbol) { + + lsp::Hover hover; + hover.contents = symbol->render(); + return hover.to_json(); + + } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + + Dictionary ret; + Array contents; + List<const lsp::DocumentSymbol *> list; + GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list); + for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { + if (const lsp::DocumentSymbol *s = E->get()) { + contents.push_back(s->render().value); + } + } + ret["contents"] = contents; + return ret; + } + + return Variant(); +} + +Array GDScriptTextDocument::definition(const Dictionary &p_params) { + Array arr; + + lsp::TextDocumentPositionParams params; + params.load(p_params); + + const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params); + if (symbol) { + lsp::Location location; + location.uri = symbol->uri; + location.range = symbol->range; + + const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri); + if (file_checker->file_exists(path)) { + arr.push_back(location.to_json()); + } else if (!symbol->native_class.empty() && GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { + String id; + switch (symbol->kind) { + case lsp::SymbolKind::Class: + id = "class_name:" + symbol->name; + break; + case lsp::SymbolKind::Constant: + id = "class_constant:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Property: + case lsp::SymbolKind::Variable: + id = "class_property:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Enum: + id = "class_enum:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Method: + case lsp::SymbolKind::Function: + id = "class_method:" + symbol->native_class + ":" + symbol->name; + break; + default: + id = "class_global:" + symbol->native_class + ":" + symbol->name; + break; + } + call_deferred("show_native_symbol_in_editor", id); + } + } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + + List<const lsp::DocumentSymbol *> list; + GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list); + for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { + + if (const lsp::DocumentSymbol *s = E->get()) { + if (!s->uri.empty()) { + lsp::Location location; + location.uri = s->uri; + location.range = s->range; + arr.push_back(location.to_json()); + } + } + } + } + + return arr; +} + +GDScriptTextDocument::GDScriptTextDocument() { + file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES); +} + +GDScriptTextDocument::~GDScriptTextDocument() { + memdelete(file_checker); +} + +void GDScriptTextDocument::sync_script_content(const String &p_uri, const String &p_content) { + String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_uri); + GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); +} + +void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) { + ScriptEditor::get_singleton()->call_deferred("_help_class_goto", p_symbol_id); + OS::get_singleton()->move_window_to_foreground(); +} diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h new file mode 100644 index 0000000000..d15022d2c4 --- /dev/null +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -0,0 +1,73 @@ +/*************************************************************************/ +/* gdscript_text_document.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_TEXT_DOCUMENT_H +#define GDSCRIPT_TEXT_DOCUMENT_H + +#include "core/os/file_access.h" +#include "core/reference.h" +#include "lsp.hpp" + +class GDScriptTextDocument : public Reference { + GDCLASS(GDScriptTextDocument, Reference) +protected: + static void _bind_methods(); + + FileAccess *file_checker; + + void didOpen(const Variant &p_param); + void didChange(const Variant &p_param); + + void sync_script_content(const String &p_path, const String &p_content); + void show_native_symbol_in_editor(const String &p_symbol_id); + + Array native_member_completions; + +private: + lsp::TextDocumentItem load_document_item(const Variant &p_param); + +public: + Array documentSymbol(const Dictionary &p_params); + Array completion(const Dictionary &p_params); + Dictionary resolve(const Dictionary &p_params); + Array foldingRange(const Dictionary &p_params); + Array codeLens(const Dictionary &p_params); + Variant documentLink(const Dictionary &p_params); + Array colorPresentation(const Dictionary &p_params); + Variant hover(const Dictionary &p_params); + Array definition(const Dictionary &p_params); + + void initialize(); + + GDScriptTextDocument(); + virtual ~GDScriptTextDocument(); +}; + +#endif diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp new file mode 100644 index 0000000000..1901daacff --- /dev/null +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -0,0 +1,504 @@ +/*************************************************************************/ +/* gdscript_workspace.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_workspace.h" +#include "../gdscript.h" +#include "../gdscript_parser.h" +#include "core/project_settings.h" +#include "core/script_language.h" +#include "editor/editor_help.h" +#include "gdscript_language_protocol.h" + +void GDScriptWorkspace::_bind_methods() { + ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol); + ClassDB::bind_method(D_METHOD("parse_script", "p_path", "p_content"), &GDScriptWorkspace::parse_script); + ClassDB::bind_method(D_METHOD("parse_local_script", "p_path"), &GDScriptWorkspace::parse_local_script); + ClassDB::bind_method(D_METHOD("get_file_path", "p_uri"), &GDScriptWorkspace::get_file_path); + ClassDB::bind_method(D_METHOD("get_file_uri", "p_path"), &GDScriptWorkspace::get_file_uri); + ClassDB::bind_method(D_METHOD("publish_diagnostics", "p_path"), &GDScriptWorkspace::publish_diagnostics); + ClassDB::bind_method(D_METHOD("generate_script_api", "p_path"), &GDScriptWorkspace::generate_script_api); +} + +void GDScriptWorkspace::remove_cache_parser(const String &p_path) { + Map<String, ExtendGDScriptParser *>::Element *parser = parse_results.find(p_path); + Map<String, ExtendGDScriptParser *>::Element *script = scripts.find(p_path); + if (parser && script) { + if (script->get() && script->get() == script->get()) { + memdelete(script->get()); + } else { + memdelete(script->get()); + memdelete(parser->get()); + } + parse_results.erase(p_path); + scripts.erase(p_path); + } else if (parser) { + memdelete(parser->get()); + parse_results.erase(p_path); + } else if (script) { + memdelete(script->get()); + scripts.erase(p_path); + } +} + +const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_class, const String &p_member) const { + + StringName class_name = p_class; + StringName empty; + + while (class_name != empty) { + if (const Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(class_name)) { + const lsp::DocumentSymbol &class_symbol = E->value(); + + if (p_member.empty()) { + return &class_symbol; + } else { + for (int i = 0; i < class_symbol.children.size(); i++) { + const lsp::DocumentSymbol &symbol = class_symbol.children[i]; + if (symbol.name == p_member) { + return &symbol; + } + } + } + } + class_name = ClassDB::get_parent_class(class_name); + } + + return NULL; +} + +const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_path) const { + const Map<String, ExtendGDScriptParser *>::Element *S = scripts.find(p_path); + if (S) { + return &(S->get()->get_symbols()); + } + return NULL; +} + +void GDScriptWorkspace::reload_all_workspace_scripts() { + List<String> pathes; + list_script_files("res://", pathes); + for (List<String>::Element *E = pathes.front(); E; E = E->next()) { + const String &path = E->get(); + Error err; + String content = FileAccess::get_file_as_string(path, &err); + ERR_CONTINUE(err != OK); + err = parse_script(path, content); + + if (err != OK) { + Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(path); + String err_msg = "Failed parse script " + path; + if (S) { + err_msg += "\n" + S->get()->get_error(); + } + ERR_EXPLAIN(err_msg); + ERR_CONTINUE(err != OK); + } + } +} + +void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String> &r_files) { + Error err; + DirAccessRef dir = DirAccess::open(p_root_dir, &err); + if (OK == err) { + dir->list_dir_begin(); + String file_name = dir->get_next(); + while (file_name.length()) { + if (dir->current_is_dir() && file_name != "." && file_name != ".." && file_name != "./") { + list_script_files(p_root_dir.plus_file(file_name), r_files); + } else if (file_name.ends_with(".gd")) { + String script_file = p_root_dir.plus_file(file_name); + r_files.push_back(script_file); + } + file_name = dir->get_next(); + } + } +} + +ExtendGDScriptParser *GDScriptWorkspace::get_parse_successed_script(const String &p_path) { + const Map<String, ExtendGDScriptParser *>::Element *S = scripts.find(p_path); + if (!S) { + parse_local_script(p_path); + S = scripts.find(p_path); + } + if (S) { + return S->get(); + } + return NULL; +} + +ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) { + const Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(p_path); + if (!S) { + parse_local_script(p_path); + S = parse_results.find(p_path); + } + if (S) { + return S->get(); + } + return NULL; +} + +Array GDScriptWorkspace::symbol(const Dictionary &p_params) { + String query = p_params["query"]; + Array arr; + if (!query.empty()) { + for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { + Vector<lsp::DocumentedSymbolInformation> script_symbols; + E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols); + for (int i = 0; i < script_symbols.size(); ++i) { + if (query.is_subsequence_ofi(script_symbols[i].name)) { + arr.push_back(script_symbols[i].to_json()); + } + } + } + } + return arr; +} + +Error GDScriptWorkspace::initialize() { + if (initialized) return OK; + + DocData *doc = EditorHelp::get_doc_data(); + for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { + + const DocData::ClassDoc &class_data = E->value(); + lsp::DocumentSymbol class_symbol; + String class_name = E->key(); + class_symbol.name = class_name; + class_symbol.native_class = class_name; + class_symbol.kind = lsp::SymbolKind::Class; + class_symbol.detail = String("<Native> class ") + class_name; + if (!class_data.inherits.empty()) { + class_symbol.detail += " extends " + class_data.inherits; + } + class_symbol.documentation = ExtendGDScriptParser::marked_documentation(class_data.brief_description) + "\n" + ExtendGDScriptParser::marked_documentation(class_data.description); + + for (int i = 0; i < class_data.constants.size(); i++) { + const DocData::ConstantDoc &const_data = class_data.constants[i]; + lsp::DocumentSymbol symbol; + symbol.name = const_data.name; + symbol.native_class = class_name; + symbol.kind = lsp::SymbolKind::Constant; + symbol.detail = "const " + class_name + "." + const_data.name; + if (const_data.enumeration.length()) { + symbol.detail += ": " + const_data.enumeration; + } + symbol.detail += " = " + const_data.value; + symbol.documentation = ExtendGDScriptParser::marked_documentation(const_data.description); + class_symbol.children.push_back(symbol); + } + + Vector<DocData::PropertyDoc> properties; + properties.append_array(class_data.properties); + const int theme_prop_start_idx = properties.size(); + properties.append_array(class_data.theme_properties); + + for (int i = 0; i < class_data.properties.size(); i++) { + const DocData::PropertyDoc &data = class_data.properties[i]; + lsp::DocumentSymbol symbol; + symbol.name = data.name; + symbol.native_class = class_name; + symbol.kind = lsp::SymbolKind::Property; + symbol.detail = String(i >= theme_prop_start_idx ? "<Theme> var" : "var") + " " + class_name + "." + data.name; + if (data.enumeration.length()) { + symbol.detail += ": " + data.enumeration; + } else { + symbol.detail += ": " + data.type; + } + symbol.documentation = ExtendGDScriptParser::marked_documentation(data.description); + class_symbol.children.push_back(symbol); + } + + Vector<DocData::MethodDoc> methods_signals; + methods_signals.append_array(class_data.methods); + const int signal_start_idx = methods_signals.size(); + methods_signals.append_array(class_data.signals); + + for (int i = 0; i < methods_signals.size(); i++) { + const DocData::MethodDoc &data = methods_signals[i]; + + lsp::DocumentSymbol symbol; + symbol.name = data.name; + symbol.native_class = class_name; + symbol.kind = i >= signal_start_idx ? lsp::SymbolKind::Event : lsp::SymbolKind::Method; + + String params = ""; + bool arg_default_value_started = false; + for (int j = 0; j < data.arguments.size(); j++) { + const DocData::ArgumentDoc &arg = data.arguments[j]; + if (!arg_default_value_started && !arg.default_value.empty()) { + arg_default_value_started = true; + } + String arg_str = arg.name + ": " + arg.type; + if (arg_default_value_started) { + arg_str += " = " + arg.default_value; + } + if (j < data.arguments.size() - 1) { + arg_str += ", "; + } + params += arg_str; + } + if (data.qualifiers.find("vararg") != -1) { + params += params.empty() ? "..." : ", ..."; + } + + symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + data.return_type; + symbol.documentation = ExtendGDScriptParser::marked_documentation(data.description); + class_symbol.children.push_back(symbol); + } + + native_symbols.insert(class_name, class_symbol); + } + + reload_all_workspace_scripts(); + + if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + for (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.front(); E; E = E->next()) { + ClassMembers members; + const lsp::DocumentSymbol &class_symbol = E->get(); + for (int i = 0; i < class_symbol.children.size(); i++) { + const lsp::DocumentSymbol &symbol = class_symbol.children[i]; + members.set(symbol.name, &symbol); + } + native_members.set(E->key(), members); + } + + // cache member completions + for (Map<String, ExtendGDScriptParser *>::Element *S = scripts.front(); S; S = S->next()) { + S->get()->get_member_completions(); + } + } + + return OK; +} + +Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_content) { + + ExtendGDScriptParser *parser = memnew(ExtendGDScriptParser); + Error err = parser->parse(p_content, p_path); + Map<String, ExtendGDScriptParser *>::Element *last_parser = parse_results.find(p_path); + Map<String, ExtendGDScriptParser *>::Element *last_script = scripts.find(p_path); + + if (err == OK) { + + remove_cache_parser(p_path); + parse_results[p_path] = parser; + scripts[p_path] = parser; + + } else { + if (last_parser && last_script && last_parser->get() != last_script->get()) { + memdelete(last_parser->get()); + } + parse_results[p_path] = parser; + } + + publish_diagnostics(p_path); + + return err; +} + +Error GDScriptWorkspace::parse_local_script(const String &p_path) { + Error err; + String content = FileAccess::get_file_as_string(p_path, &err); + if (err == OK) { + err = parse_script(p_path, content); + } + return err; +} + +String GDScriptWorkspace::get_file_path(const String &p_uri) const { + String path = p_uri; + path = path.replace(root_uri + "/", "res://"); + path = path.http_unescape(); + return path; +} + +String GDScriptWorkspace::get_file_uri(const String &p_path) const { + String uri = p_path; + uri = uri.replace("res://", root_uri + "/"); + return uri; +} + +void GDScriptWorkspace::publish_diagnostics(const String &p_path) { + Dictionary params; + Array errors; + const Map<String, ExtendGDScriptParser *>::Element *ele = parse_results.find(p_path); + if (ele) { + const Vector<lsp::Diagnostic> &list = ele->get()->get_diagnostics(); + errors.resize(list.size()); + for (int i = 0; i < list.size(); ++i) { + errors[i] = list[i].to_json(); + } + } + params["diagnostics"] = errors; + params["uri"] = get_file_uri(p_path); + GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics", params); +} + +void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options) { + + String path = get_file_path(p_params.textDocument.uri); + String call_hint; + bool forced = false; + + if (const ExtendGDScriptParser *parser = get_parse_result(path)) { + String code = parser->get_text_for_completion(p_params.position); + GDScriptLanguage::get_singleton()->complete_code(code, path, NULL, r_options, forced, call_hint); + } +} + +const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_requred) { + + const lsp::DocumentSymbol *symbol = NULL; + + String path = get_file_path(p_doc_pos.textDocument.uri); + if (const ExtendGDScriptParser *parser = get_parse_result(path)) { + + String symbol_identifier = p_symbol_name; + Vector<String> identifier_parts = symbol_identifier.split("("); + if (identifier_parts.size()) { + symbol_identifier = identifier_parts[0]; + } + + lsp::Position pos = p_doc_pos.position; + if (symbol_identifier.empty()) { + Vector2i offset; + symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); + pos.character += offset.y; + } + + if (!symbol_identifier.empty()) { + + if (ScriptServer::is_global_class(symbol_identifier)) { + + String class_path = ScriptServer::get_global_class_path(symbol_identifier); + symbol = get_script_symbol(class_path); + + } else { + + ScriptLanguage::LookupResult ret; + if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_requred), symbol_identifier, path, NULL, ret)) { + + if (ret.type == ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION) { + + String target_script_path = path; + if (!ret.script.is_null()) { + target_script_path = ret.script->get_path(); + } + + if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) { + symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location)); + } + + } else { + + String member = ret.class_member; + if (member.empty() && symbol_identifier != ret.class_name) { + member = symbol_identifier; + } + symbol = get_native_symbol(ret.class_name, member); + } + } else { + symbol = parser->get_member_symbol(symbol_identifier); + } + } + } + } + + return symbol; +} + +void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list) { + + String path = get_file_path(p_doc_pos.textDocument.uri); + if (const ExtendGDScriptParser *parser = get_parse_result(path)) { + + String symbol_identifier; + Vector2i offset; + symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); + + const StringName *class_ptr = native_members.next(NULL); + while (class_ptr) { + const ClassMembers &members = native_members.get(*class_ptr); + if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { + r_list.push_back(*symbol); + } + class_ptr = native_members.next(class_ptr); + } + + for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { + const ExtendGDScriptParser *script = E->get(); + const ClassMembers &members = script->get_members(); + if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { + r_list.push_back(*symbol); + } + + const HashMap<String, ClassMembers> &inner_classes = script->get_inner_classes(); + const String *_class = inner_classes.next(NULL); + while (_class) { + + const ClassMembers *inner_class = inner_classes.getptr(*_class); + if (const lsp::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) { + r_list.push_back(*symbol); + } + + _class = inner_classes.next(_class); + } + } + } +} + +Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) { + Dictionary api; + if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) { + api = parser->generate_api(); + } + return api; +} + +GDScriptWorkspace::GDScriptWorkspace() { + ProjectSettings::get_singleton()->get_resource_path(); +} + +GDScriptWorkspace::~GDScriptWorkspace() { + Set<String> cached_parsers; + + for (Map<String, ExtendGDScriptParser *>::Element *E = parse_results.front(); E; E = E->next()) { + cached_parsers.insert(E->key()); + } + + for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { + cached_parsers.insert(E->key()); + } + + for (Set<String>::Element *E = cached_parsers.front(); E; E = E->next()) { + remove_cache_parser(E->get()); + } +} diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h new file mode 100644 index 0000000000..adce169d4b --- /dev/null +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* gdscript_workspace.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_WORKSPACE_H +#define GDSCRIPT_WORKSPACE_H + +#include "../gdscript_parser.h" +#include "core/variant.h" +#include "gdscript_extend_parser.h" +#include "lsp.hpp" + +class GDScriptWorkspace : public Reference { + GDCLASS(GDScriptWorkspace, Reference); + +protected: + static void _bind_methods(); + void remove_cache_parser(const String &p_path); + bool initialized = false; + Map<StringName, lsp::DocumentSymbol> native_symbols; + + const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const; + const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const; + + void reload_all_workspace_scripts(); + + ExtendGDScriptParser *get_parse_successed_script(const String &p_path); + ExtendGDScriptParser *get_parse_result(const String &p_path); + + void strip_flat_symbols(const String &p_branch); + void list_script_files(const String &p_root_dir, List<String> &r_files); + +public: + String root; + String root_uri; + + Map<String, ExtendGDScriptParser *> scripts; + Map<String, ExtendGDScriptParser *> parse_results; + HashMap<StringName, ClassMembers> native_members; + +public: + Array symbol(const Dictionary &p_params); + +public: + Error initialize(); + + Error parse_script(const String &p_path, const String &p_content); + Error parse_local_script(const String &p_path); + + String get_file_path(const String &p_uri) const; + String get_file_uri(const String &p_path) const; + + void publish_diagnostics(const String &p_path); + void completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options); + + const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false); + void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list); + + Dictionary generate_script_api(const String &p_path); + + GDScriptWorkspace(); + ~GDScriptWorkspace(); +}; + +#endif diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp new file mode 100644 index 0000000000..3e57b6ee7e --- /dev/null +++ b/modules/gdscript/language_server/lsp.hpp @@ -0,0 +1,1506 @@ +/*************************************************************************/ +/* lsp.hpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_LSP_H +#define GODOT_LSP_H + +#include "core/variant.h" + +namespace lsp { + +typedef String DocumentUri; + +/** + * Text documents are identified using a URI. On the protocol level, URIs are passed as strings. + */ +struct TextDocumentIdentifier { + /** + * The text document's URI. + */ + DocumentUri uri; + + _FORCE_INLINE_ void load(const Dictionary &p_params) { + uri = p_params["uri"]; + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["uri"] = uri; + return dict; + } +}; + +/** + * Position in a text document expressed as zero-based line and zero-based character offset. + * A position is between two characters like an ‘insert’ cursor in a editor. + * Special values like for example -1 to denote the end of a line are not supported. + */ +struct Position { + /** + * Line position in a document (zero-based). + */ + int line = 0; + + /** + * Character offset on a line in a document (zero-based). Assuming that the line is + * represented as a string, the `character` value represents the gap between the + * `character` and `character + 1`. + * + * If the character value is greater than the line length it defaults back to the + * line length. + */ + int character = 0; + + _FORCE_INLINE_ void load(const Dictionary &p_params) { + line = p_params["line"]; + character = p_params["character"]; + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["line"] = line; + dict["character"] = character; + return dict; + } +}; + +/** + * A range in a text document expressed as (zero-based) start and end positions. + * A range is comparable to a selection in an editor. Therefore the end position is exclusive. + * If you want to specify a range that contains a line including the line ending character(s) then use an end position denoting the start of the next line. + */ +struct Range { + /** + * The range's start position. + */ + Position start; + + /** + * The range's end position. + */ + Position end; + + _FORCE_INLINE_ void load(const Dictionary &p_params) { + start.load(p_params["start"]); + end.load(p_params["end"]); + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["start"] = start.to_json(); + dict["end"] = end.to_json(); + return dict; + } +}; + +/** + * Represents a location inside a resource, such as a line inside a text file. + */ +struct Location { + DocumentUri uri; + Range range; + + _FORCE_INLINE_ void load(const Dictionary &p_params) { + uri = p_params["uri"]; + range.load(p_params["range"]); + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["uri"] = uri; + dict["range"] = range.to_json(); + return dict; + } +}; + +/** + * Represents a link between a source and a target location. + */ +struct LocationLink { + + /** + * Span of the origin of this link. + * + * Used as the underlined span for mouse interaction. Defaults to the word range at + * the mouse position. + */ + Range *originSelectionRange = NULL; + + /** + * The target resource identifier of this link. + */ + String targetUri; + + /** + * The full target range of this link. If the target for example is a symbol then target range is the + * range enclosing this symbol not including leading/trailing whitespace but everything else + * like comments. This information is typically used to highlight the range in the editor. + */ + Range targetRange; + + /** + * The range that should be selected and revealed when this link is being followed, e.g the name of a function. + * Must be contained by the the `targetRange`. See also `DocumentSymbol#range` + */ + Range targetSelectionRange; +}; + +/** + * A parameter literal used in requests to pass a text document and a position inside that document. + */ +struct TextDocumentPositionParams { + /** + * The text document. + */ + TextDocumentIdentifier textDocument; + + /** + * The position inside the text document. + */ + Position position; + + _FORCE_INLINE_ void load(const Dictionary &p_params) { + textDocument.load(p_params["textDocument"]); + position.load(p_params["position"]); + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["textDocument"] = textDocument.to_json(); + dict["position"] = position.to_json(); + return dict; + } +}; + +/** + * A textual edit applicable to a text document. + */ +struct TextEdit { + /** + * The range of the text document to be manipulated. To insert + * text into a document create a range where start === end. + */ + Range range; + + /** + * The string to be inserted. For delete operations use an + * empty string. + */ + String newText; +}; + +/** + * Represents a reference to a command. + * Provides a title which will be used to represent a command in the UI. + * Commands are identified by a string identifier. + * The recommended way to handle commands is to implement their execution on the server side if the client and server provides the corresponding capabilities. + * Alternatively the tool extension code could handle the command. The protocol currently doesn’t specify a set of well-known commands. + */ +struct Command { + /** + * Title of the command, like `save`. + */ + String title; + /** + * The identifier of the actual command handler. + */ + String command; + /** + * Arguments that the command handler should be + * invoked with. + */ + Array arguments; + + Dictionary to_json() const { + Dictionary dict; + dict["title"] = title; + dict["command"] = command; + if (arguments.size()) dict["arguments"] = arguments; + return dict; + } +}; + +namespace TextDocumentSyncKind { +/** + * Documents should not be synced at all. + */ +static const int None = 0; + +/** + * Documents are synced by always sending the full content + * of the document. + */ +static const int Full = 1; + +/** + * Documents are synced by sending the full content on open. + * After that only incremental updates to the document are + * send. + */ +static const int Incremental = 2; +}; // namespace TextDocumentSyncKind + +/** + * Completion options. + */ +struct CompletionOptions { + /** + * The server provides support to resolve additional + * information for a completion item. + */ + bool resolveProvider = true; + + /** + * The characters that trigger completion automatically. + */ + Vector<String> triggerCharacters; + + CompletionOptions() { + triggerCharacters.push_back("."); + triggerCharacters.push_back("$"); + triggerCharacters.push_back("'"); + triggerCharacters.push_back("\""); + triggerCharacters.push_back("("); + triggerCharacters.push_back(","); + } + + Dictionary to_json() const { + Dictionary dict; + dict["resolveProvider"] = resolveProvider; + dict["triggerCharacters"] = triggerCharacters; + return dict; + } +}; + +/** + * Signature help options. + */ +struct SignatureHelpOptions { + /** + * The characters that trigger signature help + * automatically. + */ + Vector<String> triggerCharacters; + + Dictionary to_json() { + Dictionary dict; + dict["triggerCharacters"] = triggerCharacters; + return dict; + } +}; + +/** + * Code Lens options. + */ +struct CodeLensOptions { + /** + * Code lens has a resolve provider as well. + */ + bool resolveProvider = false; + + Dictionary to_json() { + Dictionary dict; + dict["resolveProvider"] = resolveProvider; + return dict; + } +}; + +/** + * Rename options + */ +struct RenameOptions { + /** + * Renames should be checked and tested before being executed. + */ + bool prepareProvider = false; + + Dictionary to_json() { + Dictionary dict; + dict["prepareProvider"] = prepareProvider; + return dict; + } +}; + +/** + * Document link options. + */ +struct DocumentLinkOptions { + /** + * Document links have a resolve provider as well. + */ + bool resolveProvider = false; + + Dictionary to_json() { + Dictionary dict; + dict["resolveProvider"] = resolveProvider; + return dict; + } +}; + +/** + * Execute command options. + */ +struct ExecuteCommandOptions { + /** + * The commands to be executed on the server + */ + Vector<String> commands; + + Dictionary to_json() { + Dictionary dict; + dict["commands"] = commands; + return dict; + } +}; + +/** + * Save options. + */ +struct SaveOptions { + /** + * The client is supposed to include the content on save. + */ + bool includeText = true; + + Dictionary to_json() { + Dictionary dict; + dict["includeText"] = includeText; + return dict; + } +}; + +/** + * Color provider options. + */ +struct ColorProviderOptions { + Dictionary to_json() { + Dictionary dict; + return dict; + } +}; + +/** + * Folding range provider options. + */ +struct FoldingRangeProviderOptions { + Dictionary to_json() { + Dictionary dict; + return dict; + } +}; + +struct TextDocumentSyncOptions { + /** + * Open and close notifications are sent to the server. If omitted open close notification should not + * be sent. + */ + bool openClose = true; + + /** + * Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full + * and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None. + */ + int change = TextDocumentSyncKind::Full; + + /** + * If present will save notifications are sent to the server. If omitted the notification should not be + * sent. + */ + bool willSave = false; + + /** + * If present will save wait until requests are sent to the server. If omitted the request should not be + * sent. + */ + bool willSaveWaitUntil = false; + + /** + * If present save notifications are sent to the server. If omitted the notification should not be + * sent. + */ + SaveOptions save; + + Dictionary to_json() { + Dictionary dict; + dict["willSaveWaitUntil"] = willSaveWaitUntil; + dict["willSave"] = willSave; + dict["openClose"] = openClose; + dict["change"] = change; + dict["change"] = save.to_json(); + return dict; + } +}; + +/** + * Static registration options to be returned in the initialize request. + */ +struct StaticRegistrationOptions { + /** + * The id used to register the request. The id can be used to deregister + * the request again. See also Registration#id. + */ + String id; +}; + +/** + * Format document on type options. + */ +struct DocumentOnTypeFormattingOptions { + /** + * A character on which formatting should be triggered, like `}`. + */ + String firstTriggerCharacter; + + /** + * More trigger characters. + */ + Vector<String> moreTriggerCharacter; + + Dictionary to_json() { + Dictionary dict; + dict["firstTriggerCharacter"] = firstTriggerCharacter; + dict["moreTriggerCharacter"] = moreTriggerCharacter; + return dict; + } +}; + +struct TextDocumentItem { + /** + * The text document's URI. + */ + DocumentUri uri; + + /** + * The text document's language identifier. + */ + String languageId; + + /** + * The version number of this document (it will increase after each + * change, including undo/redo). + */ + int version; + + /** + * The content of the opened text document. + */ + String text; + + void load(const Dictionary &p_dict) { + uri = p_dict["uri"]; + languageId = p_dict["languageId"]; + version = p_dict["version"]; + text = p_dict["text"]; + } + + Dictionary to_json() const { + Dictionary dict; + dict["uri"] = uri; + dict["languageId"] = languageId; + dict["version"] = version; + dict["text"] = text; + return dict; + } +}; + +/** + * An event describing a change to a text document. If range and rangeLength are omitted + * the new text is considered to be the full content of the document. + */ +struct TextDocumentContentChangeEvent { + /** + * The range of the document that changed. + */ + Range range; + + /** + * The length of the range that got replaced. + */ + int rangeLength; + + /** + * The new text of the range/document. + */ + String text; + + void load(const Dictionary &p_params) { + text = p_params["text"]; + rangeLength = p_params["rangeLength"]; + range.load(p_params["range"]); + } +}; + +namespace DiagnosticSeverity { +/** + * Reports an error. + */ +static const int Error = 1; +/** + * Reports a warning. + */ +static const int Warning = 2; +/** + * Reports an information. + */ +static const int Information = 3; +/** + * Reports a hint. + */ +static const int Hint = 4; +}; // namespace DiagnosticSeverity + +/** + * Represents a related message and source code location for a diagnostic. This should be + * used to point to code locations that cause or related to a diagnostics, e.g when duplicating + * a symbol in a scope. + */ +struct DiagnosticRelatedInformation { + /** + * The location of this related diagnostic information. + */ + Location location; + + /** + * The message of this related diagnostic information. + */ + String message; + + Dictionary to_json() const { + Dictionary dict; + dict["location"] = location.to_json(), + dict["message"] = message; + return dict; + } +}; + +/** + * Represents a diagnostic, such as a compiler error or warning. + * Diagnostic objects are only valid in the scope of a resource. + */ +struct Diagnostic { + /** + * The range at which the message applies. + */ + Range range; + + /** + * The diagnostic's severity. Can be omitted. If omitted it is up to the + * client to interpret diagnostics as error, warning, info or hint. + */ + int severity; + + /** + * The diagnostic's code, which might appear in the user interface. + */ + int code; + + /** + * A human-readable string describing the source of this + * diagnostic, e.g. 'typescript' or 'super lint'. + */ + String source; + + /** + * The diagnostic's message. + */ + String message; + + /** + * An array of related diagnostic information, e.g. when symbol-names within + * a scope collide all definitions can be marked via this property. + */ + Vector<DiagnosticRelatedInformation> relatedInformation; + + Dictionary to_json() const { + Dictionary dict; + dict["range"] = range.to_json(); + dict["code"] = code; + dict["severity"] = severity; + dict["message"] = message; + dict["source"] = source; + if (!relatedInformation.empty()) { + Array arr; + arr.resize(relatedInformation.size()); + for (int i = 0; i < relatedInformation.size(); i++) { + arr[i] = relatedInformation[i].to_json(); + } + dict["relatedInformation"] = arr; + } + return dict; + } +}; + +/** + * Describes the content type that a client supports in various + * result literals like `Hover`, `ParameterInfo` or `CompletionItem`. + * + * Please note that `MarkupKinds` must not start with a `$`. This kinds + * are reserved for internal usage. + */ +namespace MarkupKind { +static const String PlainText = "plaintext"; +static const String Markdown = "markdown"; +}; // namespace MarkupKind + +/** + * A `MarkupContent` literal represents a string value which content is interpreted base on its + * kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. + * + * If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. + * See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting + * + * Here is an example how such a string can be constructed using JavaScript / TypeScript: + * ```typescript + * let markdown: MarkdownContent = { + * kind: MarkupKind.Markdown, + * value: [ + * '# Header', + * 'Some text', + * '```typescript', + * 'someCode();', + * '```' + * ].join('\n') + * }; + * ``` + * + * *Please Note* that clients might sanitize the return markdown. A client could decide to + * remove HTML from the markdown to avoid script execution. + */ +struct MarkupContent { + /** + * The type of the Markup + */ + String kind; + + /** + * The content itself + */ + String value; + + MarkupContent() { + kind = MarkupKind::Markdown; + } + + MarkupContent(const String &p_value) { + value = p_value; + kind = MarkupKind::Markdown; + } + + Dictionary to_json() const { + Dictionary dict; + dict["kind"] = kind; + dict["value"] = value; + return dict; + } +}; + +/** + * The kind of a completion entry. + */ +namespace CompletionItemKind { +static const int Text = 1; +static const int Method = 2; +static const int Function = 3; +static const int Constructor = 4; +static const int Field = 5; +static const int Variable = 6; +static const int Class = 7; +static const int Interface = 8; +static const int Module = 9; +static const int Property = 10; +static const int Unit = 11; +static const int Value = 12; +static const int Enum = 13; +static const int Keyword = 14; +static const int Snippet = 15; +static const int Color = 16; +static const int File = 17; +static const int Reference = 18; +static const int Folder = 19; +static const int EnumMember = 20; +static const int Constant = 21; +static const int Struct = 22; +static const int Event = 23; +static const int Operator = 24; +static const int TypeParameter = 25; +}; // namespace CompletionItemKind + +/** + * Defines whether the insert text in a completion item should be interpreted as + * plain text or a snippet. + */ +namespace InsertTextFormat { +/** + * The primary text to be inserted is treated as a plain string. + */ +static const int PlainText = 1; + +/** + * The primary text to be inserted is treated as a snippet. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are linked, + * that is typing in one will update others too. + */ +static const int Snippet = 2; +}; // namespace InsertTextFormat + +struct CompletionItem { + /** + * The label of this completion item. By default + * also the text that is inserted when selecting + * this completion. + */ + String label; + + /** + * The kind of this completion item. Based of the kind + * an icon is chosen by the editor. The standardized set + * of available values is defined in `CompletionItemKind`. + */ + int kind; + + /** + * A human-readable string with additional information + * about this item, like type or symbol information. + */ + String detail; + + /** + * A human-readable string that represents a doc-comment. + */ + MarkupContent documentation; + + /** + * Indicates if this item is deprecated. + */ + bool deprecated = false; + + /** + * Select this item when showing. + * + * *Note* that only one completion item can be selected and that the + * tool / client decides which item that is. The rule is that the *first* + * item of those that match best is selected. + */ + bool preselect = false; + + /** + * A string that should be used when comparing this item + * with other items. When `falsy` the label is used. + */ + String sortText; + + /** + * A string that should be used when filtering a set of + * completion items. When `falsy` the label is used. + */ + String filterText; + + /** + * A string that should be inserted into a document when selecting + * this completion. When `falsy` the label is used. + * + * The `insertText` is subject to interpretation by the client side. + * Some tools might not take the string literally. For example + * VS Code when code complete is requested in this example `con<cursor position>` + * and a completion item with an `insertText` of `console` is provided it + * will only insert `sole`. Therefore it is recommended to use `textEdit` instead + * since it avoids additional client side interpretation. + * + * @deprecated Use textEdit instead. + */ + String insertText; + + /** + * The format of the insert text. The format applies to both the `insertText` property + * and the `newText` property of a provided `textEdit`. + */ + int insertTextFormat; + + /** + * An edit which is applied to a document when selecting this completion. When an edit is provided the value of + * `insertText` is ignored. + * + * *Note:* The range of the edit must be a single line range and it must contain the position at which completion + * has been requested. + */ + TextEdit textEdit; + + /** + * An optional array of additional text edits that are applied when + * selecting this completion. Edits must not overlap (including the same insert position) + * with the main edit nor with themselves. + * + * Additional text edits should be used to change text unrelated to the current cursor position + * (for example adding an import statement at the top of the file if the completion item will + * insert an unqualified type). + */ + Vector<TextEdit> additionalTextEdits; + + /** + * An optional set of characters that when pressed while this completion is active will accept it first and + * then type that character. *Note* that all commit characters should have `length=1` and that superfluous + * characters will be ignored. + */ + Vector<String> commitCharacters; + + /** + * An optional command that is executed *after* inserting this completion. *Note* that + * additional modifications to the current document should be described with the + * additionalTextEdits-property. + */ + Command command; + + /** + * A data entry field that is preserved on a completion item between + * a completion and a completion resolve request. + */ + Variant data; + + _FORCE_INLINE_ Dictionary to_json(bool resolved = false) const { + Dictionary dict; + dict["label"] = label; + dict["kind"] = kind; + dict["data"] = data; + if (resolved) { + dict["insertText"] = insertText; + dict["detail"] = detail; + dict["documentation"] = documentation.to_json(); + dict["deprecated"] = deprecated; + dict["preselect"] = preselect; + dict["sortText"] = sortText; + dict["filterText"] = filterText; + if (commitCharacters.size()) dict["commitCharacters"] = commitCharacters; + dict["command"] = command.to_json(); + } + return dict; + } + + void load(const Dictionary &p_dict) { + if (p_dict.has("label")) label = p_dict["label"]; + if (p_dict.has("kind")) kind = p_dict["kind"]; + if (p_dict.has("detail")) detail = p_dict["detail"]; + if (p_dict.has("documentation")) { + Variant doc = p_dict["documentation"]; + if (doc.get_type() == Variant::STRING) { + documentation.value = doc; + } else if (doc.get_type() == Variant::DICTIONARY) { + Dictionary v = doc; + documentation.value = v["value"]; + } + } + if (p_dict.has("deprecated")) deprecated = p_dict["deprecated"]; + if (p_dict.has("preselect")) preselect = p_dict["preselect"]; + if (p_dict.has("sortText")) sortText = p_dict["sortText"]; + if (p_dict.has("filterText")) filterText = p_dict["filterText"]; + if (p_dict.has("insertText")) insertText = p_dict["insertText"]; + if (p_dict.has("data")) data = p_dict["data"]; + } +}; + +/** + * Represents a collection of [completion items](#CompletionItem) to be presented + * in the editor. + */ +struct CompletionList { + /** + * This list it not complete. Further typing should result in recomputing + * this list. + */ + bool isIncomplete; + + /** + * The completion items. + */ + Vector<CompletionItem> items; +}; + +/** + * A symbol kind. + */ +namespace SymbolKind { +static const int File = 1; +static const int Module = 2; +static const int Namespace = 3; +static const int Package = 4; +static const int Class = 5; +static const int Method = 6; +static const int Property = 7; +static const int Field = 8; +static const int Constructor = 9; +static const int Enum = 10; +static const int Interface = 11; +static const int Function = 12; +static const int Variable = 13; +static const int Constant = 14; +static const int String = 15; +static const int Number = 16; +static const int Boolean = 17; +static const int Array = 18; +static const int Object = 19; +static const int Key = 20; +static const int Null = 21; +static const int EnumMember = 22; +static const int Struct = 23; +static const int Event = 24; +static const int Operator = 25; +static const int TypeParameter = 26; +}; // namespace SymbolKind + +/** + * Represents information about programming constructs like variables, classes, + * interfaces etc. + */ +struct SymbolInformation { + /** + * The name of this symbol. + */ + String name; + + /** + * The kind of this symbol. + */ + int kind = SymbolKind::File; + + /** + * Indicates if this symbol is deprecated. + */ + bool deprecated = false; + + /** + * The location of this symbol. The location's range is used by a tool + * to reveal the location in the editor. If the symbol is selected in the + * tool the range's start information is used to position the cursor. So + * the range usually spans more then the actual symbol's name and does + * normally include things like visibility modifiers. + * + * The range doesn't have to denote a node range in the sense of a abstract + * syntax tree. It can therefore not be used to re-construct a hierarchy of + * the symbols. + */ + Location location; + + /** + * The name of the symbol containing this symbol. This information is for + * user interface purposes (e.g. to render a qualifier in the user interface + * if necessary). It can't be used to re-infer a hierarchy for the document + * symbols. + */ + String containerName; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["name"] = name; + dict["kind"] = kind; + dict["deprecated"] = deprecated; + dict["location"] = location.to_json(); + dict["containerName"] = containerName; + return dict; + } +}; + +struct DocumentedSymbolInformation : public SymbolInformation { + /** + * A human-readable string with additional information + */ + String detail; + + /** + * A human-readable string that represents a doc-comment. + */ + String documentation; +}; + +/** + * Represents programming constructs like variables, classes, interfaces etc. that appear in a document. Document symbols can be + * hierarchical and they have two ranges: one that encloses its definition and one that points to its most interesting range, + * e.g. the range of an identifier. + */ +struct DocumentSymbol { + + /** + * The name of this symbol. Will be displayed in the user interface and therefore must not be + * an empty string or a string only consisting of white spaces. + */ + String name; + + /** + * More detail for this symbol, e.g the signature of a function. + */ + String detail; + + /** + * Documentation for this symbol + */ + String documentation; + + /** + * Class name for the native symbols + */ + String native_class; + + /** + * The kind of this symbol. + */ + int kind = SymbolKind::File; + + /** + * Indicates if this symbol is deprecated. + */ + bool deprecated = false; + + /** + * The range enclosing this symbol not including leading/trailing whitespace but everything else + * like comments. This information is typically used to determine if the clients cursor is + * inside the symbol to reveal in the symbol in the UI. + */ + Range range; + + /** + * The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. + * Must be contained by the `range`. + */ + Range selectionRange; + + DocumentUri uri; + String script_path; + + /** + * Children of this symbol, e.g. properties of a class. + */ + Vector<DocumentSymbol> children; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["name"] = name; + dict["detail"] = detail; + dict["kind"] = kind; + dict["deprecated"] = deprecated; + dict["range"] = range.to_json(); + dict["selectionRange"] = selectionRange.to_json(); + Array arr; + arr.resize(children.size()); + for (int i = 0; i < children.size(); i++) { + arr[i] = children[i].to_json(); + } + dict["children"] = arr; + return dict; + } + + void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const { + DocumentedSymbolInformation si; + if (p_join_name && !p_container.empty()) { + si.name = p_container + ">" + name; + } else { + si.name = name; + } + si.kind = kind; + si.containerName = p_container; + si.deprecated = deprecated; + si.location.uri = p_uri; + si.location.range = range; + si.detail = detail; + si.documentation = documentation; + r_list.push_back(si); + for (int i = 0; i < children.size(); i++) { + children[i].symbol_tree_as_list(p_uri, r_list, si.name, p_join_name); + } + } + + _FORCE_INLINE_ MarkupContent render() const { + MarkupContent markdown; + if (detail.length()) { + markdown.value = "\t" + detail + "\n\n"; + } + if (documentation.length()) { + markdown.value += documentation + "\n\n"; + } + if (script_path.length()) { + markdown.value += "Defined in [" + script_path + "](" + uri + ")"; + } + return markdown; + } + + _FORCE_INLINE_ CompletionItem make_completion_item(bool resolved = false) const { + + lsp::CompletionItem item; + item.label = name; + + if (resolved) { + item.documentation = render(); + } + + switch (kind) { + case lsp::SymbolKind::Enum: + item.kind = lsp::CompletionItemKind::Enum; + break; + case lsp::SymbolKind::Class: + item.kind = lsp::CompletionItemKind::Class; + break; + case lsp::SymbolKind::Property: + item.kind = lsp::CompletionItemKind::Property; + break; + case lsp::SymbolKind::Method: + case lsp::SymbolKind::Function: + item.kind = lsp::CompletionItemKind::Method; + break; + case lsp::SymbolKind::Event: + item.kind = lsp::CompletionItemKind::Event; + break; + case lsp::SymbolKind::Constant: + item.kind = lsp::CompletionItemKind::Constant; + break; + case lsp::SymbolKind::Variable: + item.kind = lsp::CompletionItemKind::Variable; + break; + case lsp::SymbolKind::File: + item.kind = lsp::CompletionItemKind::File; + break; + default: + item.kind = lsp::CompletionItemKind::Text; + break; + } + + return item; + } +}; + +/** + * Enum of known range kinds + */ +namespace FoldingRangeKind { +/** + * Folding range for a comment + */ +static const String Comment = "comment"; +/** + * Folding range for a imports or includes + */ +static const String Imports = "imports"; +/** + * Folding range for a region (e.g. `#region`) + */ +static const String Region = "region"; +} // namespace FoldingRangeKind + +/** + * Represents a folding range. + */ +struct FoldingRange { + + /** + * The zero-based line number from where the folded range starts. + */ + int startLine = 0; + + /** + * The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line. + */ + int startCharacter = 0; + + /** + * The zero-based line number where the folded range ends. + */ + int endLine = 0; + + /** + * The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line. + */ + int endCharacter = 0; + + /** + * Describes the kind of the folding range such as `comment' or 'region'. The kind + * is used to categorize folding ranges and used by commands like 'Fold all comments'. See + * [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds. + */ + String kind = FoldingRangeKind::Region; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["startLine"] = startLine; + dict["startCharacter"] = startCharacter; + dict["endLine"] = endLine; + dict["endCharacter"] = endCharacter; + return dict; + } +}; + +/** + * How a completion was triggered + */ +namespace CompletionTriggerKind { +/** + * Completion was triggered by typing an identifier (24x7 code + * complete), manual invocation (e.g Ctrl+Space) or via API. + */ +static const int Invoked = 1; + +/** + * Completion was triggered by a trigger character specified by + * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. + */ +static const int TriggerCharacter = 2; + +/** + * Completion was re-triggered as the current completion list is incomplete. + */ +static const int TriggerForIncompleteCompletions = 3; +} // namespace CompletionTriggerKind + +/** + * Contains additional information about the context in which a completion request is triggered. + */ +struct CompletionContext { + /** + * How the completion was triggered. + */ + int triggerKind = CompletionTriggerKind::TriggerCharacter; + + /** + * The trigger character (a single character) that has trigger code complete. + * Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` + */ + String triggerCharacter; + + void load(const Dictionary &p_params) { + triggerKind = int(p_params["triggerKind"]); + triggerCharacter = p_params["triggerCharacter"]; + } +}; + +struct CompletionParams : public TextDocumentPositionParams { + + /** + * The completion context. This is only available if the client specifies + * to send this using `ClientCapabilities.textDocument.completion.contextSupport === true` + */ + CompletionContext context; + + void load(const Dictionary &p_params) { + TextDocumentPositionParams::load(p_params); + context.load(p_params["context"]); + } +}; + +/** + * The result of a hover request. + */ +struct Hover { + /** + * The hover's content + */ + MarkupContent contents; + + /** + * An optional range is a range inside a text document + * that is used to visualize a hover, e.g. by changing the background color. + */ + Range range; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["range"] = range.to_json(); + dict["contents"] = contents.to_json(); + return dict; + } +}; + +struct ServerCapabilities { + /** + * Defines how text documents are synced. Is either a detailed structure defining each notification or + * for backwards compatibility the TextDocumentSyncKind number. If omitted it defaults to `TextDocumentSyncKind.None`. + */ + TextDocumentSyncOptions textDocumentSync; + + /** + * The server provides hover support. + */ + bool hoverProvider = true; + + /** + * The server provides completion support. + */ + CompletionOptions completionProvider; + + /** + * The server provides signature help support. + */ + SignatureHelpOptions signatureHelpProvider; + + /** + * The server provides goto definition support. + */ + bool definitionProvider = true; + + /** + * The server provides Goto Type Definition support. + * + * Since 3.6.0 + */ + bool typeDefinitionProvider = false; + + /** + * The server provides Goto Implementation support. + * + * Since 3.6.0 + */ + bool implementationProvider = false; + + /** + * The server provides find references support. + */ + bool referencesProvider = false; + + /** + * The server provides document highlight support. + */ + bool documentHighlightProvider = false; + + /** + * The server provides document symbol support. + */ + bool documentSymbolProvider = true; + + /** + * The server provides workspace symbol support. + */ + bool workspaceSymbolProvider = true; + + /** + * The server provides code actions. The `CodeActionOptions` return type is only + * valid if the client signals code action literal support via the property + * `textDocument.codeAction.codeActionLiteralSupport`. + */ + bool codeActionProvider = false; + + /** + * The server provides code lens. + */ + CodeLensOptions codeLensProvider; + + /** + * The server provides document formatting. + */ + bool documentFormattingProvider = false; + + /** + * The server provides document range formatting. + */ + bool documentRangeFormattingProvider = false; + + /** + * The server provides document formatting on typing. + */ + DocumentOnTypeFormattingOptions documentOnTypeFormattingProvider; + + /** + * The server provides rename support. RenameOptions may only be + * specified if the client states that it supports + * `prepareSupport` in its initial `initialize` request. + */ + RenameOptions renameProvider; + + /** + * The server provides document link support. + */ + DocumentLinkOptions documentLinkProvider; + + /** + * The server provides color provider support. + * + * Since 3.6.0 + */ + ColorProviderOptions colorProvider; + + /** + * The server provides folding provider support. + * + * Since 3.10.0 + */ + FoldingRangeProviderOptions foldingRangeProvider; + + /** + * The server provides go to declaration support. + * + * Since 3.14.0 + */ + bool declarationProvider = true; + + /** + * The server provides execute command support. + */ + ExecuteCommandOptions executeCommandProvider; + + _FORCE_INLINE_ Dictionary to_json() { + Dictionary dict; + dict["textDocumentSync"] = (int)textDocumentSync.change; + dict["completionProvider"] = completionProvider.to_json(); + dict["signatureHelpProvider"] = signatureHelpProvider.to_json(); + dict["codeLensProvider"] = false; // codeLensProvider.to_json(); + dict["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.to_json(); + dict["renameProvider"] = renameProvider.to_json(); + dict["documentLinkProvider"] = documentLinkProvider.to_json(); + dict["colorProvider"] = false; // colorProvider.to_json(); + dict["foldingRangeProvider"] = false; //foldingRangeProvider.to_json(); + dict["executeCommandProvider"] = executeCommandProvider.to_json(); + dict["hoverProvider"] = hoverProvider; + dict["definitionProvider"] = definitionProvider; + dict["typeDefinitionProvider"] = typeDefinitionProvider; + dict["implementationProvider"] = implementationProvider; + dict["referencesProvider"] = referencesProvider; + dict["documentHighlightProvider"] = documentHighlightProvider; + dict["documentSymbolProvider"] = documentSymbolProvider; + dict["workspaceSymbolProvider"] = workspaceSymbolProvider; + dict["codeActionProvider"] = codeActionProvider; + dict["documentFormattingProvider"] = documentFormattingProvider; + dict["documentRangeFormattingProvider"] = documentRangeFormattingProvider; + dict["declarationProvider"] = declarationProvider; + return dict; + } +}; + +struct InitializeResult { + /** + * The capabilities the language server provides. + */ + ServerCapabilities capabilities; + + _FORCE_INLINE_ Dictionary to_json() { + Dictionary dict; + dict["capabilities"] = capabilities.to_json(); + return dict; + } +}; + +} // namespace lsp + +#endif diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index b8a13ed91b..94b9e8c2d9 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -32,8 +32,8 @@ #include "core/io/file_access_encrypted.h" #include "core/io/resource_loader.h" +#include "core/os/dir_access.h" #include "core/os/file_access.h" -#include "editor/gdscript_highlighter.h" #include "gdscript.h" #include "gdscript_tokenizer.h" @@ -46,6 +46,12 @@ Ref<ResourceFormatSaverGDScript> resource_saver_gd; #include "editor/editor_export.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/gdscript_highlighter.h" + +#ifndef GDSCRIPT_NO_LSP +#include "core/engine.h" +#include "language_server/gdscript_language_server.h" +#endif // !GDSCRIPT_NO_LSP class EditorExportGDScript : public EditorExportPlugin { @@ -117,6 +123,9 @@ public: file = FileAccess::get_file_as_array(tmp_path); add_file(p_path.get_basename() + ".gde", file, true); + // Clean up temporary file. + DirAccess::remove_file_or_error(tmp_path); + } else { add_file(p_path.get_basename() + ".gdc", file, true); @@ -130,9 +139,16 @@ static void _editor_init() { Ref<EditorExportGDScript> gd_export; gd_export.instance(); EditorExport::get_singleton()->add_export_plugin(gd_export); + +#ifndef GDSCRIPT_NO_LSP + register_lsp_types(); + GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer); + EditorNode::get_singleton()->add_editor_plugin(lsp_plugin); + Engine::get_singleton()->add_singleton(Engine::Singleton("GDScriptLanguageProtocol", GDScriptLanguageProtocol::get_singleton())); +#endif // !GDSCRIPT_NO_LSP } -#endif +#endif // TOOLS_ENABLED void register_gdscript_types() { @@ -151,7 +167,7 @@ void register_gdscript_types() { #ifdef TOOLS_ENABLED ScriptEditor::register_create_syntax_highlighter_function(GDScriptSyntaxHighlighter::create); EditorNode::add_init_callback(_editor_init); -#endif +#endif // TOOLS_ENABLED } void unregister_gdscript_types() { diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index bdecbbdbad..b36afd4386 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -196,16 +196,14 @@ bool GridMap::get_collision_layer_bit(int p_bit) const { #ifndef DISABLE_DEPRECATED void GridMap::set_theme(const Ref<MeshLibrary> &p_theme) { - ERR_EXPLAIN("GridMap.theme/set_theme() is deprecated and will be removed in a future version. Use GridMap.mesh_library/set_mesh_library() instead."); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("GridMap.theme/set_theme() is deprecated and will be removed in a future version. Use GridMap.mesh_library/set_mesh_library() instead."); set_mesh_library(p_theme); } Ref<MeshLibrary> GridMap::get_theme() const { - ERR_EXPLAIN("GridMap.theme/get_theme() is deprecated and will be removed in a future version. Use GridMap.mesh_library/get_mesh_library() instead."); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("GridMap.theme/get_theme() is deprecated and will be removed in a future version. Use GridMap.mesh_library/get_mesh_library() instead."); return get_mesh_library(); } diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 5a21833ffa..7e2986ca85 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -863,28 +863,36 @@ void GridMapEditor::_icon_size_changed(float p_value) { void GridMapEditor::update_palette() { int selected = mesh_library_palette->get_current(); + float min_size = EDITOR_DEF("editors/grid_map/preview_size", 64); + min_size *= EDSCALE; + mesh_library_palette->clear(); if (display_mode == DISPLAY_THUMBNAIL) { mesh_library_palette->set_max_columns(0); mesh_library_palette->set_icon_mode(ItemList::ICON_MODE_TOP); + mesh_library_palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1.5)); } else if (display_mode == DISPLAY_LIST) { mesh_library_palette->set_max_columns(1); mesh_library_palette->set_icon_mode(ItemList::ICON_MODE_LEFT); + mesh_library_palette->set_fixed_column_width(0); } - float min_size = EDITOR_DEF("editors/grid_map/preview_size", 64); - min_size *= EDSCALE; mesh_library_palette->set_fixed_icon_size(Size2(min_size, min_size)); - mesh_library_palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1.5)); mesh_library_palette->set_max_text_lines(2); Ref<MeshLibrary> mesh_library = node->get_mesh_library(); if (mesh_library.is_null()) { last_mesh_library = NULL; + search_box->set_text(""); + search_box->set_editable(false); + info_message->show(); return; } + search_box->set_editable(true); + info_message->hide(); + Vector<int> ids; ids = mesh_library->get_item_list(); @@ -1067,6 +1075,7 @@ void GridMapEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", this, "_node_removed"); mesh_library_palette->connect("item_selected", this, "_item_selected_cbk"); for (int i = 0; i < 3; i++) { @@ -1083,6 +1092,7 @@ void GridMapEditor::_notification(int p_what) { } break; case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", this, "_node_removed"); _clear_clipboard_data(); for (int i = 0; i < 3; i++) { @@ -1196,6 +1206,7 @@ void GridMapEditor::_bind_methods() { ClassDB::bind_method("_floor_changed", &GridMapEditor::_floor_changed); ClassDB::bind_method("_floor_mouse_exited", &GridMapEditor::_floor_mouse_exited); ClassDB::bind_method("_set_selection", &GridMapEditor::_set_selection); + ClassDB::bind_method("_node_removed", &GridMapEditor::_node_removed); ClassDB::bind_method(D_METHOD("_set_display_mode", "mode"), &GridMapEditor::_set_display_mode); } @@ -1269,7 +1280,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { options->get_popup()->add_item(TTR("Fill Selection"), MENU_OPTION_SELECTION_FILL, KEY_MASK_CTRL + KEY_F); options->get_popup()->add_separator(); - options->get_popup()->add_item(TTR("Settings"), MENU_OPTION_GRIDMAP_SETTINGS); + options->get_popup()->add_item(TTR("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS); settings_dialog = memnew(ConfirmationDialog); settings_dialog->set_title(TTR("GridMap Settings")); @@ -1294,6 +1305,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { search_box = memnew(LineEdit); search_box->set_h_size_flags(SIZE_EXPAND_FILL); + search_box->set_placeholder(TTR("Filter meshes")); hb->add_child(search_box); search_box->connect("text_changed", this, "_text_changed"); search_box->connect("gui_input", this, "_sbox_input"); @@ -1329,6 +1341,14 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { add_child(mesh_library_palette); mesh_library_palette->set_v_size_flags(SIZE_EXPAND_FILL); + info_message = memnew(Label); + info_message->set_text(TTR("Give a MeshLibrary resource to this GridMap to use its meshes.")); + info_message->set_valign(Label::VALIGN_CENTER); + info_message->set_align(Label::ALIGN_CENTER); + info_message->set_autowrap(true); + info_message->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); + mesh_library_palette->add_child(info_message); + edit_axis = Vector3::AXIS_Y; edit_floor[0] = -1; edit_floor[1] = -1; @@ -1344,7 +1364,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { paste_mesh = VisualServer::get_singleton()->mesh_create(); { - //selection mesh create + // Selection mesh create. PoolVector<Vector3> lines; PoolVector<Vector3> triangles; @@ -1422,7 +1442,6 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { inner_mat.instance(); inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2)); - //inner_mat->set_flag(SpatialMaterial::FLAG_ONTOP, true); inner_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true); inner_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); @@ -1442,7 +1461,6 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { selection_floor_mat->set_on_top_of_alpha(); selection_floor_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true); selection_floor_mat->set_line_width(3.0); - //selection_floor_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); d[VS::ARRAY_VERTEX] = lines; VisualServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, VS::PRIMITIVE_LINES, d); diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h index b9be925ff7..103913485f 100644 --- a/modules/gridmap/grid_map_editor_plugin.h +++ b/modules/gridmap/grid_map_editor_plugin.h @@ -35,9 +35,6 @@ #include "editor/editor_plugin.h" #include "editor/pane_drag.h" #include "grid_map.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class SpatialEditorPlugin; @@ -200,12 +197,16 @@ class GridMapEditor : public VBoxContainer { RID instance; }; + ItemList *mesh_library_palette; + Label *info_message; + + EditorNode *editor; + void update_grid(); void _configure(); void _menu_option(int); void update_palette(); void _set_display_mode(int p_mode); - ItemList *mesh_library_palette; void _item_selected_cbk(int idx); void _update_cursor_transform(); void _update_cursor_instance(); @@ -230,7 +231,6 @@ class GridMapEditor : public VBoxContainer { void _delete_selection(); void _fill_selection(); - EditorNode *editor; bool do_input_action(Camera *p_camera, const Point2 &p_point, bool p_click); friend class GridMapEditorPlugin; diff --git a/modules/hdr/image_loader_hdr.cpp b/modules/hdr/image_loader_hdr.cpp index f75a4a926a..1abf26bfee 100644 --- a/modules/hdr/image_loader_hdr.cpp +++ b/modules/hdr/image_loader_hdr.cpp @@ -37,7 +37,7 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, FileAccess *f, bool p_force String header = f->get_token(); - ERR_FAIL_COND_V(header != "#?RADIANCE" && header != "#?RGBE", ERR_FILE_UNRECOGNIZED); + ERR_FAIL_COND_V_MSG(header != "#?RADIANCE" && header != "#?RGBE", ERR_FILE_UNRECOGNIZED, "Unsupported header information in HDR: " + header + "."); while (true) { String line = f->get_line(); @@ -45,12 +45,9 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, FileAccess *f, bool p_force if (line == "") // empty line indicates end of header break; if (line.begins_with("FORMAT=")) { // leave option to implement other commands - if (line != "FORMAT=32-bit_rle_rgbe") { - ERR_EXPLAIN("Only 32-bit_rle_rgbe is supported for HDR files."); - return ERR_FILE_UNRECOGNIZED; - } + ERR_FAIL_COND_V_MSG(line != "FORMAT=32-bit_rle_rgbe", ERR_FILE_UNRECOGNIZED, "Only 32-bit_rle_rgbe is supported for HDR files."); } else if (!line.begins_with("#")) { // not comment - WARN_PRINTS("Ignoring unsupported header information in HDR : " + line); + WARN_PRINTS("Ignoring unsupported header information in HDR: " + line + "."); } } @@ -102,10 +99,7 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, FileAccess *f, bool p_force len <<= 8; len |= f->get_8(); - if (len != width) { - ERR_EXPLAIN("invalid decoded scanline length, corrupt HDR"); - ERR_FAIL_V(ERR_FILE_CORRUPT); - } + ERR_FAIL_COND_V_MSG(len != width, ERR_FILE_CORRUPT, "Invalid decoded scanline length, corrupt HDR."); for (int k = 0; k < 4; ++k) { int i = 0; diff --git a/modules/hdr/image_loader_hdr.h b/modules/hdr/image_loader_hdr.h index 8ebf52def7..e9575ee4fb 100644 --- a/modules/hdr/image_loader_hdr.h +++ b/modules/hdr/image_loader_hdr.h @@ -33,9 +33,6 @@ #include "core/io/image_loader.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class ImageLoaderHDR : public ImageFormatLoader { public: diff --git a/modules/jpg/image_loader_jpegd.h b/modules/jpg/image_loader_jpegd.h index 9a96fe008d..e9016ce43e 100644 --- a/modules/jpg/image_loader_jpegd.h +++ b/modules/jpg/image_loader_jpegd.h @@ -33,9 +33,6 @@ #include "core/io/image_loader.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class ImageLoaderJPG : public ImageFormatLoader { public: diff --git a/modules/jsonrpc/SCsub b/modules/jsonrpc/SCsub new file mode 100644 index 0000000000..13c9ffb253 --- /dev/null +++ b/modules/jsonrpc/SCsub @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_jsonrpc = env_modules.Clone() +env_jsonrpc.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/jsonrpc/config.py b/modules/jsonrpc/config.py new file mode 100644 index 0000000000..53bc827027 --- /dev/null +++ b/modules/jsonrpc/config.py @@ -0,0 +1,5 @@ +def can_build(env, platform): + return True + +def configure(env): + pass diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp new file mode 100644 index 0000000000..e1bba60f2f --- /dev/null +++ b/modules/jsonrpc/jsonrpc.cpp @@ -0,0 +1,171 @@ +/*************************************************************************/ +/* jsonrpc.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "jsonrpc.h" +#include "core/io/json.h" + +JSONRPC::JSONRPC() { +} + +JSONRPC::~JSONRPC() { +} + +void JSONRPC::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_scope", "scope", "target"), &JSONRPC::set_scope); + ClassDB::bind_method(D_METHOD("process_action", "action", "recurse"), &JSONRPC::process_action, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("process_string", "action"), &JSONRPC::process_string); + + ClassDB::bind_method(D_METHOD("make_request", "method", "params", "id"), &JSONRPC::make_request); + ClassDB::bind_method(D_METHOD("make_response", "result", "id"), &JSONRPC::make_response); + ClassDB::bind_method(D_METHOD("make_notification", "method", "params"), &JSONRPC::make_notification); + ClassDB::bind_method(D_METHOD("make_response_error", "code", "message", "id"), &JSONRPC::make_response_error, DEFVAL(Variant())); + + BIND_ENUM_CONSTANT(PARSE_ERROR) + BIND_ENUM_CONSTANT(INVALID_REQUEST) + BIND_ENUM_CONSTANT(METHOD_NOT_FOUND) + BIND_ENUM_CONSTANT(INVALID_PARAMS) + BIND_ENUM_CONSTANT(INTERNAL_ERROR) +} + +Dictionary JSONRPC::make_response_error(int p_code, const String &p_message, const Variant &p_id) const { + Dictionary dict; + dict["jsonrpc"] = "2.0"; + + Dictionary err; + err["code"] = p_code; + err["message"] = p_message; + + dict["error"] = err; + dict["id"] = p_id; + + return dict; +} + +Dictionary JSONRPC::make_response(const Variant &p_value, const Variant &p_id) { + Dictionary dict; + dict["jsonrpc"] = "2.0"; + dict["id"] = p_id; + dict["result"] = p_value; + return dict; +} + +Dictionary JSONRPC::make_notification(const String &p_method, const Variant &p_params) { + Dictionary dict; + dict["jsonrpc"] = "2.0"; + dict["method"] = p_method; + dict["params"] = p_params; + return dict; +} + +Dictionary JSONRPC::make_request(const String &p_method, const Variant &p_params, const Variant &p_id) { + Dictionary dict; + dict["jsonrpc"] = "2.0"; + dict["method"] = p_method; + dict["params"] = p_params; + dict["id"] = p_id; + return dict; +} + +Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elements) { + Variant ret; + if (p_action.get_type() == Variant::DICTIONARY) { + Dictionary dict = p_action; + String method = dict.get("method", ""); + Array args; + if (dict.has("params")) { + Variant params = dict.get("params", Variant()); + if (params.get_type() == Variant::ARRAY) { + args = params; + } else { + args.push_back(params); + } + } + + Object *object = this; + if (method_scopes.has(method.get_base_dir())) { + object = method_scopes[method.get_base_dir()]; + method = method.get_file(); + } + + Variant id; + if (dict.has("id")) { + id = dict["id"]; + } + + if (object == NULL || !object->has_method(method)) { + ret = make_response_error(JSONRPC::METHOD_NOT_FOUND, "Method not found", id); + } else { + Variant call_ret = object->callv(method, args); + if (id.get_type() != Variant::NIL) { + ret = make_response(call_ret, id); + } + } + } else if (p_action.get_type() == Variant::ARRAY && p_process_arr_elements) { + Array arr = p_action; + int size = arr.size(); + if (size) { + Array arr_ret; + for (int i = 0; i < size; i++) { + const Variant &var = arr.get(i); + arr_ret.push_back(process_action(var)); + } + ret = arr_ret; + } else { + ret = make_response_error(JSONRPC::INVALID_REQUEST, "Invalid Request"); + } + } else { + ret = make_response_error(JSONRPC::INVALID_REQUEST, "Invalid Request"); + } + return ret; +} + +String JSONRPC::process_string(const String &p_input) { + + if (p_input.empty()) return String(); + + Variant ret; + Variant input; + String err_message; + int err_line; + if (OK != JSON::parse(p_input, input, err_message, err_line)) { + ret = make_response_error(JSONRPC::PARSE_ERROR, "Parse error"); + } else { + ret = process_action(input, true); + } + + if (ret.get_type() == Variant::NIL) { + return ""; + } + return JSON::print(ret); +} + +void JSONRPC::set_scope(const String &p_scope, Object *p_obj) { + method_scopes[p_scope] = p_obj; +} diff --git a/modules/jsonrpc/jsonrpc.h b/modules/jsonrpc/jsonrpc.h new file mode 100644 index 0000000000..91897d0b55 --- /dev/null +++ b/modules/jsonrpc/jsonrpc.h @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* jsonrpc.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_JSON_RPC_H +#define GODOT_JSON_RPC_H + +#include "core/object.h" +#include "core/variant.h" + +class JSONRPC : public Object { + GDCLASS(JSONRPC, Object) + + Map<String, Object *> method_scopes; + +protected: + static void _bind_methods(); + +public: + JSONRPC(); + ~JSONRPC(); + + enum ErrorCode { + PARSE_ERROR = -32700, + INVALID_REQUEST = -32600, + METHOD_NOT_FOUND = -32601, + INVALID_PARAMS = -32602, + INTERNAL_ERROR = -32603, + }; + + Dictionary make_response_error(int p_code, const String &p_message, const Variant &p_id = Variant()) const; + Dictionary make_response(const Variant &p_value, const Variant &p_id); + Dictionary make_notification(const String &p_method, const Variant &p_params); + Dictionary make_request(const String &p_method, const Variant &p_params, const Variant &p_id); + + Variant process_action(const Variant &p_action, bool p_process_arr_elements = false); + String process_string(const String &p_input); + + void set_scope(const String &p_scope, Object *p_obj); +}; + +VARIANT_ENUM_CAST(JSONRPC::ErrorCode); + +#endif diff --git a/modules/jsonrpc/register_types.cpp b/modules/jsonrpc/register_types.cpp new file mode 100644 index 0000000000..242b0e9df4 --- /dev/null +++ b/modules/jsonrpc/register_types.cpp @@ -0,0 +1,40 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "register_types.h" +#include "core/class_db.h" +#include "jsonrpc.h" + +void register_jsonrpc_types() { + ClassDB::register_class<JSONRPC>(); +} + +void unregister_jsonrpc_types() { +} diff --git a/modules/jsonrpc/register_types.h b/modules/jsonrpc/register_types.h new file mode 100644 index 0000000000..e4648b901f --- /dev/null +++ b/modules/jsonrpc/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +void register_jsonrpc_types(); +void unregister_jsonrpc_types(); diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp new file mode 100644 index 0000000000..1e02084ae2 --- /dev/null +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -0,0 +1,285 @@ +/*************************************************************************/ +/* crypto_mbedtls.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "crypto_mbedtls.h" + +#include "core/os/file_access.h" + +#include "core/engine.h" +#include "core/io/certs_compressed.gen.h" +#include "core/io/compression.h" +#include "core/project_settings.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif +#define PEM_BEGIN_CRT "-----BEGIN CERTIFICATE-----\n" +#define PEM_END_CRT "-----END CERTIFICATE-----\n" + +#include "mbedtls/pem.h" +#include <mbedtls/debug.h> + +CryptoKey *CryptoKeyMbedTLS::create() { + return memnew(CryptoKeyMbedTLS); +} + +Error CryptoKeyMbedTLS::load(String p_path) { + ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Key is in use"); + + PoolByteArray out; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER); + + int flen = f->get_len(); + out.resize(flen + 1); + { + PoolByteArray::Write w = out.write(); + f->get_buffer(w.ptr(), flen); + w[flen] = 0; //end f string + } + memdelete(f); + + int ret = mbedtls_pk_parse_key(&pkey, out.read().ptr(), out.size(), NULL, 0); + // We MUST zeroize the memory for safety! + mbedtls_platform_zeroize(out.write().ptr(), out.size()); + ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing private key: " + itos(ret)); + + return OK; +} + +Error CryptoKeyMbedTLS::save(String p_path) { + FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER); + + unsigned char w[16000]; + memset(w, 0, sizeof(w)); + + int ret = mbedtls_pk_write_key_pem(&pkey, w, sizeof(w)); + if (ret != 0) { + memdelete(f); + memset(w, 0, sizeof(w)); // Zeroize anything we might have written. + ERR_FAIL_V_MSG(FAILED, "Error writing key: " + itos(ret)); + } + + size_t len = strlen((char *)w); + f->store_buffer(w, len); + memdelete(f); + memset(w, 0, sizeof(w)); // Zeroize temporary buffer. + return OK; +} + +X509Certificate *X509CertificateMbedTLS::create() { + return memnew(X509CertificateMbedTLS); +} + +Error X509CertificateMbedTLS::load(String p_path) { + ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Certificate is in use"); + + PoolByteArray out; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER); + + int flen = f->get_len(); + out.resize(flen + 1); + { + PoolByteArray::Write w = out.write(); + f->get_buffer(w.ptr(), flen); + w[flen] = 0; //end f string + } + memdelete(f); + + int ret = mbedtls_x509_crt_parse(&cert, out.read().ptr(), out.size()); + ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing some certificates: " + itos(ret)); + + return OK; +} + +Error X509CertificateMbedTLS::load_from_memory(const uint8_t *p_buffer, int p_len) { + ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Certificate is in use"); + + int ret = mbedtls_x509_crt_parse(&cert, p_buffer, p_len); + ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing certificates: " + itos(ret)); + return OK; +} + +Error X509CertificateMbedTLS::save(String p_path) { + FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER); + + mbedtls_x509_crt *crt = &cert; + while (crt) { + unsigned char w[4096]; + size_t wrote = 0; + int ret = mbedtls_pem_write_buffer(PEM_BEGIN_CRT, PEM_END_CRT, cert.raw.p, cert.raw.len, w, sizeof(w), &wrote); + if (ret != 0 || wrote == 0) { + memdelete(f); + ERR_FAIL_V_MSG(FAILED, "Error writing certificate: " + itos(ret)); + } + + f->store_buffer(w, wrote - 1); // don't write the string terminator + crt = crt->next; + } + memdelete(f); + return OK; +} + +Crypto *CryptoMbedTLS::create() { + return memnew(CryptoMbedTLS); +} + +void CryptoMbedTLS::initialize_crypto() { + +#ifdef DEBUG_ENABLED + mbedtls_debug_set_threshold(1); +#endif + + Crypto::_create = create; + Crypto::_load_default_certificates = load_default_certificates; + X509CertificateMbedTLS::make_default(); + CryptoKeyMbedTLS::make_default(); +} + +void CryptoMbedTLS::finalize_crypto() { + Crypto::_create = NULL; + Crypto::_load_default_certificates = NULL; + if (default_certs) { + memdelete(default_certs); + default_certs = NULL; + } + X509CertificateMbedTLS::finalize(); + CryptoKeyMbedTLS::finalize(); +} + +CryptoMbedTLS::CryptoMbedTLS() { + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); + if (ret != 0) { + ERR_PRINTS(" failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret)); + } +} + +CryptoMbedTLS::~CryptoMbedTLS() { + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); +} + +X509CertificateMbedTLS *CryptoMbedTLS::default_certs = NULL; + +X509CertificateMbedTLS *CryptoMbedTLS::get_default_certificates() { + return default_certs; +} + +void CryptoMbedTLS::load_default_certificates(String p_path) { + ERR_FAIL_COND(default_certs != NULL); + + default_certs = memnew(X509CertificateMbedTLS); + ERR_FAIL_COND(default_certs == NULL); + + String certs_path = GLOBAL_DEF("network/ssl/certificates", ""); + + if (p_path != "") { + // Use certs defined in project settings. + default_certs->load(p_path); + } +#ifdef BUILTIN_CERTS_ENABLED + else { + // Use builtin certs only if user did not override it in project settings. + PoolByteArray out; + out.resize(_certs_uncompressed_size + 1); + PoolByteArray::Write w = out.write(); + Compression::decompress(w.ptr(), _certs_uncompressed_size, _certs_compressed, _certs_compressed_size, Compression::MODE_DEFLATE); + w[_certs_uncompressed_size] = 0; // Make sure it ends with string terminator +#ifdef DEBUG_ENABLED + print_verbose("Loaded builtin certs"); +#endif + default_certs->load_from_memory(out.read().ptr(), out.size()); + } +#endif +} + +Ref<CryptoKey> CryptoMbedTLS::generate_rsa(int p_bytes) { + Ref<CryptoKeyMbedTLS> out; + out.instance(); + int ret = mbedtls_pk_setup(&(out->pkey), mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)); + ERR_FAIL_COND_V(ret != 0, NULL); + ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(out->pkey), mbedtls_ctr_drbg_random, &ctr_drbg, p_bytes, 65537); + ERR_FAIL_COND_V(ret != 0, NULL); + return out; +} + +Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after) { + Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS> >(p_key); + mbedtls_x509write_cert crt; + mbedtls_x509write_crt_init(&crt); + + mbedtls_x509write_crt_set_subject_key(&crt, &(key->pkey)); + mbedtls_x509write_crt_set_issuer_key(&crt, &(key->pkey)); + mbedtls_x509write_crt_set_subject_name(&crt, p_issuer_name.utf8().get_data()); + mbedtls_x509write_crt_set_issuer_name(&crt, p_issuer_name.utf8().get_data()); + mbedtls_x509write_crt_set_version(&crt, MBEDTLS_X509_CRT_VERSION_3); + mbedtls_x509write_crt_set_md_alg(&crt, MBEDTLS_MD_SHA256); + + mbedtls_mpi serial; + mbedtls_mpi_init(&serial); + uint8_t rand_serial[20]; + mbedtls_ctr_drbg_random(&ctr_drbg, rand_serial, 20); + ERR_FAIL_COND_V(mbedtls_mpi_read_binary(&serial, rand_serial, 20), NULL); + mbedtls_x509write_crt_set_serial(&crt, &serial); + + mbedtls_x509write_crt_set_validity(&crt, p_not_before.utf8().get_data(), p_not_after.utf8().get_data()); + mbedtls_x509write_crt_set_basic_constraints(&crt, 1, -1); + mbedtls_x509write_crt_set_basic_constraints(&crt, 1, 0); + + unsigned char buf[4096]; + memset(buf, 0, 4096); + Ref<X509CertificateMbedTLS> out; + out.instance(); + mbedtls_x509write_crt_pem(&crt, buf, 4096, mbedtls_ctr_drbg_random, &ctr_drbg); + + int err = mbedtls_x509_crt_parse(&(out->cert), buf, 4096); + if (err != 0) { + mbedtls_mpi_free(&serial); + mbedtls_x509write_crt_free(&crt); + ERR_PRINTS("Generated invalid certificate: " + itos(err)); + return NULL; + } + + mbedtls_mpi_free(&serial); + mbedtls_x509write_crt_free(&crt); + return out; +} + +PoolByteArray CryptoMbedTLS::generate_random_bytes(int p_bytes) { + PoolByteArray out; + out.resize(p_bytes); + mbedtls_ctr_drbg_random(&ctr_drbg, out.write().ptr(), p_bytes); + return out; +} diff --git a/modules/mbedtls/crypto_mbedtls.h b/modules/mbedtls/crypto_mbedtls.h new file mode 100644 index 0000000000..06b3ecd234 --- /dev/null +++ b/modules/mbedtls/crypto_mbedtls.h @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* crypto_mbedtls.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CRYPTO_MBEDTLS_H +#define CRYPTO_MBEDTLS_H + +#include "core/crypto/crypto.h" +#include "core/resource.h" + +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ssl.h> + +class CryptoMbedTLS; +class SSLContextMbedTLS; +class CryptoKeyMbedTLS : public CryptoKey { + +private: + mbedtls_pk_context pkey; + int locks; + +public: + static CryptoKey *create(); + static void make_default() { CryptoKey::_create = create; } + static void finalize() { CryptoKey::_create = NULL; } + + virtual Error load(String p_path); + virtual Error save(String p_path); + + CryptoKeyMbedTLS() { + mbedtls_pk_init(&pkey); + locks = 0; + } + ~CryptoKeyMbedTLS() { + mbedtls_pk_free(&pkey); + } + + _FORCE_INLINE_ void lock() { locks++; } + _FORCE_INLINE_ void unlock() { locks--; } + + friend class CryptoMbedTLS; + friend class SSLContextMbedTLS; +}; + +class X509CertificateMbedTLS : public X509Certificate { + +private: + mbedtls_x509_crt cert; + int locks; + +public: + static X509Certificate *create(); + static void make_default() { X509Certificate::_create = create; } + static void finalize() { X509Certificate::_create = NULL; } + + virtual Error load(String p_path); + virtual Error load_from_memory(const uint8_t *p_buffer, int p_len); + virtual Error save(String p_path); + + X509CertificateMbedTLS() { + mbedtls_x509_crt_init(&cert); + locks = 0; + } + ~X509CertificateMbedTLS() { + mbedtls_x509_crt_free(&cert); + } + + _FORCE_INLINE_ void lock() { locks++; } + _FORCE_INLINE_ void unlock() { locks--; } + + friend class CryptoMbedTLS; + friend class SSLContextMbedTLS; +}; + +class CryptoMbedTLS : public Crypto { + +private: + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + static X509CertificateMbedTLS *default_certs; + +public: + static Crypto *create(); + static void initialize_crypto(); + static void finalize_crypto(); + static X509CertificateMbedTLS *get_default_certificates(); + static void load_default_certificates(String p_path); + + virtual PoolByteArray generate_random_bytes(int p_bytes); + virtual Ref<CryptoKey> generate_rsa(int p_bytes); + virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after); + + CryptoMbedTLS(); + ~CryptoMbedTLS(); +}; + +#endif // CRYPTO_MBEDTLS_H diff --git a/modules/mbedtls/register_types.cpp b/modules/mbedtls/register_types.cpp index 121ed5eb02..f7dc6c785f 100755 --- a/modules/mbedtls/register_types.cpp +++ b/modules/mbedtls/register_types.cpp @@ -30,15 +30,17 @@ #include "register_types.h" -#include "stream_peer_mbed_tls.h" +#include "crypto_mbedtls.h" +#include "stream_peer_mbedtls.h" void register_mbedtls_types() { - ClassDB::register_class<StreamPeerMbedTLS>(); + CryptoMbedTLS::initialize_crypto(); StreamPeerMbedTLS::initialize_ssl(); } void unregister_mbedtls_types() { StreamPeerMbedTLS::finalize_ssl(); + CryptoMbedTLS::finalize_crypto(); } diff --git a/modules/mbedtls/ssl_context_mbedtls.cpp b/modules/mbedtls/ssl_context_mbedtls.cpp new file mode 100644 index 0000000000..eeaf831b4a --- /dev/null +++ b/modules/mbedtls/ssl_context_mbedtls.cpp @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* ssl_context_mbedtls.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "ssl_context_mbedtls.h" + +static void my_debug(void *ctx, int level, + const char *file, int line, + const char *str) { + + printf("%s:%04d: %s", file, line, str); + fflush(stdout); +} + +Error SSLContextMbedTLS::_setup(int p_endpoint, int p_transport, int p_authmode) { + ERR_FAIL_COND_V_MSG(inited, ERR_ALREADY_IN_USE, "This SSL context is already active"); + + mbedtls_ssl_init(&ssl); + mbedtls_ssl_config_init(&conf); + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + inited = true; + + int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); + if (ret != 0) { + clear(); // Never leave unusable resources around. + ERR_FAIL_V_MSG(FAILED, "mbedtls_ctr_drbg_seed returned an error" + itos(ret)); + } + + ret = mbedtls_ssl_config_defaults(&conf, p_endpoint, p_transport, MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { + clear(); + ERR_FAIL_V_MSG(FAILED, "mbedtls_ssl_config_defaults returned an error" + itos(ret)); + } + mbedtls_ssl_conf_authmode(&conf, p_authmode); + mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); + mbedtls_ssl_conf_dbg(&conf, my_debug, stdout); + return OK; +} + +Error SSLContextMbedTLS::init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert) { + ERR_FAIL_COND_V(!p_pkey.is_valid(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!p_cert.is_valid(), ERR_INVALID_PARAMETER); + + Error err = _setup(MBEDTLS_SSL_IS_SERVER, p_transport, p_authmode); + ERR_FAIL_COND_V(err != OK, err); + + // Locking key and certificate(s) + pkey = p_pkey; + certs = p_cert; + if (pkey.is_valid()) + pkey->lock(); + if (certs.is_valid()) + certs->lock(); + + // Adding key and certificate + int ret = mbedtls_ssl_conf_own_cert(&conf, &(certs->cert), &(pkey->pkey)); + if (ret != 0) { + clear(); + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Invalid cert/key combination " + itos(ret)); + } + // Adding CA chain if available. + if (certs->cert.next) { + mbedtls_ssl_conf_ca_chain(&conf, certs->cert.next, NULL); + } + mbedtls_ssl_setup(&ssl, &conf); + return OK; +} + +Error SSLContextMbedTLS::init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> p_valid_cas) { + Error err = _setup(MBEDTLS_SSL_IS_CLIENT, p_transport, p_authmode); + ERR_FAIL_COND_V(err != OK, err); + + X509CertificateMbedTLS *cas = NULL; + + if (p_valid_cas.is_valid()) { + // Locking CA certificates + certs = p_valid_cas; + certs->lock(); + cas = certs.ptr(); + } else { + // Fall back to default certificates (no need to lock those). + cas = CryptoMbedTLS::get_default_certificates(); + if (cas == NULL) { + clear(); + ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "SSL module failed to initialize!"); + } + } + + // Set valid CAs + mbedtls_ssl_conf_ca_chain(&conf, &(cas->cert), NULL); + mbedtls_ssl_setup(&ssl, &conf); + return OK; +} + +void SSLContextMbedTLS::clear() { + if (!inited) + return; + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + + // Unlock and key and certificates + if (certs.is_valid()) + certs->unlock(); + certs = Ref<X509Certificate>(); + if (pkey.is_valid()) + pkey->unlock(); + pkey = Ref<CryptoKeyMbedTLS>(); + inited = false; +} + +mbedtls_ssl_context *SSLContextMbedTLS::get_context() { + ERR_FAIL_COND_V(!inited, NULL); + return &ssl; +} + +SSLContextMbedTLS::SSLContextMbedTLS() { + inited = false; +} + +SSLContextMbedTLS::~SSLContextMbedTLS() { + clear(); +} diff --git a/modules/mbedtls/ssl_context_mbedtls.h b/modules/mbedtls/ssl_context_mbedtls.h new file mode 100644 index 0000000000..e49d532912 --- /dev/null +++ b/modules/mbedtls/ssl_context_mbedtls.h @@ -0,0 +1,73 @@ +/*************************************************************************/ +/* ssl_context_mbedtls.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SSL_CONTEXT_MBED_TLS_H +#define SSL_CONTEXT_MBED_TLS_H + +#include "crypto_mbedtls.h" + +#include "core/os/file_access.h" +#include "core/pool_vector.h" +#include "core/reference.h" + +#include <mbedtls/config.h> +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/debug.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ssl.h> + +class SSLContextMbedTLS : public Reference { + +protected: + bool inited; + + static PoolByteArray _read_file(String p_path); + +public: + Ref<X509CertificateMbedTLS> certs; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + + Ref<CryptoKeyMbedTLS> pkey; + + Error _setup(int p_endpoint, int p_transport, int p_authmode); + Error init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert); + Error init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> p_valid_cas); + void clear(); + + mbedtls_ssl_context *get_context(); + + SSLContextMbedTLS(); + ~SSLContextMbedTLS(); +}; + +#endif // SSL_CONTEXT_MBED_TLS_H diff --git a/modules/mbedtls/stream_peer_mbed_tls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp index 4bb7557150..a2e342e219 100755 --- a/modules/mbedtls/stream_peer_mbed_tls.cpp +++ b/modules/mbedtls/stream_peer_mbedtls.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* stream_peer_mbed_tls.cpp */ +/* stream_peer_mbedtls.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,19 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "stream_peer_mbed_tls.h" +#include "stream_peer_mbedtls.h" #include "core/io/stream_peer_tcp.h" #include "core/os/file_access.h" -static void my_debug(void *ctx, int level, - const char *file, int line, - const char *str) { - - printf("%s:%04d: %s", file, line, str); - fflush(stdout); -} - void _print_error(int ret) { printf("mbedtls error: returned -0x%x\n\n", -ret); fflush(stdout); @@ -86,18 +78,14 @@ int StreamPeerMbedTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) { void StreamPeerMbedTLS::_cleanup() { - mbedtls_ssl_free(&ssl); - mbedtls_ssl_config_free(&conf); - mbedtls_ctr_drbg_free(&ctr_drbg); - mbedtls_entropy_free(&entropy); - + ssl_ctx->clear(); base = Ref<StreamPeer>(); status = STATUS_DISCONNECTED; } Error StreamPeerMbedTLS::_do_handshake() { int ret = 0; - while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { + while ((ret = mbedtls_ssl_handshake(ssl_ctx->get_context())) != 0) { if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { // An error occurred. ERR_PRINTS("TLS handshake error: " + itos(ret)); @@ -118,7 +106,7 @@ Error StreamPeerMbedTLS::_do_handshake() { return OK; } -Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs, const String &p_for_hostname) { +Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs, const String &p_for_hostname, Ref<X509Certificate> p_ca_certs) { ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER); @@ -126,31 +114,11 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida int ret = 0; int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE; - mbedtls_ssl_init(&ssl); - mbedtls_ssl_config_init(&conf); - mbedtls_ctr_drbg_init(&ctr_drbg); - mbedtls_entropy_init(&entropy); - - ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); - if (ret != 0) { - ERR_PRINTS(" failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret)); - _cleanup(); - return FAILED; - } + Error err = ssl_ctx->init_client(MBEDTLS_SSL_TRANSPORT_STREAM, authmode, p_ca_certs); + ERR_FAIL_COND_V(err != OK, err); - mbedtls_ssl_config_defaults(&conf, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT); - - mbedtls_ssl_conf_authmode(&conf, authmode); - mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL); - mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); - mbedtls_ssl_conf_dbg(&conf, my_debug, stdout); - mbedtls_ssl_setup(&ssl, &conf); - mbedtls_ssl_set_hostname(&ssl, p_for_hostname.utf8().get_data()); - - mbedtls_ssl_set_bio(&ssl, this, bio_send, bio_recv, NULL); + mbedtls_ssl_set_hostname(ssl_ctx->get_context(), p_for_hostname.utf8().get_data()); + mbedtls_ssl_set_bio(ssl_ctx->get_context(), this, bio_send, bio_recv, NULL); status = STATUS_HANDSHAKING; @@ -162,11 +130,26 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida return OK; } -Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base) { +Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain) { + + ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER); + + Error err = ssl_ctx->init_server(MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_VERIFY_NONE, p_key, p_cert); + ERR_FAIL_COND_V(err != OK, err); + + base = p_base; + + mbedtls_ssl_set_bio(ssl_ctx->get_context(), this, bio_send, bio_recv, NULL); + + status = STATUS_HANDSHAKING; + + if ((err = _do_handshake()) != OK) { + return FAILED; + } + status = STATUS_CONNECTED; return OK; } - Error StreamPeerMbedTLS::put_data(const uint8_t *p_data, int p_bytes) { ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED); @@ -197,7 +180,7 @@ Error StreamPeerMbedTLS::put_partial_data(const uint8_t *p_data, int p_bytes, in if (p_bytes == 0) return OK; - int ret = mbedtls_ssl_write(&ssl, p_data, p_bytes); + int ret = mbedtls_ssl_write(ssl_ctx->get_context(), p_data, p_bytes); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { // Non blocking IO ret = 0; @@ -243,7 +226,7 @@ Error StreamPeerMbedTLS::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r r_received = 0; - int ret = mbedtls_ssl_read(&ssl, p_buffer, p_bytes); + int ret = mbedtls_ssl_read(ssl_ctx->get_context(), p_buffer, p_bytes); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { ret = 0; // non blocking io } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { @@ -273,7 +256,7 @@ void StreamPeerMbedTLS::poll() { // We could pass NULL as second parameter, but some behaviour sanitizers doesn't seem to like that. // Passing a 1 byte buffer to workaround it. uint8_t byte; - int ret = mbedtls_ssl_read(&ssl, &byte, 0); + int ret = mbedtls_ssl_read(ssl_ctx->get_context(), &byte, 0); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { // Nothing to read/write (non blocking IO) @@ -298,10 +281,11 @@ int StreamPeerMbedTLS::get_available_bytes() const { ERR_FAIL_COND_V(status != STATUS_CONNECTED, 0); - return mbedtls_ssl_get_bytes_avail(&ssl); + return mbedtls_ssl_get_bytes_avail(&(ssl_ctx->ssl)); } StreamPeerMbedTLS::StreamPeerMbedTLS() { + ssl_ctx.instance(); status = STATUS_DISCONNECTED; } @@ -317,7 +301,7 @@ void StreamPeerMbedTLS::disconnect_from_stream() { Ref<StreamPeerTCP> tcp = base; if (tcp.is_valid() && tcp->get_status() == StreamPeerTCP::STATUS_CONNECTED) { // We are still connected on the socket, try to send close notify. - mbedtls_ssl_close_notify(&ssl); + mbedtls_ssl_close_notify(ssl_ctx->get_context()); } _cleanup(); @@ -333,28 +317,9 @@ StreamPeerSSL *StreamPeerMbedTLS::_create_func() { return memnew(StreamPeerMbedTLS); } -mbedtls_x509_crt StreamPeerMbedTLS::cacert; - -void StreamPeerMbedTLS::_load_certs(const PoolByteArray &p_array) { - int arr_len = p_array.size(); - PoolByteArray::Read r = p_array.read(); - int err = mbedtls_x509_crt_parse(&cacert, &r[0], arr_len); - if (err != 0) { - WARN_PRINTS("Error parsing some certificates: " + itos(err)); - } -} - void StreamPeerMbedTLS::initialize_ssl() { _create = _create_func; - load_certs_func = _load_certs; - - mbedtls_x509_crt_init(&cacert); - -#ifdef DEBUG_ENABLED - mbedtls_debug_set_threshold(1); -#endif - available = true; } @@ -362,6 +327,4 @@ void StreamPeerMbedTLS::finalize_ssl() { available = false; _create = NULL; - load_certs_func = NULL; - mbedtls_x509_crt_free(&cacert); } diff --git a/modules/mbedtls/stream_peer_mbed_tls.h b/modules/mbedtls/stream_peer_mbedtls.h index ab87b779c1..eec7eab631 100755 --- a/modules/mbedtls/stream_peer_mbed_tls.h +++ b/modules/mbedtls/stream_peer_mbedtls.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* stream_peer_mbed_tls.h */ +/* stream_peer_mbedtls.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -32,15 +32,7 @@ #define STREAM_PEER_OPEN_SSL_H #include "core/io/stream_peer_ssl.h" - -#include <mbedtls/config.h> -#include <mbedtls/ctr_drbg.h> -#include <mbedtls/debug.h> -#include <mbedtls/entropy.h> -#include <mbedtls/ssl.h> - -#include <stdio.h> -#include <stdlib.h> +#include "ssl_context_mbedtls.h" class StreamPeerMbedTLS : public StreamPeerSSL { private: @@ -50,19 +42,13 @@ private: Ref<StreamPeer> base; static StreamPeerSSL *_create_func(); - static void _load_certs(const PoolByteArray &p_array); static int bio_recv(void *ctx, unsigned char *buf, size_t len); static int bio_send(void *ctx, const unsigned char *buf, size_t len); void _cleanup(); protected: - static mbedtls_x509_crt cacert; - - mbedtls_entropy_context entropy; - mbedtls_ctr_drbg_context ctr_drbg; - mbedtls_ssl_context ssl; - mbedtls_ssl_config conf; + Ref<SSLContextMbedTLS> ssl_ctx; static void _bind_methods(); @@ -70,8 +56,8 @@ protected: public: virtual void poll(); - virtual Error accept_stream(Ref<StreamPeer> p_base); - virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String()); + virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>()); + virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref<X509Certificate> p_valid_cert = Ref<X509Certificate>()); virtual Status get_status() const; virtual void disconnect_from_stream(); diff --git a/modules/mono/.gitignore b/modules/mono/.gitignore new file mode 100644 index 0000000000..fa6d00cbbb --- /dev/null +++ b/modules/mono/.gitignore @@ -0,0 +1,2 @@ +# Do not ignore solution files inside the mono module. Overrides Godot's global gitignore. +!*.sln diff --git a/modules/mono/SCsub b/modules/mono/SCsub index cc60e64a11..a9afa7ccf6 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -8,13 +8,7 @@ Import('env_modules') env_mono = env_modules.Clone() -env_mono.add_source_files(env.modules_sources, '*.cpp') -env_mono.add_source_files(env.modules_sources, 'glue/*.cpp') -env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp') -env_mono.add_source_files(env.modules_sources, 'utils/*.cpp') - if env['tools']: - env_mono.add_source_files(env.modules_sources, 'editor/*.cpp') # NOTE: It is safe to generate this file here, since this is still executed serially import build_scripts.make_cs_compressed_header as make_cs_compressed_header make_cs_compressed_header.generate_header( @@ -62,3 +56,13 @@ if env_mono['tools']: # GodotTools.ProjectEditor which doesn't depend on the Godot API solution and # is required by the bindings generator in order to be able to generated it. godot_tools_build.build_project_editor_only(env_mono) + +# Add sources + +env_mono.add_source_files(env.modules_sources, '*.cpp') +env_mono.add_source_files(env.modules_sources, 'glue/*.cpp') +env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp') +env_mono.add_source_files(env.modules_sources, 'utils/*.cpp') + +if env['tools']: + env_mono.add_source_files(env.modules_sources, 'editor/*.cpp') diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py index c47cfc8a38..35daa6d307 100644 --- a/modules/mono/build_scripts/godot_tools_build.py +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -84,10 +84,16 @@ def build(env_mono): source_filenames = ['GodotSharp.dll', 'GodotSharpEditor.dll'] sources = [os.path.join(editor_api_dir, filename) for filename in source_filenames] - target_filenames = ['GodotTools.dll', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'] + target_filenames = [ + 'GodotTools.dll', 'GodotTools.IdeConnection.dll', 'GodotTools.BuildLogger.dll', + 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll' + ] if env_mono['target'] == 'debug': - target_filenames += ['GodotTools.pdb', 'GodotTools.BuildLogger.pdb', 'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb'] + target_filenames += [ + 'GodotTools.pdb', 'GodotTools.IdeConnection.pdb', 'GodotTools.BuildLogger.pdb', + 'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb' + ] targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py index cd9210897d..8cad204d7b 100644 --- a/modules/mono/build_scripts/make_android_mono_config.py +++ b/modules/mono/build_scripts/make_android_mono_config.py @@ -3,23 +3,6 @@ def generate_compressed_config(config_src, output_dir): import os.path from compat import byte_to_str - # Header file - with open(os.path.join(output_dir, 'android_mono_config.gen.h'), 'w') as header: - header.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ -#ifndef ANDROID_MONO_CONFIG_GEN_H -#define ANDROID_MONO_CONFIG_GEN_H - -#ifdef ANDROID_ENABLED - -#include "core/ustring.h" - -String get_godot_android_mono_config(); - -#endif // ANDROID_ENABLED - -#endif // ANDROID_MONO_CONFIG_GEN_H -''') - # Source file with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp: with open(config_src, 'rb') as f: @@ -36,7 +19,7 @@ String get_godot_android_mono_config(); bytes_seq_str += byte_to_str(buf[buf_idx]) cpp.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ -#include "android_mono_config.gen.h" +#include "android_mono_config.h" #ifdef ANDROID_ENABLED diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 9f0eb58896..4c1ebd8d74 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -18,7 +18,7 @@ android_arch_dirs = { def get_android_out_dir(env): - return os.path.join(Dir('#platform/android/java/libs').abspath, + return os.path.join(Dir('#platform/android/java/lib/libs').abspath, 'release' if env['target'] == 'release' else 'debug', android_arch_dirs[env['android_arch']]) @@ -113,8 +113,8 @@ def configure(env, env_mono): else: env.Append(LINKFLAGS=os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)) - env.Append(LIBS='psapi') - env.Append(LIBS='version') + env.Append(LIBS=['psapi']) + env.Append(LIBS=['version']) else: mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib') @@ -124,7 +124,7 @@ def configure(env, env_mono): if env.msvc: env.Append(LINKFLAGS=mono_lib_name + Environment()['LIBSUFFIX']) else: - env.Append(LIBS=mono_lib_name) + env.Append(LIBS=[mono_lib_name]) mono_bin_path = os.path.join(mono_root, 'bin') diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp index 4a6637434a..7580911a0a 100644 --- a/modules/mono/class_db_api_json.cpp +++ b/modules/mono/class_db_api_json.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/mono/class_db_api_json.h b/modules/mono/class_db_api_json.h index ddfe2debea..9888ecfb55 100644 --- a/modules/mono/class_db_api_json.h +++ b/modules/mono/class_db_api_json.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 846c84d222..e14e919f92 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -115,7 +115,7 @@ void CSharpLanguage::init() { gdmono->initialize(); #if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) - // Generate bindings here, before loading assemblies. `initialize_load_assemblies` aborts + // Generate bindings here, before loading assemblies. 'initialize_load_assemblies' aborts // the applications if the api assemblies or the main tools assembly is missing, but this // is not a problem for BindingsGenerator as it only needs the tools project editor assembly. List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); @@ -123,7 +123,7 @@ void CSharpLanguage::init() { #endif #ifndef MONO_GLUE_ENABLED - print_line("Run this binary with `--generate-mono-glue path/to/modules/mono/glue`"); + print_line("Run this binary with '--generate-mono-glue path/to/modules/mono/glue'"); #endif gdmono->initialize_load_assemblies(); @@ -1036,6 +1036,7 @@ void CSharpLanguage::_load_scripts_metadata() { String old_json; Error ferr = read_all_file_utf8(scripts_metadata_path, old_json); + ERR_FAIL_COND(ferr != OK); Variant old_dict_var; @@ -1043,7 +1044,7 @@ void CSharpLanguage::_load_scripts_metadata() { int err_line; Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line); if (json_err != OK) { - ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")"); + ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")."); return; } @@ -1053,7 +1054,7 @@ void CSharpLanguage::_load_scripts_metadata() { print_verbose("Successfully loaded scripts metadata"); } else { if (!Engine::get_singleton()->is_editor_hint()) { - ERR_PRINT("Missing scripts metadata file"); + ERR_PRINT("Missing scripts metadata file."); } } } @@ -1100,7 +1101,7 @@ bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const S _debug_parse_err_line = p_line; _debug_parse_err_file = p_file; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, false); + ScriptDebugger::get_singleton()->debug(this, false, true); return true; } else { return false; @@ -1768,12 +1769,8 @@ MonoObject *CSharpInstance::_internal_new_managed() { // Search the constructor first, to fail with an error if it's not found before allocating anything else. GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - if (ctor == NULL) { - ERR_PRINTS("Cannot create script instance because the class does not define a parameterless constructor: " + script->get_path()); - - ERR_EXPLAIN("Constructor not found"); - ERR_FAIL_V(NULL); - } + ERR_FAIL_NULL_V_MSG(ctor, NULL, + "Cannot create script instance because the class does not define a parameterless constructor: '" + script->get_path() + "'."); CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); @@ -1792,8 +1789,7 @@ MonoObject *CSharpInstance::_internal_new_managed() { owner = NULL; - ERR_EXPLAIN("Failed to allocate memory for the object"); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object."); } // Tie managed to unmanaged @@ -2233,7 +2229,7 @@ bool CSharpScript::_update_exports() { MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); if (!tmp_object) { - ERR_PRINT("Failed to allocate temporary MonoObject"); + ERR_PRINT("Failed to allocate temporary MonoObject."); return false; } @@ -2241,12 +2237,8 @@ bool CSharpScript::_update_exports() { GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - if (ctor == NULL) { - ERR_PRINTS("Cannot construct temporary MonoObject because the class does not define a parameterless constructor: " + get_path()); - - ERR_EXPLAIN("Constructor not found"); - ERR_FAIL_V(NULL); - } + ERR_FAIL_NULL_V_MSG(ctor, NULL, + "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); MonoException *ctor_exc = NULL; ctor->invoke(tmp_object, NULL, &ctor_exc); @@ -2399,7 +2391,7 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve arg.type = GDMonoMarshal::managed_to_variant_type(types[i]); if (arg.type == Variant::NIL) { - ERR_PRINTS("Unknown type of signal parameter: " + arg.name + " in " + p_class->get_full_name()); + ERR_PRINTS("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'."); return false; } @@ -2427,7 +2419,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect if (p_member->is_static()) { if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) - ERR_PRINTS("Cannot export member because it is static: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + ERR_PRINTS("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); return false; } @@ -2450,12 +2442,12 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); if (!property->has_getter()) { if (exported) - ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + ERR_PRINTS("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); return false; } if (!property->has_setter()) { if (exported) - ERR_PRINTS("Write-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + ERR_PRINTS("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); return false; } } @@ -2474,16 +2466,15 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect String hint_string; if (variant_type == Variant::NIL) { - ERR_PRINTS("Unknown exported member type: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + ERR_PRINTS("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); return false; } int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string); - if (hint_res == -1) { - ERR_EXPLAIN("Error while trying to determine information about the exported member: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(hint_res == -1, false, + "Error while trying to determine information about the exported member: '" + + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); if (hint_res == 0) { hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); @@ -2532,17 +2523,11 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, NULL); - if (val_obj == NULL) { - ERR_EXPLAIN("Failed to get '" + enum_field_name + "' constant enum value"); - ERR_FAIL_V(-1); - } + ERR_FAIL_NULL_V_MSG(val_obj, -1, "Failed to get '" + enum_field_name + "' constant enum value."); bool r_error; uint64_t val = GDMonoUtils::unbox_enum_value(val_obj, enum_basetype, r_error); - if (r_error) { - ERR_EXPLAIN("Failed to unbox '" + enum_field_name + "' constant enum value"); - ERR_FAIL_V(-1); - } + ERR_FAIL_COND_V_MSG(r_error, -1, "Failed to unbox '" + enum_field_name + "' constant enum value."); if (val != (unsigned int)i) { uses_default_values = false; @@ -2577,17 +2562,11 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage PropertyHint elem_hint = PROPERTY_HINT_NONE; String elem_hint_string; - if (elem_variant_type == Variant::NIL) { - ERR_EXPLAIN("Unknown array element type"); - ERR_FAIL_V(-1); - } + ERR_FAIL_COND_V_MSG(elem_variant_type == Variant::NIL, -1, "Unknown array element type."); int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string); - if (hint_res == -1) { - ERR_EXPLAIN("Error while trying to determine information about the array element type"); - ERR_FAIL_V(-1); - } + ERR_FAIL_COND_V_MSG(hint_res == -1, -1, "Error while trying to determine information about the array element type."); // Format: type/hint:hint_string r_hint_string = itos(elem_variant_type) + "/" + itos(elem_hint) + ":" + elem_hint_string; @@ -2682,7 +2661,7 @@ void CSharpScript::_get_property_list(List<PropertyInfo> *p_properties) const { void CSharpScript::_bind_methods() { - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo(Variant::OBJECT, "new")); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo("new")); } Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) { @@ -2775,7 +2754,7 @@ bool CSharpScript::can_instance() const { "Compile", ProjectSettings::get_singleton()->globalize_path(get_path())); } else { - ERR_PRINTS("Cannot add " + get_path() + " to the C# project because it could not be created."); + ERR_PRINTS("C# project could not be created; cannot add file: '" + get_path() + "'."); } } } @@ -2793,12 +2772,10 @@ bool CSharpScript::can_instance() const { if (extra_cond && !script_class) { if (GDMono::get_singleton()->get_project_assembly() == NULL) { // The project assembly is not loaded - ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path()); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'."); } else { // The project assembly is loaded, but the class could not found - ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path()); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'."); } } @@ -2820,14 +2797,12 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg // Search the constructor first, to fail with an error if it's not found before allocating anything else. GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); if (ctor == NULL) { - if (p_argcount == 0) { - String path = get_path(); - ERR_PRINTS("Cannot create script instance. The class '" + script_class->get_full_name() + - "' does not define a parameterless constructor." + (path.empty() ? String() : ". Path: " + path)); - } + ERR_FAIL_COND_V_MSG(p_argcount == 0, NULL, + "Cannot create script instance. The class '" + script_class->get_full_name() + + "' does not define a parameterless constructor." + + (get_path().empty() ? String() : " Path: '" + get_path() + "'.")); - ERR_EXPLAIN("Constructor not found"); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Constructor not found."); } Ref<Reference> ref; @@ -2878,8 +2853,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg p_owner->set_script_instance(NULL); r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_EXPLAIN("Failed to allocate memory for the object"); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object."); } // Tie managed to unmanaged @@ -2913,11 +2887,10 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::Call r_error.error = Variant::CallError::CALL_OK; REF ref; - Object *owner = NULL; ERR_FAIL_NULL_V(native, Variant()); - owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native)); + Object *owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native)); Reference *r = Object::cast_to<Reference>(owner); if (r) { @@ -2951,8 +2924,8 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { if (ScriptDebugger::get_singleton()) { CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); } - ERR_EXPLAIN("Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + native_name + + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'."); } } @@ -3204,12 +3177,12 @@ int CSharpScript::get_member_line(const StringName &p_member) const { Error CSharpScript::load_source_code(const String &p_path) { Error ferr = read_all_file_utf8(p_path, source); - if (ferr != OK) { - if (ferr == ERR_INVALID_DATA) { - ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); - } - ERR_FAIL_V(ferr); - } + + ERR_FAIL_COND_V_MSG(ferr != OK, ferr, + ferr == ERR_INVALID_DATA ? + "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded." + " Please ensure that scripts are saved in valid UTF-8 unicode." : + "Failed to read file: '" + p_path + "'."); #ifdef TOOLS_ENABLED source_changed_cache = true; @@ -3278,8 +3251,7 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p #ifdef DEBUG_ENABLED // User is responsible for thread attach/detach - ERR_EXPLAIN("Thread is not attached"); - CRASH_COND(mono_domain_get() == NULL); + CRASH_COND_MSG(mono_domain_get() == NULL, "Thread is not attached."); #endif #endif @@ -3346,8 +3318,7 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r "Compile", ProjectSettings::get_singleton()->globalize_path(p_path)); } else { - ERR_PRINTS("Failed to create C# project"); - ERR_PRINTS("Cannot add " + p_path + " to the C# project"); + ERR_PRINTS("C# project could not be created; cannot add file: '" + p_path + "'."); } } #endif diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj index f3ac353c0f..dcfdd83831 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -11,6 +11,7 @@ <AssemblyName>GodotTools.BuildLogger</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> + <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PlatformTarget>AnyCPU</PlatformTarget> diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj index f36b40f87c..24c7cb1573 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -8,6 +8,7 @@ <RootNamespace>GodotTools.Core</RootNamespace> <AssemblyName>GodotTools.Core</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs new file mode 100644 index 0000000000..7a2ff2ca56 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs @@ -0,0 +1,33 @@ +using System; + +namespace GodotTools.IdeConnection +{ + public class ConsoleLogger : ILogger + { + public void LogDebug(string message) + { + Console.WriteLine("DEBUG: " + message); + } + + public void LogInfo(string message) + { + Console.WriteLine("INFO: " + message); + } + + public void LogWarning(string message) + { + Console.WriteLine("WARN: " + message); + } + + public void LogError(string message) + { + Console.WriteLine("ERROR: " + message); + } + + public void LogError(string message, Exception e) + { + Console.WriteLine("EXCEPTION: " + message); + Console.WriteLine(e); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs new file mode 100644 index 0000000000..be89638241 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs @@ -0,0 +1,94 @@ +using System; +using Path = System.IO.Path; + +namespace GodotTools.IdeConnection +{ + public class GodotIdeBase : IDisposable + { + private ILogger logger; + + public ILogger Logger + { + get => logger ?? (logger = new ConsoleLogger()); + set => logger = value; + } + + private readonly string projectMetadataDir; + + protected const string MetaFileName = "ide_server_meta.txt"; + protected string MetaFilePath => Path.Combine(projectMetadataDir, MetaFileName); + + private GodotIdeConnection connection; + protected readonly object ConnectionLock = new object(); + + public bool IsDisposed { get; private set; } = false; + + public bool IsConnected => connection != null && !connection.IsDisposed && connection.IsConnected; + + public event Action Connected + { + add + { + if (connection != null && !connection.IsDisposed) + connection.Connected += value; + } + remove + { + if (connection != null && !connection.IsDisposed) + connection.Connected -= value; + } + } + + protected GodotIdeConnection Connection + { + get => connection; + set + { + connection?.Dispose(); + connection = value; + } + } + + protected GodotIdeBase(string projectMetadataDir) + { + this.projectMetadataDir = projectMetadataDir; + } + + protected void DisposeConnection() + { + lock (ConnectionLock) + { + connection?.Dispose(); + } + } + + ~GodotIdeBase() + { + Dispose(disposing: false); + } + + public void Dispose() + { + if (IsDisposed) + return; + + lock (ConnectionLock) + { + if (IsDisposed) // lock may not be fair + return; + IsDisposed = true; + } + + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + connection?.Dispose(); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs new file mode 100644 index 0000000000..4f56a8d71b --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace GodotTools.IdeConnection +{ + public abstract class GodotIdeClient : GodotIdeBase + { + protected GodotIdeMetadata GodotIdeMetadata; + + private readonly FileSystemWatcher fsWatcher; + + protected GodotIdeClient(string projectMetadataDir) : base(projectMetadataDir) + { + messageHandlers = InitializeMessageHandlers(); + + // FileSystemWatcher requires an existing directory + if (!File.Exists(projectMetadataDir)) + Directory.CreateDirectory(projectMetadataDir); + + fsWatcher = new FileSystemWatcher(projectMetadataDir, MetaFileName); + } + + private void OnMetaFileChanged(object sender, FileSystemEventArgs e) + { + if (IsDisposed) + return; + + lock (ConnectionLock) + { + if (IsDisposed) + return; + + if (!File.Exists(MetaFilePath)) + return; + + var metadata = ReadMetadataFile(); + + if (metadata != null && metadata != GodotIdeMetadata) + { + GodotIdeMetadata = metadata.Value; + ConnectToServer(); + } + } + } + + private void OnMetaFileDeleted(object sender, FileSystemEventArgs e) + { + if (IsDisposed) + return; + + if (IsConnected) + DisposeConnection(); + + // The file may have been re-created + + lock (ConnectionLock) + { + if (IsDisposed) + return; + + if (IsConnected || !File.Exists(MetaFilePath)) + return; + + var metadata = ReadMetadataFile(); + + if (metadata != null) + { + GodotIdeMetadata = metadata.Value; + ConnectToServer(); + } + } + } + + private GodotIdeMetadata? ReadMetadataFile() + { + using (var reader = File.OpenText(MetaFilePath)) + { + string portStr = reader.ReadLine(); + + if (portStr == null) + return null; + + string editorExecutablePath = reader.ReadLine(); + + if (editorExecutablePath == null) + return null; + + if (!int.TryParse(portStr, out int port)) + return null; + + return new GodotIdeMetadata(port, editorExecutablePath); + } + } + + private void ConnectToServer() + { + var tcpClient = new TcpClient(); + + Connection = new GodotIdeConnectionClient(tcpClient, HandleMessage); + Connection.Logger = Logger; + + try + { + Logger.LogInfo("Connecting to Godot Ide Server"); + + tcpClient.Connect(IPAddress.Loopback, GodotIdeMetadata.Port); + + Logger.LogInfo("Connection open with Godot Ide Server"); + + var clientThread = new Thread(Connection.Start) + { + IsBackground = true, + Name = "Godot Ide Connection Client" + }; + clientThread.Start(); + } + catch (SocketException e) + { + if (e.SocketErrorCode == SocketError.ConnectionRefused) + Logger.LogError("The connection to the Godot Ide Server was refused"); + else + throw; + } + } + + public void Start() + { + Logger.LogInfo("Starting Godot Ide Client"); + + fsWatcher.Changed += OnMetaFileChanged; + fsWatcher.Deleted += OnMetaFileDeleted; + fsWatcher.EnableRaisingEvents = true; + + lock (ConnectionLock) + { + if (IsDisposed) + return; + + if (!File.Exists(MetaFilePath)) + { + Logger.LogInfo("There is no Godot Ide Server running"); + return; + } + + var metadata = ReadMetadataFile(); + + if (metadata != null) + { + GodotIdeMetadata = metadata.Value; + ConnectToServer(); + } + else + { + Logger.LogError("Failed to read Godot Ide metadata file"); + } + } + } + + public bool WriteMessage(Message message) + { + return Connection.WriteMessage(message); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + fsWatcher?.Dispose(); + } + } + + protected virtual bool HandleMessage(Message message) + { + if (messageHandlers.TryGetValue(message.Id, out var action)) + { + action(message.Arguments); + return true; + } + + return false; + } + + private readonly Dictionary<string, Action<string[]>> messageHandlers; + + private Dictionary<string, Action<string[]>> InitializeMessageHandlers() + { + return new Dictionary<string, Action<string[]>> + { + ["OpenFile"] = args => + { + switch (args.Length) + { + case 1: + OpenFile(file: args[0]); + return; + case 2: + OpenFile(file: args[0], line: int.Parse(args[1])); + return; + case 3: + OpenFile(file: args[0], line: int.Parse(args[1]), column: int.Parse(args[2])); + return; + default: + throw new ArgumentException(); + } + } + }; + } + + protected abstract void OpenFile(string file); + protected abstract void OpenFile(string file, int line); + protected abstract void OpenFile(string file, int line, int column); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs new file mode 100644 index 0000000000..e7e81f175e --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs @@ -0,0 +1,207 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net.Sockets; +using System.Text; + +namespace GodotTools.IdeConnection +{ + public abstract class GodotIdeConnection : IDisposable + { + protected const string Version = "1.0"; + + protected static readonly string ClientHandshake = $"Godot Ide Client Version {Version}"; + protected static readonly string ServerHandshake = $"Godot Ide Server Version {Version}"; + + private const int ClientWriteTimeout = 8000; + private readonly TcpClient tcpClient; + + private TextReader clientReader; + private TextWriter clientWriter; + + private readonly object writeLock = new object(); + + private readonly Func<Message, bool> messageHandler; + + public event Action Connected; + + private ILogger logger; + + public ILogger Logger + { + get => logger ?? (logger = new ConsoleLogger()); + set => logger = value; + } + + public bool IsDisposed { get; private set; } = false; + + public bool IsConnected => tcpClient.Client != null && tcpClient.Client.Connected; + + protected GodotIdeConnection(TcpClient tcpClient, Func<Message, bool> messageHandler) + { + this.tcpClient = tcpClient; + this.messageHandler = messageHandler; + } + + public void Start() + { + try + { + if (!StartConnection()) + return; + + string messageLine; + while ((messageLine = ReadLine()) != null) + { + if (!MessageParser.TryParse(messageLine, out Message msg)) + { + Logger.LogError($"Received message with invalid format: {messageLine}"); + continue; + } + + Logger.LogDebug($"Received message: {msg}"); + + if (msg.Id == "close") + { + Logger.LogInfo("Closing connection"); + return; + } + + try + { + try + { + Debug.Assert(messageHandler != null); + + if (!messageHandler(msg)) + Logger.LogError($"Received unknown message: {msg}"); + } + catch (Exception e) + { + Logger.LogError($"Message handler for '{msg}' failed with exception", e); + } + } + catch (Exception e) + { + Logger.LogError($"Exception thrown from message handler. Message: {msg}", e); + } + } + } + catch (Exception e) + { + Logger.LogError($"Unhandled exception in the Godot Ide Connection thread", e); + } + finally + { + Dispose(); + } + } + + private bool StartConnection() + { + NetworkStream clientStream = tcpClient.GetStream(); + + clientReader = new StreamReader(clientStream, Encoding.UTF8); + + lock (writeLock) + clientWriter = new StreamWriter(clientStream, Encoding.UTF8); + + clientStream.WriteTimeout = ClientWriteTimeout; + + if (!WriteHandshake()) + { + Logger.LogError("Could not write handshake"); + return false; + } + + if (!IsValidResponseHandshake(ReadLine())) + { + Logger.LogError("Received invalid handshake"); + return false; + } + + Connected?.Invoke(); + + Logger.LogInfo("Godot Ide connection started"); + + return true; + } + + private string ReadLine() + { + try + { + return clientReader?.ReadLine(); + } + catch (Exception e) + { + if (IsDisposed) + { + var se = e as SocketException ?? e.InnerException as SocketException; + if (se != null && se.SocketErrorCode == SocketError.Interrupted) + return null; + } + + throw; + } + } + + public bool WriteMessage(Message message) + { + Logger.LogDebug($"Sending message {message}"); + + var messageComposer = new MessageComposer(); + + messageComposer.AddArgument(message.Id); + foreach (string argument in message.Arguments) + messageComposer.AddArgument(argument); + + return WriteLine(messageComposer.ToString()); + } + + protected bool WriteLine(string text) + { + if (clientWriter == null || IsDisposed || !IsConnected) + return false; + + lock (writeLock) + { + try + { + clientWriter.WriteLine(text); + clientWriter.Flush(); + } + catch (Exception e) + { + if (!IsDisposed) + { + var se = e as SocketException ?? e.InnerException as SocketException; + if (se != null && se.SocketErrorCode == SocketError.Shutdown) + Logger.LogInfo("Client disconnected ungracefully"); + else + Logger.LogError("Exception thrown when trying to write to client", e); + + Dispose(); + } + } + } + + return true; + } + + protected abstract bool WriteHandshake(); + protected abstract bool IsValidResponseHandshake(string handshakeLine); + + public void Dispose() + { + if (IsDisposed) + return; + + IsDisposed = true; + + clientReader?.Dispose(); + clientWriter?.Dispose(); + ((IDisposable) tcpClient)?.Dispose(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs new file mode 100644 index 0000000000..1b11a14358 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs @@ -0,0 +1,24 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace GodotTools.IdeConnection +{ + public class GodotIdeConnectionClient : GodotIdeConnection + { + public GodotIdeConnectionClient(TcpClient tcpClient, Func<Message, bool> messageHandler) + : base(tcpClient, messageHandler) + { + } + + protected override bool WriteHandshake() + { + return WriteLine(ClientHandshake); + } + + protected override bool IsValidResponseHandshake(string handshakeLine) + { + return handshakeLine == ServerHandshake; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs new file mode 100644 index 0000000000..aa98dc7ca3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs @@ -0,0 +1,24 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace GodotTools.IdeConnection +{ + public class GodotIdeConnectionServer : GodotIdeConnection + { + public GodotIdeConnectionServer(TcpClient tcpClient, Func<Message, bool> messageHandler) + : base(tcpClient, messageHandler) + { + } + + protected override bool WriteHandshake() + { + return WriteLine(ServerHandshake); + } + + protected override bool IsValidResponseHandshake(string handshakeLine) + { + return handshakeLine == ClientHandshake; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs new file mode 100644 index 0000000000..d16daba0e2 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs @@ -0,0 +1,45 @@ +namespace GodotTools.IdeConnection +{ + public struct GodotIdeMetadata + { + public int Port { get; } + public string EditorExecutablePath { get; } + + public GodotIdeMetadata(int port, string editorExecutablePath) + { + Port = port; + EditorExecutablePath = editorExecutablePath; + } + + public static bool operator ==(GodotIdeMetadata a, GodotIdeMetadata b) + { + return a.Port == b.Port && a.EditorExecutablePath == b.EditorExecutablePath; + } + + public static bool operator !=(GodotIdeMetadata a, GodotIdeMetadata b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + if (obj is GodotIdeMetadata metadata) + return metadata == this; + + return false; + } + + public bool Equals(GodotIdeMetadata other) + { + return Port == other.Port && EditorExecutablePath == other.EditorExecutablePath; + } + + public override int GetHashCode() + { + unchecked + { + return (Port * 397) ^ (EditorExecutablePath != null ? EditorExecutablePath.GetHashCode() : 0); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj new file mode 100644 index 0000000000..94e525715b --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GodotTools.IdeConnection</RootNamespace> + <AssemblyName>GodotTools.IdeConnection</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <LangVersion>7</LangVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>portable</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>portable</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <Compile Include="ConsoleLogger.cs" /> + <Compile Include="GodotIdeMetadata.cs" /> + <Compile Include="GodotIdeBase.cs" /> + <Compile Include="GodotIdeClient.cs" /> + <Compile Include="GodotIdeConnection.cs" /> + <Compile Include="GodotIdeConnectionClient.cs" /> + <Compile Include="GodotIdeConnectionServer.cs" /> + <Compile Include="ILogger.cs" /> + <Compile Include="Message.cs" /> + <Compile Include="MessageComposer.cs" /> + <Compile Include="MessageParser.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs new file mode 100644 index 0000000000..614bb30271 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs @@ -0,0 +1,13 @@ +using System; + +namespace GodotTools.IdeConnection +{ + public interface ILogger + { + void LogDebug(string message); + void LogInfo(string message); + void LogWarning(string message); + void LogError(string message); + void LogError(string message, Exception e); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs new file mode 100644 index 0000000000..f24d324ae3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs @@ -0,0 +1,21 @@ +using System.Linq; + +namespace GodotTools.IdeConnection +{ + public struct Message + { + public string Id { get; set; } + public string[] Arguments { get; set; } + + public Message(string id, params string[] arguments) + { + Id = id; + Arguments = arguments; + } + + public override string ToString() + { + return $"(Id: '{Id}', Arguments: '{string.Join(",", Arguments)}')"; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs new file mode 100644 index 0000000000..9e4cd6ec1a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs @@ -0,0 +1,46 @@ +using System.Linq; +using System.Text; + +namespace GodotTools.IdeConnection +{ + public class MessageComposer + { + private readonly StringBuilder stringBuilder = new StringBuilder(); + + private static readonly char[] CharsToEscape = { '\\', '"' }; + + public void AddArgument(string argument) + { + AddArgument(argument, quoted: argument.Contains(",")); + } + + public void AddArgument(string argument, bool quoted) + { + if (stringBuilder.Length > 0) + stringBuilder.Append(','); + + if (quoted) + { + stringBuilder.Append('"'); + + foreach (char @char in argument) + { + if (CharsToEscape.Contains(@char)) + stringBuilder.Append('\\'); + stringBuilder.Append(@char); + } + + stringBuilder.Append('"'); + } + else + { + stringBuilder.Append(argument); + } + } + + public override string ToString() + { + return stringBuilder.ToString(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs new file mode 100644 index 0000000000..ed691e481f --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace GodotTools.IdeConnection +{ + public static class MessageParser + { + public static bool TryParse(string messageLine, out Message message) + { + var arguments = new List<string>(); + var stringBuilder = new StringBuilder(); + + bool expectingArgument = true; + + for (int i = 0; i < messageLine.Length; i++) + { + char @char = messageLine[i]; + + if (@char == ',') + { + if (expectingArgument) + arguments.Add(string.Empty); + + expectingArgument = true; + continue; + } + + bool quoted = false; + + if (messageLine[i] == '"') + { + quoted = true; + i++; + } + + while (i < messageLine.Length) + { + @char = messageLine[i]; + + if (quoted && @char == '"') + { + i++; + break; + } + + if (@char == '\\') + { + i++; + if (i < messageLine.Length) + break; + + stringBuilder.Append(messageLine[i]); + } + else if (!quoted && @char == ',') + { + break; // We don't increment the counter to allow the colon to be parsed after this + } + else + { + stringBuilder.Append(@char); + } + + i++; + } + + arguments.Add(stringBuilder.ToString()); + stringBuilder.Clear(); + + expectingArgument = false; + } + + if (arguments.Count == 0) + { + message = new Message(); + return false; + } + + message = new Message + { + Id = arguments[0], + Arguments = arguments.Skip(1).ToArray() + }; + + return true; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c7c00e66a2 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GodotTools.IdeConnection")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("92600954-25F0-4291-8E11-1FEE9FC4BE20")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index 08b8ba3946..ab3a5d1aea 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -9,6 +9,7 @@ <AssemblyName>GodotTools.ProjectEditor</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath> + <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -36,9 +37,8 @@ entire solution. $(SolutionDir) is not defined in that case, so we need to workaround that. We make SCons restore the NuGet packages in the project directory instead in this case. --> - <HintPath Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> - <HintPath>$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> - <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> <!-- Are you happy CI? --> + <HintPath Condition=" '$(SolutionDir)' != '' And Exists('$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll') ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <HintPath Condition=" '$(SolutionDir)' == '' Or !Exists('$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll') ">$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> </Reference> </ItemGroup> <ItemGroup> diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln index 6f7d44bec2..a3438ea5f3 100644 --- a/modules/mono/editor/GodotTools/GodotTools.sln +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotToo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeConnection", "GodotTools.IdeConnection\GodotTools.IdeConnection.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,5 +33,9 @@ Global {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.Build.0 = Release|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs index 53ff0891d5..44813f962c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs @@ -9,7 +9,7 @@ using Path = System.IO.Path; namespace GodotTools { - public class MonoBottomPanel : VBoxContainer + public class BottomPanel : VBoxContainer { private EditorInterface editorInterface; @@ -34,7 +34,7 @@ namespace GodotTools for (int i = 0; i < buildTabs.GetChildCount(); i++) { - var tab = (MonoBuildTab) buildTabs.GetChild(i); + var tab = (BuildTab) buildTabs.GetChild(i); if (tab == null) continue; @@ -49,11 +49,11 @@ namespace GodotTools itemTooltip += "\nStatus: "; if (tab.BuildExited) - itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored"; + itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored"; else itemTooltip += "Running"; - if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error) + if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error) itemTooltip += $"\nErrors: {tab.ErrorCount}"; itemTooltip += $"\nWarnings: {tab.WarningCount}"; @@ -68,15 +68,15 @@ namespace GodotTools } } - public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo) + public BuildTab GetBuildTabFor(BuildInfo buildInfo) { - foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren())) + foreach (var buildTab in new Array<BuildTab>(buildTabs.GetChildren())) { if (buildTab.BuildInfo.Equals(buildInfo)) return buildTab; } - var newBuildTab = new MonoBuildTab(buildInfo); + var newBuildTab = new BuildTab(buildInfo); AddBuildTab(newBuildTab); return newBuildTab; @@ -120,7 +120,7 @@ namespace GodotTools if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) throw new InvalidOperationException("No tab selected"); - var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + var buildTab = (BuildTab) buildTabs.GetChild(currentTab); buildTab.WarningsVisible = pressed; buildTab.UpdateIssuesList(); } @@ -132,7 +132,7 @@ namespace GodotTools if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) throw new InvalidOperationException("No tab selected"); - var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + var buildTab = (BuildTab) buildTabs.GetChild(currentTab); buildTab.ErrorsVisible = pressed; buildTab.UpdateIssuesList(); } @@ -145,7 +145,7 @@ namespace GodotTools string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); - CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); if (File.Exists(editorScriptsMetadataPath)) { @@ -166,7 +166,7 @@ namespace GodotTools Internal.GodotIs32Bits() ? "32" : "64" }; - bool buildSuccess = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines); + bool buildSuccess = BuildManager.BuildProjectBlocking("Tools", godotDefines); if (!buildSuccess) return; @@ -193,9 +193,9 @@ namespace GodotTools int selectedItem = selectedItems[0]; - var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem); + var buildTab = (BuildTab) buildTabs.GetTabControl(selectedItem); - OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName)); + OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, BuildManager.MsBuildLogFileName)); } public override void _Notification(int what) @@ -211,13 +211,13 @@ namespace GodotTools } } - public void AddBuildTab(MonoBuildTab buildTab) + public void AddBuildTab(BuildTab buildTab) { buildTabs.AddChild(buildTab); RaiseBuildTab(buildTab); } - public void RaiseBuildTab(MonoBuildTab buildTab) + public void RaiseBuildTab(BuildTab buildTab) { if (buildTab.GetParent() != buildTabs) throw new InvalidOperationException("Build tab is not in the tabs list"); diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index d8cb9024c3..9a2b2e3a26 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -46,8 +46,8 @@ namespace GodotTools.Build { if (OS.IsWindows()) { - return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool") - == GodotSharpBuilds.BuildTool.MsBuildMono; + return (BuildManager.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool") + == BuildManager.BuildTool.MsBuildMono; } return false; @@ -103,16 +103,16 @@ namespace GodotTools.Build return process; } - public static int Build(MonoBuildInfo monoBuildInfo) + public static int Build(BuildInfo buildInfo) { - return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration, - monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + return Build(buildInfo.Solution, buildInfo.Configuration, + buildInfo.LogsDirPath, buildInfo.CustomProperties); } - public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo) + public static async Task<int> BuildAsync(BuildInfo buildInfo) { - return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration, - monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + return await BuildAsync(buildInfo.Solution, buildInfo.Configuration, + buildInfo.LogsDirPath, buildInfo.CustomProperties); } public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index 926aabdf89..4c1e47ecad 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -19,13 +19,13 @@ namespace GodotTools.Build public static string FindMsBuild() { var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); + var buildTool = (BuildManager.BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); if (OS.IsWindows()) { switch (buildTool) { - case GodotSharpBuilds.BuildTool.MsBuildVs: + case BuildManager.BuildTool.MsBuildVs: { if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath)) { @@ -34,7 +34,7 @@ namespace GodotTools.Build if (_msbuildToolsPath.Empty()) { - throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); } } @@ -43,13 +43,13 @@ namespace GodotTools.Build return Path.Combine(_msbuildToolsPath, "MSBuild.exe"); } - case GodotSharpBuilds.BuildTool.MsBuildMono: + case BuildManager.BuildTool.MsBuildMono: { string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat"); if (!File.Exists(msbuildPath)) { - throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}"); + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildMono}'. Tried with path: {msbuildPath}"); } return msbuildPath; @@ -61,7 +61,7 @@ namespace GodotTools.Build if (OS.IsUnix()) { - if (buildTool == GodotSharpBuilds.BuildTool.MsBuildMono) + if (buildTool == BuildManager.BuildTool.MsBuildMono) { if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath)) { @@ -71,7 +71,7 @@ namespace GodotTools.Build if (_msbuildUnixPath.Empty()) { - throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'"); + throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMsbuildMono}'"); } return _msbuildUnixPath; diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs index 858e852392..70bd552f2f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs @@ -7,7 +7,7 @@ using Path = System.IO.Path; namespace GodotTools { [Serializable] - public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization + public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization { public string Solution { get; } public string Configuration { get; } @@ -17,7 +17,7 @@ namespace GodotTools public override bool Equals(object obj) { - if (obj is MonoBuildInfo other) + if (obj is BuildInfo other) return other.Solution == Solution && other.Configuration == Configuration; return false; @@ -34,11 +34,11 @@ namespace GodotTools } } - private MonoBuildInfo() + private BuildInfo() { } - public MonoBuildInfo(string solution, string configuration) + public BuildInfo(string solution, string configuration) { Solution = solution; Configuration = configuration; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs index de3a4d9a6e..417032da54 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs @@ -6,15 +6,13 @@ using GodotTools.Build; using GodotTools.Internals; using GodotTools.Utils; using static GodotTools.Internals.Globals; -using Error = Godot.Error; using File = GodotTools.Utils.File; -using Directory = GodotTools.Utils.Directory; namespace GodotTools { - public static class GodotSharpBuilds + public static class BuildManager { - private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>(); + private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>(); public const string PropNameMsbuildMono = "MSBuild (Mono)"; public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)"; @@ -28,7 +26,7 @@ namespace GodotTools MsBuildVs } - private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo) + private static void RemoveOldIssuesFile(BuildInfo buildInfo) { var issuesFile = GetIssuesFilePath(buildInfo); @@ -38,29 +36,21 @@ namespace GodotTools File.Delete(issuesFile); } - private static string _ApiFolderName(ApiAssemblyType apiType) - { - ulong apiHash = apiType == ApiAssemblyType.Core ? - Internal.GetCoreApiHash() : - Internal.GetEditorApiHash(); - return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}"; - } - private static void ShowBuildErrorDialog(string message) { GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error"); - GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab(); + GodotSharpEditor.Instance.BottomPanel.ShowBuildTab(); } - public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); - public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); + public static void RestartBuild(BuildTab buildTab) => throw new NotImplementedException(); + public static void StopBuild(BuildTab buildTab) => throw new NotImplementedException(); - private static string GetLogFilePath(MonoBuildInfo buildInfo) + private static string GetLogFilePath(BuildInfo buildInfo) { return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName); } - private static string GetIssuesFilePath(MonoBuildInfo buildInfo) + private static string GetIssuesFilePath(BuildInfo buildInfo) { return Path.Combine(buildInfo.LogsDirPath, MsBuildIssuesFileName); } @@ -71,7 +61,7 @@ namespace GodotTools Godot.GD.Print(text); } - public static bool Build(MonoBuildInfo buildInfo) + public static bool Build(BuildInfo buildInfo) { if (BuildsInProgress.Contains(buildInfo)) throw new InvalidOperationException("A build is already in progress"); @@ -80,7 +70,7 @@ namespace GodotTools try { - MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo); buildTab.OnBuildStart(); // Required in order to update the build tasks list @@ -103,7 +93,7 @@ namespace GodotTools if (exitCode != 0) PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error); return exitCode == 0; } @@ -120,7 +110,7 @@ namespace GodotTools } } - public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo) + public static async Task<bool> BuildAsync(BuildInfo buildInfo) { if (BuildsInProgress.Contains(buildInfo)) throw new InvalidOperationException("A build is already in progress"); @@ -129,7 +119,7 @@ namespace GodotTools try { - MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo); try { @@ -148,7 +138,7 @@ namespace GodotTools if (exitCode != 0) PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error); return exitCode == 0; } @@ -165,32 +155,6 @@ namespace GodotTools } } - public static bool BuildApiSolution(string apiSlnDir, string config) - { - string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln"); - - string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config); - string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll"); - - string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config); - string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll"); - - if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile)) - return true; // The assemblies are in the output folder; assume the solution is already built - - var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config); - - // TODO Replace this global NoWarn with '#pragma warning' directives on generated files, - // once we start to actively document manually maintained C# classes - apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings - - if (Build(apiBuildInfo)) - return true; - - ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution."); - return false; - } - public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines) { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) @@ -201,13 +165,13 @@ namespace GodotTools Internal.UpdateApiAssembliesFromPrebuilt(); var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool"); + var buildTool = (BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) { pr.Step("Building project solution", 0); - var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config); + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, config); // Add Godot defines string constants = buildTool == BuildTool.MsBuildVs ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\""; @@ -240,11 +204,28 @@ namespace GodotTools string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); - CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); if (File.Exists(editorScriptsMetadataPath)) File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + var currentPlayRequest = GodotSharpEditor.Instance.GodotIdeManager.GodotIdeServer.CurrentPlayRequest; + + if (currentPlayRequest != null) + { + if (currentPlayRequest.Value.HasDebugger) + { + // Set the environment variable that will tell the player to connect to the IDE debugger + // TODO: We should probably add a better way to do this + Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", + "--debugger-agent=transport=dt_socket" + + $",address={currentPlayRequest.Value.DebuggerHost}:{currentPlayRequest.Value.DebuggerPort}" + + ",server=n"); + } + + return true; // Requested play from an external editor/IDE which already built the project + } + var godotDefines = new[] { Godot.OS.GetName(), diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs index 3a74fa2f66..807a20d9a1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs @@ -7,7 +7,7 @@ using Path = System.IO.Path; namespace GodotTools { - public class MonoBuildTab : VBoxContainer + public class BuildTab : VBoxContainer { public enum BuildResults { @@ -55,7 +55,7 @@ namespace GodotTools } } - public MonoBuildInfo BuildInfo { get; private set; } + public BuildInfo BuildInfo { get; private set; } private void _LoadIssuesFromFile(string csvFile) { @@ -199,7 +199,7 @@ namespace GodotTools ErrorCount = 0; UpdateIssuesList(); - GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); } public void OnBuildExit(BuildResults result) @@ -207,10 +207,10 @@ namespace GodotTools BuildExited = true; BuildResult = result; - _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName)); + _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); UpdateIssuesList(); - GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); } public void OnBuildExecFailed(string cause) @@ -227,7 +227,7 @@ namespace GodotTools UpdateIssuesList(); - GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); } public void RestartBuild() @@ -235,7 +235,7 @@ namespace GodotTools if (!BuildExited) throw new InvalidOperationException("Build already started"); - GodotSharpBuilds.RestartBuild(this); + BuildManager.RestartBuild(this); } public void StopBuild() @@ -243,7 +243,7 @@ namespace GodotTools if (!BuildExited) throw new InvalidOperationException("Build is not in progress"); - GodotSharpBuilds.StopBuild(this); + BuildManager.StopBuild(this); } public override void _Ready() @@ -255,11 +255,11 @@ namespace GodotTools AddChild(issuesList); } - private MonoBuildTab() + private BuildTab() { } - public MonoBuildTab(MonoBuildInfo buildInfo) + public BuildTab(BuildInfo buildInfo) { BuildInfo = buildInfo; } diff --git a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index 4535ed7247..c021a9051e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -9,7 +9,7 @@ using Directory = GodotTools.Utils.Directory; namespace GodotTools { - public static class CSharpProject + public static class CsProjOperations { public static string GenerateGameProject(string dir, string name) { diff --git a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs new file mode 100644 index 0000000000..4312ca0230 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs @@ -0,0 +1,11 @@ +namespace GodotTools +{ + public enum ExternalEditorId + { + None, + VisualStudio, // TODO (Windows-only) + VisualStudioForMac, // Mac-only + MonoDevelop, + VsCode + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 9b5afb94a3..7da7cff933 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -2,16 +2,18 @@ using Godot; using GodotTools.Utils; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; +using GodotTools.Ides; using GodotTools.Internals; using GodotTools.ProjectEditor; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; -using Path = System.IO.Path; using OS = GodotTools.Utils.OS; namespace GodotTools { + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] public class GodotSharpEditor : EditorPlugin, ISerializationListener { private EditorSettings editorSettings; @@ -24,12 +26,11 @@ namespace GodotTools private ToolButton bottomPanelBtn; - private MonoDevelopInstance monoDevelopInstance; - private MonoDevelopInstance visualStudioForMacInstance; + public GodotIdeManager GodotIdeManager { get; private set; } private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization - public MonoBottomPanel MonoBottomPanel { get; private set; } + public BottomPanel BottomPanel { get; private set; } private bool CreateProjectSolution() { @@ -44,7 +45,7 @@ namespace GodotTools if (name.Empty()) name = "UnnamedProject"; - string guid = CSharpProject.GenerateGameProject(path, name); + string guid = CsProjOperations.GenerateGameProject(path, name); if (guid.Length > 0) { @@ -133,7 +134,7 @@ namespace GodotTools return; // Failed to create solution } - Instance.MonoBottomPanel.BuildProjectPressed(); + Instance.BottomPanel.BuildProjectPressed(); } public override void _Notification(int what) @@ -153,21 +154,12 @@ namespace GodotTools } } - public enum MenuOptions + private enum MenuOptions { CreateSln, AboutCSharp, } - public enum ExternalEditor - { - None, - VisualStudio, // TODO (Windows-only) - VisualStudioForMac, // Mac-only - MonoDevelop, - VsCode - } - public void ShowErrorDialog(string message, string title = "Error") { errorDialog.WindowTitle = title; @@ -184,11 +176,30 @@ namespace GodotTools public Error OpenInExternalEditor(Script script, int line, int col) { - var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor"); + var editor = (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor"); switch (editor) { - case ExternalEditor.VsCode: + case ExternalEditorId.None: + // Tells the caller to fallback to the global external editor settings or the built-in editor + return Error.Unavailable; + case ExternalEditorId.VisualStudio: + throw new NotSupportedException(); + case ExternalEditorId.VisualStudioForMac: + goto case ExternalEditorId.MonoDevelop; + case ExternalEditorId.MonoDevelop: + { + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + if (line >= 0) + GodotIdeManager.SendOpenFile(scriptPath, line + 1, col); + else + GodotIdeManager.SendOpenFile(scriptPath); + + break; + } + + case ExternalEditorId.VsCode: { if (_vsCodePath.Empty() || !File.Exists(_vsCodePath)) { @@ -273,47 +284,6 @@ namespace GodotTools break; } - case ExternalEditor.VisualStudioForMac: - goto case ExternalEditor.MonoDevelop; - case ExternalEditor.MonoDevelop: - { - MonoDevelopInstance GetMonoDevelopInstance(string solutionPath) - { - if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac) - { - if (visualStudioForMacInstance == null) - visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac); - - return visualStudioForMacInstance; - } - - if (monoDevelopInstance == null) - monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop); - - return monoDevelopInstance; - } - - string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); - - if (line >= 0) - scriptPath += $";{line + 1};{col}"; - - try - { - GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath); - } - catch (FileNotFoundException) - { - string editorName = editor == ExternalEditor.VisualStudioForMac ? "Visual Studio" : "MonoDevelop"; - GD.PushError($"Cannot find code editor: {editorName}"); - return Error.FileNotFound; - } - - break; - } - - case ExternalEditor.None: - return Error.Unavailable; default: throw new ArgumentOutOfRangeException(); } @@ -323,12 +293,12 @@ namespace GodotTools public bool OverridesExternalEditor() { - return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None; + return (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None; } public override bool Build() { - return GodotSharpBuilds.EditorBuildCallback(); + return BuildManager.EditorBuildCallback(); } public override void EnablePlugin() @@ -347,9 +317,9 @@ namespace GodotTools errorDialog = new AcceptDialog(); editorBaseControl.AddChild(errorDialog); - MonoBottomPanel = new MonoBottomPanel(); + BottomPanel = new BottomPanel(); - bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono".TTR()); + bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR()); AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); @@ -389,7 +359,7 @@ namespace GodotTools aboutLabel.Text = "C# support in Godot Engine is in late alpha stage and, while already usable, " + "it is not meant for use in production.\n\n" + - "Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " + + "Projects can be exported to Linux, macOS, Windows and Android, but not yet to iOS, HTML5 or UWP. " + "Bugs and usability issues will be addressed gradually over future releases, " + "potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" + @@ -407,7 +377,7 @@ namespace GodotTools if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) { // Make sure the existing project has Api assembly references configured correctly - CSharpProject.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); + CsProjOperations.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); } else { @@ -427,25 +397,25 @@ namespace GodotTools AddControlToContainer(CustomControlContainer.Toolbar, buildButton); // External editor settings - EditorDef("mono/editor/external_editor", ExternalEditor.None); + EditorDef("mono/editor/external_editor", ExternalEditorId.None); string settingsHintStr = "Disabled"; if (OS.IsWindows()) { - settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } else if (OS.IsOSX()) { - settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" + - $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + settingsHintStr += $",Visual Studio:{(int) ExternalEditorId.VisualStudioForMac}" + + $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } else if (OS.IsUnix()) { - settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary @@ -461,7 +431,10 @@ namespace GodotTools AddExportPlugin(exportPlugin); exportPluginWeak = WeakRef(exportPlugin); - GodotSharpBuilds.Initialize(); + BuildManager.Initialize(); + + GodotIdeManager = new GodotIdeManager(); + AddChild(GodotIdeManager); } protected override void Dispose(bool disposing) @@ -478,6 +451,8 @@ namespace GodotTools exportPluginWeak.Dispose(); } + + GodotIdeManager?.Dispose(); } public void OnBeforeSerialize() diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs index b80fe1fab7..4f93ef8530 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs @@ -16,7 +16,7 @@ namespace GodotTools { private void AddFile(string srcPath, string dstPath, bool remap = false) { - AddFile(dstPath, File.ReadAllBytes(srcPath), remap); + AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap); } public override void _ExportFile(string path, string type, string[] features) @@ -65,14 +65,14 @@ namespace GodotTools string buildConfig = isDebug ? "Debug" : "Release"; string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); - CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); AddFile(scriptsMetadataPath, scriptsMetadataPath); // Turn export features into defines var godotDefines = features; - if (!GodotSharpBuilds.BuildProjectBlocking(buildConfig, godotDefines)) + if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines)) { GD.PushError("Failed to build project"); return; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index 01e8c87d14..3c57900873 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -10,6 +10,7 @@ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> <GodotApiConfiguration>Debug</GodotApiConfiguration> + <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -39,26 +40,31 @@ </ItemGroup> <ItemGroup> <Compile Include="Build\MsBuildFinder.cs" /> + <Compile Include="ExternalEditorId.cs" /> + <Compile Include="Ides\GodotIdeManager.cs" /> + <Compile Include="Ides\GodotIdeServer.cs" /> + <Compile Include="Ides\MonoDevelop\EditorId.cs" /> + <Compile Include="Ides\MonoDevelop\Instance.cs" /> <Compile Include="Internals\BindingsGenerator.cs" /> <Compile Include="Internals\EditorProgress.cs" /> <Compile Include="Internals\GodotSharpDirs.cs" /> <Compile Include="Internals\Internal.cs" /> <Compile Include="Internals\ScriptClassParser.cs" /> <Compile Include="Internals\Globals.cs" /> - <Compile Include="MonoDevelopInstance.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Build\BuildSystem.cs" /> <Compile Include="Utils\Directory.cs" /> <Compile Include="Utils\File.cs" /> + <Compile Include="Utils\NotifyAwaiter.cs" /> <Compile Include="Utils\OS.cs" /> <Compile Include="GodotSharpEditor.cs" /> - <Compile Include="GodotSharpBuilds.cs" /> + <Compile Include="BuildManager.cs" /> <Compile Include="HotReloadAssemblyWatcher.cs" /> - <Compile Include="MonoBuildInfo.cs" /> - <Compile Include="MonoBuildTab.cs" /> - <Compile Include="MonoBottomPanel.cs" /> + <Compile Include="BuildInfo.cs" /> + <Compile Include="BuildTab.cs" /> + <Compile Include="BottomPanel.cs" /> <Compile Include="GodotSharpExport.cs" /> - <Compile Include="CSharpProject.cs" /> + <Compile Include="CsProjOperations.cs" /> <Compile Include="Utils\CollectionExtensions.cs" /> </ItemGroup> <ItemGroup> @@ -66,6 +72,10 @@ <Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project> <Name>GodotTools.BuildLogger</Name> </ProjectReference> + <ProjectReference Include="..\GodotTools.IdeConnection\GodotTools.IdeConnection.csproj"> + <Project>{92600954-25f0-4291-8e11-1fee9fc4be20}</Project> + <Name>GodotTools.IdeConnection</Name> + </ProjectReference> <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj"> <Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project> <Name>GodotTools.ProjectEditor</Name> @@ -75,8 +85,5 @@ <Name>GodotTools.Core</Name> </ProjectReference> </ItemGroup> - <ItemGroup> - <Folder Include="Editor" /> - </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> </Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs new file mode 100644 index 0000000000..9e24138143 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -0,0 +1,166 @@ +using System; +using System.IO; +using Godot; +using GodotTools.IdeConnection; +using GodotTools.Internals; + +namespace GodotTools.Ides +{ + public class GodotIdeManager : Node, ISerializationListener + { + public GodotIdeServer GodotIdeServer { get; private set; } + + private MonoDevelop.Instance monoDevelInstance; + private MonoDevelop.Instance vsForMacInstance; + + private GodotIdeServer GetRunningServer() + { + if (GodotIdeServer != null && !GodotIdeServer.IsDisposed) + return GodotIdeServer; + StartServer(); + return GodotIdeServer; + } + + public override void _Ready() + { + StartServer(); + } + + public void OnBeforeSerialize() + { + GodotIdeServer?.Dispose(); + } + + public void OnAfterDeserialize() + { + StartServer(); + } + + private ILogger logger; + + protected ILogger Logger + { + get => logger ?? (logger = new ConsoleLogger()); + set => logger = value; + } + + private void StartServer() + { + GodotIdeServer?.Dispose(); + GodotIdeServer = new GodotIdeServer(LaunchIde, + OS.GetExecutablePath(), + ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir)); + + GodotIdeServer.Logger = Logger; + + GodotIdeServer.StartServer(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + GodotIdeServer?.Dispose(); + } + + private void LaunchIde() + { + var editor = (ExternalEditorId) GodotSharpEditor.Instance.GetEditorInterface() + .GetEditorSettings().GetSetting("mono/editor/external_editor"); + + switch (editor) + { + case ExternalEditorId.None: + case ExternalEditorId.VisualStudio: + case ExternalEditorId.VsCode: + throw new NotSupportedException(); + case ExternalEditorId.VisualStudioForMac: + goto case ExternalEditorId.MonoDevelop; + case ExternalEditorId.MonoDevelop: + { + MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath) + { + if (Utils.OS.IsOSX() && editor == ExternalEditorId.VisualStudioForMac) + { + vsForMacInstance = vsForMacInstance ?? + new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac); + return vsForMacInstance; + } + + monoDevelInstance = monoDevelInstance ?? + new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop); + return monoDevelInstance; + } + + try + { + var instance = GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath); + + if (!instance.IsRunning) + instance.Execute(); + } + catch (FileNotFoundException) + { + string editorName = editor == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop"; + GD.PushError($"Cannot find code editor: {editorName}"); + } + + break; + } + + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void WriteMessage(string id, params string[] arguments) + { + GetRunningServer().WriteMessage(new Message(id, arguments)); + } + + public void SendOpenFile(string file) + { + WriteMessage("OpenFile", file); + } + + public void SendOpenFile(string file, int line) + { + WriteMessage("OpenFile", file, line.ToString()); + } + + public void SendOpenFile(string file, int line, int column) + { + WriteMessage("OpenFile", file, line.ToString(), column.ToString()); + } + + private class GodotLogger : ILogger + { + public void LogDebug(string message) + { + if (OS.IsStdoutVerbose()) + Console.WriteLine(message); + } + + public void LogInfo(string message) + { + if (OS.IsStdoutVerbose()) + Console.WriteLine(message); + } + + public void LogWarning(string message) + { + GD.PushWarning(message); + } + + public void LogError(string message) + { + GD.PushError(message); + } + + public void LogError(string message, Exception e) + { + GD.PushError(message + "\n" + e); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs new file mode 100644 index 0000000000..309b917c71 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GodotTools.IdeConnection; +using GodotTools.Internals; +using GodotTools.Utils; +using Directory = System.IO.Directory; +using File = System.IO.File; +using Thread = System.Threading.Thread; + +namespace GodotTools.Ides +{ + public class GodotIdeServer : GodotIdeBase + { + private readonly TcpListener listener; + private readonly FileStream metaFile; + private readonly Action launchIdeAction; + private readonly NotifyAwaiter<bool> clientConnectedAwaiter = new NotifyAwaiter<bool>(); + + private async Task<bool> AwaitClientConnected() + { + return await clientConnectedAwaiter.Reset(); + } + + public GodotIdeServer(Action launchIdeAction, string editorExecutablePath, string projectMetadataDir) + : base(projectMetadataDir) + { + messageHandlers = InitializeMessageHandlers(); + + this.launchIdeAction = launchIdeAction; + + // Make sure the directory exists + Directory.CreateDirectory(projectMetadataDir); + + // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing... + const FileShare metaFileShare = FileShare.ReadWrite; + + metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare); + + listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0)); + listener.Start(); + + int port = ((IPEndPoint) listener.Server.LocalEndPoint).Port; + using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8)) + { + metaFileWriter.WriteLine(port); + metaFileWriter.WriteLine(editorExecutablePath); + } + + StartServer(); + } + + public void StartServer() + { + var serverThread = new Thread(RunServerThread) {Name = "Godot Ide Connection Server"}; + serverThread.Start(); + } + + private void RunServerThread() + { + SynchronizationContext.SetSynchronizationContext(Godot.Dispatcher.SynchronizationContext); + + try + { + while (!IsDisposed) + { + TcpClient tcpClient = listener.AcceptTcpClient(); + + Logger.LogInfo("Connection open with Ide Client"); + + lock (ConnectionLock) + { + Connection = new GodotIdeConnectionServer(tcpClient, HandleMessage); + Connection.Logger = Logger; + } + + Connected += () => clientConnectedAwaiter.SetResult(true); + + Connection.Start(); + } + } + catch (Exception e) + { + if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted)) + throw; + } + } + + public async void WriteMessage(Message message) + { + async Task LaunchIde() + { + if (IsConnected) + return; + + launchIdeAction(); + await Task.WhenAny(Task.Delay(10000), AwaitClientConnected()); + } + + await LaunchIde(); + + if (!IsConnected) + { + Logger.LogError("Cannot write message: Godot Ide Server not connected"); + return; + } + + Connection.WriteMessage(message); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + listener?.Stop(); + + metaFile?.Dispose(); + + File.Delete(MetaFilePath); + } + } + + protected virtual bool HandleMessage(Message message) + { + if (messageHandlers.TryGetValue(message.Id, out var action)) + { + action(message.Arguments); + return true; + } + + return false; + } + + private readonly Dictionary<string, Action<string[]>> messageHandlers; + + private Dictionary<string, Action<string[]>> InitializeMessageHandlers() + { + return new Dictionary<string, Action<string[]>> + { + ["Play"] = args => + { + switch (args.Length) + { + case 0: + Play(); + return; + case 2: + Play(debuggerHost: args[0], debuggerPort: int.Parse(args[1])); + return; + default: + throw new ArgumentException(); + } + }, + ["ReloadScripts"] = args => ReloadScripts() + }; + } + + private void DispatchToMainThread(Action action) + { + var d = new SendOrPostCallback(state => action()); + Godot.Dispatcher.SynchronizationContext.Post(d, null); + } + + private void Play() + { + DispatchToMainThread(() => + { + CurrentPlayRequest = new PlayRequest(); + Internal.EditorRunPlay(); + CurrentPlayRequest = null; + }); + } + + private void Play(string debuggerHost, int debuggerPort) + { + DispatchToMainThread(() => + { + CurrentPlayRequest = new PlayRequest(debuggerHost, debuggerPort); + Internal.EditorRunPlay(); + CurrentPlayRequest = null; + }); + } + + private void ReloadScripts() + { + DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts); + } + + public PlayRequest? CurrentPlayRequest { get; private set; } + + public struct PlayRequest + { + public bool HasDebugger { get; } + public string DebuggerHost { get; } + public int DebuggerPort { get; } + + public PlayRequest(string debuggerHost, int debuggerPort) + { + HasDebugger = true; + DebuggerHost = debuggerHost; + DebuggerPort = debuggerPort; + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs new file mode 100644 index 0000000000..1dfc91d6d1 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs @@ -0,0 +1,8 @@ +namespace GodotTools.Ides.MonoDevelop +{ + public enum EditorId + { + MonoDevelop = 0, + VisualStudioForMac = 1 + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index 61a0a992ce..1fdccf5bbd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs @@ -1,4 +1,3 @@ -using GodotTools.Core; using System; using System.IO; using System.Collections.Generic; @@ -6,22 +5,18 @@ using System.Diagnostics; using GodotTools.Internals; using GodotTools.Utils; -namespace GodotTools +namespace GodotTools.Ides.MonoDevelop { - public class MonoDevelopInstance + public class Instance { - public enum EditorId - { - MonoDevelop = 0, - VisualStudioForMac = 1 - } - private readonly string solutionFile; private readonly EditorId editorId; private Process process; - public void Execute(params string[] files) + public bool IsRunning => process != null && !process.HasExited; + + public void Execute() { bool newWindow = process == null || process.HasExited; @@ -29,7 +24,7 @@ namespace GodotTools string command; - if (Utils.OS.IsOSX()) + if (OS.IsOSX()) { string bundleId = BundleIds[editorId]; @@ -61,16 +56,6 @@ namespace GodotTools if (newWindow) args.Add("\"" + Path.GetFullPath(solutionFile) + "\""); - foreach (var file in files) - { - int semicolonIndex = file.IndexOf(';'); - - string filePath = semicolonIndex < 0 ? file : file.Substring(0, semicolonIndex); - string cursor = semicolonIndex < 0 ? string.Empty : file.Substring(semicolonIndex); - - args.Add("\"" + Path.GetFullPath(filePath.NormalizePath()) + cursor + "\""); - } - if (command == null) throw new FileNotFoundException(); @@ -80,7 +65,7 @@ namespace GodotTools { FileName = command, Arguments = string.Join(" ", args), - UseShellExecute = false + UseShellExecute = true }); } else @@ -89,14 +74,14 @@ namespace GodotTools { FileName = command, Arguments = string.Join(" ", args), - UseShellExecute = false + UseShellExecute = true })?.Dispose(); } } - public MonoDevelopInstance(string solutionFile, EditorId editorId) + public Instance(string solutionFile, EditorId editorId) { - if (editorId == EditorId.VisualStudioForMac && !Utils.OS.IsOSX()) + if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX()) throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform"); this.solutionFile = solutionFile; @@ -106,9 +91,9 @@ namespace GodotTools private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames; private static readonly IReadOnlyDictionary<EditorId, string> BundleIds; - static MonoDevelopInstance() + static Instance() { - if (Utils.OS.IsOSX()) + if (OS.IsOSX()) { ExecutableNames = new Dictionary<EditorId, string> { @@ -122,7 +107,7 @@ namespace GodotTools {EditorId.VisualStudioForMac, "com.microsoft.visual-studio"} }; } - else if (Utils.OS.IsWindows()) + else if (OS.IsWindows()) { ExecutableNames = new Dictionary<EditorId, string> { @@ -133,7 +118,7 @@ namespace GodotTools {EditorId.MonoDevelop, "MonoDevelop.exe"} }; } - else if (Utils.OS.IsUnix()) + else if (OS.IsUnix()) { ExecutableNames = new Dictionary<EditorId, string> { diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 9526dd3c6f..7783576910 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -46,6 +46,12 @@ namespace GodotTools.Internals public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot(); + public static void EditorRunPlay() => internal_EditorRunPlay(); + + public static void EditorRunStop() => internal_EditorRunStop(); + + public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts(); + // Internal Calls [MethodImpl(MethodImplOptions.InternalCall)] @@ -95,5 +101,14 @@ namespace GodotTools.Internals [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoWindowsInstallRoot(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_EditorRunPlay(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_EditorRunStop(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_ScriptEditorDebugger_ReloadScripts(); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs index 288c65de74..e3c2c822a5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; namespace GodotTools.Utils { @@ -17,5 +18,12 @@ namespace GodotTools.Utils return orElse; } + + public static IEnumerable<string> EnumerateLines(this TextReader textReader) + { + string line; + while ((line = textReader.ReadLine()) != null) + yield return line; + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs new file mode 100644 index 0000000000..700b786752 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.CompilerServices; + +namespace GodotTools.Utils +{ + public sealed class NotifyAwaiter<T> : INotifyCompletion + { + private Action continuation; + private Exception exception; + private T result; + + public bool IsCompleted { get; private set; } + + public T GetResult() + { + if (exception != null) + throw exception; + return result; + } + + public void OnCompleted(Action continuation) + { + if (this.continuation != null) + throw new InvalidOperationException("This awaiter has already been listened"); + this.continuation = continuation; + } + + public void SetResult(T result) + { + if (IsCompleted) + throw new InvalidOperationException("This awaiter is already completed"); + + IsCompleted = true; + this.result = result; + + continuation?.Invoke(); + } + + public void SetException(Exception exception) + { + if (IsCompleted) + throw new InvalidOperationException("This awaiter is already completed"); + + IsCompleted = true; + this.exception = exception; + + continuation?.Invoke(); + } + + public NotifyAwaiter<T> Reset() + { + continuation = null; + exception = null; + result = default(T); + IsCompleted = false; + return this; + } + + public NotifyAwaiter<T> GetAwaiter() + { + return this; + } + } +} diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 45037bf637..74710db224 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -279,7 +279,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf Vector<String> link_target_parts = link_target.split("."); if (link_target_parts.size() <= 0 || link_target_parts.size() > 2) { - ERR_PRINTS("Invalid reference format: " + tag); + ERR_PRINTS("Invalid reference format: '" + tag + "'."); xml_output.append("<c>"); xml_output.append(tag); @@ -375,7 +375,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(target_enum_itype.proxy_name); // Includes nesting class if any xml_output.append("\"/>"); } else { - ERR_PRINTS("Cannot resolve enum reference in documentation: " + link_target); + ERR_PRINTS("Cannot resolve enum reference in documentation: '" + link_target + "'."); xml_output.append("<c>"); xml_output.append(link_target); @@ -424,7 +424,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(target_iconst->proxy_name); xml_output.append("\"/>"); } else { - ERR_PRINTS("Cannot resolve global constant reference in documentation: " + link_target); + ERR_PRINTS("Cannot resolve global constant reference in documentation: '" + link_target + "'."); xml_output.append("<c>"); xml_output.append(link_target); @@ -464,7 +464,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(target_iconst->proxy_name); xml_output.append("\"/>"); } else { - ERR_PRINTS("Cannot resolve constant reference in documentation: " + link_target); + ERR_PRINTS("Cannot resolve constant reference in documentation: '" + link_target + "'."); xml_output.append("<c>"); xml_output.append(link_target); @@ -534,7 +534,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(target_itype->proxy_name); xml_output.append("\"/>"); } else { - ERR_PRINTS("Cannot resolve type reference in documentation: " + tag); + ERR_PRINTS("Cannot resolve type reference in documentation: '" + tag + "'."); xml_output.append("<c>"); xml_output.append(tag); @@ -812,7 +812,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { CRASH_COND(enum_class_name != "Variant"); // Hard-coded... - _log("Declaring global enum `%s` inside static class `%s`\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); + _log("Declaring global enum '%s' inside static class '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); p_output.append("\n" INDENT1 "public static partial class "); p_output.append(enum_class_name); @@ -1083,7 +1083,7 @@ Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { proj_err = generate_cs_core_project(core_proj_dir, core_compile_items); if (proj_err != OK) { - ERR_PRINT("Generation of the Core API C# project failed"); + ERR_PRINT("Generation of the Core API C# project failed."); return proj_err; } @@ -1094,7 +1094,7 @@ Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { proj_err = generate_cs_editor_project(editor_proj_dir, editor_compile_items); if (proj_err != OK) { - ERR_PRINT("Generation of the Editor API C# project failed"); + ERR_PRINT("Generation of the Editor API C# project failed."); return proj_err; } @@ -1112,7 +1112,7 @@ Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { // FIXME: There are some members that hide other inherited members. // - In the case of both members being the same kind, the new one must be declared -// explicitly as `new` to avoid the warning (and we must print a message about it). +// explicitly as 'new' to avoid the warning (and we must print a message about it). // - In the case of both members being of a different kind, then the new one must // be renamed to avoid the name collision (and we must print a warning about it). // - Csc warning e.g.: @@ -1186,7 +1186,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(obj_types[itype.base_name].proxy_name); output.append("\n"); } else { - ERR_PRINTS("Base type '" + itype.base_name.operator String() + "' does not exist, for class " + itype.name); + ERR_PRINTS("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'."); return ERR_INVALID_DATA; } } @@ -1273,11 +1273,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str for (const List<PropertyInterface>::Element *E = itype.properties.front(); E; E = E->next()) { const PropertyInterface &iprop = E->get(); Error prop_err = _generate_cs_property(itype, iprop, output); - if (prop_err != OK) { - ERR_EXPLAIN("Failed to generate property '" + iprop.cname.operator String() + - "' for class '" + itype.name + "'"); - ERR_FAIL_V(prop_err); - } + ERR_FAIL_COND_V_MSG(prop_err != OK, prop_err, + "Failed to generate property '" + iprop.cname.operator String() + + "' for class '" + itype.name + "'."); } } @@ -1340,10 +1338,8 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str for (const List<MethodInterface>::Element *E = itype.methods.front(); E; E = E->next()) { const MethodInterface &imethod = E->get(); Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output); - if (method_err != OK) { - ERR_EXPLAIN("Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'"); - ERR_FAIL_V(method_err); - } + ERR_FAIL_COND_V_MSG(method_err != OK, method_err, + "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'."); } if (itype.is_singleton) { @@ -1626,7 +1622,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf if (p_imethod.is_deprecated) { if (p_imethod.deprecation_message.empty()) - WARN_PRINTS("An empty deprecation message is discouraged. Method: " + p_imethod.proxy_name); + WARN_PRINTS("An empty deprecation message is discouraged. Method: '" + p_imethod.proxy_name + "'."); p_output.append(MEMBER_BEGIN "[Obsolete(\""); p_output.append(p_imethod.deprecation_message); @@ -1708,8 +1704,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf Error BindingsGenerator::generate_glue(const String &p_output_dir) { bool dir_exists = DirAccess::exists(p_output_dir); - ERR_EXPLAIN("The output directory does not exist."); - ERR_FAIL_COND_V(!dir_exists, ERR_FILE_BAD_PATH); + ERR_FAIL_COND_V_MSG(!dir_exists, ERR_FILE_BAD_PATH, "The output directory does not exist."); StringBuilder output; @@ -1742,10 +1737,8 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { for (const List<MethodInterface>::Element *E = itype.methods.front(); E; E = E->next()) { const MethodInterface &imethod = E->get(); Error method_err = _generate_glue_method(itype, imethod, output); - if (method_err != OK) { - ERR_EXPLAIN("Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'"); - ERR_FAIL_V(method_err); - } + ERR_FAIL_COND_V_MSG(method_err != OK, method_err, + "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'."); } if (itype.is_singleton) { @@ -1792,6 +1785,9 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { output.append("uint32_t get_bindings_version() { return "); output.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n"); + output.append("uint32_t get_cs_glue_version() { return "); + output.append(String::num_uint64(CS_GLUE_VERSION) + "; }\n"); + output.append("\nvoid register_generated_icalls() " OPEN_BLOCK); output.append("\tgodot_register_glue_header_icalls();\n"); @@ -1879,8 +1875,7 @@ Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE); - ERR_EXPLAIN("Cannot open file: " + p_path); - ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); + ERR_FAIL_COND_V_MSG(!file, ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'."); file->store_string(p_content.as_string()); file->close(); @@ -2091,7 +2086,7 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placehol if (found) return found; - ERR_PRINTS(String() + "Type not found. Creating placeholder: " + p_typeref.cname.operator String()); + ERR_PRINTS(String() + "Type not found. Creating placeholder: '" + p_typeref.cname.operator String() + "'."); const Map<StringName, TypeInterface>::Element *match = placeholder_types.find(p_typeref.cname); @@ -2175,13 +2170,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { } if (!ClassDB::is_class_exposed(type_cname)) { - _log("Ignoring type `%s` because it's not exposed\n", String(type_cname).utf8().get_data()); + _log("Ignoring type '%s' because it's not exposed\n", String(type_cname).utf8().get_data()); class_list.pop_front(); continue; } if (!ClassDB::is_class_enabled(type_cname)) { - _log("Ignoring type `%s` because it's not enabled\n", String(type_cname).utf8().get_data()); + _log("Ignoring type '%s' because it's not enabled\n", String(type_cname).utf8().get_data()); class_list.pop_front(); continue; } @@ -2240,7 +2235,7 @@ void BindingsGenerator::_populate_object_type_interfaces() { // Prevent the property and its enclosing type from sharing the same name if (iprop.proxy_name == itype.proxy_name) { - _log("Name of property `%s` is ambiguous with the name of its enclosing class `%s`. Renaming property to `%s_`\n", + _log("Name of property '%s' is ambiguous with the name of its enclosing class '%s'. Renaming property to '%s_'\n", iprop.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), iprop.proxy_name.utf8().get_data()); iprop.proxy_name += "_"; @@ -2298,28 +2293,26 @@ void BindingsGenerator::_populate_object_type_interfaces() { imethod.is_vararg = m && m->is_vararg(); if (!m && !imethod.is_virtual) { - if (virtual_method_list.find(method_info)) { - // A virtual method without the virtual flag. This is a special case. - - // There is no method bind, so let's fallback to Godot's object.Call(string, params) - imethod.requires_object_call = true; - - // The method Object.free is registered as a virtual method, but without the virtual flag. - // This is because this method is not supposed to be overridden, but called. - // We assume the return type is void. - imethod.return_type.cname = name_cache.type_void; - - // Actually, more methods like this may be added in the future, - // which could actually will return something different. - // Let's put this to notify us if that ever happens. - if (itype.cname != name_cache.type_Object || imethod.name != "free") { - ERR_PRINTS("Notification: New unexpected virtual non-overridable method found.\n" - "We only expected Object.free, but found " + - itype.name + "." + imethod.name); - } - } else { - ERR_EXPLAIN("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name); - ERR_FAIL(); + ERR_FAIL_COND_MSG(!virtual_method_list.find(method_info), + "Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'."); + + // A virtual method without the virtual flag. This is a special case. + + // There is no method bind, so let's fallback to Godot's object.Call(string, params) + imethod.requires_object_call = true; + + // The method Object.free is registered as a virtual method, but without the virtual flag. + // This is because this method is not supposed to be overridden, but called. + // We assume the return type is void. + imethod.return_type.cname = name_cache.type_void; + + // Actually, more methods like this may be added in the future, + // which could actually will return something different. + // Let's put this to notify us if that ever happens. + if (itype.cname != name_cache.type_Object || imethod.name != "free") { + ERR_PRINTS("Notification: New unexpected virtual non-overridable method found." + " We only expected Object.free, but found '" + + itype.name + "." + imethod.name + "'."); } } else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { imethod.return_type.cname = return_info.class_name; @@ -2328,8 +2321,8 @@ void BindingsGenerator::_populate_object_type_interfaces() { imethod.return_type.cname = return_info.class_name; if (!imethod.is_virtual && ClassDB::is_parent_class(return_info.class_name, name_cache.type_Reference) && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE) { /* clang-format off */ - ERR_PRINTS("Return type is reference but hint is not " _STR(PROPERTY_HINT_RESOURCE_TYPE) "." - " Are you returning a reference type by pointer? Method: " + itype.name + "." + imethod.name); + ERR_PRINTS("Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." + " Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'."); /* clang-format on */ ERR_FAIL(); } @@ -2394,7 +2387,7 @@ void BindingsGenerator::_populate_object_type_interfaces() { // Prevent the method and its enclosing type from sharing the same name if (imethod.proxy_name == itype.proxy_name) { - _log("Name of method `%s` is ambiguous with the name of its enclosing class `%s`. Renaming method to `%s_`\n", + _log("Name of method '%s' is ambiguous with the name of its enclosing class '%s'. Renaming method to '%s_'\n", imethod.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), imethod.proxy_name.utf8().get_data()); imethod.proxy_name += "_"; @@ -2880,8 +2873,7 @@ void BindingsGenerator::_populate_global_constants() { if (global_constants_count > 0) { Map<String, DocData::ClassDoc>::Element *match = EditorHelp::get_doc_data()->class_list.find("@GlobalScope"); - ERR_EXPLAIN("Could not find `@GlobalScope` in DocData"); - CRASH_COND(!match); + CRASH_COND_MSG(!match, "Could not find '@GlobalScope' in DocData."); const DocData::ClassDoc &global_scope_doc = match->value(); @@ -2935,7 +2927,7 @@ void BindingsGenerator::_populate_global_constants() { // HARDCODED: The Error enum have the prefix 'ERR_' for everything except 'OK' and 'FAILED'. if (ienum.cname == name_cache.enum_Error) { if (prefix_length > 0) { // Just in case it ever changes - ERR_PRINTS("Prefix for enum 'Error' is not empty"); + ERR_PRINTS("Prefix for enum '" _STR(Error) "' is not empty."); } prefix_length = 1; // 'ERR_' @@ -3024,7 +3016,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) glue_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to {GODOT_ROOT}/modules/mono/glue)"); + ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to '{GODOT_ROOT}/modules/mono/glue')."); } --options_left; @@ -3035,7 +3027,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) cs_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(generate_cs_glue_option + ": No output directory specified"); + ERR_PRINTS(generate_cs_glue_option + ": No output directory specified."); } --options_left; @@ -3046,7 +3038,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) cpp_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified"); + ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified."); } --options_left; @@ -3061,20 +3053,20 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) if (glue_dir_path.length()) { if (bindings_generator.generate_glue(glue_dir_path) != OK) - ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue"); + ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue."); if (bindings_generator.generate_cs_api(glue_dir_path.plus_file("Managed/Generated")) != OK) - ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API"); + ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API."); } if (cs_dir_path.length()) { if (bindings_generator.generate_cs_api(cs_dir_path) != OK) - ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API"); + ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API."); } if (cpp_dir_path.length()) { if (bindings_generator.generate_glue(cpp_dir_path) != OK) - ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue"); + ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue."); } // Exit once done diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 8be51a6c55..6f0c297575 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -147,7 +147,7 @@ class BindingsGenerator { bool requires_object_call; /** - * Determines if the method visibility is `internal` (visible only to files in the same assembly). + * Determines if the method visibility is 'internal' (visible only to files in the same assembly). * Currently, we only use this for methods that are not meant to be exposed, * but are required by properties as getters or setters. * Methods that are not meant to be exposed are those that begin with underscore and are not virtual. diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index d88b08c646..0e6c58c9d7 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -81,16 +81,14 @@ bool generate_api_solution(const String &p_solution_dir, const String &p_core_pr _GDMONO_SCOPE_DOMAIN_(temp_domain); - GDMonoAssembly *tools_project_editor_assembly = NULL; + GDMonoAssembly *tools_project_editor_asm = NULL; - if (!GDMono::get_singleton()->load_assembly("GodotTools.ProjectEditor", &tools_project_editor_assembly)) { - ERR_EXPLAIN("Failed to load assembly: 'GodotTools.ProjectEditor'"); - ERR_FAIL_V(false); - } + bool assembly_loaded = GDMono::get_singleton()->load_assembly(TOOLS_PROJECT_EDITOR_ASM_NAME, &tools_project_editor_asm); + ERR_FAIL_COND_V_MSG(!assembly_loaded, false, "Failed to load assembly: '" TOOLS_PROJECT_EDITOR_ASM_NAME "'."); return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items, p_editor_proj_dir, p_editor_compile_items, - tools_project_editor_assembly); + tools_project_editor_asm); } } diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 0014aaca70..5a84d9e3b8 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -271,7 +271,7 @@ MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) { MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id) { #ifdef OSX_ENABLED String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id); - return (MonoBoolean)osx_is_app_bundle_installed; + return (MonoBoolean)osx_is_app_bundle_installed(bundle_id); #else (void)p_bundle_id; // UNUSED return (MonoBoolean) false; @@ -350,6 +350,21 @@ MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { #endif } +void godot_icall_Internal_EditorRunPlay() { + EditorNode::get_singleton()->run_play(); +} + +void godot_icall_Internal_EditorRunStop() { + EditorNode::get_singleton()->run_stop(); +} + +void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() { + ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); + if (sed) { + sed->reload_scripts(); + } +} + MonoString *godot_icall_Utils_OS_GetPlatformName() { String os_name = OS::get_singleton()->get_name(); return GDMonoMarshal::mono_string_from_godot(os_name); @@ -415,6 +430,9 @@ void register_editor_internal_calls() { mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen); mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing); mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); // Globals mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale); diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 020bb70a08..80a7335b1d 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -85,18 +85,12 @@ Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, co } } - if (!ref_assembly) { - ERR_EXPLAIN("Cannot load assembly (refonly): " + ref_name); - ERR_FAIL_V(ERR_CANT_RESOLVE); - } + ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); r_dependencies[ref_name] = ref_assembly->get_path(); Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); - if (err != OK) { - ERR_EXPLAIN("Cannot load one of the dependencies for the assembly: " + ref_name); - ERR_FAIL_V(err); - } + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'."); } return OK; @@ -113,8 +107,7 @@ Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_proje bool load_success = GDMono::get_singleton()->load_assembly_from(p_project_dll_name, p_project_dll_src_path, &scripts_assembly, /* refonly: */ true); - ERR_EXPLAIN("Cannot load assembly (refonly): " + p_project_dll_name); - ERR_FAIL_COND_V(!load_success, ERR_CANT_RESOLVE); + ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + p_project_dll_name + "'."); Vector<String> search_dirs; GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir); diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp index dfb652a7aa..dcb0ca5a80 100644 --- a/modules/mono/editor/script_class_parser.cpp +++ b/modules/mono/editor/script_class_parser.cpp @@ -162,8 +162,8 @@ ScriptClassParser::Token ScriptClassParser::get_token() { error = true; return TK_ERROR; } else if (code[idx] == begin_str) { - if (verbatim && code[idx + 1] == '"') { // `""` is verbatim string's `\"` - idx += 2; // skip next `"` as well + if (verbatim && code[idx + 1] == '"') { // '""' is verbatim string's '\"' + idx += 2; // skip next '"' as well continue; } @@ -590,7 +590,7 @@ Error ScriptClassParser::parse(const String &p_code) { name = String(value); } else if (tk == TK_CURLY_BRACKET_OPEN) { if (name.empty()) { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + " after keyword `struct`, found " + get_token_name(TK_CURLY_BRACKET_OPEN); + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + " after keyword 'struct', found " + get_token_name(TK_CURLY_BRACKET_OPEN); error = true; return ERR_PARSE_ERROR; } @@ -657,12 +657,12 @@ Error ScriptClassParser::parse_file(const String &p_filepath) { String source; Error ferr = read_all_file_utf8(p_filepath, source); - if (ferr != OK) { - if (ferr == ERR_INVALID_DATA) { - ERR_EXPLAIN("File '" + p_filepath + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); - } - ERR_FAIL_V(ferr); - } + + ERR_FAIL_COND_V_MSG(ferr != OK, ferr, + ferr == ERR_INVALID_DATA ? + "File '" + p_filepath + "' contains invalid unicode (UTF-8), so it was not loaded." + " Please ensure that scripts are saved in valid UTF-8 unicode." : + "Failed to read file: '" + p_filepath + "'."); return parse(source); } diff --git a/modules/mono/glue/Managed/Files/AABB.cs b/modules/mono/glue/Managed/Files/AABB.cs index a2ebbc0736..98a73382f4 100644 --- a/modules/mono/glue/Managed/Files/AABB.cs +++ b/modules/mono/glue/Managed/Files/AABB.cs @@ -5,6 +5,7 @@ // file: core/variant_call.cpp // commit: 5ad9be4c24e9d7dc5672fdc42cea896622fe5685 using System; +using System.Runtime.InteropServices; #if REAL_T_IS_DOUBLE using real_t = System.Double; #else @@ -13,6 +14,8 @@ using real_t = System.Single; namespace Godot { + [Serializable] + [StructLayout(LayoutKind.Sequential)] public struct AABB : IEquatable<AABB> { private Vector3 _position; diff --git a/modules/mono/glue/Managed/Files/Basis.cs b/modules/mono/glue/Managed/Files/Basis.cs index 9cc31a0557..0eb76e9c63 100644 --- a/modules/mono/glue/Managed/Files/Basis.cs +++ b/modules/mono/glue/Managed/Files/Basis.cs @@ -8,43 +8,10 @@ using real_t = System.Single; namespace Godot { + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Basis : IEquatable<Basis> { - private static readonly Basis identity = new Basis - ( - 1f, 0f, 0f, - 0f, 1f, 0f, - 0f, 0f, 1f - ); - - private static readonly Basis[] orthoBases = { - new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f), - new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f), - new Basis(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f), - new Basis(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f), - new Basis(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f), - new Basis(0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 0f), - new Basis(-1f, 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f), - new Basis(0f, 0f, -1f, -1f, 0f, 0f, 0f, 1f, 0f), - new Basis(1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f), - new Basis(0f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, -1f), - new Basis(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, -1f), - new Basis(0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, -1f), - new Basis(1f, 0f, 0f, 0f, 0f, 1f, 0f, -1f, 0f), - new Basis(0f, 0f, -1f, 1f, 0f, 0f, 0f, -1f, 0f), - new Basis(-1f, 0f, 0f, 0f, 0f, -1f, 0f, -1f, 0f), - new Basis(0f, 0f, 1f, -1f, 0f, 0f, 0f, -1f, 0f), - new Basis(0f, 0f, 1f, 0f, 1f, 0f, -1f, 0f, 0f), - new Basis(0f, -1f, 0f, 0f, 0f, 1f, -1f, 0f, 0f), - new Basis(0f, 0f, -1f, 0f, -1f, 0f, -1f, 0f, 0f), - new Basis(0f, 1f, 0f, 0f, 0f, -1f, -1f, 0f, 0f), - new Basis(0f, 0f, 1f, 0f, -1f, 0f, 1f, 0f, 0f), - new Basis(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 0f), - new Basis(0f, 0f, -1f, 0f, 1f, 0f, 1f, 0f, 0f), - new Basis(0f, -1f, 0f, 0f, 0f, -1f, 1f, 0f, 0f) - }; - // NOTE: x, y and z are public-only. Use Column0, Column1 and Column2 internally. /// <summary> @@ -63,7 +30,6 @@ namespace Godot /// </summary> public Vector3 y { - get => Column1; set => Column1 = value; } @@ -74,7 +40,6 @@ namespace Godot /// </summary> public Vector3 z { - get => Column2; set => Column2 = value; } @@ -114,8 +79,6 @@ namespace Godot } } - public static Basis Identity => identity; - public Vector3 Scale { get @@ -225,7 +188,7 @@ namespace Godot return orthonormalizedBasis.Quat(); } - internal void SetQuantScale(Quat quat, Vector3 scale) + internal void SetQuatScale(Quat quat, Vector3 scale) { SetDiagonal(scale); Rotate(quat); @@ -241,7 +204,6 @@ namespace Godot Row0 = new Vector3(diagonal.x, 0, 0); Row1 = new Vector3(0, diagonal.y, 0); Row2 = new Vector3(0, 0, diagonal.z); - } public real_t Determinant() @@ -361,7 +323,7 @@ namespace Godot for (int i = 0; i < 24; i++) { - if (orthoBases[i] == orth) + if (orth == _orthoBases[i]) return i; } @@ -531,6 +493,43 @@ namespace Godot } } + private static readonly Basis[] _orthoBases = { + new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f), + new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f), + new Basis(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f), + new Basis(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f), + new Basis(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f), + new Basis(0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 0f), + new Basis(-1f, 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f), + new Basis(0f, 0f, -1f, -1f, 0f, 0f, 0f, 1f, 0f), + new Basis(1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f), + new Basis(0f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, -1f), + new Basis(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, -1f), + new Basis(0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, -1f), + new Basis(1f, 0f, 0f, 0f, 0f, 1f, 0f, -1f, 0f), + new Basis(0f, 0f, -1f, 1f, 0f, 0f, 0f, -1f, 0f), + new Basis(-1f, 0f, 0f, 0f, 0f, -1f, 0f, -1f, 0f), + new Basis(0f, 0f, 1f, -1f, 0f, 0f, 0f, -1f, 0f), + new Basis(0f, 0f, 1f, 0f, 1f, 0f, -1f, 0f, 0f), + new Basis(0f, -1f, 0f, 0f, 0f, 1f, -1f, 0f, 0f), + new Basis(0f, 0f, -1f, 0f, -1f, 0f, -1f, 0f, 0f), + new Basis(0f, 1f, 0f, 0f, 0f, -1f, -1f, 0f, 0f), + new Basis(0f, 0f, 1f, 0f, -1f, 0f, 1f, 0f, 0f), + new Basis(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 0f), + new Basis(0f, 0f, -1f, 0f, 1f, 0f, 1f, 0f, 0f), + new Basis(0f, -1f, 0f, 0f, 0f, -1f, 1f, 0f, 0f) + }; + + private static readonly Basis _identity = new Basis(1, 0, 0, 0, 1, 0, 0, 0, 1); + private static readonly Basis _flipX = new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1); + private static readonly Basis _flipY = new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1); + private static readonly Basis _flipZ = new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1); + + public static Basis Identity { get { return _identity; } } + public static Basis FlipX { get { return _flipX; } } + public static Basis FlipY { get { return _flipY; } } + public static Basis FlipZ { get { return _flipZ; } } + public Basis(Quat quat) { real_t s = 2.0f / quat.LengthSquared; diff --git a/modules/mono/glue/Managed/Files/Color.cs b/modules/mono/glue/Managed/Files/Color.cs index 84ff19fc54..3a52a1a13b 100644 --- a/modules/mono/glue/Managed/Files/Color.cs +++ b/modules/mono/glue/Managed/Files/Color.cs @@ -1,7 +1,10 @@ using System; +using System.Runtime.InteropServices; namespace Godot { + [Serializable] + [StructLayout(LayoutKind.Sequential)] public struct Color : IEquatable<Color> { public float r; @@ -13,7 +16,11 @@ namespace Godot { get { - return (int)(r * 255.0f); + return (int)Math.Round(r * 255.0f); + } + set + { + r = value / 255.0f; } } @@ -21,7 +28,11 @@ namespace Godot { get { - return (int)(g * 255.0f); + return (int)Math.Round(g * 255.0f); + } + set + { + g = value / 255.0f; } } @@ -29,7 +40,11 @@ namespace Godot { get { - return (int)(b * 255.0f); + return (int)Math.Round(b * 255.0f); + } + set + { + b = value / 255.0f; } } @@ -37,7 +52,11 @@ namespace Godot { get { - return (int)(a * 255.0f); + return (int)Math.Round(a * 255.0f); + } + set + { + a = value / 255.0f; } } @@ -71,7 +90,7 @@ namespace Godot } set { - this = FromHsv(value, s, v); + this = FromHsv(value, s, v, a); } } @@ -88,7 +107,7 @@ namespace Godot } set { - this = FromHsv(h, value, v); + this = FromHsv(h, value, v, a); } } @@ -100,7 +119,7 @@ namespace Godot } set { - this = FromHsv(h, s, value); + this = FromHsv(h, s, value, a); } } @@ -163,12 +182,12 @@ namespace Godot } } - public static void ToHsv(Color color, out float hue, out float saturation, out float value) + public void ToHsv(out float hue, out float saturation, out float value) { - int max = Mathf.Max(color.r8, Mathf.Max(color.g8, color.b8)); - int min = Mathf.Min(color.r8, Mathf.Min(color.g8, color.b8)); + float max = (float)Mathf.Max(r, Mathf.Max(g, b)); + float min = (float)Mathf.Min(r, Mathf.Min(g, b)); - int delta = max - min; + float delta = max - min; if (delta == 0) { @@ -176,12 +195,12 @@ namespace Godot } else { - if (color.r == max) - hue = (color.g - color.b) / delta; // Between yellow & magenta - else if (color.g == max) - hue = 2 + (color.b - color.r) / delta; // Between cyan & yellow + if (r == max) + hue = (g - b) / delta; // Between yellow & magenta + else if (g == max) + hue = 2 + (b - r) / delta; // Between cyan & yellow else - hue = 4 + (color.r - color.g) / delta; // Between magenta & cyan + hue = 4 + (r - g) / delta; // Between magenta & cyan hue /= 6.0f; @@ -190,7 +209,7 @@ namespace Godot } saturation = max == 0 ? 0 : 1f - 1f * min / max; - value = max / 255f; + value = max; } public static Color FromHsv(float hue, float saturation, float value, float alpha = 1.0f) @@ -254,7 +273,8 @@ namespace Godot return new Color( (r + 0.5f) % 1.0f, (g + 0.5f) % 1.0f, - (b + 0.5f) % 1.0f + (b + 0.5f) % 1.0f, + a ); } @@ -272,7 +292,8 @@ namespace Godot return new Color( 1.0f - r, 1.0f - g, - 1.0f - b + 1.0f - b, + a ); } @@ -375,7 +396,7 @@ namespace Godot return c; } - public string ToHtml(bool include_alpha = true) + public string ToHtml(bool includeAlpha = true) { var txt = string.Empty; @@ -383,7 +404,7 @@ namespace Godot txt += ToHex32(g); txt += ToHex32(b); - if (include_alpha) + if (includeAlpha) txt = ToHex32(a) + txt; return txt; @@ -465,13 +486,13 @@ namespace Godot for (int i = 0; i < 2; i++) { - char[] c = { (char)0, (char)0 }; + char c; int lv = v & 0xF; if (lv < 10) - c[0] = (char)('0' + lv); + c = (char)('0' + lv); else - c[0] = (char)('a' + lv - 10); + c = (char)('a' + lv - 10); v >>= 4; ret = c + ret; @@ -490,12 +511,17 @@ namespace Godot bool alpha; - if (color.Length == 8) - alpha = true; - else if (color.Length == 6) - alpha = false; - else - return false; + switch (color.Length) + { + case 8: + alpha = true; + break; + case 6: + alpha = false; + break; + default: + return false; + } if (alpha) { diff --git a/modules/mono/glue/Managed/Files/Colors.cs b/modules/mono/glue/Managed/Files/Colors.cs index bc2a1a3bd7..f41f5e9fc8 100644 --- a/modules/mono/glue/Managed/Files/Colors.cs +++ b/modules/mono/glue/Managed/Files/Colors.cs @@ -141,6 +141,7 @@ namespace Godot {"teal", new Color(0.00f, 0.50f, 0.50f)}, {"thistle", new Color(0.85f, 0.75f, 0.85f)}, {"tomato", new Color(1.00f, 0.39f, 0.28f)}, + {"transparent", new Color(1.00f, 1.00f, 1.00f, 0.00f)}, {"turquoise", new Color(0.25f, 0.88f, 0.82f)}, {"violet", new Color(0.93f, 0.51f, 0.93f)}, {"webgreen", new Color(0.00f, 0.50f, 0.00f)}, @@ -187,7 +188,7 @@ namespace Godot public static Color DarkOrchid { get { return namedColors["darkorchid"]; } } public static Color DarkRed { get { return namedColors["darkred"]; } } public static Color DarkSalmon { get { return namedColors["darksalmon"]; } } - public static Color DarkSeagreen { get { return namedColors["darkseagreen"]; } } + public static Color DarkSeaGreen { get { return namedColors["darkseagreen"]; } } public static Color DarkSlateBlue { get { return namedColors["darkslateblue"]; } } public static Color DarkSlateGray { get { return namedColors["darkslategray"]; } } public static Color DarkTurquoise { get { return namedColors["darkturquoise"]; } } @@ -288,6 +289,7 @@ namespace Godot public static Color Teal { get { return namedColors["teal"]; } } public static Color Thistle { get { return namedColors["thistle"]; } } public static Color Tomato { get { return namedColors["tomato"]; } } + public static Color Transparent { get { return namedColors["transparent"]; } } public static Color Turquoise { get { return namedColors["turquoise"]; } } public static Color Violet { get { return namedColors["violet"]; } } public static Color WebGreen { get { return namedColors["webgreen"]; } } diff --git a/modules/mono/glue/Managed/Files/Dispatcher.cs b/modules/mono/glue/Managed/Files/Dispatcher.cs new file mode 100644 index 0000000000..072e0f20ff --- /dev/null +++ b/modules/mono/glue/Managed/Files/Dispatcher.cs @@ -0,0 +1,13 @@ +using System.Runtime.CompilerServices; + +namespace Godot +{ + public static class Dispatcher + { + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern GodotTaskScheduler godot_icall_DefaultGodotTaskScheduler(); + + public static GodotSynchronizationContext SynchronizationContext => + godot_icall_DefaultGodotTaskScheduler().Context; + } +} diff --git a/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs b/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs index e727781d63..4b5e3f8761 100644 --- a/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs +++ b/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs @@ -6,17 +6,16 @@ namespace Godot { public class GodotSynchronizationContext : SynchronizationContext { - private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); + private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); public override void Post(SendOrPostCallback d, object state) { - queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); + _queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); } public void ExecutePendingContinuations() { - KeyValuePair<SendOrPostCallback, object> workItem; - while (queue.TryTake(out workItem)) + while (_queue.TryTake(out var workItem)) { workItem.Key(workItem.Value); } diff --git a/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs b/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs index 9a40fef5a9..8eaeea50dc 100644 --- a/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs +++ b/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs @@ -8,7 +8,7 @@ namespace Godot { public class GodotTaskScheduler : TaskScheduler { - private GodotSynchronizationContext Context { get; set; } + internal GodotSynchronizationContext Context { get; } private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); public GodotTaskScheduler() @@ -28,14 +28,10 @@ namespace Godot protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { if (SynchronizationContext.Current != Context) - { return false; - } if (taskWasPreviouslyQueued) - { TryDequeue(task); - } return TryExecuteTask(task); } @@ -52,7 +48,8 @@ namespace Godot { lock (_tasks) { - return _tasks.ToArray(); + foreach (Task task in _tasks) + yield return task; } } diff --git a/modules/mono/glue/Managed/Files/Mathf.cs b/modules/mono/glue/Managed/Files/Mathf.cs index 6c1a51fcf9..ce34cd6a99 100644 --- a/modules/mono/glue/Managed/Files/Mathf.cs +++ b/modules/mono/glue/Managed/Files/Mathf.cs @@ -158,6 +158,11 @@ namespace Godot public static bool IsEqualApprox(real_t a, real_t b) { + // Check for exact equality first, required to handle "infinity" values. + if (a == b) { + return true; + } + // Then check for approximate equality. real_t tolerance = Epsilon * Abs(a); if (tolerance < Epsilon) { tolerance = Epsilon; @@ -336,14 +341,14 @@ namespace Godot public static int Wrap(int value, int min, int max) { - int rng = max - min; - return rng != 0 ? min + ((value - min) % rng + rng) % rng : min; + int range = max - min; + return range == 0 ? min : min + ((value - min) % range + range) % range; } public static real_t Wrap(real_t value, real_t min, real_t max) { - real_t rng = max - min; - return !IsEqualApprox(rng, default(real_t)) ? min + ((value - min) % rng + rng) % rng : min; + real_t range = max - min; + return IsZeroApprox(range) ? min : min + ((value - min) % range + range) % range; } } } diff --git a/modules/mono/glue/Managed/Files/MathfEx.cs b/modules/mono/glue/Managed/Files/MathfEx.cs index b96f01bc2e..6cffc7f01d 100644 --- a/modules/mono/glue/Managed/Files/MathfEx.cs +++ b/modules/mono/glue/Managed/Files/MathfEx.cs @@ -48,7 +48,12 @@ namespace Godot public static bool IsEqualApprox(real_t a, real_t b, real_t tolerance) { + // Check for exact equality first, required to handle "infinity" values. + if (a == b) { + return true; + } + // Then check for approximate equality. return Abs(a - b) < tolerance; } } -}
\ No newline at end of file +} diff --git a/modules/mono/glue/Managed/Files/NodePath.cs b/modules/mono/glue/Managed/Files/NodePath.cs index 94a4ed1de9..4de4e1e6a9 100644 --- a/modules/mono/glue/Managed/Files/NodePath.cs +++ b/modules/mono/glue/Managed/Files/NodePath.cs @@ -55,7 +55,7 @@ namespace Godot get { return ptr; } } - public NodePath() : this(string.Empty) { } + public NodePath() : this(string.Empty) {} public NodePath(string path) { diff --git a/modules/mono/glue/Managed/Files/Plane.cs b/modules/mono/glue/Managed/Files/Plane.cs index e16d4315be..a13161d2e6 100644 --- a/modules/mono/glue/Managed/Files/Plane.cs +++ b/modules/mono/glue/Managed/Files/Plane.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; #if REAL_T_IS_DOUBLE using real_t = System.Double; #else @@ -7,6 +8,8 @@ using real_t = System.Single; namespace Godot { + [Serializable] + [StructLayout(LayoutKind.Sequential)] public struct Plane : IEquatable<Plane> { private Vector3 _normal; diff --git a/modules/mono/glue/Managed/Files/Quat.cs b/modules/mono/glue/Managed/Files/Quat.cs index 0d4349084a..845c7c730e 100644 --- a/modules/mono/glue/Managed/Files/Quat.cs +++ b/modules/mono/glue/Managed/Files/Quat.cs @@ -8,6 +8,7 @@ using real_t = System.Single; namespace Godot { + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Quat : IEquatable<Quat> { @@ -95,6 +96,7 @@ namespace Godot return this / Length; } + [Obsolete("Set is deprecated. Use the Quat(" + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)] public void Set(real_t x, real_t y, real_t z, real_t w) { this.x = x; @@ -103,16 +105,19 @@ namespace Godot this.w = w; } + [Obsolete("Set is deprecated. Use the Quat(" + nameof(Quat) + ") constructor instead.", error: true)] public void Set(Quat q) { this = q; } + [Obsolete("SetAxisAngle is deprecated. Use the Quat(" + nameof(Vector3) + ", " + nameof(real_t) + ") constructor instead.", error: true)] public void SetAxisAngle(Vector3 axis, real_t angle) { this = new Quat(axis, angle); } + [Obsolete("SetEuler is deprecated. Use the Quat(" + nameof(Vector3) + ") constructor instead.", error: true)] public void SetEuler(Vector3 eulerYXZ) { this = new Quat(eulerYXZ); @@ -229,9 +234,9 @@ namespace Godot public Quat(Vector3 eulerYXZ) { - real_t half_a1 = eulerYXZ.y * (real_t)0.5; - real_t half_a2 = eulerYXZ.x * (real_t)0.5; - real_t half_a3 = eulerYXZ.z * (real_t)0.5; + real_t half_a1 = eulerYXZ.y * 0.5f; + real_t half_a2 = eulerYXZ.x * 0.5f; + real_t half_a3 = eulerYXZ.z * 0.5f; // R = Y(a1).X(a2).Z(a3) convention for Euler angles. // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6) @@ -246,7 +251,7 @@ namespace Godot x = sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3; y = sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3; - z = -sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3; + z = cos_a1 * cos_a2 * sin_a3 - sin_a1 * sin_a2 * cos_a3; w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3; } diff --git a/modules/mono/glue/Managed/Files/Rect2.cs b/modules/mono/glue/Managed/Files/Rect2.cs index 888f300347..99542d0c0a 100644 --- a/modules/mono/glue/Managed/Files/Rect2.cs +++ b/modules/mono/glue/Managed/Files/Rect2.cs @@ -8,6 +8,7 @@ using real_t = System.Single; namespace Godot { + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Rect2 : IEquatable<Rect2> { @@ -156,13 +157,13 @@ namespace Godot public bool Intersects(Rect2 b) { - if (_position.x > b._position.x + b._size.x) + if (_position.x >= b._position.x + b._size.x) return false; - if (_position.x + _size.x < b._position.x) + if (_position.x + _size.x <= b._position.x) return false; - if (_position.y > b._position.y + b._size.y) + if (_position.y >= b._position.y + b._size.y) return false; - if (_position.y + _size.y < b._position.y) + if (_position.y + _size.y <= b._position.y) return false; return true; diff --git a/modules/mono/glue/Managed/Files/Transform.cs b/modules/mono/glue/Managed/Files/Transform.cs index bd79144873..cc4d26158d 100644 --- a/modules/mono/glue/Managed/Files/Transform.cs +++ b/modules/mono/glue/Managed/Files/Transform.cs @@ -8,6 +8,7 @@ using real_t = System.Single; namespace Godot { + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Transform : IEquatable<Transform> { @@ -33,7 +34,7 @@ namespace Godot Vector3 destinationLocation = transform.origin; var interpolated = new Transform(); - interpolated.basis.SetQuantScale(sourceRotation.Slerp(destinationRotation, c).Normalized(), sourceScale.LinearInterpolate(destinationScale, c)); + interpolated.basis.SetQuatScale(sourceRotation.Slerp(destinationRotation, c).Normalized(), sourceScale.LinearInterpolate(destinationScale, c)); interpolated.origin = sourceLocation.LinearInterpolate(destinationLocation, c); return interpolated; diff --git a/modules/mono/glue/Managed/Files/Transform2D.cs b/modules/mono/glue/Managed/Files/Transform2D.cs index 33ff286769..814332dc07 100644 --- a/modules/mono/glue/Managed/Files/Transform2D.cs +++ b/modules/mono/glue/Managed/Files/Transform2D.cs @@ -8,6 +8,7 @@ using real_t = System.Single; namespace Godot { + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Transform2D : IEquatable<Transform2D> { @@ -98,6 +99,8 @@ namespace Godot return x[columnIndex]; case 1: return y[columnIndex]; + case 2: + return origin[columnIndex]; default: throw new IndexOutOfRangeException(); } @@ -112,6 +115,9 @@ namespace Godot case 1: y[columnIndex] = value; return; + case 2: + origin[columnIndex] = value; + return; default: throw new IndexOutOfRangeException(); } @@ -136,7 +142,7 @@ namespace Godot inv[0] *= new Vector2(detInv, -detInv); inv[1] *= new Vector2(-detInv, detInv); - inv[2] = BasisXform(-inv[2]); + inv[2] = inv.BasisXform(-inv[2]); return inv; } diff --git a/modules/mono/glue/Managed/Files/Vector2.cs b/modules/mono/glue/Managed/Files/Vector2.cs index a7f26283a7..0daa94057e 100644 --- a/modules/mono/glue/Managed/Files/Vector2.cs +++ b/modules/mono/glue/Managed/Files/Vector2.cs @@ -14,9 +14,19 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// 2-element structure that can be used to represent positions in 2D space or any other pair of numeric values. + /// </summary> + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Vector2 : IEquatable<Vector2> { + public enum Axis + { + X = 0, + Y + } + public real_t x; public real_t y; @@ -201,6 +211,22 @@ namespace Godot return v; } + public Vector2 PosMod(real_t mod) + { + Vector2 v; + v.x = Mathf.PosMod(x, mod); + v.y = Mathf.PosMod(y, mod); + return v; + } + + public Vector2 PosMod(Vector2 modv) + { + Vector2 v; + v.x = Mathf.PosMod(x, modv.x); + v.y = Mathf.PosMod(y, modv.y); + return v; + } + public Vector2 Project(Vector2 onNormal) { return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); @@ -222,17 +248,27 @@ namespace Godot return new Vector2(Mathf.Round(x), Mathf.Round(y)); } + [Obsolete("Set is deprecated. Use the Vector2(" + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)] public void Set(real_t x, real_t y) { this.x = x; this.y = y; } + [Obsolete("Set is deprecated. Use the Vector2(" + nameof(Vector2) + ") constructor instead.", error: true)] public void Set(Vector2 v) { x = v.x; y = v.y; } + public Vector2 Sign() + { + Vector2 v; + v.x = Mathf.Sign(x); + v.y = Mathf.Sign(y); + return v; + } + public Vector2 Slerp(Vector2 b, real_t t) { real_t theta = AngleTo(b); @@ -262,7 +298,7 @@ namespace Godot private static readonly Vector2 _up = new Vector2(0, -1); private static readonly Vector2 _down = new Vector2(0, 1); - private static readonly Vector2 _right = new Vector2(1, 0); + private static readonly Vector2 _right = new Vector2(1, 0); private static readonly Vector2 _left = new Vector2(-1, 0); public static Vector2 Zero { get { return _zero; } } @@ -343,6 +379,20 @@ namespace Godot return left; } + public static Vector2 operator %(Vector2 vec, real_t divisor) + { + vec.x %= divisor; + vec.y %= divisor; + return vec; + } + + public static Vector2 operator %(Vector2 vec, Vector2 divisorv) + { + vec.x %= divisorv.x; + vec.y %= divisorv.y; + return vec; + } + public static bool operator ==(Vector2 left, Vector2 right) { return left.Equals(right); diff --git a/modules/mono/glue/Managed/Files/Vector3.cs b/modules/mono/glue/Managed/Files/Vector3.cs index 16803ae55c..9076dbd3b0 100644 --- a/modules/mono/glue/Managed/Files/Vector3.cs +++ b/modules/mono/glue/Managed/Files/Vector3.cs @@ -14,6 +14,10 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// 3-element structure that can be used to represent positions in 3D space or any other pair of numeric values. + /// </summary> + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Vector3 : IEquatable<Vector3> { @@ -224,6 +228,24 @@ namespace Godot ); } + public Vector3 PosMod(real_t mod) + { + Vector3 v; + v.x = Mathf.PosMod(x, mod); + v.y = Mathf.PosMod(y, mod); + v.z = Mathf.PosMod(z, mod); + return v; + } + + public Vector3 PosMod(Vector3 modv) + { + Vector3 v; + v.x = Mathf.PosMod(x, modv.x); + v.y = Mathf.PosMod(y, modv.y); + v.z = Mathf.PosMod(z, modv.z); + return v; + } + public Vector3 Project(Vector3 onNormal) { return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); @@ -248,12 +270,14 @@ namespace Godot return new Basis(axis, phi).Xform(this); } + [Obsolete("Set is deprecated. Use the Vector3(" + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)] public void Set(real_t x, real_t y, real_t z) { this.x = x; this.y = y; this.z = z; } + [Obsolete("Set is deprecated. Use the Vector3(" + nameof(Vector3) + ") constructor instead.", error: true)] public void Set(Vector3 v) { x = v.x; @@ -261,6 +285,15 @@ namespace Godot z = v.z; } + public Vector3 Sign() + { + Vector3 v; + v.x = Mathf.Sign(x); + v.y = Mathf.Sign(y); + v.z = Mathf.Sign(z); + return v; + } + public Vector3 Slerp(Vector3 b, real_t t) { real_t theta = AngleTo(b); @@ -394,6 +427,22 @@ namespace Godot return left; } + public static Vector3 operator %(Vector3 vec, real_t divisor) + { + vec.x %= divisor; + vec.y %= divisor; + vec.z %= divisor; + return vec; + } + + public static Vector3 operator %(Vector3 vec, Vector3 divisorv) + { + vec.x %= divisorv.x; + vec.y %= divisorv.y; + vec.z %= divisorv.z; + return vec; + } + public static bool operator ==(Vector3 left, Vector3 right) { return left.Equals(right); diff --git a/modules/mono/glue/Managed/Managed.csproj b/modules/mono/glue/Managed/Managed.csproj index 61f738922b..c8eca71199 100644 --- a/modules/mono/glue/Managed/Managed.csproj +++ b/modules/mono/glue/Managed/Managed.csproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -8,6 +8,7 @@ <RootNamespace>Managed</RootNamespace> <AssemblyName>Managed</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> <DebugSymbols>true</DebugSymbols> @@ -37,4 +38,4 @@ <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> -</Project> +</Project>
\ No newline at end of file diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp index 7c30092855..8b9a1380d8 100644 --- a/modules/mono/glue/gd_glue.cpp +++ b/modules/mono/glue/gd_glue.cpp @@ -167,7 +167,7 @@ MonoObject *godot_icall_GD_str2var(MonoString *p_str) { int line; Error err = VariantParser::parse(&ss, ret, errs, line); if (err != OK) { - String err_str = "Parse error at line " + itos(line) + ": " + errs; + String err_str = "Parse error at line " + itos(line) + ": " + errs + "."; ERR_PRINTS(err_str); ret = err_str; } @@ -193,8 +193,7 @@ MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_object PoolByteArray barr; int len; Error err = encode_variant(var, NULL, len, p_full_objects); - ERR_EXPLAIN("Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); - ERR_FAIL_COND_V(err != OK, NULL); + ERR_FAIL_COND_V_MSG(err != OK, NULL, "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); barr.resize(len); { @@ -211,6 +210,10 @@ MonoString *godot_icall_GD_var2str(MonoObject *p_var) { return GDMonoMarshal::mono_string_from_godot(vars); } +MonoObject *godot_icall_DefaultGodotTaskScheduler() { + return GDMonoUtils::mono_cache.task_scheduler_handle->get_target(); +} + void godot_register_gd_icalls() { mono_add_internal_call("Godot.GD::godot_icall_GD_bytes2var", (void *)godot_icall_GD_bytes2var); mono_add_internal_call("Godot.GD::godot_icall_GD_convert", (void *)godot_icall_GD_convert); @@ -234,6 +237,9 @@ void godot_register_gd_icalls() { mono_add_internal_call("Godot.GD::godot_icall_GD_type_exists", (void *)godot_icall_GD_type_exists); mono_add_internal_call("Godot.GD::godot_icall_GD_var2bytes", (void *)godot_icall_GD_var2bytes); mono_add_internal_call("Godot.GD::godot_icall_GD_var2str", (void *)godot_icall_GD_var2str); + + // Dispatcher + mono_add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", (void *)godot_icall_DefaultGodotTaskScheduler); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/gd_glue.h b/modules/mono/glue/gd_glue.h index d4e20e2887..a34c0bc50f 100644 --- a/modules/mono/glue/gd_glue.h +++ b/modules/mono/glue/gd_glue.h @@ -75,6 +75,8 @@ MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_object MonoString *godot_icall_GD_var2str(MonoObject *p_var); +MonoObject *godot_icall_DefaultGodotTaskScheduler(); + // Register internal calls void godot_register_gd_icalls(); diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h index 4ad4088514..4c17a6ec9d 100644 --- a/modules/mono/godotsharp_defs.h +++ b/modules/mono/godotsharp_defs.h @@ -39,8 +39,8 @@ #define API_SOLUTION_NAME "GodotSharp" #define CORE_API_ASSEMBLY_NAME "GodotSharp" #define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor" -#define TOOLS_ASSEMBLY_NAME "GodotTools" -#define TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME "GodotTools.ProjectEditor" +#define TOOLS_ASM_NAME "GodotTools" +#define TOOLS_PROJECT_EDITOR_ASM_NAME "GodotTools.ProjectEditor" #define BINDINGS_CLASS_NATIVECALLS "NativeCalls" #define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls" diff --git a/modules/mono/icons/icon_c_#.svg b/modules/mono/icons/icon_c_#.svg new file mode 100644 index 0000000000..69664ca553 --- /dev/null +++ b/modules/mono/icons/icon_c_#.svg @@ -0,0 +1,5 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<g transform="translate(0 -1036.4)"> +<path d="m6 1046.4c-1.6569 0-3 1.3431-3 3s1.3431 3 3 3h1v-2h-1c-0.55228 0-1-0.4478-1-1 0-0.5523 0.44772-1 1-1h1v-2zm1-9-0.56445 2.2578c-0.23643 0.076-0.46689 0.1692-0.68945 0.2793l-1.9883-1.1933-1.4141 1.414 1.1953 1.9942c-0.11191 0.2211-0.20723 0.4502-0.28516 0.6855l-2.2539 0.5625v2h5.2715c-0.17677-0.3037-0.27041-0.6486-0.27148-1 9.6e-6 -1.1046 0.89543-2 2-2s2 0.8954 2 2c-4.817e-4 0.3512-0.093442 0.6961-0.26953 1h5.2695v-2l-2.2578-0.5645c-0.07594-0.2357-0.1693-0.4655-0.2793-0.6875l1.1934-1.9902-1.4141-1.414-1.9941 1.1953c-0.22113-0.1119-0.45028-0.2073-0.68555-0.2852l-0.5625-2.2539zm4 9c-0.71466-1e-4 -1.3751 0.3811-1.7324 1-0.35727 0.6188-0.35727 1.3812 0 2 0.35733 0.6189 1.0178 1.0001 1.7324 1h-2v2h2c0.71466 1e-4 1.3751-0.3811 1.7324-1 0.35727-0.6188 0.35727-1.3812 0-2-0.35733-0.6189-1.0178-1.0001-1.7324-1h2v-2z" fill="#e0e0e0"/> +</g> +</svg> diff --git a/modules/mono/mono_gd/android_mono_config.h b/modules/mono/mono_gd/android_mono_config.h new file mode 100644 index 0000000000..c5cc244aec --- /dev/null +++ b/modules/mono/mono_gd/android_mono_config.h @@ -0,0 +1,43 @@ +/*************************************************************************/ +/* android_mono_config.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef ANDROID_MONO_CONFIG_H +#define ANDROID_MONO_CONFIG_H + +#ifdef ANDROID_ENABLED + +#include "core/ustring.h" + +// This function is defined in an auto-generated source file +String get_godot_android_mono_config(); + +#endif // ANDROID_ENABLED + +#endif // ANDROID_MONO_CONFIG_H diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 45f79074be..915a01af7e 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -44,7 +44,6 @@ #include "core/project_settings.h" #include "../csharp_script.h" -#include "../glue/cs_glue_version.gen.h" #include "../godotsharp_dirs.h" #include "../utils/path_utils.h" #include "gd_mono_class.h" @@ -56,7 +55,7 @@ #endif #ifdef ANDROID_ENABLED -#include "android_mono_config.gen.h" +#include "android_mono_config.h" #endif GDMono *GDMono::singleton = NULL; @@ -265,7 +264,7 @@ void GDMono::initialize() { #ifdef WINDOWS_ENABLED if (assembly_rootdir.empty() || config_dir.empty()) { - ERR_PRINT("Cannot find Mono in the registry"); + ERR_PRINT("Cannot find Mono in the registry."); // Assertion: if they are not set, then they weren't found in the registry CRASH_COND(mono_reg_info.assembly_dir.length() > 0 || mono_reg_info.config_dir.length() > 0); } @@ -317,10 +316,15 @@ void GDMono::initialize() { return; #endif - root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); +#if !defined(WINDOWS_ENABLED) && !defined(NO_MONO_THREADS_SUSPEND_WORKAROUND) + // FIXME: Temporary workaround. See: https://github.com/godotengine/godot/issues/29812 + if (!OS::get_singleton()->has_environment("MONO_THREADS_SUSPEND")) { + OS::get_singleton()->set_environment("MONO_THREADS_SUSPEND", "preemptive"); + } +#endif - ERR_EXPLAIN("Mono: Failed to initialize runtime"); - ERR_FAIL_NULL(root_domain); + root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); + ERR_FAIL_NULL_MSG(root_domain, "Mono: Failed to initialize runtime."); GDMonoUtils::set_main_thread(GDMonoUtils::get_current_thread()); @@ -331,11 +335,11 @@ void GDMono::initialize() { print_verbose("Mono: Runtime initialized"); // mscorlib assembly MUST be present at initialization - ERR_EXPLAIN("Mono: Failed to load mscorlib assembly"); - ERR_FAIL_COND(!_load_corlib_assembly()); + bool corlib_loaded = _load_corlib_assembly(); + ERR_FAIL_COND_MSG(!corlib_loaded, "Mono: Failed to load mscorlib assembly."); - ERR_EXPLAIN("Mono: Failed to load scripts domain"); - ERR_FAIL_COND(_load_scripts_domain() != OK); + Error domain_load_err = _load_scripts_domain(); + ERR_FAIL_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain."); #ifdef DEBUG_ENABLED bool debugger_attached = _wait_for_debugger_msecs(500); @@ -351,8 +355,7 @@ void GDMono::initialize() { void GDMono::initialize_load_assemblies() { #ifndef MONO_GLUE_ENABLED - ERR_EXPLAIN("Mono: This binary was built with `mono_glue=no`; cannot load assemblies"); - CRASH_NOW(); + CRASH_NOW_MSG("Mono: This binary was built with 'mono_glue=no'; cannot load assemblies."); #endif // Load assemblies. The API and tools assemblies are required, @@ -361,10 +364,8 @@ void GDMono::initialize_load_assemblies() { _load_api_assemblies(); #if defined(TOOLS_ENABLED) - if (!_load_tools_assemblies()) { - ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); - CRASH_NOW(); - } + bool tool_assemblies_loaded = _load_tools_assemblies(); + CRASH_COND_MSG(!tool_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies."); #endif // Load the project's main assembly. This doesn't necessarily need to succeed. @@ -393,23 +394,24 @@ uint64_t get_core_api_hash(); uint64_t get_editor_api_hash(); #endif uint32_t get_bindings_version(); +uint32_t get_cs_glue_version(); void register_generated_icalls(); #else uint64_t get_core_api_hash() { - CRASH_NOW(); GD_UNREACHABLE(); } #ifdef TOOLS_ENABLED uint64_t get_editor_api_hash() { - CRASH_NOW(); GD_UNREACHABLE(); } #endif uint32_t get_bindings_version() { - CRASH_NOW(); + GD_UNREACHABLE(); +} +uint32_t get_cs_glue_version() { GD_UNREACHABLE(); } @@ -428,12 +430,12 @@ void GDMono::_initialize_and_check_api_hashes() { #ifdef MONO_GLUE_ENABLED if (get_api_core_hash() != GodotSharpBindings::get_core_api_hash()) { - ERR_PRINT("Mono: Core API hash mismatch!"); + ERR_PRINT("Mono: Core API hash mismatch."); } #ifdef TOOLS_ENABLED if (get_api_editor_hash() != GodotSharpBindings::get_editor_api_hash()) { - ERR_PRINT("Mono: Editor API hash mismatch!"); + ERR_PRINT("Mono: Editor API hash mismatch."); } #endif // TOOLS_ENABLED #endif // MONO_GLUE_ENABLED @@ -561,14 +563,14 @@ bool GDMono::_load_corlib_assembly() { } #ifdef TOOLS_ENABLED -bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) { +bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config) { bool &api_assembly_out_of_sync = (p_api_type == APIAssembly::API_CORE) ? GDMono::get_singleton()->core_api_assembly_out_of_sync : GDMono::get_singleton()->editor_api_assembly_out_of_sync; - String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); - String dst_dir = GodotSharpDirs::get_res_assemblies_dir(); + String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); + String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config); String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; @@ -579,7 +581,7 @@ bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) { memdelete(da); if (err != OK) { - ERR_PRINTS("Failed to create destination directory for the API assemblies. Error: " + itos(err)); + ERR_PRINTS("Failed to create destination directory for the API assemblies. Error: " + itos(err) + "."); return false; } } @@ -593,16 +595,16 @@ bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) { String xml_file = assembly_name + ".xml"; if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK) - WARN_PRINTS("Failed to copy " + xml_file); + WARN_PRINTS("Failed to copy '" + xml_file + "'."); String pdb_file = assembly_name + ".pdb"; if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK) - WARN_PRINTS("Failed to copy " + pdb_file); + WARN_PRINTS("Failed to copy '" + pdb_file + "'."); Error err = da->copy(assembly_src, assembly_dst); if (err != OK) { - ERR_PRINTS("Failed to copy " + assembly_file); + ERR_PRINTS("Failed to copy '" + assembly_file + "'."); return false; } @@ -617,11 +619,11 @@ String GDMono::update_api_assemblies_from_prebuilt() { #define FAIL_REASON(m_out_of_sync, m_prebuilt_exists) \ ( \ (m_out_of_sync ? \ - String("The assembly is invalidated") : \ - String("The assembly was not found")) + \ + String("The assembly is invalidated ") : \ + String("The assembly was not found ")) + \ (m_prebuilt_exists ? \ - String(" and the prebuilt assemblies are missing") : \ - String(" and we failed to copy the prebuilt assemblies"))) + String("and the prebuilt assemblies are missing.") : \ + String("and we failed to copy the prebuilt assemblies."))) bool api_assembly_out_of_sync = core_api_assembly_out_of_sync || editor_api_assembly_out_of_sync; @@ -631,18 +633,28 @@ String GDMono::update_api_assemblies_from_prebuilt() { if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) return String(); // No update needed - print_verbose("Updating API assemblies"); + const int CONFIGS_LEN = 2; + String configs[CONFIGS_LEN] = { String("Debug"), String("Release") }; + + for (int i = 0; i < CONFIGS_LEN; i++) { + String config = configs[i]; - String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); - String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + print_verbose("Updating '" + config + "' API assemblies"); - if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) - return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false); + String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(config); + String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - // Copy the prebuilt Api - if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE) || !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR)) - return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true); + if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) { + return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false); + } + + // Copy the prebuilt Api + if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE, config) || + !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR, config)) { + return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true); + } + } return String(); // Updated successfully @@ -675,7 +687,7 @@ bool GDMono::_load_core_api_assembly() { APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(core_api_assembly, APIAssembly::API_CORE); core_api_assembly_out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash || GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || - CS_GLUE_VERSION != api_assembly_ver.cs_glue_version; + GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; if (!core_api_assembly_out_of_sync) { GDMonoUtils::update_godot_api_cache(); @@ -710,7 +722,7 @@ bool GDMono::_load_editor_api_assembly() { APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(editor_api_assembly, APIAssembly::API_EDITOR); editor_api_assembly_out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash || GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || - CS_GLUE_VERSION != api_assembly_ver.cs_glue_version; + GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; } else { editor_api_assembly_out_of_sync = false; } @@ -755,29 +767,19 @@ void GDMono::_load_api_assemblies() { // update them from the prebuilt assemblies directory before trying to load them. // Shouldn't happen. The project manager loads the prebuilt API assemblies - if (Main::is_project_manager()) { - ERR_EXPLAIN("Failed to load one of the prebuilt API assemblies"); - CRASH_NOW(); - } + CRASH_COND_MSG(Main::is_project_manager(), "Failed to load one of the prebuilt API assemblies."); // 1. Unload the scripts domain - if (_unload_scripts_domain() != OK) { - ERR_EXPLAIN("Mono: Failed to unload scripts domain"); - CRASH_NOW(); - } + Error domain_unload_err = _unload_scripts_domain(); + CRASH_COND_MSG(domain_unload_err != OK, "Mono: Failed to unload scripts domain."); // 2. Update the API assemblies String update_error = update_api_assemblies_from_prebuilt(); - if (!update_error.empty()) { - ERR_EXPLAIN(update_error); - CRASH_NOW(); - } + CRASH_COND_MSG(!update_error.empty(), update_error); // 3. Load the scripts domain again - if (_load_scripts_domain() != OK) { - ERR_EXPLAIN("Mono: Failed to load scripts domain"); - CRASH_NOW(); - } + Error domain_load_err = _load_scripts_domain(); + CRASH_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain."); // 4. Try loading the updated assemblies if (!_try_load_api_assemblies()) { @@ -785,24 +787,22 @@ void GDMono::_load_api_assemblies() { if (_are_api_assemblies_out_of_sync()) { if (core_api_assembly_out_of_sync) { - ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync"); + ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync."); } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) { - ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed"); + ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed."); } if (editor_api_assembly_out_of_sync) { - ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync"); + ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync."); } CRASH_NOW(); } else { - ERR_EXPLAIN("Failed to load one of the API assemblies"); - CRASH_NOW(); + CRASH_NOW_MSG("Failed to load one of the API assemblies."); } } #else - ERR_EXPLAIN("Failed to load one of the API assemblies"); - CRASH_NOW(); + CRASH_NOW_MSG("Failed to load one of the API assemblies."); #endif } } @@ -813,8 +813,8 @@ bool GDMono::_load_tools_assemblies() { if (tools_assembly && tools_project_editor_assembly) return true; - bool success = load_assembly(TOOLS_ASSEMBLY_NAME, &tools_assembly) && - load_assembly(TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME, &tools_project_editor_assembly); + bool success = load_assembly(TOOLS_ASM_NAME, &tools_assembly) && + load_assembly(TOOLS_PROJECT_EDITOR_ASM_NAME, &tools_project_editor_assembly); return success; } @@ -851,7 +851,7 @@ void GDMono::_install_trace_listener() { (DebuggingUtils_InstallTraceListener)debug_utils->get_method_thunk("InstallTraceListener"); install_func((MonoObject **)&exc); if (exc) { - ERR_PRINT("Failed to install System.Diagnostics.Trace listener"); + ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener."); GDMonoUtils::debug_print_unhandled_exception(exc); } #endif @@ -865,8 +865,7 @@ Error GDMono::_load_scripts_domain() { scripts_domain = GDMonoUtils::create_domain("GodotEngine.ScriptsDomain"); - ERR_EXPLAIN("Mono: Could not create scripts app domain"); - ERR_FAIL_NULL_V(scripts_domain, ERR_CANT_CREATE); + ERR_FAIL_NULL_V_MSG(scripts_domain, ERR_CANT_CREATE, "Mono: Could not create scripts app domain."); mono_domain_set(scripts_domain, true); @@ -885,7 +884,7 @@ Error GDMono::_unload_scripts_domain() { finalizing_scripts_domain = true; if (!mono_domain_finalize(scripts_domain, 2000)) { - ERR_PRINT("Mono: Domain finalization timeout"); + ERR_PRINT("Mono: Domain finalization timeout."); } finalizing_scripts_domain = false; @@ -911,7 +910,7 @@ Error GDMono::_unload_scripts_domain() { mono_domain_try_unload(domain, (MonoObject **)&exc); if (exc) { - ERR_PRINT("Exception thrown when unloading scripts domain"); + ERR_PRINT("Exception thrown when unloading scripts domain."); GDMonoUtils::debug_unhandled_exception(exc); return FAILED; } @@ -925,20 +924,14 @@ Error GDMono::reload_scripts_domain() { ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG); if (scripts_domain) { - Error err = _unload_scripts_domain(); - if (err != OK) { - ERR_PRINT("Mono: Failed to unload scripts domain"); - return err; - } + Error domain_unload_err = _unload_scripts_domain(); + ERR_FAIL_COND_V_MSG(domain_unload_err != OK, domain_unload_err, "Mono: Failed to unload scripts domain."); } CSharpLanguage::get_singleton()->_on_scripts_domain_unloaded(); - Error err = _load_scripts_domain(); - if (err != OK) { - ERR_PRINT("Mono: Failed to load scripts domain"); - return err; - } + Error domain_load_err = _load_scripts_domain(); + ERR_FAIL_COND_V_MSG(domain_load_err != OK, domain_load_err, "Mono: Failed to load scripts domain."); // Load assemblies. The API and tools assemblies are required, // the application is aborted if these assemblies cannot be loaded. @@ -946,10 +939,8 @@ Error GDMono::reload_scripts_domain() { _load_api_assemblies(); #if defined(TOOLS_ENABLED) - if (!_load_tools_assemblies()) { - ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); - CRASH_NOW(); - } + bool tools_assemblies_loaded = _load_tools_assemblies(); + CRASH_COND_MSG(!tools_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies."); #endif // Load the project's main assembly. Here, during hot-reloading, we do @@ -971,13 +962,13 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { String domain_name = mono_domain_get_friendly_name(p_domain); - print_verbose("Mono: Unloading domain `" + domain_name + "`..."); + print_verbose("Mono: Unloading domain '" + domain_name + "'..."); if (mono_domain_get() == p_domain) mono_domain_set(root_domain, true); if (!mono_domain_finalize(p_domain, 2000)) { - ERR_PRINT("Mono: Domain finalization timeout"); + ERR_PRINT("Mono: Domain finalization timeout."); } mono_gc_collect(mono_gc_max_generation()); @@ -988,7 +979,7 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { mono_domain_try_unload(p_domain, (MonoObject **)&exc); if (exc) { - ERR_PRINTS("Exception thrown when unloading domain `" + domain_name + "`"); + ERR_PRINTS("Exception thrown when unloading domain '" + domain_name + "'."); GDMonoUtils::debug_print_unhandled_exception(exc); return FAILED; } @@ -1105,7 +1096,7 @@ GDMono::~GDMono() { if (scripts_domain) { Error err = _unload_scripts_domain(); if (err != OK) { - ERR_PRINT("Mono: Failed to unload scripts domain"); + ERR_PRINT("Mono: Failed to unload scripts domain."); } } diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index c5bcce4fa1..4f7d3791f7 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -165,7 +165,7 @@ public: #endif #ifdef TOOLS_ENABLED - bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type); + bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config); String update_api_assemblies_from_prebuilt(); #endif diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 761c7f6fcb..a82bb42731 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -151,14 +151,14 @@ MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, vo } { - // If we find the assembly here, we load it with `mono_assembly_load_from_full`, + // If we find the assembly here, we load it with 'mono_assembly_load_from_full', // which in turn invokes load hooks before returning the MonoAssembly to us. - // One of the load hooks is `load_aot_module`. This hook can end up calling preload hooks - // again for the same assembly in certain in certain circumstances (the `do_load_image` part). + // One of the load hooks is 'load_aot_module'. This hook can end up calling preload hooks + // again for the same assembly in certain in certain circumstances (the 'do_load_image' part). // If this is the case and we return NULL due to the no_search condition below, // it will result in an internal crash later on. Therefore we need to return the assembly we didn't - // get yet from `mono_assembly_load_from_full`. Luckily we have the image, which already got it. - // This must be done here. If done in search hooks, it would cause `mono_assembly_load_from_full` + // get yet from 'mono_assembly_load_from_full'. Luckily we have the image, which already got it. + // This must be done here. If done in search hooks, it would cause 'mono_assembly_load_from_full' // to think another MonoAssembly for this assembly was already loaded, making it delete its own, // when in fact both pointers were the same... This hooks thing is confusing. if (image_corlib_loading) { diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp index 1c10d3c8eb..89a88fcfb2 100644 --- a/modules/mono/mono_gd/gd_mono_class.cpp +++ b/modules/mono/mono_gd/gd_mono_class.cpp @@ -165,8 +165,8 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base #ifdef DEBUG_ENABLED String fullname = method->get_ret_type_full_name() + " " + name + "(" + method->get_signature_desc(true) + ")"; - WARN_PRINTS("Method `" + fullname + "` is hidden by Godot API method. Should be `" + - method->get_full_name_no_class() + "`. In class `" + namespace_name + "." + class_name + "`."); + WARN_PRINTS("Method '" + fullname + "' is hidden by Godot API method. Should be '" + + method->get_full_name_no_class() + "'. In class '" + namespace_name + "." + class_name + "'."); #endif continue; } @@ -184,8 +184,8 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base if (m && m->get_name() != name) { // found String fullname = m->get_ret_type_full_name() + " " + name + "(" + m->get_signature_desc(true) + ")"; - WARN_PRINTS("Method `" + fullname + "` should be `" + m->get_full_name_no_class() + - "`. In class `" + namespace_name + "." + class_name + "`."); + WARN_PRINTS("Method '" + fullname + "' should be '" + m->get_full_name_no_class() + + "'. In class '" + namespace_name + "." + class_name + "'."); break; } diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp index 3999658f93..7b8e6f89e9 100644 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -219,16 +219,14 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } default: { - ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed enum value of unmarshallable base type."); - ERR_FAIL(); + ERR_FAIL_MSG("Attempted to convert Variant to a managed enum value of unmarshallable base type."); } } break; } - ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + tclass->get_name()); - ERR_FAIL(); + ERR_FAIL_MSG("Attempted to set the value of a field of unmarshallable type: '" + tclass->get_name() + "'."); } break; case MONO_TYPE_ARRAY: @@ -275,8 +273,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } - ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed array of unmarshallable element type."); - ERR_FAIL(); + ERR_FAIL_MSG("Attempted to convert Variant to a managed array of unmarshallable element type."); } break; case MONO_TYPE_CLASS: { @@ -351,8 +348,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } } - ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + type_class->get_name()); - ERR_FAIL(); + ERR_FAIL_MSG("Attempted to set the value of a field of unmarshallable type: '" + type_class->get_name() + "'."); } break; case MONO_TYPE_OBJECT: { @@ -508,7 +504,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } break; default: { - ERR_PRINTS(String() + "Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding)); + ERR_PRINTS("Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding) + "."); } break; } diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index e50e3b0794..3324ecb3a8 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -48,7 +48,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { CRASH_COND(!unmanaged); - // All mono objects created from the managed world (e.g.: `new Player()`) + // All mono objects created from the managed world (e.g.: 'new Player()') // need to have a CSharpScript in order for their methods to be callable from the unmanaged side Reference *ref = Object::cast_to<Reference>(unmanaged); diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp index a6e04e561d..6d91075ce3 100644 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -72,7 +72,7 @@ static void mono_log_callback(const char *log_domain, const char *log_level, con } if (fatal) { - ERR_PRINTS("Mono: FATAL ERROR, ABORTING! Logfile: " + GDMonoLog::get_singleton()->get_log_file_path() + "\n"); + ERR_PRINTS("Mono: FATAL ERROR, ABORTING! Logfile: '" + GDMonoLog::get_singleton()->get_log_file_path() + "'."); // Make sure to flush before aborting f->flush(); f->close(); @@ -90,8 +90,7 @@ bool GDMonoLog::_try_create_logs_dir(const String &p_logs_dir) { DirAccessRef diraccess = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!diraccess, false); Error logs_mkdir_err = diraccess->make_dir_recursive(p_logs_dir); - ERR_EXPLAIN("Failed to create mono logs directory"); - ERR_FAIL_COND_V(logs_mkdir_err != OK, false); + ERR_FAIL_COND_V_MSG(logs_mkdir_err != OK, false, "Failed to create mono logs directory."); } return true; @@ -131,7 +130,7 @@ void GDMonoLog::initialize() { CharString log_level = OS::get_singleton()->get_environment("GODOT_MONO_LOG_LEVEL").utf8(); if (log_level.length() != 0 && log_level_get_id(log_level.get_data()) == -1) { - ERR_PRINTS(String() + "Mono: Ignoring invalid log level (GODOT_MONO_LOG_LEVEL): " + log_level.get_data()); + ERR_PRINTS(String() + "Mono: Ignoring invalid log level (GODOT_MONO_LOG_LEVEL): '" + log_level.get_data() + "'."); log_level = CharString(); } @@ -160,7 +159,7 @@ void GDMonoLog::initialize() { log_file = FileAccess::open(log_file_path, FileAccess::WRITE); if (!log_file) { - ERR_PRINT("Mono: Cannot create log file"); + ERR_PRINTS("Mono: Cannot create log file at: " + log_file_path); } } diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index 42102ed835..7aac691102 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -276,7 +276,7 @@ String mono_to_utf8_string(MonoString *p_mono_string) { char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error); if (!mono_error_ok(&error)) { - ERR_PRINTS(String("Failed to convert MonoString* to UTF-8: ") + mono_error_get_message(&error)); + ERR_PRINTS(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&error) + "'."); mono_error_cleanup(&error); return String(); } @@ -474,8 +474,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return BOX_ENUM(enum_baseclass, val); } default: { - ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed enum value of unmarshallable base type."); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to a managed enum value of unmarshallable base type."); } } } @@ -509,8 +508,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty if (array_type->eklass == CACHED_CLASS_RAW(Color)) return (MonoObject *)PoolColorArray_to_mono_array(p_var->operator PoolColorArray()); - ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed array of unmarshallable element type."); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to a managed array of unmarshallable element type."); } break; case MONO_TYPE_CLASS: { @@ -695,9 +693,8 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } break; } - ERR_EXPLAIN(String() + "Attempted to convert Variant to an unmarshallable managed type. Name: \'" + - p_type.type_class->get_name() + "\' Encoding: " + itos(p_type.type_encoding)); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to an unmarshallable managed type. Name: '" + + p_type.type_class->get_name() + "' Encoding: " + itos(p_type.type_encoding) + "."); } Variant mono_object_to_variant(MonoObject *p_obj) { @@ -809,8 +806,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) { if (array_type->eklass == CACHED_CLASS_RAW(Color)) return mono_array_to_PoolColorArray((MonoArray *)p_obj); - ERR_EXPLAIN(String() + "Attempted to convert a managed array of unmarshallable element type to Variant."); - ERR_FAIL_V(Variant()); + ERR_FAIL_V_MSG(Variant(), "Attempted to convert a managed array of unmarshallable element type to Variant."); } break; case MONO_TYPE_CLASS: { @@ -908,9 +904,8 @@ Variant mono_object_to_variant(MonoObject *p_obj) { } break; } - ERR_EXPLAIN(String() + "Attempted to convert an unmarshallable managed type to Variant. Name: \'" + - type.type_class->get_name() + "\' Encoding: " + itos(type.type_encoding)); - ERR_FAIL_V(Variant()); + ERR_FAIL_V_MSG(Variant(), "Attempted to convert an unmarshallable managed type to Variant. Name: '" + + type.type_class->get_name() + "' Encoding: " + itos(type.type_encoding) + "."); } MonoArray *Array_to_mono_array(const Array &p_array) { diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index 7afdfc8ac8..e385f4c601 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -52,14 +52,11 @@ namespace GDMonoUtils { MonoCache mono_cache; -#define CACHE_AND_CHECK(m_var, m_val) \ - { \ - CRASH_COND(m_var != NULL); \ - m_var = m_val; \ - if (!m_var) { \ - ERR_EXPLAIN("Mono Cache: Member " #m_var " is null"); \ - ERR_FAIL(); \ - } \ +#define CACHE_AND_CHECK(m_var, m_val) \ + { \ + CRASH_COND(m_var != NULL); \ + m_var = m_val; \ + ERR_FAIL_COND_MSG(!m_var, "Mono Cache: Member " #m_var " is null."); \ } #define CACHE_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.class_##m_class, m_val) @@ -453,10 +450,9 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) { } MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object) { - if (!ClassDB::is_parent_class(p_object->get_class_name(), p_native)) { - ERR_EXPLAIN("Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'"); - ERR_FAIL_V(NULL); - } + bool parent_is_object_class = ClassDB::is_parent_class(p_object->get_class_name(), p_native); + ERR_FAIL_COND_V_MSG(!parent_is_object_class, NULL, + "Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'."); MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); ERR_FAIL_NULL_V(mono_object, NULL); diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index 54d73c971f..189ceaab1b 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -67,10 +67,8 @@ Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { #ifdef DEBUG_ENABLED - if (conn_target_id && !ObjectDB::get_instance(conn_target_id)) { - ERR_EXPLAIN("Resumed after await, but class instance is gone"); - ERR_FAIL_V(Variant()); - } + ERR_FAIL_COND_V_MSG(conn_target_id && !ObjectDB::get_instance(conn_target_id), Variant(), + "Resumed after await, but class instance is gone."); #endif if (p_argcount < 1) { diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 2b014c2a45..716c712ccc 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -55,10 +55,7 @@ int sfind(const String &p_text, int p_from) { for (int j = 0; j < src_len; j++) { int read_pos = i + j; - if (read_pos >= len) { - ERR_PRINT("read_pos >= len"); - return -1; - }; + ERR_FAIL_COND_V(read_pos >= len, -1); switch (j) { case 0: @@ -211,14 +208,18 @@ String str_format(const char *p_format, ...) { #endif #if defined(MINGW_ENABLED) || defined(_MSC_VER) && _MSC_VER < 1900 -#define vsnprintf(m_buffer, m_count, m_format, m_argptr) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_argptr) +#define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_args_copy) +#define gd_vscprintf(m_format, m_args_copy) _vscprintf(m_format, m_args_copy) +#else +#define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf(m_buffer, m_count, m_format, m_args_copy) +#define gd_vscprintf(m_format, m_args_copy) vsnprintf(NULL, 0, p_format, m_args_copy) #endif String str_format(const char *p_format, va_list p_list) { va_list list; va_copy(list, p_list); - int len = vsnprintf(NULL, 0, p_format, list); + int len = gd_vscprintf(p_format, list); va_end(list); len += 1; // for the trailing '/0' @@ -226,7 +227,7 @@ String str_format(const char *p_format, va_list p_list) { char *buffer(memnew_arr(char, len)); va_copy(list, p_list); - vsnprintf(buffer, len, p_format, list); + gd_vsnprintf(buffer, len, p_format, list); va_end(list); String res(buffer); diff --git a/modules/opus/audio_stream_opus.cpp b/modules/opus/audio_stream_opus.cpp index 615081d818..d3e8d3c9bb 100644 --- a/modules/opus/audio_stream_opus.cpp +++ b/modules/opus/audio_stream_opus.cpp @@ -287,8 +287,7 @@ int AudioStreamPlaybackOpus::mix(int16_t *p_buffer, int p_frames) { int ret = op_read(opus_file, (opus_int16 *)p_buffer, todo * stream_channels, ¤t_section); if (ret < 0) { playing = false; - ERR_EXPLAIN("Error reading Opus File: " + file); - ERR_BREAK(ret < 0); + ERR_BREAK_MSG(ret < 0, "Error reading Opus file: " + file + "."); } else if (ret == 0) { // end of song, reload? op_free(opus_file); diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp index 8b1f21d95d..cf6b396180 100644 --- a/modules/pvr/texture_loader_pvr.cpp +++ b/modules/pvr/texture_loader_pvr.cpp @@ -149,8 +149,7 @@ RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path, format = Image::FORMAT_ETC; break; default: - ERR_EXPLAIN("Unsupported format in PVR texture: " + itos(flags & 0xFF)); - ERR_FAIL_V(RES()); + ERR_FAIL_V_MSG(RES(), "Unsupported format in PVR texture: " + itos(flags & 0xFF) + "."); } w.release(); diff --git a/modules/recast/navigation_mesh_generator.cpp b/modules/recast/navigation_mesh_generator.cpp index 14467dc5c7..c5b60f2dca 100644 --- a/modules/recast/navigation_mesh_generator.cpp +++ b/modules/recast/navigation_mesh_generator.cpp @@ -49,6 +49,10 @@ #include "modules/csg/csg_shape.h" #endif +#ifdef MODULE_GRIDMAP_ENABLED +#include "modules/gridmap/grid_map.h" +#endif + EditorNavigationMeshGenerator *EditorNavigationMeshGenerator::singleton = NULL; void EditorNavigationMeshGenerator::_add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies) { @@ -240,8 +244,21 @@ void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_tran } } - if (Object::cast_to<Spatial>(p_node)) { +#ifdef MODULE_GRIDMAP_ENABLED + if (Object::cast_to<GridMap>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { + GridMap *gridmap_instance = Object::cast_to<GridMap>(p_node); + Array meshes = gridmap_instance->get_meshes(); + Transform xform = gridmap_instance->get_transform(); + for (int i = 0; i < meshes.size(); i += 2) { + Ref<Mesh> mesh = meshes[i + 1]; + if (mesh.is_valid()) { + _add_mesh(mesh, p_accumulated_transform * xform * meshes[i], p_verticies, p_indices); + } + } + } +#endif + if (Object::cast_to<Spatial>(p_node)) { Spatial *spatial = Object::cast_to<Spatial>(p_node); p_accumulated_transform = p_accumulated_transform * spatial->get_transform(); } diff --git a/modules/squish/image_compress_squish.cpp b/modules/squish/image_compress_squish.cpp index 64f4c169cb..9b0a55eae3 100644 --- a/modules/squish/image_compress_squish.cpp +++ b/modules/squish/image_compress_squish.cpp @@ -57,8 +57,7 @@ void image_decompress_squish(Image *p_image) { } else if (p_image->get_format() == Image::FORMAT_RGTC_RG) { squish_flags = squish::kBc5; } else { - ERR_EXPLAIN("Squish: Can't decompress unknown format: " + itos(p_image->get_format())); - ERR_FAIL_COND(true); + ERR_FAIL_MSG("Squish: Can't decompress unknown format: " + itos(p_image->get_format()) + "."); return; } diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp index b0cd648734..a2ef88d130 100644 --- a/modules/svg/image_loader_svg.cpp +++ b/modules/svg/image_loader_svg.cpp @@ -109,12 +109,10 @@ Error ImageLoaderSVG::_create_image(Ref<Image> p_image, const PoolVector<uint8_t float upscale = upsample ? 2.0 : 1.0; int w = (int)(svg_image->width * p_scale * upscale); - ERR_EXPLAIN(vformat("Can't create image from SVG with scale %s, the resulting image size exceeds max width.", rtos(p_scale))); - ERR_FAIL_COND_V(w > Image::MAX_WIDTH, ERR_PARAMETER_RANGE_ERROR); + ERR_FAIL_COND_V_MSG(w > Image::MAX_WIDTH, ERR_PARAMETER_RANGE_ERROR, vformat("Can't create image from SVG with scale %s, the resulting image size exceeds max width.", rtos(p_scale))); int h = (int)(svg_image->height * p_scale * upscale); - ERR_EXPLAIN(vformat("Can't create image from SVG with scale %s, the resulting image size exceeds max height.", rtos(p_scale))); - ERR_FAIL_COND_V(h > Image::MAX_HEIGHT, ERR_PARAMETER_RANGE_ERROR); + ERR_FAIL_COND_V_MSG(h > Image::MAX_HEIGHT, ERR_PARAMETER_RANGE_ERROR, vformat("Can't create image from SVG with scale %s, the resulting image size exceeds max height.", rtos(p_scale))); PoolVector<uint8_t> dst_image; dst_image.resize(w * h * 4); diff --git a/modules/tinyexr/image_loader_tinyexr.h b/modules/tinyexr/image_loader_tinyexr.h index 4003fdc802..ee8479b1b4 100644 --- a/modules/tinyexr/image_loader_tinyexr.h +++ b/modules/tinyexr/image_loader_tinyexr.h @@ -33,9 +33,6 @@ #include "core/io/image_loader.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class ImageLoaderTinyEXR : public ImageFormatLoader { public: diff --git a/modules/tinyexr/image_saver_tinyexr.cpp b/modules/tinyexr/image_saver_tinyexr.cpp new file mode 100644 index 0000000000..e1d42d3217 --- /dev/null +++ b/modules/tinyexr/image_saver_tinyexr.cpp @@ -0,0 +1,279 @@ +/*************************************************************************/ +/* image_saver_tinyexr.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "image_saver_tinyexr.h" +#include "core/math/math_funcs.h" + +#include "thirdparty/tinyexr/tinyexr.h" + +static bool is_supported_format(Image::Format p_format) { + // This is checked before anything else. + // Mostly uncompressed formats are considered. + switch (p_format) { + case Image::FORMAT_RF: + case Image::FORMAT_RGF: + case Image::FORMAT_RGBF: + case Image::FORMAT_RGBAF: + case Image::FORMAT_RH: + case Image::FORMAT_RGH: + case Image::FORMAT_RGBH: + case Image::FORMAT_RGBAH: + case Image::FORMAT_R8: + case Image::FORMAT_RG8: + case Image::FORMAT_RGB8: + case Image::FORMAT_RGBA8: + return true; + default: + return false; + } +} + +enum SrcPixelType { + SRC_FLOAT, + SRC_HALF, + SRC_BYTE +}; + +static SrcPixelType get_source_pixel_type(Image::Format p_format) { + switch (p_format) { + case Image::FORMAT_RF: + case Image::FORMAT_RGF: + case Image::FORMAT_RGBF: + case Image::FORMAT_RGBAF: + return SRC_FLOAT; + case Image::FORMAT_RH: + case Image::FORMAT_RGH: + case Image::FORMAT_RGBH: + case Image::FORMAT_RGBAH: + return SRC_HALF; + case Image::FORMAT_R8: + case Image::FORMAT_RG8: + case Image::FORMAT_RGB8: + case Image::FORMAT_RGBA8: + return SRC_BYTE; + default: + CRASH_NOW(); + } +} + +static int get_target_pixel_type(Image::Format p_format) { + switch (p_format) { + case Image::FORMAT_RF: + case Image::FORMAT_RGF: + case Image::FORMAT_RGBF: + case Image::FORMAT_RGBAF: + return TINYEXR_PIXELTYPE_FLOAT; + case Image::FORMAT_RH: + case Image::FORMAT_RGH: + case Image::FORMAT_RGBH: + case Image::FORMAT_RGBAH: + // EXR doesn't support 8-bit channels so in that case we'll convert + case Image::FORMAT_R8: + case Image::FORMAT_RG8: + case Image::FORMAT_RGB8: + case Image::FORMAT_RGBA8: + return TINYEXR_PIXELTYPE_HALF; + default: + CRASH_NOW(); + } +} + +static int get_pixel_type_size(int p_pixel_type) { + switch (p_pixel_type) { + case TINYEXR_PIXELTYPE_HALF: + return 2; + case TINYEXR_PIXELTYPE_FLOAT: + return 4; + } + CRASH_NOW(); +} + +static int get_channel_count(Image::Format p_format) { + switch (p_format) { + case Image::FORMAT_RF: + case Image::FORMAT_RH: + case Image::FORMAT_R8: + return 1; + case Image::FORMAT_RGF: + case Image::FORMAT_RGH: + case Image::FORMAT_RG8: + return 2; + case Image::FORMAT_RGBF: + case Image::FORMAT_RGBH: + case Image::FORMAT_RGB8: + return 3; + case Image::FORMAT_RGBAF: + case Image::FORMAT_RGBAH: + case Image::FORMAT_RGBA8: + return 4; + default: + CRASH_NOW(); + } +} + +Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) { + + Image::Format format = p_img->get_format(); + + if (!is_supported_format(format)) { + // Format not supported + print_error("Image format not supported for saving as EXR. Consider saving as PNG."); + return ERR_UNAVAILABLE; + } + + EXRHeader header; + InitEXRHeader(&header); + + EXRImage image; + InitEXRImage(&image); + + const int max_channels = 4; + + // Godot does not support more than 4 channels, + // so we can preallocate header infos on the stack and use only the subset we need + PoolByteArray channels[max_channels]; + unsigned char *channels_ptrs[max_channels]; + EXRChannelInfo channel_infos[max_channels]; + int pixel_types[max_channels]; + int requested_pixel_types[max_channels] = { -1 }; + + // Gimp and Blender are a bit annoying so order of channels isn't straightforward. + const int channel_mappings[4][4] = { + { 0 }, // R + { 1, 0 }, // GR + { 2, 1, 0 }, // BGR + { 2, 1, 0, 3 } // BGRA + }; + + int channel_count = get_channel_count(format); + ERR_FAIL_COND_V(p_grayscale && channel_count != 1, ERR_INVALID_PARAMETER); + + int target_pixel_type = get_target_pixel_type(format); + int target_pixel_type_size = get_pixel_type_size(target_pixel_type); + SrcPixelType src_pixel_type = get_source_pixel_type(format); + const int pixel_count = p_img->get_width() * p_img->get_height(); + + const int *channel_mapping = channel_mappings[channel_count - 1]; + + { + PoolByteArray src_data = p_img->get_data(); + PoolByteArray::Read src_r = src_data.read(); + + for (int channel_index = 0; channel_index < channel_count; ++channel_index) { + + // De-interleave channels + + PoolByteArray &dst = channels[channel_index]; + dst.resize(pixel_count * target_pixel_type_size); + + PoolByteArray::Write dst_w = dst.write(); + + if (src_pixel_type == SRC_FLOAT && target_pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + + // Note: we don't save mipmaps + CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size); + + const float *src_rp = (float *)src_r.ptr(); + float *dst_wp = (float *)dst_w.ptr(); + + for (int i = 0; i < pixel_count; ++i) { + dst_wp[i] = src_rp[channel_index + i * channel_count]; + } + + } else if (src_pixel_type == SRC_HALF && target_pixel_type == TINYEXR_PIXELTYPE_HALF) { + + CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size); + + const uint16_t *src_rp = (uint16_t *)src_r.ptr(); + uint16_t *dst_wp = (uint16_t *)dst_w.ptr(); + + for (int i = 0; i < pixel_count; ++i) { + dst_wp[i] = src_rp[channel_index + i * channel_count]; + } + + } else if (src_pixel_type == SRC_BYTE && target_pixel_type == TINYEXR_PIXELTYPE_HALF) { + + CRASH_COND(src_data.size() < pixel_count * channel_count); + + const uint8_t *src_rp = (uint8_t *)src_r.ptr(); + uint16_t *dst_wp = (uint16_t *)dst_w.ptr(); + + for (int i = 0; i < pixel_count; ++i) { + dst_wp[i] = Math::make_half_float(src_rp[channel_index + i * channel_count] / 255.f); + } + + } else { + CRASH_NOW(); + } + + int remapped_index = channel_mapping[channel_index]; + + channels_ptrs[remapped_index] = dst_w.ptr(); + + // No conversion + pixel_types[remapped_index] = target_pixel_type; + requested_pixel_types[remapped_index] = target_pixel_type; + + // Write channel name + if (p_grayscale) { + channel_infos[remapped_index].name[0] = 'Y'; + channel_infos[remapped_index].name[1] = '\0'; + } else { + const char *rgba = "RGBA"; + channel_infos[remapped_index].name[0] = rgba[channel_index]; + channel_infos[remapped_index].name[1] = '\0'; + } + } + } + + image.images = channels_ptrs; + image.num_channels = channel_count; + image.width = p_img->get_width(); + image.height = p_img->get_height(); + + header.num_channels = image.num_channels; + header.channels = channel_infos; + header.pixel_types = pixel_types; + header.requested_pixel_types = requested_pixel_types; + // TODO DEBUG REMOVE + for (int i = 0; i < 4; ++i) { + print_line(String("requested_pixel_types{0}: {1}").format(varray(i, requested_pixel_types[i]))); + } + + CharString utf8_filename = p_path.utf8(); + const char *err; + int ret = SaveEXRImageToFile(&image, &header, utf8_filename.ptr(), &err); + if (ret != TINYEXR_SUCCESS) { + print_error(String("Saving EXR failed. Error: {0}").format(varray(err))); + return ERR_FILE_CANT_WRITE; + } + + return OK; +} diff --git a/modules/tinyexr/image_saver_tinyexr.h b/modules/tinyexr/image_saver_tinyexr.h new file mode 100644 index 0000000000..298bd1d21c --- /dev/null +++ b/modules/tinyexr/image_saver_tinyexr.h @@ -0,0 +1,38 @@ +/*************************************************************************/ +/* image_saver_tinyexr.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef IMAGE_SAVER_TINYEXR_H +#define IMAGE_SAVER_TINYEXR_H + +#include "core/os/os.h" + +Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale); + +#endif // IMAGE_SAVER_TINYEXR_H diff --git a/modules/tinyexr/register_types.cpp b/modules/tinyexr/register_types.cpp index 5473a55687..233b3afa08 100644 --- a/modules/tinyexr/register_types.cpp +++ b/modules/tinyexr/register_types.cpp @@ -31,6 +31,7 @@ #include "register_types.h" #include "image_loader_tinyexr.h" +#include "image_saver_tinyexr.h" static ImageLoaderTinyEXR *image_loader_tinyexr = NULL; @@ -38,9 +39,13 @@ void register_tinyexr_types() { image_loader_tinyexr = memnew(ImageLoaderTinyEXR); ImageLoader::add_image_format_loader(image_loader_tinyexr); + + Image::save_exr_func = save_exr; } void unregister_tinyexr_types() { memdelete(image_loader_tinyexr); + + Image::save_exr_func = NULL; } diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 4eaad3db31..6bed1742eb 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -328,8 +328,7 @@ void VisualScript::add_node(const StringName &p_func, int p_id, const Ref<Visual if (Object::cast_to<VisualScriptFunction>(*p_node)) { //the function indeed - ERR_EXPLAIN("A function node already has been set here."); - ERR_FAIL_COND(func.function_id >= 0); + ERR_FAIL_COND_MSG(func.function_id >= 0, "A function node has already been set here."); func.function_id = p_id; } @@ -1928,8 +1927,7 @@ Variant VisualScriptInstance::call(const StringName &p_method, const Variant **p if (!E) { r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; - ERR_EXPLAIN("No VisualScriptFunction node in function!"); - ERR_FAIL_V(Variant()); + ERR_FAIL_V_MSG(Variant(), "No VisualScriptFunction node in function."); } VisualScriptNodeInstance *node = E->get(); @@ -1985,8 +1983,7 @@ String VisualScriptInstance::to_string(bool *r_valid) { if (ret.get_type() != Variant::STRING) { if (r_valid) *r_valid = false; - ERR_EXPLAIN("Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String."); - ERR_FAIL_V(String()); + ERR_FAIL_V_MSG(String(), "Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String."); } if (r_valid) *r_valid = true; @@ -2273,15 +2270,10 @@ Variant VisualScriptFunctionState::_signal_callback(const Variant **p_args, int ERR_FAIL_COND_V(function == StringName(), Variant()); #ifdef DEBUG_ENABLED - if (instance_id && !ObjectDB::get_instance(instance_id)) { - ERR_EXPLAIN("Resumed after yield, but class instance is gone"); - ERR_FAIL_V(Variant()); - } - if (script_id && !ObjectDB::get_instance(script_id)) { - ERR_EXPLAIN("Resumed after yield, but script is gone"); - ERR_FAIL_V(Variant()); - } + ERR_FAIL_COND_V_MSG(instance_id && !ObjectDB::get_instance(instance_id), Variant(), "Resumed after yield, but class instance is gone."); + ERR_FAIL_COND_V_MSG(script_id && !ObjectDB::get_instance(script_id), Variant(), "Resumed after yield, but script is gone."); + #endif r_error.error = Variant::CallError::CALL_OK; @@ -2340,15 +2332,10 @@ Variant VisualScriptFunctionState::resume(Array p_args) { ERR_FAIL_COND_V(function == StringName(), Variant()); #ifdef DEBUG_ENABLED - if (instance_id && !ObjectDB::get_instance(instance_id)) { - ERR_EXPLAIN("Resumed after yield, but class instance is gone"); - ERR_FAIL_V(Variant()); - } - if (script_id && !ObjectDB::get_instance(script_id)) { - ERR_EXPLAIN("Resumed after yield, but script is gone"); - ERR_FAIL_V(Variant()); - } + ERR_FAIL_COND_V_MSG(instance_id && !ObjectDB::get_instance(instance_id), Variant(), "Resumed after yield, but class instance is gone."); + ERR_FAIL_COND_V_MSG(script_id && !ObjectDB::get_instance(script_id), Variant(), "Resumed after yield, but script is gone."); + #endif Variant::CallError r_error; @@ -2474,7 +2461,7 @@ bool VisualScriptLanguage::debug_break_parse(const String &p_file, int p_node, c _debug_parse_err_node = p_node; _debug_parse_err_file = p_file; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, false); + ScriptDebugger::get_singleton()->debug(this, false, true); return true; } else { return false; @@ -2488,7 +2475,7 @@ bool VisualScriptLanguage::debug_break(const String &p_error, bool p_allow_conti _debug_parse_err_node = -1; _debug_parse_err_file = ""; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, p_allow_continue); + ScriptDebugger::get_singleton()->debug(this, p_allow_continue, true); return true; } else { return false; diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp index 8088a71198..3fdceacebb 100644 --- a/modules/visual_script/visual_script_builtin_funcs.cpp +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -106,6 +106,7 @@ const char *VisualScriptBuiltinFunc::func_name[VisualScriptBuiltinFunc::FUNC_MAX "smoothstep", "posmod", "lerp_angle", + "ord", }; VisualScriptBuiltinFunc::BuiltinFunc VisualScriptBuiltinFunc::find_function(const String &p_string) { @@ -181,6 +182,7 @@ int VisualScriptBuiltinFunc::get_func_argument_count(BuiltinFunc p_func) { case OBJ_WEAKREF: case TYPE_OF: case TEXT_CHAR: + case TEXT_ORD: case TEXT_STR: case TEXT_PRINT: case TEXT_PRINTERR: @@ -438,6 +440,9 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const case TYPE_EXISTS: { return PropertyInfo(Variant::STRING, "type"); } break; + case TEXT_ORD: { + return PropertyInfo(Variant::STRING, "character"); + } break; case TEXT_CHAR: { return PropertyInfo(Variant::INT, "ascii"); } break; @@ -594,6 +599,7 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons case TYPE_CONVERT: { } break; + case TEXT_ORD: case TYPE_OF: { t = Variant::INT; @@ -1132,6 +1138,30 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in *r_return = String(result); } break; + case VisualScriptBuiltinFunc::TEXT_ORD: { + + if (p_inputs[0]->get_type() != Variant::STRING) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + + return; + } + + String str = p_inputs[0]->operator String(); + + if (str.length() != 1) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + *r_return = "Expected a string of length 1 (a character)."; + + return; + } + + *r_return = str.get(0); + + } break; case VisualScriptBuiltinFunc::TEXT_STR: { String str = *p_inputs[0]; @@ -1374,6 +1404,7 @@ void VisualScriptBuiltinFunc::_bind_methods() { BIND_ENUM_CONSTANT(MATH_SMOOTHSTEP); BIND_ENUM_CONSTANT(MATH_POSMOD); BIND_ENUM_CONSTANT(MATH_LERP_ANGLE); + BIND_ENUM_CONSTANT(TEXT_ORD); BIND_ENUM_CONSTANT(FUNC_MAX); } @@ -1460,6 +1491,7 @@ void register_visual_script_builtin_func_node() { VisualScriptLanguage::singleton->add_register_func("functions/built_in/typeof", create_builtin_func_node<VisualScriptBuiltinFunc::TYPE_OF>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/type_exists", create_builtin_func_node<VisualScriptBuiltinFunc::TYPE_EXISTS>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/char", create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_CHAR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/ord", create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_ORD>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/str", create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_STR>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/print", create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_PRINT>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/printerr", create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_PRINTERR>); diff --git a/modules/visual_script/visual_script_builtin_funcs.h b/modules/visual_script/visual_script_builtin_funcs.h index cf475d675d..998a6cbc6a 100644 --- a/modules/visual_script/visual_script_builtin_funcs.h +++ b/modules/visual_script/visual_script_builtin_funcs.h @@ -106,6 +106,7 @@ public: MATH_SMOOTHSTEP, MATH_POSMOD, MATH_LERP_ANGLE, + TEXT_ORD, FUNC_MAX }; diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index d9eae03799..7262dde359 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -591,6 +591,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { gnode->add_color_override("title_color", c); c.a = 0.7; gnode->add_color_override("close_color", c); + gnode->add_color_override("resizer_color", c); gnode->add_style_override("frame", sbf); } @@ -776,8 +777,8 @@ void VisualScriptEditor::_update_members() { TreeItem *functions = members->create_item(root); functions->set_selectable(0, false); functions->set_text(0, TTR("Functions:")); - functions->add_button(0, Control::get_icon("Override", "EditorIcons"), 1); - functions->add_button(0, Control::get_icon("Add", "EditorIcons"), 0); + functions->add_button(0, Control::get_icon("Override", "EditorIcons"), 1, false, TTR("Override an existing built-in function.")); + functions->add_button(0, Control::get_icon("Add", "EditorIcons"), 0, false, TTR("Create a new function.")); functions->set_custom_color(0, Control::get_color("mono_color", "Editor")); List<StringName> func_names; @@ -795,7 +796,7 @@ void VisualScriptEditor::_update_members() { TreeItem *variables = members->create_item(root); variables->set_selectable(0, false); variables->set_text(0, TTR("Variables:")); - variables->add_button(0, Control::get_icon("Add", "EditorIcons")); + variables->add_button(0, Control::get_icon("Add", "EditorIcons"), -1, false, TTR("Create a new variable.")); variables->set_custom_color(0, Control::get_color("mono_color", "Editor")); Ref<Texture> type_icons[Variant::VARIANT_MAX] = { @@ -848,7 +849,7 @@ void VisualScriptEditor::_update_members() { TreeItem *_signals = members->create_item(root); _signals->set_selectable(0, false); _signals->set_text(0, TTR("Signals:")); - _signals->add_button(0, Control::get_icon("Add", "EditorIcons")); + _signals->add_button(0, Control::get_icon("Add", "EditorIcons"), -1, false, TTR("Create a new signal.")); _signals->set_custom_color(0, Control::get_color("mono_color", "Editor")); List<StringName> signal_names; diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 3b0210597b..65820b4c15 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -2530,7 +2530,7 @@ String VisualScriptCustomNode::get_category() const { if (get_script_instance() && get_script_instance()->has_method("_get_category")) { return get_script_instance()->call("_get_category"); } - return "custom"; + return "Custom"; } class VisualScriptNodeInstanceCustomNode : public VisualScriptNodeInstance { diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp index 41828f040e..764807cffd 100644 --- a/modules/visual_script/visual_script_property_selector.cpp +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -130,12 +130,14 @@ void VisualScriptPropertySelector::_update_search() { { String b = String(E->get()); category = search_options->create_item(root); - category->set_text(0, b.replace_first("*", "")); - category->set_selectable(0, false); - Ref<Texture> icon; - String rep = b.replace("*", ""); - icon = EditorNode::get_singleton()->get_class_icon(rep); - category->set_icon(0, icon); + if (category) { + category->set_text(0, b.replace_first("*", "")); + category->set_selectable(0, false); + Ref<Texture> icon; + String rep = b.replace("*", ""); + icon = EditorNode::get_singleton()->get_class_icon(rep); + category->set_icon(0, icon); + } } if (properties || seq_connect) { if (instance) { diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index 2f4a45f108..2f56e778b9 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -116,8 +116,7 @@ int AudioStreamPlaybackOGGVorbis::mix(int16_t *p_buffer, int p_frames) { if (ret < 0) { playing = false; - ERR_EXPLAIN("Error reading OGG Vorbis File: " + file); - ERR_BREAK(ret < 0); + ERR_BREAK_MSG(ret < 0, "Error reading OGG Vorbis file: " + file + "."); } else if (ret == 0) { // end of song, reload? ov_clear(&vf); diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp index 3670edc9ea..fa3602ad27 100644 --- a/modules/webm/video_stream_webm.cpp +++ b/modules/webm/video_stream_webm.cpp @@ -53,8 +53,7 @@ public: file = FileAccess::open(p_file, FileAccess::READ); - ERR_EXPLAIN("Failed loading resource: '" + p_file + "';"); - ERR_FAIL_COND(!file); + ERR_FAIL_COND_MSG(!file, "Failed loading resource: '" + p_file + "'."); } ~MkvReader() { diff --git a/modules/webp/image_loader_webp.cpp b/modules/webp/image_loader_webp.cpp index 630c15f140..d1bfa20842 100644 --- a/modules/webp/image_loader_webp.cpp +++ b/modules/webp/image_loader_webp.cpp @@ -84,8 +84,7 @@ static Ref<Image> _webp_lossy_unpack(const PoolVector<uint8_t> &p_buffer) { ERR_FAIL_COND_V(r[0] != 'W' || r[1] != 'E' || r[2] != 'B' || r[3] != 'P', Ref<Image>()); WebPBitstreamFeatures features; if (WebPGetFeatures(&r[4], size, &features) != VP8_STATUS_OK) { - ERR_EXPLAIN("Error unpacking WEBP image:"); - ERR_FAIL_V(Ref<Image>()); + ERR_FAIL_V_MSG(Ref<Image>(), "Error unpacking WEBP image."); } /* @@ -107,8 +106,7 @@ static Ref<Image> _webp_lossy_unpack(const PoolVector<uint8_t> &p_buffer) { errdec = WebPDecodeRGBInto(&r[4], size, dst_w.ptr(), datasize, 3 * features.width) == NULL; } - //ERR_EXPLAIN("Error decoding webp! - "+p_file); - ERR_FAIL_COND_V(errdec, Ref<Image>()); + ERR_FAIL_COND_V_MSG(errdec, Ref<Image>(), "Failed decoding WebP image."); dst_w.release(); @@ -122,7 +120,6 @@ Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p WebPBitstreamFeatures features; if (WebPGetFeatures(p_buffer, p_buffer_len, &features) != VP8_STATUS_OK) { - // ERR_EXPLAIN("Error decoding WEBP image"); ERR_FAIL_V(ERR_FILE_CORRUPT); } @@ -139,8 +136,7 @@ Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p } dst_w.release(); - //ERR_EXPLAIN("Error decoding webp!"); - ERR_FAIL_COND_V(errdec, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V_MSG(errdec, ERR_FILE_CORRUPT, "Failed decoding WebP image."); p_image->create(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image); diff --git a/modules/webp/image_loader_webp.h b/modules/webp/image_loader_webp.h index 0c4e54df09..5a5c038017 100644 --- a/modules/webp/image_loader_webp.h +++ b/modules/webp/image_loader_webp.h @@ -33,9 +33,6 @@ #include "core/io/image_loader.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class ImageLoaderWEBP : public ImageFormatLoader { public: diff --git a/modules/webrtc/register_types.cpp b/modules/webrtc/register_types.cpp index 58b68d926b..6f97842064 100644 --- a/modules/webrtc/register_types.cpp +++ b/modules/webrtc/register_types.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "register_types.h" +#include "core/project_settings.h" #include "webrtc_data_channel.h" #include "webrtc_peer_connection.h" @@ -43,6 +44,12 @@ #include "webrtc_multiplayer.h" void register_webrtc_types() { +#define _SET_HINT(NAME, _VAL_, _MAX_) \ + GLOBAL_DEF(NAME, _VAL_); \ + ProjectSettings::get_singleton()->set_custom_property_info(NAME, PropertyInfo(Variant::INT, NAME, PROPERTY_HINT_RANGE, "2," #_MAX_ ",1,or_greater")); + + _SET_HINT(WRTC_IN_BUF, 64, 4096); + #ifdef JAVASCRIPT_ENABLED WebRTCPeerConnectionJS::make_default(); #elif defined(WEBRTC_GDNATIVE_ENABLED) diff --git a/modules/webrtc/webrtc_data_channel.cpp b/modules/webrtc/webrtc_data_channel.cpp index 2bd30e68f5..7b3843410a 100644 --- a/modules/webrtc/webrtc_data_channel.cpp +++ b/modules/webrtc/webrtc_data_channel.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "webrtc_data_channel.h" +#include "core/project_settings.h" void WebRTCDataChannel::_bind_methods() { ClassDB::bind_method(D_METHOD("poll"), &WebRTCDataChannel::poll); @@ -58,6 +59,7 @@ void WebRTCDataChannel::_bind_methods() { } WebRTCDataChannel::WebRTCDataChannel() { + _in_buffer_shift = nearest_shift((int)GLOBAL_GET(WRTC_IN_BUF) - 1) + 10; } WebRTCDataChannel::~WebRTCDataChannel() { diff --git a/modules/webrtc/webrtc_data_channel.h b/modules/webrtc/webrtc_data_channel.h index 0b161da784..7e2c08d9d7 100644 --- a/modules/webrtc/webrtc_data_channel.h +++ b/modules/webrtc/webrtc_data_channel.h @@ -33,6 +33,8 @@ #include "core/io/packet_peer.h" +#define WRTC_IN_BUF "network/limits/webrtc/max_channel_in_buffer_kb" + class WebRTCDataChannel : public PacketPeer { GDCLASS(WebRTCDataChannel, PacketPeer); @@ -50,6 +52,8 @@ public: }; protected: + unsigned int _in_buffer_shift; + static void _bind_methods(); public: diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp index 069918cc9c..2edd212a50 100644 --- a/modules/webrtc/webrtc_data_channel_js.cpp +++ b/modules/webrtc/webrtc_data_channel_js.cpp @@ -56,7 +56,7 @@ EMSCRIPTEN_KEEPALIVE void _emrtc_on_ch_message(void *obj, uint8_t *p_data, uint3 } void WebRTCDataChannelJS::_on_open() { - in_buffer.resize(16); + in_buffer.resize(_in_buffer_shift); } void WebRTCDataChannelJS::_on_close() { @@ -68,10 +68,8 @@ void WebRTCDataChannelJS::_on_error() { } void WebRTCDataChannelJS::_on_message(uint8_t *p_data, uint32_t p_size, bool p_is_string) { - if (in_buffer.space_left() < (int)(p_size + 5)) { - ERR_EXPLAIN("Buffer full! Dropping data"); - ERR_FAIL(); - } + + ERR_FAIL_COND_MSG(in_buffer.space_left() < (int)(p_size + 5), "Buffer full! Dropping data."); uint8_t is_string = p_is_string ? 1 : 0; in_buffer.write((uint8_t *)&p_size, 4); diff --git a/modules/webrtc/webrtc_multiplayer.cpp b/modules/webrtc/webrtc_multiplayer.cpp index 17dafff93a..a759b17b83 100644 --- a/modules/webrtc/webrtc_multiplayer.cpp +++ b/modules/webrtc/webrtc_multiplayer.cpp @@ -321,10 +321,8 @@ Error WebRTCMultiplayer::put_packet(const uint8_t *p_buffer, int p_buffer_size) if (target_peer > 0) { E = peer_map.find(target_peer); - if (!E) { - ERR_EXPLAIN("Invalid Target Peer: " + itos(target_peer)); - ERR_FAIL_V(ERR_INVALID_PARAMETER); - } + ERR_FAIL_COND_V_MSG(!E, ERR_INVALID_PARAMETER, "Invalid target peer: " + itos(target_peer) + "."); + ERR_FAIL_COND_V(E->value()->channels.size() <= ch, ERR_BUG); ERR_FAIL_COND_V(!E->value()->channels[ch].is_valid(), ERR_BUG); return E->value()->channels[ch]->put_packet(p_buffer, p_buffer_size); diff --git a/modules/webrtc/webrtc_peer_connection_gdnative.cpp b/modules/webrtc/webrtc_peer_connection_gdnative.cpp index af98aa750a..5e9dcb5366 100644 --- a/modules/webrtc/webrtc_peer_connection_gdnative.cpp +++ b/modules/webrtc/webrtc_peer_connection_gdnative.cpp @@ -51,13 +51,11 @@ Error WebRTCPeerConnectionGDNative::set_default_library(const godot_net_webrtc_l WebRTCPeerConnection *WebRTCPeerConnectionGDNative::_create() { WebRTCPeerConnectionGDNative *obj = memnew(WebRTCPeerConnectionGDNative); - ERR_EXPLAIN("Default GDNative WebRTC implementation not defined."); - ERR_FAIL_COND_V(!default_library, obj); + ERR_FAIL_COND_V_MSG(!default_library, obj, "Default GDNative WebRTC implementation not defined."); // Call GDNative constructor Error err = (Error)default_library->create_peer_connection(obj); - ERR_EXPLAIN("GDNative default library constructor returned an error"); - ERR_FAIL_COND_V(err != OK, obj); + ERR_FAIL_COND_V_MSG(err != OK, obj, "GDNative default library constructor returned an error."); return obj; } diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index a9f38f1a82..58d4c52688 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -131,14 +131,12 @@ void EMWSPeer::close(int p_code, String p_reason) { IP_Address EMWSPeer::get_connected_host() const { - ERR_EXPLAIN("Not supported in HTML5 export"); - ERR_FAIL_V(IP_Address()); + ERR_FAIL_V_MSG(IP_Address(), "Not supported in HTML5 export."); }; uint16_t EMWSPeer::get_connected_port() const { - ERR_EXPLAIN("Not supported in HTML5 export"); - ERR_FAIL_V(0); + ERR_FAIL_V_MSG(0, "Not supported in HTML5 export."); }; EMWSPeer::EMWSPeer() { diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index e24cb850ec..dd86c758bf 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -98,16 +98,14 @@ void WebSocketMultiplayerPeer::_bind_methods() { // int WebSocketMultiplayerPeer::get_available_packet_count() const { - ERR_EXPLAIN("Please use get_peer(ID).get_available_packet_count to get available packet count from peers when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).get_available_packet_count to get available packet count from peers when not using the MultiplayerAPI."); return _incoming_packets.size(); } Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - ERR_EXPLAIN("Please use get_peer(ID).get_packet/var to communicate with peers when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).get_packet/var to communicate with peers when not using the MultiplayerAPI."); r_buffer_size = 0; @@ -127,8 +125,7 @@ Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buff Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - ERR_EXPLAIN("Please use get_peer(ID).put_packet/var to communicate with peers when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).put_packet/var to communicate with peers when not using the MultiplayerAPI."); PoolVector<uint8_t> buffer = _make_pkt(SYS_NONE, get_unique_id(), _target_peer, p_buffer, p_buffer_size); @@ -160,8 +157,7 @@ void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) { int WebSocketMultiplayerPeer::get_packet_peer() const { - ERR_EXPLAIN("This function is not available when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, 1); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, 1, "This function is not available when not using the MultiplayerAPI."); ERR_FAIL_COND_V(_incoming_packets.size() == 0, 1); return _incoming_packets.front()->get().source; @@ -269,7 +265,10 @@ Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, cons ERR_FAIL_COND_V(p_to == p_from, FAILED); - return get_peer(p_to)->put_packet(p_buffer, p_buffer_size); // Sending to specific peer + Ref<WebSocketPeer> peer_to = get_peer(p_to); + ERR_FAIL_COND_V(peer_to.is_null(), FAILED); + + return peer_to->put_packet(p_buffer, p_buffer_size); // Sending to specific peer } } @@ -300,8 +299,6 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u ERR_FAIL_COND(type != SYS_NONE); // Only server sends sys messages ERR_FAIL_COND(from != p_peer_id); // Someone is cheating - _server_relay(from, to, in_buffer, size); // Relay if needed - if (to == 1) { // This is for the server _store_pkt(from, to, in_buffer, data_size); @@ -316,13 +313,9 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u // All but one, for us if not excluded if (_peer_id != -(int32_t)p_peer_id) _store_pkt(from, to, in_buffer, data_size); - - } else { - - // Send to specific peer - ERR_FAIL_COND(!_peer_map.has(to)); - get_peer(to)->put_packet(in_buffer, size); } + // Relay if needed (i.e. "to" includes a peer that is not the server) + _server_relay(from, to, in_buffer, size); } else { @@ -354,8 +347,7 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u _peer_id = id; break; default: - ERR_EXPLAIN("Invalid multiplayer message"); - ERR_FAIL(); + ERR_FAIL_MSG("Invalid multiplayer message."); break; } } diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index 86374e8f80..0006a057e0 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -53,8 +53,7 @@ void WSLClient::_do_handshake() { // Header is too big disconnect_from_host(); _on_error(); - ERR_EXPLAIN("Response headers too big"); - ERR_FAIL(); + ERR_FAIL_MSG("Response headers too big."); } Error err = _connection->get_partial_data(&_resp_buf[_resp_pos], 1, read); if (err == ERR_FILE_EOF) { @@ -81,8 +80,7 @@ void WSLClient::_do_handshake() { if (!_verify_headers(protocol)) { disconnect_from_host(); _on_error(); - ERR_EXPLAIN("Invalid response headers"); - ERR_FAIL(); + ERR_FAIL_MSG("Invalid response headers."); } // Create peer. WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); @@ -103,29 +101,18 @@ bool WSLClient::_verify_headers(String &r_protocol) { String s = (char *)_resp_buf; Vector<String> psa = s.split("\r\n"); int len = psa.size(); - if (len < 4) { - ERR_EXPLAIN("Not enough response headers."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); Vector<String> req = psa[0].split(" ", false); - if (req.size() < 2) { - ERR_EXPLAIN("Invalid protocol or status code."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code."); + // Wrong protocol - if (req[0] != "HTTP/1.1" || req[1] != "101") { - ERR_EXPLAIN("Invalid protocol or status code."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(req[0] != "HTTP/1.1" || req[1] != "101", false, "Invalid protocol or status code."); Map<String, String> headers; for (int i = 1; i < len; i++) { Vector<String> header = psa[i].split(":", false, 1); - if (header.size() != 2) { - ERR_EXPLAIN("Invalid header -> " + psa[i]); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i] + "."); String name = header[0].to_lower(); String value = header[1].strip_edges(); if (headers.has(name)) @@ -134,17 +121,17 @@ bool WSLClient::_verify_headers(String &r_protocol) { headers[name] = value; } -#define _WLS_EXPLAIN(NAME, VALUE) \ - ERR_EXPLAIN("Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'"); -#define _WLS_CHECK(NAME, VALUE) \ - _WLS_EXPLAIN(NAME, VALUE); \ - ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false); -#define _WLS_CHECK_NC(NAME, VALUE) \ - _WLS_EXPLAIN(NAME, VALUE); \ - ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME] != VALUE, false); - _WLS_CHECK("connection", "upgrade"); - _WLS_CHECK("upgrade", "websocket"); - _WLS_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key)); +#define _WSL_CHECK(NAME, VALUE) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ + "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); +#define _WSL_CHECK_NC(NAME, VALUE) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME] != VALUE, false, \ + "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); + _WSL_CHECK("connection", "upgrade"); + _WSL_CHECK("upgrade", "websocket"); + _WSL_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key)); +#undef _WSL_CHECK_NC +#undef _WSL_CHECK if (_protocols.size() == 0) { // We didn't request a custom protocol ERR_FAIL_COND_V(headers.has("sec-websocket-protocol"), false); @@ -161,10 +148,6 @@ bool WSLClient::_verify_headers(String &r_protocol) { if (!valid) return false; } -#undef _WLS_CHECK_NC -#undef _WLS_CHECK -#undef _WLS_EXPLAIN - return true; } @@ -190,8 +173,8 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, Error err = _tcp->connect_to_host(addr, p_port); if (err != OK) { - _on_error(); _tcp->disconnect_from_host(); + _on_error(); return err; } _connection = _tcp; @@ -230,8 +213,8 @@ void WSLClient::poll() { if (_peer->is_connected_to_host()) { _peer->poll(); if (!_peer->is_connected_to_host()) { - _on_disconnect(_peer->close_code != -1); disconnect_from_host(); + _on_disconnect(_peer->close_code != -1); } return; } @@ -242,8 +225,8 @@ void WSLClient::poll() { switch (_tcp->get_status()) { case StreamPeerTCP::STATUS_NONE: // Clean close - _on_error(); disconnect_from_host(); + _on_error(); break; case StreamPeerTCP::STATUS_CONNECTED: { Ref<StreamPeerSSL> ssl; @@ -251,12 +234,11 @@ void WSLClient::poll() { if (_connection == _tcp) { // Start SSL handshake ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); - ERR_EXPLAIN("SSL is not available in this build"); - ERR_FAIL_COND(ssl.is_null()); + ERR_FAIL_COND_MSG(ssl.is_null(), "SSL is not available in this build."); ssl->set_blocking_handshake_enabled(false); if (ssl->connect_to_stream(_tcp, verify_ssl, _host) != OK) { - _on_error(); disconnect_from_host(); + _on_error(); return; } _connection = ssl; @@ -268,8 +250,8 @@ void WSLClient::poll() { if (ssl->get_status() == StreamPeerSSL::STATUS_HANDSHAKING) return; // Need more polling. else if (ssl->get_status() != StreamPeerSSL::STATUS_CONNECTED) { - _on_error(); disconnect_from_host(); + _on_error(); return; // Error. } } @@ -277,8 +259,8 @@ void WSLClient::poll() { _do_handshake(); } break; case StreamPeerTCP::STATUS_ERROR: - _on_error(); disconnect_from_host(); + _on_error(); break; case StreamPeerTCP::STATUS_CONNECTING: break; // Wait for connection @@ -332,8 +314,7 @@ uint16_t WSLClient::get_connected_port() const { } Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); - ERR_FAIL_COND_V(_connection.is_valid(), FAILED); + ERR_FAIL_COND_V_MSG(_connection.is_valid(), FAILED, "Buffers sizes can only be set before listening or connecting."); _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; _in_pkt_size = nearest_shift(p_in_packets - 1); diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index b11bd2b70f..74fb901232 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_peer.cpp */ +/* wsl_peer.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -35,7 +35,7 @@ #include "wsl_client.h" #include "wsl_server.h" -#include "core/math/crypto_core.h" +#include "core/crypto/crypto_core.h" #include "core/math/random_number_generator.h" #include "core/os/os.h" diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index 0d09a4d74e..efb526eed1 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_server.cpp */ +/* wsl_server.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -45,29 +45,18 @@ WSLServer::PendingPeer::PendingPeer() { bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) { Vector<String> psa = String((char *)req_buf).split("\r\n"); int len = psa.size(); - if (len < 4) { - ERR_EXPLAIN("Not enough response headers."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); Vector<String> req = psa[0].split(" ", false); - if (req.size() < 2) { - ERR_EXPLAIN("Invalid protocol or status code."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code."); + // Wrong protocol - if (req[0] != "GET" || req[2] != "HTTP/1.1") { - ERR_EXPLAIN("Invalid method or HTTP version."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", false, "Invalid method or HTTP version."); Map<String, String> headers; for (int i = 1; i < len; i++) { Vector<String> header = psa[i].split(":", false, 1); - if (header.size() != 2) { - ERR_EXPLAIN("Invalid header -> " + psa[i]); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i]); String name = header[0].to_lower(); String value = header[1].strip_edges(); if (headers.has(name)) @@ -75,18 +64,17 @@ bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) { else headers[name] = value; } -#define _WLS_CHECK(NAME, VALUE) \ - ERR_EXPLAIN("Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'"); \ - ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false); -#define _WLS_CHECK_EX(NAME) \ - ERR_EXPLAIN("Missing header '" + String(NAME) + "'."); \ - ERR_FAIL_COND_V(!headers.has(NAME), false); - _WLS_CHECK("upgrade", "websocket"); - _WLS_CHECK("sec-websocket-version", "13"); - _WLS_CHECK_EX("sec-websocket-key"); - _WLS_CHECK_EX("connection"); -#undef _WLS_CHECK_EX -#undef _WLS_CHECK +#define _WSL_CHECK(NAME, VALUE) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ + "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); +#define _WSL_CHECK_EX(NAME) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME), false, "Missing header '" + String(NAME) + "'."); + _WSL_CHECK("upgrade", "websocket"); + _WSL_CHECK("sec-websocket-version", "13"); + _WSL_CHECK_EX("sec-websocket-key"); + _WSL_CHECK_EX("connection"); +#undef _WSL_CHECK_EX +#undef _WSL_CHECK key = headers["sec-websocket-key"]; if (headers.has("sec-websocket-protocol")) { Vector<String> protos = headers["sec-websocket-protocol"].split(","); @@ -115,11 +103,7 @@ Error WSLServer::PendingPeer::do_handshake(PoolStringArray p_protocols) { if (!has_request) { int read = 0; while (true) { - if (req_pos >= WSL_MAX_HEADER_SIZE) { - // Header is too big - ERR_EXPLAIN("Response headers too big"); - ERR_FAIL_V(ERR_OUT_OF_MEMORY); - } + ERR_FAIL_COND_V_MSG(req_pos >= WSL_MAX_HEADER_SIZE, ERR_OUT_OF_MEMORY, "Response headers too big."); Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); if (err != OK) // Got an error return FAILED; @@ -277,8 +261,7 @@ void WSLServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { } Error WSLServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); - ERR_FAIL_COND_V(_server->is_listening(), FAILED); + ERR_FAIL_COND_V_MSG(_server->is_listening(), FAILED, "Buffers sizes can only be set before listening or connecting."); _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; _in_pkt_size = nearest_shift(p_in_packets - 1); diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp index c18aa04336..65b3cf08f5 100644 --- a/modules/xatlas_unwrap/register_types.cpp +++ b/modules/xatlas_unwrap/register_types.cpp @@ -29,11 +29,14 @@ /*************************************************************************/ #include "register_types.h" + #include "core/error_macros.h" + #include "thirdparty/xatlas/xatlas.h" #include <stdio.h> #include <stdlib.h> + extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, const int *p_face_materials, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y); bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, const int *p_face_materials, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y) { @@ -56,7 +59,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver xatlas::PackOptions pack_options; pack_options.maxChartSize = 4096; - pack_options.bruteForce = true; + pack_options.blockAlign = true; pack_options.texelsPerUnit = 1.0 / p_texel_size; xatlas::Atlas *atlas = xatlas::Create(); @@ -75,7 +78,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver float h = *r_size_hint_y; if (w == 0 || h == 0) { - return false; //could not bake + return false; //could not bake because there is no area } const xatlas::Mesh &output = atlas->meshes[0]; @@ -103,7 +106,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver *r_index_count = output.indexCount; - //xatlas::Destroy(atlas); + xatlas::Destroy(atlas); printf("Done\n"); return true; } |