diff options
Diffstat (limited to 'modules')
125 files changed, 3473 insertions, 1944 deletions
diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm index 9614f775a5..71642cfc30 100644 --- a/modules/arkit/arkit_interface.mm +++ b/modules/arkit/arkit_interface.mm @@ -37,6 +37,8 @@ #import <ARKit/ARKit.h> #import <UIKit/UIKit.h> +#include <dlfcn.h> + #include "arkit_interface.h" #include "arkit_session_delegate.h" @@ -53,7 +55,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 +226,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..2cb2a71f1e 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,257 @@ 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 if (element_assimp_node->mNumMeshes > 0) { + spatial = memnew(Spatial); + } 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 (parent_node && spatial != state.root) { + parent_node->add_child(spatial); + spatial->set_owner(state.root); + } else if (spatial == state.root) { + // required - think about it root never has a parent yet is valid, anything else without a parent is not valid. + } else // Safety for instances + { + WARN_PRINT( + "Failed to find parent node instance after lookup, serious warning report to godot with model"); + memdelete(spatial); // this node is broken + } + } 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(); + Node *parent_node = mesh_template->get_parent(); + + ERR_CONTINUE(assimp_node == NULL); + ERR_CONTINUE(mesh_template == NULL); + + 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 +547,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 +604,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 +615,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; + + xform = skeleton->get_bone_pose(skeleton_bone).inverse() * xform; - rot.normalize(); + 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 +657,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 +715,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 +738,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(); - - bool is_bone = skeleton->find_bone(node_name) != -1; - //print_verbose("Bone " + node_name + " is bone? " + (is_bone ? "Yes" : "No")); - NodePath node_path; + Skeleton *skeleton = NULL; + NodePath node_path; + aiBone *bone = NULL; - 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 +806,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 +839,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 +857,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 +868,6 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( } } } - // // Process Vertex Weights // @@ -570,19 +877,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 +937,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,6 +1004,8 @@ 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); } } @@ -697,7 +1018,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 +1033,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 +1053,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 +1065,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 +1162,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 +1306,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); - Map<const aiBone *, Skeleton *>::Element *match = state.bone_to_skeleton_lookup.find(first); - if (match != NULL) { - skeleton = match->value(); + RegenerateBoneStack(state); - 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 +1335,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 +1491,30 @@ 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; + state.nodes.push_back(assimp_node); 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); + 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..8135b352c6 100644 --- a/modules/assimp/import_utils.h +++ b/modules/assimp/import_utils.h @@ -369,8 +369,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 +394,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 89f3064d7a..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) { @@ -473,7 +473,7 @@ void RigidBodyBullet::assert_no_constraints() { void RigidBodyBullet::set_activation_state(bool p_active) { if (p_active) { - btBody->setActivationState(ACTIVE_TAG); + btBody->activate(); } else { btBody->setActivationState(WANTS_DEACTIVATION); } @@ -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/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/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/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..a22d18b970 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 @@ -839,6 +839,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"> @@ -1362,6 +1363,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/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 9b3bf8ad5b..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"; @@ -1944,11 +1947,10 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); r_result.insert(option.display, option); } - } else { - for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } + } + for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } if (!p_only_functions) { 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 d9535d0f1f..bbafef68ed 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(); p = base.ptr(); @@ -1273,6 +1274,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 21434cd150..6ef3ab67ae 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; @@ -858,11 +865,23 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s if (current_function) { int arg_idx = current_function->arguments.find(identifier); if (arg_idx != -1) { - if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { - // Assignment is not really usage - current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] - 1; - } else { - current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1; + switch (tokenizer->get_token()) { + case GDScriptTokenizer::TK_OP_ASSIGN_ADD: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: + case GDScriptTokenizer::TK_OP_ASSIGN_DIV: + case GDScriptTokenizer::TK_OP_ASSIGN_MOD: + case GDScriptTokenizer::TK_OP_ASSIGN_MUL: + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: + case GDScriptTokenizer::TK_OP_ASSIGN_SUB: + case GDScriptTokenizer::TK_OP_ASSIGN: { + // Assignment is not really usage + } break; + default: { + current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1; + } } } } @@ -2213,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; @@ -2228,13 +2247,11 @@ 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 } - if (pending_newline != -1) { - pending_newline = -1; - } + pending_newline = -1; PatternBranchNode *branch = alloc_node<PatternBranchNode>(); branch->body = alloc_node<BlockNode>(); @@ -2685,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 @@ -2698,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; } @@ -2710,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 } @@ -2914,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; @@ -2969,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; } @@ -3341,32 +3362,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(); @@ -3464,7 +3498,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) { @@ -3472,7 +3506,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 } @@ -6111,12 +6145,18 @@ bool GDScriptParser::_is_type_compatible(const DataType &p_container, const Data break; } + // Some classes are prefixed with `_` internally + if (!ClassDB::class_exists(expr_native)) { + expr_native = "_" + expr_native; + } + switch (p_container.kind) { case DataType::NATIVE: { if (p_container.is_meta_type) { return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static()); } else { - return ClassDB::is_parent_class(expr_native, p_container.native_type); + StringName container_native = ClassDB::class_exists(p_container.native_type) ? p_container.native_type : StringName("_" + p_container.native_type); + return ClassDB::is_parent_class(expr_native, container_native); } } break; case DataType::SCRIPT: @@ -6426,6 +6466,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 @@ -8333,6 +8375,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; } @@ -8546,8 +8591,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 8b20b0ff48..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)) { @@ -849,12 +823,8 @@ void GDScriptTokenizerText::_advance() { _make_error("Unterminated String"); return; } - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { - _make_error("Malformed hex constant in string"); - return; - } - CharType v; + CharType v = 0; if (c >= '0' && c <= '9') { v = c - '0'; } else if (c >= 'a' && c <= 'f') { @@ -864,8 +834,8 @@ void GDScriptTokenizerText::_advance() { v = c - 'A'; v += 10; } else { - ERR_PRINT("BUG"); - v = 0; + _make_error("Malformed hex constant in string"); + return; } res <<= 4; @@ -937,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; } @@ -951,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))) { @@ -1112,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(); } @@ -1187,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_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 cf360b5291..35471d63d6 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -496,7 +496,7 @@ struct TextDocumentSyncOptions { dict["willSave"] = willSave; dict["openClose"] = openClose; dict["change"] = change; - dict["change"] = save.to_json(); + dict["save"] = save.to_json(); return dict; } }; @@ -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..b762868f2c 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -217,6 +217,14 @@ Deprecated, use [member mesh_library] instead. </member> </members> + <signals> + <signal name="cell_size_changed"> + <argument index="0" name="cell_size" type="Vector3"> + </argument> + <description> + </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 c97524a54d..1bd570c55f 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -40,11 +40,8 @@ void GridMapEditor::_node_removed(Node *p_node) { - if (p_node == node) { + if (p_node == node) node = NULL; - hide(); - mesh_library_palette->hide(); - } } void GridMapEditor::_configure() { @@ -279,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()) { @@ -301,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); @@ -353,7 +351,14 @@ void GridMapEditor::_set_selection(bool p_active, const Vector3 &p_begin, const selection.click = p_begin; selection.current = p_end; - _update_selection_transform(); + if (is_visible_in_tree()) { + _update_selection_transform(); + } + + options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CLEAR), !selection.active); + options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CUT), !selection.active); + options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_DUPLICATE), !selection.active); + options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_FILL), !selection.active); } bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, bool p_click) { @@ -591,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); } @@ -929,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; @@ -957,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() { @@ -1055,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) { @@ -1193,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) { @@ -1330,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); @@ -1465,9 +1466,16 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { } } - selection.active = false; + _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/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..03aaa925f0 100644 --- a/modules/mono/build_scripts/godot_tools_build.py +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -74,15 +74,11 @@ 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', @@ -97,7 +93,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_configure.py b/modules/mono/build_scripts/mono_configure.py index 4c1ebd8d74..5388061f84 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 (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')) and not mono_prefix: + if is_javascript: + mono_static = True + + 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': @@ -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') @@ -183,7 +211,30 @@ def configure(env, env_mono): 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 +242,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']) @@ -230,19 +283,22 @@ def configure(env, env_mono): 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 + config_file_path = os.path.join(mono_root, 'etc', 'mono', 'config') + 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 +307,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 +318,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 +333,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 @@ -377,6 +432,11 @@ def copy_mono_shared_libs(env, mono_root, target_mono_root_dir): os.makedirs(target_mono_bin_dir) copy(os.path.join(mono_root, 'bin', 'MonoPosixHelper.dll'), target_mono_bin_dir) + + # For newer versions + btls_dll_path = os.path.join(mono_root, 'bin', '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 +446,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..34dcde40f4 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" @@ -130,8 +132,6 @@ void CSharpLanguage::init() { #ifdef TOOLS_ENABLED EditorNode::add_init_callback(&_editor_init_callback); - - GLOBAL_DEF("mono/export/include_scripts_content", false); #endif } @@ -545,7 +545,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 +571,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 +583,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 +593,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 +616,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 +1077,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 +1086,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..cf3823fd16 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -0,0 +1,615 @@ +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/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; + + { + string platformBclDir = DeterminePlatformBclDir(platform); + + internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, platformBclDir, 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) + string outputDataDir = null; + + if (PlatformHasTemplateDir(platform)) + outputDataDir = ExportDataDirectory(features, platform, isDebug, outputDir); + + // 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()); + + if (!Directory.Exists(templateDirPath)) + { + templateDirPath = null; + + if (isDebug) + { + target = "debug"; // Support both 'release_debug' and 'debug' for the template data directory name + templateDirPath = Path.Combine(Internal.FullTemplatesDir, TemplateDirName()); + + if (!Directory.Exists(templateDirPath)) + templateDirPath = null; + } + } + + if (templateDirPath == null) + throw new FileNotFoundException("Data template 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))); + } + + 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 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"))) + platformBclDir = null; // Use the one we're running on + } + + return platformBclDir; + } + + private static string DeterminePlatformBclProfile(string platform) + { + switch (platform) + { + case OS.Platforms.Windows: + case OS.Platforms.UWP: + 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(string projectDllName, string projectDllSrcPath, + string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencies); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 12edd651df..2a5d3de126 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -1,4 +1,5 @@ using Godot; +using GodotTools.Export; using GodotTools.Utils; using System; using System.Collections.Generic; @@ -225,7 +226,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 +266,7 @@ namespace GodotTools string command; - if (OS.IsOSX()) + if (OS.IsOSX) { if (!osxAppBundleInstalled && _vsCodePath.Empty()) { @@ -415,18 +416,18 @@ namespace GodotTools string settingsHintStr = "Disabled"; - if (OS.IsWindows()) + if (OS.IsWindows) { settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } - else if (OS.IsOSX()) + else if (OS.IsOSX) { settingsHintStr += $",Visual Studio:{(int) ExternalEditorId.VisualStudioForMac}" + $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } - else if (OS.IsUnix()) + else if (OS.IsUnixLike()) { settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; @@ -441,8 +442,9 @@ namespace GodotTools }); // Export plugin - var exportPlugin = new GodotSharpExport(); + var exportPlugin = new ExportPlugin(); AddExportPlugin(exportPlugin); + exportPlugin.RegisterExportSettings(); exportPluginWeak = WeakRef(exportPlugin); BuildManager.Initialize(); @@ -461,7 +463,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..fb2cbabc8e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -30,6 +30,7 @@ <ConsolePause>false</ConsolePause> </PropertyGroup> <ItemGroup> + <Reference Include="Mono.Posix" /> <Reference Include="System" /> <Reference Include="GodotSharp"> <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath> @@ -40,6 +41,7 @@ </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" /> @@ -63,7 +65,6 @@ <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" /> </ItemGroup> diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 01aa0d0ab1..f94d6f998c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -79,7 +79,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/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..1a8c26acd7 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; + 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, 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..4055ec005a 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -219,15 +219,15 @@ 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) { +uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoString *p_project_dll_name, MonoString *p_project_dll_src_path, + MonoString *p_build_config, MonoString *p_custom_bcl_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); 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(project_dll_name, project_dll_src_path, build_config, custom_bcl_dir, dependencies); } MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) { @@ -411,8 +411,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..9ae9399e1d 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 { 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/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/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..bfb7b0f775 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" 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..cb0ac9431e 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" @@ -130,7 +130,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 +164,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,8 +201,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 diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 504b8d41d0..384ef08cd0 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) @@ -279,11 +319,17 @@ void GDMono::initialize() { config_dir = bundled_config_dir; #endif // TOOLS_ENABLED +#if !defined(JAVASCRIPT_ENABLED) // 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 + +#if defined(ANDROID_ENABLED) + GDMonoAndroid::register_android_dl_fallback(); +#endif { PropertyInfo exc_policy_prop = PropertyInfo(Variant::INT, "mono/unhandled_exception_policy", PROPERTY_HINT_ENUM, @@ -299,10 +345,8 @@ void GDMono::initialize() { GDMonoAssembly::initialize(); - gdmono_profiler_init(); - -#ifdef DEBUG_ENABLED - gdmono_debug_init(); +#if !defined(JAVASCRIPT_ENABLED) + gd_mono_profiler_init(); #endif #ifdef ANDROID_ENABLED @@ -326,12 +370,14 @@ void GDMono::initialize() { } #endif - root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); + 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; @@ -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; @@ -561,7 +607,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 +880,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 +930,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 +1030,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)); diff --git a/modules/mono/utils/android_utils.cpp b/modules/mono/mono_gd/gd_mono_android.cpp index 7dd67e3b8e..1ee035589d 100644 --- a/modules/mono/utils/android_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_android.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* android_utils.cpp */ +/* gd_mono_android.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,16 +28,25 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "android_utils.h" +#include "gd_mono_android.h" -#ifdef __ANDROID__ +#if defined(ANDROID_ENABLED) +#include <dlfcn.h> // dlopen, dlsym +#include <mono/utils/mono-dl-fallback.h> + +#include "core/os/os.h" +#include "core/ustring.h" #include "platform/android/thread_jandroid.h" -namespace GDMonoUtils { -namespace Android { +#include "../utils/path_utils.h" +#include "../utils/string_utils.h" -String get_app_native_lib_dir() { +namespace GDMonoAndroid { + +String app_native_lib_dir_cache; + +String determine_app_native_lib_dir() { JNIEnv *env = ThreadAndroid::get_env(); jclass activityThreadClass = env->FindClass("android/app/ActivityThread"); @@ -62,7 +71,76 @@ String get_app_native_lib_dir() { return result; } -} // namespace Android -} // namespace GDMonoUtils +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; +} + +void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void *p_user_data) { + 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); + + if (!FileAccess::exists(so_path)) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Cannot find shared library: '%s'\n", so_path.utf8().get_data()); + return NULL; + } + + int lflags = gd_mono_convert_dl_flags(p_flags); + + void *handle = dlopen(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", so_path.utf8().get_data(), dlerror()); + return NULL; + } + + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Successfully loaded AOT shared library: '%s'\n", so_path.utf8().get_data()); + + return handle; + } + + 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 (r_err) + *r_err = str_format_new("%s\n", dlerror()); + + return NULL; +} + +void register_android_dl_fallback() { + mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, NULL, NULL); +} + +} // namespace GDMonoAndroid -#endif // __ANDROID__ +#endif diff --git a/modules/mono/utils/android_utils.h b/modules/mono/mono_gd/gd_mono_android.h index f911c3fdfe..72bc799bfd 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,21 @@ /* 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 register_android_dl_fallback(); -#endif // __ANDROID__ +} // namespace GDMonoAndroid -#endif // ANDROID_UTILS_H +#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..91842420b7 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; 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..b21f92cdd8 --- /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; + 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; + 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..3827960e6b 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(); } 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 c5b60f2dca..320591cf7c 100644 --- a/modules/recast/navigation_mesh_generator.cpp +++ b/modules/recast/navigation_mesh_generator.cpp @@ -131,7 +131,7 @@ void EditorNavigationMeshGenerator::_add_faces(const PoolVector3Array &p_faces, } } -void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask) { +void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { if (Object::cast_to<MeshInstance>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { @@ -263,8 +263,10 @@ void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_tran p_accumulated_transform = p_accumulated_transform * spatial->get_transform(); } - for (int i = 0; i < p_node->get_child_count(); i++) { - _parse_geometry(p_accumulated_transform, p_node->get_child(i), p_verticies, p_indices, p_generate_from, p_collision_mask); + if (p_recurse_children) { + for (int i = 0; i < p_node->get_child_count(); i++) { + _parse_geometry(p_accumulated_transform, p_node->get_child(i), p_verticies, p_indices, p_generate_from, p_collision_mask, p_recurse_children); + } } } @@ -439,7 +441,21 @@ void EditorNavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p Vector<float> vertices; Vector<int> indices; - _parse_geometry(Object::cast_to<Spatial>(p_node)->get_transform().affine_inverse(), p_node, vertices, indices, p_nav_mesh->get_parsed_geometry_type(), p_nav_mesh->get_collision_mask()); + List<Node *> parse_nodes; + + if (p_nav_mesh->get_source_geometry_mode() == NavigationMesh::SOURCE_GEOMETRY_NAVMESH_CHILDREN) { + parse_nodes.push_back(p_node); + } else { + p_node->get_tree()->get_nodes_in_group(p_nav_mesh->get_source_group_name(), &parse_nodes); + } + + Transform navmesh_xform = Object::cast_to<Spatial>(p_node)->get_transform().affine_inverse(); + for (const List<Node *>::Element *E = parse_nodes.front(); E; E = E->next()) { + int geometry_type = p_nav_mesh->get_parsed_geometry_type(); + uint32_t collision_mask = p_nav_mesh->get_collision_mask(); + bool recurse_children = p_nav_mesh->get_source_geometry_mode() != NavigationMesh::SOURCE_GEOMETRY_GROUPS_EXPLICIT; + _parse_geometry(navmesh_xform, E->get(), vertices, indices, geometry_type, collision_mask, recurse_children); + } if (vertices.size() > 0 && indices.size() > 0) { diff --git a/modules/recast/navigation_mesh_generator.h b/modules/recast/navigation_mesh_generator.h index 30a6e3c835..f19622a4a9 100644 --- a/modules/recast/navigation_mesh_generator.h +++ b/modules/recast/navigation_mesh_generator.h @@ -47,7 +47,7 @@ protected: static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies); static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices); static void _add_faces(const PoolVector3Array &p_faces, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices); - static void _parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask); + static void _parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh); static void _build_recast_navigation_mesh(Ref<NavigationMesh> p_nav_mesh, EditorProgress *ep, 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/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 093901ad07..b2791cfc8b 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()); @@ -1916,8 +1916,6 @@ bool VisualScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant & return false; } -#ifdef TOOLS_ENABLED - static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const Ref<Script> &script) { if (p_edited_scene != p_current_node && p_current_node->get_owner() != p_edited_scene) @@ -1937,8 +1935,6 @@ static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const return NULL; } -#endif - void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { if (p_from != graph) { @@ -2167,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; } @@ -2237,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; } @@ -2456,12 +2452,8 @@ Ref<Texture> VisualScriptEditor::get_icon() { } bool VisualScriptEditor::is_unsaved() { -#ifdef TOOLS_ENABLED return script->is_edited() || script->are_subnodes_edited(); -#else - return false; -#endif } Variant VisualScriptEditor::get_edit_state() { @@ -2946,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; @@ -2955,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; @@ -3523,6 +3515,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")) { @@ -3533,8 +3526,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; @@ -3586,6 +3579,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); @@ -4229,7 +4225,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 { @@ -4260,7 +4256,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_editor.h b/modules/visual_script/visual_script_editor.h index 5a00469eea..fbf021e7b6 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -228,7 +228,7 @@ class VisualScriptEditor : public ScriptEditorBase { void _update_node_size(int p_id); void _port_name_focus_out(const Node *p_name_box, int p_id, int p_port, bool is_input); - Vector2 _get_available_pos(bool centered = true, Vector2 pos = Vector2()) const; + Vector2 _get_available_pos(bool centered = true, Vector2 ofs = Vector2()) const; StringName _get_function_of_node(int p_id) const; void _move_nodes_with_rescan(const StringName &p_func_from, const StringName &p_func_to, int p_id); diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 957127fe61..857d640b43 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, diff --git a/modules/visual_script/visual_script_property_selector.h b/modules/visual_script/visual_script_property_selector.h index 405949e8c1..3a7c8de6a2 100644 --- a/modules/visual_script/visual_script_property_selector.h +++ b/modules/visual_script/visual_script_property_selector.h @@ -45,7 +45,7 @@ class VisualScriptPropertySelector : public ConfirmationDialog { void create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text); - void get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box); + void get_visual_node_names(const String &root_filter, const Set<String> &p_modifiers, bool &found, TreeItem *const root, LineEdit *const search_box); void _sbox_input(const Ref<InputEvent> &p_ie); 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/emws_client.cpp b/modules/websocket/emws_client.cpp index fad766ea5d..983db60d5e 100644 --- a/modules/websocket/emws_client.cpp +++ b/modules/websocket/emws_client.cpp @@ -64,9 +64,15 @@ EMSCRIPTEN_KEEPALIVE void _esws_on_close(void *obj, int code, char *reason, int } } -Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const PoolVector<String> p_protocols, const Vector<String> p_custom_headers) { +Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocols, const Vector<String> p_custom_headers) { + + String proto_string; + for (int i = 0; i < p_protocols.size(); i++) { + if (i != 0) + proto_string += ","; + proto_string += p_protocols[i]; + } - String proto_string = p_protocols.join(","); String str = "ws://"; if (p_custom_headers.size()) { diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h index 2d35f7f0f6..67705891b2 100644 --- a/modules/websocket/emws_client.h +++ b/modules/websocket/emws_client.h @@ -50,7 +50,7 @@ public: bool _is_connecting; Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets); - Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const PoolVector<String> p_protocol = PoolVector<String>(), const Dictionary p_custom_headers = Dictionary()); + Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>()); Ref<WebSocketPeer> get_peer(int p_peer_id) const; void disconnect_from_host(int p_code = 1000, String p_reason = ""); IP_Address get_connected_host() const; diff --git a/modules/websocket/emws_server.cpp b/modules/websocket/emws_server.cpp index c4bb459ad0..9a6a30d613 100644 --- a/modules/websocket/emws_server.cpp +++ b/modules/websocket/emws_server.cpp @@ -33,7 +33,7 @@ #include "emws_server.h" #include "core/os/os.h" -Error EMWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { +Error EMWSServer::listen(int p_port, Vector<String> p_protocols, bool gd_mp_api) { return FAILED; } diff --git a/modules/websocket/emws_server.h b/modules/websocket/emws_server.h index a5e5b4090e..e8da8c26b4 100644 --- a/modules/websocket/emws_server.h +++ b/modules/websocket/emws_server.h @@ -43,7 +43,7 @@ class EMWSServer : public WebSocketServer { public: Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets); - Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); + Error listen(int p_port, Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false); void stop(); bool is_listening() const; bool has_peer(int p_id) const; diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index a422f65cfc..ad70c9c0e1 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -181,8 +181,12 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, _connection = _tcp; _use_ssl = p_ssl; _host = p_host; - _protocols.clear(); - _protocols.append_array(p_protocols); + // Strip edges from protocols. + _protocols.resize(p_protocols.size()); + String *pw = _protocols.ptrw(); + for (int i = 0; i < p_protocols.size(); i++) { + pw[i] = p_protocols[i].strip_edges(); + } _key = WSLPeer::generate_key(); // TODO custom extra headers (allow overriding this too?) 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/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index 993dceafb9..2181775b99 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -80,11 +80,12 @@ bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols) { if (headers.has("sec-websocket-protocol")) { Vector<String> protos = headers["sec-websocket-protocol"].split(","); for (int i = 0; i < protos.size(); i++) { + String proto = protos[i].strip_edges(); // Check if we have the given protocol for (int j = 0; j < p_protocols.size(); j++) { - if (protos[i] != p_protocols[j]) + if (proto != p_protocols[j]) continue; - protocol = protos[i]; + protocol = proto; break; } // Found a protocol @@ -158,7 +159,12 @@ Error WSLServer::listen(int p_port, const Vector<String> p_protocols, bool gd_mp ERR_FAIL_COND_V(is_listening(), ERR_ALREADY_IN_USE); _is_multiplayer = gd_mp_api; - _protocols.append_array(p_protocols); + // Strip edges from protocols. + _protocols.resize(p_protocols.size()); + String *pw = _protocols.ptrw(); + for (int i = 0; i < p_protocols.size(); i++) { + pw[i] = p_protocols[i].strip_edges(); + } _server->listen(p_port); return OK; 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); |