diff options
Diffstat (limited to 'modules')
152 files changed, 6052 insertions, 2060 deletions
diff --git a/modules/arkit/SCsub b/modules/arkit/SCsub index b43d936768..e605703a72 100644 --- a/modules/arkit/SCsub +++ b/modules/arkit/SCsub @@ -5,6 +5,8 @@ Import('env_modules') env_arkit = env_modules.Clone() -# Add source files -env_arkit.add_source_files(env.modules_sources, "*.cpp") -env_arkit.add_source_files(env.modules_sources, "*.mm") +# (iOS) Build as separate static library +modules_sources = [] +env_arkit.add_source_files(modules_sources, "*.cpp") +env_arkit.add_source_files(modules_sources, "*.mm") +mod_lib = env_modules.add_library('#bin/libgodot_arkit_module' + env['LIBSUFFIX'], modules_sources)
\ No newline at end of file diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm index 9614f775a5..3408477458 100644 --- a/modules/arkit/arkit_interface.mm +++ b/modules/arkit/arkit_interface.mm @@ -28,7 +28,6 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "camera_ios.h" #include "core/os/input.h" #include "core/os/os.h" #include "scene/resources/surface_tool.h" @@ -37,6 +36,8 @@ #import <ARKit/ARKit.h> #import <UIKit/UIKit.h> +#include <dlfcn.h> + #include "arkit_interface.h" #include "arkit_session_delegate.h" @@ -53,7 +54,10 @@ void ARKitInterface::start_session() { // Ignore this if we're not initialized... if (initialized) { print_line("Starting ARKit session"); - ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfiguration new]; + + Class ARWorldTrackingConfigurationClass = NSClassFromString(@"ARWorldTrackingConfiguration"); + ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfigurationClass new]; + configuration.lightEstimationEnabled = light_estimation_is_enabled; if (plane_detection_is_enabled) { configuration.planeDetection = ARPlaneDetectionVertical | ARPlaneDetectionHorizontal; @@ -221,7 +225,17 @@ bool ARKitInterface::initialize() { print_line("initializing ARKit"); // create our ar session and delegate - ar_session = [ARSession new]; + Class ARSessionClass = NSClassFromString(@"ARSession"); + if (ARSessionClass == Nil) { + void *arkit_handle = dlopen("/System/Library/Frameworks/ARKit.framework/ARKit", RTLD_NOW); + if (arkit_handle) { + ARSessionClass = NSClassFromString(@"ARSession"); + } else { + print_line("ARKit init failed"); + return false; + } + } + ar_session = [ARSessionClass new]; ar_delegate = [ARKitSessionDelegate new]; ar_delegate.arkit_interface = this; ar_session.delegate = ar_delegate; diff --git a/modules/assimp/SCsub b/modules/assimp/SCsub index 275f1ff5e9..5e66b50de3 100644 --- a/modules/assimp/SCsub +++ b/modules/assimp/SCsub @@ -72,6 +72,9 @@ 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']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_M3D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MMD_IMPORTER']) + if(env['platform'] == 'windows'): env_assimp.Append(CPPDEFINES=['PLATFORM_WINDOWS']) diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp index 1ea9399c02..752b74b9f2 100644 --- a/modules/assimp/editor_scene_importer_assimp.cpp +++ b/modules/assimp/editor_scene_importer_assimp.cpp @@ -29,35 +29,42 @@ /*************************************************************************/ #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/editor_settings.h" #include "editor/import/resource_importer_scene.h" #include "import_utils.h" #include "scene/3d/camera.h" #include "scene/3d/light.h" #include "scene/3d/mesh_instance.h" -#include "scene/animation/animation_player.h" #include "scene/main/node.h" #include "scene/resources/material.h" #include "scene/resources/surface_tool.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> +// move 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]; + + // iterate over all the bones on the mesh for this node only! + for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) { + + aiBone *bone = mesh->mBones[boneIndex]; + if (bone->mName == bone_name) { + printf("matched bone by name: %s\n", bone->mName.C_Str()); + return bone; + } + } + } + + return NULL; +} + void EditorSceneImporterAssimp::get_extensions(List<String> *r_extensions) const { const String import_setting_string = "filesystem/import/open_asset_import/"; @@ -69,18 +76,15 @@ void EditorSceneImporterAssimp::get_extensions(List<String> *r_extensions) const ImportFormat import = { exts, true }; import_format.insert("fbx", import); } - { - Vector<String> exts; - exts.push_back("pmx"); - ImportFormat import = { exts, true }; - import_format.insert("mmd", import); - } for (Map<String, ImportFormat>::Element *E = import_format.front(); E; E = E->next()) { - _register_project_setting_import(E->key(), import_setting_string, E->get().extensions, r_extensions, E->get().is_default); + _register_project_setting_import(E->key(), import_setting_string, E->get().extensions, r_extensions, + E->get().is_default); } } -void EditorSceneImporterAssimp::_register_project_setting_import(const String generic, const String import_setting_string, const Vector<String> &exts, List<String> *r_extensions, const bool p_enabled) const { +void EditorSceneImporterAssimp::_register_project_setting_import(const String generic, const String import_setting_string, + const Vector<String> &exts, List<String> *r_extensions, + const bool p_enabled) const { const String use_generic = "use_" + generic; _GLOBAL_DEF(import_setting_string + use_generic, p_enabled, true); if (ProjectSettings::get_singleton()->get(import_setting_string + use_generic)) { @@ -97,7 +101,8 @@ uint32_t EditorSceneImporterAssimp::get_import_flags() const { void EditorSceneImporterAssimp::_bind_methods() { } -Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { +Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, + List<String> *r_missing_deps, Error *r_err) { Assimp::Importer importer; std::wstring w_path = ProjectSettings::get_singleton()->globalize_path(p_path).c_str(); std::string s_path(w_path.begin(), w_path.end()); @@ -115,9 +120,11 @@ Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_f //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_GlobalScale | + // imports models and listens to their file scale for CM to M conversions //aiProcess_FlipUVs | - aiProcess_FlipWindingOrder | // very important for culling so that it is done in the correct order. + aiProcess_FlipWindingOrder | + // very important for culling so that it is done in the correct order. //aiProcess_DropNormals | //aiProcess_GenSmoothNormals | //aiProcess_JoinIdenticalVertices | @@ -132,16 +139,18 @@ Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_f aiProcess_TransformUVCoords | aiProcess_FindInstances | //aiProcess_FixInfacingNormals | - aiProcess_ValidateDataStructure | + //aiProcess_ValidateDataStructure | aiProcess_OptimizeMeshes | + aiProcess_PopulateArmatureData | //aiProcess_OptimizeGraph | //aiProcess_Debone | // aiProcess_EmbedTextures | //aiProcess_SplitByBoneCount | 0; 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); + + ERR_FAIL_COND_V_MSG(scene == NULL, NULL, String("Open Asset Import failed to open: ") + String(importer.GetErrorString())); + return _generate_scene(p_path, scene, p_flags, p_bake_fps, max_bone_weights); } @@ -158,7 +167,8 @@ struct EditorSceneImporterAssetImportInterpolate { float t2 = t * t; float t3 = t2 * t; - return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); + return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 + + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); } T bezier(T start, T control_1, T control_2, T end, float t) { @@ -200,7 +210,8 @@ struct EditorSceneImporterAssetImportInterpolate<Quat> { }; template <class T> -T EditorSceneImporterAssimp::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, AssetImportAnimation::Interpolation p_interp) { +T EditorSceneImporterAssimp::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, + AssetImportAnimation::Interpolation p_interp) { //could use binary search, worth it? int idx = -1; for (int i = 0; i < p_times.size(); i++) { @@ -272,48 +283,251 @@ T EditorSceneImporterAssimp::_interpolate_track(const Vector<float> &p_times, co ERR_FAIL_V(p_values[0]); } -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) { +aiBone *EditorSceneImporterAssimp::get_bone_from_stack(ImportState &state, aiString name) { + List<aiBone *>::Element *iter; + aiBone *bone = NULL; + for (iter = state.bone_stack.front(); iter; iter = iter->next()) { + bone = (aiBone *)iter->get(); + + if (bone && bone->mName == name) { + state.bone_stack.erase(bone); + return bone; + } + } + + return NULL; +} + +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; state.path = p_path; state.assimp_scene = scene; state.max_bone_weights = p_max_bone_weights; - state.root = memnew(Spatial); - state.fbx = false; state.animation_player = NULL; - //fill light map cache - for (size_t l = 0; l < scene->mNumLights; l++) { + // populate light map + for (unsigned int l = 0; l < scene->mNumLights; l++) { aiLight *ai_light = scene->mLights[l]; ERR_CONTINUE(ai_light == NULL); state.light_cache[AssimpUtils::get_assimp_string(ai_light->mName)] = l; } - //fill camera cache - for (size_t c = 0; c < scene->mNumCameras; c++) { + // fill camera cache + for (unsigned int c = 0; c < scene->mNumCameras; c++) { aiCamera *ai_camera = scene->mCameras[c]; ERR_CONTINUE(ai_camera == NULL); state.camera_cache[AssimpUtils::get_assimp_string(ai_camera->mName)] = c; } if (scene->mRootNode) { + state.nodes.push_back(scene->mRootNode); - //generate nodes - for (uint32_t i = 0; i < scene->mRootNode->mNumChildren; i++) { - _generate_node(state, NULL, scene->mRootNode->mChildren[i], state.root); + // make flat node tree - in order to make processing deterministic + for (unsigned int i = 0; i < scene->mRootNode->mNumChildren; i++) { + _generate_node(state, scene->mRootNode->mChildren[i]); } - // 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(); + RegenerateBoneStack(state); + + Node *last_valid_parent = NULL; + + List<const aiNode *>::Element *iter; + for (iter = state.nodes.front(); iter; iter = iter->next()) { + const aiNode *element_assimp_node = iter->get(); + const aiNode *parent_assimp_node = element_assimp_node->mParent; + + String node_name = AssimpUtils::get_assimp_string(element_assimp_node->mName); + //print_verbose("node: " + node_name); + + Spatial *spatial = NULL; + Transform transform = AssimpUtils::assimp_matrix_transform(element_assimp_node->mTransformation); + + // retrieve this node bone + aiBone *bone = get_bone_from_stack(state, element_assimp_node->mName); + + if (state.light_cache.has(node_name)) { + spatial = create_light(state, node_name, transform); + } else if (state.camera_cache.has(node_name)) { + spatial = create_camera(state, node_name, transform); + } else if (state.armature_nodes.find(element_assimp_node)) { + // create skeleton + print_verbose("Making skeleton: " + node_name); + Skeleton *skeleton = memnew(Skeleton); + spatial = skeleton; + if (!state.armature_skeletons.has(element_assimp_node)) { + state.armature_skeletons.insert(element_assimp_node, skeleton); + } + } else if (bone != NULL) { + continue; + } else { + spatial = memnew(Spatial); + } + + ERR_CONTINUE_MSG(spatial == NULL, "FBX Import - are we out of ram?"); + // we on purpose set the transform and name after creating the node. + + spatial->set_name(node_name); + spatial->set_global_transform(transform); + + // first element is root + if (iter == state.nodes.front()) { + state.root = spatial; + } + + // flat node map parent lookup tool + state.flat_node_map.insert(element_assimp_node, spatial); + + Map<const aiNode *, Spatial *>::Element *parent_lookup = state.flat_node_map.find(parent_assimp_node); + + // note: this always fails on the root node :) keep that in mind this is by design + if (parent_lookup) { + Spatial *parent_node = parent_lookup->value(); + + ERR_FAIL_COND_V_MSG(parent_node == NULL, state.root, + "Parent node invalid even though lookup successful, out of ram?") + + if (spatial != state.root) { + parent_node->add_child(spatial); + spatial->set_owner(state.root); + } else { + // required - think about it root never has a parent yet is valid, anything else without a parent is not valid. + } + } else if (spatial != state.root) { + // if the ainode is not in the tree + // parent it to the last good parent found + if (last_valid_parent) { + last_valid_parent->add_child(spatial); + spatial->set_owner(state.root); + } else { + // this is a serious error? + memdelete(spatial); + } + } + + // update last valid parent + last_valid_parent = spatial; + } + print_verbose("node counts: " + itos(state.nodes.size())); + + // make clean bone stack + RegenerateBoneStack(state); + + print_verbose("generating godot bone data"); + + print_verbose("Godot bone stack count: " + itos(state.bone_stack.size())); + + // This is a list of bones, duplicates are from other meshes and must be dealt with properly + for (List<aiBone *>::Element *element = state.bone_stack.front(); element; element = element->next()) { + aiBone *bone = element->get(); + + ERR_CONTINUE_MSG(!bone, "invalid bone read from assimp?"); + + // Utilities for armature lookup - for now only FBX makes these + aiNode *armature_for_bone = bone->mArmature; + + // Utilities for bone node lookup - for now only FBX makes these + aiNode *bone_node = bone->mNode; + aiNode *parent_node = bone_node->mParent; + + String bone_name = AssimpUtils::get_anim_string_from_assimp(bone->mName); + ERR_CONTINUE_MSG(armature_for_bone == NULL, "Armature for bone invalid: " + bone_name); + Skeleton *skeleton = state.armature_skeletons[armature_for_bone]; + + state.skeleton_bone_map[bone] = skeleton; + + if (bone_name.empty()) { + bone_name = "untitled_bone_name"; + WARN_PRINT("Untitled bone name detected... report with file please"); + } + + // todo: this is where skin support goes + if (skeleton && skeleton->find_bone(bone_name) == -1) { + print_verbose("[Godot Glue] Imported bone" + bone_name); + int boneIdx = skeleton->get_bone_count(); + + Transform pform = AssimpUtils::assimp_matrix_transform(bone->mNode->mTransformation); + skeleton->add_bone(bone_name); + skeleton->set_bone_rest(boneIdx, pform); + skeleton->set_bone_pose(boneIdx, pform); + + if (parent_node != NULL) { + int parent_bone_id = skeleton->find_bone(AssimpUtils::get_anim_string_from_assimp(parent_node->mName)); + int current_bone_id = boneIdx; + skeleton->set_bone_parent(current_bone_id, parent_bone_id); + } + } } print_verbose("generating mesh phase from skeletal mesh"); - generate_mesh_phase_from_skeletal_mesh(state); + + List<Spatial *> cleanup_template_nodes; + + for (Map<const aiNode *, Spatial *>::Element *key_value_pair = state.flat_node_map.front(); key_value_pair; key_value_pair = key_value_pair->next()) { + const aiNode *assimp_node = key_value_pair->key(); + Spatial *mesh_template = key_value_pair->value(); + + ERR_CONTINUE(assimp_node == NULL); + ERR_CONTINUE(mesh_template == NULL); + + Node *parent_node = mesh_template->get_parent(); + + if (mesh_template == state.root) { + continue; + } + + if (parent_node == NULL) { + print_error("Found invalid parent node!"); + continue; // root node + } + + String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); + Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation); + + if (assimp_node->mNumMeshes > 0) { + MeshInstance *mesh = create_mesh(state, assimp_node, node_name, parent_node, node_transform); + if (mesh) { + + parent_node->remove_child(mesh_template); + + // re-parent children + List<Node *> children; + // re-parent all children to new node + // note: since get_child_count will change during execution we must build a list first to be safe. + for (int childId = 0; childId < mesh_template->get_child_count(); childId++) { + // get child + Node *child = mesh_template->get_child(childId); + children.push_back(child); + } + + for (List<Node *>::Element *element = children.front(); element; element = element->next()) { + // reparent the children to the real mesh node. + mesh_template->remove_child(element->get()); + mesh->add_child(element->get()); + element->get()->set_owner(state.root); + } + + // update mesh in list so that each mesh node is available + // this makes the template unavailable which is the desired behaviour + state.flat_node_map[assimp_node] = mesh; + + cleanup_template_nodes.push_back(mesh_template); + + // clean up this list we don't need it + children.clear(); + } + } + } + + for (List<Spatial *>::Element *element = cleanup_template_nodes.front(); element; element = element->next()) { + if (element->get()) { + memdelete(element->get()); + } + } } if (p_flags & IMPORT_ANIMATION && scene->mNumAnimations) { @@ -327,29 +541,39 @@ Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, aiScen } } + // + // Cleanup operations + // + + state.mesh_cache.clear(); + state.material_cache.clear(); + state.light_cache.clear(); + state.camera_cache.clear(); + state.assimp_node_map.clear(); + state.path_to_image_cache.clear(); + state.nodes.clear(); + state.flat_node_map.clear(); + state.armature_skeletons.clear(); + state.bone_stack.clear(); return state.root; } -void EditorSceneImporterAssimp::_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) { - - const aiNodeAnim *assimp_track = assimp_anim->mChannels[p_track]; +void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int track_id, + int anim_fps, Ref<Animation> animation, float ticks_per_second, + Skeleton *skeleton, const NodePath &node_path, + const String &node_name, aiBone *track_bone) { + const aiNodeAnim *assimp_track = assimp_anim->mChannels[track_id]; //make transform track int track_idx = animation->get_track_count(); animation->add_track(Animation::TYPE_TRANSFORM); - animation->track_set_path(track_idx, p_path); + animation->track_set_path(track_idx, node_path); //first determine animation length - float increment = 1.0 / float(p_bake_fps); + float increment = 1.0 / float(anim_fps); float time = 0.0; bool last = false; - int skeleton_bone = -1; - - if (p_skeleton) { - skeleton_bone = p_skeleton->find_bone(p_name); - } - Vector<Vector3> pos_values; Vector<float> pos_times; Vector<Vector3> scale_values; @@ -374,6 +598,7 @@ void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, cons scale_values.push_back(Vector3(scale.x, scale.y, scale.z)); scale_times.push_back(assimp_track->mScalingKeys[sc].mTime / ticks_per_second); } + while (true) { Vector3 pos; Quat rot; @@ -384,26 +609,34 @@ void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, cons } if (rot_values.size()) { - rot = _interpolate_track<Quat>(rot_times, rot_values, time, AssetImportAnimation::INTERP_LINEAR).normalized(); + rot = _interpolate_track<Quat>(rot_times, rot_values, time, + AssetImportAnimation::INTERP_LINEAR) + .normalized(); } if (scale_values.size()) { scale = _interpolate_track<Vector3>(scale_times, scale_values, time, AssetImportAnimation::INTERP_LINEAR); } - if (skeleton_bone >= 0) { - Transform xform; - xform.basis.set_quat_scale(rot, scale); - xform.origin = pos; + if (skeleton) { + int skeleton_bone = skeleton->find_bone(node_name); - Transform rest_xform = p_skeleton->get_bone_rest(skeleton_bone); - xform = rest_xform.affine_inverse() * xform; - rot = xform.basis.get_rotation_quat(); - scale = xform.basis.get_scale(); - pos = xform.origin; - } + if (skeleton_bone >= 0 && track_bone) { + + Transform xform; + xform.basis.set_quat_scale(rot, scale); + xform.origin = pos; - rot.normalize(); + xform = skeleton->get_bone_pose(skeleton_bone).inverse() * xform; + + rot = xform.basis.get_rotation_quat(); + rot.normalize(); + scale = xform.basis.get_scale(); + pos = xform.origin; + } else { + ERR_FAIL_MSG("Skeleton bone lookup failed for skeleton: " + skeleton->get_name()); + } + } animation->track_set_interpolation_type(track_idx, Animation::INTERPOLATION_LINEAR); animation->transform_track_insert_key(track_idx, time, pos, rot, scale); @@ -418,6 +651,53 @@ void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, cons } } +// I really do not like this but need to figure out a better way of removing it later. +Node *EditorSceneImporterAssimp::get_node_by_name(ImportState &state, String name) { + for (Map<const aiNode *, Spatial *>::Element *key_value_pair = state.flat_node_map.front(); key_value_pair; key_value_pair = key_value_pair->next()) { + const aiNode *assimp_node = key_value_pair->key(); + Spatial *node = key_value_pair->value(); + + String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); + if (name == node_name && node) { + return node; + } + } + return NULL; +} + +/* Bone stack is a fifo handler for multiple armatures since armatures aren't a thing in assimp (yet) */ +void EditorSceneImporterAssimp::RegenerateBoneStack(ImportState &state) { + + state.bone_stack.clear(); + // build bone stack list + for (unsigned int mesh_id = 0; mesh_id < state.assimp_scene->mNumMeshes; ++mesh_id) { + aiMesh *mesh = state.assimp_scene->mMeshes[mesh_id]; + + // iterate over all the bones on the mesh for this node only! + for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) { + aiBone *bone = mesh->mBones[boneIndex]; + + // doubtful this is required right now but best to check + if (!state.bone_stack.find(bone)) { + //print_verbose("[assimp] bone stack added: " + String(bone->mName.C_Str()) ); + state.bone_stack.push_back(bone); + } + } + } +} + +/* Bone stack is a fifo handler for multiple armatures since armatures aren't a thing in assimp (yet) */ +void EditorSceneImporterAssimp::RegenerateBoneStack(ImportState &state, aiMesh *mesh) { + state.bone_stack.clear(); + // iterate over all the bones on the mesh for this node only! + for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) { + aiBone *bone = mesh->mBones[boneIndex]; + if (state.bone_stack.find(bone) == NULL) { + state.bone_stack.push_back(bone); + } + } +} + // animation tracks are per bone void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_animation_index, int p_bake_fps) { @@ -429,7 +709,7 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim if (name == String()) { name = "Animation " + itos(p_animation_index + 1); } - + print_verbose("import animation: " + name); float ticks_per_second = anim->mTicksPerSecond; if (state.assimp_scene->mMetaData != NULL && Math::is_equal_approx(ticks_per_second, 0.0f)) { @@ -452,34 +732,60 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim animation->set_name(name); animation->set_length(anim->mDuration / ticks_per_second); - //regular tracks + // generate bone stack for animation import + RegenerateBoneStack(state); + //regular tracks for (size_t i = 0; i < anim->mNumChannels; i++) { const aiNodeAnim *track = anim->mChannels[i]; String node_name = AssimpUtils::get_assimp_string(track->mNodeName); - + print_verbose("track name import: " + node_name); if (track->mNumRotationKeys == 0 && track->mNumPositionKeys == 0 && track->mNumScalingKeys == 0) { continue; //do not bother } - 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(); + Skeleton *skeleton = NULL; + NodePath node_path; + aiBone *bone = NULL; - bool is_bone = skeleton->find_bone(node_name) != -1; - //print_verbose("Bone " + node_name + " is bone? " + (is_bone ? "Yes" : "No")); - NodePath node_path; - - 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); + // Import skeleton bone animation for this track + // Any bone will do, no point in processing more than just what is in the skeleton + { + bone = get_bone_from_stack(state, track->mNodeName); + + if (bone) { + // get skeleton by bone + skeleton = state.armature_skeletons[bone->mArmature]; + + if (skeleton) { + String path = state.root->get_path_to(skeleton); + path += ":" + node_name; + node_path = path; + + if (node_path != NodePath()) { + _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, + node_path, node_name, bone); + } else { + print_error("Failed to find valid node path for animation"); + } + } } + } - _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, node_path, node_name); + // not a bone + // note this is flaky it uses node names which is unreliable + Node *allocated_node = get_node_by_name(state, node_name); + // todo: implement skeleton grabbing for node based animations too :) + // check if node exists, if it does then also apply animation track for node and bones above are all handled. + // this is now inclusive animation handling so that + // we import all the data and do not miss anything. + if (allocated_node) { + node_path = state.root->get_path_to(allocated_node); + + if (node_path != NodePath()) { + _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, + node_path, node_name, nullptr); + } } } @@ -494,10 +800,9 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim ERR_CONTINUE(prop_name.split("*").size() != 2); - ERR_CONTINUE(!state.node_map.has(mesh_name)); - - const MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(state.node_map[mesh_name]); - + Node *item = get_node_by_name(state, mesh_name); + ERR_CONTINUE_MSG(!item, "failed to look up node by name"); + const MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(item); ERR_CONTINUE(mesh_instance == NULL); String base_path = state.root->get_path_to(mesh_instance); @@ -528,15 +833,13 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim state.animation_player->add_animation(name, animation); } } - // // 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) { +Ref<Mesh> +EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, + const aiNode *assimp_node, Ref<Skin> &skin, + Skeleton *&skeleton_assigned) { Ref<ArrayMesh> mesh; mesh.instance(); @@ -548,7 +851,6 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( 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++) { - 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); @@ -560,7 +862,6 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( } } } - // // Process Vertex Weights // @@ -570,19 +871,30 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( Map<uint32_t, Vector<BoneInfo> > vertex_weights; - if (p_skeleton) { + if (ai_mesh->mNumBones > 0) { for (size_t b = 0; b < ai_mesh->mNumBones; b++) { aiBone *bone = ai_mesh->mBones[b]; - 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. + if (!skeleton_assigned) { + print_verbose("Assigned mesh skeleton during mesh creation"); + skeleton_assigned = state.skeleton_bone_map[bone]; + + if (!skin.is_valid()) { + print_verbose("Configured new skin"); + skin.instance(); + } else { + print_verbose("Reusing existing skin!"); + } + } + // skeleton_assigned = + String bone_name = AssimpUtils::get_assimp_string(bone->mName); + int bone_index = skeleton_assigned->find_bone(bone_name); + ERR_CONTINUE(bone_index == -1); for (size_t w = 0; w < bone->mNumWeights; w++) { aiVertexWeight ai_weights = bone->mWeights[w]; BoneInfo bi; - uint32_t vertex_index = ai_weights.mVertexId; bi.bone = bone_index; bi.weight = ai_weights.mWeight; @@ -619,7 +931,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( // 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); + 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); } @@ -685,10 +998,11 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( if (AI_SUCCESS == ai_material->Get(AI_MATKEY_TWOSIDED, mat_two_sided)) { if (mat_two_sided > 0) { mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); + } else { + mat->set_cull_mode(SpatialMaterial::CULL_BACK); } } - 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)); @@ -697,7 +1011,7 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( // Culling handling for meshes // cull all back faces - mat->set_cull_mode(SpatialMaterial::CULL_BACK); + mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); // Now process materials aiTextureType base_color = aiTextureType_BASE_COLOR; @@ -712,7 +1026,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( 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_cull_mode( + SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode } mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, image_data.texture); @@ -731,7 +1046,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( 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_cull_mode( + SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode } mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, image_data.texture); @@ -742,7 +1058,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( 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_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)); } @@ -838,7 +1155,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( } 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 (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); @@ -981,62 +1299,27 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( return mesh; } -/* 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]; - - // iterate over all the bones on the mesh for this node only! - for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) { - - aiBone *bone = mesh->mBones[boneIndex]; - if (bone->mName == bone_name) { - printf("matched bone by name: %s\n", bone->mName.C_Str()); - return bone; - } - } - } - - return NULL; -} - /** * 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) { +MeshInstance * +EditorSceneImporterAssimp::create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *active_node, Transform node_transform) { /* MESH NODE */ Ref<Mesh> mesh; - Skeleton *skeleton = NULL; + Ref<Skin> skin; // 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); + RegenerateBoneStack(state); - 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. - } - } + // Configure indices + for (uint32_t i = 0; i < assimp_node->mNumMeshes; i++) { + int mesh_index = assimp_node->mMeshes[i]; + // create list of mesh indexes surface_indices.push_back(mesh_index); } - surface_indices.sort(); + //surface_indices.sort(); String mesh_key; for (int i = 0; i < surface_indices.size(); i++) { if (i > 0) { @@ -1045,262 +1328,154 @@ void EditorSceneImporterAssimp::create_mesh(ImportState &state, const aiNode *as mesh_key += itos(surface_indices[i]); } + Skeleton *skeleton = NULL; + aiNode *armature = NULL; + if (!state.mesh_cache.has(mesh_key)) { - mesh = _generate_mesh_from_surface_indices(state, surface_indices, assimp_node, skeleton); + mesh = _generate_mesh_from_surface_indices(state, surface_indices, assimp_node, skin, skeleton); state.mesh_cache[mesh_key] = mesh; } - //Transform transform = recursive_state.node_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(); - } - MeshInstance *mesh_node = memnew(MeshInstance); mesh = state.mesh_cache[mesh_key]; mesh_node->set_mesh(mesh); - attach_new_node(state, - mesh_node, - assimp_node, - parent_node, - node_name, - node_transform); - - // set this once and for all - if (skeleton != NULL) { - // root must be informed of its new child - parent_node->add_child(skeleton); + // if we have a valid skeleton set it up + if (skin.is_valid()) { + for (uint32_t i = 0; i < assimp_node->mNumMeshes; i++) { + unsigned int mesh_index = assimp_node->mMeshes[i]; + const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_index]; + + // please remember bone id relative to the skin is NOT the mesh relative index. + // it is the index relative to the skeleton that is why + // we have state.bone_id_map, it allows for duplicate bone id's too :) + // hope this makes sense + + int bind_count = 0; + for (unsigned int boneId = 0; boneId < ai_mesh->mNumBones; ++boneId) { + aiBone *iterBone = ai_mesh->mBones[boneId]; + + // used to reparent mesh to the correct armature later on if assigned. + if (!armature) { + print_verbose("Configured mesh armature, will reparent later to armature"); + armature = iterBone->mArmature; + } - // owner must be set after adding to tree - skeleton->set_owner(state.root); + if (skeleton) { + int id = skeleton->find_bone(AssimpUtils::get_assimp_string(iterBone->mName)); + if (id != -1) { + print_verbose("Set bind bone: mesh: " + itos(mesh_index) + " bone index: " + itos(id)); + Transform t = AssimpUtils::assimp_matrix_transform(iterBone->mOffsetMatrix); - skeleton->set_transform(node_transform); + skin->add_bind(bind_count, t); + skin->set_bind_bone(bind_count, id); + bind_count++; + } + } + } + } - // must be done after added to tree - mesh_node->set_skeleton_path(mesh_node->get_path_to(skeleton)); + print_verbose("Finished configuring bind pose for skin mesh"); } -} - -/** 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(); - - ERR_CONTINUE(assimp_node == NULL); - ERR_CONTINUE(parent_node == NULL); - String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); - Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation); + // this code parents all meshes with bones to the armature they are for + // GLTF2 specification relies on this and we are enforcing it for FBX. + if (armature && state.flat_node_map[armature]) { + Node *armature_parent = state.flat_node_map[armature]; + print_verbose("Parented mesh " + node_name + " to armature " + armature_parent->get_name()); + // static mesh handling + armature_parent->add_child(mesh_node); + // transform must be identity + mesh_node->set_global_transform(Transform()); + mesh_node->set_name(node_name); + mesh_node->set_owner(state.root); + } else { + // static mesh handling + active_node->add_child(mesh_node); + mesh_node->set_global_transform(node_transform); + mesh_node->set_name(node_name); + mesh_node->set_owner(state.root); + } - if (assimp_node->mNumMeshes > 0) { - create_mesh(state, assimp_node, node_name, current_node, parent_node, node_transform); - } + if (skeleton) { + print_verbose("Attempted to set skeleton path!"); + mesh_node->set_skeleton_path(mesh_node->get_path_to(skeleton)); + mesh_node->set_skin(skin); } -} -/** - * 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; + return mesh_node; } /** * Create a light for the scene * Automatically caches lights for lookup later */ -void EditorSceneImporterAssimp::create_light(ImportState &state, RecursiveState &recursive_state) { +Spatial *EditorSceneImporterAssimp::create_light( + ImportState &state, + const String &node_name, + Transform &look_at_transform) { Light *light = NULL; - aiLight *ai_light = state.assimp_scene->mLights[state.light_cache[recursive_state.node_name]]; - ERR_FAIL_COND(!ai_light); + aiLight *assimp_light = state.assimp_scene->mLights[state.light_cache[node_name]]; + ERR_FAIL_COND_V(!assimp_light, NULL); - if (ai_light->mType == aiLightSource_DIRECTIONAL) { + if (assimp_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) { + } else if (assimp_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; - - recursive_state.node_transform *= xform; - - light->set_transform(xform); - - //light->set_param(Light::PARAM_ATTENUATION, 1); - } else if (ai_light->mType == aiLightSource_SPOT) { + } else if (assimp_light->mType == aiLightSource_SPOT) { light = memnew(SpotLight); - - 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; - - //light->set_param(Light::PARAM_ATTENUATION, 0.0f); } - ERR_FAIL_COND(light == NULL); + ERR_FAIL_COND_V(light == NULL, NULL); + + if (assimp_light->mType != aiLightSource_POINT) { + Vector3 pos = Vector3( + assimp_light->mPosition.x, + assimp_light->mPosition.y, + assimp_light->mPosition.z); + Vector3 look_at = Vector3( + assimp_light->mDirection.y, + assimp_light->mDirection.x, + assimp_light->mDirection.z) + .normalized(); + Vector3 up = Vector3( + assimp_light->mUp.x, + assimp_light->mUp.y, + assimp_light->mUp.z); + + look_at_transform.set_look_at(pos, look_at, up); + } + // properties for light variables should be put here. + // not really hugely important yet but we will need them in the future - light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b)); - recursive_state.new_node = light; + light->set_color( + Color(assimp_light->mColorDiffuse.r, assimp_light->mColorDiffuse.g, assimp_light->mColorDiffuse.b)); - attach_new_node(state, - recursive_state.new_node, - recursive_state.assimp_node, - recursive_state.parent_node, - recursive_state.node_name, - recursive_state.node_transform); + return light; } /** * 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; +Spatial *EditorSceneImporterAssimp::create_camera( + ImportState &state, + const String &node_name, + Transform &look_at_transform) { + aiCamera *camera = state.assimp_scene->mCameras[state.camera_cache[node_name]]; + ERR_FAIL_COND_V(!camera, NULL); + + Camera *camera_node = memnew(Camera); + ERR_FAIL_COND_V(!camera_node, NULL); + float near = camera->mClipPlaneNear; if (Math::is_equal_approx(near, 0.0f)) { near = 0.1f; } - camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar); + camera_node->set_perspective(Math::rad2deg(camera->mHorizontalFOV) * 2.0f, near, camera->mClipPlaneFar); + Vector3 pos = Vector3(camera->mPosition.x, camera->mPosition.y, camera->mPosition.z); + Vector3 look_at = Vector3(camera->mLookAt.y, camera->mLookAt.x, camera->mLookAt.z).normalized(); + Vector3 up = Vector3(camera->mUp.x, camera->mUp.y, camera->mUp.z); - 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); - - Transform xform; - xform.set_look_at(pos, look_at, up); - - recursive_state.new_node = camera; - - attach_new_node(state, - recursive_state.new_node, - recursive_state.assimp_node, - recursive_state.parent_node, - recursive_state.node_name, - recursive_state.node_transform); -} - -/** - * 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); - - // 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; - } - } - - // 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); - - ERR_FAIL_COND(state.root == NULL); - ERR_FAIL_COND(recursive_state.skeleton == NULL); - - 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. - - 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"); - } - - ERR_FAIL_COND_MSG(recursive_state.skeleton == NULL, "Mesh has invalid armature detection - report this"); - - // this transform is a bone - recursive_state.skeleton->add_bone(recursive_state.node_name); - - //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); - - 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()); - - // 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); - } + look_at_transform.set_look_at(pos + look_at_transform.origin, look_at, up); + return camera_node; } /** @@ -1309,46 +1484,29 @@ void EditorSceneImporterAssimp::create_bone(ImportState &state, RecursiveState & */ void EditorSceneImporterAssimp::_generate_node( ImportState &state, - Skeleton *skeleton, - const aiNode *assimp_node, Node *parent_node) { + const aiNode *assimp_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); - - Spatial *new_node = NULL; - String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); - Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation); - - // 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); + state.nodes.push_back(assimp_node); + String parent_name = AssimpUtils::get_assimp_string(assimp_node->mParent->mName); + + // please note + // duplicate bone names exist + // this is why we only check if the bone exists + // so everything else is useless but the name + // please do not copy any other values from get_bone_by_name. + aiBone *parent_bone = get_bone_by_name(state.assimp_scene, assimp_node->mParent->mName); + aiBone *current_bone = get_bone_by_name(state.assimp_scene, assimp_node->mName); + + // is this an armature + // parent null + // and this is the first bone :) + if (parent_bone == NULL && current_bone) { + state.armature_nodes.push_back(assimp_node->mParent); + print_verbose("found valid armature: " + parent_name); } - // 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); + for (size_t i = 0; i < assimp_node->mNumChildren; i++) { + _generate_node(state, assimp_node->mChildren[i]); } } diff --git a/modules/assimp/editor_scene_importer_assimp.h b/modules/assimp/editor_scene_importer_assimp.h index 787376c9af..a47d7ac46e 100644 --- a/modules/assimp/editor_scene_importer_assimp.h +++ b/modules/assimp/editor_scene_importer_assimp.h @@ -50,6 +50,7 @@ #include <assimp/DefaultLogger.hpp> #include <assimp/LogStream.hpp> #include <assimp/Logger.hpp> +#include <map> #include "import_state.h" #include "import_utils.h" @@ -72,7 +73,6 @@ public: class EditorSceneImporterAssimp : public EditorSceneImporter { private: GDCLASS(EditorSceneImporterAssimp, EditorSceneImporter); - const String ASSIMP_FBX_KEY = "_$AssimpFbx$"; struct AssetImportAnimation { enum Interpolation { @@ -88,40 +88,32 @@ private: float weight; }; - struct SkeletonHole { //nodes may be part of the skeleton by used by vertex - String name; - String parent; - Transform pose; - const aiNode *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); + Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, + const aiNode *assimp_node, Ref<Skin> &skin, + Skeleton *&skeleton_assigned); - 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); + Spatial *create_light(ImportState &state, + const String &node_name, + Transform &look_at_transform); + Spatial *create_camera( + ImportState &state, + const String &node_name, + Transform &look_at_transform); // 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); - + MeshInstance *create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *active_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 _generate_node(ImportState &state, const aiNode *assimp_node); + void _insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int track_id, + int anim_fps, Ref<Animation> animation, float ticks_per_second, + Skeleton *skeleton, const NodePath &node_path, + const String &node_name, aiBone *track_bone); void _import_animation(ImportState &state, int p_animation_index, int p_bake_fps); - + Node *get_node_by_name(ImportState &state, String name); + aiBone *get_bone_from_stack(ImportState &state, aiString name); 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; - float _get_fbx_fps(int32_t time_mode, const aiScene *p_scene); template <class T> T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, AssetImportAnimation::Interpolation p_interp); void _register_project_setting_import(const String generic, const String import_setting_string, const Vector<String> &exts, List<String> *r_extensions, const bool p_enabled) const; @@ -148,6 +140,10 @@ public: 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); Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path); + + static void RegenerateBoneStack(ImportState &state); + + void RegenerateBoneStack(ImportState &state, aiMesh *mesh); }; #endif #endif diff --git a/modules/assimp/import_state.h b/modules/assimp/import_state.h index 56d89ffea7..9859a88c1c 100644 --- a/modules/assimp/import_state.h +++ b/modules/assimp/import_state.h @@ -52,28 +52,42 @@ namespace AssimpImporter { /** Import state is for global scene import data - * This makes the code simpler and contains useful lookups. - */ + * This makes the code simpler and contains useful lookups. + */ struct ImportState { String path; + Spatial *root; 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<const aiNode *, Node *> assimp_node_map; Map<String, Ref<Image> > path_to_image_cache; - bool fbx; //for some reason assimp does some things different for FBX + + // Generation 3 - determinisitic iteration + // to lower potential recursion errors + List<const aiNode *> nodes; + Map<const aiNode *, Spatial *> flat_node_map; AnimationPlayer *animation_player; + + // Generation 3 - deterministic armatures + // list of armature nodes - flat and simple to parse + // assimp node, node in godot + List<aiNode *> armature_nodes; + Map<const aiNode *, Skeleton *> armature_skeletons; + Map<aiBone *, Skeleton *> skeleton_bone_map; + // Generation 3 - deterministic bone handling + // bones from the stack are popped when found + // this means we can detect + // what bones are for other armatures + List<aiBone *> bone_stack; }; struct AssimpImageData { @@ -86,14 +100,15 @@ struct AssimpImageData { * This makes the code easier to handle too and add extra arguments without breaking things */ struct RecursiveState { + RecursiveState() {} // do not construct :) RecursiveState( Transform &_node_transform, Skeleton *_skeleton, Spatial *_new_node, - const String &_node_name, - const aiNode *_assimp_node, + String &_node_name, + aiNode *_assimp_node, Node *_parent_node, - const aiBone *_bone) : + aiBone *_bone) : node_transform(_node_transform), skeleton(_skeleton), new_node(_new_node), @@ -102,13 +117,13 @@ struct RecursiveState { 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; + Transform node_transform; + Skeleton *skeleton = NULL; + Spatial *new_node = NULL; + String node_name; + aiNode *assimp_node = NULL; + Node *parent_node = NULL; + aiBone *bone = NULL; }; } // namespace AssimpImporter diff --git a/modules/assimp/import_utils.h b/modules/assimp/import_utils.h index 4be76ade0f..bf7552e7db 100644 --- a/modules/assimp/import_utils.h +++ b/modules/assimp/import_utils.h @@ -309,9 +309,7 @@ public: if (r_found) { return; } - if (r_found == false) { - find_texture_path(r_p_path, dir, r_path, r_found, "." + exts[i]); - } + find_texture_path(r_p_path, dir, r_path, r_found, "." + exts[i]); } } @@ -322,9 +320,7 @@ public: 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]; + aiTextureMapMode tex_mode = map_mode[0]; int32_t flags = Texture::FLAGS_DEFAULT; if (tex_mode == aiTextureMapMode_Wrap) { @@ -369,8 +365,7 @@ public: 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>()); + ERR_FAIL_COND_V_MSG(true, Ref<Image>(), "Open Asset Import: Embedded dds not implemented"); } } else { Ref<Image> img; @@ -395,7 +390,9 @@ public: return Ref<Image>(); } else { Ref<Texture> texture = ResourceLoader::load(p_path); + ERR_FAIL_COND_V(texture.is_null(), Ref<Image>()); Ref<Image> image = texture->get_data(); + ERR_FAIL_COND_V(image.is_null(), Ref<Image>()); state.path_to_image_cache.insert(p_path, image); return image; } diff --git a/modules/bullet/btRayShape.h b/modules/bullet/btRayShape.h index 7f3229b3e8..09c1f6c241 100644 --- a/modules/bullet/btRayShape.h +++ b/modules/bullet/btRayShape.h @@ -62,7 +62,7 @@ public: virtual void setMargin(btScalar margin); - void setSlipsOnSlope(bool p_slipOnSlope); + void setSlipsOnSlope(bool p_slipsOnSlope); bool getSlipsOnSlope() const { return slipsOnSlope; } const btTransform &getSupportPoint() const { return m_cacheSupportPoint; } diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index f29c4cb9ca..d611810bfa 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -126,16 +126,16 @@ void BulletPhysicsDirectBodyState::add_torque(const Vector3 &p_torque) { body->apply_torque(p_torque); } -void BulletPhysicsDirectBodyState::apply_central_impulse(const Vector3 &p_j) { - body->apply_central_impulse(p_j); +void BulletPhysicsDirectBodyState::apply_central_impulse(const Vector3 &p_impulse) { + body->apply_central_impulse(p_impulse); } -void BulletPhysicsDirectBodyState::apply_impulse(const Vector3 &p_pos, const Vector3 &p_j) { - body->apply_impulse(p_pos, p_j); +void BulletPhysicsDirectBodyState::apply_impulse(const Vector3 &p_pos, const Vector3 &p_impulse) { + body->apply_impulse(p_pos, p_impulse); } -void BulletPhysicsDirectBodyState::apply_torque_impulse(const Vector3 &p_j) { - body->apply_torque_impulse(p_j); +void BulletPhysicsDirectBodyState::apply_torque_impulse(const Vector3 &p_impulse) { + body->apply_torque_impulse(p_impulse); } void BulletPhysicsDirectBodyState::set_sleep_state(bool p_enable) { @@ -920,7 +920,7 @@ void RigidBodyBullet::reload_space_override_modificator() { currentArea = areasWhereIam[i]; - if (PhysicsServer::AREA_SPACE_OVERRIDE_DISABLED == currentArea->get_spOv_mode()) { + if (!currentArea || PhysicsServer::AREA_SPACE_OVERRIDE_DISABLED == currentArea->get_spOv_mode()) { continue; } diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h index f63148092f..0b6dc997db 100644 --- a/modules/bullet/rigid_body_bullet.h +++ b/modules/bullet/rigid_body_bullet.h @@ -114,8 +114,8 @@ public: virtual void add_force(const Vector3 &p_force, const Vector3 &p_pos); virtual void add_torque(const Vector3 &p_torque); virtual void apply_central_impulse(const Vector3 &p_impulse); - virtual void apply_impulse(const Vector3 &p_pos, const Vector3 &p_j); - virtual void apply_torque_impulse(const Vector3 &p_j); + virtual void apply_impulse(const Vector3 &p_pos, const Vector3 &p_impulse); + virtual void apply_torque_impulse(const Vector3 &p_impulse); virtual void set_sleep_state(bool p_enable); virtual bool is_sleeping() const; diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index e74c29769f..d73930775d 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -945,8 +945,8 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f btVector3 motion; G_TO_B(p_motion, motion); - - { /// phase two - sweep test, from a secure position without margin + if (!motion.fuzzyZero()) { + // Phase two - sweep test, from a secure position without margin const int shape_count(p_body->get_shape_count()); diff --git a/modules/camera/SCsub b/modules/camera/SCsub new file mode 100644 index 0000000000..23f031f06e --- /dev/null +++ b/modules/camera/SCsub @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_camera = env_modules.Clone() + +if env["platform"] == "iphone": + # (iOS) Build as separate static library + modules_sources = [] + env_camera.add_source_files(modules_sources, "register_types.cpp") + env_camera.add_source_files(modules_sources, "camera_ios.mm") + mod_lib = env_modules.add_library('#bin/libgodot_camera_module' + env['LIBSUFFIX'], modules_sources) + +elif env["platform"] == "windows": + env_camera.add_source_files(env.modules_sources, "register_types.cpp") + env_camera.add_source_files(env.modules_sources, "camera_win.cpp") + +elif env["platform"] == "osx": + env_camera.add_source_files(env.modules_sources, "register_types.cpp") + env_camera.add_source_files(env.modules_sources, "camera_osx.mm") + diff --git a/modules/camera/camera_ios.h b/modules/camera/camera_ios.h new file mode 100644 index 0000000000..ceabdba6a3 --- /dev/null +++ b/modules/camera/camera_ios.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* camera_ios.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 CAMERAIOS_H +#define CAMERAIOS_H + +#include "servers/camera_server.h" + +class CameraIOS : public CameraServer { +private: +public: + CameraIOS(); + ~CameraIOS(); + + void update_feeds(); +}; + +#endif /* CAMERAIOS_H */
\ No newline at end of file diff --git a/modules/camera/camera_ios.mm b/modules/camera/camera_ios.mm new file mode 100644 index 0000000000..dcf09b28fd --- /dev/null +++ b/modules/camera/camera_ios.mm @@ -0,0 +1,436 @@ +/*************************************************************************/ +/* camera_ios.mm */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +///@TODO this is a near duplicate of CameraOSX, we should find a way to combine those to minimize code duplication!!!! +// If you fix something here, make sure you fix it there as wel! + +#include "camera_ios.h" +#include "servers/camera/camera_feed.h" + +#import <AVFoundation/AVFoundation.h> +#import <UIKit/UIKit.h> + +////////////////////////////////////////////////////////////////////////// +// MyCaptureSession - This is a little helper class so we can capture our frames + +@interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> { + Ref<CameraFeed> feed; + size_t width[2]; + size_t height[2]; + PoolVector<uint8_t> img_data[2]; + + AVCaptureDeviceInput *input; + AVCaptureVideoDataOutput *output; +} + +@end + +@implementation MyCaptureSession + +- (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device { + if (self = [super init]) { + NSError *error; + feed = p_feed; + width[0] = 0; + height[0] = 0; + width[1] = 0; + height[1] = 0; + + // prepare our device + [p_device lockForConfiguration:&error]; + + [p_device setFocusMode:AVCaptureFocusModeLocked]; + [p_device setExposureMode:AVCaptureExposureModeLocked]; + [p_device setWhiteBalanceMode:AVCaptureWhiteBalanceModeLocked]; + + [p_device unlockForConfiguration]; + + [self beginConfiguration]; + + // setup our capture + self.sessionPreset = AVCaptureSessionPreset1280x720; + + input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error]; + if (!input) { + print_line("Couldn't get input device for camera"); + } else { + [self addInput:input]; + } + + output = [AVCaptureVideoDataOutput new]; + if (!output) { + print_line("Couldn't get output device for camera"); + } else { + NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) }; + output.videoSettings = settings; + + // discard if the data output queue is blocked (as we process the still image) + [output setAlwaysDiscardsLateVideoFrames:YES]; + + // now set ourselves as the delegate to receive new frames. Note that we're doing this on the main thread at the moment, we may need to change this.. + [output setSampleBufferDelegate:self queue:dispatch_get_main_queue()]; + + [self addOutput:output]; + } + + [self commitConfiguration]; + + // kick off our session.. + [self startRunning]; + }; + return self; +} + +- (void)cleanup { + // stop running + [self stopRunning]; + + // cleanup + [self beginConfiguration]; + + if (input) { + [self removeInput:input]; + // don't release this + input = nil; + } + + if (output) { + [self removeOutput:output]; + [output setSampleBufferDelegate:nil queue:NULL]; + [output release]; + output = nil; + } + + [self commitConfiguration]; +} + +- (void)dealloc { + // bye bye + [super dealloc]; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { + // This gets called every time our camera has a new image for us to process. + // May need to investigate in a way to throttle this if we get more images then we're rendering frames.. + + // For now, version 1, we're just doing the bare minimum to make this work... + + CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + // int width = CVPixelBufferGetWidth(pixelBuffer); + // int height = CVPixelBufferGetHeight(pixelBuffer); + + // It says that we need to lock this on the documentation pages but it's not in the samples + // need to lock our base address so we can access our pixel buffers, better safe then sorry? + CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); + + // get our buffers + unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); + unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); + if (dataY == NULL) { + print_line("Couldn't access Y pixel buffer data"); + } else if (dataCbCr == NULL) { + print_line("Couldn't access CbCr pixel buffer data"); + } else { + UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; + Ref<Image> img[2]; + + { + // do Y + int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); + int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); + int _bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); + + if ((width[0] != new_width) || (height[0] != new_height)) { + // printf("Camera Y plane %i, %i - %i\n", new_width, new_height, bytes_per_row); + + width[0] = new_width; + height[0] = new_height; + img_data[0].resize(new_width * new_height); + } + + PoolVector<uint8_t>::Write w = img_data[0].write(); + memcpy(w.ptr(), dataY, new_width * new_height); + + img[0].instance(); + img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]); + } + + { + // do CbCr + int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); + int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); + int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); + + if ((width[1] != new_width) || (height[1] != new_height)) { + // printf("Camera CbCr plane %i, %i - %i\n", new_width, new_height, bytes_per_row); + + width[1] = new_width; + height[1] = new_height; + img_data[1].resize(2 * new_width * new_height); + } + + PoolVector<uint8_t>::Write w = img_data[1].write(); + memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height); + + ///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion + img[1].instance(); + img[1]->create(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]); + } + + // set our texture... + feed->set_YCbCr_imgs(img[0], img[1]); + + // update our matrix to match the orientation, note, before changing anything + // here, be aware that the project orientation settings must match your xcode + // settings or this will go wrong! + Transform2D display_transform; + switch (orientation) { + case UIInterfaceOrientationPortrait: { + display_transform = Transform2D(0.0, -1.0, -1.0, 0.0, 1.0, 1.0); + } break; + case UIInterfaceOrientationLandscapeRight: { + display_transform = Transform2D(1.0, 0.0, 0.0, -1.0, 0.0, 1.0); + } break; + case UIInterfaceOrientationLandscapeLeft: { + display_transform = Transform2D(-1.0, 0.0, 0.0, 1.0, 1.0, 0.0); + } break; + default: { + display_transform = Transform2D(0.0, 1.0, 1.0, 0.0, 0.0, 0.0); + } break; + } + + //TODO: this is correct for the camera on the back, I have a feeling this needs to be inversed for the camera on the front! + feed->set_transform(display_transform); + } + + // and unlock + CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); +} + +@end + +////////////////////////////////////////////////////////////////////////// +// CameraFeedIOS - Subclass for camera feeds in iOS + +class CameraFeedIOS : public CameraFeed { +private: + AVCaptureDevice *device; + MyCaptureSession *capture_session; + +public: + bool get_is_arkit() const; + AVCaptureDevice *get_device() const; + + CameraFeedIOS(); + ~CameraFeedIOS(); + + void set_device(AVCaptureDevice *p_device); + + bool activate_feed(); + void deactivate_feed(); +}; + +AVCaptureDevice *CameraFeedIOS::get_device() const { + return device; +}; + +CameraFeedIOS::CameraFeedIOS() { + capture_session = NULL; + device = NULL; + transform = Transform2D(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); /* should re-orientate this based on device orientation */ +}; + +void CameraFeedIOS::set_device(AVCaptureDevice *p_device) { + device = p_device; + [device retain]; + + // get some info + NSString *device_name = p_device.localizedName; + name = device_name.UTF8String; + position = CameraFeed::FEED_UNSPECIFIED; + if ([p_device position] == AVCaptureDevicePositionBack) { + position = CameraFeed::FEED_BACK; + } else if ([p_device position] == AVCaptureDevicePositionFront) { + position = CameraFeed::FEED_FRONT; + }; +}; + +CameraFeedIOS::~CameraFeedIOS() { + if (capture_session != NULL) { + [capture_session release]; + capture_session = NULL; + }; + + if (device != NULL) { + [device release]; + device = NULL; + }; +}; + +bool CameraFeedIOS::activate_feed() { + if (capture_session) { + // already recording! + } else { + // start camera capture + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + }; + + return true; +}; + +void CameraFeedIOS::deactivate_feed() { + // end camera capture if we have one + if (capture_session) { + [capture_session cleanup]; + [capture_session release]; + capture_session = NULL; + }; +}; + +////////////////////////////////////////////////////////////////////////// +// MyDeviceNotifications - This is a little helper class gets notifications +// when devices are connected/disconnected + +@interface MyDeviceNotifications : NSObject { + CameraIOS *camera_server; +} + +@end + +@implementation MyDeviceNotifications + +- (void)devices_changed:(NSNotification *)notification { + camera_server->update_feeds(); +} + +- (id)initForServer:(CameraIOS *)p_server { + if (self = [super init]) { + camera_server = p_server; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil]; + }; + return self; +} + +- (void)dealloc { + // remove notifications + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil]; + + [super dealloc]; +} + +@end + +MyDeviceNotifications *device_notifications = nil; + +////////////////////////////////////////////////////////////////////////// +// CameraIOS - Subclass for our camera server on iPhone + +void CameraIOS::update_feeds() { + // this way of doing things is deprecated but still works, + // rewrite to using AVCaptureDeviceDiscoverySession + + AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeBuiltInTelephotoCamera, AVCaptureDeviceTypeBuiltInDualCamera, AVCaptureDeviceTypeBuiltInTrueDepthCamera, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; + + // remove devices that are gone.. + for (int i = feeds.size() - 1; i >= 0; i--) { + Ref<CameraFeedIOS> feed(feeds[i]); + + if (feed.is_null()) { + // feed not managed by us + } else if (![session.devices containsObject:feed->get_device()]) { + // remove it from our array, this will also destroy it ;) + remove_feed(feed); + }; + }; + + // add new devices.. + for (AVCaptureDevice *device in session.devices) { + bool found = false; + + for (int i = 0; i < feeds.size() && !found; i++) { + Ref<CameraFeedIOS> feed(feeds[i]); + + if (feed.is_null()) { + // feed not managed by us + } else if (feed->get_device() == device) { + found = true; + }; + }; + + if (!found) { + Ref<CameraFeedIOS> newfeed; + newfeed.instance(); + newfeed->set_device(device); + add_feed(newfeed); + }; + }; +}; + +CameraIOS::CameraIOS() { + // check if we have our usage description + NSString *usage_desc = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSCameraUsageDescription"]; + if (usage_desc == NULL) { + // don't initialise if we don't get anything + print_line("No NSCameraUsageDescription key in pList, no access to cameras."); + return; + } else if (usage_desc.length == 0) { + // don't initialise if we don't get anything + print_line("Empty NSCameraUsageDescription key in pList, no access to cameras."); + return; + } + + // now we'll request access. + // If this is the first time the user will be prompted with the string (iOS will read it). + // Once a decision is made it is returned. If the user wants to change it later on they + // need to go into setting. + print_line("Requesting Camera permissions"); + + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo + completionHandler:^(BOOL granted) { + if (granted) { + print_line("Access to cameras granted!"); + + // Find available cameras we have at this time + update_feeds(); + + // should only have one of these.... + device_notifications = [[MyDeviceNotifications alloc] initForServer:this]; + } else { + print_line("No access to cameras!"); + } + }]; +}; + +CameraIOS::~CameraIOS() { + [device_notifications release]; +}; diff --git a/modules/mono/utils/android_utils.cpp b/modules/camera/camera_osx.h index 7dd67e3b8e..7477d8e647 100644 --- a/modules/mono/utils/android_utils.cpp +++ b/modules/camera/camera_osx.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* android_utils.cpp */ +/* camera_osx.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,41 +28,20 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "android_utils.h" +#ifndef CAMERAOSX_H +#define CAMERAOSX_H -#ifdef __ANDROID__ +///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!! +// If you fix something here, make sure you fix it there as wel! -#include "platform/android/thread_jandroid.h" +#include "servers/camera_server.h" -namespace GDMonoUtils { -namespace Android { +class CameraOSX : public CameraServer { +public: + CameraOSX(); + ~CameraOSX(); -String get_app_native_lib_dir() { - JNIEnv *env = ThreadAndroid::get_env(); + void update_feeds(); +}; - jclass activityThreadClass = env->FindClass("android/app/ActivityThread"); - jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;"); - jobject activityThread = env->CallStaticObjectMethod(activityThreadClass, currentActivityThread); - jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;"); - jobject ctx = env->CallObjectMethod(activityThread, getApplication); - - jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); - jobject applicationInfo = env->CallObjectMethod(ctx, getApplicationInfo); - jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;"); - jstring nativeLibraryDir = (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField); - - String result; - - const char *const nativeLibraryDir_utf8 = env->GetStringUTFChars(nativeLibraryDir, NULL); - if (nativeLibraryDir_utf8) { - result.parse_utf8(nativeLibraryDir_utf8); - env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDir_utf8); - } - - return result; -} - -} // namespace Android -} // namespace GDMonoUtils - -#endif // __ANDROID__ +#endif /* CAMERAOSX_H */ diff --git a/modules/camera/camera_osx.mm b/modules/camera/camera_osx.mm new file mode 100644 index 0000000000..2b0f4906fc --- /dev/null +++ b/modules/camera/camera_osx.mm @@ -0,0 +1,362 @@ +/*************************************************************************/ +/* camera_osx.mm */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!! +// If you fix something here, make sure you fix it there as wel! + +#include "camera_osx.h" +#include "servers/camera/camera_feed.h" +#import <AVFoundation/AVFoundation.h> + +////////////////////////////////////////////////////////////////////////// +// MyCaptureSession - This is a little helper class so we can capture our frames + +@interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> { + Ref<CameraFeed> feed; + size_t width[2]; + size_t height[2]; + PoolVector<uint8_t> img_data[2]; + + AVCaptureDeviceInput *input; + AVCaptureVideoDataOutput *output; +} + +@end + +@implementation MyCaptureSession + +- (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device { + if (self = [super init]) { + NSError *error; + feed = p_feed; + width[0] = 0; + height[0] = 0; + width[1] = 0; + height[1] = 0; + + [self beginConfiguration]; + + input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error]; + if (!input) { + print_line("Couldn't get input device for camera"); + } else { + [self addInput:input]; + } + + output = [AVCaptureVideoDataOutput new]; + if (!output) { + print_line("Couldn't get output device for camera"); + } else { + NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) }; + output.videoSettings = settings; + + // discard if the data output queue is blocked (as we process the still image) + [output setAlwaysDiscardsLateVideoFrames:YES]; + + // now set ourselves as the delegate to receive new frames. + [output setSampleBufferDelegate:self queue:dispatch_get_main_queue()]; + + // this takes ownership + [self addOutput:output]; + } + + [self commitConfiguration]; + + // kick off our session.. + [self startRunning]; + }; + return self; +} + +- (void)cleanup { + // stop running + [self stopRunning]; + + // cleanup + [self beginConfiguration]; + + // remove input + if (input) { + [self removeInput:input]; + // don't release this + input = NULL; + } + + // free up our output + if (output) { + [self removeOutput:output]; + [output setSampleBufferDelegate:nil queue:NULL]; + [output release]; + output = NULL; + } + + [self commitConfiguration]; +} + +- (void)dealloc { + // bye bye + [super dealloc]; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { + // This gets called every time our camera has a new image for us to process. + // May need to investigate in a way to throttle this if we get more images then we're rendering frames.. + + // For now, version 1, we're just doing the bare minimum to make this work... + CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + // int _width = CVPixelBufferGetWidth(pixelBuffer); + // int _height = CVPixelBufferGetHeight(pixelBuffer); + + // It says that we need to lock this on the documentation pages but it's not in the samples + // need to lock our base address so we can access our pixel buffers, better safe then sorry? + CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); + + // get our buffers + unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); + unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); + if (dataY == NULL) { + print_line("Couldn't access Y pixel buffer data"); + } else if (dataCbCr == NULL) { + print_line("Couldn't access CbCr pixel buffer data"); + } else { + Ref<Image> img[2]; + + { + // do Y + size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); + size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); + + if ((width[0] != new_width) || (height[0] != new_height)) { + width[0] = new_width; + height[0] = new_height; + img_data[0].resize(new_width * new_height); + } + + PoolVector<uint8_t>::Write w = img_data[0].write(); + memcpy(w.ptr(), dataY, new_width * new_height); + + img[0].instance(); + img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]); + } + + { + // do CbCr + size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); + size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); + + if ((width[1] != new_width) || (height[1] != new_height)) { + width[1] = new_width; + height[1] = new_height; + img_data[1].resize(2 * new_width * new_height); + } + + PoolVector<uint8_t>::Write w = img_data[1].write(); + memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height); + + ///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion + img[1].instance(); + img[1]->create(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]); + } + + // set our texture... + feed->set_YCbCr_imgs(img[0], img[1]); + } + + // and unlock + CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); +} + +@end + +////////////////////////////////////////////////////////////////////////// +// CameraFeedOSX - Subclass for camera feeds in OSX + +class CameraFeedOSX : public CameraFeed { +private: + AVCaptureDevice *device; + MyCaptureSession *capture_session; + +public: + AVCaptureDevice *get_device() const; + + CameraFeedOSX(); + ~CameraFeedOSX(); + + void set_device(AVCaptureDevice *p_device); + + bool activate_feed(); + void deactivate_feed(); +}; + +AVCaptureDevice *CameraFeedOSX::get_device() const { + return device; +}; + +CameraFeedOSX::CameraFeedOSX() { + device = NULL; + capture_session = NULL; +}; + +void CameraFeedOSX::set_device(AVCaptureDevice *p_device) { + device = p_device; + [device retain]; + + // get some info + NSString *device_name = p_device.localizedName; + name = device_name.UTF8String; + position = CameraFeed::FEED_UNSPECIFIED; + if ([p_device position] == AVCaptureDevicePositionBack) { + position = CameraFeed::FEED_BACK; + } else if ([p_device position] == AVCaptureDevicePositionFront) { + position = CameraFeed::FEED_FRONT; + }; +}; + +CameraFeedOSX::~CameraFeedOSX() { + if (capture_session != NULL) { + [capture_session release]; + capture_session = NULL; + }; + + if (device != NULL) { + [device release]; + device = NULL; + }; +}; + +bool CameraFeedOSX::activate_feed() { + if (capture_session) { + // already recording! + } else { + // start camera capture + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + }; + + return true; +}; + +void CameraFeedOSX::deactivate_feed() { + // end camera capture if we have one + if (capture_session) { + [capture_session cleanup]; + [capture_session release]; + capture_session = NULL; + }; +}; + +////////////////////////////////////////////////////////////////////////// +// MyDeviceNotifications - This is a little helper class gets notifications +// when devices are connected/disconnected + +@interface MyDeviceNotifications : NSObject { + CameraOSX *camera_server; +} + +@end + +@implementation MyDeviceNotifications + +- (void)devices_changed:(NSNotification *)notification { + camera_server->update_feeds(); +} + +- (id)initForServer:(CameraOSX *)p_server { + if (self = [super init]) { + camera_server = p_server; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil]; + }; + return self; +} + +- (void)dealloc { + // remove notifications + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil]; + + [super dealloc]; +} + +@end + +MyDeviceNotifications *device_notifications = nil; + +////////////////////////////////////////////////////////////////////////// +// CameraOSX - Subclass for our camera server on OSX + +void CameraOSX::update_feeds() { + NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + + // remove devices that are gone.. + for (int i = feeds.size() - 1; i >= 0; i--) { + Ref<CameraFeedOSX> feed = (Ref<CameraFeedOSX>)feeds[i]; + + if (![devices containsObject:feed->get_device()]) { + // remove it from our array, this will also destroy it ;) + remove_feed(feed); + }; + }; + + // add new devices.. + for (AVCaptureDevice *device in devices) { + bool found = false; + for (int i = 0; i < feeds.size() && !found; i++) { + Ref<CameraFeedOSX> feed = (Ref<CameraFeedOSX>)feeds[i]; + if (feed->get_device() == device) { + found = true; + }; + }; + + if (!found) { + Ref<CameraFeedOSX> newfeed; + newfeed.instance(); + newfeed->set_device(device); + + // assume display camera so inverse + Transform2D transform = Transform2D(-1.0, 0.0, 0.0, -1.0, 1.0, 1.0); + newfeed->set_transform(transform); + + add_feed(newfeed); + }; + }; +}; + +CameraOSX::CameraOSX() { + // Find available cameras we have at this time + update_feeds(); + + // should only have one of these.... + device_notifications = [[MyDeviceNotifications alloc] initForServer:this]; +}; + +CameraOSX::~CameraOSX() { + [device_notifications release]; +}; diff --git a/modules/camera/camera_win.cpp b/modules/camera/camera_win.cpp new file mode 100644 index 0000000000..10787d0d0a --- /dev/null +++ b/modules/camera/camera_win.cpp @@ -0,0 +1,98 @@ +/*************************************************************************/ +/* camera_win.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 "camera_win.h" + +///@TODO sorry guys, I got about 80% through implementing this using DirectShow only +// to find out Microsoft deprecated half the API and its replacement is as confusing +// as they could make it. Joey suggested looking into libuvc which offers a more direct +// route to webcams over USB and this is very promising but it wouldn't compile on +// windows for me...I've gutted the classes I implemented DirectShow in just to have +// a skeleton for someone to work on, mail me for more details or if you want a copy.... + +////////////////////////////////////////////////////////////////////////// +// CameraFeedWindows - Subclass for our camera feed on windows + +/// @TODO need to implement this + +class CameraFeedWindows : public CameraFeed { +private: +protected: +public: + CameraFeedWindows(); + virtual ~CameraFeedWindows(); + + bool activate_feed(); + void deactivate_feed(); +}; + +CameraFeedWindows::CameraFeedWindows(){ + ///@TODO implement this, should store information about our available camera +}; + +CameraFeedWindows::~CameraFeedWindows() { + // make sure we stop recording if we are! + if (is_active()) { + deactivate_feed(); + }; + + ///@TODO free up anything used by this +}; + +bool CameraFeedWindows::activate_feed() { + ///@TODO this should activate our camera and start the process of capturing frames + + return true; +}; + +///@TODO we should probably have a callback method here that is being called by the +// camera API which provides frames and call back into the CameraServer to update our texture + +void CameraFeedWindows::deactivate_feed(){ + ///@TODO this should deactivate our camera and stop the process of capturing frames +}; + +////////////////////////////////////////////////////////////////////////// +// CameraWindows - Subclass for our camera server on windows + +void CameraWindows::add_active_cameras(){ + ///@TODO scan through any active cameras and create CameraFeedWindows objects for them +}; + +CameraWindows::CameraWindows() { + // Find cameras active right now + add_active_cameras(); + + // need to add something that will react to devices being connected/removed... +}; + +CameraWindows::~CameraWindows(){ + +}; diff --git a/modules/camera/camera_win.h b/modules/camera/camera_win.h new file mode 100644 index 0000000000..22ce9aa43f --- /dev/null +++ b/modules/camera/camera_win.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* camera_win.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 CAMERAWIN_H +#define CAMERAWIN_H + +#include "servers/camera/camera_feed.h" +#include "servers/camera_server.h" + +class CameraWindows : public CameraServer { +private: + void add_active_cameras(); + +public: + CameraWindows(); + ~CameraWindows(); +}; + +#endif /* CAMERAWIN_H */ diff --git a/modules/camera/config.py b/modules/camera/config.py new file mode 100644 index 0000000000..d308c04195 --- /dev/null +++ b/modules/camera/config.py @@ -0,0 +1,5 @@ +def can_build(env, platform): + return platform == 'iphone' or platform == 'osx' or platform == 'windows' + +def configure(env): + pass diff --git a/modules/camera/register_types.cpp b/modules/camera/register_types.cpp new file mode 100644 index 0000000000..313df40112 --- /dev/null +++ b/modules/camera/register_types.cpp @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* 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" + +#if defined(WINDOWS_ENABLED) +#include "camera_win.h" +#endif +#if defined(IPHONE_ENABLED) +#include "camera_ios.h" +#endif +#if defined(OSX_ENABLED) +#include "camera_osx.h" +#endif + +void register_camera_types() { +#if defined(WINDOWS_ENABLED) + CameraServer::make_default<CameraWindows>(); +#endif +#if defined(IPHONE_ENABLED) + CameraServer::make_default<CameraIOS>(); +#endif +#if defined(OSX_ENABLED) + CameraServer::make_default<CameraOSX>(); +#endif +} + +void unregister_camera_types() { +} diff --git a/modules/camera/register_types.h b/modules/camera/register_types.h new file mode 100644 index 0000000000..0ccb0885d0 --- /dev/null +++ b/modules/camera/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_camera_types(); +void unregister_camera_types(); diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp index 5a76f32977..925fff0cc8 100644 --- a/modules/csg/csg.cpp +++ b/modules/csg/csg.cpp @@ -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 (segment[j] == points[i].point) { + if (segment[j].is_equal_approx(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 (closest == segment[j]) { + if (closest.is_equal_approx(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 (segment[0] == segment[1]) + if (segment[0].is_equal_approx(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 (va[0] == va[1] || va[0] == va[2] || va[1] == va[2]) + if (va[0].is_equal_approx(va[1]) || va[0].is_equal_approx(va[2]) || va[1].is_equal_approx(va[2])) return; - if (vb[0] == vb[1] || vb[0] == vb[2] || vb[1] == vb[2]) + if (vb[0].is_equal_approx(vb[1]) || vb[0].is_equal_approx(vb[2]) || vb[1].is_equal_approx(vb[2])) return; } diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml index 4c10588aa6..78a8e94012 100644 --- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml +++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml @@ -116,6 +116,9 @@ The compression method used for network packets. These have different tradeoffs of compression speed versus bandwidth, you may need to test which one works best for your use case if you use compression at all. </member> <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" override="true" default="false" /> + <member name="server_relay" type="bool" setter="set_server_relay_enabled" getter="is_server_relay_enabled" default="true"> + Enable or disable the server feature that notifies clients of other peers' connection/disconnection, and relays messages between them. When this option is [code]false[/code], clients won't be automatically notified of other peers and won't be able to send them packets through the server. + </member> <member name="transfer_channel" type="int" setter="set_transfer_channel" getter="get_transfer_channel" default="-1"> Set the default channel to be used to transfer data. By default, this value is [code]-1[/code] which means that ENet will only use 2 channels, one for reliable and one for unreliable packets. Channel [code]0[/code] is reserved, and cannot be used. Setting this member to any value between [code]0[/code] and [member channel_count] (excluded) will force ENet to use that channel for sending data. </member> diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp index a787cd3b80..2f5307d041 100644 --- a/modules/enet/networked_multiplayer_enet.cpp +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -255,6 +255,10 @@ void NetworkedMultiplayerENet::poll() { emit_signal("peer_connected", *new_id); if (server) { + // Do not notify other peers when server_relay is disabled. + if (!server_relay) + break; + // Someone connected, notify all the peers available for (Map<int, ENetPeer *>::Element *E = peer_map.front(); E; E = E->next()) { @@ -287,31 +291,34 @@ void NetworkedMultiplayerENet::poll() { if (!server) { emit_signal("connection_failed"); } - } else { + // Never fully connected. + break; + } - if (server) { - // Someone disconnected, notify everyone else - for (Map<int, ENetPeer *>::Element *E = peer_map.front(); E; E = E->next()) { + if (!server) { - if (E->key() == *id) - continue; + // Client just disconnected from server. + emit_signal("server_disconnected"); + close_connection(); + return; + } else if (server_relay) { - ENetPacket *packet = enet_packet_create(NULL, 8, ENET_PACKET_FLAG_RELIABLE); - encode_uint32(SYSMSG_REMOVE_PEER, &packet->data[0]); - encode_uint32(*id, &packet->data[4]); - enet_peer_send(E->get(), SYSCH_CONFIG, packet); - } - } else { - emit_signal("server_disconnected"); - close_connection(); - return; - } + // Server just received a client disconnect and is in relay mode, notify everyone else. + for (Map<int, ENetPeer *>::Element *E = peer_map.front(); E; E = E->next()) { + + if (E->key() == *id) + continue; - emit_signal("peer_disconnected", *id); - peer_map.erase(*id); - memdelete(id); + ENetPacket *packet = enet_packet_create(NULL, 8, ENET_PACKET_FLAG_RELIABLE); + encode_uint32(SYSMSG_REMOVE_PEER, &packet->data[0]); + encode_uint32(*id, &packet->data[4]); + enet_peer_send(E->get(), SYSCH_CONFIG, packet); + } } + emit_signal("peer_disconnected", *id); + peer_map.erase(*id); + memdelete(id); } break; case ENET_EVENT_TYPE_RECEIVE: { @@ -361,7 +368,13 @@ void NetworkedMultiplayerENet::poll() { packet.from = *id; - if (target == 0) { + if (target == 1) { + // To myself and only myself + incoming_packets.push_back(packet); + } else if (!server_relay) { + // No other destination is allowed when server is not relaying + continue; + } else if (target == 0) { // Re-send to everyone but sender :| incoming_packets.push_back(packet); @@ -398,9 +411,6 @@ void NetworkedMultiplayerENet::poll() { enet_packet_destroy(packet.packet); } - } else if (target == 1) { - // To myself and only myself - incoming_packets.push_back(packet); } else { // To someone else, specifically ERR_CONTINUE(!peer_map.has(target)); @@ -440,6 +450,8 @@ void NetworkedMultiplayerENet::close_connection(uint32_t wait_usec) { for (Map<int, ENetPeer *>::Element *E = peer_map.front(); E; E = E->next()) { if (E->get()) { enet_peer_disconnect_now(E->get(), unique_id); + int *id = (int *)(E->get()->data); + memdelete(id); peers_disconnected = true; } } @@ -455,6 +467,7 @@ void NetworkedMultiplayerENet::close_connection(uint32_t wait_usec) { enet_host_destroy(host); active = false; incoming_packets.clear(); + peer_map.clear(); unique_id = 1; // Server is 1 connection_status = CONNECTION_DISCONNECTED; } @@ -471,10 +484,13 @@ void NetworkedMultiplayerENet::disconnect_peer(int p_peer, bool now) { // enet_peer_disconnect_now doesn't generate ENET_EVENT_TYPE_DISCONNECT, // notify everyone else, send disconnect signal & remove from peer_map like in poll() + int *id = NULL; for (Map<int, ENetPeer *>::Element *E = peer_map.front(); E; E = E->next()) { - if (E->key() == p_peer) + if (E->key() == p_peer) { + id = (int *)(E->get()->data); continue; + } ENetPacket *packet = enet_packet_create(NULL, 8, ENET_PACKET_FLAG_RELIABLE); encode_uint32(SYSMSG_REMOVE_PEER, &packet->data[0]); @@ -482,6 +498,9 @@ void NetworkedMultiplayerENet::disconnect_peer(int p_peer, bool now) { enet_peer_send(E->get(), SYSCH_CONFIG, packet); } + if (id) + memdelete(id); + emit_signal("peer_disconnected", p_peer); peer_map.erase(p_peer); } else { @@ -818,6 +837,16 @@ bool NetworkedMultiplayerENet::is_always_ordered() const { return always_ordered; } +void NetworkedMultiplayerENet::set_server_relay_enabled(bool p_enabled) { + ERR_FAIL_COND(active); + + server_relay = p_enabled; +} + +bool NetworkedMultiplayerENet::is_server_relay_enabled() const { + return server_relay; +} + void NetworkedMultiplayerENet::_bind_methods() { ClassDB::bind_method(D_METHOD("create_server", "port", "max_clients", "in_bandwidth", "out_bandwidth"), &NetworkedMultiplayerENet::create_server, DEFVAL(32), DEFVAL(0), DEFVAL(0)); @@ -838,11 +867,14 @@ void NetworkedMultiplayerENet::_bind_methods() { ClassDB::bind_method(D_METHOD("get_channel_count"), &NetworkedMultiplayerENet::get_channel_count); ClassDB::bind_method(D_METHOD("set_always_ordered", "ordered"), &NetworkedMultiplayerENet::set_always_ordered); ClassDB::bind_method(D_METHOD("is_always_ordered"), &NetworkedMultiplayerENet::is_always_ordered); + ClassDB::bind_method(D_METHOD("set_server_relay_enabled", "enabled"), &NetworkedMultiplayerENet::set_server_relay_enabled); + ClassDB::bind_method(D_METHOD("is_server_relay_enabled"), &NetworkedMultiplayerENet::is_server_relay_enabled); ADD_PROPERTY(PropertyInfo(Variant::INT, "compression_mode", PROPERTY_HINT_ENUM, "None,Range Coder,FastLZ,ZLib,ZStd"), "set_compression_mode", "get_compression_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel"), "set_transfer_channel", "get_transfer_channel"); ADD_PROPERTY(PropertyInfo(Variant::INT, "channel_count"), "set_channel_count", "get_channel_count"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "always_ordered"), "set_always_ordered", "is_always_ordered"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_relay"), "set_server_relay_enabled", "is_server_relay_enabled"); BIND_ENUM_CONSTANT(COMPRESS_NONE); BIND_ENUM_CONSTANT(COMPRESS_RANGE_CODER); @@ -856,6 +888,7 @@ NetworkedMultiplayerENet::NetworkedMultiplayerENet() { active = false; server = false; refuse_connections = false; + server_relay = true; unique_id = 0; target_peer = 0; current_packet.packet = NULL; diff --git a/modules/enet/networked_multiplayer_enet.h b/modules/enet/networked_multiplayer_enet.h index 8dcb202314..1c4c15ae7b 100644 --- a/modules/enet/networked_multiplayer_enet.h +++ b/modules/enet/networked_multiplayer_enet.h @@ -78,6 +78,7 @@ private: ENetHost *host; bool refuse_connections; + bool server_relay; ConnectionStatus connection_status; @@ -158,6 +159,8 @@ public: int get_channel_count() const; void set_always_ordered(bool p_ordered); bool is_always_ordered() const; + void set_server_relay_enabled(bool p_enabled); + bool is_server_relay_enabled() const; NetworkedMultiplayerENet(); ~NetworkedMultiplayerENet(); diff --git a/modules/etc/image_etc.cpp b/modules/etc/image_etc.cpp index 6f54436bf9..f0cbf6ae28 100644 --- a/modules/etc/image_etc.cpp +++ b/modules/etc/image_etc.cpp @@ -168,6 +168,7 @@ static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_f } PoolVector<uint8_t>::Read r = img->get_data().read(); + ERR_FAIL_COND(!r.ptr()); unsigned int target_size = Image::get_image_data_size(imgw, imgh, etc_format, p_img->has_mipmaps()); int mmc = 1 + (p_img->has_mipmaps() ? Image::get_image_required_mipmaps(imgw, imgh, etc_format) : 0); diff --git a/modules/etc/texture_loader_pkm.cpp b/modules/etc/texture_loader_pkm.cpp index 4d8af6883f..da6da74025 100644 --- a/modules/etc/texture_loader_pkm.cpp +++ b/modules/etc/texture_loader_pkm.cpp @@ -91,6 +91,8 @@ RES ResourceFormatPKM::load(const String &p_path, const String &p_original_path, if (r_error) *r_error = OK; + f->close(); + memdelete(f); return texture; } diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub index b47377cbc4..8f4a8de895 100644 --- a/modules/freetype/SCsub +++ b/modules/freetype/SCsub @@ -66,7 +66,7 @@ if env['builtin_freetype']: env.Prepend(CPPPATH=[thirdparty_dir + "/include"]) env_freetype.Append(CPPDEFINES=['FT2_BUILD_LIBRARY', 'FT_CONFIG_OPTION_USE_PNG']) - if (env['target'] != 'release'): + if (env['target'] == 'debug'): env_freetype.Append(CPPDEFINES=['ZLIB_DEBUG']) # Also requires libpng headers diff --git a/modules/gdnative/gdnative/pool_arrays.cpp b/modules/gdnative/gdnative/pool_arrays.cpp index 74c540ca14..23791af67e 100644 --- a/modules/gdnative/gdnative/pool_arrays.cpp +++ b/modules/gdnative/gdnative/pool_arrays.cpp @@ -129,6 +129,11 @@ godot_int GDAPI godot_pool_byte_array_size(const godot_pool_byte_array *p_self) return self->size(); } +godot_bool GDAPI godot_pool_byte_array_empty(const godot_pool_byte_array *p_self) { + const PoolVector<uint8_t> *self = (const PoolVector<uint8_t> *)p_self; + return self->empty(); +} + void GDAPI godot_pool_byte_array_destroy(godot_pool_byte_array *p_self) { ((PoolVector<uint8_t> *)p_self)->~PoolVector(); } @@ -218,6 +223,11 @@ godot_int GDAPI godot_pool_int_array_size(const godot_pool_int_array *p_self) { return self->size(); } +godot_bool GDAPI godot_pool_int_array_empty(const godot_pool_int_array *p_self) { + const PoolVector<godot_int> *self = (const PoolVector<godot_int> *)p_self; + return self->empty(); +} + void GDAPI godot_pool_int_array_destroy(godot_pool_int_array *p_self) { ((PoolVector<godot_int> *)p_self)->~PoolVector(); } @@ -307,6 +317,11 @@ godot_int GDAPI godot_pool_real_array_size(const godot_pool_real_array *p_self) return self->size(); } +godot_bool GDAPI godot_pool_real_array_empty(const godot_pool_real_array *p_self) { + const PoolVector<godot_real> *self = (const PoolVector<godot_real> *)p_self; + return self->empty(); +} + void GDAPI godot_pool_real_array_destroy(godot_pool_real_array *p_self) { ((PoolVector<godot_real> *)p_self)->~PoolVector(); } @@ -404,6 +419,11 @@ godot_int GDAPI godot_pool_string_array_size(const godot_pool_string_array *p_se return self->size(); } +godot_bool GDAPI godot_pool_string_array_empty(const godot_pool_string_array *p_self) { + const PoolVector<String> *self = (const PoolVector<String> *)p_self; + return self->empty(); +} + void GDAPI godot_pool_string_array_destroy(godot_pool_string_array *p_self) { ((PoolVector<String> *)p_self)->~PoolVector(); } @@ -500,6 +520,11 @@ godot_int GDAPI godot_pool_vector2_array_size(const godot_pool_vector2_array *p_ return self->size(); } +godot_bool GDAPI godot_pool_vector2_array_empty(const godot_pool_vector2_array *p_self) { + const PoolVector<Vector2> *self = (const PoolVector<Vector2> *)p_self; + return self->empty(); +} + void GDAPI godot_pool_vector2_array_destroy(godot_pool_vector2_array *p_self) { ((PoolVector<Vector2> *)p_self)->~PoolVector(); } @@ -596,6 +621,11 @@ godot_int GDAPI godot_pool_vector3_array_size(const godot_pool_vector3_array *p_ return self->size(); } +godot_bool GDAPI godot_pool_vector3_array_empty(const godot_pool_vector3_array *p_self) { + const PoolVector<Vector3> *self = (const PoolVector<Vector3> *)p_self; + return self->empty(); +} + void GDAPI godot_pool_vector3_array_destroy(godot_pool_vector3_array *p_self) { ((PoolVector<Vector3> *)p_self)->~PoolVector(); } @@ -692,6 +722,11 @@ godot_int GDAPI godot_pool_color_array_size(const godot_pool_color_array *p_self return self->size(); } +godot_bool GDAPI godot_pool_color_array_empty(const godot_pool_color_array *p_self) { + const PoolVector<Color> *self = (const PoolVector<Color> *)p_self; + return self->empty(); +} + void GDAPI godot_pool_color_array_destroy(godot_pool_color_array *p_self) { ((PoolVector<Color> *)p_self)->~PoolVector(); } diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 55ba4ecc1e..9e5295a936 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -91,6 +91,55 @@ ["const godot_int", "p_step"], ["const godot_bool", "p_deep"] ] + }, + { + "name": "godot_pool_byte_array_empty", + "return_type": "godot_bool", + "arguments": [ + ["const godot_pool_byte_array *", "p_self"] + ] + }, + { + "name": "godot_pool_int_array_empty", + "return_type": "godot_bool", + "arguments": [ + ["const godot_pool_int_array *", "p_self"] + ] + }, + { + "name": "godot_pool_real_array_empty", + "return_type": "godot_bool", + "arguments": [ + ["const godot_pool_real_array *", "p_self"] + ] + }, + { + "name": "godot_pool_string_array_empty", + "return_type": "godot_bool", + "arguments": [ + ["const godot_pool_string_array *", "p_self"] + ] + }, + { + "name": "godot_pool_vector2_array_empty", + "return_type": "godot_bool", + "arguments": [ + ["const godot_pool_vector2_array *", "p_self"] + ] + }, + { + "name": "godot_pool_vector3_array_empty", + "return_type": "godot_bool", + "arguments": [ + ["const godot_pool_vector3_array *", "p_self"] + ] + }, + { + "name": "godot_pool_color_array_empty", + "return_type": "godot_bool", + "arguments": [ + ["const godot_pool_color_array *", "p_self"] + ] } ] }, diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index 5d272a6cdc..5c84222797 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -224,7 +224,6 @@ void GDNativeLibraryEditor::_erase_entry(const String &platform, const String &e if (List<String>::Element *E = platforms[platform].entries.find(entry)) { String target = platform + "." + entry; - Ref<ConfigFile> config = library->get_config_file(); platforms[platform].entries.erase(E); _set_target_value("entry", target, ""); diff --git a/modules/gdnative/include/gdnative/array.h b/modules/gdnative/include/gdnative/array.h index 2e3ce58033..a27626325e 100644 --- a/modules/gdnative/include/gdnative/array.h +++ b/modules/gdnative/include/gdnative/array.h @@ -132,7 +132,7 @@ void GDAPI godot_array_destroy(godot_array *p_self); godot_array GDAPI godot_array_duplicate(const godot_array *p_self, const godot_bool p_deep); -godot_array GDAPI godot_array_slice(const godot_array *p_self, const godot_int p_begin, const godot_int p_end, const godot_int p_delta, const godot_bool p_deep); +godot_array GDAPI godot_array_slice(const godot_array *p_self, const godot_int p_begin, const godot_int p_end, const godot_int p_step, const godot_bool p_deep); godot_variant GDAPI godot_array_max(const godot_array *p_self); diff --git a/modules/gdnative/include/gdnative/pool_arrays.h b/modules/gdnative/include/gdnative/pool_arrays.h index 96730ab085..63e8267f0e 100644 --- a/modules/gdnative/include/gdnative/pool_arrays.h +++ b/modules/gdnative/include/gdnative/pool_arrays.h @@ -191,6 +191,8 @@ uint8_t GDAPI godot_pool_byte_array_get(const godot_pool_byte_array *p_self, con godot_int GDAPI godot_pool_byte_array_size(const godot_pool_byte_array *p_self); +godot_bool GDAPI godot_pool_byte_array_empty(const godot_pool_byte_array *p_self); + void GDAPI godot_pool_byte_array_destroy(godot_pool_byte_array *p_self); // int @@ -222,6 +224,8 @@ godot_int GDAPI godot_pool_int_array_get(const godot_pool_int_array *p_self, con godot_int GDAPI godot_pool_int_array_size(const godot_pool_int_array *p_self); +godot_bool GDAPI godot_pool_int_array_empty(const godot_pool_int_array *p_self); + void GDAPI godot_pool_int_array_destroy(godot_pool_int_array *p_self); // real @@ -253,6 +257,8 @@ godot_real GDAPI godot_pool_real_array_get(const godot_pool_real_array *p_self, godot_int GDAPI godot_pool_real_array_size(const godot_pool_real_array *p_self); +godot_bool GDAPI godot_pool_real_array_empty(const godot_pool_real_array *p_self); + void GDAPI godot_pool_real_array_destroy(godot_pool_real_array *p_self); // string @@ -284,6 +290,8 @@ godot_string GDAPI godot_pool_string_array_get(const godot_pool_string_array *p_ godot_int GDAPI godot_pool_string_array_size(const godot_pool_string_array *p_self); +godot_bool GDAPI godot_pool_string_array_empty(const godot_pool_string_array *p_self); + void GDAPI godot_pool_string_array_destroy(godot_pool_string_array *p_self); // vector2 @@ -315,6 +323,8 @@ godot_vector2 GDAPI godot_pool_vector2_array_get(const godot_pool_vector2_array godot_int GDAPI godot_pool_vector2_array_size(const godot_pool_vector2_array *p_self); +godot_bool GDAPI godot_pool_vector2_array_empty(const godot_pool_vector2_array *p_self); + void GDAPI godot_pool_vector2_array_destroy(godot_pool_vector2_array *p_self); // vector3 @@ -346,6 +356,8 @@ godot_vector3 GDAPI godot_pool_vector3_array_get(const godot_pool_vector3_array godot_int GDAPI godot_pool_vector3_array_size(const godot_pool_vector3_array *p_self); +godot_bool GDAPI godot_pool_vector3_array_empty(const godot_pool_vector3_array *p_self); + void GDAPI godot_pool_vector3_array_destroy(godot_pool_vector3_array *p_self); // color @@ -377,6 +389,8 @@ godot_color GDAPI godot_pool_color_array_get(const godot_pool_color_array *p_sel godot_int GDAPI godot_pool_color_array_size(const godot_pool_color_array *p_self); +godot_bool GDAPI godot_pool_color_array_empty(const godot_pool_color_array *p_self); + void GDAPI godot_pool_color_array_destroy(godot_pool_color_array *p_self); // diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp index e0cf990f83..eace195c33 100644 --- a/modules/gdnative/nativescript/api_generator.cpp +++ b/modules/gdnative/nativescript/api_generator.cpp @@ -108,6 +108,7 @@ struct ClassAPI { ClassDB::APIType api_type; bool is_singleton; + String singleton_name; bool is_instanciable; // @Unclear bool is_reference; @@ -183,6 +184,7 @@ List<ClassAPI> generate_c_api_classes() { global_constants_api.class_name = L"GlobalConstants"; global_constants_api.api_type = ClassDB::API_CORE; global_constants_api.is_singleton = true; + global_constants_api.singleton_name = L"GlobalConstants"; global_constants_api.is_instanciable = false; const int constants_count = GlobalConstants::get_global_constant_count(); for (int i = 0; i < constants_count; ++i) { @@ -208,6 +210,9 @@ List<ClassAPI> generate_c_api_classes() { name.remove(0); } class_api.is_singleton = Engine::get_singleton()->has_singleton(name); + if (class_api.is_singleton) { + class_api.singleton_name = name; + } } class_api.is_instanciable = !class_api.is_singleton && ClassDB::can_instance(class_name); @@ -421,6 +426,7 @@ static List<String> generate_c_api_json(const List<ClassAPI> &p_api) { source.push_back("\t\t\"base_class\": \"" + api.super_class_name + "\",\n"); source.push_back(String("\t\t\"api_type\": \"") + (api.api_type == ClassDB::API_CORE ? "core" : (api.api_type == ClassDB::API_EDITOR ? "tools" : "none")) + "\",\n"); source.push_back(String("\t\t\"singleton\": ") + (api.is_singleton ? "true" : "false") + ",\n"); + source.push_back("\t\t\"singleton_name\": \"" + api.singleton_name + "\",\n"); source.push_back(String("\t\t\"instanciable\": ") + (api.is_instanciable ? "true" : "false") + ",\n"); source.push_back(String("\t\t\"is_reference\": ") + (api.is_reference ? "true" : "false") + ",\n"); // @Unclear diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index 7c313c983f..768b12baea 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -119,7 +119,10 @@ String NativeScript::get_class_name() const { void NativeScript::set_library(Ref<GDNativeLibrary> p_library) { if (!library.is_null()) { - WARN_PRINT("library on NativeScript already set. Do nothing."); + WARN_PRINT("Library in NativeScript already set. Do nothing."); + return; + } + if (p_library.is_null()) { return; } library = p_library; diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp index f7c961d38b..6bb521173f 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.cpp +++ b/modules/gdnative/pluginscript/pluginscript_script.cpp @@ -251,7 +251,19 @@ Error PluginScript::reload(bool p_keep_state) { (godot_string *)&_path, (godot_string *)&_source, (godot_error *)&err); +// Manifest's attributes must be explicitly freed +#define FREE_SCRIPT_MANIFEST(manifest) \ + { \ + godot_string_name_destroy(&manifest.name); \ + godot_string_name_destroy(&manifest.base); \ + godot_dictionary_destroy(&manifest.member_lines); \ + godot_array_destroy(&manifest.methods); \ + godot_array_destroy(&manifest.signals); \ + godot_array_destroy(&manifest.properties); \ + } + if (err) { + FREE_SCRIPT_MANIFEST(manifest); // TODO: GDscript uses `ScriptDebugger` here to jump into the parsing error return err; } @@ -269,6 +281,7 @@ Error PluginScript::reload(bool p_keep_state) { _ref_base_parent = res; } else { String name = *(StringName *)&manifest.name; + FREE_SCRIPT_MANIFEST(manifest); ERR_FAIL_V_MSG(ERR_PARSE_ERROR, _path + ": Script '" + name + "' has an invalid parent '" + *base_name + "'."); } } @@ -317,13 +330,6 @@ Error PluginScript::reload(bool p_keep_state) { _methods_rpc_mode[pi.name] = MultiplayerAPI::RPCMode(int(var)); } } - // Manifest's attributes must be explicitly freed - godot_string_name_destroy(&manifest.name); - godot_string_name_destroy(&manifest.base); - godot_dictionary_destroy(&manifest.member_lines); - godot_array_destroy(&manifest.methods); - godot_array_destroy(&manifest.signals); - godot_array_destroy(&manifest.properties); #ifdef TOOLS_ENABLED /*for (Set<PlaceHolderScriptInstance*>::Element *E=placeholders.front();E;E=E->next()) { @@ -331,7 +337,10 @@ Error PluginScript::reload(bool p_keep_state) { _update_placeholder(E->get()); }*/ #endif + + FREE_SCRIPT_MANIFEST(manifest); return OK; +#undef FREE_SCRIPT_MANIFEST } void PluginScript::get_script_method_list(List<MethodInfo> *r_methods) const { diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index 0194199133..fa59c704d5 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -158,7 +158,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" "extern void add_ios_init_callback(void (*cb)());\n"; String linker_flags = ""; - for (unsigned int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { + for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; String code = declare_pattern.replace("$name", full_name); code = code.replace("$weak", expected_symbols[i].is_required ? "" : " __attribute__((weak))"); @@ -174,7 +174,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty additional_code += String("void $prefixinit() {\n").replace("$prefix", lib->get_symbol_prefix()); String register_pattern = " if (&$name) register_dynamic_symbol((char *)\"$name\", (void *)$name);\n"; - for (unsigned int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { + for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; additional_code += register_pattern.replace("$name", full_name); } diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.cpp b/modules/gdnative/videodecoder/video_stream_gdnative.cpp index f3c34fd5e0..ab14f01858 100644 --- a/modules/gdnative/videodecoder/video_stream_gdnative.cpp +++ b/modules/gdnative/videodecoder/video_stream_gdnative.cpp @@ -123,9 +123,12 @@ bool VideoStreamPlaybackGDNative::open_file(const String &p_file) { godot_vector2 vec = interface->get_texture_size(data_struct); texture_size = *(Vector2 *)&vec; + // Only do memset if num_channels > 0 otherwise it will crash. + if (num_channels > 0) { + pcm = (float *)memalloc(num_channels * AUX_BUFFER_SIZE * sizeof(float)); + memset(pcm, 0, num_channels * AUX_BUFFER_SIZE * sizeof(float)); + } - pcm = (float *)memalloc(num_channels * AUX_BUFFER_SIZE * sizeof(float)); - memset(pcm, 0, num_channels * AUX_BUFFER_SIZE * sizeof(float)); pcm_write_idx = -1; samples_decoded = 0; @@ -146,7 +149,8 @@ void VideoStreamPlaybackGDNative::update(float p_delta) { ERR_FAIL_COND(interface == NULL); interface->update(data_struct, p_delta); - if (mix_callback) { + // Don't mix if there's no audio (num_channels == 0). + if (mix_callback && num_channels > 0) { if (pcm_write_idx >= 0) { // Previous remains int mixed = mix_callback(mix_udata, pcm + pcm_write_idx * num_channels, samples_decoded); @@ -279,7 +283,7 @@ void VideoStreamPlaybackGDNative::set_paused(bool p_paused) { paused = p_paused; } -Ref<Texture> VideoStreamPlaybackGDNative::get_texture() { +Ref<Texture> VideoStreamPlaybackGDNative::get_texture() const { return texture; } diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.h b/modules/gdnative/videodecoder/video_stream_gdnative.h index 9aed1fd2a0..5ff7acb616 100644 --- a/modules/gdnative/videodecoder/video_stream_gdnative.h +++ b/modules/gdnative/videodecoder/video_stream_gdnative.h @@ -168,7 +168,7 @@ public: //virtual int mix(int16_t* p_buffer,int p_frames)=0; - virtual Ref<Texture> get_texture(); + virtual Ref<Texture> get_texture() const; virtual void update(float p_delta); virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata); diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 1d0567dd8d..d1f52d2422 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -21,7 +21,7 @@ <argument index="3" name="a8" type="int" default="255"> </argument> <description> - Returns a 32 bit color with red, green, blue and alpha channels. Each channel has 8 bits of information ranging from 0 to 255. + Returns a color constructed from integer red, green, blue, and alpha channels. Each channel should have 8 bits of information ranging from 0 to 255. [code]r8[/code] red channel [code]g8[/code] green channel [code]b8[/code] blue channel @@ -208,7 +208,7 @@ <argument index="1" name="type" type="int"> </argument> <description> - Converts from a type to another in the best way possible. The [code]type[/code] parameter uses the enum [code]TYPE_*[/code] in [@GlobalScope]. + Converts from a type to another in the best way possible. The [code]type[/code] parameter uses the [enum Variant.Type] values. [codeblock] a = Vector2(1, 0) # Prints 1 @@ -336,11 +336,12 @@ <description> Rounds [code]s[/code] to the closest smaller integer and returns it. [codeblock] - # a is 2 + # a is 2.0 a = floor(2.99) - # a is -3 + # a is -3.0 a = floor(-2.99) [/codeblock] + [b]Note:[/b] This method returns a float. If you need an integer, you can use [code]int(s)[/code] directly. </description> </method> <method name="fmod"> @@ -502,7 +503,7 @@ <argument index="1" name="b" type="float"> </argument> <description> - Returns True/False whether [code]a[/code] and [code]b[/code] are approximately equal to each other. + Returns [code]true[/code] if [code]a[/code] and [code]b[/code] are approximately equal to each other. </description> </method> <method name="is_inf"> @@ -538,7 +539,7 @@ <argument index="0" name="s" type="float"> </argument> <description> - Returns True/False whether [code]s[/code] is zero or almost zero. + Returns [code]true[/code] if [code]s[/code] is zero or almost zero. </description> </method> <method name="len"> @@ -839,6 +840,7 @@ printraw("B") # Prints AB [/codeblock] + [b]Note:[/b] Due to limitations with Godot's built-in console, this only prints to the terminal. If you need to print in the editor, use another method, such as [method print]. </description> </method> <method name="prints" qualifiers="vararg"> @@ -1215,7 +1217,7 @@ <argument index="0" name="what" type="Variant"> </argument> <description> - Returns the internal type of the given Variant object, using the [code]TYPE_*[/code] enum in [@GlobalScope]. + Returns the internal type of the given Variant object, using the [enum Variant.Type] values. [codeblock] p = parse_json('["a", "b", "c"]') if typeof(p) == TYPE_ARRAY: @@ -1231,7 +1233,7 @@ <argument index="0" name="json" type="String"> </argument> <description> - Checks that [code]json[/code] is valid JSON data. Returns empty string if valid. Returns error message if not valid. + Checks that [code]json[/code] is valid JSON data. Returns an empty string if valid, or an error message otherwise. [codeblock] j = to_json([1, 2, 3]) v = validate_json(j) @@ -1362,6 +1364,26 @@ Stops the function execution and returns the current suspended state to the calling function. From the caller, call [method GDScriptFunctionState.resume] on the state to resume execution. This invalidates the state. Within the resumed function, [code]yield()[/code] returns whatever was passed to the [code]resume()[/code] function call. If passed an object and a signal, the execution is resumed when the object emits the given signal. In this case, [code]yield()[/code] returns the argument passed to [code]emit_signal()[/code] if the signal takes only one argument, or an array containing all the arguments passed to [code]emit_signal()[/code] if the signal takes multiple arguments. + You can also use [code]yield[/code] to wait for a function to finish: + [codeblock] + func _ready(): + yield(do_something(), "completed") + yield(do_something_else(), "completed") + print("All functions are done!") + + func do_something(): + print("Something is done!") + + func do_something_else(): + print("Something else is done!") + + # prints: + # Something is done! + # Something else is done! + # All functions are done! + [/codeblock] + When yielding on a function, the [code]completed[/code] signal will be emitted automatically when the function returns. It can, therefore, be used as the [code]signal[/code] parameter of the [code]yield[/code] method to resume. + If you are planning on calling the same function within a loop, you should consider using [code]yield(get_tree(), "idle_frame")[/code] also. </description> </method> </methods> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 6f43361914..8e175a7ab8 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -4,7 +4,7 @@ A script implemented in the GDScript programming language. </brief_description> <description> - A script implemented in the GDScript programming language. The script exends the functionality of all objects that instance it. + A script implemented in the GDScript programming language. The script extends the functionality of all objects that instance it. [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <tutorials> diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index db7f8d22e6..563f7e2471 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -199,7 +199,7 @@ StringName GDScript::get_instance_base_type() const { if (native.is_valid()) return native->get_name(); - if (base.is_valid()) + if (base.is_valid() && base->is_valid()) return base->get_instance_base_type(); return StringName(); } @@ -486,7 +486,7 @@ bool GDScript::_update_exports() { placeholder_fallback_enabled = false; - if (base_cache.is_valid()) { + if (base_cache.is_valid() && base_cache->is_valid()) { if (base_cache->_update_exports()) { changed = true; } @@ -1155,8 +1155,6 @@ bool GDScriptInstance::has_method(const StringName &p_method) const { } Variant GDScriptInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { - //printf("calling %ls:%i method %ls\n", script->get_path().c_str(), -1, String(p_method).c_str()); - GDScript *sptr = script.ptr(); while (sptr) { Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method); @@ -1952,11 +1950,11 @@ String GDScriptWarning::get_message() const { } break; case UNUSED_VARIABLE: { CHECK_SYMBOLS(1); - return "The local variable '" + symbols[0] + "' is declared but never used in the block."; + return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'"; } break; case SHADOWED_VARIABLE: { CHECK_SYMBOLS(2); - return "The local variable '" + symbols[0] + "' is shadowing an already defined variable at line " + symbols[1] + "."; + return "The local variable '" + symbols[0] + "' is shadowing an already-defined variable at line " + symbols[1] + "."; } break; case UNUSED_CLASS_VARIABLE: { CHECK_SYMBOLS(1); @@ -1964,7 +1962,7 @@ String GDScriptWarning::get_message() const { } break; case UNUSED_ARGUMENT: { CHECK_SYMBOLS(2); - return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'."; + return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'"; } break; case UNREACHABLE_CODE: { CHECK_SYMBOLS(1); @@ -2141,6 +2139,7 @@ GDScriptLanguage::GDScriptLanguage() { #ifdef DEBUG_ENABLED GLOBAL_DEF("debug/gdscript/warnings/enable", true); GLOBAL_DEF("debug/gdscript/warnings/treat_warnings_as_errors", false); + GLOBAL_DEF("debug/gdscript/warnings/exclude_addons", true); GLOBAL_DEF("debug/gdscript/completion/autocomplete_setters_and_getters", false); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 1d82735328..280bc37dc0 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -84,14 +84,17 @@ String GDScriptLanguage::_get_processed_template(const String &p_template, const Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const { String _template = "extends %BASE%\n" "\n" + "\n" "# Declare member variables here. Examples:\n" "# var a%INT_TYPE% = 2\n" "# var b%STRING_TYPE% = \"text\"\n" "\n" + "\n" "# Called when the node enters the scene tree for the first time.\n" "func _ready()%VOID_RETURN%:\n" "%TS%pass # Replace with function body.\n" "\n" + "\n" "# Called every frame. 'delta' is the elapsed time since the previous frame.\n" "#func _process(delta%FLOAT_TYPE%)%VOID_RETURN%:\n" "#%TS%pass\n"; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 83d02e4977..0a01321851 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -397,8 +397,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (unlikely(m_cond)) { \ _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition ' " _STR(m_cond) " ' is true. Breaking..:"); \ OPCODE_BREAK; \ - } else \ - _err_error_exists = false; \ + } \ } #define CHECK_SPACE(m_space) \ @@ -1561,14 +1560,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a //error // function, file, line, error, explanation String err_file; - if (p_instance && p_instance->script->is_valid() && p_instance->script->path != "") + if (p_instance && ObjectDB::instance_validate(p_instance->owner) && p_instance->script->is_valid() && p_instance->script->path != "") err_file = p_instance->script->path; else if (script) err_file = script->path; if (err_file == "") err_file = "<built-in>"; String err_func = name; - if (p_instance && p_instance->script->is_valid() && p_instance->script->name != "") + if (p_instance && ObjectDB::instance_validate(p_instance->owner) && p_instance->script->is_valid() && p_instance->script->name != "") err_func = p_instance->script->name + "." + err_func; int err_line = line; if (err_text == "") { diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index 1d6562e69d..9e05c7b574 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -589,7 +589,8 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ r_ret = wref; } } else if (p_args[0]->get_type() == Variant::NIL) { - r_ret = memnew(WeakRef); + Ref<WeakRef> wref = memnew(WeakRef); + r_ret = wref; } else { r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; @@ -1125,7 +1126,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ Dictionary d; d["@subpath"] = cp; - d["@path"] = p->path; + d["@path"] = p->get_path(); for (Map<StringName, GDScript::MemberInfo>::Element *E = base->member_indices.front(); E; E = E->next()) { if (!d.has(E->key())) { @@ -1259,6 +1260,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ if (err != OK) { r_ret = Variant(); + ERR_PRINTS(vformat("Error parsing JSON at line %s: %s", errl, errs)); } } break; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index da6a52ff0d..ef1a282e51 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -89,8 +89,8 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) { // be more python-like - int current = tab_level.back()->get(); - tab_level.push_back(current); + IndentLevel current_level = indent_level.back()->get(); + indent_level.push_back(current_level); return true; //_set_error("newline expected after ':'."); //return false; @@ -105,12 +105,19 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { } else if (tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) { int indent = tokenizer->get_token_line_indent(); - int current = tab_level.back()->get(); - if (indent <= current) { + int tabs = tokenizer->get_token_line_tab_indent(); + IndentLevel current_level = indent_level.back()->get(); + IndentLevel new_indent(indent, tabs); + if (new_indent.is_mixed(current_level)) { + _set_error("Mixed tabs and spaces in indentation."); return false; } - tab_level.push_back(indent); + if (indent <= current_level.indent) { + return false; + } + + indent_level.push_back(new_indent); tokenizer->advance(); return true; @@ -2225,7 +2232,7 @@ GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) { } void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static) { - int indent_level = tab_level.back()->get(); + IndentLevel current_level = indent_level.back()->get(); p_block->has_return = true; @@ -2240,7 +2247,7 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran if (error_set) return; - if (indent_level > tab_level.back()->get()) { + if (current_level.indent > indent_level.back()->get().indent) { break; // go back a level } @@ -2695,7 +2702,7 @@ void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) { void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { - int indent_level = tab_level.back()->get(); + IndentLevel current_level = indent_level.back()->get(); #ifdef DEBUG_ENABLED @@ -2708,9 +2715,13 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { bool is_first_line = true; while (true) { - if (!is_first_line && tab_level.back()->prev() && tab_level.back()->prev()->get() == indent_level) { + if (!is_first_line && indent_level.back()->prev() && indent_level.back()->prev()->get().indent == current_level.indent) { + if (indent_level.back()->prev()->get().is_mixed(current_level)) { + _set_error("Mixed tabs and spaces in indentation."); + return; + } // pythonic single-line expression, don't parse future lines - tab_level.pop_back(); + indent_level.pop_back(); p_block->end_line = tokenizer->get_token_line(); return; } @@ -2720,7 +2731,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { if (error_set) return; - if (indent_level > tab_level.back()->get()) { + if (current_level.indent > indent_level.back()->get().indent) { p_block->end_line = tokenizer->get_token_line(); return; //go back a level } @@ -2924,14 +2935,14 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) ; - if (tab_level.back()->get() < indent_level) { //not at current indent level + if (indent_level.back()->get().indent < current_level.indent) { //not at current indent level p_block->end_line = tokenizer->get_token_line(); return; } if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELIF) { - if (tab_level.back()->get() > indent_level) { + if (indent_level.back()->get().indent > current_level.indent) { _set_error("Invalid indentation."); return; @@ -2979,7 +2990,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) { + if (indent_level.back()->get().indent > current_level.indent) { _set_error("Invalid indentation."); return; } @@ -3338,7 +3349,12 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { } p_block->statements.push_back(expression); if (!_end_statement()) { - _set_error("Expected end of statement after expression."); + // Attempt to guess a better error message if the user "retypes" a variable + if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { + _set_error("Unexpected ':=', use '=' instead. Expected end of statement after expression."); + } else { + _set_error(String() + "Expected end of statement after expression, got " + tokenizer->get_token_name(tokenizer->get_token()) + " instead"); + } return; } @@ -3351,32 +3367,45 @@ bool GDScriptParser::_parse_newline() { if (tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) { + IndentLevel current_level = indent_level.back()->get(); int indent = tokenizer->get_token_line_indent(); - int current_indent = tab_level.back()->get(); + int tabs = tokenizer->get_token_line_tab_indent(); + IndentLevel new_level(indent, tabs); + + if (new_level.is_mixed(current_level)) { + _set_error("Mixed tabs and spaces in indentation."); + return false; + } - if (indent > current_indent) { + if (indent > current_level.indent) { _set_error("Unexpected indentation."); return false; } - if (indent < current_indent) { + if (indent < current_level.indent) { - while (indent < current_indent) { + while (indent < current_level.indent) { //exit block - if (tab_level.size() == 1) { + if (indent_level.size() == 1) { _set_error("Invalid indentation. Bug?"); return false; } - tab_level.pop_back(); + indent_level.pop_back(); - if (tab_level.back()->get() < indent) { + if (indent_level.back()->get().indent < indent) { _set_error("Unindent does not match any outer indentation level."); return false; } - current_indent = tab_level.back()->get(); + + if (indent_level.back()->get().is_mixed(current_level)) { + _set_error("Mixed tabs and spaces in indentation."); + return false; + } + + current_level = indent_level.back()->get(); } tokenizer->advance(); @@ -3474,7 +3503,7 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) { void GDScriptParser::_parse_class(ClassNode *p_class) { - int indent_level = tab_level.back()->get(); + IndentLevel current_level = indent_level.back()->get(); while (true) { @@ -3482,7 +3511,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (error_set) return; - if (indent_level > tab_level.back()->get()) { + if (current_level.indent > indent_level.back()->get().indent) { p_class->end_line = tokenizer->get_token_line(); return; //go back a level } @@ -6442,6 +6471,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { DataType true_type = _reduce_node_type(op->arguments[1]); DataType false_type = _reduce_node_type(op->arguments[2]); + // Check arguments[0] errors. + _reduce_node_type(op->arguments[0]); // If types are equal, then the expression is of the same type // If they are compatible, return the broader type @@ -8349,6 +8380,9 @@ void GDScriptParser::_add_warning(int p_code, int p_line, const String &p_symbol } void GDScriptParser::_add_warning(int p_code, int p_line, const Vector<String> &p_symbols) { + if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && base_path.begins_with("res://addons/")) { + return; + } if (tokenizer->is_ignoring_warnings() || !GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) { return; } @@ -8562,8 +8596,8 @@ void GDScriptParser::clear() { validating = false; for_completion = false; error_set = false; - tab_level.clear(); - tab_level.push_back(0); + indent_level.clear(); + indent_level.push_back(IndentLevel(0, 0)); error_line = 0; error_column = 0; pending_newline = -1; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 04ce9cf4c6..93557d745d 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -552,7 +552,27 @@ private: int pending_newline; - List<int> tab_level; + struct IndentLevel { + int indent; + int tabs; + + bool is_mixed(IndentLevel other) { + return ( + (indent == other.indent && tabs != other.tabs) || + (indent > other.indent && tabs < other.tabs) || + (indent < other.indent && tabs > other.tabs)); + } + + IndentLevel() : + indent(0), + tabs(0) {} + + IndentLevel(int p_indent, int p_tabs) : + indent(p_indent), + tabs(p_tabs) {} + }; + + List<IndentLevel> indent_level; String base_path; String self_path; diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 4730e9b6bc..761a3de37c 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -450,11 +450,11 @@ void GDScriptTokenizerText::_make_error(const String &p_error) { tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; } -void GDScriptTokenizerText::_make_newline(int p_spaces) { +void GDScriptTokenizerText::_make_newline(int p_indentation, int p_tabs) { TokenData &tk = tk_rb[tk_rb_pos]; tk.type = TK_NEWLINE; - tk.constant = p_spaces; + tk.constant = Vector2(p_indentation, p_tabs); tk.line = line; tk.col = column; tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; @@ -511,33 +511,6 @@ void GDScriptTokenizerText::_advance() { case ' ': INCPOS(1); continue; - case '\n': { - line++; - INCPOS(1); - column = 1; - int i = 0; - 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; - } case '#': { // line comment skip #ifdef DEBUG_ENABLED String comment; @@ -565,33 +538,34 @@ void GDScriptTokenizerText::_advance() { ignore_warnings = true; } #endif // DEBUG_ENABLED + FALLTHROUGH; + } + case '\n': { + line++; INCPOS(1); + bool used_spaces = false; + int tabs = 0; column = 1; - line++; int i = 0; 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; - } + i++; + used_spaces = true; } 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!"); + if (used_spaces) { + _make_error("Spaces used before tabs on a line"); return; } + i++; + tabs++; } else { break; // not indentation anymore } - i++; } - _make_newline(i); + _make_newline(i, tabs); return; - - } break; + } case '/': { switch (GETCHAR(1)) { @@ -933,8 +907,10 @@ void GDScriptTokenizerText::_advance() { return; } hexa_found = true; - } else if (GETCHAR(i) == 'b') { - if (hexa_found || bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) { + } else if (hexa_found && _is_hex(GETCHAR(i))) { + + } else if (!hexa_found && GETCHAR(i) == 'b') { + if (bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) { _make_error("Invalid numeric constant at 'b'"); return; } @@ -947,7 +923,6 @@ void GDScriptTokenizerText::_advance() { exponent_found = true; } else if (_is_number(GETCHAR(i))) { //all ok - } else if (hexa_found && _is_hex(GETCHAR(i))) { } else if (bin_found && _is_bin(GETCHAR(i))) { @@ -1108,7 +1083,6 @@ 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(); } @@ -1183,7 +1157,17 @@ int GDScriptTokenizerText::get_token_line_indent(int p_offset) const { int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0); - return tk_rb[ofs].constant; + return tk_rb[ofs].constant.operator Vector2().x; +} + +int GDScriptTokenizerText::get_token_line_tab_indent(int p_offset) const { + + ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, 0); + ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, 0); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; + ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0); + return tk_rb[ofs].constant.operator Vector2().y; } String GDScriptTokenizerText::get_token_error(int p_offset) const { diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 89d586b912..58749012b7 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -168,6 +168,7 @@ public: virtual int get_token_line(int p_offset = 0) const = 0; virtual int get_token_column(int p_offset = 0) const = 0; virtual int get_token_line_indent(int p_offset = 0) const = 0; + virtual int get_token_line_tab_indent(int p_offset = 0) const = 0; virtual String get_token_error(int p_offset = 0) const = 0; virtual void advance(int p_amount = 1) = 0; #ifdef DEBUG_ENABLED @@ -205,7 +206,7 @@ class GDScriptTokenizerText : public GDScriptTokenizer { }; void _make_token(Token p_type); - void _make_newline(int p_spaces = 0); + void _make_newline(int p_indentation = 0, int p_tabs = 0); void _make_identifier(const StringName &p_identifier); void _make_built_in_func(GDScriptFunctions::Function p_func); void _make_constant(const Variant &p_constant); @@ -222,11 +223,6 @@ 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; @@ -245,6 +241,7 @@ public: virtual int get_token_line(int p_offset = 0) const; virtual int get_token_column(int p_offset = 0) const; virtual int get_token_line_indent(int p_offset = 0) const; + virtual int get_token_line_tab_indent(int p_offset = 0) const; virtual const Variant &get_token_constant(int p_offset = 0) const; virtual String get_token_error(int p_offset = 0) const; virtual void advance(int p_amount = 1); @@ -283,6 +280,7 @@ public: virtual int get_token_line(int p_offset = 0) const; virtual int get_token_column(int p_offset = 0) const; virtual int get_token_line_indent(int p_offset = 0) const; + virtual int get_token_line_tab_indent(int p_offset = 0) const { return 0; } virtual const Variant &get_token_constant(int p_offset = 0) const; virtual String get_token_error(int p_offset = 0) const; virtual void advance(int p_amount = 1); diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 6b5c26ec81..d63f786fcb 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -115,7 +115,7 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) { if (tokenizer.get_token() == GDScriptTokenizer::TK_EOF) { break; } else if (tokenizer.get_token() == GDScriptTokenizer::TK_CONSTANT) { - Variant const_val = tokenizer.get_token_constant(); + const Variant &const_val = tokenizer.get_token_constant(); if (const_val.get_type() == Variant::STRING) { String path = const_val; bool exists = fs->file_exists(path); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index b83db718b8..1ca2a50c21 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -281,8 +281,6 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { } Array GDScriptTextDocument::foldingRange(const Dictionary &p_params) { - Dictionary params = p_params["textDocument"]; - String path = params["uri"]; Array arr; return arr; } diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index c289ff6c07..f9a974bad3 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -102,9 +102,9 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_ } 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()) { + List<String> paths; + list_script_files("res://", paths); + for (List<String>::Element *E = paths.front(); E; E = E->next()) { const String &path = E->get(); Error err; String content = FileAccess::get_file_as_string(path, &err); @@ -117,8 +117,7 @@ void GDScriptWorkspace::reload_all_workspace_scripts() { if (S) { err_msg += "\n" + S->get()->get_error(); } - ERR_EXPLAIN(err_msg); - ERR_CONTINUE(err != OK); + ERR_CONTINUE_MSG(err != OK, err_msg); } } } diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index a048af88bb..35471d63d6 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -1583,7 +1583,7 @@ struct GodotNativeClassInfo { } }; -/** Features not included in the standart lsp specifications */ +/** Features not included in the standard lsp specifications */ struct GodotCapabilities { /** diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 1bd3d72066..3de971db6d 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -4,10 +4,10 @@ Node for 3D tile-based maps. </brief_description> <description> - GridMap lets you place meshes on a grid interactively. It works both from the editor and can help you create in-game level editors. - GridMaps use a [MeshLibrary] which contain a list of tiles: meshes with materials plus optional collisions and extra elements. - A GridMap contains a collection of cells. Each grid cell refers to a [MeshLibrary] item. All cells in the map have the same dimensions. - A GridMap is split into a sparse collection of octants for efficient rendering and physics processing. Every octant has the same dimensions and can contain several cells. + GridMap lets you place meshes on a grid interactively. It works both from the editor and from scripts, which can help you create in-game level editors. + GridMaps use a [MeshLibrary] which contains a list of tiles. Each tile is a mesh with materials plus optional collision and navigation shapes. + A GridMap contains a collection of cells. Each grid cell refers to a tile in the [MeshLibrary]. All cells in the map have the same dimensions. + Internally, a GridMap is split into a sparse collection of octants for efficient rendering and physics processing. Every octant has the same dimensions and can contain several cells. </description> <tutorials> <link>https://docs.godotengine.org/en/latest/tutorials/3d/using_gridmaps.html</link> @@ -72,6 +72,7 @@ <argument index="0" name="bit" type="int"> </argument> <description> + Returns an individual bit on the [member collision_layer]. </description> </method> <method name="get_collision_mask_bit" qualifiers="const"> @@ -80,20 +81,21 @@ <argument index="0" name="bit" type="int"> </argument> <description> + Returns an individual bit on the [member collision_mask]. </description> </method> <method name="get_meshes"> <return type="Array"> </return> <description> - Array of [Transform] and [Mesh] references corresponding to the non-empty cells in the grid. The transforms are specified in world space. + Returns an array of [Transform] and [Mesh] references corresponding to the non-empty cells in the grid. The transforms are specified in world space. </description> </method> <method name="get_used_cells" qualifiers="const"> <return type="Array"> </return> <description> - Array of [Vector3] with the non-empty cell coordinates in the grid map. + Returns an array of [Vector3] with the non-empty cell coordinates in the grid map. </description> </method> <method name="make_baked_meshes"> @@ -116,6 +118,7 @@ <argument index="2" name="z" type="int"> </argument> <description> + Returns the position of a grid cell in the GridMap's local coordinate space. </description> </method> <method name="resource_changed"> @@ -140,9 +143,9 @@ <argument index="4" name="orientation" type="int" default="0"> </argument> <description> - Set the mesh index for the cell referenced by its grid-based X, Y and Z coordinates. - A negative item index will clear the cell. - Optionally, the item's orientation can be passed. + Sets the mesh index for the cell referenced by its grid-based X, Y and Z coordinates. + A negative item index such as [constant INVALID_CELL_ITEM] will clear the cell. + Optionally, the item's orientation can be passed. For valid orientation values, see [method Basis.get_orthogonal_index]. </description> </method> <method name="set_clip"> @@ -167,6 +170,7 @@ <argument index="1" name="value" type="bool"> </argument> <description> + Sets an individual bit on the [member collision_layer]. </description> </method> <method name="set_collision_mask_bit"> @@ -177,6 +181,7 @@ <argument index="1" name="value" type="bool"> </argument> <description> + Sets an individual bit on the [member collision_mask]. </description> </method> <method name="world_to_map" qualifiers="const"> @@ -185,6 +190,8 @@ <argument index="0" name="pos" type="Vector3"> </argument> <description> + Returns the coordinates of the grid cell containing the given point. + [code]pos[/code] should be in the GridMap's local coordinate space. </description> </method> </methods> @@ -202,21 +209,33 @@ The size of each octant measured in number of cells. This applies to all three axis. </member> <member name="cell_scale" type="float" setter="set_cell_scale" getter="get_cell_scale" default="1.0"> + The scale of the cell items. + This does not affect the size of the grid cells themselves, only the items in them. This can be used to make cell items overlap their neighbors. </member> <member name="cell_size" type="Vector3" setter="set_cell_size" getter="get_cell_size" default="Vector3( 2, 2, 2 )"> The dimensions of the grid's cells. + This does not affect the size of the meshes. See [member cell_scale]. </member> <member name="collision_layer" type="int" setter="set_collision_layer" getter="get_collision_layer" default="1"> + The physics layers this GridMap is in. + GridMaps act as static bodies, meaning they aren't affected by gravity or other forces. They only affect other physics bodies that collide with them. </member> <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1"> + The physics layers this GridMap detects collisions in. </member> <member name="mesh_library" type="MeshLibrary" setter="set_mesh_library" getter="get_mesh_library"> The assigned [MeshLibrary]. </member> - <member name="theme" type="MeshLibrary" setter="set_theme" getter="get_theme"> - Deprecated, use [member mesh_library] instead. - </member> </members> + <signals> + <signal name="cell_size_changed"> + <argument index="0" name="cell_size" type="Vector3"> + </argument> + <description> + Emitted when [member cell_size] changes. + </description> + </signal> + </signals> <constants> <constant name="INVALID_CELL_ITEM" value="-1"> Invalid cell item that can be used in [method set_cell_item] to clear cells (or represent an empty cell in [method get_cell_item]). diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index b36afd4386..d69e60ced3 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -193,22 +193,6 @@ bool GridMap::get_collision_layer_bit(int p_bit) const { return get_collision_layer() & (1 << p_bit); } -#ifndef DISABLE_DEPRECATED -void GridMap::set_theme(const Ref<MeshLibrary> &p_theme) { - - 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 { - - 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(); -} -#endif // DISABLE_DEPRECATED - void GridMap::set_mesh_library(const Ref<MeshLibrary> &p_mesh_library) { if (!mesh_library.is_null()) @@ -227,10 +211,10 @@ Ref<MeshLibrary> GridMap::get_mesh_library() const { } void GridMap::set_cell_size(const Vector3 &p_size) { - ERR_FAIL_COND(p_size.x < 0.001 || p_size.y < 0.001 || p_size.z < 0.001); cell_size = p_size; _recreate_octant_data(); + emit_signal("cell_size_changed", cell_size); } Vector3 GridMap::get_cell_size() const { @@ -838,11 +822,6 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &GridMap::set_collision_layer_bit); ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &GridMap::get_collision_layer_bit); -#ifndef DISABLE_DEPRECATED - ClassDB::bind_method(D_METHOD("set_theme", "theme"), &GridMap::set_theme); - ClassDB::bind_method(D_METHOD("get_theme"), &GridMap::get_theme); -#endif // DISABLE_DEPRECATED - ClassDB::bind_method(D_METHOD("set_mesh_library", "mesh_library"), &GridMap::set_mesh_library); ClassDB::bind_method(D_METHOD("get_mesh_library"), &GridMap::get_mesh_library); @@ -885,10 +864,6 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_baked_meshes"), &GridMap::clear_baked_meshes); ClassDB::bind_method(D_METHOD("make_baked_meshes", "gen_lightmap_uv", "lightmap_uv_texel_size"), &GridMap::make_baked_meshes, DEFVAL(false), DEFVAL(0.1)); -#ifndef DISABLE_DEPRECATED - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "MeshLibrary", 0), "set_theme", "get_theme"); -#endif // DISABLE_DEPRECATED - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh_library", PROPERTY_HINT_RESOURCE_TYPE, "MeshLibrary"), "set_mesh_library", "get_mesh_library"); ADD_GROUP("Cell", "cell_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "cell_size"), "set_cell_size", "get_cell_size"); @@ -902,6 +877,8 @@ void GridMap::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); BIND_CONSTANT(INVALID_CELL_ITEM); + + ADD_SIGNAL(MethodInfo("cell_size_changed", PropertyInfo(Variant::VECTOR3, "cell_size"))); } void GridMap::set_clip(bool p_enabled, bool p_clip_above, int p_floor, Vector3::Axis p_axis) { diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index f4407099f5..10c96956b7 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -227,11 +227,6 @@ public: void set_collision_mask_bit(int p_bit, bool p_value); bool get_collision_mask_bit(int p_bit) const; -#ifndef DISABLE_DEPRECATED - void set_theme(const Ref<MeshLibrary> &p_theme); - Ref<MeshLibrary> get_theme() const; -#endif // DISABLE_DEPRECATED - void set_mesh_library(const Ref<MeshLibrary> &p_mesh_library); Ref<MeshLibrary> get_mesh_library() const; diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 804c57fc6e..1bd570c55f 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -276,6 +276,7 @@ void GridMapEditor::_update_cursor_transform() { cursor_transform = Transform(); cursor_transform.origin = cursor_origin; cursor_transform.basis.set_orthogonal_index(cursor_rot); + cursor_transform.basis *= node->get_cell_scale(); cursor_transform = node->get_global_transform() * cursor_transform; if (cursor_instance.is_valid()) { @@ -298,7 +299,7 @@ void GridMapEditor::_update_selection_transform() { } Transform xf; - xf.scale(Vector3(1, 1, 1) * (Vector3(1, 1, 1) + (selection.end - selection.begin)) * node->get_cell_size()); + xf.scale((Vector3(1, 1, 1) + (selection.end - selection.begin)) * node->get_cell_size()); xf.origin = selection.begin * node->get_cell_size(); VisualServer::get_singleton()->instance_set_transform(selection_instance, node->get_global_transform() * xf); @@ -595,7 +596,7 @@ void GridMapEditor::_update_paste_indicator() { Basis item_rot; item_rot.set_orthogonal_index(item.orientation); - xf.basis = item_rot * xf.basis; + xf.basis = item_rot * xf.basis * node->get_cell_scale(); VisualServer::get_singleton()->instance_set_transform(item.instance, node->get_global_transform() * xf); } @@ -933,9 +934,10 @@ void GridMapEditor::update_palette() { } void GridMapEditor::edit(GridMap *p_gridmap) { + if (!p_gridmap && node) + node->disconnect("cell_size_changed", this, "_draw_grids"); node = p_gridmap; - VS *vs = VS::get_singleton(); input_action = INPUT_NONE; selection.active = false; @@ -961,75 +963,13 @@ void GridMapEditor::edit(GridMap *p_gridmap) { set_process(true); - Vector3 edited_floor = p_gridmap->has_meta("_editor_floor_") ? p_gridmap->get_meta("_editor_floor_") : Variant(); clip_mode = p_gridmap->has_meta("_editor_clip_") ? ClipMode(p_gridmap->get_meta("_editor_clip_").operator int()) : CLIP_DISABLED; - for (int i = 0; i < 3; i++) { - if (vs->mesh_get_surface_count(grid[i]) > 0) - vs->mesh_remove_surface(grid[i], 0); - edit_floor[i] = edited_floor[i]; - } - - { - - // Update grids. - indicator_mat.instance(); - indicator_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true); - indicator_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - indicator_mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true); - indicator_mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - indicator_mat->set_albedo(Color(0.8, 0.5, 0.1)); - - Vector<Vector3> grid_points[3]; - Vector<Color> grid_colors[3]; - - float cell_size[3] = { p_gridmap->get_cell_size().x, p_gridmap->get_cell_size().y, p_gridmap->get_cell_size().z }; - - for (int i = 0; i < 3; i++) { - - Vector3 axis; - axis[i] = 1; - Vector3 axis_n1; - axis_n1[(i + 1) % 3] = cell_size[(i + 1) % 3]; - Vector3 axis_n2; - axis_n2[(i + 2) % 3] = cell_size[(i + 2) % 3]; - - for (int j = -GRID_CURSOR_SIZE; j <= GRID_CURSOR_SIZE; j++) { - - for (int k = -GRID_CURSOR_SIZE; k <= GRID_CURSOR_SIZE; k++) { - - Vector3 p = axis_n1 * j + axis_n2 * k; - float trans = Math::pow(MAX(0, 1.0 - (Vector2(j, k).length() / GRID_CURSOR_SIZE)), 2); - - Vector3 pj = axis_n1 * (j + 1) + axis_n2 * k; - float transj = Math::pow(MAX(0, 1.0 - (Vector2(j + 1, k).length() / GRID_CURSOR_SIZE)), 2); - - Vector3 pk = axis_n1 * j + axis_n2 * (k + 1); - float transk = Math::pow(MAX(0, 1.0 - (Vector2(j, k + 1).length() / GRID_CURSOR_SIZE)), 2); - - grid_points[i].push_back(p); - grid_points[i].push_back(pk); - grid_colors[i].push_back(Color(1, 1, 1, trans)); - grid_colors[i].push_back(Color(1, 1, 1, transk)); - - grid_points[i].push_back(p); - grid_points[i].push_back(pj); - grid_colors[i].push_back(Color(1, 1, 1, trans)); - grid_colors[i].push_back(Color(1, 1, 1, transj)); - } - } - - Array d; - d.resize(VS::ARRAY_MAX); - d[VS::ARRAY_VERTEX] = grid_points[i]; - d[VS::ARRAY_COLOR] = grid_colors[i]; - VisualServer::get_singleton()->mesh_add_surface_from_arrays(grid[i], VisualServer::PRIMITIVE_LINES, d); - VisualServer::get_singleton()->mesh_surface_set_material(grid[i], 0, indicator_mat->get_rid()); - } - } - + _draw_grids(node->get_cell_size()); update_grid(); _update_clip(); + + node->connect("cell_size_changed", this, "_draw_grids"); } void GridMapEditor::_update_clip() { @@ -1059,6 +999,61 @@ void GridMapEditor::update_grid() { updating = false; } +void GridMapEditor::_draw_grids(const Vector3 &cell_size) { + Vector3 edited_floor = node->has_meta("_editor_floor_") ? node->get_meta("_editor_floor_") : Variant(); + + for (int i = 0; i < 3; i++) { + if (VS::get_singleton()->mesh_get_surface_count(grid[i]) > 0) + VS::get_singleton()->mesh_remove_surface(grid[i], 0); + edit_floor[i] = edited_floor[i]; + } + + Vector<Vector3> grid_points[3]; + Vector<Color> grid_colors[3]; + + for (int i = 0; i < 3; i++) { + + Vector3 axis; + axis[i] = 1; + Vector3 axis_n1; + axis_n1[(i + 1) % 3] = cell_size[(i + 1) % 3]; + Vector3 axis_n2; + axis_n2[(i + 2) % 3] = cell_size[(i + 2) % 3]; + + for (int j = -GRID_CURSOR_SIZE; j <= GRID_CURSOR_SIZE; j++) { + + for (int k = -GRID_CURSOR_SIZE; k <= GRID_CURSOR_SIZE; k++) { + + Vector3 p = axis_n1 * j + axis_n2 * k; + float trans = Math::pow(MAX(0, 1.0 - (Vector2(j, k).length() / GRID_CURSOR_SIZE)), 2); + + Vector3 pj = axis_n1 * (j + 1) + axis_n2 * k; + float transj = Math::pow(MAX(0, 1.0 - (Vector2(j + 1, k).length() / GRID_CURSOR_SIZE)), 2); + + Vector3 pk = axis_n1 * j + axis_n2 * (k + 1); + float transk = Math::pow(MAX(0, 1.0 - (Vector2(j, k + 1).length() / GRID_CURSOR_SIZE)), 2); + + grid_points[i].push_back(p); + grid_points[i].push_back(pk); + grid_colors[i].push_back(Color(1, 1, 1, trans)); + grid_colors[i].push_back(Color(1, 1, 1, transk)); + + grid_points[i].push_back(p); + grid_points[i].push_back(pj); + grid_colors[i].push_back(Color(1, 1, 1, trans)); + grid_colors[i].push_back(Color(1, 1, 1, transj)); + } + } + + Array d; + d.resize(VS::ARRAY_MAX); + d[VS::ARRAY_VERTEX] = grid_points[i]; + d[VS::ARRAY_COLOR] = grid_colors[i]; + VisualServer::get_singleton()->mesh_add_surface_from_arrays(grid[i], VisualServer::PRIMITIVE_LINES, d); + VisualServer::get_singleton()->mesh_surface_set_material(grid[i], 0, indicator_mat->get_rid()); + } +} + void GridMapEditor::_notification(int p_what) { switch (p_what) { @@ -1197,6 +1192,7 @@ void GridMapEditor::_bind_methods() { ClassDB::bind_method("_node_removed", &GridMapEditor::_node_removed); ClassDB::bind_method(D_METHOD("_set_display_mode", "mode"), &GridMapEditor::_set_display_mode); + ClassDB::bind_method("_draw_grids", &GridMapEditor::_draw_grids); } GridMapEditor::GridMapEditor(EditorNode *p_editor) { @@ -1334,6 +1330,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { info_message->set_valign(Label::VALIGN_CENTER); info_message->set_align(Label::ALIGN_CENTER); info_message->set_autowrap(true); + info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); info_message->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); mesh_library_palette->add_child(info_message); @@ -1472,6 +1469,13 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { _set_selection(false); updating = false; accumulated_floor_delta = 0.0; + + indicator_mat.instance(); + indicator_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true); + indicator_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + indicator_mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true); + indicator_mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + indicator_mat->set_albedo(Color(0.8, 0.5, 0.1)); } GridMapEditor::~GridMapEditor() { diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h index 48a07e9c7f..42e62f2842 100644 --- a/modules/gridmap/grid_map_editor_plugin.h +++ b/modules/gridmap/grid_map_editor_plugin.h @@ -201,7 +201,8 @@ class GridMapEditor : public VBoxContainer { EditorNode *editor; - void update_grid(); + void update_grid(); // Change which and where the grid is displayed + void _draw_grids(const Vector3 &cell_size); void _configure(); void _menu_option(int); void update_palette(); diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index 204f4e8905..62912d1459 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -203,8 +203,6 @@ void CryptoMbedTLS::load_default_certificates(String p_path) { 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); diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp index a2e342e219..78e99a3a65 100755 --- a/modules/mbedtls/stream_peer_mbedtls.cpp +++ b/modules/mbedtls/stream_peer_mbedtls.cpp @@ -111,7 +111,6 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER); base = p_base; - int ret = 0; int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE; Error err = ssl_ctx->init_client(MBEDTLS_SSL_TRANSPORT_STREAM, authmode, p_ca_certs); @@ -122,7 +121,7 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida status = STATUS_HANDSHAKING; - if ((ret = _do_handshake()) != OK) { + if (_do_handshake() != OK) { status = STATUS_ERROR_HOSTNAME_MISMATCH; return FAILED; } @@ -143,7 +142,7 @@ Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_ status = STATUS_HANDSHAKING; - if ((err = _do_handshake()) != OK) { + if (_do_handshake() != OK) { return FAILED; } diff --git a/modules/mono/SCsub b/modules/mono/SCsub index a9afa7ccf6..457edfaeed 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -42,14 +42,14 @@ mono_configure.configure(env, env_mono) if env_mono['tools'] and env_mono['mono_glue']: import build_scripts.api_solution_build as api_solution_build - api_solution_build.build(env_mono) + api_sln_cmd = api_solution_build.build(env_mono) # Build GodotTools if env_mono['tools']: import build_scripts.godot_tools_build as godot_tools_build if env_mono['mono_glue']: - godot_tools_build.build(env_mono) + godot_tools_build.build(env_mono, api_sln_cmd) else: # Building without the glue sources so the Godot API solution may be missing. # GodotTools depends on the Godot API solution. As such, we will only build diff --git a/modules/mono/build_scripts/api_solution_build.py b/modules/mono/build_scripts/api_solution_build.py index 1fe00a3028..be54d0a679 100644 --- a/modules/mono/build_scripts/api_solution_build.py +++ b/modules/mono/build_scripts/api_solution_build.py @@ -55,12 +55,22 @@ def build(env_mono): 'GodotSharpEditor.dll', 'GodotSharpEditor.pdb', 'GodotSharpEditor.xml' ] + depend_cmd = [] + for build_config in ['Debug', 'Release']: output_dir = Dir('#bin').abspath editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', build_config) targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames] - cmd = env_mono.CommandNoCache(targets, [], build_api_solution, + cmd = env_mono.CommandNoCache(targets, depend_cmd, build_api_solution, module_dir=os.getcwd(), solution_build_config=build_config) env_mono.AlwaysBuild(cmd) + + # Make the Release build of the API solution depend on the Debug build. + # We do this in order to prevent SCons from building them in parallel, + # which can freak out MSBuild. In many cases, one of the builds would + # hang indefinitely requiring a key to be pressed for it to continue. + depend_cmd = cmd + + return depend_cmd diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py index 35daa6d307..6e5273f5e0 100644 --- a/modules/mono/build_scripts/godot_tools_build.py +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -74,19 +74,16 @@ def build_godot_tools_project_editor(source, target, env): copy_target(str(scons_target)) -def build(env_mono): +def build(env_mono, api_sln_cmd): assert env_mono['tools'] output_dir = Dir('#bin').abspath editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools') - editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', 'Debug') - - source_filenames = ['GodotSharp.dll', 'GodotSharpEditor.dll'] - sources = [os.path.join(editor_api_dir, filename) for filename in source_filenames] target_filenames = [ 'GodotTools.dll', 'GodotTools.IdeConnection.dll', 'GodotTools.BuildLogger.dll', - 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll' + 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll', + 'JetBrains.Annotations.dll', 'Newtonsoft.Json.dll' ] if env_mono['target'] == 'debug': @@ -97,7 +94,7 @@ def build(env_mono): targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] - cmd = env_mono.CommandNoCache(targets, sources, build_godot_tools, module_dir=os.getcwd()) + cmd = env_mono.CommandNoCache(targets, api_sln_cmd, build_godot_tools, module_dir=os.getcwd()) env_mono.AlwaysBuild(cmd) diff --git a/modules/mono/build_scripts/mono_android_config.xml b/modules/mono/build_scripts/mono_android_config.xml new file mode 100644 index 0000000000..e79670afd2 --- /dev/null +++ b/modules/mono/build_scripts/mono_android_config.xml @@ -0,0 +1,28 @@ +<configuration> + <dllmap wordsize="32" dll="i:cygwin1.dll" target="/system/lib/libc.so" /> + <dllmap wordsize="64" dll="i:cygwin1.dll" target="/system/lib64/libc.so" /> + <dllmap wordsize="32" dll="libc" target="/system/lib/libc.so" /> + <dllmap wordsize="64" dll="libc" target="/system/lib64/libc.so" /> + <dllmap wordsize="32" dll="intl" target="/system/lib/libc.so" /> + <dllmap wordsize="64" dll="intl" target="/system/lib64/libc.so" /> + <dllmap wordsize="32" dll="libintl" target="/system/lib/libc.so" /> + <dllmap wordsize="64" dll="libintl" target="/system/lib64/libc.so" /> + <dllmap dll="MonoPosixHelper" target="libMonoPosixHelper.so" /> + <dllmap dll="System.Native" target="libmono-native.so" /> + <dllmap wordsize="32" dll="i:msvcrt" target="/system/lib/libc.so" /> + <dllmap wordsize="64" dll="i:msvcrt" target="/system/lib64/libc.so" /> + <dllmap wordsize="32" dll="i:msvcrt.dll" target="/system/lib/libc.so" /> + <dllmap wordsize="64" dll="i:msvcrt.dll" target="/system/lib64/libc.so" /> + <dllmap wordsize="32" dll="sqlite" target="/system/lib/libsqlite.so" /> + <dllmap wordsize="64" dll="sqlite" target="/system/lib64/libsqlite.so" /> + <dllmap wordsize="32" dll="sqlite3" target="/system/lib/libsqlite.so" /> + <dllmap wordsize="64" dll="sqlite3" target="/system/lib64/libsqlite.so" /> + <dllmap wordsize="32" dll="liblog" target="/system/lib/liblog.so" /> + <dllmap wordsize="64" dll="liblog" target="/system/lib64/liblog.so" /> + <dllmap dll="i:kernel32.dll"> + <dllentry dll="__Internal" name="CopyMemory" target="mono_win32_compat_CopyMemory"/> + <dllentry dll="__Internal" name="FillMemory" target="mono_win32_compat_FillMemory"/> + <dllentry dll="__Internal" name="MoveMemory" target="mono_win32_compat_MoveMemory"/> + <dllentry dll="__Internal" name="ZeroMemory" target="mono_win32_compat_ZeroMemory"/> + </dllmap> +</configuration> diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 4c1ebd8d74..89d56def7d 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -44,9 +44,33 @@ def copy_file(src_dir, dst_dir, name): copy(src_path, dst_dir) +def is_desktop(platform): + return platform in ['windows', 'osx', 'x11', 'server', 'uwp', 'haiku'] + + +def is_unix_like(platform): + return platform in ['osx', 'x11', 'server', 'android', 'haiku'] + + +def module_supports_tools_on(platform): + return platform not in ['android', 'javascript'] + + +def find_wasm_src_dir(mono_root): + hint_dirs = [ + os.path.join(mono_root, 'src'), + os.path.join(mono_root, '../src'), + ] + for hint_dir in hint_dirs: + if os.path.isfile(os.path.join(hint_dir, 'driver.c')): + return hint_dir + return '' + + def configure(env, env_mono): bits = env['bits'] is_android = env['platform'] == 'android' + is_javascript = env['platform'] == 'javascript' tools_enabled = env['tools'] mono_static = env['mono_static'] @@ -63,17 +87,21 @@ def configure(env, env_mono): env_mono.Append(CPPDEFINES=['NO_PENDING_EXCEPTIONS']) if is_android and not env['android_arch'] in android_arch_dirs: - raise RuntimeError('This module does not support for the specified \'android_arch\': ' + env['android_arch']) + raise RuntimeError('This module does not support the specified \'android_arch\': ' + env['android_arch']) - if is_android and tools_enabled: - # TODO: Implement this. We have to add the data directory to the apk, concretely the Api and Tools folders. - raise RuntimeError('This module does not currently support building for android with tools enabled') + if tools_enabled and not module_supports_tools_on(env['platform']): + # TODO: + # Android: We have to add the data directory to the apk, concretely the Api and Tools folders. + raise RuntimeError('This module does not currently support building for this platform with tools enabled') if is_android and mono_static: - # When static linking and doing something that requires libmono-native, we get a dlopen error as libmono-native seems to depend on libmonosgen-2.0 - raise RuntimeError('Linking Mono statically is not currently supported on Android') + # Android: When static linking and doing something that requires libmono-native, we get a dlopen error as libmono-native seems to depend on libmonosgen-2.0 + raise RuntimeError('Statically linking Mono is not currently supported on this platform') + + if is_javascript: + mono_static = True - if (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')) and not mono_prefix: + if not mono_prefix and (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')): print("WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead") if env['platform'] == 'windows': @@ -92,9 +120,9 @@ def configure(env, env_mono): env.Append(LIBPATH=mono_lib_path) env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) - if mono_static: - lib_suffix = Environment()['LIBSUFFIX'] + lib_suffix = Environment()['LIBSUFFIX'] + if mono_static: if env.msvc: mono_static_lib_name = 'libmono-static-sgen' else: @@ -116,13 +144,13 @@ def configure(env, env_mono): env.Append(LIBS=['psapi']) env.Append(LIBS=['version']) else: - mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib') + mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension=lib_suffix) if not mono_lib_name: raise RuntimeError('Could not find mono library in: ' + mono_lib_path) if env.msvc: - env.Append(LINKFLAGS=mono_lib_name + Environment()['LIBSUFFIX']) + env.Append(LINKFLAGS=mono_lib_name + lib_suffix) else: env.Append(LIBS=[mono_lib_name]) @@ -143,7 +171,7 @@ def configure(env, env_mono): mono_lib_path = '' mono_so_name = '' - if not mono_root and is_android: + if not mono_root and (is_android or is_javascript): raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter") if not mono_root and is_apple: @@ -167,7 +195,7 @@ def configure(env, env_mono): mono_lib_path = os.path.join(mono_root, 'lib') - env.Append(LIBPATH=mono_lib_path) + env.Append(LIBPATH=[mono_lib_path]) env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) mono_lib = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension='.a') @@ -178,12 +206,37 @@ def configure(env, env_mono): env_mono.Append(CPPDEFINES=['_REENTRANT']) if mono_static: + env.Append(LINKFLAGS=['-rdynamic']) + mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a') if is_apple: env.Append(LINKFLAGS=['-Wl,-force_load,' + mono_lib_file]) else: + assert is_desktop(env['platform']) or is_android or is_javascript env.Append(LINKFLAGS=['-Wl,-whole-archive', mono_lib_file, '-Wl,-no-whole-archive']) + + if is_javascript: + env.Append(LIBS=['mono-icall-table', 'mono-native', 'mono-ilgen', 'mono-ee-interp']) + + wasm_src_dir = os.path.join(mono_root, 'src') + if not os.path.isdir(wasm_src_dir): + raise RuntimeError('Could not find mono wasm src directory') + + # Ideally this should be defined only for 'driver.c', but I can't fight scons for another 2 hours + env_mono.Append(CPPDEFINES=['CORE_BINDINGS']) + + env_mono.add_source_files(env.modules_sources, [ + os.path.join(wasm_src_dir, 'driver.c'), + os.path.join(wasm_src_dir, 'zlib-helper.c'), + os.path.join(wasm_src_dir, 'corebindings.c') + ]) + + env.Append(LINKFLAGS=[ + '--js-library', os.path.join(wasm_src_dir, 'library_mono.js'), + '--js-library', os.path.join(wasm_src_dir, 'binding_support.js'), + '--js-library', os.path.join(wasm_src_dir, 'dotnet_support.js') + ]) else: env.Append(LIBS=[mono_lib]) @@ -191,6 +244,8 @@ def configure(env, env_mono): env.Append(LIBS=['iconv', 'pthread']) elif is_android: pass # Nothing + elif is_javascript: + env.Append(LIBS=['m', 'rt', 'dl', 'pthread']) else: env.Append(LIBS=['m', 'rt', 'dl', 'pthread']) @@ -228,21 +283,23 @@ def configure(env, env_mono): libs_output_dir = get_android_out_dir(env) if is_android else '#bin' copy_file(mono_lib_path, libs_output_dir, 'lib' + mono_so_name + sharedlib_ext) - env.Append(LINKFLAGS='-rdynamic') - - if not tools_enabled and not is_android: - if not mono_root: - mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip() + if not tools_enabled: + if is_desktop(env['platform']): + if not mono_root: + mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip() - make_template_dir(env, mono_root) - elif not tools_enabled and is_android: - # Compress Android Mono Config - from . import make_android_mono_config - config_file_path = os.path.join(mono_root, 'etc', 'mono', 'config') - make_android_mono_config.generate_compressed_config(config_file_path, 'mono_gd/') + make_template_dir(env, mono_root) + elif is_android: + # Compress Android Mono Config + from . import make_android_mono_config + module_dir = os.getcwd() + config_file_path = os.path.join(module_dir, 'build_scripts', 'mono_android_config.xml') + make_android_mono_config.generate_compressed_config(config_file_path, 'mono_gd/') - # Copy the required shared libraries - copy_mono_shared_libs(env, mono_root, None) + # Copy the required shared libraries + copy_mono_shared_libs(env, mono_root, None) + elif is_javascript: + pass # No data directory for this platform if copy_mono_root: if not mono_root: @@ -251,7 +308,7 @@ def configure(env, env_mono): if tools_enabled: copy_mono_root_files(env, mono_root) else: - print("Ignoring option: 'copy_mono_root'. Only available for builds with 'tools' enabled.") + print("Ignoring option: 'copy_mono_root'; only available for builds with 'tools' enabled.") def make_template_dir(env, mono_root): @@ -262,10 +319,9 @@ def make_template_dir(env, mono_root): template_dir_name = '' - if platform in ['windows', 'osx', 'x11', 'android']: - template_dir_name = 'data.mono.%s.%s.%s' % (platform, env['bits'], target) - else: - assert False + assert is_desktop(platform) + + template_dir_name = 'data.mono.%s.%s.%s' % (platform, env['bits'], target) output_dir = Dir('#bin').abspath template_dir = os.path.join(output_dir, template_dir_name) @@ -278,7 +334,7 @@ def make_template_dir(env, mono_root): # Copy etc/mono/ template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono') - copy_mono_etc_dir(mono_root, template_mono_config_dir, env['platform']) + copy_mono_etc_dir(mono_root, template_mono_config_dir, platform) # Copy the required shared libraries @@ -371,12 +427,19 @@ def copy_mono_shared_libs(env, mono_root, target_mono_root_dir): platform = env['platform'] if platform == 'windows': + src_mono_bin_dir = os.path.join(mono_root, 'bin') target_mono_bin_dir = os.path.join(target_mono_root_dir, 'bin') if not os.path.isdir(target_mono_bin_dir): os.makedirs(target_mono_bin_dir) - copy(os.path.join(mono_root, 'bin', 'MonoPosixHelper.dll'), target_mono_bin_dir) + mono_posix_helper_name = find_file_in_dir(src_mono_bin_dir, ['MonoPosixHelper', 'libMonoPosixHelper'], extension='.dll') + copy(os.path.join(src_mono_bin_dir, mono_posix_helper_name + '.dll'), os.path.join(target_mono_bin_dir, 'MonoPosixHelper.dll')) + + # For newer versions + btls_dll_path = os.path.join(src_mono_bin_dir, 'libmono-btls-shared.dll') + if os.path.isfile(btls_dll_path): + copy(btls_dll_path, target_mono_bin_dir) else: target_mono_lib_dir = get_android_out_dir(env) if platform == 'android' else os.path.join(target_mono_root_dir, 'lib') @@ -386,7 +449,7 @@ def copy_mono_shared_libs(env, mono_root, target_mono_root_dir): if platform == 'osx': # TODO: Make sure nothing is missing copy(os.path.join(mono_root, 'lib', 'libMonoPosixHelper.dylib'), target_mono_lib_dir) - elif platform == 'x11' or platform == 'android': + elif is_unix_like(platform): lib_file_names = [lib_name + '.so' for lib_name in [ 'libmono-btls-shared', 'libmono-ee-interp', 'libmono-native', 'libMonoPosixHelper', 'libmono-profiler-aot', 'libmono-profiler-coverage', 'libmono-profiler-log', 'libMonoSupportW' diff --git a/modules/mono/build_scripts/patches/fix-mono-android-tkill.diff b/modules/mono/build_scripts/patches/fix-mono-android-tkill.diff deleted file mode 100644 index 05f8dcadcc..0000000000 --- a/modules/mono/build_scripts/patches/fix-mono-android-tkill.diff +++ /dev/null @@ -1,70 +0,0 @@ -diff --git a/libgc/include/private/gcconfig.h b/libgc/include/private/gcconfig.h -index e2bdf13ac3e..f962200ba4e 100644 ---- a/libgc/include/private/gcconfig.h -+++ b/libgc/include/private/gcconfig.h -@@ -2255,6 +2255,14 @@ - # define GETPAGESIZE() getpagesize() - # endif - -+#if defined(HOST_ANDROID) && !(__ANDROID_API__ >= 23) \ -+ && ((defined(MIPS) && (CPP_WORDSZ == 32)) \ -+ || defined(ARM32) || defined(I386) /* but not x32 */) -+ /* tkill() exists only on arm32/mips(32)/x86. */ -+ /* NDK r11+ deprecates tkill() but keeps it for Mono clients. */ -+# define USE_TKILL_ON_ANDROID -+#endif -+ - # if defined(SUNOS5) || defined(DRSNX) || defined(UTS4) - /* OS has SVR4 generic features. Probably others also qualify. */ - # define SVR4 -diff --git a/libgc/pthread_stop_world.c b/libgc/pthread_stop_world.c -index f93ce26b562..4a49a6d578c 100644 ---- a/libgc/pthread_stop_world.c -+++ b/libgc/pthread_stop_world.c -@@ -336,7 +336,7 @@ void GC_push_all_stacks() - pthread_t GC_stopping_thread; - int GC_stopping_pid; - --#ifdef HOST_ANDROID -+#ifdef USE_TKILL_ON_ANDROID - static - int android_thread_kill(pid_t tid, int sig) - { -diff --git a/mono/metadata/threads.c b/mono/metadata/threads.c -index ad9b8823f8f..3542b32b540 100644 ---- a/mono/metadata/threads.c -+++ b/mono/metadata/threads.c -@@ -77,8 +77,12 @@ mono_native_thread_join_handle (HANDLE thread_handle, gboolean close_handle); - #include <zircon/syscalls.h> - #endif - --#if defined(HOST_ANDROID) && !defined(TARGET_ARM64) && !defined(TARGET_AMD64) --#define USE_TKILL_ON_ANDROID 1 -+#if defined(HOST_ANDROID) && !(__ANDROID_API__ >= 23) \ -+ && ((defined(MIPS) && (CPP_WORDSZ == 32)) \ -+ || defined(ARM32) || defined(I386) /* but not x32 */) -+ /* tkill() exists only on arm32/mips(32)/x86. */ -+ /* NDK r11+ deprecates tkill() but keeps it for Mono clients. */ -+# define USE_TKILL_ON_ANDROID - #endif - - #ifdef HOST_ANDROID -diff --git a/mono/utils/mono-threads-posix.c b/mono/utils/mono-threads-posix.c -index 3e4bf93de5f..79c9f731fe7 100644 ---- a/mono/utils/mono-threads-posix.c -+++ b/mono/utils/mono-threads-posix.c -@@ -31,8 +31,12 @@ - - #include <errno.h> - --#if defined(HOST_ANDROID) && !defined(TARGET_ARM64) && !defined(TARGET_AMD64) --#define USE_TKILL_ON_ANDROID 1 -+#if defined(HOST_ANDROID) && !(__ANDROID_API__ >= 23) \ -+ && ((defined(MIPS) && (CPP_WORDSZ == 32)) \ -+ || defined(ARM32) || defined(I386) /* but not x32 */) -+ /* tkill() exists only on arm32/mips(32)/x86. */ -+ /* NDK r11+ deprecates tkill() but keeps it for Mono clients. */ -+# define USE_TKILL_ON_ANDROID - #endif - - #ifdef USE_TKILL_ON_ANDROID diff --git a/modules/mono/config.py b/modules/mono/config.py index 9adf4ee6e5..70cb464c7a 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -1,10 +1,11 @@ def can_build(env, platform): - if platform in ['javascript']: - return False # Not yet supported return True def configure(env): + if env['platform'] not in ['windows', 'osx', 'x11', 'server', 'android', 'haiku', 'javascript']: + raise RuntimeError('This module does not currently support building for this platform') + env.use_ptrcall = True env.add_module_version_string('mono') @@ -18,6 +19,13 @@ def configure(env): envvars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False)) envvars.Update(env) + if env['platform'] == 'javascript': + # Mono wasm already has zlib builtin, so we need this workaround to avoid symbol collisions + print('Compiling with Mono wasm disables \'builtin_zlib\'') + env['builtin_zlib'] = False + thirdparty_zlib_dir = "#thirdparty/zlib/" + env.Prepend(CPPPATH=[thirdparty_zlib_dir]) + def get_doc_classes(): return [ diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 83be10dee3..4536614379 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -50,8 +50,10 @@ #include "editor/editor_internal_calls.h" #include "godotsharp_dirs.h" +#include "mono_gd/gd_mono_cache.h" #include "mono_gd/gd_mono_class.h" #include "mono_gd/gd_mono_marshal.h" +#include "mono_gd/gd_mono_utils.h" #include "signal_awaiter_utils.h" #include "utils/macros.h" #include "utils/mutex_utils.h" @@ -126,12 +128,11 @@ void CSharpLanguage::init() { print_line("Run this binary with '--generate-mono-glue path/to/modules/mono/glue'"); #endif - gdmono->initialize_load_assemblies(); + if (gdmono->is_runtime_initialized()) + gdmono->initialize_load_assemblies(); #ifdef TOOLS_ENABLED EditorNode::add_init_callback(&_editor_init_callback); - - GLOBAL_DEF("mono/export/include_scripts_content", false); #endif } @@ -545,7 +546,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() #ifdef DEBUG_ENABLED _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); - if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoUtils::mono_cache.corlib_cache_updated) + if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated) return Vector<StackInfo>(); MonoObject *stack_trace = mono_object_new(mono_domain_get(), CACHED_CLASS(System_Diagnostics_StackTrace)->get_mono_ptr()); @@ -571,7 +572,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec MonoException *exc = NULL; - MonoArray *frames = invoke_method_thunk(CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames), p_stack_trace, &exc); + MonoArray *frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames).invoke(p_stack_trace, &exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); @@ -583,8 +584,6 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec if (frame_count <= 0) return Vector<StackInfo>(); - GDMonoUtils::DebugUtils_StackFrameInfo get_sf_info = CACHED_METHOD_THUNK(DebuggingUtils, GetStackFrameInfo); - Vector<StackInfo> si; si.resize(frame_count); @@ -595,7 +594,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec MonoString *file_name; int file_line_num; MonoString *method_decl; - invoke_method_thunk(get_sf_info, frame, &file_name, &file_line_num, &method_decl, &exc); + CACHED_METHOD_THUNK(DebuggingUtils, GetStackFrameInfo).invoke(frame, &file_name, &file_line_num, &method_decl, &exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); @@ -618,14 +617,14 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec void CSharpLanguage::frame() { if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) { - const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle; + const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle; if (task_scheduler_handle.is_valid()) { MonoObject *task_scheduler = task_scheduler_handle->get_target(); if (task_scheduler) { MonoException *exc = NULL; - invoke_method_thunk(CACHED_METHOD_THUNK(GodotTaskScheduler, Activate), task_scheduler, &exc); + CACHED_METHOD_THUNK(GodotTaskScheduler, Activate).invoke(task_scheduler, &exc); if (exc) { GDMonoUtils::debug_unhandled_exception(exc); @@ -1079,7 +1078,7 @@ bool CSharpLanguage::overrides_external_editor() { void CSharpLanguage::thread_enter() { #if 0 - if (mono->is_runtime_initialized()) { + if (gdmono->is_runtime_initialized()) { GDMonoUtils::attach_current_thread(); } #endif @@ -1088,7 +1087,7 @@ void CSharpLanguage::thread_enter() { void CSharpLanguage::thread_exit() { #if 0 - if (mono->is_runtime_initialized()) { + if (gdmono->is_runtime_initialized()) { GDMonoUtils::detach_current_thread(); } #endif diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index 9a2b2e3a26..da90c960e5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -44,7 +44,7 @@ namespace GodotTools.Build { get { - if (OS.IsWindows()) + if (OS.IsWindows) { return (BuildManager.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool") == BuildManager.BuildTool.MsBuildMono; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index eb2c2dd77c..ad8a6516ab 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -21,7 +21,7 @@ namespace GodotTools.Build var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); var buildTool = (BuildManager.BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); - if (OS.IsWindows()) + if (OS.IsWindows) { switch (buildTool) { @@ -59,7 +59,7 @@ namespace GodotTools.Build } } - if (OS.IsUnix()) + if (OS.IsUnixLike()) { if (buildTool == BuildManager.BuildTool.MsBuildMono) { @@ -91,7 +91,7 @@ namespace GodotTools.Build { var result = new List<string>(); - if (OS.IsOSX()) + if (OS.IsOSX) { result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/"); result.Add("/usr/local/var/homebrew/linked/mono/bin/"); @@ -128,7 +128,7 @@ namespace GodotTools.Build private static string FindMsBuildToolsPathOnWindows() { - if (!OS.IsWindows()) + if (!OS.IsWindows) throw new PlatformNotSupportedException(); // Try to find 15.0 with vswhere diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs index ab37d89955..217bf5c144 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs @@ -246,7 +246,7 @@ namespace GodotTools { // Build tool settings - EditorDef("mono/builds/build_tool", OS.IsWindows() ? BuildTool.MsBuildVs : BuildTool.MsBuildMono); + EditorDef("mono/builds/build_tool", OS.IsWindows ? BuildTool.MsBuildVs : BuildTool.MsBuildMono); var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); @@ -255,7 +255,7 @@ namespace GodotTools ["type"] = Godot.Variant.Type.Int, ["name"] = "mono/builds/build_tool", ["hint"] = Godot.PropertyHint.Enum, - ["hint_string"] = OS.IsWindows() ? + ["hint_string"] = OS.IsWindows ? $"{PropNameMsbuildMono},{PropNameMsbuildVs}" : $"{PropNameMsbuildMono}" }); diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs new file mode 100644 index 0000000000..c7e8ea511a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -0,0 +1,676 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using GodotTools.Core; +using GodotTools.Internals; +using static GodotTools.Internals.Globals; +using Directory = GodotTools.Utils.Directory; +using File = GodotTools.Utils.File; +using OS = GodotTools.Utils.OS; +using Path = System.IO.Path; + +namespace GodotTools.Export +{ + public class ExportPlugin : EditorExportPlugin + { + public void RegisterExportSettings() + { + // TODO: These would be better as export preset options, but that doesn't seem to be supported yet + + GlobalDef("mono/export/include_scripts_content", false); + GlobalDef("mono/export/export_assemblies_inside_pck", true); + + GlobalDef("mono/export/aot/enabled", false); + GlobalDef("mono/export/aot/full_aot", false); + + // --aot or --aot=opt1,opt2 (use 'mono --aot=help AuxAssembly.dll' to list AOT options) + GlobalDef("mono/export/aot/extra_aot_options", new string[] { }); + // --optimize/-O=opt1,opt2 (use 'mono --list-opt'' to list optimize options) + GlobalDef("mono/export/aot/extra_optimizer_options", new string[] { }); + + GlobalDef("mono/export/aot/android_toolchain_path", ""); + } + + private string maybeLastExportError; + + private void AddFile(string srcPath, string dstPath, bool remap = false) + { + AddFile(dstPath, File.ReadAllBytes(srcPath), remap); + } + + public override void _ExportFile(string path, string type, string[] features) + { + base._ExportFile(path, type, features); + + if (type != Internal.CSharpLanguageType) + return; + + if (Path.GetExtension(path) != $".{Internal.CSharpLanguageExtension}") + throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path)); + + // TODO What if the source file is not part of the game's C# project + + bool includeScriptsContent = (bool) ProjectSettings.GetSetting("mono/export/include_scripts_content"); + + if (!includeScriptsContent) + { + // We don't want to include the source code on exported games. + + // Sadly, Godot prints errors when adding an empty file (nothing goes wrong, it's just noise). + // Because of this, we add a file which contains a line break. + AddFile(path, System.Text.Encoding.UTF8.GetBytes("\n"), remap: false); + Skip(); + } + } + + public override void _ExportBegin(string[] features, bool isDebug, string path, int flags) + { + base._ExportBegin(features, isDebug, path, flags); + + try + { + _ExportBeginImpl(features, isDebug, path, flags); + } + catch (Exception e) + { + maybeLastExportError = e.Message; + GD.PushError($"Failed to export project: {e.Message}"); + Console.Error.WriteLine(e); + // TODO: Do something on error once _ExportBegin supports failing. + } + } + + private void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags) + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return; + + string platform = DeterminePlatformFromFeatures(features); + + if (platform == null) + throw new NotSupportedException("Target platform not supported"); + + string outputDir = new FileInfo(path).Directory?.FullName ?? + throw new FileNotFoundException("Base directory not found"); + + string buildConfig = isDebug ? "Debug" : "Release"; + + string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); + + AddFile(scriptsMetadataPath, scriptsMetadataPath); + + // Turn export features into defines + var godotDefines = features; + + if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines)) + throw new Exception("Failed to build project"); + + // Add dependency assemblies + + var dependencies = new Godot.Collections.Dictionary<string, string>(); + + var projectDllName = (string) ProjectSettings.GetSetting("application/config/name"); + if (projectDllName.Empty()) + { + projectDllName = "UnnamedProject"; + } + + string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig); + string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll"); + + dependencies[projectDllName] = projectDllSrcPath; + + if (platform == OS.Platforms.Android) + { + string godotAndroidExtProfileDir = GetBclProfileDir("godot_android_ext"); + string monoAndroidAssemblyPath = Path.Combine(godotAndroidExtProfileDir, "Mono.Android.dll"); + + if (!File.Exists(monoAndroidAssemblyPath)) + throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath); + + dependencies["Mono.Android"] = monoAndroidAssemblyPath; + } + + var initialDependencies = dependencies.Duplicate(); + internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, DeterminePlatformBclDir(platform), dependencies); + + string outputDataDir = null; + + if (PlatformHasTemplateDir(platform)) + outputDataDir = ExportDataDirectory(features, platform, isDebug, outputDir); + + string apiConfig = isDebug ? "Debug" : "Release"; + string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig); + + bool assembliesInsidePck = (bool) ProjectSettings.GetSetting("mono/export/export_assemblies_inside_pck") || outputDataDir == null; + + if (!assembliesInsidePck) + { + string outputDataGameAssembliesDir = Path.Combine(outputDataDir, "Assemblies"); + if (!Directory.Exists(outputDataGameAssembliesDir)) + Directory.CreateDirectory(outputDataGameAssembliesDir); + } + + foreach (var dependency in dependencies) + { + string dependSrcPath = dependency.Value; + + if (assembliesInsidePck) + { + string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile()); + AddFile(dependSrcPath, dependDstPath); + } + else + { + string dependDstPath = Path.Combine(outputDataDir, "Assemblies", dependSrcPath.GetFile()); + File.Copy(dependSrcPath, dependDstPath); + } + } + + // AOT + + if ((bool) ProjectSettings.GetSetting("mono/export/aot/enabled")) + { + AotCompileDependencies(features, platform, isDebug, outputDir, outputDataDir, dependencies); + } + } + + public override void _ExportEnd() + { + base._ExportEnd(); + + string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}"); + + if (Directory.Exists(aotTempDir)) + Directory.Delete(aotTempDir, recursive: true); + + // TODO: Just a workaround until the export plugins can be made to abort with errors + if (!string.IsNullOrEmpty(maybeLastExportError)) // Check empty as well, because it's set to empty after hot-reloading + { + string lastExportError = maybeLastExportError; + maybeLastExportError = null; + + GodotSharpEditor.Instance.ShowErrorDialog(lastExportError, "Failed to export C# project"); + } + } + + private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir) + { + string target = isDebug ? "release_debug" : "release"; + + // NOTE: Bits is ok for now as all platforms with a data directory have it, but that may change in the future. + string bits = features.Contains("64") ? "64" : "32"; + + string TemplateDirName() => $"data.mono.{platform}.{bits}.{target}"; + + string templateDirPath = Path.Combine(Internal.FullTemplatesDir, TemplateDirName()); + bool validTemplatePathFound = true; + + if (!Directory.Exists(templateDirPath)) + { + validTemplatePathFound = false; + + if (isDebug) + { + target = "debug"; // Support both 'release_debug' and 'debug' for the template data directory name + templateDirPath = Path.Combine(Internal.FullTemplatesDir, TemplateDirName()); + validTemplatePathFound = true; + + if (!Directory.Exists(templateDirPath)) + validTemplatePathFound = false; + } + } + + if (!validTemplatePathFound) + throw new FileNotFoundException("Data template directory not found", templateDirPath); + + string outputDataDir = Path.Combine(outputDir, DataDirName); + + if (Directory.Exists(outputDataDir)) + Directory.Delete(outputDataDir, recursive: true); // Clean first + + Directory.CreateDirectory(outputDataDir); + + foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories)) + { + Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1))); + } + + foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories)) + { + File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1))); + } + + return outputDataDir; + } + + private void AotCompileDependencies(string[] features, string platform, bool isDebug, string outputDir, string outputDataDir, IDictionary<string, string> dependencies) + { + // TODO: WASM + + string bclDir = DeterminePlatformBclDir(platform) ?? typeof(object).Assembly.Location.GetBaseDir(); + + string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}"); + + if (!Directory.Exists(aotTempDir)) + Directory.CreateDirectory(aotTempDir); + + var assemblies = new Dictionary<string, string>(); + + foreach (var dependency in dependencies) + { + string assemblyName = dependency.Key; + string assemblyPath = dependency.Value; + + string assemblyPathInBcl = Path.Combine(bclDir, assemblyName + ".dll"); + + if (File.Exists(assemblyPathInBcl)) + { + // Don't create teporaries for assemblies from the BCL + assemblies.Add(assemblyName, assemblyPathInBcl); + } + else + { + string tempAssemblyPath = Path.Combine(aotTempDir, assemblyName + ".dll"); + File.Copy(assemblyPath, tempAssemblyPath); + assemblies.Add(assemblyName, tempAssemblyPath); + } + } + + foreach (var assembly in assemblies) + { + string assemblyName = assembly.Key; + string assemblyPath = assembly.Value; + + string sharedLibExtension = platform == OS.Platforms.Windows ? ".dll" : + platform == OS.Platforms.OSX ? ".dylib" : + platform == OS.Platforms.HTML5 ? ".wasm" : + ".so"; + + string outputFileName = assemblyName + ".dll" + sharedLibExtension; + + if (platform == OS.Platforms.Android) + { + // Not sure if the 'lib' prefix is an Android thing or just Godot being picky, + // but we use '-aot-' as well just in case to avoid conflicts with other libs. + outputFileName = "lib-aot-" + outputFileName; + } + + string outputFilePath = null; + string tempOutputFilePath; + + switch (platform) + { + case OS.Platforms.OSX: + tempOutputFilePath = Path.Combine(aotTempDir, outputFileName); + break; + case OS.Platforms.Android: + tempOutputFilePath = Path.Combine(aotTempDir, "%%ANDROID_ABI%%", outputFileName); + break; + case OS.Platforms.HTML5: + tempOutputFilePath = Path.Combine(aotTempDir, outputFileName); + outputFilePath = Path.Combine(outputDir, outputFileName); + break; + default: + tempOutputFilePath = Path.Combine(aotTempDir, outputFileName); + outputFilePath = Path.Combine(outputDataDir, "Mono", platform == OS.Platforms.Windows ? "bin" : "lib", outputFileName); + break; + } + + var data = new Dictionary<string, string>(); + var enabledAndroidAbis = platform == OS.Platforms.Android ? GetEnabledAndroidAbis(features).ToArray() : null; + + if (platform == OS.Platforms.Android) + { + Debug.Assert(enabledAndroidAbis != null); + + foreach (var abi in enabledAndroidAbis) + { + data["abi"] = abi; + var outputFilePathForThisAbi = tempOutputFilePath.Replace("%%ANDROID_ABI%%", abi); + + AotCompileAssembly(platform, isDebug, data, assemblyPath, outputFilePathForThisAbi); + + AddSharedObject(outputFilePathForThisAbi, tags: new[] {abi}); + } + } + else + { + string bits = features.Contains("64") ? "64" : features.Contains("64") ? "32" : null; + + if (bits != null) + data["bits"] = bits; + + AotCompileAssembly(platform, isDebug, data, assemblyPath, tempOutputFilePath); + + if (platform == OS.Platforms.OSX) + { + AddSharedObject(tempOutputFilePath, tags: null); + } + else + { + Debug.Assert(outputFilePath != null); + File.Copy(tempOutputFilePath, outputFilePath); + } + } + } + } + + private static void AotCompileAssembly(string platform, bool isDebug, Dictionary<string, string> data, string assemblyPath, string outputFilePath) + { + // Make sure the output directory exists + Directory.CreateDirectory(outputFilePath.GetBaseDir()); + + string exeExt = OS.IsWindows ? ".exe" : string.Empty; + + string monoCrossDirName = DetermineMonoCrossDirName(platform, data); + string monoCrossRoot = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", monoCrossDirName); + string monoCrossBin = Path.Combine(monoCrossRoot, "bin"); + + string toolPrefix = DetermineToolPrefix(monoCrossBin); + string monoExeName = System.IO.File.Exists(Path.Combine(monoCrossBin, $"{toolPrefix}mono{exeExt}")) ? "mono" : "mono-sgen"; + + string compilerCommand = Path.Combine(monoCrossBin, $"{toolPrefix}{monoExeName}{exeExt}"); + + bool fullAot = (bool) ProjectSettings.GetSetting("mono/export/aot/full_aot"); + + string EscapeOption(string option) => option.Contains(',') ? $"\"{option}\"" : option; + string OptionsToString(IEnumerable<string> options) => string.Join(",", options.Select(EscapeOption)); + + var aotOptions = new List<string>(); + var optimizerOptions = new List<string>(); + + if (fullAot) + aotOptions.Add("full"); + + aotOptions.Add(isDebug ? "soft-debug" : "nodebug"); + + if (platform == OS.Platforms.Android) + { + string abi = data["abi"]; + + string androidToolchain = (string) ProjectSettings.GetSetting("mono/export/aot/android_toolchain_path"); + + if (string.IsNullOrEmpty(androidToolchain)) + { + androidToolchain = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "android-toolchains", $"{abi}"); // TODO: $"{abi}-{apiLevel}{(clang?"clang":"")}" + + if (!Directory.Exists(androidToolchain)) + throw new FileNotFoundException("Missing android toolchain. Specify one in the AOT export settings."); + } + else if (!Directory.Exists(androidToolchain)) + { + throw new FileNotFoundException("Android toolchain not found: " + androidToolchain); + } + + var androidToolPrefixes = new Dictionary<string, string> + { + ["armeabi-v7a"] = "arm-linux-androideabi-", + ["arm64-v8a"] = "aarch64-linux-android-", + ["x86"] = "i686-linux-android-", + ["x86_64"] = "x86_64-linux-android-" + }; + + aotOptions.Add("tool-prefix=" + Path.Combine(androidToolchain, "bin", androidToolPrefixes[abi])); + + string triple = GetAndroidTriple(abi); + aotOptions.Add ($"mtriple={triple}"); + } + + aotOptions.Add($"outfile={outputFilePath}"); + + var extraAotOptions = (string[]) ProjectSettings.GetSetting("mono/export/aot/extra_aot_options"); + var extraOptimizerOptions = (string[]) ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options"); + + if (extraAotOptions.Length > 0) + aotOptions.AddRange(extraAotOptions); + + if (extraOptimizerOptions.Length > 0) + optimizerOptions.AddRange(extraOptimizerOptions); + + var compilerArgs = new List<string>(); + + if (isDebug) + compilerArgs.Add("--debug"); // Required for --aot=soft-debug + + compilerArgs.Add(aotOptions.Count > 0 ? $"--aot={OptionsToString(aotOptions)}" : "--aot"); + + if (optimizerOptions.Count > 0) + compilerArgs.Add($"-O={OptionsToString(optimizerOptions)}"); + + compilerArgs.Add(ProjectSettings.GlobalizePath(assemblyPath)); + + // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead + string CmdLineArgsToString(IEnumerable<string> args) + { + // Not perfect, but as long as we are careful... + return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); + } + + using (var process = new Process()) + { + process.StartInfo = new ProcessStartInfo(compilerCommand, CmdLineArgsToString(compilerArgs)) + { + UseShellExecute = false + }; + + string platformBclDir = DeterminePlatformBclDir(platform); + process.StartInfo.EnvironmentVariables.Add("MONO_PATH", string.IsNullOrEmpty(platformBclDir) ? + typeof(object).Assembly.Location.GetBaseDir() : + platformBclDir); + + Console.WriteLine($"Running: \"{process.StartInfo.FileName}\" {process.StartInfo.Arguments}"); + + if (!process.Start()) + throw new Exception("Failed to start process for Mono AOT compiler"); + + process.WaitForExit(); + + if (process.ExitCode != 0) + throw new Exception($"Mono AOT compiler exited with error code: {process.ExitCode}"); + + if (!System.IO.File.Exists(outputFilePath)) + throw new Exception("Mono AOT compiler finished successfully but the output file is missing"); + } + } + + private static string DetermineMonoCrossDirName(string platform, IReadOnlyDictionary<string, string> data) + { + switch (platform) + { + case OS.Platforms.Windows: + case OS.Platforms.UWP: + { + string arch = data["bits"] == "64" ? "x86_64" : "i686"; + return $"windows-{arch}"; + } + case OS.Platforms.OSX: + { + string arch = "x86_64"; + return $"{platform}-{arch}"; + } + case OS.Platforms.X11: + case OS.Platforms.Server: + { + string arch = data["bits"] == "64" ? "x86_64" : "i686"; + return $"linux-{arch}"; + } + case OS.Platforms.Haiku: + { + string arch = data["bits"] == "64" ? "x86_64" : "i686"; + return $"{platform}-{arch}"; + } + case OS.Platforms.Android: + { + string abi = data["abi"]; + return $"{platform}-{abi}"; + } + case OS.Platforms.HTML5: + return "wasm-wasm32"; + default: + throw new NotSupportedException(); + } + } + + private static string DetermineToolPrefix(string monoCrossBin) + { + string exeExt = OS.IsWindows ? ".exe" : string.Empty; + + if (System.IO.File.Exists(Path.Combine(monoCrossBin, $"mono{exeExt}"))) + return string.Empty; + + if (System.IO.File.Exists(Path.Combine(monoCrossBin, $"mono-sgen{exeExt}" + exeExt))) + return string.Empty; + + var files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono{exeExt}" + exeExt, SearchOption.TopDirectoryOnly); + if (files.Length > 0) + { + string fileName = files[0].Name; + return fileName.Substring(0, fileName.Length - $"mono{exeExt}".Length); + } + + files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono-sgen{exeExt}" + exeExt, SearchOption.TopDirectoryOnly); + if (files.Length > 0) + { + string fileName = files[0].Name; + return fileName.Substring(0, fileName.Length - $"mono-sgen{exeExt}".Length); + } + + throw new FileNotFoundException($"Cannot find the mono runtime executable in {monoCrossBin}"); + } + + private static IEnumerable<string> GetEnabledAndroidAbis(string[] features) + { + var androidAbis = new[] + { + "armeabi-v7a", + "arm64-v8a", + "x86", + "x86_64" + }; + + return androidAbis.Where(features.Contains); + } + + private static string GetAndroidTriple(string abi) + { + var abiArchs = new Dictionary<string, string> + { + ["armeabi-v7a"] = "armv7", + ["arm64-v8a"] = "aarch64-v8a", + ["x86"] = "i686", + ["x86_64"] = "x86_64" + }; + + string arch = abiArchs[abi]; + + return $"{arch}-linux-android"; + } + + private static bool PlatformHasTemplateDir(string platform) + { + // OSX export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest. + return !new[] {OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.HTML5}.Contains(platform); + } + + private static string DeterminePlatformFromFeatures(IEnumerable<string> features) + { + foreach (var feature in features) + { + if (OS.PlatformNameMap.TryGetValue(feature, out string platform)) + return platform; + } + + return null; + } + + private static string GetBclProfileDir(string profile) + { + string templatesDir = Internal.FullTemplatesDir; + return Path.Combine(templatesDir, "bcl", profile); + } + + private static string DeterminePlatformBclDir(string platform) + { + string templatesDir = Internal.FullTemplatesDir; + string platformBclDir = Path.Combine(templatesDir, "bcl", platform); + + if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll"))) + { + string profile = DeterminePlatformBclProfile(platform); + platformBclDir = Path.Combine(templatesDir, "bcl", profile); + + if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll"))) + { + if (PlatformRequiresCustomBcl(platform)) + throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}"); + + platformBclDir = null; // Use the one we're running on + } + } + + return platformBclDir; + } + + /// <summary> + /// Determines whether the BCL bundled with the Godot editor can be used for the target platform, + /// or if it requires a custom BCL that must be distributed with the export templates. + /// </summary> + private static bool PlatformRequiresCustomBcl(string platform) + { + if (new[] {OS.Platforms.Android, OS.Platforms.HTML5}.Contains(platform)) + return true; + + // The 'net_4_x' BCL is not compatible between Windows and the other platforms. + // We use the names 'net_4_x_win' and 'net_4_x' to differentiate between the two. + + bool isWinOrUwp = new[] + { + OS.Platforms.Windows, + OS.Platforms.UWP + }.Contains(platform); + + return OS.IsWindows ? !isWinOrUwp : isWinOrUwp; + } + + private static string DeterminePlatformBclProfile(string platform) + { + switch (platform) + { + case OS.Platforms.Windows: + case OS.Platforms.UWP: + return "net_4_x_win"; + case OS.Platforms.OSX: + case OS.Platforms.X11: + case OS.Platforms.Server: + case OS.Platforms.Haiku: + return "net_4_x"; + case OS.Platforms.Android: + return "monodroid"; + case OS.Platforms.HTML5: + return "wasm"; + default: + throw new NotSupportedException(); + } + } + + private static string DataDirName + { + get + { + var appName = (string) ProjectSettings.GetSetting("application/config/name"); + string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false); + return $"data_{appNameSafe}"; + } + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialDependencies, + string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencies); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs index 4312ca0230..bb218c2f19 100644 --- a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs +++ b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs @@ -6,6 +6,7 @@ namespace GodotTools VisualStudio, // TODO (Windows-only) VisualStudioForMac, // Mac-only MonoDevelop, - VsCode + VsCode, + Rider } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 12edd651df..660971d912 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -1,12 +1,15 @@ using Godot; +using GodotTools.Export; using GodotTools.Utils; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using GodotTools.Ides; +using GodotTools.Ides.Rider; using GodotTools.Internals; using GodotTools.ProjectEditor; +using JetBrains.Annotations; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; using OS = GodotTools.Utils.OS; @@ -188,6 +191,7 @@ namespace GodotTools "code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss" }; + [UsedImplicitly] public Error OpenInExternalEditor(Script script, int line, int col) { var editor = (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor"); @@ -201,6 +205,12 @@ namespace GodotTools throw new NotSupportedException(); case ExternalEditorId.VisualStudioForMac: goto case ExternalEditorId.MonoDevelop; + case ExternalEditorId.Rider: + { + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + RiderPathManager.OpenFile(GodotSharpDirs.ProjectSlnPath, scriptPath, line); + return Error.Ok; + } case ExternalEditorId.MonoDevelop: { string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); @@ -225,7 +235,7 @@ namespace GodotTools bool osxAppBundleInstalled = false; - if (OS.IsOSX()) + if (OS.IsOSX) { // The package path is '/Applications/Visual Studio Code.app' const string vscodeBundleId = "com.microsoft.VSCode"; @@ -265,7 +275,7 @@ namespace GodotTools string command; - if (OS.IsOSX()) + if (OS.IsOSX) { if (!osxAppBundleInstalled && _vsCodePath.Empty()) { @@ -305,6 +315,7 @@ namespace GodotTools return Error.Ok; } + [UsedImplicitly] public bool OverridesExternalEditor() { return (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None; @@ -415,21 +426,24 @@ namespace GodotTools string settingsHintStr = "Disabled"; - if (OS.IsWindows()) + if (OS.IsWindows) { settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}" + + $",JetBrains Rider:{(int) ExternalEditorId.Rider}"; } - else if (OS.IsOSX()) + else if (OS.IsOSX) { settingsHintStr += $",Visual Studio:{(int) ExternalEditorId.VisualStudioForMac}" + $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}" + + $",JetBrains Rider:{(int) ExternalEditorId.Rider}"; } - else if (OS.IsUnix()) + else if (OS.IsUnixLike()) { settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}" + + $",JetBrains Rider:{(int) ExternalEditorId.Rider}"; } editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary @@ -441,11 +455,13 @@ namespace GodotTools }); // Export plugin - var exportPlugin = new GodotSharpExport(); + var exportPlugin = new ExportPlugin(); AddExportPlugin(exportPlugin); + exportPlugin.RegisterExportSettings(); exportPluginWeak = WeakRef(exportPlugin); BuildManager.Initialize(); + RiderPathManager.Initialize(); GodotIdeManager = new GodotIdeManager(); AddChild(GodotIdeManager); @@ -461,7 +477,7 @@ namespace GodotTools // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid // will be freed after EditorSettings already was, and its device polling thread // will try to access the EditorSettings singleton, resulting in null dereferencing. - (exportPluginWeak.GetRef() as GodotSharpExport)?.Dispose(); + (exportPluginWeak.GetRef() as ExportPlugin)?.Dispose(); exportPluginWeak.Dispose(); } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs deleted file mode 100644 index 4f93ef8530..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs +++ /dev/null @@ -1,197 +0,0 @@ -using Godot; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using GodotTools.Core; -using GodotTools.Internals; -using Directory = GodotTools.Utils.Directory; -using File = GodotTools.Utils.File; -using Path = System.IO.Path; - -namespace GodotTools -{ - public class GodotSharpExport : EditorExportPlugin - { - private void AddFile(string srcPath, string dstPath, bool remap = false) - { - AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap); - } - - public override void _ExportFile(string path, string type, string[] features) - { - base._ExportFile(path, type, features); - - if (type != Internal.CSharpLanguageType) - return; - - if (Path.GetExtension(path) != $".{Internal.CSharpLanguageExtension}") - throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path)); - - // TODO What if the source file is not part of the game's C# project - - bool includeScriptsContent = (bool) ProjectSettings.GetSetting("mono/export/include_scripts_content"); - - if (!includeScriptsContent) - { - // We don't want to include the source code on exported games - AddFile(path, new byte[] { }, remap: false); - Skip(); - } - } - - public override void _ExportBegin(string[] features, bool isDebug, string path, int flags) - { - base._ExportBegin(features, isDebug, path, flags); - - try - { - _ExportBeginImpl(features, isDebug, path, flags); - } - catch (Exception e) - { - GD.PushError($"Failed to export project. Exception message: {e.Message}"); - Console.Error.WriteLine(e); - } - } - - public void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags) - { - // TODO Right now there is no way to stop the export process with an error - - if (File.Exists(GodotSharpDirs.ProjectSlnPath)) - { - string buildConfig = isDebug ? "Debug" : "Release"; - - string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); - CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); - - AddFile(scriptsMetadataPath, scriptsMetadataPath); - - // Turn export features into defines - var godotDefines = features; - - if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines)) - { - GD.PushError("Failed to build project"); - return; - } - - // Add dependency assemblies - - var dependencies = new Godot.Collections.Dictionary<string, string>(); - - var projectDllName = (string) ProjectSettings.GetSetting("application/config/name"); - if (projectDllName.Empty()) - { - projectDllName = "UnnamedProject"; - } - - string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig); - string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll"); - - dependencies[projectDllName] = projectDllSrcPath; - - { - string templatesDir = Internal.FullTemplatesDir; - string androidBclDir = Path.Combine(templatesDir, "android-bcl"); - - string customLibDir = features.Contains("Android") && Directory.Exists(androidBclDir) ? androidBclDir : string.Empty; - - GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies); - } - - string apiConfig = isDebug ? "Debug" : "Release"; - string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig); - - foreach (var dependency in dependencies) - { - string dependSrcPath = dependency.Value; - string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile()); - AddFile(dependSrcPath, dependDstPath); - } - } - - // Mono specific export template extras (data dir) - ExportDataDirectory(features, isDebug, path); - } - - private static void ExportDataDirectory(IEnumerable<string> features, bool debug, string path) - { - var featureSet = new HashSet<string>(features); - - if (!PlatformHasTemplateDir(featureSet)) - return; - - string templateDirName = "data.mono"; - - if (featureSet.Contains("Windows")) - { - templateDirName += ".windows"; - templateDirName += featureSet.Contains("64") ? ".64" : ".32"; - } - else if (featureSet.Contains("X11")) - { - templateDirName += ".x11"; - templateDirName += featureSet.Contains("64") ? ".64" : ".32"; - } - else - { - throw new NotSupportedException("Target platform not supported"); - } - - templateDirName += debug ? ".release_debug" : ".release"; - - string templateDirPath = Path.Combine(Internal.FullTemplatesDir, templateDirName); - - if (!Directory.Exists(templateDirPath)) - throw new FileNotFoundException("Data template directory not found"); - - string outputDir = new FileInfo(path).Directory?.FullName ?? - throw new FileNotFoundException("Base directory not found"); - - string outputDataDir = Path.Combine(outputDir, DataDirName); - - if (Directory.Exists(outputDataDir)) - Directory.Delete(outputDataDir, recursive: true); // Clean first - - Directory.CreateDirectory(outputDataDir); - - foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories)) - { - Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1))); - } - - foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories)) - { - File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1))); - } - } - - private static bool PlatformHasTemplateDir(IEnumerable<string> featureSet) - { - // OSX export templates are contained in a zip, so we place - // our custom template inside it and let Godot do the rest. - return !featureSet.Any(f => new[] {"OSX", "Android"}.Contains(f)); - } - - private static string DataDirName - { - get - { - var appName = (string) ProjectSettings.GetSetting("application/config/name"); - string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false); - return $"data_{appNameSafe}"; - } - } - - private static void GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath, - string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies) => - internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath, - string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies); - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index 3c57900873..be2b70529e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -30,6 +30,15 @@ <ConsolePause>false</ConsolePause> </PropertyGroup> <ItemGroup> + <Reference Include="JetBrains.Annotations, Version=2019.1.3.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325"> + <HintPath>..\packages\JetBrains.Annotations.2019.1.3\lib\net20\JetBrains.Annotations.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Mono.Posix" /> + <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed"> + <HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="System" /> <Reference Include="GodotSharp"> <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath> @@ -40,11 +49,14 @@ </ItemGroup> <ItemGroup> <Compile Include="Build\MsBuildFinder.cs" /> + <Compile Include="Export\ExportPlugin.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="Ides\Rider\RiderPathLocator.cs" /> + <Compile Include="Ides\Rider\RiderPathManager.cs" /> <Compile Include="Internals\BindingsGenerator.cs" /> <Compile Include="Internals\EditorProgress.cs" /> <Compile Include="Internals\GodotSharpDirs.cs" /> @@ -63,9 +75,9 @@ <Compile Include="BuildInfo.cs" /> <Compile Include="BuildTab.cs" /> <Compile Include="BottomPanel.cs" /> - <Compile Include="GodotSharpExport.cs" /> <Compile Include="CsProjOperations.cs" /> <Compile Include="Utils\CollectionExtensions.cs" /> + <Compile Include="Utils\User32Dll.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj"> @@ -85,5 +97,11 @@ <Name>GodotTools.Core</Name> </ProjectReference> </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <Content Include="Ides\Rider\.editorconfig" /> + </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 index 01aa0d0ab1..3213de0127 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -72,6 +72,7 @@ namespace GodotTools.Ides case ExternalEditorId.None: case ExternalEditorId.VisualStudio: case ExternalEditorId.VsCode: + case ExternalEditorId.Rider: throw new NotSupportedException(); case ExternalEditorId.VisualStudioForMac: goto case ExternalEditorId.MonoDevelop; @@ -79,7 +80,7 @@ namespace GodotTools.Ides { MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath) { - if (Utils.OS.IsOSX() && editor == ExternalEditorId.VisualStudioForMac) + if (Utils.OS.IsOSX && editor == ExternalEditorId.VisualStudioForMac) { vsForMacInstance = vsForMacInstance ?? new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index 1fdccf5bbd..6026c109ad 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs @@ -24,7 +24,7 @@ namespace GodotTools.Ides.MonoDevelop string command; - if (OS.IsOSX()) + if (OS.IsOSX) { string bundleId = BundleIds[editorId]; @@ -81,7 +81,7 @@ namespace GodotTools.Ides.MonoDevelop public Instance(string solutionFile, EditorId editorId) { - if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX()) + if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX) throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform"); this.solutionFile = solutionFile; @@ -93,7 +93,7 @@ namespace GodotTools.Ides.MonoDevelop static Instance() { - if (OS.IsOSX()) + if (OS.IsOSX) { ExecutableNames = new Dictionary<EditorId, string> { @@ -107,7 +107,7 @@ namespace GodotTools.Ides.MonoDevelop {EditorId.VisualStudioForMac, "com.microsoft.visual-studio"} }; } - else if (OS.IsWindows()) + else if (OS.IsWindows) { ExecutableNames = new Dictionary<EditorId, string> { @@ -118,7 +118,7 @@ namespace GodotTools.Ides.MonoDevelop {EditorId.MonoDevelop, "MonoDevelop.exe"} }; } - else if (OS.IsUnix()) + else if (OS.IsUnixLike()) { ExecutableNames = new Dictionary<EditorId, string> { diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/.editorconfig b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/.editorconfig new file mode 100644 index 0000000000..aca19790ca --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs new file mode 100644 index 0000000000..901ade71e3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs @@ -0,0 +1,416 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using JetBrains.Annotations; +using Microsoft.Win32; +using Newtonsoft.Json; +using Directory = System.IO.Directory; +using Environment = System.Environment; +using File = System.IO.File; +using Path = System.IO.Path; +using OS = GodotTools.Utils.OS; + +namespace GodotTools.Ides.Rider +{ + /// <summary> + /// This code is a modified version of the JetBrains resharper-unity plugin listed under Apache License 2.0 license: + /// https://github.com/JetBrains/resharper-unity/blob/master/unity/JetBrains.Rider.Unity.Editor/EditorPlugin/RiderPathLocator.cs + /// </summary> + public static class RiderPathLocator + { + public static RiderInfo[] GetAllRiderPaths() + { + try + { + if (OS.IsWindows) + { + return CollectRiderInfosWindows(); + } + if (OS.IsOSX) + { + return CollectRiderInfosMac(); + } + if (OS.IsUnixLike()) + { + return CollectAllRiderPathsLinux(); + } + throw new Exception("Unexpected OS."); + } + catch (Exception e) + { + GD.PushWarning(e.Message); + } + + return new RiderInfo[0]; + } + + private static RiderInfo[] CollectAllRiderPathsLinux() + { + var installInfos = new List<RiderInfo>(); + var home = Environment.GetEnvironmentVariable("HOME"); + if (!string.IsNullOrEmpty(home)) + { + var toolboxRiderRootPath = GetToolboxBaseDir(); + installInfos.AddRange(CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider.sh", false) + .Select(a => new RiderInfo(a, true)).ToList()); + + //$Home/.local/share/applications/jetbrains-rider.desktop + var shortcut = new FileInfo(Path.Combine(home, @".local/share/applications/jetbrains-rider.desktop")); + + if (shortcut.Exists) + { + var lines = File.ReadAllLines(shortcut.FullName); + foreach (var line in lines) + { + if (!line.StartsWith("Exec=\"")) + continue; + var path = line.Split('"').Where((item, index) => index == 1).SingleOrDefault(); + if (string.IsNullOrEmpty(path)) + continue; + + if (installInfos.Any(a => a.Path == path)) // avoid adding similar build as from toolbox + continue; + installInfos.Add(new RiderInfo(path, false)); + } + } + } + + // snap install + var snapInstallPath = "/snap/rider/current/bin/rider.sh"; + if (new FileInfo(snapInstallPath).Exists) + installInfos.Add(new RiderInfo(snapInstallPath, false)); + + return installInfos.ToArray(); + } + + private static RiderInfo[] CollectRiderInfosMac() + { + var installInfos = new List<RiderInfo>(); + // "/Applications/*Rider*.app" + var folder = new DirectoryInfo("/Applications"); + if (folder.Exists) + { + installInfos.AddRange(folder.GetDirectories("*Rider*.app") + .Select(a => new RiderInfo(a.FullName, false)) + .ToList()); + } + + // /Users/user/Library/Application Support/JetBrains/Toolbox/apps/Rider/ch-1/181.3870.267/Rider EAP.app + var toolboxRiderRootPath = GetToolboxBaseDir(); + var paths = CollectPathsFromToolbox(toolboxRiderRootPath, "", "Rider*.app", true) + .Select(a => new RiderInfo(a, true)); + installInfos.AddRange(paths); + + return installInfos.ToArray(); + } + + private static RiderInfo[] CollectRiderInfosWindows() + { + var installInfos = new List<RiderInfo>(); + var toolboxRiderRootPath = GetToolboxBaseDir(); + var installPathsToolbox = CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider64.exe", false).ToList(); + installInfos.AddRange(installPathsToolbox.Select(a => new RiderInfo(a, true)).ToList()); + + var installPaths = new List<string>(); + const string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; + CollectPathsFromRegistry(registryKey, installPaths); + const string wowRegistryKey = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"; + CollectPathsFromRegistry(wowRegistryKey, installPaths); + + installInfos.AddRange(installPaths.Select(a => new RiderInfo(a, false)).ToList()); + + return installInfos.ToArray(); + } + + private static string GetToolboxBaseDir() + { + if (OS.IsWindows) + { + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + return Path.Combine(localAppData, @"JetBrains\Toolbox\apps\Rider"); + } + + if (OS.IsOSX) + { + var home = Environment.GetEnvironmentVariable("HOME"); + if (!string.IsNullOrEmpty(home)) + { + return Path.Combine(home, @"Library/Application Support/JetBrains/Toolbox/apps/Rider"); + } + } + + if (OS.IsUnixLike()) + { + var home = Environment.GetEnvironmentVariable("HOME"); + if (!string.IsNullOrEmpty(home)) + { + return Path.Combine(home, @".local/share/JetBrains/Toolbox/apps/Rider"); + } + } + + throw new Exception("Unexpected OS."); + } + + internal static ProductInfo GetBuildVersion(string path) + { + var buildTxtFileInfo = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt())); + var dir = buildTxtFileInfo.DirectoryName; + if (!Directory.Exists(dir)) + return null; + var buildVersionFile = new FileInfo(Path.Combine(dir, "product-info.json")); + if (!buildVersionFile.Exists) + return null; + var json = File.ReadAllText(buildVersionFile.FullName); + return ProductInfo.GetProductInfo(json); + } + + internal static Version GetBuildNumber(string path) + { + var file = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt())); + if (!file.Exists) + return null; + var text = File.ReadAllText(file.FullName); + if (text.Length <= 3) + return null; + + var versionText = text.Substring(3); + return Version.TryParse(versionText, out var v) ? v : null; + } + + internal static bool IsToolbox(string path) + { + return path.StartsWith(GetToolboxBaseDir()); + } + + private static string GetRelativePathToBuildTxt() + { + if (OS.IsWindows || OS.IsUnixLike()) + return "../../build.txt"; + if (OS.IsOSX) + return "Contents/Resources/build.txt"; + throw new Exception("Unknown OS."); + } + + private static void CollectPathsFromRegistry(string registryKey, List<string> installPaths) + { + using (var key = Registry.LocalMachine.OpenSubKey(registryKey)) + { + if (key == null) return; + foreach (var subkeyName in key.GetSubKeyNames().Where(a => a.Contains("Rider"))) + { + using (var subkey = key.OpenSubKey(subkeyName)) + { + var folderObject = subkey?.GetValue("InstallLocation"); + if (folderObject == null) continue; + var folder = folderObject.ToString(); + var possiblePath = Path.Combine(folder, @"bin\rider64.exe"); + if (File.Exists(possiblePath)) + installPaths.Add(possiblePath); + } + } + } + } + + private static string[] CollectPathsFromToolbox(string toolboxRiderRootPath, string dirName, string searchPattern, + bool isMac) + { + if (!Directory.Exists(toolboxRiderRootPath)) + return new string[0]; + + var channelDirs = Directory.GetDirectories(toolboxRiderRootPath); + var paths = channelDirs.SelectMany(channelDir => + { + try + { + // use history.json - last entry stands for the active build https://jetbrains.slack.com/archives/C07KNP99D/p1547807024066500?thread_ts=1547731708.057700&cid=C07KNP99D + var historyFile = Path.Combine(channelDir, ".history.json"); + if (File.Exists(historyFile)) + { + var json = File.ReadAllText(historyFile); + var build = ToolboxHistory.GetLatestBuildFromJson(json); + if (build != null) + { + var buildDir = Path.Combine(channelDir, build); + var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir); + if (executablePaths.Any()) + return executablePaths; + } + } + + var channelFile = Path.Combine(channelDir, ".channel.settings.json"); + if (File.Exists(channelFile)) + { + var json = File.ReadAllText(channelFile).Replace("active-application", "active_application"); + var build = ToolboxInstallData.GetLatestBuildFromJson(json); + if (build != null) + { + var buildDir = Path.Combine(channelDir, build); + var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir); + if (executablePaths.Any()) + return executablePaths; + } + } + + // changes in toolbox json files format may brake the logic above, so return all found Rider installations + return Directory.GetDirectories(channelDir) + .SelectMany(buildDir => GetExecutablePaths(dirName, searchPattern, isMac, buildDir)); + } + catch (Exception e) + { + // do not write to Debug.Log, just log it. + Logger.Warn($"Failed to get RiderPath from {channelDir}", e); + } + + return new string[0]; + }) + .Where(c => !string.IsNullOrEmpty(c)) + .ToArray(); + return paths; + } + + private static string[] GetExecutablePaths(string dirName, string searchPattern, bool isMac, string buildDir) + { + var folder = new DirectoryInfo(Path.Combine(buildDir, dirName)); + if (!folder.Exists) + return new string[0]; + + if (!isMac) + return new[] {Path.Combine(folder.FullName, searchPattern)}.Where(File.Exists).ToArray(); + return folder.GetDirectories(searchPattern).Select(f => f.FullName) + .Where(Directory.Exists).ToArray(); + } + + // Disable the "field is never assigned" compiler warning. We never assign it, but Unity does. + // Note that Unity disable this warning in the generated C# projects +#pragma warning disable 0649 + + [Serializable] + class ToolboxHistory + { + public List<ItemNode> history; + + public static string GetLatestBuildFromJson(string json) + { + try + { + return JsonConvert.DeserializeObject<ToolboxHistory>(json).history.LastOrDefault()?.item.build; + } + catch (Exception) + { + Logger.Warn($"Failed to get latest build from json {json}"); + } + + return null; + } + } + + [Serializable] + class ItemNode + { + public BuildNode item; + } + + [Serializable] + class BuildNode + { + public string build; + } + + [Serializable] + public class ProductInfo + { + public string version; + public string versionSuffix; + + [CanBeNull] + internal static ProductInfo GetProductInfo(string json) + { + try + { + var productInfo = JsonConvert.DeserializeObject<ProductInfo>(json); + return productInfo; + } + catch (Exception) + { + Logger.Warn($"Failed to get version from json {json}"); + } + + return null; + } + } + + // ReSharper disable once ClassNeverInstantiated.Global + [Serializable] + class ToolboxInstallData + { + // ReSharper disable once InconsistentNaming + public ActiveApplication active_application; + + [CanBeNull] + public static string GetLatestBuildFromJson(string json) + { + try + { + var toolbox = JsonConvert.DeserializeObject<ToolboxInstallData>(json); + var builds = toolbox.active_application.builds; + if (builds != null && builds.Any()) + return builds.First(); + } + catch (Exception) + { + Logger.Warn($"Failed to get latest build from json {json}"); + } + + return null; + } + } + + [Serializable] + class ActiveApplication + { + // ReSharper disable once InconsistentNaming + public List<string> builds; + } + +#pragma warning restore 0649 + + public struct RiderInfo + { + public bool IsToolbox; + public string Presentation; + public Version BuildNumber; + public ProductInfo ProductInfo; + public string Path; + + public RiderInfo(string path, bool isToolbox) + { + BuildNumber = GetBuildNumber(path); + ProductInfo = GetBuildVersion(path); + Path = new FileInfo(path).FullName; // normalize separators + var presentation = $"Rider {BuildNumber}"; + + if (ProductInfo != null && !string.IsNullOrEmpty(ProductInfo.version)) + { + var suffix = string.IsNullOrEmpty(ProductInfo.versionSuffix) ? "" : $" {ProductInfo.versionSuffix}"; + presentation = $"Rider {ProductInfo.version}{suffix}"; + } + + if (isToolbox) + presentation += " (JetBrains Toolbox)"; + + Presentation = presentation; + IsToolbox = isToolbox; + } + } + + private static class Logger + { + internal static void Warn(string message, Exception e = null) + { + throw new Exception(message, e); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs new file mode 100644 index 0000000000..b7dba13bbe --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using GodotTools.Internals; + +namespace GodotTools.Ides.Rider +{ + public static class RiderPathManager + { + private static readonly string editorPathSettingName= "mono/editor/editor_path_optional"; + + private static string GetRiderPathFromSettings() + { + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + if (editorSettings.HasSetting(editorPathSettingName)) + return (string) editorSettings.GetSetting(editorPathSettingName); + return null; + } + + public static void Initialize() + { + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editor = (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor"); + if (editor == ExternalEditorId.Rider) + { + if (!editorSettings.HasSetting(editorPathSettingName)) + { + Globals.EditorDef(editorPathSettingName, "Optional"); + editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = Variant.Type.String, + ["name"] = editorPathSettingName, + ["hint"] = PropertyHint.File, + ["hint_string"] = "" + }); + } + + var riderPath = (string) editorSettings.GetSetting(editorPathSettingName); + if (IsRiderAndExists(riderPath)) + { + Globals.EditorDef(editorPathSettingName, riderPath); + return; + } + + var paths = RiderPathLocator.GetAllRiderPaths(); + + if (!paths.Any()) + return; + + var newPath = paths.Last().Path; + Globals.EditorDef(editorPathSettingName, newPath); + editorSettings.SetSetting(editorPathSettingName, newPath); + } + } + + private static bool IsRider(string path) + { + if (string.IsNullOrEmpty(path)) + { + return false; + } + + var fileInfo = new FileInfo(path); + var filename = fileInfo.Name.ToLowerInvariant(); + return filename.StartsWith("rider", StringComparison.Ordinal); + } + + private static string CheckAndUpdatePath(string riderPath) + { + if (IsRiderAndExists(riderPath)) + { + return riderPath; + } + + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var paths = RiderPathLocator.GetAllRiderPaths(); + + if (!paths.Any()) + return null; + + var newPath = paths.Last().Path; + editorSettings.SetSetting(editorPathSettingName, newPath); + Globals.EditorDef(editorPathSettingName, newPath); + return newPath; + } + + private static bool IsRiderAndExists(string riderPath) + { + return !string.IsNullOrEmpty(riderPath) && IsRider(riderPath) && new FileInfo(riderPath).Exists; + } + + public static void OpenFile(string slnPath, string scriptPath, int line) + { + var pathFromSettings = GetRiderPathFromSettings(); + var path = CheckAndUpdatePath(pathFromSettings); + + var args = new List<string>(); + args.Add(slnPath); + if (line >= 0) + { + args.Add("--line"); + args.Add(line.ToString()); + } + args.Add(scriptPath); + try + { + Utils.OS.RunProcess(path, args); + } + catch (Exception e) + { + GD.PushError($"Error when trying to run code editor: JetBrains Rider. Exception message: '{e.Message}'"); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 836c9c11e4..de361ba844 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -52,7 +52,7 @@ namespace GodotTools.Internals public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts(); - // Internal Calls + #region Internal [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_UpdateApiAssembliesFromPrebuilt(string config); @@ -110,5 +110,7 @@ namespace GodotTools.Internals [MethodImpl(MethodImplOptions.InternalCall)] private static extern void internal_ScriptEditorDebugger_ReloadScripts(); + + #endregion } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index e48b1115db..1fe07e0bb6 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -1,72 +1,102 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using Mono.Unix.Native; namespace GodotTools.Utils { + [SuppressMessage("ReSharper", "InconsistentNaming")] public static class OS { [MethodImpl(MethodImplOptions.InternalCall)] - extern static string GetPlatformName(); + static extern string GetPlatformName(); - const string HaikuName = "Haiku"; - const string OSXName = "OSX"; - const string ServerName = "Server"; - const string UWPName = "UWP"; - const string WindowsName = "Windows"; - const string X11Name = "X11"; - - public static bool IsHaiku() + public static class Names { - return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + public const string Windows = "Windows"; + public const string OSX = "OSX"; + public const string X11 = "X11"; + public const string Server = "Server"; + public const string UWP = "UWP"; + public const string Haiku = "Haiku"; + public const string Android = "Android"; + public const string HTML5 = "HTML5"; } - public static bool IsOSX() + public static class Platforms { - return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + public const string Windows = "windows"; + public const string OSX = "osx"; + public const string X11 = "x11"; + public const string Server = "server"; + public const string UWP = "uwp"; + public const string Haiku = "haiku"; + public const string Android = "android"; + public const string HTML5 = "javascript"; } - public static bool IsServer() + public static readonly Dictionary<string, string> PlatformNameMap = new Dictionary<string, string> { - return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsUWP() + [Names.Windows] = Platforms.Windows, + [Names.OSX] = Platforms.OSX, + [Names.X11] = Platforms.X11, + [Names.Server] = Platforms.Server, + [Names.UWP] = Platforms.UWP, + [Names.Haiku] = Platforms.Haiku, + [Names.Android] = Platforms.Android, + [Names.HTML5] = Platforms.HTML5 + }; + + private static bool IsOS(string name) { - return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); } - public static bool IsWindows() - { - return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsX11() - { - return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } + private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows)); + private static readonly Lazy<bool> _isOSX = new Lazy<bool>(() => IsOS(Names.OSX)); + private static readonly Lazy<bool> _isX11 = new Lazy<bool>(() => IsOS(Names.X11)); + private static readonly Lazy<bool> _isServer = new Lazy<bool>(() => IsOS(Names.Server)); + private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP)); + private static readonly Lazy<bool> _isHaiku = new Lazy<bool>(() => IsOS(Names.Haiku)); + private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android)); + private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5)); + + public static bool IsWindows => _isWindows.Value || IsUWP; + public static bool IsOSX => _isOSX.Value; + public static bool IsX11 => _isX11.Value; + public static bool IsServer => _isServer.Value; + public static bool IsUWP => _isUWP.Value; + public static bool IsHaiku => _isHaiku.Value; + public static bool IsAndroid => _isAndroid.Value; + public static bool IsHTML5 => _isHTML5.Value; private static bool? _isUnixCache; - private static readonly string[] UnixPlatforms = {HaikuName, OSXName, ServerName, X11Name}; + private static readonly string[] UnixLikePlatforms = {Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android}; - public static bool IsUnix() + public static bool IsUnixLike() { if (_isUnixCache.HasValue) return _isUnixCache.Value; string osName = GetPlatformName(); - _isUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase)); + _isUnixCache = UnixLikePlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase)); return _isUnixCache.Value; } - public static char PathSep => IsWindows() ? ';' : ':'; + public static char PathSep => IsWindows ? ';' : ':'; public static string PathWhich(string name) { - string[] windowsExts = IsWindows() ? Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) : null; + return IsWindows ? PathWhichWindows(name) : PathWhichUnix(name); + } + + private static string PathWhichWindows(string name) + { + string[] windowsExts = Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? new string[] { }; string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); var searchDirs = new List<string>(); @@ -74,40 +104,46 @@ namespace GodotTools.Utils if (pathDirs != null) searchDirs.AddRange(pathDirs); + string nameExt = Path.GetExtension(name); + bool hasPathExt = string.IsNullOrEmpty(nameExt) || windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase); + searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list - foreach (var dir in searchDirs) - { - string path = Path.Combine(dir, name); - - if (IsWindows() && windowsExts != null) - { - foreach (var extension in windowsExts) - { - string pathWithExtension = path + extension; - - if (File.Exists(pathWithExtension)) - return pathWithExtension; - } - } - else - { - if (File.Exists(path)) - return path; - } - } + if (hasPathExt) + return searchDirs.Select(dir => Path.Combine(dir, name)).FirstOrDefault(File.Exists); + + return (from dir in searchDirs + select Path.Combine(dir, name) + into path + from ext in windowsExts + select path + ext).FirstOrDefault(File.Exists); + } + + private static string PathWhichUnix(string name) + { + string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); + + var searchDirs = new List<string>(); + + if (pathDirs != null) + searchDirs.AddRange(pathDirs); + + searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list - return null; + return searchDirs.Select(dir => Path.Combine(dir, name)) + .FirstOrDefault(path => File.Exists(path) && Syscall.access(path, AccessModes.X_OK) == 0); } public static void RunProcess(string command, IEnumerable<string> arguments) { + // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead string CmdLineArgsToString(IEnumerable<string> args) { + // Not perfect, but as long as we are careful... return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); } - ProcessStartInfo startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)) + var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)) { RedirectStandardOutput = true, RedirectStandardError = true, @@ -121,6 +157,8 @@ namespace GodotTools.Utils process.BeginOutputReadLine(); process.BeginErrorReadLine(); + if (IsWindows && process.Id>0) + User32Dll.AllowSetForegroundWindow(process.Id); // allows application to focus itself } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/User32Dll.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/User32Dll.cs new file mode 100644 index 0000000000..6810a991b3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/User32Dll.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace GodotTools.Utils +{ + public static class User32Dll + { + [DllImport("user32.dll")] + public static extern bool AllowSetForegroundWindow(int dwProcessId); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/packages.config b/modules/mono/editor/GodotTools/GodotTools/packages.config new file mode 100644 index 0000000000..2db4b4acc6 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/packages.config @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="JetBrains.Annotations" version="2019.1.3" targetFramework="net45" /> + <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net45" /> +</packages>
\ No newline at end of file diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 28cab2ab61..2252f7676d 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -97,7 +97,7 @@ #define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type #define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array" -#define BINDINGS_GENERATOR_VERSION UINT32_C(9) +#define BINDINGS_GENERATOR_VERSION UINT32_C(11) const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n"); @@ -731,13 +731,26 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { i++; } + String im_type_out = return_type->im_type_out; + + if (return_type->ret_as_byref_arg) { + // Doesn't affect the unique signature + im_type_out = "void"; + + im_sig += ", "; + im_sig += return_type->im_type_out; + im_sig += " argRet"; + + i++; + } + // godot_icall_{argc}_{icallcount} String icall_method = ICALL_PREFIX; icall_method += itos(imethod.arguments.size()); icall_method += "_"; icall_method += itos(method_icalls.size()); - InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, return_type->im_type_out, im_sig, im_unique_sig); + InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, im_type_out, im_sig, im_unique_sig); List<InternalCall>::Element *match = method_icalls.find(im_icall); @@ -1685,17 +1698,18 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf const InternalCall *im_icall = match->value(); String im_call = im_icall->editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS; - im_call += "." + im_icall->name + "(" + icall_params + ")"; + im_call += "."; + im_call += im_icall->name; if (p_imethod.arguments.size()) p_output.append(cs_in_statements); if (return_type->cname == name_cache.type_void) { - p_output.append(im_call + ";\n"); + p_output.append(im_call + "(" + icall_params + ");\n"); } else if (return_type->cs_out.empty()) { - p_output.append("return " + im_call + ";\n"); + p_output.append("return " + im_call + "(" + icall_params + ");\n"); } else { - p_output.append(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out)); + p_output.append(sformat(return_type->cs_out, im_call, icall_params, return_type->cs_type, return_type->im_type_out)); p_output.append("\n"); } @@ -1937,6 +1951,15 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte i++; } + if (return_type->ret_as_byref_arg) { + c_func_sig += ", "; + c_func_sig += return_type->c_type_in; + c_func_sig += " "; + c_func_sig += "arg_ret"; + + i++; + } + const Map<const MethodInterface *, const InternalCall *>::Element *match = method_icalls_map.find(&p_imethod); ERR_FAIL_NULL_V(match, ERR_BUG); @@ -1951,14 +1974,12 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte // Generate icall function - p_output.append(ret_void ? "void " : return_type->c_type_out + " "); + p_output.append((ret_void || return_type->ret_as_byref_arg) ? "void " : return_type->c_type_out + " "); p_output.append(icall_method); p_output.append("("); p_output.append(c_func_sig); p_output.append(") " OPEN_BLOCK); - String fail_ret = ret_void ? "" : ", " + (return_type->c_type_out.ends_with("*") ? "NULL" : return_type->c_type_out + "()"); - if (!ret_void) { String ptrcall_return_type; String initialization; @@ -1982,9 +2003,18 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte p_output.append("\t" + ptrcall_return_type); p_output.append(" " C_LOCAL_RET); p_output.append(initialization + ";\n"); - p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE); - p_output.append(fail_ret); - p_output.append(");\n"); + + String fail_ret = return_type->c_type_out.ends_with("*") && !return_type->ret_as_byref_arg ? "NULL" : return_type->c_type_out + "()"; + + if (return_type->ret_as_byref_arg) { + p_output.append("\tif (" CS_PARAM_INSTANCE " == NULL) { *arg_ret = "); + p_output.append(fail_ret); + p_output.append("; ERR_FAIL_MSG(\"Parameter ' arg_ret ' is null.\"); }\n"); + } else { + p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE ", "); + p_output.append(fail_ret); + p_output.append(");\n"); + } } else { p_output.append("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n"); } @@ -2045,10 +2075,13 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte } if (!ret_void) { - if (return_type->c_out.empty()) + if (return_type->c_out.empty()) { p_output.append("\treturn " C_LOCAL_RET ";\n"); - else + } else if (return_type->ret_as_byref_arg) { + p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name, "arg_ret")); + } else { p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name)); + } } p_output.append(CLOSE_BLOCK "\n"); @@ -2620,30 +2653,32 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { TypeInterface itype; -#define INSERT_STRUCT_TYPE(m_type, m_type_in) \ +#define INSERT_STRUCT_TYPE(m_type) \ { \ itype = TypeInterface::create_value_type(String(#m_type)); \ itype.c_in = "\t%0 %1_in = MARSHALLED_IN(" #m_type ", %1);\n"; \ - itype.c_out = "\treturn MARSHALLED_OUT(" #m_type ", %1);\n"; \ + itype.c_out = "\t*%3 = MARSHALLED_OUT(" #m_type ", %1);\n"; \ itype.c_arg_in = "&%s_in"; \ itype.c_type_in = "GDMonoMarshal::M_" #m_type "*"; \ itype.c_type_out = "GDMonoMarshal::M_" #m_type; \ itype.cs_in = "ref %s"; \ - itype.cs_out = "return (%1)%0;"; \ - itype.im_type_out = itype.cs_type; \ + /* in cs_out, im_type_out (%3) includes the 'out ' part */ \ + itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; \ + itype.im_type_out = "out " + itype.cs_type; \ + itype.ret_as_byref_arg = true; \ builtin_types.insert(itype.cname, itype); \ } - INSERT_STRUCT_TYPE(Vector2, "real_t*") - INSERT_STRUCT_TYPE(Rect2, "real_t*") - INSERT_STRUCT_TYPE(Transform2D, "real_t*") - INSERT_STRUCT_TYPE(Vector3, "real_t*") - INSERT_STRUCT_TYPE(Basis, "real_t*") - INSERT_STRUCT_TYPE(Quat, "real_t*") - INSERT_STRUCT_TYPE(Transform, "real_t*") - INSERT_STRUCT_TYPE(AABB, "real_t*") - INSERT_STRUCT_TYPE(Color, "real_t*") - INSERT_STRUCT_TYPE(Plane, "real_t*") + INSERT_STRUCT_TYPE(Vector2) + INSERT_STRUCT_TYPE(Rect2) + INSERT_STRUCT_TYPE(Transform2D) + INSERT_STRUCT_TYPE(Vector3) + INSERT_STRUCT_TYPE(Basis) + INSERT_STRUCT_TYPE(Quat) + INSERT_STRUCT_TYPE(Transform) + INSERT_STRUCT_TYPE(AABB) + INSERT_STRUCT_TYPE(Color) + INSERT_STRUCT_TYPE(Plane) #undef INSERT_STRUCT_TYPE @@ -2687,11 +2722,44 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { INSERT_INT_TYPE("sbyte", int8_t, int64_t); INSERT_INT_TYPE("short", int16_t, int64_t); INSERT_INT_TYPE("int", int32_t, int64_t); - INSERT_INT_TYPE("long", int64_t, int64_t); INSERT_INT_TYPE("byte", uint8_t, int64_t); INSERT_INT_TYPE("ushort", uint16_t, int64_t); INSERT_INT_TYPE("uint", uint32_t, int64_t); - INSERT_INT_TYPE("ulong", uint64_t, int64_t); + + itype = TypeInterface::create_value_type(String("long")); + { + itype.c_out = "\treturn (%0)%1;\n"; + itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; + itype.c_out = "\t*%3 = (%0)%1;\n"; + itype.c_type = "int64_t"; + itype.c_arg_in = "&%s_in"; + } + itype.c_type_in = "int64_t*"; + itype.c_type_out = "int64_t"; + itype.im_type_in = "ref " + itype.name; + itype.im_type_out = "out " + itype.name; + itype.cs_in = "ref %0"; + /* in cs_out, im_type_out (%3) includes the 'out ' part */ + itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; + itype.ret_as_byref_arg = true; + builtin_types.insert(itype.cname, itype); + + itype = TypeInterface::create_value_type(String("ulong")); + { + itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; + itype.c_out = "\t*%3 = (%0)%1;\n"; + itype.c_type = "int64_t"; + itype.c_arg_in = "&%s_in"; + } + itype.c_type_in = "uint64_t*"; + itype.c_type_out = "uint64_t"; + itype.im_type_in = "ref " + itype.name; + itype.im_type_out = "out " + itype.name; + itype.cs_in = "ref %0"; + /* in cs_out, im_type_out (%3) includes the 'out ' part */ + itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; + itype.ret_as_byref_arg = true; + builtin_types.insert(itype.cname, itype); } // Floating point types @@ -2703,16 +2771,20 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.proxy_name = "float"; { // The expected type for 'float' in ptrcall is 'double' - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; + itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; + itype.c_out = "\t*%3 = (%0)%1;\n"; itype.c_type = "double"; - itype.c_type_in = "float"; + itype.c_type_in = "float*"; itype.c_type_out = "float"; itype.c_arg_in = "&%s_in"; } itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.im_type_in = "ref " + itype.proxy_name; + itype.im_type_out = "out " + itype.proxy_name; + itype.cs_in = "ref %0"; + /* in cs_out, im_type_out (%3) includes the 'out ' part */ + itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; + itype.ret_as_byref_arg = true; builtin_types.insert(itype.cname, itype); // double @@ -2720,13 +2792,21 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "double"; itype.cname = itype.name; itype.proxy_name = "double"; - itype.c_type = "double"; - itype.c_type_in = "double"; - itype.c_type_out = "double"; - itype.c_arg_in = "&%s"; + { + itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; + itype.c_out = "\t*%3 = (%0)%1;\n"; + itype.c_type = "double"; + itype.c_type_in = "double*"; + itype.c_type_out = "double"; + itype.c_arg_in = "&%s_in"; + } itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.im_type_in = "ref " + itype.proxy_name; + itype.im_type_out = "out " + itype.proxy_name; + itype.cs_in = "ref %0"; + /* in cs_out, im_type_out (%3) includes the 'out ' part */ + itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; + itype.ret_as_byref_arg = true; builtin_types.insert(itype.cname, itype); } @@ -2757,7 +2837,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.c_type + "*"; itype.cs_type = itype.proxy_name; itype.cs_in = "NodePath." CS_SMETHOD_GETINSTANCE "(%0)"; - itype.cs_out = "return new %1(%0);"; + itype.cs_out = "return new %2(%0(%1));"; itype.im_type_in = "IntPtr"; itype.im_type_out = "IntPtr"; builtin_types.insert(itype.cname, itype); @@ -2773,7 +2853,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.c_type + "*"; itype.cs_type = itype.proxy_name; itype.cs_in = "RID." CS_SMETHOD_GETINSTANCE "(%0)"; - itype.cs_out = "return new %1(%0);"; + itype.cs_out = "return new %2(%0(%1));"; itype.im_type_in = "IntPtr"; itype.im_type_out = "IntPtr"; builtin_types.insert(itype.cname, itype); @@ -2855,7 +2935,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.c_type + "*"; itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name; itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()"; - itype.cs_out = "return new " + itype.cs_type + "(%0);"; + itype.cs_out = "return new " + itype.cs_type + "(%0(%1));"; itype.im_type_in = "IntPtr"; itype.im_type_out = "IntPtr"; builtin_types.insert(itype.cname, itype); @@ -2871,7 +2951,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.c_type + "*"; itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name; itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()"; - itype.cs_out = "return new " + itype.cs_type + "(%0);"; + itype.cs_out = "return new " + itype.cs_type + "(%0(%1));"; itype.im_type_in = "IntPtr"; itype.im_type_out = "IntPtr"; builtin_types.insert(itype.cname, itype); diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 8f3676940b..07918a2d03 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -214,6 +214,14 @@ class BindingsGenerator { */ bool memory_own; + /** + * This must be set to true for any struct bigger than 32-bits. Those cannot be passed/returned by value + * with internal calls, so we must use pointers instead. Returns must be replace with out parameters. + * In this case, [c_out] and [cs_out] must have a different format, explained below. + * The Mono IL interpreter icall trampolines don't support passing structs bigger than 32-bits by value (at least not on WASM). + */ + bool ret_as_byref_arg; + // !! The comments of the following fields make reference to other fields via square brackets, e.g.: [field_name] // !! When renaming those fields, make sure to rename their references in the comments @@ -248,6 +256,14 @@ class BindingsGenerator { * %0: [c_type_out] of the return type * %1: name of the variable to be returned * %2: [name] of the return type + * --------------------------------------- + * If [ret_as_byref_arg] is true, the format is different. Instead of using a return statement, + * the value must be assigned to a parameter. This type of this parameter is a pointer to [c_type_out]. + * Formatting elements: + * %0: [c_type_out] of the return type + * %1: name of the variable to be returned + * %2: [name] of the return type + * %3: name of the parameter that must be assigned the return value */ String c_out; @@ -291,9 +307,10 @@ class BindingsGenerator { * One or more statements that determine how a variable of this type is returned from a method. * It must contain the return statement(s). * Formatting elements: - * %0: internal method call statement - * %1: [cs_type] of the return type - * %2: [im_type_out] of the return type + * %0: internal method name + * %1: internal method call arguments without surrounding parenthesis + * %2: [cs_type] of the return type + * %3: [im_type_out] of the return type */ String cs_out; @@ -417,7 +434,7 @@ class BindingsGenerator { r_enum_itype.cs_type = r_enum_itype.proxy_name; r_enum_itype.cs_in = "(int)%s"; - r_enum_itype.cs_out = "return (%1)%0;"; + r_enum_itype.cs_out = "return (%2)%0(%1);"; r_enum_itype.im_type_in = "int"; r_enum_itype.im_type_out = "int"; r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name]; @@ -435,6 +452,8 @@ class BindingsGenerator { memory_own = false; + ret_as_byref_arg = false; + c_arg_in = "%s"; class_doc = NULL; diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 1564d73c2a..443b4ba841 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -219,15 +219,14 @@ int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObje return err; } -uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString *p_project_dll_name, MonoString *p_project_dll_src_path, - MonoString *p_build_config, MonoString *p_custom_lib_dir, MonoObject *r_dependencies) { - String project_dll_name = GDMonoMarshal::mono_string_to_godot(p_project_dll_name); - String project_dll_src_path = GDMonoMarshal::mono_string_to_godot(p_project_dll_src_path); +uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_dependencies, + MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_dependencies) { + Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_dependencies); String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config); - String custom_lib_dir = GDMonoMarshal::mono_string_to_godot(p_custom_lib_dir); + String custom_bcl_dir = GDMonoMarshal::mono_string_to_godot(p_custom_bcl_dir); Dictionary dependencies = GDMonoMarshal::mono_object_to_variant(r_dependencies); - return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_lib_dir, dependencies); + return GodotSharpExport::get_exported_assembly_dependencies(initial_dependencies, build_config, custom_bcl_dir, dependencies); } MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) { @@ -411,8 +410,8 @@ void register_editor_internal_calls() { // ScriptClassParser mono_add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", (void *)godot_icall_ScriptClassParser_ParseFile); - // GodotSharpExport - mono_add_internal_call("GodotTools.GodotSharpExport::internal_GetExportedAssemblyDependencies", (void *)godot_icall_GodotSharpExport_GetExportedAssemblyDependencies); + // ExportPlugin + mono_add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", (void *)godot_icall_ExportPlugin_GetExportedAssemblyDependencies); // Internals mono_add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", (void *)godot_icall_Internal_UpdateApiAssembliesFromPrebuilt); diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index e83152d668..e02bd3be58 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -36,6 +36,7 @@ #include "../mono_gd/gd_mono.h" #include "../mono_gd/gd_mono_assembly.h" +#include "../mono_gd/gd_mono_cache.h" namespace GodotSharpExport { @@ -100,23 +101,32 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> return OK; } -Error get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) { +Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencies, + const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) { MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport"); ERR_FAIL_NULL_V(export_domain, FAILED); _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); _GDMONO_SCOPE_DOMAIN_(export_domain); - GDMonoAssembly *scripts_assembly = NULL; - bool load_success = GDMono::get_singleton()->load_assembly_from(p_project_dll_name, - p_project_dll_src_path, &scripts_assembly, /* refonly: */ true); - - 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_bcl_dir); - return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies); + for (const Variant *key = p_initial_dependencies.next(); key; key = p_initial_dependencies.next(key)) { + String assembly_name = *key; + String assembly_path = p_initial_dependencies[*key]; + + GDMonoAssembly *assembly = NULL; + bool load_success = GDMono::get_singleton()->load_assembly_from(assembly_name, assembly_path, &assembly, /* refonly: */ true); + + ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'."); + + Error err = get_assembly_dependencies(assembly, search_dirs, r_dependencies); + if (err != OK) + return err; + } + + return OK; } } // namespace GodotSharpExport diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h index 58e46e2f2d..8eb5a4d9dc 100644 --- a/modules/mono/editor/godotsharp_export.h +++ b/modules/mono/editor/godotsharp_export.h @@ -41,9 +41,8 @@ namespace GodotSharpExport { Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies); -Error get_exported_assembly_dependencies(const String &p_project_dll_name, - const String &p_project_dll_src_path, const String &p_build_config, - const String &p_custom_lib_dir, Dictionary &r_dependencies); +Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencies, + const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies); } // namespace GodotSharpExport diff --git a/modules/mono/glue/Managed/Files/AABB.cs b/modules/mono/glue/Managed/Files/AABB.cs index 98a73382f4..6a4f785551 100644 --- a/modules/mono/glue/Managed/Files/AABB.cs +++ b/modules/mono/glue/Managed/Files/AABB.cs @@ -458,6 +458,11 @@ namespace Godot return _position == other._position && _size == other._size; } + public bool IsEqualApprox(AABB other) + { + return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other._size); + } + public override int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); diff --git a/modules/mono/glue/Managed/Files/Array.cs b/modules/mono/glue/Managed/Files/Array.cs index 0e7b0362e0..aba1065498 100644 --- a/modules/mono/glue/Managed/Files/Array.cs +++ b/modules/mono/glue/Managed/Files/Array.cs @@ -64,6 +64,11 @@ namespace Godot.Collections return safeHandle.DangerousGetHandle(); } + public Array Duplicate(bool deep = false) + { + return new Array(godot_icall_Array_Duplicate(GetPtr(), deep)); + } + public Error Resize(int newSize) { return godot_icall_Array_Resize(GetPtr(), newSize); @@ -179,6 +184,9 @@ namespace Godot.Collections internal extern static void godot_icall_Array_CopyTo(IntPtr ptr, System.Array array, int arrayIndex); [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Array_Duplicate(IntPtr ptr, bool deep); + + [MethodImpl(MethodImplOptions.InternalCall)] internal extern static int godot_icall_Array_IndexOf(IntPtr ptr, object item); [MethodImpl(MethodImplOptions.InternalCall)] @@ -250,6 +258,11 @@ namespace Godot.Collections return from.objectArray; } + public Array<T> Duplicate(bool deep = false) + { + return new Array<T>(objectArray.Duplicate(deep)); + } + public Error Resize(int newSize) { return objectArray.Resize(newSize); diff --git a/modules/mono/glue/Managed/Files/Basis.cs b/modules/mono/glue/Managed/Files/Basis.cs index 0eb76e9c63..c5e62b77c8 100644 --- a/modules/mono/glue/Managed/Files/Basis.cs +++ b/modules/mono/glue/Managed/Files/Basis.cs @@ -654,6 +654,11 @@ namespace Godot return Row0.Equals(other.Row0) && Row1.Equals(other.Row1) && Row2.Equals(other.Row2); } + public bool IsEqualApprox(Basis other) + { + return Row0.IsEqualApprox(other.Row0) && Row1.IsEqualApprox(other.Row1) && Row2.IsEqualApprox(other.Row2); + } + public override int GetHashCode() { return Row0.GetHashCode() ^ Row1.GetHashCode() ^ Row2.GetHashCode(); diff --git a/modules/mono/glue/Managed/Files/Color.cs b/modules/mono/glue/Managed/Files/Color.cs index 3a52a1a13b..df817e47e9 100644 --- a/modules/mono/glue/Managed/Files/Color.cs +++ b/modules/mono/glue/Managed/Files/Color.cs @@ -661,6 +661,11 @@ namespace Godot public bool Equals(Color other) { + return r == other.r && g == other.g && b == other.b && a == other.a; + } + + public bool IsEqualApprox(Color other) + { return Mathf.IsEqualApprox(r, other.r) && Mathf.IsEqualApprox(g, other.g) && Mathf.IsEqualApprox(b, other.b) && Mathf.IsEqualApprox(a, other.a); } diff --git a/modules/mono/glue/Managed/Files/Dictionary.cs b/modules/mono/glue/Managed/Files/Dictionary.cs index 6ab8549a01..d72109de92 100644 --- a/modules/mono/glue/Managed/Files/Dictionary.cs +++ b/modules/mono/glue/Managed/Files/Dictionary.cs @@ -80,6 +80,11 @@ namespace Godot.Collections disposed = true; } + public Dictionary Duplicate(bool deep = false) + { + return new Dictionary(godot_icall_Dictionary_Duplicate(GetPtr(), deep)); + } + // IDictionary public ICollection Keys @@ -235,6 +240,9 @@ namespace Godot.Collections internal extern static bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key); [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Dictionary_Duplicate(IntPtr ptr, bool deep); + + [MethodImpl(MethodImplOptions.InternalCall)] internal extern static bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key); [MethodImpl(MethodImplOptions.InternalCall)] @@ -313,6 +321,11 @@ namespace Godot.Collections return objectDict.GetPtr(); } + public Dictionary<TKey, TValue> Duplicate(bool deep = false) + { + return new Dictionary<TKey, TValue>(objectDict.Duplicate(deep)); + } + // IDictionary<TKey, TValue> public TValue this[TKey key] diff --git a/modules/mono/glue/Managed/Files/GD.cs b/modules/mono/glue/Managed/Files/GD.cs index 2068099ac6..19962d418a 100644 --- a/modules/mono/glue/Managed/Files/GD.cs +++ b/modules/mono/glue/Managed/Files/GD.cs @@ -93,22 +93,22 @@ namespace Godot public static void PrintErr(params object[] what) { - godot_icall_GD_printerr(Array.ConvertAll(what, x => x.ToString())); + godot_icall_GD_printerr(Array.ConvertAll(what, x => x?.ToString())); } public static void PrintRaw(params object[] what) { - godot_icall_GD_printraw(Array.ConvertAll(what, x => x.ToString())); + godot_icall_GD_printraw(Array.ConvertAll(what, x => x?.ToString())); } public static void PrintS(params object[] what) { - godot_icall_GD_prints(Array.ConvertAll(what, x => x.ToString())); + godot_icall_GD_prints(Array.ConvertAll(what, x => x?.ToString())); } public static void PrintT(params object[] what) { - godot_icall_GD_printt(Array.ConvertAll(what, x => x.ToString())); + godot_icall_GD_printt(Array.ConvertAll(what, x => x?.ToString())); } public static float Randf() diff --git a/modules/mono/glue/Managed/Files/Mathf.cs b/modules/mono/glue/Managed/Files/Mathf.cs index ce34cd6a99..54821fe790 100644 --- a/modules/mono/glue/Managed/Files/Mathf.cs +++ b/modules/mono/glue/Managed/Files/Mathf.cs @@ -19,12 +19,12 @@ namespace Godot private const real_t Deg2RadConst = (real_t) 0.0174532925199432957692369077M; // 0.0174532924f and 0.0174532925199433 private const real_t Rad2DegConst = (real_t) 57.295779513082320876798154814M; // 57.29578f and 57.2957795130823 - public static real_t Abs(real_t s) + public static int Abs(int s) { return Math.Abs(s); } - public static int Abs(int s) + public static real_t Abs(real_t s) { return Math.Abs(s); } @@ -79,29 +79,6 @@ namespace Godot return (real_t)Math.Cosh(s); } - public static int StepDecimals(real_t step) - { - double[] sd = new double[] { - 0.9999, - 0.09999, - 0.009999, - 0.0009999, - 0.00009999, - 0.000009999, - 0.0000009999, - 0.00000009999, - 0.000000009999, - }; - double abs = Mathf.Abs(step); - double decs = abs - (int)abs; // Strip away integer part - for (int i = 0; i < sd.Length; i++) { - if (decs >= sd[i]) { - return i; - } - } - return 0; - } - public static real_t Deg2Rad(real_t deg) { return deg * Deg2RadConst; @@ -159,12 +136,14 @@ 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) { + if (a == b) + { return true; } // Then check for approximate equality. real_t tolerance = Epsilon * Abs(a); - if (tolerance < Epsilon) { + if (tolerance < Epsilon) + { tolerance = Epsilon; } return Abs(a - b) < tolerance; @@ -190,7 +169,8 @@ namespace Godot return from + (to - from) * weight; } - public static real_t LerpAngle(real_t from, real_t to, real_t weight) { + public static real_t LerpAngle(real_t from, real_t to, real_t weight) + { real_t difference = (to - from) % Mathf.Tau; real_t distance = ((2 * difference) % Mathf.Tau) - difference; return from + distance * weight; @@ -246,9 +226,9 @@ namespace Godot /// <summary> /// Performs a canonical Modulus operation, where the output is on the range [0, b). /// </summary> - public static real_t PosMod(real_t a, real_t b) + public static int PosMod(int a, int b) { - real_t c = a % b; + int c = a % b; if ((c < 0 && b > 0) || (c > 0 && b < 0)) { c += b; @@ -259,9 +239,9 @@ namespace Godot /// <summary> /// Performs a canonical Modulus operation, where the output is on the range [0, b). /// </summary> - public static int PosMod(int a, int b) + public static real_t PosMod(real_t a, real_t b) { - int c = a % b; + real_t c = a % b; if ((c < 0 && b > 0) || (c > 0 && b < 0)) { c += b; @@ -319,6 +299,31 @@ namespace Godot return (real_t)Math.Sqrt(s); } + public static int StepDecimals(real_t step) + { + double[] sd = new double[] { + 0.9999, + 0.09999, + 0.009999, + 0.0009999, + 0.00009999, + 0.000009999, + 0.0000009999, + 0.00000009999, + 0.000000009999, + }; + double abs = Mathf.Abs(step); + double decs = abs - (int)abs; // Strip away integer part + for (int i = 0; i < sd.Length; i++) + { + if (decs >= sd[i]) + { + return i; + } + } + return 0; + } + public static real_t Stepify(real_t s, real_t step) { if (step != 0f) diff --git a/modules/mono/glue/Managed/Files/MathfEx.cs b/modules/mono/glue/Managed/Files/MathfEx.cs index 6cffc7f01d..1b7fd4906f 100644 --- a/modules/mono/glue/Managed/Files/MathfEx.cs +++ b/modules/mono/glue/Managed/Files/MathfEx.cs @@ -49,7 +49,8 @@ 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) { + if (a == b) + { return true; } // Then check for approximate equality. diff --git a/modules/mono/glue/Managed/Files/NodePath.cs b/modules/mono/glue/Managed/Files/NodePath.cs index 4de4e1e6a9..8c5872ba5a 100644 --- a/modules/mono/glue/Managed/Files/NodePath.cs +++ b/modules/mono/glue/Managed/Files/NodePath.cs @@ -12,7 +12,7 @@ namespace Godot internal static IntPtr GetPtr(NodePath instance) { if (instance == null) - return IntPtr.Zero; + throw new NullReferenceException($"The instance of type {nameof(NodePath)} is null."); if (instance.disposed) throw new ObjectDisposedException(instance.GetType().FullName); diff --git a/modules/mono/glue/Managed/Files/Plane.cs b/modules/mono/glue/Managed/Files/Plane.cs index a13161d2e6..885845e3a4 100644 --- a/modules/mono/glue/Managed/Files/Plane.cs +++ b/modules/mono/glue/Managed/Files/Plane.cs @@ -82,12 +82,12 @@ namespace Godot return Mathf.Abs(dist) <= epsilon; } - public Vector3 Intersect3(Plane b, Plane c) + public Vector3? Intersect3(Plane b, Plane c) { real_t denom = _normal.Cross(b._normal).Dot(c._normal); - if (Mathf.Abs(denom) <= Mathf.Epsilon) - return new Vector3(); + if (Mathf.IsZeroApprox(denom)) + return null; Vector3 result = b._normal.Cross(c._normal) * D + c._normal.Cross(_normal) * b.D + @@ -96,34 +96,35 @@ namespace Godot return result / denom; } - public Vector3 IntersectRay(Vector3 from, Vector3 dir) + public Vector3? IntersectRay(Vector3 from, Vector3 dir) { real_t den = _normal.Dot(dir); - if (Mathf.Abs(den) <= Mathf.Epsilon) - return new Vector3(); + if (Mathf.IsZeroApprox(den)) + return null; real_t dist = (_normal.Dot(from) - D) / den; // This is a ray, before the emitting pos (from) does not exist if (dist > Mathf.Epsilon) - return new Vector3(); + return null; return from + dir * -dist; } - public Vector3 IntersectSegment(Vector3 begin, Vector3 end) + public Vector3? IntersectSegment(Vector3 begin, Vector3 end) { Vector3 segment = begin - end; real_t den = _normal.Dot(segment); - if (Mathf.Abs(den) <= Mathf.Epsilon) - return new Vector3(); + if (Mathf.IsZeroApprox(den)) + return null; real_t dist = (_normal.Dot(begin) - D) / den; + // Only allow dist to be in the range of 0 to 1, with tolerance. if (dist < -Mathf.Epsilon || dist > 1.0f + Mathf.Epsilon) - return new Vector3(); + return null; return begin + segment * -dist; } @@ -203,7 +204,12 @@ namespace Godot public bool Equals(Plane other) { - return _normal == other._normal && Mathf.IsEqualApprox(D, other.D); + return _normal == other._normal && D == other.D; + } + + public bool IsEqualApprox(Plane other) + { + return _normal.IsEqualApprox(other._normal) && Mathf.IsEqualApprox(D, other.D); } public override int GetHashCode() diff --git a/modules/mono/glue/Managed/Files/Quat.cs b/modules/mono/glue/Managed/Files/Quat.cs index 845c7c730e..8f60867ac3 100644 --- a/modules/mono/glue/Managed/Files/Quat.cs +++ b/modules/mono/glue/Managed/Files/Quat.cs @@ -363,6 +363,11 @@ namespace Godot public bool Equals(Quat other) { + return x == other.x && y == other.y && z == other.z && w == other.w; + } + + public bool IsEqualApprox(Quat other) + { return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w); } diff --git a/modules/mono/glue/Managed/Files/RID.cs b/modules/mono/glue/Managed/Files/RID.cs index 12064beca2..94761531b1 100644 --- a/modules/mono/glue/Managed/Files/RID.cs +++ b/modules/mono/glue/Managed/Files/RID.cs @@ -12,7 +12,7 @@ namespace Godot internal static IntPtr GetPtr(RID instance) { if (instance == null) - return IntPtr.Zero; + throw new NullReferenceException($"The instance of type {nameof(RID)} is null."); if (instance.disposed) throw new ObjectDisposedException(instance.GetType().FullName); diff --git a/modules/mono/glue/Managed/Files/Rect2.cs b/modules/mono/glue/Managed/Files/Rect2.cs index 99542d0c0a..91e614dc7b 100644 --- a/modules/mono/glue/Managed/Files/Rect2.cs +++ b/modules/mono/glue/Managed/Files/Rect2.cs @@ -231,6 +231,11 @@ namespace Godot return _position.Equals(other._position) && _size.Equals(other._size); } + public bool IsEqualApprox(Rect2 other) + { + return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other.Size); + } + public override int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); diff --git a/modules/mono/glue/Managed/Files/Transform.cs b/modules/mono/glue/Managed/Files/Transform.cs index cc4d26158d..0b84050f07 100644 --- a/modules/mono/glue/Managed/Files/Transform.cs +++ b/modules/mono/glue/Managed/Files/Transform.cs @@ -185,6 +185,11 @@ namespace Godot return basis.Equals(other.basis) && origin.Equals(other.origin); } + public bool IsEqualApprox(Transform other) + { + return basis.IsEqualApprox(other.basis) && origin.IsEqualApprox(other.origin); + } + public override int GetHashCode() { return basis.GetHashCode() ^ origin.GetHashCode(); diff --git a/modules/mono/glue/Managed/Files/Transform2D.cs b/modules/mono/glue/Managed/Files/Transform2D.cs index 814332dc07..77ea3e5830 100644 --- a/modules/mono/glue/Managed/Files/Transform2D.cs +++ b/modules/mono/glue/Managed/Files/Transform2D.cs @@ -357,6 +357,11 @@ namespace Godot return x.Equals(other.x) && y.Equals(other.y) && origin.Equals(other.origin); } + public bool IsEqualApprox(Transform2D other) + { + return x.IsEqualApprox(other.x) && y.IsEqualApprox(other.y) && origin.IsEqualApprox(other.origin); + } + public override int GetHashCode() { return x.GetHashCode() ^ y.GetHashCode() ^ origin.GetHashCode(); diff --git a/modules/mono/glue/Managed/Files/Vector2.cs b/modules/mono/glue/Managed/Files/Vector2.cs index 0daa94057e..f92453f546 100644 --- a/modules/mono/glue/Managed/Files/Vector2.cs +++ b/modules/mono/glue/Managed/Files/Vector2.cs @@ -455,6 +455,11 @@ namespace Godot public bool Equals(Vector2 other) { + return x == other.x && y == other.y; + } + + public bool IsEqualApprox(Vector2 other) + { return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y); } diff --git a/modules/mono/glue/Managed/Files/Vector3.cs b/modules/mono/glue/Managed/Files/Vector3.cs index 9076dbd3b0..025b09199f 100644 --- a/modules/mono/glue/Managed/Files/Vector3.cs +++ b/modules/mono/glue/Managed/Files/Vector3.cs @@ -513,6 +513,11 @@ namespace Godot public bool Equals(Vector3 other) { + return x == other.x && y == other.y && z == other.z; + } + + public bool IsEqualApprox(Vector3 other) + { return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z); } diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp index 6d85f55b97..2488f78350 100644 --- a/modules/mono/glue/base_object_glue.cpp +++ b/modules/mono/glue/base_object_glue.cpp @@ -36,6 +36,7 @@ #include "core/string_name.h" #include "../csharp_script.h" +#include "../mono_gd/gd_mono_cache.h" #include "../mono_gd/gd_mono_class.h" #include "../mono_gd/gd_mono_internals.h" #include "../mono_gd/gd_mono_utils.h" diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp index e67c8b9ad9..e84a5becd6 100644 --- a/modules/mono/glue/collections_glue.cpp +++ b/modules/mono/glue/collections_glue.cpp @@ -34,6 +34,7 @@ #include <mono/metadata/exception.h> +#include "../mono_gd/gd_mono_cache.h" #include "../mono_gd/gd_mono_class.h" #include "../mono_gd/gd_mono_utils.h" @@ -102,6 +103,10 @@ void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) { } } +Array *godot_icall_Array_Duplicate(Array *ptr, MonoBoolean deep) { + return memnew(Array(ptr->duplicate(deep))); +} + int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) { return ptr->find(GDMonoMarshal::mono_object_to_variant(item)); } @@ -223,6 +228,10 @@ MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key) return ptr->has(GDMonoMarshal::mono_object_to_variant(key)); } +Dictionary *godot_icall_Dictionary_Duplicate(Dictionary *ptr, MonoBoolean deep) { + return memnew(Dictionary(ptr->duplicate(deep))); +} + MonoBoolean godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key) { return ptr->erase(GDMonoMarshal::mono_object_to_variant(key)); } @@ -283,6 +292,7 @@ void godot_register_collections_icalls() { mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", (void *)godot_icall_Array_Clear); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", (void *)godot_icall_Array_Contains); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", (void *)godot_icall_Array_CopyTo); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Duplicate", (void *)godot_icall_Array_Duplicate); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_IndexOf", (void *)godot_icall_Array_IndexOf); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Insert", (void *)godot_icall_Array_Insert); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Remove", (void *)godot_icall_Array_Remove); @@ -303,6 +313,7 @@ void godot_register_collections_icalls() { mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Clear", (void *)godot_icall_Dictionary_Clear); mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Contains", (void *)godot_icall_Dictionary_Contains); mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ContainsKey", (void *)godot_icall_Dictionary_ContainsKey); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Duplicate", (void *)godot_icall_Dictionary_Duplicate); mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_RemoveKey", (void *)godot_icall_Dictionary_RemoveKey); mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Remove", (void *)godot_icall_Dictionary_Remove); mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue", (void *)godot_icall_Dictionary_TryGetValue); diff --git a/modules/mono/glue/collections_glue.h b/modules/mono/glue/collections_glue.h index 54ed42c3b6..a79a651fcd 100644 --- a/modules/mono/glue/collections_glue.h +++ b/modules/mono/glue/collections_glue.h @@ -59,6 +59,8 @@ MonoBoolean godot_icall_Array_Contains(Array *ptr, MonoObject *item); void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index); +Array *godot_icall_Array_Duplicate(Array *ptr, MonoBoolean deep); + int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item); void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item); @@ -99,6 +101,8 @@ MonoBoolean godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, Mo MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key); +Dictionary *godot_icall_Dictionary_Duplicate(Dictionary *ptr, MonoBoolean deep); + MonoBoolean godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key); MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value); diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp index 8b9a1380d8..1381d79e2e 100644 --- a/modules/mono/glue/gd_glue.cpp +++ b/modules/mono/glue/gd_glue.cpp @@ -39,6 +39,7 @@ #include "core/variant.h" #include "core/variant_parser.h" +#include "../mono_gd/gd_mono_cache.h" #include "../mono_gd/gd_mono_utils.h" MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes, MonoBoolean p_allow_objects) { @@ -211,7 +212,7 @@ MonoString *godot_icall_GD_var2str(MonoObject *p_var) { } MonoObject *godot_icall_DefaultGodotTaskScheduler() { - return GDMonoUtils::mono_cache.task_scheduler_handle->get_target(); + return GDMonoCache::cached_data.task_scheduler_handle->get_target(); } void godot_register_gd_icalls() { diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 5fa8aed5a9..ef30a52b72 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -39,8 +39,8 @@ #include "editor/editor_settings.h" #endif -#ifdef __ANDROID__ -#include "utils/android_utils.h" +#ifdef ANDROID_ENABLED +#include "mono_gd/gd_mono_android.h" #endif #include "mono_gd/gd_mono.h" @@ -108,6 +108,10 @@ public: String data_editor_tools_dir; String data_editor_prebuilt_api_dir; +#else + // Equivalent of res_assemblies_dir, but in the data directory rather than in 'res://'. + // Only defined on export templates. Used when exporting assemblies outside of PCKs. + String data_game_assemblies_dir; #endif String data_mono_etc_dir; @@ -130,7 +134,11 @@ private: res_temp_assemblies_base_dir = res_temp_dir.plus_file("bin"); res_temp_assemblies_dir = res_temp_assemblies_base_dir.plus_file(_get_expected_build_config()); +#ifdef JAVASCRIPT_ENABLED + mono_user_dir = "user://"; +#else mono_user_dir = _get_mono_user_dir(); +#endif mono_logs_dir = mono_user_dir.plus_file("mono_logs"); #ifdef TOOLS_ENABLED @@ -160,8 +168,8 @@ private: String data_mono_root_dir = data_dir_root.plus_file("Mono"); data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); -#if __ANDROID__ - data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir(); +#ifdef ANDROID_ENABLED + data_mono_lib_dir = GDMonoAndroid::get_app_native_lib_dir(); #else data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); #endif @@ -197,10 +205,11 @@ private: String data_mono_root_dir = data_dir_root.plus_file("Mono"); data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); -#if __ANDROID__ - data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir(); +#ifdef ANDROID_ENABLED + data_mono_lib_dir = GDMonoAndroid::get_app_native_lib_dir(); #else data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); + data_game_assemblies_dir = data_dir_root.plus_file("Assemblies"); #endif #ifdef WINDOWS_ENABLED @@ -212,6 +221,10 @@ private: data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc"); data_mono_lib_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Mono/lib"); } + + if (!DirAccess::exists(data_game_assemblies_dir)) { + data_game_assemblies_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Assemblies"); + } #endif #endif @@ -291,6 +304,10 @@ String get_data_editor_tools_dir() { String get_data_editor_prebuilt_api_dir() { return _GodotSharpDirs::get_singleton().data_editor_prebuilt_api_dir; } +#else +String get_data_game_assemblies_dir() { + return _GodotSharpDirs::get_singleton().data_game_assemblies_dir; +} #endif String get_data_mono_etc_dir() { diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h index ff51888d1c..43da44b0f5 100644 --- a/modules/mono/godotsharp_dirs.h +++ b/modules/mono/godotsharp_dirs.h @@ -56,6 +56,8 @@ String get_project_csproj_path(); String get_data_editor_tools_dir(); String get_data_editor_prebuilt_api_dir(); +#else +String get_data_game_assemblies_dir(); #endif String get_data_mono_etc_dir(); diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 504b8d41d0..33ba877352 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -46,6 +46,7 @@ #include "../csharp_script.h" #include "../godotsharp_dirs.h" #include "../utils/path_utils.h" +#include "gd_mono_cache.h" #include "gd_mono_class.h" #include "gd_mono_marshal.h" #include "gd_mono_utils.h" @@ -56,13 +57,26 @@ #ifdef ANDROID_ENABLED #include "android_mono_config.h" +#include "gd_mono_android.h" #endif +// TODO: +// This has turn into a gigantic mess. There's too much going on here. Too much #ifdef as well. +// It's just painful to read... It needs to be re-structured. Please, clean this up, future me. + GDMono *GDMono::singleton = NULL; namespace { -void setup_runtime_main_args() { +#if defined(JAVASCRIPT_ENABLED) +extern "C" { +void mono_wasm_load_runtime(const char *managed_path, int enable_debugging); +} +#endif + +#if !defined(JAVASCRIPT_ENABLED) + +void gd_mono_setup_runtime_main_args() { CharString execpath = OS::get_singleton()->get_executable_path().utf8(); List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); @@ -83,7 +97,7 @@ void setup_runtime_main_args() { mono_runtime_set_main_args(main_args.size(), main_args.ptrw()); } -void gdmono_profiler_init() { +void gd_mono_profiler_init() { String profiler_args = GLOBAL_DEF("mono/profiler/args", "log:calls,alloc,sample,output=output.mlpd"); bool profiler_enabled = GLOBAL_DEF("mono/profiler/enabled", false); if (profiler_enabled) { @@ -91,9 +105,9 @@ void gdmono_profiler_init() { } } -#ifdef DEBUG_ENABLED +#if defined(DEBUG_ENABLED) -bool _wait_for_debugger_msecs(uint32_t p_msecs) { +bool gd_mono_wait_for_debugger_msecs(uint32_t p_msecs) { do { if (mono_is_debugger_attached()) @@ -115,7 +129,7 @@ bool _wait_for_debugger_msecs(uint32_t p_msecs) { return mono_is_debugger_attached(); } -void gdmono_debug_init() { +void gd_mono_debug_init() { mono_debug_init(MONO_DEBUG_FORMAT_MONO); @@ -151,11 +165,37 @@ void gdmono_debug_init() { mono_jit_parse_options(2, (char **)options); } +#endif // defined(DEBUG_ENABLED) +#endif // !defined(JAVASCRIPT_ENABLED) + +#if defined(JAVASCRIPT_ENABLED) +MonoDomain *gd_initialize_mono_runtime() { + const char *vfs_prefix = "managed"; + int enable_debugging = 0; + +#ifdef DEBUG_ENABLED + enable_debugging = 1; +#endif + + mono_wasm_load_runtime(vfs_prefix, enable_debugging); + + return mono_get_root_domain(); +} +#else +MonoDomain *gd_initialize_mono_runtime() { +#ifdef DEBUG_ENABLED + gd_mono_debug_init(); +#endif + + return mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); +} #endif } // namespace void GDMono::add_mono_shared_libs_dir_to_path() { + // TODO: Replace this with a mono_dl_fallback + // By default Mono seems to search shared libraries in the following directories: // Current working directory, @executable_path@ and PATH // The parent directory of the image file (assembly where the dllimport method is declared) @@ -199,35 +239,22 @@ void GDMono::add_mono_shared_libs_dir_to_path() { #endif // WINDOWS_ENABLED || UNIX_ENABLED } -void GDMono::initialize() { +void GDMono::determine_mono_dirs(String &r_assembly_rootdir, String &r_config_dir) { - ERR_FAIL_NULL(Engine::get_singleton()); - - print_verbose("Mono: Initializing module..."); - - char *runtime_build_info = mono_get_runtime_build_info(); - print_verbose("Mono JIT compiler version " + String(runtime_build_info)); - mono_free(runtime_build_info); - -#ifdef DEBUG_METHODS_ENABLED - _initialize_and_check_api_hashes(); -#endif - - GDMonoLog::get_singleton()->initialize(); - - String assembly_rootdir; - String config_dir; + String bundled_assembly_rootdir = GodotSharpDirs::get_data_mono_lib_dir(); + String bundled_config_dir = GodotSharpDirs::get_data_mono_etc_dir(); #ifdef TOOLS_ENABLED + #if defined(WINDOWS_ENABLED) mono_reg_info = MonoRegUtils::find_mono(); if (mono_reg_info.assembly_dir.length() && DirAccess::exists(mono_reg_info.assembly_dir)) { - assembly_rootdir = mono_reg_info.assembly_dir; + r_assembly_rootdir = mono_reg_info.assembly_dir; } if (mono_reg_info.config_dir.length() && DirAccess::exists(mono_reg_info.config_dir)) { - config_dir = mono_reg_info.config_dir; + r_config_dir = mono_reg_info.config_dir; } #elif defined(OSX_ENABLED) const char *c_assembly_rootdir = mono_assembly_getrootdir(); @@ -244,29 +271,24 @@ void GDMono::initialize() { String hint_config_dir = path::join(locations[i], "etc"); if (FileAccess::exists(hint_mscorlib_path) && DirAccess::exists(hint_config_dir)) { - assembly_rootdir = hint_assembly_rootdir; - config_dir = hint_config_dir; + r_assembly_rootdir = hint_assembly_rootdir; + r_config_dir = hint_config_dir; break; } } } #endif -#endif // TOOLS_ENABLED - - String bundled_assembly_rootdir = GodotSharpDirs::get_data_mono_lib_dir(); - String bundled_config_dir = GodotSharpDirs::get_data_mono_etc_dir(); -#ifdef TOOLS_ENABLED if (DirAccess::exists(bundled_assembly_rootdir)) { - assembly_rootdir = bundled_assembly_rootdir; + r_assembly_rootdir = bundled_assembly_rootdir; } if (DirAccess::exists(bundled_config_dir)) { - config_dir = bundled_config_dir; + r_config_dir = bundled_config_dir; } #ifdef WINDOWS_ENABLED - if (assembly_rootdir.empty() || config_dir.empty()) { + if (r_assembly_rootdir.empty() || r_config_dir.empty()) { 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); @@ -274,35 +296,47 @@ void GDMono::initialize() { #endif // WINDOWS_ENABLED #else - // These are always the directories in export templates - assembly_rootdir = bundled_assembly_rootdir; - config_dir = bundled_config_dir; -#endif // TOOLS_ENABLED + // Export templates always use the bundled directories + r_assembly_rootdir = bundled_assembly_rootdir; + r_config_dir = bundled_config_dir; +#endif +} + +void GDMono::initialize() { + + ERR_FAIL_NULL(Engine::get_singleton()); + + print_verbose("Mono: Initializing module..."); + + char *runtime_build_info = mono_get_runtime_build_info(); + print_verbose("Mono JIT compiler version " + String(runtime_build_info)); + mono_free(runtime_build_info); + + _init_godot_api_hashes(); + _init_exception_policy(); + + GDMonoLog::get_singleton()->initialize(); + +#if !defined(JAVASCRIPT_ENABLED) + String assembly_rootdir; + String config_dir; + determine_mono_dirs(assembly_rootdir, config_dir); // Leak if we call mono_set_dirs more than once mono_set_dirs(assembly_rootdir.length() ? assembly_rootdir.utf8().get_data() : NULL, config_dir.length() ? config_dir.utf8().get_data() : NULL); add_mono_shared_libs_dir_to_path(); +#endif - { - PropertyInfo exc_policy_prop = PropertyInfo(Variant::INT, "mono/unhandled_exception_policy", PROPERTY_HINT_ENUM, - vformat("Terminate Application:%s,Log Error:%s", (int)POLICY_TERMINATE_APP, (int)POLICY_LOG_ERROR)); - unhandled_exception_policy = (UnhandledExceptionPolicy)(int)GLOBAL_DEF(exc_policy_prop.name, (int)POLICY_TERMINATE_APP); - ProjectSettings::get_singleton()->set_custom_property_info(exc_policy_prop.name, exc_policy_prop); - - if (Engine::get_singleton()->is_editor_hint()) { - // Unhandled exceptions should not terminate the editor - unhandled_exception_policy = POLICY_LOG_ERROR; - } - } +#if defined(ANDROID_ENABLED) + GDMonoAndroid::initialize(); +#endif GDMonoAssembly::initialize(); - gdmono_profiler_init(); - -#ifdef DEBUG_ENABLED - gdmono_debug_init(); +#if !defined(JAVASCRIPT_ENABLED) + gd_mono_profiler_init(); #endif #ifdef ANDROID_ENABLED @@ -314,9 +348,12 @@ void GDMono::initialize() { mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL); #ifndef TOOLS_ENABLED - // Export templates only load the Mono runtime if the project uses it - if (!DirAccess::exists("res://.mono")) + // Exported games that don't use C# must still work. They likely don't ship with mscorlib. + // We only initialize the Mono runtime if we can find mscorlib. Otherwise it would crash. + if (GDMonoAssembly::find_assembly("mscorlib.dll").empty()) { + print_verbose("Mono: Skipping runtime initialization because 'mscorlib.dll' could not be found"); return; + } #endif #if !defined(WINDOWS_ENABLED) && !defined(NO_MONO_THREADS_SUSPEND_WORKAROUND) @@ -326,17 +363,26 @@ void GDMono::initialize() { } #endif - root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); + // NOTE: Internal calls must be registered after the Mono runtime initialization. + // Otherwise registration fails with the error: 'assertion 'hash != NULL' failed'. + + root_domain = gd_initialize_mono_runtime(); ERR_FAIL_NULL_MSG(root_domain, "Mono: Failed to initialize runtime."); GDMonoUtils::set_main_thread(GDMonoUtils::get_current_thread()); - setup_runtime_main_args(); // Required for System.Environment.GetCommandLineArgs +#if !defined(JAVASCRIPT_ENABLED) + gd_mono_setup_runtime_main_args(); // Required for System.Environment.GetCommandLineArgs +#endif runtime_initialized = true; print_verbose("Mono: Runtime initialized"); +#if defined(ANDROID_ENABLED) + GDMonoAndroid::register_internal_calls(); +#endif + // mscorlib assembly MUST be present at initialization bool corlib_loaded = _load_corlib_assembly(); ERR_FAIL_COND_MSG(!corlib_loaded, "Mono: Failed to load mscorlib assembly."); @@ -344,8 +390,8 @@ void GDMono::initialize() { 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); +#if defined(DEBUG_ENABLED) && !defined(JAVASCRIPT_ENABLED) + bool debugger_attached = gd_mono_wait_for_debugger_msecs(500); if (!debugger_attached && OS::get_singleton()->is_stdout_verbose()) print_error("Mono: Debugger wait timeout"); #endif @@ -381,7 +427,7 @@ void GDMono::initialize_load_assemblies() { } bool GDMono::_are_api_assemblies_out_of_sync() { - bool out_of_sync = core_api_assembly.assembly && (core_api_assembly.out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated); + bool out_of_sync = core_api_assembly.assembly && (core_api_assembly.out_of_sync || !GDMonoCache::cached_data.godot_api_cache_updated); #ifdef TOOLS_ENABLED if (!out_of_sync) out_of_sync = editor_api_assembly.assembly && editor_api_assembly.out_of_sync; @@ -429,9 +475,8 @@ void GDMono::_register_internal_calls() { GodotSharpBindings::register_generated_icalls(); } -void GDMono::_initialize_and_check_api_hashes() { -#ifdef MONO_GLUE_ENABLED -#ifdef DEBUG_METHODS_ENABLED +void GDMono::_init_godot_api_hashes() { +#if defined(MONO_GLUE_ENABLED) && defined(DEBUG_METHODS_ENABLED) if (get_api_core_hash() != GodotSharpBindings::get_core_api_hash()) { ERR_PRINT("Mono: Core API hash mismatch."); } @@ -441,8 +486,19 @@ void GDMono::_initialize_and_check_api_hashes() { ERR_PRINT("Mono: Editor API hash mismatch."); } #endif // TOOLS_ENABLED -#endif // DEBUG_METHODS_ENABLED -#endif // MONO_GLUE_ENABLED +#endif // MONO_GLUE_ENABLED && DEBUG_METHODS_ENABLED +} + +void GDMono::_init_exception_policy() { + PropertyInfo exc_policy_prop = PropertyInfo(Variant::INT, "mono/unhandled_exception_policy", PROPERTY_HINT_ENUM, + vformat("Terminate Application:%s,Log Error:%s", (int)POLICY_TERMINATE_APP, (int)POLICY_LOG_ERROR)); + unhandled_exception_policy = (UnhandledExceptionPolicy)(int)GLOBAL_DEF(exc_policy_prop.name, (int)POLICY_TERMINATE_APP); + ProjectSettings::get_singleton()->set_custom_property_info(exc_policy_prop.name, exc_policy_prop); + + if (Engine::get_singleton()->is_editor_hint()) { + // Unhandled exceptions should not terminate the editor + unhandled_exception_policy = POLICY_LOG_ERROR; + } } void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) { @@ -561,7 +617,7 @@ bool GDMono::_load_corlib_assembly() { bool success = load_assembly("mscorlib", &corlib_assembly); if (success) - GDMonoUtils::update_corlib_cache(); + GDMonoCache::update_corlib_cache(); return success; } @@ -834,9 +890,9 @@ bool GDMono::_try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, Lo } bool GDMono::_on_core_api_assembly_loaded() { - GDMonoUtils::update_godot_api_cache(); + GDMonoCache::update_godot_api_cache(); - if (!GDMonoUtils::mono_cache.godot_api_cache_updated) + if (!GDMonoCache::cached_data.godot_api_cache_updated) return false; get_singleton()->_install_trace_listener(); @@ -884,7 +940,7 @@ 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."); - } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) { + } else if (!GDMonoCache::cached_data.godot_api_cache_updated) { ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed."); } @@ -984,7 +1040,7 @@ Error GDMono::_unload_scripts_domain() { mono_gc_collect(mono_gc_max_generation()); - GDMonoUtils::clear_godot_api_cache(); + GDMonoCache::clear_godot_api_cache(); _domain_assemblies_cleanup(mono_domain_get_id(scripts_domain)); @@ -1204,6 +1260,10 @@ GDMono::~GDMono() { mono_jit_cleanup(root_domain); +#if defined(ANDROID_ENABLED) + GDMonoAndroid::cleanup(); +#endif + print_verbose("Mono: Finalized"); runtime_initialized = false; diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index e14a0d8409..7fb03b82ad 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -153,7 +153,8 @@ private: #ifdef TOOLS_ENABLED uint64_t api_editor_hash; #endif - void _initialize_and_check_api_hashes(); + void _init_godot_api_hashes(); + void _init_exception_policy(); GDMonoLog *gdmono_log; @@ -162,6 +163,7 @@ private: #endif void add_mono_shared_libs_dir_to_path(); + void determine_mono_dirs(String &r_assembly_rootdir, String &r_config_dir); protected: static GDMono *singleton; diff --git a/modules/mono/mono_gd/gd_mono_android.cpp b/modules/mono/mono_gd/gd_mono_android.cpp new file mode 100644 index 0000000000..86af8d1812 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_android.cpp @@ -0,0 +1,685 @@ +/*************************************************************************/ +/* gd_mono_android.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 "gd_mono_android.h" + +#if defined(ANDROID_ENABLED) + +#include <dlfcn.h> // dlopen, dlsym +#include <mono/utils/mono-dl-fallback.h> +#include <sys/system_properties.h> +#include <cstddef> + +#if __ANDROID_API__ < 24 +#include "thirdparty/misc/ifaddrs-android.h" +#else +#include <ifaddrs.h> +#endif + +#include "core/os/os.h" +#include "core/ustring.h" +#include "platform/android/java_godot_wrapper.h" +#include "platform/android/os_android.h" +#include "platform/android/thread_jandroid.h" + +#include "../utils/path_utils.h" +#include "../utils/string_utils.h" +#include "gd_mono_cache.h" +#include "gd_mono_marshal.h" + +// Warning: JNI boilerplate ahead... continue at your own risk + +namespace GDMonoAndroid { + +template <typename T> +struct ScopedLocalRef { + JNIEnv *env; + T local_ref; + + _FORCE_INLINE_ T get() const { return local_ref; } + _FORCE_INLINE_ operator T() const { return local_ref; } + _FORCE_INLINE_ operator jvalue() const { return (jvalue)local_ref; } + + _FORCE_INLINE_ operator bool() const { return local_ref != NULL; } + + _FORCE_INLINE_ bool operator==(std::nullptr_t) const { + return local_ref == nullptr; + } + + _FORCE_INLINE_ bool operator!=(std::nullptr_t) const { + return local_ref != nullptr; + } + + ScopedLocalRef(const ScopedLocalRef &) = delete; + ScopedLocalRef &operator=(const ScopedLocalRef &) = delete; + + ScopedLocalRef(JNIEnv *p_env, T p_local_ref) : + env(p_env), local_ref(p_local_ref) { + } + + ~ScopedLocalRef() { + if (local_ref) { + env->DeleteLocalRef(local_ref); + } + } +}; + +bool jni_exception_check(JNIEnv *p_env) { + if (p_env->ExceptionCheck()) { + // Print the exception to logcat + p_env->ExceptionDescribe(); + + p_env->ExceptionClear(); + return true; + } + + return false; +} + +String app_native_lib_dir_cache; + +String determine_app_native_lib_dir() { + JNIEnv *env = ThreadAndroid::get_env(); + + ScopedLocalRef<jclass> activityThreadClass(env, env->FindClass("android/app/ActivityThread")); + jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;"); + ScopedLocalRef<jobject> activityThread(env, env->CallStaticObjectMethod(activityThreadClass, currentActivityThread)); + jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;"); + ScopedLocalRef<jobject> ctx(env, env->CallObjectMethod(activityThread, getApplication)); + + jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); + ScopedLocalRef<jobject> applicationInfo(env, env->CallObjectMethod(ctx, getApplicationInfo)); + jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;"); + ScopedLocalRef<jstring> nativeLibraryDir(env, (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField)); + + String result; + + const char *const nativeLibraryDirUtf8 = env->GetStringUTFChars(nativeLibraryDir, NULL); + if (nativeLibraryDirUtf8) { + result.parse_utf8(nativeLibraryDirUtf8); + env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDirUtf8); + } + + return result; +} + +String get_app_native_lib_dir() { + if (app_native_lib_dir_cache.empty()) + app_native_lib_dir_cache = determine_app_native_lib_dir(); + return app_native_lib_dir_cache; +} + +int gd_mono_convert_dl_flags(int flags) { + // from mono's runtime-bootstrap.c + + int lflags = flags & MONO_DL_LOCAL ? 0 : RTLD_GLOBAL; + + if (flags & MONO_DL_LAZY) + lflags |= RTLD_LAZY; + else + lflags |= RTLD_NOW; + + return lflags; +} + +#ifndef GD_MONO_ANDROID_SO_NAME +#define GD_MONO_ANDROID_SO_NAME "libmonosgen-2.0.so" +#endif + +const char *mono_so_name = GD_MONO_ANDROID_SO_NAME; +const char *godot_so_name = "libgodot_android.so"; + +void *mono_dl_handle = NULL; +void *godot_dl_handle = NULL; + +void *try_dlopen(const String &p_so_path, int p_flags) { + if (!FileAccess::exists(p_so_path)) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Cannot find shared library: '%s'\n", p_so_path.utf8().get_data()); + return NULL; + } + + int lflags = gd_mono_convert_dl_flags(p_flags); + + void *handle = dlopen(p_so_path.utf8().get_data(), lflags); + + if (!handle) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Failed to open shared library: '%s'. Error: '%s'\n", p_so_path.utf8().get_data(), dlerror()); + return NULL; + } + + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Successfully loaded shared library: '%s'\n", p_so_path.utf8().get_data()); + + return handle; +} + +void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void *p_user_data) { + if (p_name == NULL) { + // __Internal + + if (!mono_dl_handle) { + String app_native_lib_dir = get_app_native_lib_dir(); + String so_path = path::join(app_native_lib_dir, mono_so_name); + + mono_dl_handle = try_dlopen(so_path, p_flags); + } + + return mono_dl_handle; + } + + String name = String::utf8(p_name); + + if (name.ends_with(".dll.so") || name.ends_with(".exe.so")) { + String app_native_lib_dir = get_app_native_lib_dir(); + + String orig_so_name = name.get_file(); + String so_name = "lib-aot-" + orig_so_name; + String so_path = path::join(app_native_lib_dir, so_name); + + return try_dlopen(so_path, p_flags); + } + + return NULL; +} + +void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, void *p_user_data) { + void *sym_addr = dlsym(p_handle, p_name); + + if (sym_addr) + return sym_addr; + + if (p_handle == mono_dl_handle && godot_dl_handle) { + // Looking up for '__Internal' P/Invoke. We want to search in both the Mono and Godot shared libraries. + // This is needed to resolve the monodroid P/Invoke functions that are defined at the bottom of the file. + sym_addr = dlsym(godot_dl_handle, p_name); + + if (sym_addr) + return sym_addr; + } + + if (r_err) + *r_err = str_format_new("%s\n", dlerror()); + + return NULL; +} + +void *gd_mono_android_dlclose(void *p_handle, void *p_user_data) { + dlclose(p_handle); + + // Not sure if this ever happens. Does Mono close the handle for the main module? + if (p_handle == mono_dl_handle) + mono_dl_handle = NULL; + + return NULL; +} + +int32_t build_version_sdk_int = 0; + +int32_t get_build_version_sdk_int() { + // The JNI code is the equivalent of: + // + // android.os.Build.VERSION.SDK_INT + + if (build_version_sdk_int == 0) { + JNIEnv *env = ThreadAndroid::get_env(); + + jclass versionClass = env->FindClass("android/os/Build$VERSION"); + ERR_FAIL_NULL_V(versionClass, 0); + + jfieldID sdkIntField = env->GetStaticFieldID(versionClass, "SDK_INT", "I"); + ERR_FAIL_NULL_V(sdkIntField, 0); + + build_version_sdk_int = (int32_t)env->GetStaticIntField(versionClass, sdkIntField); + } + + return build_version_sdk_int; +} + +jobject certStore = NULL; // KeyStore + +MonoBoolean _gd_mono_init_cert_store() { + // The JNI code is the equivalent of: + // + // try { + // certStoreLocal = KeyStore.getInstance("AndroidCAStore"); + // certStoreLocal.load(null); + // certStore = certStoreLocal; + // return true; + // } catch (Exception e) { + // return false; + // } + + JNIEnv *env = ThreadAndroid::get_env(); + + ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore")); + + jmethodID getInstance = env->GetStaticMethodID(keyStoreClass, "getInstance", "(Ljava/lang/String;)Ljava/security/KeyStore;"); + jmethodID load = env->GetMethodID(keyStoreClass, "load", "(Ljava/security/KeyStore$LoadStoreParameter;)V"); + + ScopedLocalRef<jstring> androidCAStoreString(env, env->NewStringUTF("AndroidCAStore")); + + ScopedLocalRef<jobject> certStoreLocal(env, env->CallStaticObjectMethod(keyStoreClass, getInstance, androidCAStoreString.get())); + + if (jni_exception_check(env)) + return 0; + + env->CallVoidMethod(certStoreLocal, load, NULL); + + if (jni_exception_check(env)) + return 0; + + certStore = env->NewGlobalRef(certStoreLocal); + + return 1; +} + +MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { + // The JNI code is the equivalent of: + // + // Certificate certificate = certStore.getCertificate(alias); + // if (certificate == null) + // return null; + // return certificate.getEncoded(); + + MonoError mono_error; + char *alias_utf8 = mono_string_to_utf8_checked(p_alias, &mono_error); + + if (!mono_error_ok(&mono_error)) { + ERR_PRINTS(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&mono_error) + "'."); + mono_error_cleanup(&mono_error); + return NULL; + } + + JNIEnv *env = ThreadAndroid::get_env(); + + ScopedLocalRef<jstring> js_alias(env, env->NewStringUTF(alias_utf8)); + mono_free(alias_utf8); + + ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore")); + ERR_FAIL_NULL_V(keyStoreClass, NULL); + ScopedLocalRef<jclass> certificateClass(env, env->FindClass("java/security/cert/Certificate")); + ERR_FAIL_NULL_V(certificateClass, NULL); + + jmethodID getCertificate = env->GetMethodID(keyStoreClass, "getCertificate", "(Ljava/lang/String;)Ljava/security/cert/Certificate;"); + ERR_FAIL_NULL_V(getCertificate, NULL); + + jmethodID getEncoded = env->GetMethodID(certificateClass, "getEncoded", "()[B"); + ERR_FAIL_NULL_V(getEncoded, NULL); + + ScopedLocalRef<jobject> certificate(env, env->CallObjectMethod(certStore, getCertificate, js_alias.get())); + + if (!certificate) + return NULL; + + ScopedLocalRef<jbyteArray> encoded(env, (jbyteArray)env->CallObjectMethod(certificate, getEncoded)); + jsize encodedLength = env->GetArrayLength(encoded); + + MonoArray *encoded_ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), encodedLength); + uint8_t *dest = (uint8_t *)mono_array_addr(encoded_ret, uint8_t, 0); + + env->GetByteArrayRegion(encoded, 0, encodedLength, reinterpret_cast<jbyte *>(dest)); + + return encoded_ret; +} + +void initialize() { + // We need to set this environment variable to make the monodroid BCL use btls instead of legacy as the default provider + OS::get_singleton()->set_environment("XA_TLS_PROVIDER", "btls"); + + mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, gd_mono_android_dlclose, NULL); + + String app_native_lib_dir = get_app_native_lib_dir(); + String so_path = path::join(app_native_lib_dir, godot_so_name); + + godot_dl_handle = try_dlopen(so_path, gd_mono_convert_dl_flags(MONO_DL_LAZY)); +} + +void register_internal_calls() { + mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", (void *)_gd_mono_init_cert_store); + mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", (void *)_gd_mono_android_cert_store_lookup); +} + +void cleanup() { + // This is called after shutting down the Mono runtime + + if (mono_dl_handle) + gd_mono_android_dlclose(mono_dl_handle, NULL); + + if (godot_dl_handle) + gd_mono_android_dlclose(godot_dl_handle, NULL); + + JNIEnv *env = ThreadAndroid::get_env(); + + if (certStore) { + env->DeleteGlobalRef(certStore); + certStore = NULL; + } +} + +} // namespace GDMonoAndroid + +using namespace GDMonoAndroid; + +// The following are P/Invoke functions required by the monodroid profile of the BCL. +// These are P/Invoke functions and not internal calls, hence why they use +// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'. + +#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default"))) + +GD_PINVOKE_EXPORT int32_t _monodroid_get_android_api_level() { + return get_build_version_sdk_int(); +} + +GD_PINVOKE_EXPORT void monodroid_free(void *ptr) { + free(ptr); +} + +GD_PINVOKE_EXPORT int32_t monodroid_get_system_property(const char *p_name, char **r_value) { + char prop_value_str[PROP_VALUE_MAX + 1] = { 0 }; + + int len = __system_property_get(p_name, prop_value_str); + + if (r_value) { + if (len >= 0) { + *r_value = (char *)malloc(len + 1); + if (!*r_value) + return -1; + memcpy(*r_value, prop_value_str, len); + (*r_value)[len] = '\0'; + } else { + *r_value = NULL; + } + } + + return len; +} + +GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_up_state(const char *p_ifname, mono_bool *r_is_up) { + // The JNI code is the equivalent of: + // + // NetworkInterface.getByName(p_ifname).isUp() + + if (!r_is_up || !p_ifname || strlen(p_ifname) == 0) + return 0; + + *r_is_up = 0; + + JNIEnv *env = ThreadAndroid::get_env(); + + jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface"); + ERR_FAIL_NULL_V(networkInterfaceClass, 0); + + jmethodID getByName = env->GetStaticMethodID(networkInterfaceClass, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;"); + ERR_FAIL_NULL_V(getByName, 0); + + jmethodID isUp = env->GetMethodID(networkInterfaceClass, "isUp", "()Z"); + ERR_FAIL_NULL_V(isUp, 0); + + ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname)); + ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get())); + + if (!networkInterface) + return 0; + + *r_is_up = (mono_bool)env->CallBooleanMethod(networkInterface, isUp); + + return 1; +} + +GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_supports_multicast(const char *p_ifname, mono_bool *r_supports_multicast) { + // The JNI code is the equivalent of: + // + // NetworkInterface.getByName(p_ifname).supportsMulticast() + + if (!r_supports_multicast || !p_ifname || strlen(p_ifname) == 0) + return 0; + + *r_supports_multicast = 0; + + JNIEnv *env = ThreadAndroid::get_env(); + + jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface"); + ERR_FAIL_NULL_V(networkInterfaceClass, 0); + + jmethodID getByName = env->GetStaticMethodID(networkInterfaceClass, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;"); + ERR_FAIL_NULL_V(getByName, 0); + + jmethodID supportsMulticast = env->GetMethodID(networkInterfaceClass, "supportsMulticast", "()Z"); + ERR_FAIL_NULL_V(supportsMulticast, 0); + + ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname)); + ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get())); + + if (!networkInterface) + return 0; + + *r_supports_multicast = (mono_bool)env->CallBooleanMethod(networkInterface, supportsMulticast); + + return 1; +} + +static const int dns_servers_len = 8; + +static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dns_servers_count) { + // The JNI code is the equivalent of: + // + // ConnectivityManager connectivityManager = (ConnectivityManager)getApplicationContext() + // .getSystemService(Context.CONNECTIVITY_SERVICE); + // Network activeNerwork = connectivityManager.getActiveNetwork(); + // LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNerwork); + // List<String> dnsServers = linkProperties.getDnsServers().stream() + // .map(inetAddress -> inetAddress.getHostAddress()).collect(Collectors.toList()); + +#ifdef DEBUG_ENABLED + CRASH_COND(get_build_version_sdk_int() < 23); +#endif + + JNIEnv *env = ThreadAndroid::get_env(); + + GodotJavaWrapper *godot_java = ((OS_Android *)OS::get_singleton())->get_godot_java(); + jobject activity = godot_java->get_activity(); + + ScopedLocalRef<jclass> activityClass(env, env->GetObjectClass(activity)); + ERR_FAIL_NULL(activityClass); + + jmethodID getApplicationContext = env->GetMethodID(activityClass, "getApplicationContext", "()Landroid/content/Context;"); + + ScopedLocalRef<jobject> applicationContext(env, env->CallObjectMethod(activity, getApplicationContext)); + + ScopedLocalRef<jclass> contextClass(env, env->FindClass("android/content/Context")); + ERR_FAIL_NULL(contextClass); + + jfieldID connectivityServiceField = env->GetStaticFieldID(contextClass, "CONNECTIVITY_SERVICE", "Ljava/lang/String;"); + ScopedLocalRef<jstring> connectivityServiceString(env, (jstring)env->GetStaticObjectField(contextClass, connectivityServiceField)); + + jmethodID getSystemService = env->GetMethodID(contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + + ScopedLocalRef<jobject> connectivityManager(env, env->CallObjectMethod(applicationContext, getSystemService, connectivityServiceString.get())); + + if (!connectivityManager) + return; + + ScopedLocalRef<jclass> connectivityManagerClass(env, env->FindClass("android/net/ConnectivityManager")); + ERR_FAIL_NULL(connectivityManagerClass); + + jmethodID getActiveNetwork = env->GetMethodID(connectivityManagerClass, "getActiveNetwork", "()Landroid/net/Network;"); + ERR_FAIL_NULL(getActiveNetwork); + + ScopedLocalRef<jobject> activeNetwork(env, env->CallObjectMethod(connectivityManager, getActiveNetwork)); + + if (!activeNetwork) + return; + + jmethodID getLinkProperties = env->GetMethodID(connectivityManagerClass, + "getLinkProperties", "(Landroid/net/Network;)Landroid/net/LinkProperties;"); + ERR_FAIL_NULL(getLinkProperties); + + ScopedLocalRef<jobject> linkProperties(env, env->CallObjectMethod(connectivityManager, getLinkProperties, activeNetwork.get())); + + if (!linkProperties) + return; + + ScopedLocalRef<jclass> linkPropertiesClass(env, env->FindClass("android/net/LinkProperties")); + ERR_FAIL_NULL(linkPropertiesClass); + + jmethodID getDnsServers = env->GetMethodID(linkPropertiesClass, "getDnsServers", "()Ljava/util/List;"); + ERR_FAIL_NULL(getDnsServers); + + ScopedLocalRef<jobject> dnsServers(env, env->CallObjectMethod(linkProperties, getDnsServers)); + + if (!dnsServers) + return; + + ScopedLocalRef<jclass> listClass(env, env->FindClass("java/util/List")); + ERR_FAIL_NULL(listClass); + + jmethodID listSize = env->GetMethodID(listClass, "size", "()I"); + ERR_FAIL_NULL(listSize); + + int dnsServersCount = env->CallIntMethod(dnsServers, listSize); + + if (dnsServersCount > dns_servers_len) + dnsServersCount = dns_servers_len; + + if (dnsServersCount <= 0) + return; + + jmethodID listGet = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"); + ERR_FAIL_NULL(listGet); + + ScopedLocalRef<jclass> inetAddressClass(env, env->FindClass("java/net/InetAddress")); + ERR_FAIL_NULL(inetAddressClass); + + jmethodID getHostAddress = env->GetMethodID(inetAddressClass, "getHostAddress", "()Ljava/lang/String;"); + ERR_FAIL_NULL(getHostAddress); + + for (int i = 0; i < dnsServersCount; i++) { + ScopedLocalRef<jobject> dnsServer(env, env->CallObjectMethod(dnsServers, listGet, (jint)i)); + if (!dnsServer) + continue; + + ScopedLocalRef<jstring> hostAddress(env, (jstring)env->CallObjectMethod(dnsServer, getHostAddress)); + const char *host_address = env->GetStringUTFChars(hostAddress, 0); + + r_dns_servers[i] = strdup(host_address); // freed by the BCL + (*dns_servers_count)++; + + env->ReleaseStringUTFChars(hostAddress, host_address); + } + + // jesus... +} + +GD_PINVOKE_EXPORT int32_t _monodroid_get_dns_servers(void **r_dns_servers_array) { + if (!r_dns_servers_array) + return -1; + + *r_dns_servers_array = NULL; + + char *dns_servers[dns_servers_len]; + int dns_servers_count = 0; + + if (_monodroid_get_android_api_level() < 26) { + // The 'net.dns*' system properties are no longer available in Android 8.0 (API level 26) and greater: + // https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri + + char prop_name[] = "net.dns*"; + + for (int i = 0; i < dns_servers_len; i++) { + prop_name[7] = (char)(i + 0x31); + char *prop_value; + int32_t len = monodroid_get_system_property(prop_name, &prop_value); + + if (len > 0) { + dns_servers[dns_servers_count] = strndup(prop_value, (size_t)len); // freed by the BCL + dns_servers_count++; + free(prop_value); + } + } + } else { + // Alternative for Oreo and greater + interop_get_active_network_dns_servers(dns_servers, &dns_servers_count); + } + + if (dns_servers_count > 0) { + size_t ret_size = sizeof(char *) * (size_t)dns_servers_count; + *r_dns_servers_array = malloc(ret_size); // freed by the BCL + memcpy(*r_dns_servers_array, dns_servers, ret_size); + } + + return dns_servers_count; +} + +GD_PINVOKE_EXPORT const char *_monodroid_timezone_get_default_id() { + // The JNI code is the equivalent of: + // + // TimeZone.getDefault().getID() + + JNIEnv *env = ThreadAndroid::get_env(); + + ScopedLocalRef<jclass> timeZoneClass(env, env->FindClass("java/util/TimeZone")); + ERR_FAIL_NULL_V(timeZoneClass, NULL); + + jmethodID getDefault = env->GetStaticMethodID(timeZoneClass, "getDefault", "()Ljava/util/TimeZone;"); + ERR_FAIL_NULL_V(getDefault, NULL); + + jmethodID getID = env->GetMethodID(timeZoneClass, "getID", "()Ljava/lang/String;"); + ERR_FAIL_NULL_V(getID, NULL); + + ScopedLocalRef<jobject> defaultTimeZone(env, env->CallStaticObjectMethod(timeZoneClass, getDefault)); + + if (!defaultTimeZone) + return NULL; + + ScopedLocalRef<jstring> defaultTimeZoneID(env, (jstring)env->CallObjectMethod(defaultTimeZone, getID)); + + if (!defaultTimeZoneID) + return NULL; + + const char *default_time_zone_id = env->GetStringUTFChars(defaultTimeZoneID, 0); + + char *result = strdup(default_time_zone_id); // freed by the BCL + + env->ReleaseStringUTFChars(defaultTimeZoneID, default_time_zone_id); + + return result; +} + +GD_PINVOKE_EXPORT int32_t _monodroid_getifaddrs(struct ifaddrs **p_ifap) { + return getifaddrs(p_ifap); +} + +GD_PINVOKE_EXPORT void _monodroid_freeifaddrs(struct ifaddrs *p_ifap) { + freeifaddrs(p_ifap); +} + +#endif diff --git a/modules/mono/utils/android_utils.h b/modules/mono/mono_gd/gd_mono_android.h index f911c3fdfe..20ca266428 100644 --- a/modules/mono/utils/android_utils.h +++ b/modules/mono/mono_gd/gd_mono_android.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* android_utils.h */ +/* gd_mono_android.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,21 +28,25 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef ANDROID_UTILS_H -#define ANDROID_UTILS_H +#ifndef GD_MONO_ANDROID_H +#define GD_MONO_ANDROID_H -#ifdef __ANDROID__ +#if defined(ANDROID_ENABLED) #include "core/ustring.h" -namespace GDMonoUtils { -namespace Android { +namespace GDMonoAndroid { String get_app_native_lib_dir(); -} // namespace Android -} // namespace GDMonoUtils +void initialize(); -#endif // __ANDROID__ +void register_internal_calls(); -#endif // ANDROID_UTILS_H +void cleanup(); + +} // namespace GDMonoAndroid + +#endif // ANDROID_ENABLED + +#endif // GD_MONO_ANDROID_H diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index a82bb42731..105560fe9a 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -39,6 +39,7 @@ #include "core/project_settings.h" #include "../godotsharp_dirs.h" +#include "gd_mono_cache.h" #include "gd_mono_class.h" bool GDMonoAssembly::no_search = false; @@ -61,6 +62,13 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin r_search_dirs.push_back(framework_dir.plus_file("Facades")); } +#if !defined(TOOLS_ENABLED) + String data_game_assemblies_dir = GodotSharpDirs::get_data_game_assemblies_dir(); + if (!data_game_assemblies_dir.empty()) { + r_search_dirs.push_back(data_game_assemblies_dir); + } +#endif + if (p_custom_config.length()) { r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(p_custom_config)); } else { @@ -146,10 +154,6 @@ MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, vo (void)user_data; // UNUSED - if (search_dirs.empty()) { - fill_search_dirs(search_dirs); - } - { // 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. @@ -227,6 +231,33 @@ GDMonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, cons return NULL; } +String GDMonoAssembly::find_assembly(const String &p_name) { + + String path; + + bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe"); + + for (int i = 0; i < search_dirs.size(); i++) { + const String &search_dir = search_dirs[i]; + + if (has_extension) { + path = search_dir.plus_file(p_name); + if (FileAccess::exists(path)) + return path; + } else { + path = search_dir.plus_file(p_name + ".dll"); + if (FileAccess::exists(path)) + return path; + + path = search_dir.plus_file(p_name + ".exe"); + if (FileAccess::exists(path)) + return path; + } + } + + return String(); +} + GDMonoAssembly *GDMonoAssembly::_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly) { GDMonoAssembly *assembly = memnew(GDMonoAssembly(p_name, p_path)); @@ -263,6 +294,8 @@ void GDMonoAssembly::_wrap_mono_assembly(MonoAssembly *assembly) { void GDMonoAssembly::initialize() { + fill_search_dirs(search_dirs); + mono_install_assembly_search_hook(&assembly_search_hook, NULL); mono_install_assembly_refonly_search_hook(&assembly_refonly_search_hook, NULL); mono_install_assembly_preload_hook(&assembly_preload_hook, NULL); diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h index 39749dfc1d..04a219f742 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ b/modules/mono/mono_gd/gd_mono_assembly.h @@ -122,6 +122,8 @@ public: GDMonoClass *get_object_derived_class(const StringName &p_class); + static String find_assembly(const String &p_name); + static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String()); static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly); diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp new file mode 100644 index 0000000000..caa1ca9203 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -0,0 +1,312 @@ +/*************************************************************************/ +/* gd_mono_cache.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 "gd_mono_cache.h" + +#include "gd_mono.h" +#include "gd_mono_class.h" +#include "gd_mono_marshal.h" +#include "gd_mono_method.h" +#include "gd_mono_utils.h" + +namespace GDMonoCache { + +CachedData cached_data; + +#define CACHE_AND_CHECK(m_var, m_val) \ + { \ + CRASH_COND(m_var != NULL); \ + m_var = m_val; \ + ERR_FAIL_COND_MSG(m_var == NULL, "Mono Cache: Member " #m_var " is null."); \ + } + +#define CACHE_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(cached_data.class_##m_class, m_val) +#define CACHE_NS_CLASS_AND_CHECK(m_ns, m_class, m_val) CACHE_AND_CHECK(cached_data.class_##m_ns##_##m_class, m_val) +#define CACHE_RAW_MONO_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(cached_data.rawclass_##m_class, m_val) +#define CACHE_FIELD_AND_CHECK(m_class, m_field, m_val) CACHE_AND_CHECK(cached_data.field_##m_class##_##m_field, m_val) +#define CACHE_METHOD_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(cached_data.method_##m_class##_##m_method, m_val) +#define CACHE_PROPERTY_AND_CHECK(m_class, m_property, m_val) CACHE_AND_CHECK(cached_data.property_##m_class##_##m_property, m_val) + +#define CACHE_METHOD_THUNK_AND_CHECK_IMPL(m_var, m_val) \ + { \ + CRASH_COND(!m_var.is_null()); \ + ERR_FAIL_COND_MSG(m_val == NULL, "Mono Cache: Method for member " #m_var " is null."); \ + m_var.set_from_method(m_val); \ + ERR_FAIL_COND_MSG(m_var.is_null(), "Mono Cache: Member " #m_var " is null."); \ + } + +#define CACHE_METHOD_THUNK_AND_CHECK(m_class, m_method, m_val) CACHE_METHOD_THUNK_AND_CHECK_IMPL(cached_data.methodthunk_##m_class##_##m_method, m_val) + +void CachedData::clear_corlib_cache() { + + corlib_cache_updated = false; + + class_MonoObject = NULL; + class_bool = NULL; + class_int8_t = NULL; + class_int16_t = NULL; + class_int32_t = NULL; + class_int64_t = NULL; + class_uint8_t = NULL; + class_uint16_t = NULL; + class_uint32_t = NULL; + class_uint64_t = NULL; + class_float = NULL; + class_double = NULL; + class_String = NULL; + class_IntPtr = NULL; + + class_System_Collections_IEnumerable = NULL; + class_System_Collections_IDictionary = NULL; + +#ifdef DEBUG_ENABLED + class_System_Diagnostics_StackTrace = NULL; + methodthunk_System_Diagnostics_StackTrace_GetFrames.nullify(); + method_System_Diagnostics_StackTrace_ctor_bool = NULL; + method_System_Diagnostics_StackTrace_ctor_Exception_bool = NULL; +#endif + + class_KeyNotFoundException = NULL; +} + +void CachedData::clear_godot_api_cache() { + + godot_api_cache_updated = false; + + rawclass_Dictionary = NULL; + + class_Vector2 = NULL; + class_Rect2 = NULL; + class_Transform2D = NULL; + class_Vector3 = NULL; + class_Basis = NULL; + class_Quat = NULL; + class_Transform = NULL; + class_AABB = NULL; + class_Color = NULL; + class_Plane = NULL; + class_NodePath = NULL; + class_RID = NULL; + class_GodotObject = NULL; + class_GodotResource = NULL; + class_Node = NULL; + class_Control = NULL; + class_Spatial = NULL; + class_WeakRef = NULL; + class_Array = NULL; + class_Dictionary = NULL; + class_MarshalUtils = NULL; + class_ISerializationListener = NULL; + +#ifdef DEBUG_ENABLED + class_DebuggingUtils = NULL; + methodthunk_DebuggingUtils_GetStackFrameInfo.nullify(); +#endif + + class_ExportAttribute = NULL; + field_ExportAttribute_hint = NULL; + field_ExportAttribute_hintString = NULL; + class_SignalAttribute = NULL; + class_ToolAttribute = NULL; + class_RemoteAttribute = NULL; + class_SyncAttribute = NULL; + class_MasterAttribute = NULL; + class_PuppetAttribute = NULL; + class_SlaveAttribute = NULL; + class_RemoteSyncAttribute = NULL; + class_MasterSyncAttribute = NULL; + class_PuppetSyncAttribute = NULL; + class_GodotMethodAttribute = NULL; + field_GodotMethodAttribute_methodName = NULL; + + field_GodotObject_ptr = NULL; + field_NodePath_ptr = NULL; + field_Image_ptr = NULL; + field_RID_ptr = NULL; + + methodthunk_GodotObject_Dispose.nullify(); + methodthunk_Array_GetPtr.nullify(); + methodthunk_Dictionary_GetPtr.nullify(); + methodthunk_SignalAwaiter_SignalCallback.nullify(); + methodthunk_SignalAwaiter_FailureCallback.nullify(); + methodthunk_GodotTaskScheduler_Activate.nullify(); + + // Start of MarshalUtils methods + + methodthunk_MarshalUtils_TypeIsGenericArray.nullify(); + methodthunk_MarshalUtils_TypeIsGenericDictionary.nullify(); + + methodthunk_MarshalUtils_ArrayGetElementType.nullify(); + methodthunk_MarshalUtils_DictionaryGetKeyValueTypes.nullify(); + + methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType.nullify(); + methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType.nullify(); + methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType_with_info.nullify(); + methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType_with_info.nullify(); + + methodthunk_MarshalUtils_MakeGenericArrayType.nullify(); + methodthunk_MarshalUtils_MakeGenericDictionaryType.nullify(); + + methodthunk_MarshalUtils_EnumerableToArray.nullify(); + methodthunk_MarshalUtils_IDictionaryToDictionary.nullify(); + methodthunk_MarshalUtils_GenericIDictionaryToDictionary.nullify(); + + // End of MarshalUtils methods + + task_scheduler_handle = Ref<MonoGCHandle>(); +} + +#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class)) +#define GODOT_API_NS_CLASS(m_ns, m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(m_ns, #m_class)) + +void update_corlib_cache() { + + CACHE_CLASS_AND_CHECK(MonoObject, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_object_class())); + CACHE_CLASS_AND_CHECK(bool, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_boolean_class())); + CACHE_CLASS_AND_CHECK(int8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_sbyte_class())); + CACHE_CLASS_AND_CHECK(int16_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int16_class())); + CACHE_CLASS_AND_CHECK(int32_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int32_class())); + CACHE_CLASS_AND_CHECK(int64_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int64_class())); + CACHE_CLASS_AND_CHECK(uint8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_byte_class())); + CACHE_CLASS_AND_CHECK(uint16_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint16_class())); + CACHE_CLASS_AND_CHECK(uint32_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint32_class())); + CACHE_CLASS_AND_CHECK(uint64_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint64_class())); + CACHE_CLASS_AND_CHECK(float, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_single_class())); + CACHE_CLASS_AND_CHECK(double, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_double_class())); + CACHE_CLASS_AND_CHECK(String, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_string_class())); + CACHE_CLASS_AND_CHECK(IntPtr, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_intptr_class())); + + CACHE_CLASS_AND_CHECK(System_Collections_IEnumerable, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IEnumerable")); + CACHE_CLASS_AND_CHECK(System_Collections_IDictionary, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IDictionary")); + +#ifdef DEBUG_ENABLED + CACHE_CLASS_AND_CHECK(System_Diagnostics_StackTrace, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Diagnostics", "StackTrace")); + CACHE_METHOD_THUNK_AND_CHECK(System_Diagnostics_StackTrace, GetFrames, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method("GetFrames")); + CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(bool)", true)); + CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_Exception_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(System.Exception,bool)", true)); +#endif + + CACHE_CLASS_AND_CHECK(KeyNotFoundException, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections.Generic", "KeyNotFoundException")); + + cached_data.corlib_cache_updated = true; +} + +void update_godot_api_cache() { + + CACHE_CLASS_AND_CHECK(Vector2, GODOT_API_CLASS(Vector2)); + CACHE_CLASS_AND_CHECK(Rect2, GODOT_API_CLASS(Rect2)); + CACHE_CLASS_AND_CHECK(Transform2D, GODOT_API_CLASS(Transform2D)); + CACHE_CLASS_AND_CHECK(Vector3, GODOT_API_CLASS(Vector3)); + CACHE_CLASS_AND_CHECK(Basis, GODOT_API_CLASS(Basis)); + CACHE_CLASS_AND_CHECK(Quat, GODOT_API_CLASS(Quat)); + CACHE_CLASS_AND_CHECK(Transform, GODOT_API_CLASS(Transform)); + CACHE_CLASS_AND_CHECK(AABB, GODOT_API_CLASS(AABB)); + CACHE_CLASS_AND_CHECK(Color, GODOT_API_CLASS(Color)); + CACHE_CLASS_AND_CHECK(Plane, GODOT_API_CLASS(Plane)); + CACHE_CLASS_AND_CHECK(NodePath, GODOT_API_CLASS(NodePath)); + CACHE_CLASS_AND_CHECK(RID, GODOT_API_CLASS(RID)); + CACHE_CLASS_AND_CHECK(GodotObject, GODOT_API_CLASS(Object)); + CACHE_CLASS_AND_CHECK(GodotResource, GODOT_API_CLASS(Resource)); + CACHE_CLASS_AND_CHECK(Node, GODOT_API_CLASS(Node)); + CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control)); + CACHE_CLASS_AND_CHECK(Spatial, GODOT_API_CLASS(Spatial)); + CACHE_CLASS_AND_CHECK(WeakRef, GODOT_API_CLASS(WeakRef)); + CACHE_CLASS_AND_CHECK(Array, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Array)); + CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)); + CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils)); + CACHE_CLASS_AND_CHECK(ISerializationListener, GODOT_API_CLASS(ISerializationListener)); + +#ifdef DEBUG_ENABLED + CACHE_CLASS_AND_CHECK(DebuggingUtils, GODOT_API_CLASS(DebuggingUtils)); +#endif + + // Attributes + CACHE_CLASS_AND_CHECK(ExportAttribute, GODOT_API_CLASS(ExportAttribute)); + CACHE_FIELD_AND_CHECK(ExportAttribute, hint, CACHED_CLASS(ExportAttribute)->get_field("hint")); + CACHE_FIELD_AND_CHECK(ExportAttribute, hintString, CACHED_CLASS(ExportAttribute)->get_field("hintString")); + CACHE_CLASS_AND_CHECK(SignalAttribute, GODOT_API_CLASS(SignalAttribute)); + CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute)); + CACHE_CLASS_AND_CHECK(RemoteAttribute, GODOT_API_CLASS(RemoteAttribute)); + CACHE_CLASS_AND_CHECK(SyncAttribute, GODOT_API_CLASS(SyncAttribute)); + CACHE_CLASS_AND_CHECK(MasterAttribute, GODOT_API_CLASS(MasterAttribute)); + CACHE_CLASS_AND_CHECK(PuppetAttribute, GODOT_API_CLASS(PuppetAttribute)); + CACHE_CLASS_AND_CHECK(SlaveAttribute, GODOT_API_CLASS(SlaveAttribute)); + CACHE_CLASS_AND_CHECK(RemoteSyncAttribute, GODOT_API_CLASS(RemoteSyncAttribute)); + CACHE_CLASS_AND_CHECK(MasterSyncAttribute, GODOT_API_CLASS(MasterSyncAttribute)); + CACHE_CLASS_AND_CHECK(PuppetSyncAttribute, GODOT_API_CLASS(PuppetSyncAttribute)); + CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute)); + CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); + + CACHE_FIELD_AND_CHECK(GodotObject, ptr, CACHED_CLASS(GodotObject)->get_field(BINDINGS_PTR_FIELD)); + CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD)); + CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD)); + + CACHE_METHOD_THUNK_AND_CHECK(GodotObject, Dispose, CACHED_CLASS(GodotObject)->get_method("Dispose", 0)); + CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Array)->get_method("GetPtr", 0)); + CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)->get_method("GetPtr", 0)); + CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, SignalCallback, GODOT_API_CLASS(SignalAwaiter)->get_method("SignalCallback", 1)); + CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, GODOT_API_CLASS(SignalAwaiter)->get_method("FailureCallback", 0)); + CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, GODOT_API_CLASS(GodotTaskScheduler)->get_method("Activate", 0)); + + // Start of MarshalUtils methods + + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericArray, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericArray", 1)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericDictionary", 1)); + + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArrayGetElementType, GODOT_API_CLASS(MarshalUtils)->get_method("ArrayGetElementType", 2)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryGetKeyValueTypes, GODOT_API_CLASS(MarshalUtils)->get_method("DictionaryGetKeyValueTypes", 3)); + + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIEnumerableIsAssignableFromType, GODOT_API_CLASS(MarshalUtils)->get_method("GenericIEnumerableIsAssignableFromType", 1)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryIsAssignableFromType, GODOT_API_CLASS(MarshalUtils)->get_method("GenericIDictionaryIsAssignableFromType", 1)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info, GODOT_API_CLASS(MarshalUtils)->get_method("GenericIEnumerableIsAssignableFromType", 2)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info, GODOT_API_CLASS(MarshalUtils)->get_method("GenericIDictionaryIsAssignableFromType", 3)); + + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericArrayType, GODOT_API_CLASS(MarshalUtils)->get_method("MakeGenericArrayType", 1)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericDictionaryType, GODOT_API_CLASS(MarshalUtils)->get_method("MakeGenericDictionaryType", 2)); + + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, EnumerableToArray, GODOT_API_CLASS(MarshalUtils)->get_method("EnumerableToArray", 2)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IDictionaryToDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("IDictionaryToDictionary", 2)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryToDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("GenericIDictionaryToDictionary", 2)); + + // End of MarshalUtils methods + +#ifdef DEBUG_ENABLED + CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, GODOT_API_CLASS(DebuggingUtils)->get_method("GetStackFrameInfo", 4)); +#endif + + // TODO Move to CSharpLanguage::init() and do handle disposal + MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr()); + GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler)); + cached_data.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler); + + cached_data.godot_api_cache_updated = true; +} + +} // namespace GDMonoCache diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h new file mode 100644 index 0000000000..a6d6da4f2b --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -0,0 +1,204 @@ +/*************************************************************************/ +/* gd_mono_cache.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 GD_MONO_CACHE_H +#define GD_MONO_CACHE_H + +#include "gd_mono_header.h" +#include "gd_mono_method_thunk.h" + +namespace GDMonoCache { + +struct CachedData { + + // ----------------------------------------------- + // corlib classes + + // Let's use the no-namespace format for these too + GDMonoClass *class_MonoObject; // object + GDMonoClass *class_bool; // bool + GDMonoClass *class_int8_t; // sbyte + GDMonoClass *class_int16_t; // short + GDMonoClass *class_int32_t; // int + GDMonoClass *class_int64_t; // long + GDMonoClass *class_uint8_t; // byte + GDMonoClass *class_uint16_t; // ushort + GDMonoClass *class_uint32_t; // uint + GDMonoClass *class_uint64_t; // ulong + GDMonoClass *class_float; // float + GDMonoClass *class_double; // double + GDMonoClass *class_String; // string + GDMonoClass *class_IntPtr; // System.IntPtr + + GDMonoClass *class_System_Collections_IEnumerable; + GDMonoClass *class_System_Collections_IDictionary; + +#ifdef DEBUG_ENABLED + GDMonoClass *class_System_Diagnostics_StackTrace; + GDMonoMethodThunkR<MonoArray *, MonoObject *> methodthunk_System_Diagnostics_StackTrace_GetFrames; + GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_bool; + GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_Exception_bool; +#endif + + GDMonoClass *class_KeyNotFoundException; + + MonoClass *rawclass_Dictionary; + // ----------------------------------------------- + + GDMonoClass *class_Vector2; + GDMonoClass *class_Rect2; + GDMonoClass *class_Transform2D; + GDMonoClass *class_Vector3; + GDMonoClass *class_Basis; + GDMonoClass *class_Quat; + GDMonoClass *class_Transform; + GDMonoClass *class_AABB; + GDMonoClass *class_Color; + GDMonoClass *class_Plane; + GDMonoClass *class_NodePath; + GDMonoClass *class_RID; + GDMonoClass *class_GodotObject; + GDMonoClass *class_GodotResource; + GDMonoClass *class_Node; + GDMonoClass *class_Control; + GDMonoClass *class_Spatial; + GDMonoClass *class_WeakRef; + GDMonoClass *class_Array; + GDMonoClass *class_Dictionary; + GDMonoClass *class_MarshalUtils; + GDMonoClass *class_ISerializationListener; + +#ifdef DEBUG_ENABLED + GDMonoClass *class_DebuggingUtils; + GDMonoMethodThunk<MonoObject *, MonoString **, int *, MonoString **> methodthunk_DebuggingUtils_GetStackFrameInfo; +#endif + + GDMonoClass *class_ExportAttribute; + GDMonoField *field_ExportAttribute_hint; + GDMonoField *field_ExportAttribute_hintString; + GDMonoClass *class_SignalAttribute; + GDMonoClass *class_ToolAttribute; + GDMonoClass *class_RemoteAttribute; + GDMonoClass *class_SyncAttribute; + GDMonoClass *class_RemoteSyncAttribute; + GDMonoClass *class_MasterSyncAttribute; + GDMonoClass *class_PuppetSyncAttribute; + GDMonoClass *class_MasterAttribute; + GDMonoClass *class_PuppetAttribute; + GDMonoClass *class_SlaveAttribute; + GDMonoClass *class_GodotMethodAttribute; + GDMonoField *field_GodotMethodAttribute_methodName; + + GDMonoField *field_GodotObject_ptr; + GDMonoField *field_NodePath_ptr; + GDMonoField *field_Image_ptr; + GDMonoField *field_RID_ptr; + + GDMonoMethodThunk<MonoObject *> methodthunk_GodotObject_Dispose; + GDMonoMethodThunkR<Array *, MonoObject *> methodthunk_Array_GetPtr; + GDMonoMethodThunkR<Dictionary *, MonoObject *> methodthunk_Dictionary_GetPtr; + GDMonoMethodThunk<MonoObject *, MonoArray *> methodthunk_SignalAwaiter_SignalCallback; + GDMonoMethodThunk<MonoObject *> methodthunk_SignalAwaiter_FailureCallback; + GDMonoMethodThunk<MonoObject *> methodthunk_GodotTaskScheduler_Activate; + + // Start of MarshalUtils methods + + GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericArray; + GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericDictionary; + + GDMonoMethodThunk<MonoReflectionType *, MonoReflectionType **> methodthunk_MarshalUtils_ArrayGetElementType; + GDMonoMethodThunk<MonoReflectionType *, MonoReflectionType **, MonoReflectionType **> methodthunk_MarshalUtils_DictionaryGetKeyValueTypes; + + GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType; + GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType; + GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *, MonoReflectionType **> methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType_with_info; + GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *, MonoReflectionType **, MonoReflectionType **> methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType_with_info; + + GDMonoMethodThunkR<MonoReflectionType *, MonoReflectionType *> methodthunk_MarshalUtils_MakeGenericArrayType; + GDMonoMethodThunkR<MonoReflectionType *, MonoReflectionType *, MonoReflectionType *> methodthunk_MarshalUtils_MakeGenericDictionaryType; + + GDMonoMethodThunk<MonoObject *, Array *> methodthunk_MarshalUtils_EnumerableToArray; + GDMonoMethodThunk<MonoObject *, Dictionary *> methodthunk_MarshalUtils_IDictionaryToDictionary; + GDMonoMethodThunk<MonoObject *, Dictionary *> methodthunk_MarshalUtils_GenericIDictionaryToDictionary; + + // End of MarshalUtils methods + + Ref<MonoGCHandle> task_scheduler_handle; + + bool corlib_cache_updated; + bool godot_api_cache_updated; + + void clear_corlib_cache(); + void clear_godot_api_cache(); + + CachedData() { + clear_corlib_cache(); + clear_godot_api_cache(); + } +}; + +extern CachedData cached_data; + +void update_corlib_cache(); +void update_godot_api_cache(); + +inline void clear_corlib_cache() { + cached_data.clear_corlib_cache(); +} + +inline void clear_godot_api_cache() { + cached_data.clear_godot_api_cache(); +} + +_FORCE_INLINE_ bool tools_godot_api_check() { +#ifdef TOOLS_ENABLED + return cached_data.godot_api_cache_updated; +#else + return true; // Assume it's updated if this was called, otherwise it's a bug +#endif +} + +} // namespace GDMonoCache + +#define CACHED_CLASS(m_class) (GDMonoCache::cached_data.class_##m_class) +#define CACHED_CLASS_RAW(m_class) (GDMonoCache::cached_data.class_##m_class->get_mono_ptr()) +#define CACHED_RAW_MONO_CLASS(m_class) (GDMonoCache::cached_data.rawclass_##m_class) +#define CACHED_FIELD(m_class, m_field) (GDMonoCache::cached_data.field_##m_class##_##m_field) +#define CACHED_METHOD(m_class, m_method) (GDMonoCache::cached_data.method_##m_class##_##m_method) +#define CACHED_METHOD_THUNK(m_class, m_method) (GDMonoCache::cached_data.methodthunk_##m_class##_##m_method) +#define CACHED_PROPERTY(m_class, m_property) (GDMonoCache::cached_data.property_##m_class##_##m_property) + +#ifdef REAL_T_IS_DOUBLE +#define REAL_T_MONOCLASS CACHED_CLASS_RAW(double) +#else +#define REAL_T_MONOCLASS CACHED_CLASS_RAW(float) +#endif + +#endif // GD_MONO_CACHE_H diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp index 89a88fcfb2..fb9b6be3d4 100644 --- a/modules/mono/mono_gd/gd_mono_class.cpp +++ b/modules/mono/mono_gd/gd_mono_class.cpp @@ -33,6 +33,7 @@ #include <mono/metadata/attrdefs.h> #include "gd_mono_assembly.h" +#include "gd_mono_cache.h" #include "gd_mono_marshal.h" String GDMonoClass::get_full_name(MonoClass *p_mono_class) { @@ -332,12 +333,6 @@ GDMonoMethod *GDMonoClass::get_method_with_desc(const String &p_description, boo return get_method(method); } -void *GDMonoClass::get_method_thunk(const StringName &p_name, int p_params_count) { - - GDMonoMethod *method = get_method(p_name, p_params_count); - return method ? method->get_thunk() : NULL; -} - GDMonoField *GDMonoClass::get_field(const StringName &p_name) { Map<StringName, GDMonoField *>::Element *result = fields.find(p_name); diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h index 40e1574927..562c337822 100644 --- a/modules/mono/mono_gd/gd_mono_class.h +++ b/modules/mono/mono_gd/gd_mono_class.h @@ -144,8 +144,6 @@ public: GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name, int p_params_count); GDMonoMethod *get_method_with_desc(const String &p_description, bool p_include_namespace); - void *get_method_thunk(const StringName &p_name, int p_params_count = 0); - GDMonoField *get_field(const StringName &p_name); const Vector<GDMonoField *> &get_all_fields(); diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp index 7b8e6f89e9..d84359b1ab 100644 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -32,8 +32,10 @@ #include <mono/metadata/attrdefs.h> +#include "gd_mono_cache.h" #include "gd_mono_class.h" #include "gd_mono_marshal.h" +#include "gd_mono_utils.h" void GDMonoField::set_value_raw(MonoObject *p_object, void *p_ptr) { mono_field_set_value(p_object, mono_field, &p_ptr); @@ -337,7 +339,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - if (GDMonoUtils::tools_godot_api_check()) { + if (GDMonoCache::tools_godot_api_check()) { MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); mono_field_set_value(p_object, mono_field, managed); break; @@ -491,7 +493,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - if (GDMonoUtils::tools_godot_api_check()) { + if (GDMonoCache::tools_godot_api_check()) { MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); mono_field_set_value(p_object, mono_field, managed); break; diff --git a/modules/mono/mono_gd/gd_mono_header.h b/modules/mono/mono_gd/gd_mono_header.h index d7962eac8b..173e1613bf 100644 --- a/modules/mono/mono_gd/gd_mono_header.h +++ b/modules/mono/mono_gd/gd_mono_header.h @@ -33,6 +33,12 @@ #include "core/int_types.h" +#ifdef WIN32 +#define GD_MONO_STDCALL __stdcall +#else +#define GD_MONO_STDCALL +#endif + class GDMonoAssembly; class GDMonoClass; class GDMonoField; diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp index 7b3421fdb3..261b800619 100644 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -30,7 +30,6 @@ #include "gd_mono_log.h" -#include <mono/utils/mono-logger.h> #include <stdlib.h> // abort #include "core/os/dir_access.h" @@ -39,7 +38,19 @@ #include "../godotsharp_dirs.h" #include "../utils/string_utils.h" -static int log_level_get_id(const char *p_log_level) { +static CharString get_default_log_level() { +#ifdef DEBUG_ENABLED + return String("info").utf8(); +#else + return String("warning").utf8(); +#endif +} + +GDMonoLog *GDMonoLog::singleton = NULL; + +#if !defined(JAVASCRIPT_ENABLED) + +static int get_log_level_id(const char *p_log_level) { const char *valid_log_levels[] = { "error", "critical", "warning", "message", "info", "debug", NULL }; @@ -53,11 +64,11 @@ static int log_level_get_id(const char *p_log_level) { return -1; } -static void mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) { +void GDMonoLog::mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *) { - FileAccess *f = GDMonoLog::get_singleton()->get_log_file(); + FileAccess *f = GDMonoLog::get_singleton()->log_file; - if (GDMonoLog::get_singleton()->get_log_level_id() >= log_level_get_id(log_level)) { + if (GDMonoLog::get_singleton()->log_level_id >= get_log_level_id(log_level)) { String text(message); text += " (in domain "; text += log_domain; @@ -72,7 +83,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() + "'."); + ERR_PRINTS("Mono: FATAL ERROR, ABORTING! Logfile: '" + GDMonoLog::get_singleton()->log_file_path + "'."); // Make sure to flush before aborting f->flush(); f->close(); @@ -82,8 +93,6 @@ static void mono_log_callback(const char *log_domain, const char *log_level, con } } -GDMonoLog *GDMonoLog::singleton = NULL; - bool GDMonoLog::_try_create_logs_dir(const String &p_logs_dir) { if (!DirAccess::exists(p_logs_dir)) { @@ -129,17 +138,13 @@ 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) { + if (log_level.length() != 0 && get_log_level_id(log_level.get_data()) == -1) { ERR_PRINTS(String() + "Mono: Ignoring invalid log level (GODOT_MONO_LOG_LEVEL): '" + log_level.get_data() + "'."); log_level = CharString(); } if (log_level.length() == 0) { -#ifdef DEBUG_ENABLED - log_level = String("info").utf8(); -#else - log_level = String("warning").utf8(); -#endif + log_level = get_default_log_level(); } String logs_dir = GodotSharpDirs::get_mono_logs_dir(); @@ -149,11 +154,14 @@ void GDMonoLog::initialize() { OS::Date date_now = OS::get_singleton()->get_date(); OS::Time time_now = OS::get_singleton()->get_time(); - int pid = OS::get_singleton()->get_process_id(); - String log_file_name = str_format("%d_%02d_%02d %02d.%02d.%02d (%d).txt", + String log_file_name = str_format("%d_%02d_%02d %02d.%02d.%02d", date_now.year, date_now.month, date_now.day, - time_now.hour, time_now.min, time_now.sec, pid); + time_now.hour, time_now.min, time_now.sec); + + log_file_name += str_format(" (%d)", OS::get_singleton()->get_process_id()); + + log_file_name += ".txt"; log_file_path = logs_dir.plus_file(log_file_name); @@ -164,7 +172,7 @@ void GDMonoLog::initialize() { } mono_trace_set_level_string(log_level.get_data()); - log_level_id = log_level_get_id(log_level.get_data()); + log_level_id = get_log_level_id(log_level.get_data()); if (log_file) { OS::get_singleton()->print("Mono: Logfile is: %s\n", log_file_path.utf8().get_data()); @@ -190,3 +198,22 @@ GDMonoLog::~GDMonoLog() { memdelete(log_file); } } + +#else + +void GDMonoLog::initialize() { + CharString log_level = get_default_log_level(); + mono_trace_set_level_string(log_level.get_data()); +} + +GDMonoLog::GDMonoLog() { + + singleton = this; +} + +GDMonoLog::~GDMonoLog() { + + singleton = NULL; +} + +#endif // !defined(JAVASCRIPT_ENABLED) diff --git a/modules/mono/mono_gd/gd_mono_log.h b/modules/mono/mono_gd/gd_mono_log.h index 91d0557aa3..4cd5a662fb 100644 --- a/modules/mono/mono_gd/gd_mono_log.h +++ b/modules/mono/mono_gd/gd_mono_log.h @@ -31,10 +31,17 @@ #ifndef GD_MONO_LOG_H #define GD_MONO_LOG_H +#include <mono/utils/mono-logger.h> + +#include "core/typedefs.h" + +#if !defined(JAVASCRIPT_ENABLED) #include "core/os/file_access.h" +#endif class GDMonoLog { +#if !defined(JAVASCRIPT_ENABLED) int log_level_id; FileAccess *log_file; @@ -43,6 +50,9 @@ class GDMonoLog { bool _try_create_logs_dir(const String &p_logs_dir); void _delete_old_log_files(const String &p_logs_dir); + static void mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data); +#endif + static GDMonoLog *singleton; public: @@ -50,10 +60,6 @@ public: void initialize(); - _FORCE_INLINE_ FileAccess *get_log_file() { return log_file; } - _FORCE_INLINE_ String get_log_file_path() { return log_file_path; } - _FORCE_INLINE_ int get_log_level_id() { return log_level_id; } - GDMonoLog(); ~GDMonoLog(); }; diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index 7aac691102..f74fe5715c 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -31,6 +31,7 @@ #include "gd_mono_marshal.h" #include "gd_mono.h" +#include "gd_mono_cache.h" #include "gd_mono_class.h" namespace GDMonoMarshal { @@ -556,7 +557,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - if (GDMonoUtils::tools_godot_api_check()) { + if (GDMonoCache::tools_godot_api_check()) { return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); } else { return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array()); @@ -683,7 +684,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - if (GDMonoUtils::tools_godot_api_check()) { + if (GDMonoCache::tools_godot_api_check()) { return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); } else { return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array()); @@ -834,14 +835,14 @@ Variant mono_object_to_variant(MonoObject *p_obj) { if (CACHED_CLASS(Array) == type_class) { MonoException *exc = NULL; - Array *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Array, GetPtr), p_obj, &exc); + Array *ptr = CACHED_METHOD_THUNK(Array, GetPtr).invoke(p_obj, &exc); UNHANDLED_EXCEPTION(exc); return ptr ? Variant(*ptr) : Variant(); } if (CACHED_CLASS(Dictionary) == type_class) { MonoException *exc = NULL; - Dictionary *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Dictionary, GetPtr), p_obj, &exc); + Dictionary *ptr = CACHED_METHOD_THUNK(Dictionary, GetPtr).invoke(p_obj, &exc); UNHANDLED_EXCEPTION(exc); return ptr ? Variant(*ptr) : Variant(); } @@ -934,6 +935,8 @@ Array mono_array_to_Array(MonoArray *p_array) { return ret; } +// TODO: Use memcpy where possible + MonoArray *PoolIntArray_to_mono_array(const PoolIntArray &p_array) { PoolIntArray::Read r = p_array.read(); diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h index 3fa958ac32..53eae45320 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.h +++ b/modules/mono/mono_gd/gd_mono_marshal.h @@ -43,6 +43,11 @@ T unbox(MonoObject *p_obj) { return *(T *)mono_object_unbox(p_obj); } +template <typename T> +T *unbox_addr(MonoObject *p_obj) { + return (T *)mono_object_unbox(p_obj); +} + #define BOX_DOUBLE(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(double), &x) #define BOX_FLOAT(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(float), &x) #define BOX_INT64(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int64_t), &x) diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp index 968b316a3e..080b3a676a 100644 --- a/modules/mono/mono_gd/gd_mono_method.cpp +++ b/modules/mono/mono_gd/gd_mono_method.cpp @@ -30,8 +30,10 @@ #include "gd_mono_method.h" +#include "gd_mono_cache.h" #include "gd_mono_class.h" #include "gd_mono_marshal.h" +#include "gd_mono_utils.h" #include <mono/metadata/attrdefs.h> @@ -99,10 +101,6 @@ IMonoClassMember::Visibility GDMonoMethod::get_visibility() { } } -void *GDMonoMethod::get_thunk() { - return mono_method_get_unmanaged_thunk(mono_method); -} - MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc) { if (get_return_type().type_encoding != MONO_TYPE_VOID || get_parameters_count() > 0) { MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), get_parameters_count()); diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h index 2fc8628f27..abbae92e29 100644 --- a/modules/mono/mono_gd/gd_mono_method.h +++ b/modules/mono/mono_gd/gd_mono_method.h @@ -71,11 +71,11 @@ public: virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) GD_FINAL; void fetch_attributes(); + _FORCE_INLINE_ MonoMethod *get_mono_ptr() { return mono_method; } + _FORCE_INLINE_ int get_parameters_count() { return params_count; } _FORCE_INLINE_ ManagedType get_return_type() { return return_type; } - void *get_thunk(); - MonoObject *invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc = NULL); MonoObject *invoke(MonoObject *p_object, MonoException **r_exc = NULL); MonoObject *invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc = NULL); diff --git a/modules/mono/mono_gd/gd_mono_method_thunk.h b/modules/mono/mono_gd/gd_mono_method_thunk.h new file mode 100644 index 0000000000..f8cc736ec3 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_method_thunk.h @@ -0,0 +1,332 @@ +/*************************************************************************/ +/* gd_mono_method_thunk.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 GD_MONO_METHOD_THUNK_H +#define GD_MONO_METHOD_THUNK_H + +#include <type_traits> + +#include "gd_mono_class.h" +#include "gd_mono_header.h" +#include "gd_mono_marshal.h" +#include "gd_mono_method.h" +#include "gd_mono_utils.h" + +#if !defined(JAVASCRIPT_ENABLED) +#define HAVE_METHOD_THUNKS +#endif + +#ifdef HAVE_METHOD_THUNKS + +template <class... ParamTypes> +struct GDMonoMethodThunk { + + typedef void(GD_MONO_STDCALL *M)(ParamTypes... p_args, MonoException **); + + M mono_method_thunk; + +public: + _FORCE_INLINE_ void invoke(ParamTypes... p_args, MonoException **r_exc) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + mono_method_thunk(p_args..., r_exc); + GD_MONO_END_RUNTIME_INVOKE; + } + + _FORCE_INLINE_ bool is_null() { + return mono_method_thunk == NULL; + } + + _FORCE_INLINE_ void nullify() { + mono_method_thunk = NULL; + } + + _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_mono_method == NULL); + CRASH_COND(p_mono_method->get_return_type().type_encoding != MONO_TYPE_VOID); + + if (p_mono_method->is_static()) { + CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); + } else { + CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); + } +#endif + mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); + } + + GDMonoMethodThunk() : + mono_method_thunk(NULL) { + } + + explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) { + set_from_method(p_mono_method); + } +}; + +template <class R, class... ParamTypes> +struct GDMonoMethodThunkR { + + typedef R(GD_MONO_STDCALL *M)(ParamTypes... p_args, MonoException **); + + M mono_method_thunk; + +public: + _FORCE_INLINE_ R invoke(ParamTypes... p_args, MonoException **r_exc) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + R r = mono_method_thunk(p_args..., r_exc); + GD_MONO_END_RUNTIME_INVOKE; + return r; + } + + _FORCE_INLINE_ bool is_null() { + return mono_method_thunk == NULL; + } + + _FORCE_INLINE_ void nullify() { + mono_method_thunk = NULL; + } + + _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_mono_method == NULL); + CRASH_COND(p_mono_method->get_return_type().type_encoding == MONO_TYPE_VOID); + + if (p_mono_method->is_static()) { + CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); + } else { + CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); + } +#endif + mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); + } + + GDMonoMethodThunkR() : + mono_method_thunk(NULL) { + } + + explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_mono_method == NULL); +#endif + mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); + } +}; + +#else + +template <unsigned int ThunkParamCount, class P1, class... ParamTypes> +struct VariadicInvokeMonoMethodImpl { + static void invoke(GDMonoMethod *p_mono_method, P1 p_arg1, ParamTypes... p_args, MonoException **r_exc) { + if (p_mono_method->is_static()) { + void *args[ThunkParamCount] = { p_arg1, p_args... }; + p_mono_method->invoke_raw(NULL, args, r_exc); + } else { + void *args[ThunkParamCount] = { p_args... }; + p_mono_method->invoke_raw((MonoObject *)p_arg1, args, r_exc); + } + } +}; + +template <unsigned int ThunkParamCount, class... ParamTypes> +struct VariadicInvokeMonoMethod { + static void invoke(GDMonoMethod *p_mono_method, ParamTypes... p_args, MonoException **r_exc) { + VariadicInvokeMonoMethodImpl<ThunkParamCount, ParamTypes...>::invoke(p_mono_method, p_args..., r_exc); + } +}; + +template <> +struct VariadicInvokeMonoMethod<0> { + static void invoke(GDMonoMethod *p_mono_method, MonoException **r_exc) { +#ifdef DEBUG_ENABLED + CRASH_COND(!p_mono_method->is_static()); +#endif + p_mono_method->invoke_raw(NULL, NULL, r_exc); + } +}; + +template <class P1> +struct VariadicInvokeMonoMethod<1, P1> { + static void invoke(GDMonoMethod *p_mono_method, P1 p_arg1, MonoException **r_exc) { + if (p_mono_method->is_static()) { + void *args[1] = { p_arg1 }; + p_mono_method->invoke_raw(NULL, args, r_exc); + } else { + p_mono_method->invoke_raw((MonoObject *)p_arg1, NULL, r_exc); + } + } +}; + +template <class R> +R unbox_if_needed(MonoObject *p_val, const ManagedType &, typename std::enable_if<!std::is_pointer<R>::value>::type * = 0) { + return GDMonoMarshal::unbox<R>(p_val); +} + +template <class R> +R unbox_if_needed(MonoObject *p_val, const ManagedType &p_type, typename std::enable_if<std::is_pointer<R>::value>::type * = 0) { + if (mono_class_is_valuetype(p_type.type_class->get_mono_ptr())) { + return GDMonoMarshal::unbox<R>(p_val); + } else { + // If it's not a value type, we assume 'R' is a pointer to 'MonoObject' or a compatible type, like 'MonoException'. + return (R)p_val; + } +} + +template <unsigned int ThunkParamCount, class R, class P1, class... ParamTypes> +struct VariadicInvokeMonoMethodRImpl { + static R invoke(GDMonoMethod *p_mono_method, P1 p_arg1, ParamTypes... p_args, MonoException **r_exc) { + if (p_mono_method->is_static()) { + void *args[ThunkParamCount] = { p_arg1, p_args... }; + MonoObject *r = p_mono_method->invoke_raw(NULL, args, r_exc); + return unbox_if_needed<R>(r, p_mono_method->get_return_type()); + } else { + void *args[ThunkParamCount] = { p_args... }; + MonoObject *r = p_mono_method->invoke_raw((MonoObject *)p_arg1, args, r_exc); + return unbox_if_needed<R>(r, p_mono_method->get_return_type()); + } + } +}; + +template <unsigned int ThunkParamCount, class R, class... ParamTypes> +struct VariadicInvokeMonoMethodR { + static R invoke(GDMonoMethod *p_mono_method, ParamTypes... p_args, MonoException **r_exc) { + return VariadicInvokeMonoMethodRImpl<ThunkParamCount, R, ParamTypes...>::invoke(p_mono_method, p_args..., r_exc); + } +}; + +template <class R> +struct VariadicInvokeMonoMethodR<0, R> { + static R invoke(GDMonoMethod *p_mono_method, MonoException **r_exc) { +#ifdef DEBUG_ENABLED + CRASH_COND(!p_mono_method->is_static()); +#endif + MonoObject *r = p_mono_method->invoke_raw(NULL, NULL, r_exc); + return unbox_if_needed<R>(r, p_mono_method->get_return_type()); + } +}; + +template <class R, class P1> +struct VariadicInvokeMonoMethodR<1, R, P1> { + static R invoke(GDMonoMethod *p_mono_method, P1 p_arg1, MonoException **r_exc) { + if (p_mono_method->is_static()) { + void *args[1] = { p_arg1 }; + MonoObject *r = p_mono_method->invoke_raw(NULL, args, r_exc); + return unbox_if_needed<R>(r, p_mono_method->get_return_type()); + } else { + MonoObject *r = p_mono_method->invoke_raw((MonoObject *)p_arg1, NULL, r_exc); + return unbox_if_needed<R>(r, p_mono_method->get_return_type()); + } + } +}; + +template <class... ParamTypes> +struct GDMonoMethodThunk { + + GDMonoMethod *mono_method; + +public: + _FORCE_INLINE_ void invoke(ParamTypes... p_args, MonoException **r_exc) { + VariadicInvokeMonoMethod<sizeof...(ParamTypes), ParamTypes...>::invoke(mono_method, p_args..., r_exc); + } + + _FORCE_INLINE_ bool is_null() { + return mono_method == NULL; + } + + _FORCE_INLINE_ void nullify() { + mono_method = NULL; + } + + _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_mono_method == NULL); + CRASH_COND(p_mono_method->get_return_type().type_encoding != MONO_TYPE_VOID); + + if (p_mono_method->is_static()) { + CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); + } else { + CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); + } +#endif + mono_method = p_mono_method; + } + + GDMonoMethodThunk() : + mono_method(NULL) { + } + + explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) { + set_from_method(p_mono_method); + } +}; + +template <class R, class... ParamTypes> +struct GDMonoMethodThunkR { + + GDMonoMethod *mono_method; + +public: + _FORCE_INLINE_ R invoke(ParamTypes... p_args, MonoException **r_exc) { + return VariadicInvokeMonoMethodR<sizeof...(ParamTypes), R, ParamTypes...>::invoke(mono_method, p_args..., r_exc); + } + + _FORCE_INLINE_ bool is_null() { + return mono_method == NULL; + } + + _FORCE_INLINE_ void nullify() { + mono_method = NULL; + } + + _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_mono_method == NULL); + CRASH_COND(p_mono_method->get_return_type().type_encoding == MONO_TYPE_VOID); + + if (p_mono_method->is_static()) { + CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); + } else { + CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); + } +#endif + mono_method = p_mono_method; + } + + GDMonoMethodThunkR() : + mono_method(NULL) { + } + + explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) { + set_from_method(p_mono_method); + } +}; + +#endif + +#endif // GD_MONO_METHOD_THUNK_H diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp index f1da00638f..277fe10087 100644 --- a/modules/mono/mono_gd/gd_mono_property.cpp +++ b/modules/mono/mono_gd/gd_mono_property.cpp @@ -30,8 +30,10 @@ #include "gd_mono_property.h" +#include "gd_mono_cache.h" #include "gd_mono_class.h" #include "gd_mono_marshal.h" +#include "gd_mono_utils.h" #include <mono/metadata/attrdefs.h> diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index 6504fbe423..8d7aaa97f2 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -45,273 +45,13 @@ #include "../utils/macros.h" #include "../utils/mutex_utils.h" #include "gd_mono.h" +#include "gd_mono_cache.h" #include "gd_mono_class.h" #include "gd_mono_marshal.h" +#include "gd_mono_method_thunk.h" namespace GDMonoUtils { -MonoCache mono_cache; - -#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) -#define CACHE_NS_CLASS_AND_CHECK(m_ns, m_class, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.class_##m_ns##_##m_class, m_val) -#define CACHE_RAW_MONO_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.rawclass_##m_class, m_val) -#define CACHE_FIELD_AND_CHECK(m_class, m_field, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.field_##m_class##_##m_field, m_val) -#define CACHE_METHOD_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.method_##m_class##_##m_method, m_val) -#define CACHE_METHOD_THUNK_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.methodthunk_##m_class##_##m_method, m_val) -#define CACHE_PROPERTY_AND_CHECK(m_class, m_property, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.property_##m_class##_##m_property, m_val) - -void MonoCache::clear_corlib_cache() { - - corlib_cache_updated = false; - - class_MonoObject = NULL; - class_bool = NULL; - class_int8_t = NULL; - class_int16_t = NULL; - class_int32_t = NULL; - class_int64_t = NULL; - class_uint8_t = NULL; - class_uint16_t = NULL; - class_uint32_t = NULL; - class_uint64_t = NULL; - class_float = NULL; - class_double = NULL; - class_String = NULL; - class_IntPtr = NULL; - - class_System_Collections_IEnumerable = NULL; - class_System_Collections_IDictionary = NULL; - -#ifdef DEBUG_ENABLED - class_System_Diagnostics_StackTrace = NULL; - methodthunk_System_Diagnostics_StackTrace_GetFrames = NULL; - method_System_Diagnostics_StackTrace_ctor_bool = NULL; - method_System_Diagnostics_StackTrace_ctor_Exception_bool = NULL; -#endif - - class_KeyNotFoundException = NULL; -} - -void MonoCache::clear_godot_api_cache() { - - godot_api_cache_updated = false; - - rawclass_Dictionary = NULL; - - class_Vector2 = NULL; - class_Rect2 = NULL; - class_Transform2D = NULL; - class_Vector3 = NULL; - class_Basis = NULL; - class_Quat = NULL; - class_Transform = NULL; - class_AABB = NULL; - class_Color = NULL; - class_Plane = NULL; - class_NodePath = NULL; - class_RID = NULL; - class_GodotObject = NULL; - class_GodotResource = NULL; - class_Node = NULL; - class_Control = NULL; - class_Spatial = NULL; - class_WeakRef = NULL; - class_Array = NULL; - class_Dictionary = NULL; - class_MarshalUtils = NULL; - class_ISerializationListener = NULL; - -#ifdef DEBUG_ENABLED - class_DebuggingUtils = NULL; - methodthunk_DebuggingUtils_GetStackFrameInfo = NULL; -#endif - - class_ExportAttribute = NULL; - field_ExportAttribute_hint = NULL; - field_ExportAttribute_hintString = NULL; - class_SignalAttribute = NULL; - class_ToolAttribute = NULL; - class_RemoteAttribute = NULL; - class_SyncAttribute = NULL; - class_MasterAttribute = NULL; - class_PuppetAttribute = NULL; - class_SlaveAttribute = NULL; - class_RemoteSyncAttribute = NULL; - class_MasterSyncAttribute = NULL; - class_PuppetSyncAttribute = NULL; - class_GodotMethodAttribute = NULL; - field_GodotMethodAttribute_methodName = NULL; - - field_GodotObject_ptr = NULL; - field_NodePath_ptr = NULL; - field_Image_ptr = NULL; - field_RID_ptr = NULL; - - methodthunk_GodotObject_Dispose = NULL; - methodthunk_Array_GetPtr = NULL; - methodthunk_Dictionary_GetPtr = NULL; - methodthunk_SignalAwaiter_SignalCallback = NULL; - methodthunk_SignalAwaiter_FailureCallback = NULL; - methodthunk_GodotTaskScheduler_Activate = NULL; - - // Start of MarshalUtils methods - - methodthunk_MarshalUtils_TypeIsGenericArray = NULL; - methodthunk_MarshalUtils_TypeIsGenericDictionary = NULL; - - methodthunk_MarshalUtils_ArrayGetElementType = NULL; - methodthunk_MarshalUtils_DictionaryGetKeyValueTypes = NULL; - - methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType = NULL; - methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType = NULL; - methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType_with_info = NULL; - methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType_with_info = NULL; - - methodthunk_MarshalUtils_MakeGenericArrayType = NULL; - methodthunk_MarshalUtils_MakeGenericDictionaryType = NULL; - - methodthunk_MarshalUtils_EnumerableToArray = NULL; - methodthunk_MarshalUtils_IDictionaryToDictionary = NULL; - methodthunk_MarshalUtils_GenericIDictionaryToDictionary = NULL; - - // End of MarshalUtils methods - - task_scheduler_handle = Ref<MonoGCHandle>(); -} - -#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class)) -#define GODOT_API_NS_CLAS(m_ns, m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(m_ns, #m_class)) - -void update_corlib_cache() { - - CACHE_CLASS_AND_CHECK(MonoObject, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_object_class())); - CACHE_CLASS_AND_CHECK(bool, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_boolean_class())); - CACHE_CLASS_AND_CHECK(int8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_sbyte_class())); - CACHE_CLASS_AND_CHECK(int16_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int16_class())); - CACHE_CLASS_AND_CHECK(int32_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int32_class())); - CACHE_CLASS_AND_CHECK(int64_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int64_class())); - CACHE_CLASS_AND_CHECK(uint8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_byte_class())); - CACHE_CLASS_AND_CHECK(uint16_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint16_class())); - CACHE_CLASS_AND_CHECK(uint32_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint32_class())); - CACHE_CLASS_AND_CHECK(uint64_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint64_class())); - CACHE_CLASS_AND_CHECK(float, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_single_class())); - CACHE_CLASS_AND_CHECK(double, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_double_class())); - CACHE_CLASS_AND_CHECK(String, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_string_class())); - CACHE_CLASS_AND_CHECK(IntPtr, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_intptr_class())); - - CACHE_CLASS_AND_CHECK(System_Collections_IEnumerable, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IEnumerable")); - CACHE_CLASS_AND_CHECK(System_Collections_IDictionary, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IDictionary")); - -#ifdef DEBUG_ENABLED - CACHE_CLASS_AND_CHECK(System_Diagnostics_StackTrace, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Diagnostics", "StackTrace")); - CACHE_METHOD_THUNK_AND_CHECK(System_Diagnostics_StackTrace, GetFrames, (StackTrace_GetFrames)CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_thunk("GetFrames")); - CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(bool)", true)); - CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_Exception_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(System.Exception,bool)", true)); -#endif - - CACHE_CLASS_AND_CHECK(KeyNotFoundException, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections.Generic", "KeyNotFoundException")); - - mono_cache.corlib_cache_updated = true; -} - -void update_godot_api_cache() { - - CACHE_CLASS_AND_CHECK(Vector2, GODOT_API_CLASS(Vector2)); - CACHE_CLASS_AND_CHECK(Rect2, GODOT_API_CLASS(Rect2)); - CACHE_CLASS_AND_CHECK(Transform2D, GODOT_API_CLASS(Transform2D)); - CACHE_CLASS_AND_CHECK(Vector3, GODOT_API_CLASS(Vector3)); - CACHE_CLASS_AND_CHECK(Basis, GODOT_API_CLASS(Basis)); - CACHE_CLASS_AND_CHECK(Quat, GODOT_API_CLASS(Quat)); - CACHE_CLASS_AND_CHECK(Transform, GODOT_API_CLASS(Transform)); - CACHE_CLASS_AND_CHECK(AABB, GODOT_API_CLASS(AABB)); - CACHE_CLASS_AND_CHECK(Color, GODOT_API_CLASS(Color)); - CACHE_CLASS_AND_CHECK(Plane, GODOT_API_CLASS(Plane)); - CACHE_CLASS_AND_CHECK(NodePath, GODOT_API_CLASS(NodePath)); - CACHE_CLASS_AND_CHECK(RID, GODOT_API_CLASS(RID)); - CACHE_CLASS_AND_CHECK(GodotObject, GODOT_API_CLASS(Object)); - CACHE_CLASS_AND_CHECK(GodotResource, GODOT_API_CLASS(Resource)); - CACHE_CLASS_AND_CHECK(Node, GODOT_API_CLASS(Node)); - CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control)); - CACHE_CLASS_AND_CHECK(Spatial, GODOT_API_CLASS(Spatial)); - CACHE_CLASS_AND_CHECK(WeakRef, GODOT_API_CLASS(WeakRef)); - CACHE_CLASS_AND_CHECK(Array, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array)); - CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)); - CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils)); - CACHE_CLASS_AND_CHECK(ISerializationListener, GODOT_API_CLASS(ISerializationListener)); - -#ifdef DEBUG_ENABLED - CACHE_CLASS_AND_CHECK(DebuggingUtils, GODOT_API_CLASS(DebuggingUtils)); -#endif - - // Attributes - CACHE_CLASS_AND_CHECK(ExportAttribute, GODOT_API_CLASS(ExportAttribute)); - CACHE_FIELD_AND_CHECK(ExportAttribute, hint, CACHED_CLASS(ExportAttribute)->get_field("hint")); - CACHE_FIELD_AND_CHECK(ExportAttribute, hintString, CACHED_CLASS(ExportAttribute)->get_field("hintString")); - CACHE_CLASS_AND_CHECK(SignalAttribute, GODOT_API_CLASS(SignalAttribute)); - CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute)); - CACHE_CLASS_AND_CHECK(RemoteAttribute, GODOT_API_CLASS(RemoteAttribute)); - CACHE_CLASS_AND_CHECK(SyncAttribute, GODOT_API_CLASS(SyncAttribute)); - CACHE_CLASS_AND_CHECK(MasterAttribute, GODOT_API_CLASS(MasterAttribute)); - CACHE_CLASS_AND_CHECK(PuppetAttribute, GODOT_API_CLASS(PuppetAttribute)); - CACHE_CLASS_AND_CHECK(SlaveAttribute, GODOT_API_CLASS(SlaveAttribute)); - CACHE_CLASS_AND_CHECK(RemoteSyncAttribute, GODOT_API_CLASS(RemoteSyncAttribute)); - CACHE_CLASS_AND_CHECK(MasterSyncAttribute, GODOT_API_CLASS(MasterSyncAttribute)); - CACHE_CLASS_AND_CHECK(PuppetSyncAttribute, GODOT_API_CLASS(PuppetSyncAttribute)); - CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute)); - CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); - - CACHE_FIELD_AND_CHECK(GodotObject, ptr, CACHED_CLASS(GodotObject)->get_field(BINDINGS_PTR_FIELD)); - CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD)); - CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD)); - - CACHE_METHOD_THUNK_AND_CHECK(GodotObject, Dispose, (GodotObject_Dispose)CACHED_CLASS(GodotObject)->get_method_thunk("Dispose", 0)); - CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, (Array_GetPtr)GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array)->get_method_thunk("GetPtr", 0)); - CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, (Dictionary_GetPtr)GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)->get_method_thunk("GetPtr", 0)); - CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, SignalCallback, (SignalAwaiter_SignalCallback)GODOT_API_CLASS(SignalAwaiter)->get_method_thunk("SignalCallback", 1)); - CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, (SignalAwaiter_FailureCallback)GODOT_API_CLASS(SignalAwaiter)->get_method_thunk("FailureCallback", 0)); - CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, (GodotTaskScheduler_Activate)GODOT_API_CLASS(GodotTaskScheduler)->get_method_thunk("Activate", 0)); - - // Start of MarshalUtils methods - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericArray, (TypeIsGenericArray)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("TypeIsGenericArray", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericDictionary, (TypeIsGenericDictionary)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("TypeIsGenericDictionary", 1)); - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArrayGetElementType, (ArrayGetElementType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("ArrayGetElementType", 2)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryGetKeyValueTypes, (DictionaryGetKeyValueTypes)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("DictionaryGetKeyValueTypes", 3)); - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIEnumerableIsAssignableFromType, (GenericIEnumerableIsAssignableFromType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIEnumerableIsAssignableFromType", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryIsAssignableFromType, (GenericIDictionaryIsAssignableFromType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIDictionaryIsAssignableFromType", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info, (GenericIEnumerableIsAssignableFromType_with_info)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIEnumerableIsAssignableFromType", 2)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info, (GenericIDictionaryIsAssignableFromType_with_info)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIDictionaryIsAssignableFromType", 3)); - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericArrayType, (MakeGenericArrayType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("MakeGenericArrayType", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericDictionaryType, (MakeGenericDictionaryType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("MakeGenericDictionaryType", 2)); - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, EnumerableToArray, (EnumerableToArray)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("EnumerableToArray", 2)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IDictionaryToDictionary, (IDictionaryToDictionary)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("IDictionaryToDictionary", 2)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryToDictionary, (GenericIDictionaryToDictionary)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIDictionaryToDictionary", 2)); - - // End of MarshalUtils methods - -#ifdef DEBUG_ENABLED - CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, (DebugUtils_StackFrameInfo)GODOT_API_CLASS(DebuggingUtils)->get_method_thunk("GetStackFrameInfo", 4)); -#endif - - // TODO Move to CSharpLanguage::init() and do handle disposal - MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr()); - GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler)); - mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler); - - mono_cache.godot_api_cache_updated = true; -} - MonoObject *unmanaged_get_managed(Object *unmanaged) { if (!unmanaged) @@ -386,7 +126,7 @@ void set_main_thread(MonoThread *p_thread) { void attach_current_thread() { ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - MonoThread *mono_thread = mono_thread_attach(mono_domain_get()); + MonoThread *mono_thread = mono_thread_attach(mono_get_root_domain()); ERR_FAIL_NULL(mono_thread); } @@ -421,7 +161,7 @@ GDMonoClass *type_get_proxy_class(const StringName &p_type) { if (klass && klass->is_static()) { // A static class means this is a Godot singleton class. If an instance is needed we use Godot.Object. - return mono_cache.class_GodotObject; + return GDMonoCache::cached_data.class_GodotObject; } #ifdef TOOLS_ENABLED @@ -751,16 +491,16 @@ uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool & } void dispose(MonoObject *p_mono_object, MonoException **r_exc) { - invoke_method_thunk(CACHED_METHOD_THUNK(GodotObject, Dispose), p_mono_object, r_exc); + CACHED_METHOD_THUNK(GodotObject, Dispose).invoke(p_mono_object, r_exc); } namespace Marshal { #ifdef MONO_GLUE_ENABLED #ifdef TOOLS_ENABLED -#define NO_GLUE_RET(m_ret) \ - { \ - if (!mono_cache.godot_api_cache_updated) return m_ret; \ +#define NO_GLUE_RET(m_ret) \ + { \ + if (!GDMonoCache::cached_data.godot_api_cache_updated) return m_ret; \ } #else #define NO_GLUE_RET(m_ret) \ @@ -773,68 +513,60 @@ namespace Marshal { bool type_is_generic_array(MonoReflectionType *p_reftype) { NO_GLUE_RET(false); - TypeIsGenericArray thunk = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray); MonoException *exc = NULL; - MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc); + MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray).invoke(p_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; } bool type_is_generic_dictionary(MonoReflectionType *p_reftype) { NO_GLUE_RET(false); - TypeIsGenericDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary); MonoException *exc = NULL; - MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc); + MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary).invoke(p_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; } void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype) { - ArrayGetElementType thunk = CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType); MonoException *exc = NULL; - invoke_method_thunk(thunk, p_array_reftype, r_elem_reftype, &exc); + CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType).invoke(p_array_reftype, r_elem_reftype, &exc); UNHANDLED_EXCEPTION(exc); } void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) { - DictionaryGetKeyValueTypes thunk = CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes); MonoException *exc = NULL; - invoke_method_thunk(thunk, p_dict_reftype, r_key_reftype, r_value_reftype, &exc); + CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes).invoke(p_dict_reftype, r_key_reftype, r_value_reftype, &exc); UNHANDLED_EXCEPTION(exc); } bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) { NO_GLUE_RET(false); - GenericIEnumerableIsAssignableFromType thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType); MonoException *exc = NULL; - MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc); + MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType).invoke(p_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; } bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) { NO_GLUE_RET(false); - GenericIDictionaryIsAssignableFromType thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType); MonoException *exc = NULL; - MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc); + MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType).invoke(p_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; } bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype) { NO_GLUE_RET(false); - GenericIEnumerableIsAssignableFromType_with_info thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info); MonoException *exc = NULL; - MonoBoolean res = invoke_method_thunk(thunk, p_reftype, r_elem_reftype, &exc); + MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info).invoke(p_reftype, r_elem_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; } bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) { NO_GLUE_RET(false); - GenericIDictionaryIsAssignableFromType_with_info thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info); MonoException *exc = NULL; - MonoBoolean res = invoke_method_thunk(thunk, p_reftype, r_key_reftype, r_value_reftype, &exc); + MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info).invoke(p_reftype, r_key_reftype, r_value_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; } @@ -842,9 +574,8 @@ bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoR Array enumerable_to_array(MonoObject *p_enumerable) { NO_GLUE_RET(Array()); Array result; - EnumerableToArray thunk = CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray); MonoException *exc = NULL; - invoke_method_thunk(thunk, p_enumerable, &result, &exc); + CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray).invoke(p_enumerable, &result, &exc); UNHANDLED_EXCEPTION(exc); return result; } @@ -852,9 +583,8 @@ Array enumerable_to_array(MonoObject *p_enumerable) { Dictionary idictionary_to_dictionary(MonoObject *p_idictionary) { NO_GLUE_RET(Dictionary()); Dictionary result; - IDictionaryToDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary); MonoException *exc = NULL; - invoke_method_thunk(thunk, p_idictionary, &result, &exc); + CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary).invoke(p_idictionary, &result, &exc); UNHANDLED_EXCEPTION(exc); return result; } @@ -862,27 +592,24 @@ Dictionary idictionary_to_dictionary(MonoObject *p_idictionary) { Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary) { NO_GLUE_RET(Dictionary()); Dictionary result; - GenericIDictionaryToDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryToDictionary); MonoException *exc = NULL; - invoke_method_thunk(thunk, p_generic_idictionary, &result, &exc); + CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryToDictionary).invoke(p_generic_idictionary, &result, &exc); UNHANDLED_EXCEPTION(exc); return result; } GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype) { NO_GLUE_RET(NULL); - MakeGenericArrayType thunk = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericArrayType); MonoException *exc = NULL; - MonoReflectionType *reftype = invoke_method_thunk(thunk, p_elem_reftype, &exc); + MonoReflectionType *reftype = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericArrayType).invoke(p_elem_reftype, &exc); UNHANDLED_EXCEPTION(exc); return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype))); } GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) { NO_GLUE_RET(NULL); - MakeGenericDictionaryType thunk = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericDictionaryType); MonoException *exc = NULL; - MonoReflectionType *reftype = invoke_method_thunk(thunk, p_key_reftype, p_value_reftype, &exc); + MonoReflectionType *reftype = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericDictionaryType).invoke(p_key_reftype, p_value_reftype, &exc); UNHANDLED_EXCEPTION(exc); return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype))); } diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index d73743bf0b..848df843fe 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -49,33 +49,6 @@ namespace GDMonoUtils { -typedef void (*GodotObject_Dispose)(MonoObject *, MonoException **); -typedef Array *(*Array_GetPtr)(MonoObject *, MonoException **); -typedef Dictionary *(*Dictionary_GetPtr)(MonoObject *, MonoException **); -typedef MonoObject *(*SignalAwaiter_SignalCallback)(MonoObject *, MonoArray *, MonoException **); -typedef MonoObject *(*SignalAwaiter_FailureCallback)(MonoObject *, MonoException **); -typedef MonoObject *(*GodotTaskScheduler_Activate)(MonoObject *, MonoException **); -typedef MonoArray *(*StackTrace_GetFrames)(MonoObject *, MonoException **); -typedef void (*DebugUtils_StackFrameInfo)(MonoObject *, MonoString **, int *, MonoString **, MonoException **); - -typedef MonoBoolean (*TypeIsGenericArray)(MonoReflectionType *, MonoException **); -typedef MonoBoolean (*TypeIsGenericDictionary)(MonoReflectionType *, MonoException **); - -typedef void (*ArrayGetElementType)(MonoReflectionType *, MonoReflectionType **, MonoException **); -typedef void (*DictionaryGetKeyValueTypes)(MonoReflectionType *, MonoReflectionType **, MonoReflectionType **, MonoException **); - -typedef MonoBoolean (*GenericIEnumerableIsAssignableFromType)(MonoReflectionType *, MonoException **); -typedef MonoBoolean (*GenericIDictionaryIsAssignableFromType)(MonoReflectionType *, MonoException **); -typedef MonoBoolean (*GenericIEnumerableIsAssignableFromType_with_info)(MonoReflectionType *, MonoReflectionType **, MonoException **); -typedef MonoBoolean (*GenericIDictionaryIsAssignableFromType_with_info)(MonoReflectionType *, MonoReflectionType **, MonoReflectionType **, MonoException **); - -typedef MonoReflectionType *(*MakeGenericArrayType)(MonoReflectionType *, MonoException **); -typedef MonoReflectionType *(*MakeGenericDictionaryType)(MonoReflectionType *, MonoReflectionType *, MonoException **); - -typedef void (*EnumerableToArray)(MonoObject *, Array *, MonoException **); -typedef void (*IDictionaryToDictionary)(MonoObject *, Dictionary *, MonoException **); -typedef void (*GenericIDictionaryToDictionary)(MonoObject *, Dictionary *, MonoException **); - namespace Marshal { bool type_is_generic_array(MonoReflectionType *p_reftype); @@ -98,157 +71,6 @@ Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary); } // namespace Marshal -// End of MarshalUtils methods - -struct MonoCache { - - // ----------------------------------------------- - // corlib classes - - // Let's use the no-namespace format for these too - GDMonoClass *class_MonoObject; - GDMonoClass *class_bool; - GDMonoClass *class_int8_t; - GDMonoClass *class_int16_t; - GDMonoClass *class_int32_t; - GDMonoClass *class_int64_t; - GDMonoClass *class_uint8_t; - GDMonoClass *class_uint16_t; - GDMonoClass *class_uint32_t; - GDMonoClass *class_uint64_t; - GDMonoClass *class_float; - GDMonoClass *class_double; - GDMonoClass *class_String; - GDMonoClass *class_IntPtr; - - GDMonoClass *class_System_Collections_IEnumerable; - GDMonoClass *class_System_Collections_IDictionary; - -#ifdef DEBUG_ENABLED - GDMonoClass *class_System_Diagnostics_StackTrace; - StackTrace_GetFrames methodthunk_System_Diagnostics_StackTrace_GetFrames; - GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_bool; - GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_Exception_bool; -#endif - - GDMonoClass *class_KeyNotFoundException; - - MonoClass *rawclass_Dictionary; - // ----------------------------------------------- - - GDMonoClass *class_Vector2; - GDMonoClass *class_Rect2; - GDMonoClass *class_Transform2D; - GDMonoClass *class_Vector3; - GDMonoClass *class_Basis; - GDMonoClass *class_Quat; - GDMonoClass *class_Transform; - GDMonoClass *class_AABB; - GDMonoClass *class_Color; - GDMonoClass *class_Plane; - GDMonoClass *class_NodePath; - GDMonoClass *class_RID; - GDMonoClass *class_GodotObject; - GDMonoClass *class_GodotResource; - GDMonoClass *class_Node; - GDMonoClass *class_Control; - GDMonoClass *class_Spatial; - GDMonoClass *class_WeakRef; - GDMonoClass *class_Array; - GDMonoClass *class_Dictionary; - GDMonoClass *class_MarshalUtils; - GDMonoClass *class_ISerializationListener; - -#ifdef DEBUG_ENABLED - GDMonoClass *class_DebuggingUtils; - DebugUtils_StackFrameInfo methodthunk_DebuggingUtils_GetStackFrameInfo; -#endif - - GDMonoClass *class_ExportAttribute; - GDMonoField *field_ExportAttribute_hint; - GDMonoField *field_ExportAttribute_hintString; - GDMonoClass *class_SignalAttribute; - GDMonoClass *class_ToolAttribute; - GDMonoClass *class_RemoteAttribute; - GDMonoClass *class_SyncAttribute; - GDMonoClass *class_RemoteSyncAttribute; - GDMonoClass *class_MasterSyncAttribute; - GDMonoClass *class_PuppetSyncAttribute; - GDMonoClass *class_MasterAttribute; - GDMonoClass *class_PuppetAttribute; - GDMonoClass *class_SlaveAttribute; - GDMonoClass *class_GodotMethodAttribute; - GDMonoField *field_GodotMethodAttribute_methodName; - - GDMonoField *field_GodotObject_ptr; - GDMonoField *field_NodePath_ptr; - GDMonoField *field_Image_ptr; - GDMonoField *field_RID_ptr; - - GodotObject_Dispose methodthunk_GodotObject_Dispose; - Array_GetPtr methodthunk_Array_GetPtr; - Dictionary_GetPtr methodthunk_Dictionary_GetPtr; - SignalAwaiter_SignalCallback methodthunk_SignalAwaiter_SignalCallback; - SignalAwaiter_FailureCallback methodthunk_SignalAwaiter_FailureCallback; - GodotTaskScheduler_Activate methodthunk_GodotTaskScheduler_Activate; - - // Start of MarshalUtils methods - - TypeIsGenericArray methodthunk_MarshalUtils_TypeIsGenericArray; - TypeIsGenericDictionary methodthunk_MarshalUtils_TypeIsGenericDictionary; - - ArrayGetElementType methodthunk_MarshalUtils_ArrayGetElementType; - DictionaryGetKeyValueTypes methodthunk_MarshalUtils_DictionaryGetKeyValueTypes; - - GenericIEnumerableIsAssignableFromType methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType; - GenericIDictionaryIsAssignableFromType methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType; - GenericIEnumerableIsAssignableFromType_with_info methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType_with_info; - GenericIDictionaryIsAssignableFromType_with_info methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType_with_info; - - MakeGenericArrayType methodthunk_MarshalUtils_MakeGenericArrayType; - MakeGenericDictionaryType methodthunk_MarshalUtils_MakeGenericDictionaryType; - - EnumerableToArray methodthunk_MarshalUtils_EnumerableToArray; - IDictionaryToDictionary methodthunk_MarshalUtils_IDictionaryToDictionary; - GenericIDictionaryToDictionary methodthunk_MarshalUtils_GenericIDictionaryToDictionary; - - // End of MarshalUtils methods - - Ref<MonoGCHandle> task_scheduler_handle; - - bool corlib_cache_updated; - bool godot_api_cache_updated; - - void clear_corlib_cache(); - void clear_godot_api_cache(); - - MonoCache() { - clear_corlib_cache(); - clear_godot_api_cache(); - } -}; - -extern MonoCache mono_cache; - -void update_corlib_cache(); -void update_godot_api_cache(); - -inline void clear_corlib_cache() { - mono_cache.clear_corlib_cache(); -} - -inline void clear_godot_api_cache() { - mono_cache.clear_godot_api_cache(); -} - -_FORCE_INLINE_ bool tools_godot_api_check() { -#ifdef TOOLS_ENABLED - return mono_cache.godot_api_cache_updated; -#else - return true; // Assume it's updated if this was called, otherwise it's a bug -#endif -} - _FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) { p_hash ^= p_with_hash + 0x9e3779b9 + (p_hash << 6) + (p_hash >> 2); } @@ -324,20 +146,6 @@ void dispose(MonoObject *p_mono_object, MonoException **r_exc); #define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL))) -#define CACHED_CLASS(m_class) (GDMonoUtils::mono_cache.class_##m_class) -#define CACHED_CLASS_RAW(m_class) (GDMonoUtils::mono_cache.class_##m_class->get_mono_ptr()) -#define CACHED_RAW_MONO_CLASS(m_class) (GDMonoUtils::mono_cache.rawclass_##m_class) -#define CACHED_FIELD(m_class, m_field) (GDMonoUtils::mono_cache.field_##m_class##_##m_field) -#define CACHED_METHOD(m_class, m_method) (GDMonoUtils::mono_cache.method_##m_class##_##m_method) -#define CACHED_METHOD_THUNK(m_class, m_method) (GDMonoUtils::mono_cache.methodthunk_##m_class##_##m_method) -#define CACHED_PROPERTY(m_class, m_property) (GDMonoUtils::mono_cache.property_##m_class##_##m_property) - -#ifdef REAL_T_IS_DOUBLE -#define REAL_T_MONOCLASS CACHED_CLASS_RAW(double) -#else -#define REAL_T_MONOCLASS CACHED_CLASS_RAW(float) -#endif - #define GD_MONO_BEGIN_RUNTIME_INVOKE \ int &_runtime_invoke_count_ref = GDMonoUtils::get_runtime_invoke_count_ref(); \ _runtime_invoke_count_ref += 1; @@ -345,93 +153,4 @@ void dispose(MonoObject *p_mono_object, MonoException **r_exc); #define GD_MONO_END_RUNTIME_INVOKE \ _runtime_invoke_count_ref -= 1; -inline void invoke_method_thunk(void (*p_method_thunk)()) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - p_method_thunk(); - GD_MONO_END_RUNTIME_INVOKE; -} - -template <class R> -R invoke_method_thunk(R (*p_method_thunk)()) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - R r = p_method_thunk(); - GD_MONO_END_RUNTIME_INVOKE; - return r; -} - -template <class P1> -void invoke_method_thunk(void (*p_method_thunk)(P1), P1 p_arg1) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - p_method_thunk(p_arg1); - GD_MONO_END_RUNTIME_INVOKE; -} - -template <class R, class P1> -R invoke_method_thunk(R (*p_method_thunk)(P1), P1 p_arg1) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - R r = p_method_thunk(p_arg1); - GD_MONO_END_RUNTIME_INVOKE; - return r; -} - -template <class P1, class P2> -void invoke_method_thunk(void (*p_method_thunk)(P1, P2), P1 p_arg1, P2 p_arg2) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - p_method_thunk(p_arg1, p_arg2); - GD_MONO_END_RUNTIME_INVOKE; -} - -template <class R, class P1, class P2> -R invoke_method_thunk(R (*p_method_thunk)(P1, P2), P1 p_arg1, P2 p_arg2) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - R r = p_method_thunk(p_arg1, p_arg2); - GD_MONO_END_RUNTIME_INVOKE; - return r; -} - -template <class P1, class P2, class P3> -void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3), P1 p_arg1, P2 p_arg2, P3 p_arg3) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - p_method_thunk(p_arg1, p_arg2, p_arg3); - GD_MONO_END_RUNTIME_INVOKE; -} - -template <class R, class P1, class P2, class P3> -R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3), P1 p_arg1, P2 p_arg2, P3 p_arg3) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - R r = p_method_thunk(p_arg1, p_arg2, p_arg3); - GD_MONO_END_RUNTIME_INVOKE; - return r; -} - -template <class P1, class P2, class P3, class P4> -void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3, P4), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4); - GD_MONO_END_RUNTIME_INVOKE; -} - -template <class R, class P1, class P2, class P3, class P4> -R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3, P4), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - R r = p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4); - GD_MONO_END_RUNTIME_INVOKE; - return r; -} - -template <class P1, class P2, class P3, class P4, class P5> -void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3, P4, P5), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4, P5 p_arg5) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4, p_arg5); - GD_MONO_END_RUNTIME_INVOKE; -} - -template <class R, class P1, class P2, class P3, class P4, class P5> -R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3, P4, P5), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4, P5 p_arg5) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - R r = p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4, p_arg5); - GD_MONO_END_RUNTIME_INVOKE; - return r; -} - #endif // GD_MONOUTILS_H diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index 189ceaab1b..ee16327856 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -31,6 +31,7 @@ #include "signal_awaiter_utils.h" #include "csharp_script.h" +#include "mono_gd/gd_mono_cache.h" #include "mono_gd/gd_mono_class.h" #include "mono_gd/gd_mono_marshal.h" #include "mono_gd/gd_mono_utils.h" @@ -98,7 +99,7 @@ Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argc MonoException *exc = NULL; GD_MONO_BEGIN_RUNTIME_INVOKE; - invoke_method_thunk(CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback), get_target(), signal_args, &exc); + CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback).invoke(get_target(), signal_args, &exc); GD_MONO_END_RUNTIME_INVOKE; if (exc) { @@ -130,7 +131,7 @@ SignalAwaiterHandle::~SignalAwaiterHandle() { if (awaiter) { MonoException *exc = NULL; GD_MONO_BEGIN_RUNTIME_INVOKE; - invoke_method_thunk(CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback), awaiter, &exc); + CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback).invoke(awaiter, &exc); GD_MONO_END_RUNTIME_INVOKE; if (exc) { diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index e9efc7626d..88366a6a03 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -216,6 +216,25 @@ String str_format(const char *p_format, ...) { #endif String str_format(const char *p_format, va_list p_list) { + char *buffer = str_format_new(p_format, p_list); + + String res(buffer); + memdelete_arr(buffer); + + return res; +} + +char *str_format_new(const char *p_format, ...) { + va_list list; + + va_start(list, p_format); + char *res = str_format_new(p_format, list); + va_end(list); + + return res; +} + +char *str_format_new(const char *p_format, va_list p_list) { va_list list; va_copy(list, p_list); @@ -230,8 +249,5 @@ String str_format(const char *p_format, va_list p_list) { gd_vsnprintf(buffer, len, p_format, list); va_end(list); - String res(buffer); - memdelete_arr(buffer); - - return res; + return buffer; } diff --git a/modules/mono/utils/string_utils.h b/modules/mono/utils/string_utils.h index 565b9bb644..e7f02955bd 100644 --- a/modules/mono/utils/string_utils.h +++ b/modules/mono/utils/string_utils.h @@ -56,5 +56,7 @@ Error read_all_file_utf8(const String &p_path, String &r_content); String str_format(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_1_2; String str_format(const char *p_format, va_list p_list) _PRINTF_FORMAT_ATTRIBUTE_1_0; +char *str_format_new(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_1_2; +char *str_format_new(const char *p_format, va_list p_list) _PRINTF_FORMAT_ATTRIBUTE_1_0; #endif // STRING_FORMAT_H diff --git a/modules/recast/navigation_mesh_generator.cpp b/modules/recast/navigation_mesh_generator.cpp index 320591cf7c..b668085f90 100644 --- a/modules/recast/navigation_mesh_generator.cpp +++ b/modules/recast/navigation_mesh_generator.cpp @@ -62,7 +62,7 @@ void EditorNavigationMeshGenerator::_add_vertex(const Vector3 &p_vec3, Vector<fl } void EditorNavigationMeshGenerator::_add_mesh(const Ref<Mesh> &p_mesh, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices) { - int current_vertex_count = 0; + int current_vertex_count; for (int i = 0; i < p_mesh->get_surface_count(); i++) { current_vertex_count = p_verticies.size() / 3; diff --git a/modules/regex/SCsub b/modules/regex/SCsub index 1be5af02a5..6238cd3d9f 100644 --- a/modules/regex/SCsub +++ b/modules/regex/SCsub @@ -6,12 +6,10 @@ Import('env_modules') env_regex = env_modules.Clone() if env['builtin_pcre2']: - jit_blacklist = ['javascript', 'uwp'] - thirdparty_dir = '#thirdparty/pcre2/src/' thirdparty_flags = ['PCRE2_STATIC', 'HAVE_CONFIG_H'] - if 'platform' in env and env['platform'] not in jit_blacklist: + if env['builtin_pcre2_with_jit']: thirdparty_flags.append('SUPPORT_JIT') thirdparty_sources = [ diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp index 0922471500..10097316ec 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp @@ -266,8 +266,8 @@ void AudioStreamOGGVorbis::_bind_methods() { ClassDB::bind_method(D_METHOD("get_loop_offset"), &AudioStreamOGGVorbis::get_loop_offset); ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_loop", "has_loop"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "loop_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_loop_offset", "get_loop_offset"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "loop_offset"), "set_loop_offset", "get_loop_offset"); } AudioStreamOGGVorbis::AudioStreamOGGVorbis() { diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index 28a8b77283..12f07aa773 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -368,7 +368,7 @@ float VideoStreamPlaybackTheora::get_time() const { return time - AudioServer::get_singleton()->get_output_latency() - delay_compensation; //-((get_total())/(float)vi.rate); }; -Ref<Texture> VideoStreamPlaybackTheora::get_texture() { +Ref<Texture> VideoStreamPlaybackTheora::get_texture() const { return texture; } @@ -741,6 +741,8 @@ RES ResourceFormatLoaderTheora::load(const String &p_path, const String &p_origi *r_error = OK; } + f->close(); + memdelete(f); return ogv_stream; } diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h index 0c37d33358..b241722cd1 100644 --- a/modules/theora/video_stream_theora.h +++ b/modules/theora/video_stream_theora.h @@ -147,7 +147,7 @@ public: void set_file(const String &p_file); - virtual Ref<Texture> get_texture(); + virtual Ref<Texture> get_texture() const; virtual void update(float p_delta); virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata); diff --git a/modules/tinyexr/image_saver_tinyexr.cpp b/modules/tinyexr/image_saver_tinyexr.cpp index e1d42d3217..894f223597 100644 --- a/modules/tinyexr/image_saver_tinyexr.cpp +++ b/modules/tinyexr/image_saver_tinyexr.cpp @@ -262,10 +262,6 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) 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; diff --git a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml index b079653591..f70e22c7d0 100644 --- a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml +++ b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml @@ -45,7 +45,7 @@ <argument index="0" name="idx" type="int"> </argument> <description> - Return the specified input port's type. See the [code]TYPE_*[/code] enum in [@GlobalScope]. + Return the specified input port's type. See the [enum Variant.Type] values. </description> </method> <method name="_get_output_sequence_port_count" qualifiers="virtual"> @@ -86,7 +86,7 @@ <argument index="0" name="idx" type="int"> </argument> <description> - Return the specified output's type. See the [code]TYPE_*[/code] enum in [@GlobalScope]. + Return the specified output's type. See the [enum Variant.Type] values. </description> </method> <method name="_get_text" qualifiers="virtual"> @@ -136,17 +136,17 @@ The start mode used the first time when [method _step] is called. </constant> <constant name="START_MODE_CONTINUE_SEQUENCE" value="1" enum="StartMode"> - The start mode used when [method _step] is called after coming back from a STEP_PUSH_STACK_BIT. + The start mode used when [method _step] is called after coming back from a [constant STEP_PUSH_STACK_BIT]. </constant> <constant name="START_MODE_RESUME_YIELD" value="2" enum="StartMode"> - The start mode used when [method _step] is called after resuming from STEP_YIELD_BIT. + The start mode used when [method _step] is called after resuming from [constant STEP_YIELD_BIT]. </constant> <constant name="STEP_PUSH_STACK_BIT" value="16777216"> Hint used by [method _step] to tell that control should return to it when there is no other node left to execute. This is used by [VisualScriptCondition] to redirect the sequence to the "Done" port after the [code]true[/code]/[code]false[/code] branch has finished execution. </constant> <constant name="STEP_GO_BACK_BIT" value="33554432"> - Hint used by [method _step] to tell that control should return back, either hitting a previous STEP_PUSH_STACK_BIT or exiting the function. + Hint used by [method _step] to tell that control should return back, either hitting a previous [constant STEP_PUSH_STACK_BIT] or exiting the function. </constant> <constant name="STEP_NO_ADVANCE_BIT" value="67108864"> </constant> diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 0cacd0f0b5..bb8612af6f 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -1361,6 +1361,7 @@ void VisualScript::_bind_methods() { VisualScript::VisualScript() { base_type = "Object"; + is_tool_script = false; } StringName VisualScript::get_default_func() const { diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index c1a4c58620..bf353d287f 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -1637,7 +1637,7 @@ void VisualScriptEditor::_on_nodes_duplicate() { for (Set<int>::Element *F = to_duplicate.front(); F; F = F->next()) { - // duplicate from the specifc function but place it into the default func as it would lack the connections + // duplicate from the specific function but place it into the default func as it would lack the connections StringName func = _get_function_of_node(F->get()); Ref<VisualScriptNode> node = script->get_node(func, F->get()); @@ -2163,7 +2163,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da Node *sn = _find_script_node(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root(), script); if (!sn) { - EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes because script '" + get_name() + "' is not used in this scene.")); + EditorNode::get_singleton()->show_warning(vformat(TTR("Can't drop nodes because script '%s' is not used in this scene."), get_name())); return; } @@ -2233,7 +2233,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da Node *sn = _find_script_node(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root(), script); if (!sn && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { - EditorNode::get_singleton()->show_warning(TTR("Can't drop properties because script '" + get_name() + "' is not used in this scene.\nDrop holding 'Shift' to just copy the signature.")); + EditorNode::get_singleton()->show_warning(vformat(TTR("Can't drop properties because script '%s' is not used in this scene.\nDrop holding 'Shift' to just copy the signature."), get_name())); return; } @@ -2938,7 +2938,7 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, if ((to_node_pos.x - from_node_pos.x) < 0) { // to is behind from node if (to_node_pos.x > (from_node_pos.x - to_node_size.x - 240)) - new_to_node_pos.x = from_node_pos.x - to_node_size.x - 240; // approx size of construtor node + padding + new_to_node_pos.x = from_node_pos.x - to_node_size.x - 240; // approx size of constructor node + padding else new_to_node_pos.x = to_node_pos.x; new_to_node_pos.y = to_node_pos.y; @@ -2947,7 +2947,7 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, } else { // to is ahead of from node if (to_node_pos.x < (from_node_size.x + from_node_pos.x + 240)) - new_to_node_pos.x = from_node_size.x + from_node_pos.x + 240; // approx size of construtor node + padding + new_to_node_pos.x = from_node_size.x + from_node_pos.x + 240; // approx size of constructor node + padding else new_to_node_pos.x = to_node_pos.x; new_to_node_pos.y = to_node_pos.y; @@ -3464,6 +3464,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri ofs = ofs.snapped(Vector2(snap, snap)); } ofs /= EDSCALE; + ofs /= graph->get_zoom(); Set<int> vn; @@ -3515,6 +3516,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } Ref<VisualScriptNode> vnode; + Ref<VisualScriptPropertySet> script_prop_set; if (p_category == String("method")) { @@ -3525,8 +3527,8 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri Ref<VisualScriptPropertySet> n; n.instance(); - n->set_property(p_text); vnode = n; + script_prop_set = n; } else if (p_category == String("get")) { Ref<VisualScriptPropertyGet> n; @@ -3578,6 +3580,9 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri undo_redo->add_undo_method(this, "_update_graph", new_id); undo_redo->commit_action(); + if (script_prop_set.is_valid()) + script_prop_set->set_property(p_text); + port_action_new_node = new_id; Ref<VisualScriptNode> vsn = script->get_node(func, port_action_new_node); @@ -4221,7 +4226,7 @@ void VisualScriptEditor::_menu_option(int p_what) { if (nd.is_valid() && nd->has_input_sequence_port()) start_node = nodes.front()->key(); else { - EditorNode::get_singleton()->show_warning(TTR("Select atleast one node with sequence port.")); + EditorNode::get_singleton()->show_warning(TTR("Select at least one node with sequence port.")); return; } } else { @@ -4252,7 +4257,7 @@ void VisualScriptEditor::_menu_option(int p_what) { if (nd.is_valid() && nd->has_input_sequence_port()) start_node = top_nd; else { - EditorNode::get_singleton()->show_warning(TTR("Select atleast one node with sequence port.")); + EditorNode::get_singleton()->show_warning(TTR("Select at least one node with sequence port.")); return; } } else { diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 957127fe61..2a8d67d403 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -949,7 +949,7 @@ String VisualScriptOperator::get_caption() const { //mathematic L"A + B", //OP_ADD, L"A - B", //OP_SUBTRACT, - L"A x B", //OP_MULTIPLY, + L"A \u00D7 B", //OP_MULTIPLY, L"A \u00F7 B", //OP_DIVIDE, L"\u00AC A", //OP_NEGATE, L"+ A", //OP_POSITIVE, @@ -1290,7 +1290,7 @@ public: if (!instance->get_variable(variable, p_outputs[0])) { r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; r_error_str = RTR("VariableGet not found in script: ") + "'" + String(variable) + "'"; - return false; + return 0; } return 0; } diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp index fa3602ad27..54b284f939 100644 --- a/modules/webm/video_stream_webm.cpp +++ b/modules/webm/video_stream_webm.cpp @@ -230,7 +230,7 @@ void VideoStreamPlaybackWebm::set_audio_track(int p_idx) { audio_track = p_idx; } -Ref<Texture> VideoStreamPlaybackWebm::get_texture() { +Ref<Texture> VideoStreamPlaybackWebm::get_texture() const { return texture; } @@ -484,6 +484,8 @@ RES ResourceFormatLoaderWebm::load(const String &p_path, const String &p_origina *r_error = OK; } + f->close(); + memdelete(f); return webm_stream; } diff --git a/modules/webm/video_stream_webm.h b/modules/webm/video_stream_webm.h index ddcbb1eb08..4f79d46cce 100644 --- a/modules/webm/video_stream_webm.h +++ b/modules/webm/video_stream_webm.h @@ -90,7 +90,7 @@ public: virtual void set_audio_track(int p_idx); - virtual Ref<Texture> get_texture(); + virtual Ref<Texture> get_texture() const; virtual void update(float p_delta); virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata); diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 32beccde5d..9d610109ed 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -213,7 +213,7 @@ void WSLPeer::make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigne wslay_event_context_server_init(&(_data->ctx), &wsl_callbacks, _data); else wslay_event_context_client_init(&(_data->ctx), &wsl_callbacks, _data); - wslay_event_config_set_max_recv_msg_length(_data->ctx, (1 << p_in_buf_size)); + wslay_event_config_set_max_recv_msg_length(_data->ctx, (1ULL << p_in_buf_size)); } void WSLPeer::set_write_mode(WriteMode p_mode) { diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp index 65b3cf08f5..2fc554fe7a 100644 --- a/modules/xatlas_unwrap/register_types.cpp +++ b/modules/xatlas_unwrap/register_types.cpp @@ -65,8 +65,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver xatlas::Atlas *atlas = xatlas::Create(); printf("Adding mesh..\n"); xatlas::AddMeshError::Enum err = xatlas::AddMesh(atlas, input_mesh, 1); - ERR_EXPLAINC(xatlas::StringForEnum(err)); - ERR_FAIL_COND_V(err != xatlas::AddMeshError::Enum::Success, false); + ERR_FAIL_COND_V_MSG(err != xatlas::AddMeshError::Enum::Success, false, xatlas::StringForEnum(err)); printf("Generate..\n"); xatlas::Generate(atlas, chart_options, NULL, pack_options); |