diff options
Diffstat (limited to 'modules')
210 files changed, 8657 insertions, 6279 deletions
diff --git a/modules/SCsub b/modules/SCsub index 67f5893db4..42d89d6ce2 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -6,14 +6,14 @@ env_modules = env.Clone() Export('env_modules') -env.modules_sources = [ - "register_module_types.gen.cpp", -] +env.modules_sources = [] + +env_modules.add_source_files(env.modules_sources, "register_module_types.gen.cpp") for x in env.module_list: if (x in env.disabled_modules): continue - env_modules.Append(CPPFLAGS=["-DMODULE_" + x.upper() + "_ENABLED"]) + env_modules.Append(CPPDEFINES=["MODULE_" + x.upper() + "_ENABLED"]) SConscript(x + "/SCsub") if env.split_modules: diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm index de58f93276..68844c54c2 100644 --- a/modules/arkit/arkit_interface.mm +++ b/modules/arkit/arkit_interface.mm @@ -430,7 +430,7 @@ void ARKitInterface::process() { // get some info about our screen and orientation Size2 screen_size = OS::get_singleton()->get_window_size(); - UIDeviceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; + UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; // Grab our camera image for our backbuffer CVPixelBufferRef pixelBuffer = current_frame.capturedImage; @@ -531,7 +531,7 @@ void ARKitInterface::process() { // we need to invert this, probably row v.s. column notation affine_transform = CGAffineTransformInvert(affine_transform); - if (orientation != UIDeviceOrientationPortrait) { + if (orientation != UIInterfaceOrientationPortrait) { affine_transform.b = -affine_transform.b; affine_transform.d = -affine_transform.d; affine_transform.ty = 1.0 - affine_transform.ty; @@ -582,28 +582,28 @@ void ARKitInterface::process() { // copy our current frame transform matrix_float4x4 m44 = camera.transform; - if (orientation == UIDeviceOrientationLandscapeLeft) { + if (orientation == UIInterfaceOrientationLandscapeLeft) { transform.basis.elements[0].x = m44.columns[0][0]; transform.basis.elements[1].x = m44.columns[0][1]; transform.basis.elements[2].x = m44.columns[0][2]; transform.basis.elements[0].y = m44.columns[1][0]; transform.basis.elements[1].y = m44.columns[1][1]; transform.basis.elements[2].y = m44.columns[1][2]; - } else if (orientation == UIDeviceOrientationPortrait) { + } else if (orientation == UIInterfaceOrientationPortrait) { transform.basis.elements[0].x = m44.columns[1][0]; transform.basis.elements[1].x = m44.columns[1][1]; transform.basis.elements[2].x = m44.columns[1][2]; transform.basis.elements[0].y = -m44.columns[0][0]; transform.basis.elements[1].y = -m44.columns[0][1]; transform.basis.elements[2].y = -m44.columns[0][2]; - } else if (orientation == UIDeviceOrientationLandscapeRight) { + } else if (orientation == UIInterfaceOrientationLandscapeRight) { transform.basis.elements[0].x = -m44.columns[0][0]; transform.basis.elements[1].x = -m44.columns[0][1]; transform.basis.elements[2].x = -m44.columns[0][2]; transform.basis.elements[0].y = -m44.columns[1][0]; transform.basis.elements[1].y = -m44.columns[1][1]; transform.basis.elements[2].y = -m44.columns[1][2]; - } else if (orientation == UIDeviceOrientationPortraitUpsideDown) { + } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) { // this may not be correct transform.basis.elements[0].x = m44.columns[1][0]; transform.basis.elements[1].x = m44.columns[1][1]; diff --git a/modules/assimp/SCsub b/modules/assimp/SCsub index 0da7e432e2..d8ef866bec 100644 --- a/modules/assimp/SCsub +++ b/modules/assimp/SCsub @@ -17,75 +17,70 @@ env_assimp.Prepend(CPPPATH=['#thirdparty/zlib/']) env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/contrib/openddlparser/include']) env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/contrib/rapidjson/include']) env_assimp.Prepend(CPPPATH=['.']) -#env_assimp.Append(CPPFLAGS=['-DASSIMP_DOUBLE_PRECISION']) # TODO default to what godot is compiled with for future double support -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_BOOST_WORKAROUND']) -env_assimp.Append(CPPFLAGS=['-DOPENDDLPARSER_BUILD']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OWN_ZLIB']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_EXPORT']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_X_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_AMF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_3DS_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD3_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD5_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MDL_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD2_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_PLY_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_ASE_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OBJ_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_HMP_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_SMD_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MDC_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD5_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_STL_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_LWO_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_DXF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_NFF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_RAW_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_SIB_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OFF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_AC_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_BVH_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_IRRMESH_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_IRR_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_Q3D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_B3D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_COLLADA_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_TERRAGEN_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_CSM_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_3D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_LWS_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OGRE_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OPENGEX_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MS3D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_COB_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_BLEND_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_Q3BSP_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_NDO_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_STEP_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_IFC_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_XGL_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_ASSBIN_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_GLTF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_C4D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_3MF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_X3D_IMPORTER']) +#env_assimp.Append(CPPDEFINES=['ASSIMP_DOUBLE_PRECISION']) # TODO default to what godot is compiled with for future double support +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_BOOST_WORKAROUND']) +env_assimp.Append(CPPDEFINES=['OPENDDLPARSER_BUILD']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_OWN_ZLIB']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_EXPORT']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_X_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_AMF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_3DS_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MD3_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MD5_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MDL_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MD2_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_PLY_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_ASE_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_OBJ_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_HMP_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_SMD_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MDC_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MD5_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_STL_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_LWO_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_DXF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_NFF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_RAW_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_SIB_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_OFF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_AC_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_BVH_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_IRRMESH_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_IRR_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_Q3D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_B3D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_COLLADA_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_TERRAGEN_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_CSM_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_3D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_LWS_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_OGRE_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_OPENGEX_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MS3D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_COB_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_BLEND_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_Q3BSP_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_NDO_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_STEP_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_IFC_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_XGL_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_ASSBIN_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_GLTF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_C4D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_3MF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_X3D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_SINGLETHREADED']) - -if (not env.msvc): - env_assimp.Append(CXXFLAGS=['-std=c++11']) -elif (env.msvc == False and env['platform'] == 'windows'): - env_assimp.Append(LDFLAGS=['-pthread']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_SINGLETHREADED']) if(env['platform'] == 'windows'): - env_assimp.Append(CPPFLAGS=['-DPLATFORM_WINDOWS']) - env_assimp.Append(CPPFLAGS=['-DPLATFORM=WINDOWS']) + env_assimp.Append(CPPDEFINES=['PLATFORM_WINDOWS']) + env_assimp.Append(CPPDEFINES=[('PLATFORM', 'WINDOWS')]) elif(env['platform'] == 'x11'): - env_assimp.Append(CPPFLAGS=['-DPLATFORM_LINUX']) - env_assimp.Append(CPPFLAGS=['-DPLATFORM=LINUX']) + env_assimp.Append(CPPDEFINES=['PLATFORM_LINUX']) + env_assimp.Append(CPPDEFINES=[('PLATFORM', 'LINUX')]) elif(env['platform'] == 'osx'): - env_assimp.Append(CPPFLAGS=['-DPLATFORM_DARWIN']) - env_assimp.Append(CPPFLAGS=['-DPLATFORM=DARWIN']) + env_assimp.Append(CPPDEFINES=['PLATFORM_DARWIN']) + env_assimp.Append(CPPDEFINES=[('PLATFORM', 'DARWIN')]) env_thirdparty = env_assimp.Clone() env_thirdparty.disable_warnings() diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp index 093e2f3006..65fa8b6459 100644 --- a/modules/assimp/editor_scene_importer_assimp.cpp +++ b/modules/assimp/editor_scene_importer_assimp.cpp @@ -854,7 +854,7 @@ Ref<Material> EditorSceneImporterAssimp::_generate_material_from_index(ImportSta if (found) { Ref<Texture> texture = _load_texture(state, path); - if (texture != NULL) { + if (texture.is_valid()) { _set_texture_mapping_mode(map_mode, texture); mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); @@ -1718,7 +1718,7 @@ void EditorSceneImporterAssimp::_find_texture_path(const String &p_path, _Direct } } -String EditorSceneImporterAssimp::_assimp_get_string(const aiString p_string) const { +String EditorSceneImporterAssimp::_assimp_get_string(const aiString &p_string) const { //convert an assimp String to a Godot String String name; name.parse_utf8(p_string.C_Str() /*,p_string.length*/); @@ -1733,7 +1733,7 @@ String EditorSceneImporterAssimp::_assimp_get_string(const aiString p_string) co return name; } -String EditorSceneImporterAssimp::_assimp_anim_string_to_string(const aiString p_string) const { +String EditorSceneImporterAssimp::_assimp_anim_string_to_string(const aiString &p_string) const { String name; name.parse_utf8(p_string.C_Str() /*,p_string.length*/); @@ -1745,7 +1745,7 @@ String EditorSceneImporterAssimp::_assimp_anim_string_to_string(const aiString p return name; } -String EditorSceneImporterAssimp::_assimp_raw_string_to_string(const aiString p_string) const { +String EditorSceneImporterAssimp::_assimp_raw_string_to_string(const aiString &p_string) const { String name; name.parse_utf8(p_string.C_Str() /*,p_string.length*/); return name; diff --git a/modules/assimp/editor_scene_importer_assimp.h b/modules/assimp/editor_scene_importer_assimp.h index 598845236e..7a30816e3b 100644 --- a/modules/assimp/editor_scene_importer_assimp.h +++ b/modules/assimp/editor_scene_importer_assimp.h @@ -178,7 +178,7 @@ private: }; const Transform _assimp_matrix_transform(const aiMatrix4x4 p_matrix); - String _assimp_get_string(const aiString p_string) const; + String _assimp_get_string(const aiString &p_string) const; Transform _get_global_assimp_node_transform(const aiNode *p_current_node); void _calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w); @@ -200,8 +200,8 @@ private: Spatial *_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights); - String _assimp_anim_string_to_string(const aiString p_string) const; - String _assimp_raw_string_to_string(const aiString p_string) const; + 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); diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index a7e8dec11e..88732dff33 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -47,9 +47,6 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, size_t height = (size_t)p_header.bmp_info_header.bmp_height; size_t bits_per_pixel = (size_t)p_header.bmp_info_header.bmp_bit_count; - if (p_header.bmp_info_header.bmp_compression != BI_RGB) { - err = FAILED; - } // Check whether we can load it if (bits_per_pixel == 1) { @@ -238,11 +235,16 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f, bmp_header.bmp_info_header.bmp_colors_used = f->get_32(); bmp_header.bmp_info_header.bmp_important_colors = f->get_32(); - // Compressed bitmaps not supported, stop parsing - if (bmp_header.bmp_info_header.bmp_compression != BI_RGB) { - ERR_EXPLAIN("Unsupported bmp file: " + f->get_path()); - f->close(); - ERR_FAIL_V(ERR_UNAVAILABLE); + switch (bmp_header.bmp_info_header.bmp_compression) { + case BI_RLE8: + case BI_RLE4: + case BI_CMYKRLE8: + case BI_CMYKRLE4: { + // Stop parsing + ERR_EXPLAIN("Compressed BMP files are not supported: " + f->get_path()); + f->close(); + ERR_FAIL_V(ERR_UNAVAILABLE); + } break; } // Don't rely on sizeof(bmp_file_header) as structure padding // adds 2 bytes offset leading to misaligned color table reading @@ -257,8 +259,8 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f, if (bmp_header.bmp_info_header.bmp_bit_count <= 8) { // Support 256 colors max color_table_size = 1 << bmp_header.bmp_info_header.bmp_bit_count; + ERR_FAIL_COND_V(color_table_size == 0, ERR_BUG); } - ERR_FAIL_COND_V(color_table_size == 0, ERR_BUG); PoolVector<uint8_t> bmp_color_table; // Color table is usually 4 bytes per color -> [B][G][R][0] diff --git a/modules/bmp/image_loader_bmp.h b/modules/bmp/image_loader_bmp.h index 0082cf778a..2debb19a1c 100644 --- a/modules/bmp/image_loader_bmp.h +++ b/modules/bmp/image_loader_bmp.h @@ -42,15 +42,15 @@ protected: enum bmp_compression_s { BI_RGB = 0x00, - BI_RLE8 = 0x01, - BI_RLE4 = 0x02, + BI_RLE8 = 0x01, // compressed + BI_RLE4 = 0x02, // compressed BI_BITFIELDS = 0x03, BI_JPEG = 0x04, BI_PNG = 0x05, BI_ALPHABITFIELDS = 0x06, BI_CMYK = 0x0b, - BI_CMYKRLE8 = 0x0c, - BI_CMYKRLE4 = 0x0d + BI_CMYKRLE8 = 0x0c, // compressed + BI_CMYKRLE4 = 0x0d // compressed }; struct bmp_header_s { diff --git a/modules/bullet/SCsub b/modules/bullet/SCsub index 2fe7a1b4c0..ecc8a9b481 100644 --- a/modules/bullet/SCsub +++ b/modules/bullet/SCsub @@ -192,7 +192,7 @@ if env['builtin_bullet']: else: env_bullet.Prepend(CPPPATH=[thirdparty_dir]) # if env['target'] == "debug" or env['target'] == "release_debug": - # env_bullet.Append(CPPFLAGS=['-DBT_DEBUG']) + # env_bullet.Append(CPPDEFINES=['BT_DEBUG']) env_thirdparty = env_bullet.Clone() env_thirdparty.disable_warnings() diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index 085cce9733..8d21b25b20 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -728,12 +728,12 @@ bool RigidBodyBullet::is_axis_locked(PhysicsServer::BodyAxis p_axis) const { void RigidBodyBullet::reload_axis_lock() { - btBody->setLinearFactor(btVector3(!is_axis_locked(PhysicsServer::BODY_AXIS_LINEAR_X), !is_axis_locked(PhysicsServer::BODY_AXIS_LINEAR_Y), !is_axis_locked(PhysicsServer::BODY_AXIS_LINEAR_Z))); + btBody->setLinearFactor(btVector3(float(!is_axis_locked(PhysicsServer::BODY_AXIS_LINEAR_X)), float(!is_axis_locked(PhysicsServer::BODY_AXIS_LINEAR_Y)), float(!is_axis_locked(PhysicsServer::BODY_AXIS_LINEAR_Z)))); if (PhysicsServer::BODY_MODE_CHARACTER == mode) { /// When character angular is always locked btBody->setAngularFactor(btVector3(0., 0., 0.)); } else { - btBody->setAngularFactor(btVector3(!is_axis_locked(PhysicsServer::BODY_AXIS_ANGULAR_X), !is_axis_locked(PhysicsServer::BODY_AXIS_ANGULAR_Y), !is_axis_locked(PhysicsServer::BODY_AXIS_ANGULAR_Z))); + btBody->setAngularFactor(btVector3(float(!is_axis_locked(PhysicsServer::BODY_AXIS_ANGULAR_X)), float(!is_axis_locked(PhysicsServer::BODY_AXIS_ANGULAR_Y)), float(!is_axis_locked(PhysicsServer::BODY_AXIS_ANGULAR_Z)))); } } diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h index 2c9bdb8b0b..f63148092f 100644 --- a/modules/bullet/rigid_body_bullet.h +++ b/modules/bullet/rigid_body_bullet.h @@ -305,7 +305,7 @@ public: void reload_axis_lock(); /// Doc: - /// http://www.bulletphysics.org/mediawiki-1.5.8/index.php?title=Anti_tunneling_by_Motion_Clamping + /// https://web.archive.org/web/20180404091446/http://www.bulletphysics.org/mediawiki-1.5.8/index.php/Anti_tunneling_by_Motion_Clamping void set_continuous_collision_detection(bool p_enable); bool is_continuous_collision_detection_enabled() const; diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index 738b415d16..9d632aaf83 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -581,6 +581,10 @@ void SpaceBullet::create_empty_world(bool p_create_soft_world) { } else { world_mem = malloc(sizeof(btDiscreteDynamicsWorld)); } + if (!world_mem) { + ERR_EXPLAIN("Out of memory"); + ERR_FAIL(); + } if (p_create_soft_world) { collisionConfiguration = bulletnew(GodotSoftCollisionConfiguration(static_cast<btDiscreteDynamicsWorld *>(world_mem))); diff --git a/modules/csg/csg_gizmos.cpp b/modules/csg/csg_gizmos.cpp index d4069b901f..e6bfa5525d 100644 --- a/modules/csg/csg_gizmos.cpp +++ b/modules/csg/csg_gizmos.cpp @@ -34,8 +34,18 @@ CSGShapeSpatialGizmoPlugin::CSGShapeSpatialGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/csg", Color(0.2, 0.5, 1, 0.1)); - create_material("shape_material", gizmo_color); + Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15)); + create_material("shape_union_material", gizmo_color); + create_material("shape_union_solid_material", gizmo_color); + gizmo_color.invert(); + create_material("shape_subtraction_material", gizmo_color); + create_material("shape_subtraction_solid_material", gizmo_color); + gizmo_color.r = 0.95; + gizmo_color.g = 0.95; + gizmo_color.b = 0.95; + create_material("shape_intersection_material", gizmo_color); + create_material("shape_intersection_solid_material", gizmo_color); + create_handle_material("handles"); } @@ -120,6 +130,10 @@ void CSGShapeSpatialGizmoPlugin::set_handle(EditorSpatialGizmo *p_gizmo, int p_i Vector3 ra, rb; Geometry::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); float d = ra.x; + if (SpatialEditor::get_singleton()->is_snap_enabled()) { + d = Math::stepify(d, SpatialEditor::get_singleton()->get_translate_snap()); + } + if (d < 0.001) d = 0.001; @@ -135,6 +149,10 @@ void CSGShapeSpatialGizmoPlugin::set_handle(EditorSpatialGizmo *p_gizmo, int p_i Vector3 ra, rb; Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); float d = ra[p_idx]; + if (SpatialEditor::get_singleton()->is_snap_enabled()) { + d = Math::stepify(d, SpatialEditor::get_singleton()->get_translate_snap()); + } + if (d < 0.001) d = 0.001; @@ -154,6 +172,9 @@ void CSGShapeSpatialGizmoPlugin::set_handle(EditorSpatialGizmo *p_gizmo, int p_i Vector3 ra, rb; Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); float d = axis.dot(ra); + if (SpatialEditor::get_singleton()->is_snap_enabled()) { + d = Math::stepify(d, SpatialEditor::get_singleton()->get_translate_snap()); + } if (d < 0.001) d = 0.001; @@ -173,6 +194,9 @@ void CSGShapeSpatialGizmoPlugin::set_handle(EditorSpatialGizmo *p_gizmo, int p_i Vector3 ra, rb; Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); float d = axis.dot(ra); + if (SpatialEditor::get_singleton()->is_snap_enabled()) { + d = Math::stepify(d, SpatialEditor::get_singleton()->get_translate_snap()); + } if (d < 0.001) d = 0.001; @@ -297,7 +321,19 @@ void CSGShapeSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { p_gizmo->clear(); - Ref<Material> material = get_material("shape_material", p_gizmo); + Ref<Material> material; + switch (cs->get_operation()) { + case CSGShape::OPERATION_UNION: + material = get_material("shape_union_material", p_gizmo); + break; + case CSGShape::OPERATION_INTERSECTION: + material = get_material("shape_intersection_material", p_gizmo); + break; + case CSGShape::OPERATION_SUBTRACTION: + material = get_material("shape_subtraction_material", p_gizmo); + break; + } + Ref<Material> handles_material = get_material("handles"); PoolVector<Vector3> faces = cs->get_brush_faces(); @@ -320,6 +356,30 @@ void CSGShapeSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { p_gizmo->add_lines(lines, material); p_gizmo->add_collision_segments(lines); + if (p_gizmo->is_selected()) { + // Draw a translucent representation of the CSG node + Ref<ArrayMesh> mesh = memnew(ArrayMesh); + Array array; + array.resize(Mesh::ARRAY_MAX); + array[Mesh::ARRAY_VERTEX] = faces; + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array); + + Ref<Material> solid_material; + switch (cs->get_operation()) { + case CSGShape::OPERATION_UNION: + solid_material = get_material("shape_union_solid_material", p_gizmo); + break; + case CSGShape::OPERATION_INTERSECTION: + solid_material = get_material("shape_intersection_solid_material", p_gizmo); + break; + case CSGShape::OPERATION_SUBTRACTION: + solid_material = get_material("shape_subtraction_solid_material", p_gizmo); + break; + } + + p_gizmo->add_mesh(mesh, false, RID(), solid_material); + } + if (Object::cast_to<CSGSphere>(cs)) { CSGSphere *s = Object::cast_to<CSGSphere>(cs); diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index a496a214fd..23725c4960 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -436,10 +436,10 @@ void CSGShape::_update_shape() { } // unset write access - surfaces.write[i].verticesw = PoolVector<Vector3>::Write(); - surfaces.write[i].normalsw = PoolVector<Vector3>::Write(); - surfaces.write[i].uvsw = PoolVector<Vector2>::Write(); - surfaces.write[i].tansw = PoolVector<float>::Write(); + surfaces.write[i].verticesw.release(); + surfaces.write[i].normalsw.release(); + surfaces.write[i].uvsw.release(); + surfaces.write[i].tansw.release(); if (surfaces[i].last_added == 0) continue; @@ -557,6 +557,7 @@ void CSGShape::set_operation(Operation p_operation) { operation = p_operation; _make_dirty(); + update_gizmo(); } CSGShape::Operation CSGShape::get_operation() const { diff --git a/modules/csg/doc_classes/CSGBox.xml b/modules/csg/doc_classes/CSGBox.xml index d100c01205..14f5a1952e 100644 --- a/modules/csg/doc_classes/CSGBox.xml +++ b/modules/csg/doc_classes/CSGBox.xml @@ -17,7 +17,7 @@ <member name="height" type="float" setter="set_height" getter="get_height" default="2.0"> Height of the box measured from the center of the box. </member> - <member name="material" type="Material" setter="set_material" getter="get_material" default="null"> + <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the box. </member> <member name="width" type="float" setter="set_width" getter="get_width" default="2.0"> diff --git a/modules/csg/doc_classes/CSGCylinder.xml b/modules/csg/doc_classes/CSGCylinder.xml index 643eb7c7f4..9fc0281887 100644 --- a/modules/csg/doc_classes/CSGCylinder.xml +++ b/modules/csg/doc_classes/CSGCylinder.xml @@ -17,7 +17,7 @@ <member name="height" type="float" setter="set_height" getter="get_height" default="1.0"> The height of the cylinder. </member> - <member name="material" type="Material" setter="set_material" getter="get_material" default="null"> + <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the cylinder. </member> <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0"> diff --git a/modules/csg/doc_classes/CSGMesh.xml b/modules/csg/doc_classes/CSGMesh.xml index daa08decb6..afe0bc262d 100644 --- a/modules/csg/doc_classes/CSGMesh.xml +++ b/modules/csg/doc_classes/CSGMesh.xml @@ -11,9 +11,9 @@ <methods> </methods> <members> - <member name="material" type="Material" setter="set_material" getter="get_material" default="null"> + <member name="material" type="Material" setter="set_material" getter="get_material"> </member> - <member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh" default="null"> + <member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh"> The mesh resource to use as a CSG shape. </member> </members> diff --git a/modules/csg/doc_classes/CSGPolygon.xml b/modules/csg/doc_classes/CSGPolygon.xml index 48f5d730cc..0ecee92cd5 100644 --- a/modules/csg/doc_classes/CSGPolygon.xml +++ b/modules/csg/doc_classes/CSGPolygon.xml @@ -14,7 +14,7 @@ <member name="depth" type="float" setter="set_depth" getter="get_depth" default="1.0"> Extrusion depth when [member mode] is [constant MODE_DEPTH]. </member> - <member name="material" type="Material" setter="set_material" getter="get_material" default="null"> + <member name="material" type="Material" setter="set_material" getter="get_material"> Material to use for the resulting mesh. </member> <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="CSGPolygon.Mode" default="0"> diff --git a/modules/csg/doc_classes/CSGShape.xml b/modules/csg/doc_classes/CSGShape.xml index 91f54f8246..755d8df67e 100644 --- a/modules/csg/doc_classes/CSGShape.xml +++ b/modules/csg/doc_classes/CSGShape.xml @@ -92,7 +92,7 @@ Only intersecting geometry remains, the rest is removed. </constant> <constant name="OPERATION_SUBTRACTION" value="2" enum="Operation"> - The second shape is susbtracted from the first, leaving a dent with it's shape. + The second shape is subtracted from the first, leaving a dent with its shape. </constant> </constants> </class> diff --git a/modules/csg/doc_classes/CSGSphere.xml b/modules/csg/doc_classes/CSGSphere.xml index 0a62644179..714e725acb 100644 --- a/modules/csg/doc_classes/CSGSphere.xml +++ b/modules/csg/doc_classes/CSGSphere.xml @@ -11,7 +11,7 @@ <methods> </methods> <members> - <member name="material" type="Material" setter="set_material" getter="get_material" default="null"> + <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the sphere. </member> <member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" default="12"> diff --git a/modules/csg/doc_classes/CSGTorus.xml b/modules/csg/doc_classes/CSGTorus.xml index 156fb185e7..5dc6bb8380 100644 --- a/modules/csg/doc_classes/CSGTorus.xml +++ b/modules/csg/doc_classes/CSGTorus.xml @@ -14,7 +14,7 @@ <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="2.0"> The inner radius of the torus. </member> - <member name="material" type="Material" setter="set_material" getter="get_material" default="null"> + <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the torus. </member> <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="3.0"> diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index 024e9ffc3b..17b0038780 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -388,8 +388,8 @@ void image_decompress_cvtt(Image *p_image) { h >>= 1; } - rb = PoolVector<uint8_t>::Read(); - wb = PoolVector<uint8_t>::Write(); + rb.release(); + wb.release(); p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp index 197b41b30c..4628bd9a5b 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -251,7 +251,6 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, src_data.resize(size); PoolVector<uint8_t>::Write wb = src_data.write(); f->get_buffer(wb.ptr(), size); - wb = PoolVector<uint8_t>::Write(); } else if (info.palette) { @@ -296,8 +295,6 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, if (colsize == 4) wb[dst_ofs + 3] = palette[src_ofs + 3]; } - - wb = PoolVector<uint8_t>::Write(); } else { //uncompressed generic... @@ -444,8 +441,6 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, default: { } } - - wb = PoolVector<uint8_t>::Write(); } Ref<Image> img = memnew(Image(width, height, mipmaps - 1, info.format, src_data)); diff --git a/modules/enet/SCsub b/modules/enet/SCsub index 78d8d43414..485c33b1a8 100644 --- a/modules/enet/SCsub +++ b/modules/enet/SCsub @@ -22,7 +22,7 @@ if env['builtin_enet']: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_enet.Prepend(CPPPATH=[thirdparty_dir]) - env_enet.Append(CPPFLAGS=["-DGODOT_ENET"]) + env_enet.Append(CPPDEFINES=["GODOT_ENET"]) env_thirdparty = env_enet.Clone() env_thirdparty.disable_warnings() diff --git a/modules/etc/SCsub b/modules/etc/SCsub index 532b97b006..1742d3534f 100644 --- a/modules/etc/SCsub +++ b/modules/etc/SCsub @@ -29,10 +29,6 @@ thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_etc.Prepend(CPPPATH=[thirdparty_dir]) -# upstream uses c++11 -if not env.msvc: - env_etc.Append(CXXFLAGS="-std=c++11") - env_thirdparty = env_etc.Clone() env_thirdparty.disable_warnings() env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) diff --git a/modules/etc/texture_loader_pkm.cpp b/modules/etc/texture_loader_pkm.cpp index f302834222..ff925480b8 100644 --- a/modules/etc/texture_loader_pkm.cpp +++ b/modules/etc/texture_loader_pkm.cpp @@ -80,7 +80,7 @@ RES ResourceFormatPKM::load(const String &p_path, const String &p_original_path, src_data.resize(size); PoolVector<uint8_t>::Write wb = src_data.write(); f->get_buffer(wb.ptr(), size); - wb = PoolVector<uint8_t>::Write(); + wb.release(); int mipmaps = h.format; int width = h.origWidth; diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub index 0ea581220e..b47377cbc4 100644 --- a/modules/freetype/SCsub +++ b/modules/freetype/SCsub @@ -65,16 +65,16 @@ if env['builtin_freetype']: # Also needed in main env for scene/ env.Prepend(CPPPATH=[thirdparty_dir + "/include"]) - env_freetype.Append(CPPFLAGS=['-DFT2_BUILD_LIBRARY', '-DFT_CONFIG_OPTION_USE_PNG']) + env_freetype.Append(CPPDEFINES=['FT2_BUILD_LIBRARY', 'FT_CONFIG_OPTION_USE_PNG']) if (env['target'] != 'release'): - env_freetype.Append(CPPFLAGS=['-DZLIB_DEBUG']) + env_freetype.Append(CPPDEFINES=['ZLIB_DEBUG']) # Also requires libpng headers if env['builtin_libpng']: env_freetype.Prepend(CPPPATH=["#thirdparty/libpng"]) sfnt = thirdparty_dir + 'src/sfnt/sfnt.c' - # Must be done after all CPPFLAGS are being set so we can copy them. + # Must be done after all CPPDEFINES are being set so we can copy them. if env['platform'] == 'javascript': # Forcibly undefine this macro so SIMD is not used in this file, # since currently unsupported in WASM @@ -103,4 +103,4 @@ if env['builtin_freetype']: # Godot source files env_freetype.add_source_files(env.modules_sources, "*.cpp") # Used in scene/, needs to be in main env -env.Append(CPPFLAGS=['-DFREETYPE_ENABLED']) +env.Append(CPPDEFINES=['FREETYPE_ENABLED']) diff --git a/modules/gdnative/doc_classes/GDNative.xml b/modules/gdnative/doc_classes/GDNative.xml index 95ed1fc048..8750ddc56d 100644 --- a/modules/gdnative/doc_classes/GDNative.xml +++ b/modules/gdnative/doc_classes/GDNative.xml @@ -33,7 +33,7 @@ </method> </methods> <members> - <member name="library" type="GDNativeLibrary" setter="set_library" getter="get_library" default="null"> + <member name="library" type="GDNativeLibrary" setter="set_library" getter="get_library"> </member> </members> <constants> diff --git a/modules/gdnative/doc_classes/NativeScript.xml b/modules/gdnative/doc_classes/NativeScript.xml index 460471386d..e34e209374 100644 --- a/modules/gdnative/doc_classes/NativeScript.xml +++ b/modules/gdnative/doc_classes/NativeScript.xml @@ -53,7 +53,7 @@ <members> <member name="class_name" type="String" setter="set_class_name" getter="get_class_name" default=""""> </member> - <member name="library" type="GDNativeLibrary" setter="set_library" getter="get_library" default="null"> + <member name="library" type="GDNativeLibrary" setter="set_library" getter="get_library"> </member> <member name="script_class_icon_path" type="String" setter="set_script_class_icon_path" getter="get_script_class_icon_path" default=""""> </member> diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index a27935bfe2..4eb9a2a0a3 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -48,7 +48,7 @@ static const bool default_reloadable = true; // Defined in gdnative_api_struct.gen.cpp extern const godot_gdnative_core_api_struct api_struct; -Map<String, Vector<Ref<GDNative> > > *GDNativeLibrary::loaded_libraries = NULL; +Map<String, Vector<Ref<GDNative> > > GDNativeLibrary::loaded_libraries; GDNativeLibrary::GDNativeLibrary() { config_file.instance(); @@ -57,10 +57,6 @@ GDNativeLibrary::GDNativeLibrary() { load_once = default_load_once; singleton = default_singleton; reloadable = default_reloadable; - - if (GDNativeLibrary::loaded_libraries == NULL) { - GDNativeLibrary::loaded_libraries = memnew((Map<String, Vector<Ref<GDNative> > >)); - } } GDNativeLibrary::~GDNativeLibrary() { @@ -318,10 +314,10 @@ bool GDNative::initialize() { #endif if (library->should_load_once()) { - if (GDNativeLibrary::loaded_libraries->has(lib_path)) { + if (GDNativeLibrary::loaded_libraries.has(lib_path)) { // already loaded. Don't load again. // copy some of the stuff instead - this->native_handle = (*GDNativeLibrary::loaded_libraries)[lib_path][0]->native_handle; + this->native_handle = GDNativeLibrary::loaded_libraries[lib_path][0]->native_handle; initialized = true; return true; } @@ -377,11 +373,11 @@ bool GDNative::initialize() { initialized = true; - if (library->should_load_once() && !GDNativeLibrary::loaded_libraries->has(lib_path)) { + if (library->should_load_once() && !GDNativeLibrary::loaded_libraries.has(lib_path)) { Vector<Ref<GDNative> > gdnatives; gdnatives.resize(1); gdnatives.write[0] = Ref<GDNative>(this); - GDNativeLibrary::loaded_libraries->insert(lib_path, gdnatives); + GDNativeLibrary::loaded_libraries.insert(lib_path, gdnatives); } return true; @@ -395,7 +391,7 @@ bool GDNative::terminate() { } if (library->should_load_once()) { - Vector<Ref<GDNative> > *gdnatives = &(*GDNativeLibrary::loaded_libraries)[library->get_current_library_path()]; + Vector<Ref<GDNative> > *gdnatives = &GDNativeLibrary::loaded_libraries[library->get_current_library_path()]; if (gdnatives->size() > 1) { // there are other GDNative's still using this library, so we actually don't terminate gdnatives->erase(Ref<GDNative>(this)); @@ -405,7 +401,7 @@ bool GDNative::terminate() { // we're the last one, terminate! gdnatives->clear(); // whew this looks scary, but all it does is remove the entry completely - GDNativeLibrary::loaded_libraries->erase(GDNativeLibrary::loaded_libraries->find(library->get_current_library_path())); + GDNativeLibrary::loaded_libraries.erase(GDNativeLibrary::loaded_libraries.find(library->get_current_library_path())); } } diff --git a/modules/gdnative/gdnative.h b/modules/gdnative/gdnative.h index 005d1d2bff..408af26753 100644 --- a/modules/gdnative/gdnative.h +++ b/modules/gdnative/gdnative.h @@ -47,7 +47,7 @@ class GDNative; class GDNativeLibrary : public Resource { GDCLASS(GDNativeLibrary, Resource); - static Map<String, Vector<Ref<GDNative> > > *loaded_libraries; + static Map<String, Vector<Ref<GDNative> > > loaded_libraries; friend class GDNativeLibraryResourceLoader; friend class GDNative; diff --git a/modules/gdnative/gdnative/string.cpp b/modules/gdnative/gdnative/string.cpp index 913c57eb56..9086121940 100644 --- a/modules/gdnative/gdnative/string.cpp +++ b/modules/gdnative/gdnative/string.cpp @@ -186,6 +186,20 @@ godot_bool GDAPI godot_string_ends_with(const godot_string *p_self, const godot_ return self->ends_with(*string); } +godot_int GDAPI godot_string_count(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to) { + const String *self = (const String *)p_self; + String *what = (String *)&p_what; + + return self->count(*what, p_from, p_to); +} + +godot_int GDAPI godot_string_countn(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to) { + const String *self = (const String *)p_self; + String *what = (String *)&p_what; + + return self->countn(*what, p_from, p_to); +} + godot_int GDAPI godot_string_find(const godot_string *p_self, godot_string p_what) { const String *self = (const String *)p_self; String *what = (String *)&p_what; diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 6c12ee6534..7372990d5f 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -44,6 +44,26 @@ ["const godot_vector2 *", "p_to"], ["const godot_real", "p_delta"] ] + }, + { + "name": "godot_string_count", + "return_type": "godot_int", + "arguments": [ + ["const godot_string *", "p_self"], + ["godot_string", "p_what"], + ["godot_int", "p_from"], + ["godot_int", "p_to"] + ] + }, + { + "name": "godot_string_countn", + "return_type": "godot_int", + "arguments": [ + ["const godot_string *", "p_self"], + ["godot_string", "p_what"], + ["godot_int", "p_from"], + ["godot_int", "p_to"] + ] } ] }, diff --git a/modules/gdnative/include/gdnative/string.h b/modules/gdnative/include/gdnative/string.h index f045ac9d58..7500d70f20 100644 --- a/modules/gdnative/include/gdnative/string.h +++ b/modules/gdnative/include/gdnative/string.h @@ -102,6 +102,8 @@ godot_bool GDAPI godot_string_begins_with_char_array(const godot_string *p_self, godot_array GDAPI godot_string_bigrams(const godot_string *p_self); godot_string GDAPI godot_string_chr(wchar_t p_character); godot_bool GDAPI godot_string_ends_with(const godot_string *p_self, const godot_string *p_string); +godot_int GDAPI godot_string_count(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to); +godot_int GDAPI godot_string_countn(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to); godot_int GDAPI godot_string_find(const godot_string *p_self, godot_string p_what); godot_int GDAPI godot_string_find_from(const godot_string *p_self, godot_string p_what, godot_int p_from); godot_int GDAPI godot_string_findmk(const godot_string *p_self, const godot_array *p_keys); diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index f3b9f7fb31..7f52f5736c 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -56,7 +56,7 @@ typedef enum { GODOT_PROPERTY_HINT_ENUM, ///< hint_text= "val1,val2,val3,etc" GODOT_PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) GODOT_PROPERTY_HINT_LENGTH, ///< hint_text= "length" (as integer) - GODOT_PROPERTY_HINT_SPRITE_FRAME, + GODOT_PROPERTY_HINT_SPRITE_FRAME, // FIXME: Obsolete: drop whenever we can break compat GODOT_PROPERTY_HINT_KEY_ACCEL, ///< hint_text= "length" (as integer) GODOT_PROPERTY_HINT_FLAGS, ///< hint_text= "flag1,flag2,etc" (as bit flags) GODOT_PROPERTY_HINT_LAYERS_2D_RENDER, @@ -98,8 +98,8 @@ typedef enum { GODOT_PROPERTY_USAGE_INTERNATIONALIZED = 64, //hint for internationalized strings GODOT_PROPERTY_USAGE_GROUP = 128, //used for grouping props in the editor GODOT_PROPERTY_USAGE_CATEGORY = 256, - GODOT_PROPERTY_USAGE_STORE_IF_NONZERO = 512, //only store if nonzero - GODOT_PROPERTY_USAGE_STORE_IF_NONONE = 1024, //only store if false + GODOT_PROPERTY_USAGE_STORE_IF_NONZERO = 512, // FIXME: Obsolete: drop whenever we can break compat + GODOT_PROPERTY_USAGE_STORE_IF_NONONE = 1024, // FIXME: Obsolete: drop whenever we can break compat GODOT_PROPERTY_USAGE_NO_INSTANCE_STATE = 2048, GODOT_PROPERTY_USAGE_RESTART_IF_CHANGED = 4096, GODOT_PROPERTY_USAGE_SCRIPT_VARIABLE = 8192, diff --git a/modules/gdnative/nativescript/SCsub b/modules/gdnative/nativescript/SCsub index 5841ad5531..92c9d6630d 100644 --- a/modules/gdnative/nativescript/SCsub +++ b/modules/gdnative/nativescript/SCsub @@ -4,7 +4,6 @@ Import('env') Import('env_gdnative') env_gdnative.add_source_files(env.modules_sources, '*.cpp') -env_gdnative.Append(CPPFLAGS=['-DGDAPI_BUILT_IN']) if "platform" in env and env["platform"] in ["x11", "iphone"]: env.Append(LINKFLAGS=["-rdynamic"]) diff --git a/modules/gdnative/pluginscript/pluginscript_language.cpp b/modules/gdnative/pluginscript/pluginscript_language.cpp index 4bbadc62e7..9de073fc8e 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.cpp +++ b/modules/gdnative/pluginscript/pluginscript_language.cpp @@ -159,7 +159,7 @@ String PluginScriptLanguage::make_function(const String &p_class, const String & return String(); } -Error PluginScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<String> *r_options, bool &r_force, String &r_call_hint) { +Error PluginScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { if (_desc.complete_code) { Array options; godot_error tmp = _desc.complete_code( @@ -171,7 +171,8 @@ Error PluginScriptLanguage::complete_code(const String &p_code, const String &p_ &r_force, (godot_string *)&r_call_hint); for (int i = 0; i < options.size(); i++) { - r_options->push_back(String(options[i])); + ScriptCodeCompletionOption option(options[i], ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + r_options->push_back(option); } return (Error)tmp; } diff --git a/modules/gdnative/pluginscript/pluginscript_language.h b/modules/gdnative/pluginscript/pluginscript_language.h index a11e916975..7b3844d0b0 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.h +++ b/modules/gdnative/pluginscript/pluginscript_language.h @@ -81,7 +81,7 @@ public: virtual bool can_inherit_from_file() { return true; } virtual int find_function(const String &p_function, const String &p_code) const; virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const; - virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<String> *r_options, bool &r_force, String &r_call_hint); + virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_force, String &r_call_hint); virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const; virtual void add_global_constant(const StringName &p_variable, const Variant &p_value); diff --git a/modules/gdnative/pluginscript/register_types.cpp b/modules/gdnative/pluginscript/register_types.cpp index b7ab887e11..3b46f33afb 100644 --- a/modules/gdnative/pluginscript/register_types.cpp +++ b/modules/gdnative/pluginscript/register_types.cpp @@ -114,6 +114,8 @@ void unregister_pluginscript_types() { for (List<PluginScriptLanguage *>::Element *e = pluginscript_languages.front(); e; e = e->next()) { PluginScriptLanguage *language = e->get(); ScriptServer::unregister_language(language); + ResourceLoader::remove_resource_format_loader(language->get_resource_loader()); + ResourceSaver::remove_resource_format_saver(language->get_resource_saver()); memdelete(language); } } diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index 55d44ceec8..6ff6262b56 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -100,6 +100,11 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty } String entry_lib_path = config->get_value("entry", key); + if (!entry_lib_path.begins_with("res://")) { + print_line("Skipping export of out-of-project library " + entry_lib_path); + continue; + } + add_shared_object(entry_lib_path, tags); } } @@ -129,6 +134,10 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty Vector<String> dependency_paths = config->get_value("dependencies", key); for (int i = 0; i < dependency_paths.size(); i++) { + if (!dependency_paths[i].begins_with("res://")) { + print_line("Skipping export of out-of-project library " + dependency_paths[i]); + continue; + } add_shared_object(dependency_paths[i], tags); } } diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 3870a5ea7d..f65f2a8935 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -345,45 +345,47 @@ <method name="fmod"> <return type="float"> </return> - <argument index="0" name="x" type="float"> + <argument index="0" name="a" type="float"> </argument> - <argument index="1" name="y" type="float"> + <argument index="1" name="b" type="float"> </argument> <description> - Returns the floating-point remainder of [code]x/y[/code]. + Returns the floating-point remainder of [code]a/b[/code], keeping the sign of [code]a[/code]. [codeblock] # Remainder is 1.5 var remainder = fmod(7, 5.5) [/codeblock] + For the integer remainder operation, use the % operator. </description> </method> <method name="fposmod"> <return type="float"> </return> - <argument index="0" name="x" type="float"> + <argument index="0" name="a" type="float"> </argument> - <argument index="1" name="y" type="float"> + <argument index="1" name="b" type="float"> </argument> <description> - Returns the floating-point remainder of [code]x/y[/code] that wraps equally in positive and negative. + Returns the floating-point modulus of [code]a/b[/code] that wraps equally in positive and negative. [codeblock] - var i = -10 - while i < 0: - prints(i, fposmod(i, 10)) + var i = -6 + while i < 5: + prints(i, fposmod(i, 3)) i += 1 [/codeblock] Produces: [codeblock] - -10 10 - -9 1 - -8 2 - -7 3 - -6 4 - -5 5 - -4 6 - -3 7 - -2 8 - -1 9 + -6 0 + -5 1 + -4 2 + -3 0 + -2 1 + -1 2 + 0 0 + 1 1 + 2 2 + 3 0 + 4 1 [/codeblock] </description> </method> @@ -571,6 +573,29 @@ [/codeblock] </description> </method> + <method name="lerp_angle"> + <return type="float"> + </return> + <argument index="0" name="from" type="float"> + </argument> + <argument index="1" name="to" type="float"> + </argument> + <argument index="2" name="weight" type="float"> + </argument> + <description> + Linearly interpolates between two angles (in radians) by a normalized value. + Similar to [method lerp] but interpolate correctly when the angles wrap around [constant @GDScript.TAU]. + [codeblock] + extends Sprite + var elapsed = 0.0 + func _process(delta): + var min_angle = deg2rad(0.0) + var max_angle = deg2rad(90.0) + rotation = lerp_angle(min_angle, max_angle, elapsed) + elapsed += delta + [/codeblock] + </description> + </method> <method name="linear2db"> <return type="float"> </return> @@ -697,12 +722,43 @@ Converts a 2D point expressed in the polar coordinate system (a distance from the origin [code]r[/code] and an angle [code]th[/code]) to the cartesian coordinate system (X and Y axis). </description> </method> + <method name="posmod"> + <return type="int"> + </return> + <argument index="0" name="a" type="int"> + </argument> + <argument index="1" name="b" type="int"> + </argument> + <description> + Returns the integer modulus of [code]a/b[/code] that wraps equally in positive and negative. + [codeblock] + var i = -6 + while i < 5: + prints(i, posmod(i, 3)) + i += 1 + [/codeblock] + Produces: + [codeblock] + -6 0 + -5 1 + -4 2 + -3 0 + -2 1 + -1 2 + 0 0 + 1 1 + 2 2 + 3 0 + 4 1 + [/codeblock] + </description> + </method> <method name="pow"> <return type="float"> </return> - <argument index="0" name="x" type="float"> + <argument index="0" name="base" type="float"> </argument> - <argument index="1" name="y" type="float"> + <argument index="1" name="exp" type="float"> </argument> <description> Returns the result of [code]x[/code] raised to the power of [code]y[/code]. @@ -1039,7 +1095,7 @@ <argument index="0" name="step" type="float"> </argument> <description> - Returns the position of the first non-zero digit, after the decimal point. + Returns the position of the first non-zero digit, after the decimal point. Note that the maximum return value is 10, which is a design decision in the implementation. [codeblock] # n is 0 n = step_decimals(5) diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 62b65fe96b..963b40529d 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -370,8 +370,8 @@ void GDScriptSyntaxHighlighter::_update_cache() { bool default_theme = text_editor_color_theme == "Default"; bool dark_theme = settings->is_dark_theme(); - function_definition_color = Color::html(default_theme ? "#01e1ff" : dark_theme ? "#01e1ff" : "#00a5ba"); - node_path_color = Color::html(default_theme ? "#64c15a" : dark_theme ? "64c15a" : "#518b4b"); + function_definition_color = default_theme ? Color(0.0, 0.88, 1.0) : dark_theme ? Color(0.0, 0.88, 1.0) : Color(0.0, 0.65, 0.73); + node_path_color = default_theme ? Color(0.39, 0.76, 0.35) : dark_theme ? Color(0.39, 0.76, 0.35) : Color(0.32, 0.55, 0.29); EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color); EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 1756f6eabc..a5ad23c75d 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -458,7 +458,7 @@ public: virtual bool can_inherit_from_file() { return true; } virtual int find_function(const String &p_function, const String &p_code) const; virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const; - virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint); + virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint); #ifdef TOOLS_ENABLED virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result); #endif diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 26b14a6148..9f65a9fff1 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -159,7 +159,11 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & for (int i = 0; i < cl->subclasses.size(); i++) { for (int j = 0; j < cl->subclasses[i]->functions.size(); j++) { - funcs[cl->subclasses[i]->functions[j]->line] = String(cl->subclasses[i]->name) + "." + String(cl->subclasses[i]->functions[j]->name); + funcs[cl->subclasses[i]->functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->functions[j]->name; + } + for (int j = 0; j < cl->subclasses[i]->static_functions.size(); j++) { + + funcs[cl->subclasses[i]->static_functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->static_functions[j]->name; } } @@ -382,8 +386,6 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> String GDScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) { - if (_debug_parse_err_line >= 0) - return ""; return ""; } @@ -509,12 +511,14 @@ struct GDScriptCompletionIdentifier { assigned_expression(NULL) {} }; -static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Set<String> &r_list) { +static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) { const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; for (int i = 0; i < p_dir->get_file_count(); i++) { - r_list.insert(quote_style + p_dir->get_file_path(i) + quote_style); + ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH); + option.insert_text = quote_style + option.display + quote_style; + r_list.insert(option.display, option); } for (int i = 0; i < p_dir->get_subdir_count(); i++) { @@ -1807,14 +1811,15 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio return arghint; } -static void _find_enumeration_candidates(const String p_enum_hint, Set<String> &r_result) { +static void _find_enumeration_candidates(const String p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) { if (p_enum_hint.find(".") == -1) { // Global constant StringName current_enum = p_enum_hint; for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { if (GlobalConstants::get_global_constant_enum(i) == current_enum) { - r_result.insert(GlobalConstants::get_global_constant_name(i)); + ScriptCodeCompletionOption option(GlobalConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM); + r_result.insert(option.display, option); } } } else { @@ -1829,15 +1834,17 @@ static void _find_enumeration_candidates(const String p_enum_hint, Set<String> & ClassDB::get_enum_constants(class_name, enum_name, &enum_constants); for (List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) { String candidate = class_name + "." + E->get(); - r_result.insert(candidate); + ScriptCodeCompletionOption option(candidate, ScriptCodeCompletionOption::KIND_ENUM); + r_result.insert(option.display, option); } } } -static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Set<String> &r_result) { +static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Map<String, ScriptCodeCompletionOption> &r_result) { for (Map<StringName, GDScriptParser::LocalVarNode *>::Element *E = p_context.block->variables.front(); E; E = E->next()) { if (E->get()->line < p_context.line) { - r_result.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_VARIABLE); + r_result.insert(option.display, option); } } if (p_context.block->parent_block) { @@ -1847,40 +1854,47 @@ static void _find_identifiers_in_block(const GDScriptCompletionContext &p_contex } } -static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result); +static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result); -static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Set<String> &r_result) { +static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) { if (!p_parent_only) { if (!p_static && !p_only_functions) { for (int i = 0; i < p_context._class->variables.size(); i++) { - r_result.insert(p_context._class->variables[i].identifier); + ScriptCodeCompletionOption option(p_context._class->variables[i].identifier, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } if (!p_only_functions) { for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_context._class->constant_expressions.front(); E; E = E->next()) { - r_result.insert(E->key()); + ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); } for (int i = 0; i < p_context._class->subclasses.size(); i++) { - r_result.insert(p_context._class->subclasses[i]->name); + ScriptCodeCompletionOption option(p_context._class->subclasses[i]->name, ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); } } for (int i = 0; i < p_context._class->static_functions.size(); i++) { + ScriptCodeCompletionOption option(p_context._class->static_functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); if (p_context._class->static_functions[i]->arguments.size()) { - r_result.insert(p_context._class->static_functions[i]->name.operator String() + "("); + option.insert_text += "("; } else { - r_result.insert(p_context._class->static_functions[i]->name.operator String() + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } if (!p_static) { for (int i = 0; i < p_context._class->functions.size(); i++) { + ScriptCodeCompletionOption option(p_context._class->functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); if (p_context._class->functions[i]->arguments.size()) { - r_result.insert(p_context._class->functions[i]->name.operator String() + "("); + option.insert_text += "("; } else { - r_result.insert(p_context._class->functions[i]->name.operator String() + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } } } @@ -1898,12 +1912,14 @@ static void _find_identifiers_in_class(const GDScriptCompletionContext &p_contex _find_identifiers_in_base(c, base_type, p_only_functions, r_result); } -static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result) { +static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { GDScriptParser::DataType base_type = p_base.type; bool _static = base_type.is_meta_type; if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) { - r_result.insert("new("); + ScriptCodeCompletionOption option("new", ScriptCodeCompletionOption::KIND_FUNCTION); + option.insert_text += "("; + r_result.insert(option.display, option); } while (base_type.has_type) { @@ -1921,26 +1937,31 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context if (script.is_valid()) { if (!_static && !p_only_functions) { for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { - r_result.insert(E->get().operator String()); + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } if (!p_only_functions) { for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) { - r_result.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); } } for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { if (!_static || E->get()->is_static()) { + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); if (E->get()->get_argument_count()) { - r_result.insert(E->key().operator String() + "("); + option.insert_text += "("; } else { - r_result.insert(E->key().operator String() + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } } if (!p_only_functions) { for (const Map<StringName, Ref<GDScript> >::Element *E = script->get_subclasses().front(); E; E = E->next()) { - r_result.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); } } base_type = GDScriptParser::DataType(); @@ -1964,25 +1985,29 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context List<PropertyInfo> members; scr->get_script_property_list(&members); for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - r_result.insert(E->get().name); + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } if (!p_only_functions) { Map<StringName, Variant> constants; scr->get_constants(&constants); for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - r_result.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); } } List<MethodInfo> methods; scr->get_script_method_list(&methods); for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); if (E->get().arguments.size()) { - r_result.insert(E->get().name + "("); + option.insert_text += "("; } else { - r_result.insert(E->get().name + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } Ref<Script> base_script = scr->get_base_script(); @@ -2009,7 +2034,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context List<String> constants; ClassDB::get_integer_constant_list(type, &constants); for (List<String>::Element *E = constants.front(); E; E = E->next()) { - r_result.insert(E->get()); + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); } if (!_static) { @@ -2022,7 +2048,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context if (E->get().name.find("/") != -1) { continue; } - r_result.insert(E->get().name); + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } } @@ -2035,11 +2062,13 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context if (E->get().name.begins_with("_")) { continue; } + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); if (E->get().arguments.size()) { - r_result.insert(E->get().name + "("); + option.insert_text += "("; } else { - r_result.insert(E->get().name + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } } @@ -2058,7 +2087,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { if (String(E->get().name).find("/") == -1) { - r_result.insert(E->get().name); + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } } @@ -2066,11 +2096,13 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context List<MethodInfo> methods; tmp.get_method_list(&methods); for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); if (E->get().arguments.size()) { - r_result.insert(E->get().name + "("); + option.insert_text += "("; } else { - r_result.insert(E->get().name + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } return; @@ -2082,7 +2114,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context } } -static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Set<String> &r_result) { +static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { const GDScriptParser::BlockNode *block = p_context.block; @@ -2091,7 +2123,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p const GDScriptParser::FunctionNode *f = p_context.function; for (int i = 0; i < f->arguments.size(); i++) { - r_result.insert(f->arguments[i].operator String()); + ScriptCodeCompletionOption option(f->arguments[i].operator String(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + r_result.insert(option.display, option); } } @@ -2116,11 +2149,13 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { MethodInfo mi = GDScriptFunctions::get_info(GDScriptFunctions::Function(i)); + ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION); if (mi.arguments.size() || (mi.flags & METHOD_FLAG_VARARG)) { - r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "("); + option.insert_text += "("; } else { - r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } static const char *_type_names[Variant::VARIANT_MAX] = { @@ -2130,7 +2165,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p }; for (int i = 0; i < Variant::VARIANT_MAX; i++) { - r_result.insert(_type_names[i]); + ScriptCodeCompletionOption option(_type_names[i], ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); } static const char *_keywords[] = { @@ -2144,7 +2180,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p const char **kw = _keywords; while (*kw) { - r_result.insert(*kw); + ScriptCodeCompletionOption option(*kw, ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + r_result.insert(option.display, option); kw++; } @@ -2158,7 +2195,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p } String path = ProjectSettings::get_singleton()->get(s); if (path.begins_with("*")) { - r_result.insert(s.get_slice("/", 1)); + ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); } } @@ -2166,16 +2204,18 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p List<StringName> named_scripts; ScriptServer::get_global_class_list(&named_scripts); for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) { - r_result.insert(E->get().operator String()); + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); } // Native classes for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { - r_result.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); } } -static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Set<String> &r_result, String &r_arghint) { +static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Map<String, ScriptCodeCompletionOption> &r_result, String &r_arghint) { Variant base = p_base.value; GDScriptParser::DataType base_type = p_base.type; @@ -2199,7 +2239,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) { for (int i = 0; i < base_type.class_type->_signals.size(); i++) { - r_result.insert(quote_style + base_type.class_type->_signals[i].name.operator String() + quote_style); + ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } @@ -2212,7 +2254,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con List<MethodInfo> signals; gds->get_script_signal_list(&signals); for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - r_result.insert(quote_style + E->get().name + quote_style); + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } Ref<GDScript> base_script = gds->get_base_script(); @@ -2250,7 +2294,8 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con List<String> options; obj->get_argument_options(p_method, p_argidx, &options); for (List<String>::Element *F = options.front(); F; F = F->next()) { - r_result.insert(F->get()); + ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION); + r_result.insert(option.display, option); } } } @@ -2271,7 +2316,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con List<MethodInfo> signals; ClassDB::get_signal_list(class_name, &signals); for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - r_result.insert(quote_style + E->get().name + quote_style); + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } @@ -2286,7 +2333,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con continue; } String name = s.get_slice("/", 1); - r_result.insert(quote_style + "/root/" + name + quote_style); + ScriptCodeCompletionOption option("/root/" + name, ScriptCodeCompletionOption::KIND_NODE_PATH); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } @@ -2300,7 +2349,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con continue; } String name = s.get_slice("/", 1); - r_result.insert(quote_style + name + quote_style); + ScriptCodeCompletionOption option(name, ScriptCodeCompletionOption::KIND_CONSTANT); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } @@ -2333,7 +2384,7 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con } } -static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Set<String> &r_result, bool &r_forced, String &r_arghint) { +static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Map<String, ScriptCodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) { const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; @@ -2451,17 +2502,19 @@ static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDS _find_call_arguments(p_context, ci, function, p_argidx, _static, r_result, r_arghint); if (function == "connect" && p_argidx == 2) { - Set<String> methods; + Map<String, ScriptCodeCompletionOption> methods; _find_identifiers_in_base(p_context, connect_base, true, methods); - for (Set<String>::Element *E = methods.front(); E; E = E->next()) { - r_result.insert(quote_style + E->get().replace("(", "").replace(")", "") + quote_style); + for (Map<String, ScriptCodeCompletionOption>::Element *E = methods.front(); E; E = E->next()) { + ScriptCodeCompletionOption &option = E->value(); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } r_forced = r_result.size() > 0; } -Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) { +Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; @@ -2469,7 +2522,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path parser.parse(p_code, p_path.get_base_dir(), false, p_path, true); r_forced = false; - Set<String> options; + Map<String, ScriptCodeCompletionOption> options; GDScriptCompletionContext context; context._class = parser.get_completion_class(); context.block = parser.get_completion_block(); @@ -2490,7 +2543,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path List<StringName> constants; Variant::get_constants_for_type(parser.get_completion_built_in_constant(), &constants); for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { - options.insert(E->get().operator String()); + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + options.insert(option.display, option); } } break; case GDScriptParser::COMPLETION_PARENT_FUNCTION: { @@ -2515,9 +2569,11 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path r_forced = true; String idopt = opt.unquote(); if (idopt.replace("/", "_").is_valid_identifier()) { - options.insert(idopt); + ScriptCodeCompletionOption option(idopt, ScriptCodeCompletionOption::KIND_NODE_PATH); + options.insert(option.display, option); } else { - options.insert(opt); + ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH); + options.insert(option.display, option); } } } @@ -2532,7 +2588,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path continue; } String name = s.get_slice("/", 1); - options.insert(quote_style + "/root/" + name + quote_style); + ScriptCodeCompletionOption option(quote_style + "/root/" + name + quote_style, ScriptCodeCompletionOption::KIND_NODE_PATH); + options.insert(option.display, option); } } } break; @@ -2655,7 +2712,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path } method_hint += ":"; - options.insert(method_hint); + ScriptCodeCompletionOption option(method_hint, ScriptCodeCompletionOption::KIND_FUNCTION); + options.insert(option.display, option); } } break; case GDScriptParser::COMPLETION_YIELD: { @@ -2673,7 +2731,9 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { for (int i = 0; i < base_type.class_type->_signals.size(); i++) { - options.insert(quote_style + base_type.class_type->_signals[i].name.operator String() + quote_style); + ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL); + option.insert_text = quote_style + option.display + quote_style; + options.insert(option.display, option); } base_type = base_type.class_type->base_type; } break; @@ -2684,7 +2744,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path List<MethodInfo> signals; scr->get_script_signal_list(&signals); for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - options.insert(quote_style + E->get().name + quote_style); + ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL); + options.insert(option.display, option); } Ref<Script> base_script = scr->get_base_script(); if (base_script.is_valid()) { @@ -2711,7 +2772,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path List<MethodInfo> signals; ClassDB::get_signal_list(class_name, &signals); for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - options.insert(quote_style + E->get().name + quote_style); + ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL); + options.insert(option.display, option); } } break; default: { @@ -2748,18 +2810,21 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path c.line = E->value().expression->line; if (_guess_expression_type(c, E->value().expression, constant)) { if (constant.type.has_type && constant.type.is_meta_type) { - options.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } } for (int i = 0; i < clss->subclasses.size(); i++) { if (clss->subclasses[i]->name != StringName()) { - options.insert(clss->subclasses[i]->name.operator String()); + ScriptCodeCompletionOption option(clss->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } clss = clss->owner; for (int i = 0; i < Variant::VARIANT_MAX; i++) { - options.insert(Variant::get_type_name((Variant::Type)i)); + ScriptCodeCompletionOption option(Variant::get_type_name((Variant::Type)i), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } @@ -2773,18 +2838,21 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path if (Engine::get_singleton()->has_singleton(class_name)) { continue; } - options.insert(class_name); + ScriptCodeCompletionOption option(class_name, ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } // Named scripts List<StringName> named_scripts; ScriptServer::get_global_class_list(&named_scripts); for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) { - options.insert(E->get().operator String()); + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } if (parser.get_completion_identifier_is_function()) { - options.insert("void"); + ScriptCodeCompletionOption option("void", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + options.insert(option.display, option); } r_forced = true; } break; @@ -2831,13 +2899,15 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path c2.line = E->value().expression->line; if (_guess_expression_type(c2, E->value().expression, constant)) { if (constant.type.has_type && constant.type.is_meta_type) { - options.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } } for (int i = 0; i < base_type.class_type->subclasses.size(); i++) { if (base_type.class_type->subclasses[i]->name != StringName()) { - options.insert(base_type.class_type->subclasses[i]->name.operator String()); + ScriptCodeCompletionOption option(base_type.class_type->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } @@ -2855,7 +2925,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { Ref<Script> const_scr = E->value(); if (const_scr.is_valid()) { - options.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } Ref<Script> base_script = scr->get_base_script(); @@ -2877,7 +2948,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path } break; } - for (Set<String>::Element *E = options.front(); E; E = E->next()) { + for (Map<String, ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) { r_options->push_back(E->get()); } @@ -2886,7 +2957,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path #else -Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) { +Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { return OK; } diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index d5e74c07c9..42f349ffc0 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -1784,20 +1784,9 @@ GDScriptFunction::~GDScriptFunction() { Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { - if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { -#ifdef DEBUG_ENABLED - ERR_EXPLAIN("Resumed function '" + String(function->get_name()) + "()' after yield, but class instance is gone. At script: " + state.script->get_path() + ":" + itos(state.line)); - ERR_FAIL_V(Variant()); -#else - return Variant(); -#endif - } - Variant arg; r_error.error = Variant::CallError::CALL_OK; - ERR_FAIL_COND_V(!function, Variant()); - if (p_argcount == 0) { r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 1; @@ -1823,44 +1812,7 @@ Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_ar return Variant(); } - state.result = arg; - Variant ret = function->call(NULL, NULL, 0, r_error, &state); - - bool completed = true; - - // If the return value is a GDScriptFunctionState reference, - // then the function did yield again after resuming. - if (ret.is_ref()) { - GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret); - if (gdfs && gdfs->function == function) { - completed = false; - gdfs->first_state = first_state.is_valid() ? first_state : Ref<GDScriptFunctionState>(this); - } - } - - function = NULL; //cleaned up; - state.result = Variant(); - - if (completed) { - if (first_state.is_valid()) { - first_state->emit_signal("completed", ret); - } else { - emit_signal("completed", ret); - } - } - -#ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton()) - GDScriptLanguage::get_singleton()->exit_function(); - if (state.stack_size) { - //free stack - Variant *stack = (Variant *)state.stack.ptr(); - for (int i = 0; i < state.stack_size; i++) - stack[i].~Variant(); - } -#endif - - return ret; + return resume(arg); } bool GDScriptFunctionState::is_valid(bool p_extended_check) const { diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index 0736f3d010..f5f245b25f 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -58,6 +58,7 @@ const char *GDScriptFunctions::get_func_name(Function p_func) { "sqrt", "fmod", "fposmod", + "posmod", "floor", "ceil", "round", @@ -75,6 +76,7 @@ const char *GDScriptFunctions::get_func_name(Function p_func) { "step_decimals", "stepify", "lerp", + "lerp_angle", "inverse_lerp", "range_lerp", "smoothstep", @@ -243,6 +245,12 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ VALIDATE_ARG_NUM(1); r_ret = Math::fposmod((double)*p_args[0], (double)*p_args[1]); } break; + case MATH_POSMOD: { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + r_ret = Math::posmod((int)*p_args[0], (int)*p_args[1]); + } break; case MATH_FLOOR: { VALIDATE_ARG_COUNT(1); VALIDATE_ARG_NUM(0); @@ -376,6 +384,13 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; } } break; + case MATH_LERP_ANGLE: { + VALIDATE_ARG_COUNT(3); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + r_ret = Math::lerp_angle((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); + } break; case MATH_INVERSE_LERP: { VALIDATE_ARG_COUNT(3); VALIDATE_ARG_NUM(0); @@ -1456,6 +1471,7 @@ bool GDScriptFunctions::is_deterministic(Function p_func) { case MATH_SQRT: case MATH_FMOD: case MATH_FPOSMOD: + case MATH_POSMOD: case MATH_FLOOR: case MATH_CEIL: case MATH_ROUND: @@ -1568,15 +1584,20 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case MATH_FMOD: { - MethodInfo mi("fmod", PropertyInfo(Variant::REAL, "x"), PropertyInfo(Variant::REAL, "y")); + MethodInfo mi("fmod", PropertyInfo(Variant::REAL, "a"), PropertyInfo(Variant::REAL, "b")); mi.return_val.type = Variant::REAL; return mi; } break; case MATH_FPOSMOD: { - MethodInfo mi("fposmod", PropertyInfo(Variant::REAL, "x"), PropertyInfo(Variant::REAL, "y")); + MethodInfo mi("fposmod", PropertyInfo(Variant::REAL, "a"), PropertyInfo(Variant::REAL, "b")); mi.return_val.type = Variant::REAL; return mi; } break; + case MATH_POSMOD: { + MethodInfo mi("posmod", PropertyInfo(Variant::INT, "a"), PropertyInfo(Variant::INT, "b")); + mi.return_val.type = Variant::INT; + return mi; + } break; case MATH_FLOOR: { MethodInfo mi("floor", PropertyInfo(Variant::REAL, "s")); mi.return_val.type = Variant::REAL; @@ -1603,7 +1624,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case MATH_POW: { - MethodInfo mi("pow", PropertyInfo(Variant::REAL, "x"), PropertyInfo(Variant::REAL, "y")); + MethodInfo mi("pow", PropertyInfo(Variant::REAL, "base"), PropertyInfo(Variant::REAL, "exp")); mi.return_val.type = Variant::REAL; return mi; } break; @@ -1663,6 +1684,11 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; return mi; } break; + case MATH_LERP_ANGLE: { + MethodInfo mi("lerp_angle", PropertyInfo(Variant::REAL, "from"), PropertyInfo(Variant::REAL, "to"), PropertyInfo(Variant::REAL, "weight")); + mi.return_val.type = Variant::REAL; + return mi; + } break; case MATH_INVERSE_LERP: { MethodInfo mi("inverse_lerp", PropertyInfo(Variant::REAL, "from"), PropertyInfo(Variant::REAL, "to"), PropertyInfo(Variant::REAL, "weight")); mi.return_val.type = Variant::REAL; diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_functions.h index 6ad70f2eb4..8f7ba76d2c 100644 --- a/modules/gdscript/gdscript_functions.h +++ b/modules/gdscript/gdscript_functions.h @@ -49,6 +49,7 @@ public: MATH_SQRT, MATH_FMOD, MATH_FPOSMOD, + MATH_POSMOD, MATH_FLOOR, MATH_CEIL, MATH_ROUND, @@ -66,6 +67,7 @@ public: MATH_STEP_DECIMALS, MATH_STEPIFY, MATH_LERP, + MATH_LERP_ANGLE, MATH_INVERSE_LERP, MATH_RANGE_LERP, MATH_SMOOTHSTEP, diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 9ab86a5459..f006d50a83 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -125,7 +125,7 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { } } -bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete) { +bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete, bool p_parsing_constant) { if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { tokenizer->advance(); @@ -149,7 +149,7 @@ bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bo return false; } - Node *arg = _parse_expression(p_parent, p_static); + Node *arg = _parse_expression(p_parent, p_static, false, p_parsing_constant); if (!arg) { return false; } @@ -639,7 +639,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s id->name = identifier; op->arguments.push_back(id); - if (!_parse_arguments(op, op->arguments, p_static, true)) + if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) return NULL; expr = op; @@ -731,7 +731,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s completion_node = op; } if (!replaced) { - if (!_parse_arguments(op, op->arguments, p_static, true)) + if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) return NULL; expr = op; } @@ -826,11 +826,12 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } // Check parents for the constant - if (!bfn && cln->extends_file != StringName()) { - Ref<GDScript> parent = ResourceLoader::load(cln->extends_file); - if (parent.is_valid() && parent->is_valid()) { + if (!bfn) { + // Using current_class instead of cln here, since cln is const* + _determine_inheritance(current_class, false); + if (cln->base_type.has_type && cln->base_type.kind == DataType::GDSCRIPT && cln->base_type.script_type->is_valid()) { Map<StringName, Variant> parent_constants; - parent->get_constants(&parent_constants); + current_class->base_type.script_type->get_constants(&parent_constants); if (parent_constants.has(identifier)) { ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = parent_constants[identifier]; @@ -1112,7 +1113,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } } else { tokenizer->advance(); - if (!_parse_arguments(op, op->arguments, p_static)) { + if (!_parse_arguments(op, op->arguments, p_static, false, p_parsing_constant)) { return NULL; } } @@ -1164,22 +1165,14 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s tokenizer->advance(); IdentifierNode *id = alloc_node<IdentifierNode>(); - if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_FUNC) { - //small hack so built in funcs don't obfuscate methods - - id->name = GDScriptFunctions::get_func_name(tokenizer->get_token_built_in_func()); - tokenizer->advance(); - - } else { - StringName identifier; - if (_get_completable_identifier(COMPLETION_METHOD, identifier)) { - completion_node = op; - //indexing stuff - } - - id->name = identifier; + StringName identifier; + if (_get_completable_identifier(COMPLETION_METHOD, identifier)) { + completion_node = op; + //indexing stuff } + id->name = identifier; + op->arguments.push_back(expr); // call what op->arguments.push_back(id); // call func //get arguments @@ -1188,7 +1181,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s _make_completable_call(0); completion_node = op; } - if (!_parse_arguments(op, op->arguments, p_static, true)) + if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) return NULL; expr = op; @@ -1773,8 +1766,6 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to cn->value = v; cn->datatype = _type_from_variant(v); return cn; - - } else if (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && last_not_constant == 0) { } return op; //don't reduce yet @@ -2218,6 +2209,8 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran p_block->has_return = true; + bool catch_all_appeared = false; + while (true) { while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) @@ -2228,7 +2221,7 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran return; if (indent_level > tab_level.back()->get()) { - return; // go back a level + break; // go back a level } if (pending_newline != -1) { @@ -2243,12 +2236,20 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran branch->patterns.push_back(_parse_pattern(p_static)); if (!branch->patterns[0]) { - return; + break; } bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND; bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD; +#ifdef DEBUG_ENABLED + // Branches after a wildcard or binding are unreachable + if (catch_all_appeared && !current_function->has_unreachable_code) { + _add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String()); + current_function->has_unreachable_code = true; + } +#endif + while (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); branch->patterns.push_back(_parse_pattern(p_static)); @@ -2266,6 +2267,8 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran catch_all = catch_all || pt == PatternNode::PT_WILDCARD; } + catch_all_appeared = catch_all_appeared || catch_all; + if (!_enter_indent_block()) { _set_error("Expected block in pattern branch"); return; @@ -2281,6 +2284,11 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran p_branches.push_back(branch); } + + // Even if all branches return, there is possibility of default fallthrough + if (!catch_all_appeared) { + p_block->has_return = false; + } } void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) { @@ -5084,6 +5092,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); + } else if (tokenizer->is_token_literal(0, true)) { + _set_error("Unexpected identifier"); + return; } if (enum_name != "") { @@ -5158,9 +5169,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } -void GDScriptParser::_determine_inheritance(ClassNode *p_class) { +void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive) { - if (p_class->extends_used) { + if (p_class->base_type.has_type) { + // Already determined + } else if (p_class->extends_used) { //do inheritance String path = p_class->extends_file; @@ -5355,9 +5368,11 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) { p_class->base_type.native_type = "Reference"; } - // Recursively determine subclasses - for (int i = 0; i < p_class->subclasses.size(); i++) { - _determine_inheritance(p_class->subclasses[i]); + if (p_recursive) { + // Recursively determine subclasses + for (int i = 0; i < p_class->subclasses.size(); i++) { + _determine_inheritance(p_class->subclasses[i], p_recursive); + } } } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 5e4de11357..62d7bdb393 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -582,7 +582,7 @@ private: #endif // DEBUG_ENABLED bool _recover_from_completion(); - bool _parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete = false); + bool _parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete = false, bool p_parsing_constant = false); bool _enter_indent_block(BlockNode *p_block = NULL); bool _parse_newline(); Node *_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign = false, bool p_parsing_constant = false); @@ -599,7 +599,7 @@ private: void _parse_class(ClassNode *p_class); bool _end_statement(); - void _determine_inheritance(ClassNode *p_class); + void _determine_inheritance(ClassNode *p_class, bool p_recursive = true); bool _parse_type(DataType &r_type, bool p_can_be_void = false); DataType _resolve_type(const DataType &p_source, int p_line); DataType _type_from_variant(const Variant &p_value) const; diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 325432579f..1bd3d72066 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -210,7 +210,7 @@ </member> <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1"> </member> - <member name="mesh_library" type="MeshLibrary" setter="set_mesh_library" getter="get_mesh_library" default="null"> + <member name="mesh_library" type="MeshLibrary" setter="set_mesh_library" getter="get_mesh_library"> The assigned [MeshLibrary]. </member> <member name="theme" type="MeshLibrary" setter="set_theme" getter="get_theme"> diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 994a84fbc4..bdecbbdbad 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -481,11 +481,6 @@ bool GridMap::_octant_update(const OctantKey &p_key) { Transform xform; - if (clip && ((clip_above && cellpos[clip_axis] > clip_floor) || (!clip_above && cellpos[clip_axis] < clip_floor))) { - - } else { - } - xform.basis.set_orthogonal_index(c.rot); xform.set_origin(cellpos * cell_size + ofs); xform.basis.scale(Vector3(cell_scale, cell_scale, cell_scale)); diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 20e454c218..5a21833ffa 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -345,7 +345,7 @@ void GridMapEditor::_validate_selection() { _update_selection_transform(); } -void GridMapEditor::_set_selection(bool p_active, const Vector3 p_begin, const Vector3 p_end) { +void GridMapEditor::_set_selection(bool p_active, const Vector3 &p_begin, const Vector3 &p_end) { selection.active = p_active; selection.begin = p_begin; @@ -578,7 +578,7 @@ void GridMapEditor::_update_paste_indicator() { return; } - Vector3 center = 0.5 * Vector3(node->get_center_x(), node->get_center_y(), node->get_center_z()); + Vector3 center = 0.5 * Vector3(float(node->get_center_x()), float(node->get_center_y()), float(node->get_center_z())); Vector3 scale = (Vector3(1, 1, 1) + (paste_indicator.end - paste_indicator.begin)) * node->get_cell_size(); Transform xf; xf.scale(scale); diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h index da36165d4e..b9be925ff7 100644 --- a/modules/gridmap/grid_map_editor_plugin.h +++ b/modules/gridmap/grid_map_editor_plugin.h @@ -222,7 +222,7 @@ class GridMapEditor : public VBoxContainer { void _do_paste(); void _update_selection_transform(); void _validate_selection(); - void _set_selection(bool p_active, const Vector3 p_begin = Vector3(), const Vector3 p_end = Vector3()); + void _set_selection(bool p_active, const Vector3 &p_begin = Vector3(), const Vector3 &p_end = Vector3()); void _floor_changed(float p_value); void _floor_mouse_exited(); diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp index 5493223cb0..dcd8b8aebd 100644 --- a/modules/jpg/image_loader_jpegd.cpp +++ b/modules/jpg/image_loader_jpegd.cpp @@ -96,7 +96,7 @@ Error jpeg_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p else fmt = Image::FORMAT_RGB8; - dw = PoolVector<uint8_t>::Write(); + dw.release(); p_image->create(image_width, image_height, 0, fmt, data); return OK; @@ -117,8 +117,6 @@ Error ImageLoaderJPG::load_image(Ref<Image> p_image, FileAccess *f, bool p_force Error err = jpeg_load_image_from_buffer(p_image.ptr(), w.ptr(), src_image_len); - w = PoolVector<uint8_t>::Write(); - return err; } diff --git a/modules/mbedtls/stream_peer_mbed_tls.cpp b/modules/mbedtls/stream_peer_mbed_tls.cpp index 3541eff25a..4bb7557150 100755 --- a/modules/mbedtls/stream_peer_mbed_tls.cpp +++ b/modules/mbedtls/stream_peer_mbed_tls.cpp @@ -33,8 +33,6 @@ #include "core/io/stream_peer_tcp.h" #include "core/os/file_access.h" -#include <mbedtls/platform_util.h> - static void my_debug(void *ctx, int level, const char *file, int line, const char *str) { diff --git a/modules/mbedtls/stream_peer_mbed_tls.h b/modules/mbedtls/stream_peer_mbed_tls.h index 3ddbea3ce4..ab87b779c1 100755 --- a/modules/mbedtls/stream_peer_mbed_tls.h +++ b/modules/mbedtls/stream_peer_mbed_tls.h @@ -37,7 +37,6 @@ #include <mbedtls/ctr_drbg.h> #include <mbedtls/debug.h> #include <mbedtls/entropy.h> -#include <mbedtls/net.h> #include <mbedtls/ssl.h> #include <stdio.h> diff --git a/modules/mono/SCsub b/modules/mono/SCsub index 6c3ecee272..cc60e64a11 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -1,5 +1,8 @@ #!/usr/bin/env python +import build_scripts.tls_configure as tls_configure +import build_scripts.mono_configure as mono_configure + Import('env') Import('env_modules') @@ -26,27 +29,36 @@ if env_mono['mono_glue']: import os.path if not os.path.isfile('glue/mono_glue.gen.cpp'): - raise RuntimeError('Missing mono glue sources. Did you forget to generate them?') + raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?") if env_mono['tools'] or env_mono['target'] != 'release': env_mono.Append(CPPDEFINES=['GD_MONO_HOT_RELOAD']) # Configure Thread Local Storage -import build_scripts.tls_configure as tls_configure - conf = Configure(env_mono) tls_configure.configure(conf) env_mono = conf.Finish() # Configure Mono -import build_scripts.mono_configure as mono_configure - mono_configure.configure(env, env_mono) -# Build GodotSharpTools +# Build Godot API solution + +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) -import build_scripts.godotsharptools_build as godotsharptools_build +# Build GodotTools -godotsharptools_build.build(env_mono) +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) + 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 + # GodotTools.ProjectEditor which doesn't depend on the Godot API solution and + # is required by the bindings generator in order to be able to generated it. + godot_tools_build.build_project_editor_only(env_mono) diff --git a/modules/mono/__init__.py b/modules/mono/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/modules/mono/__init__.py diff --git a/modules/mono/build_scripts/api_solution_build.py b/modules/mono/build_scripts/api_solution_build.py new file mode 100644 index 0000000000..1fe00a3028 --- /dev/null +++ b/modules/mono/build_scripts/api_solution_build.py @@ -0,0 +1,66 @@ +# Build the Godot API solution + +import os + +from SCons.Script import Dir + + +def build_api_solution(source, target, env): + # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str + + module_dir = env['module_dir'] + + solution_path = os.path.join(module_dir, 'glue/Managed/Generated/GodotSharp.sln') + + if not os.path.isfile(solution_path): + raise RuntimeError("Godot API solution not found. Did you forget to run '--generate-mono-glue'?") + + build_config = env['solution_build_config'] + + extra_msbuild_args = ['/p:NoWarn=1591'] # Ignore missing documentation warnings + + from .solution_builder import build_solution + build_solution(env, solution_path, build_config, extra_msbuild_args=extra_msbuild_args) + + # Copy targets + + core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharp', 'bin', build_config)) + editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharpEditor', 'bin', build_config)) + + dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) + + if not os.path.isdir(dst_dir): + assert not os.path.isfile(dst_dir) + os.makedirs(dst_dir) + + def copy_target(target_path): + from shutil import copy + filename = os.path.basename(target_path) + + src_path = os.path.join(core_src_dir, filename) + if not os.path.isfile(src_path): + src_path = os.path.join(editor_src_dir, filename) + + copy(src_path, target_path) + + for scons_target in target: + copy_target(str(scons_target)) + + +def build(env_mono): + assert env_mono['tools'] + + target_filenames = [ + 'GodotSharp.dll', 'GodotSharp.pdb', 'GodotSharp.xml', + 'GodotSharpEditor.dll', 'GodotSharpEditor.pdb', 'GodotSharpEditor.xml' + ] + + 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, + module_dir=os.getcwd(), solution_build_config=build_config) + env_mono.AlwaysBuild(cmd) diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py new file mode 100644 index 0000000000..c47cfc8a38 --- /dev/null +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -0,0 +1,112 @@ +# Build GodotTools solution + +import os + +from SCons.Script import Dir + + +def build_godot_tools(source, target, env): + # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str + + module_dir = env['module_dir'] + + solution_path = os.path.join(module_dir, 'editor/GodotTools/GodotTools.sln') + build_config = 'Debug' if env['target'] == 'debug' else 'Release' + + from . solution_builder import build_solution, nuget_restore + nuget_restore(env, solution_path) + build_solution(env, solution_path, build_config) + + # Copy targets + + solution_dir = os.path.abspath(os.path.join(solution_path, os.pardir)) + + src_dir = os.path.join(solution_dir, 'GodotTools', 'bin', build_config) + dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) + + if not os.path.isdir(dst_dir): + assert not os.path.isfile(dst_dir) + os.makedirs(dst_dir) + + def copy_target(target_path): + from shutil import copy + filename = os.path.basename(target_path) + copy(os.path.join(src_dir, filename), target_path) + + for scons_target in target: + copy_target(str(scons_target)) + + +def build_godot_tools_project_editor(source, target, env): + # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str + + module_dir = env['module_dir'] + + project_name = 'GodotTools.ProjectEditor' + + csproj_dir = os.path.join(module_dir, 'editor/GodotTools', project_name) + csproj_path = os.path.join(csproj_dir, project_name + '.csproj') + build_config = 'Debug' if env['target'] == 'debug' else 'Release' + + from . solution_builder import build_solution, nuget_restore + + # Make sure to restore NuGet packages in the project directory for the project to find it + nuget_restore(env, os.path.join(csproj_dir, 'packages.config'), '-PackagesDirectory', + os.path.join(csproj_dir, 'packages')) + + build_solution(env, csproj_path, build_config) + + # Copy targets + + src_dir = os.path.join(csproj_dir, 'bin', build_config) + dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) + + if not os.path.isdir(dst_dir): + assert not os.path.isfile(dst_dir) + os.makedirs(dst_dir) + + def copy_target(target_path): + from shutil import copy + filename = os.path.basename(target_path) + copy(os.path.join(src_dir, filename), target_path) + + for scons_target in target: + copy_target(str(scons_target)) + + +def build(env_mono): + 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.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'] + + if env_mono['target'] == 'debug': + target_filenames += ['GodotTools.pdb', 'GodotTools.BuildLogger.pdb', 'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb'] + + 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()) + env_mono.AlwaysBuild(cmd) + + +def build_project_editor_only(env_mono): + assert env_mono['tools'] + + output_dir = Dir('#bin').abspath + editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools') + + target_filenames = ['GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'] + + if env_mono['target'] == 'debug': + target_filenames += ['GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb'] + + targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] + + cmd = env_mono.CommandNoCache(targets, [], build_godot_tools_project_editor, module_dir=os.getcwd()) + env_mono.AlwaysBuild(cmd) diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py new file mode 100644 index 0000000000..cd9210897d --- /dev/null +++ b/modules/mono/build_scripts/make_android_mono_config.py @@ -0,0 +1,69 @@ + +def generate_compressed_config(config_src, output_dir): + import os.path + from compat import byte_to_str + + # Header file + with open(os.path.join(output_dir, 'android_mono_config.gen.h'), 'w') as header: + header.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ +#ifndef ANDROID_MONO_CONFIG_GEN_H +#define ANDROID_MONO_CONFIG_GEN_H + +#ifdef ANDROID_ENABLED + +#include "core/ustring.h" + +String get_godot_android_mono_config(); + +#endif // ANDROID_ENABLED + +#endif // ANDROID_MONO_CONFIG_GEN_H +''') + + # Source file + with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp: + with open(config_src, 'rb') as f: + buf = f.read() + decompr_size = len(buf) + import zlib + buf = zlib.compress(buf) + compr_size = len(buf) + + bytes_seq_str = '' + for i, buf_idx in enumerate(range(compr_size)): + if i > 0: + bytes_seq_str += ', ' + bytes_seq_str += byte_to_str(buf[buf_idx]) + + cpp.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ +#include "android_mono_config.gen.h" + +#ifdef ANDROID_ENABLED + +#include "core/io/compression.h" +#include "core/pool_vector.h" + +namespace { + +// config +static const int config_compressed_size = %d; +static const int config_uncompressed_size = %d; +static const unsigned char config_compressed_data[] = { %s }; + +} // namespace + +String get_godot_android_mono_config() { + PoolVector<uint8_t> data; + data.resize(config_uncompressed_size); + PoolVector<uint8_t>::Write w = data.write(); + Compression::decompress(w.ptr(), config_uncompressed_size, config_compressed_data, + config_compressed_size, Compression::MODE_DEFLATE); + String s; + if (s.parse_utf8((const char *)w.ptr(), data.size())) { + ERR_FAIL_V(String()); + } + return s; +} + +#endif // ANDROID_ENABLED +''' % (compr_size, decompr_size, bytes_seq_str)) diff --git a/modules/mono/build_scripts/make_cs_compressed_header.py b/modules/mono/build_scripts/make_cs_compressed_header.py index 1f9177cef8..ed49db5bb2 100644 --- a/modules/mono/build_scripts/make_cs_compressed_header.py +++ b/modules/mono/build_scripts/make_cs_compressed_header.py @@ -23,30 +23,31 @@ def generate_header(src, dst, version_dst): latest_mtime = mtime if mtime > latest_mtime else latest_mtime with open(filepath, 'rb') as f: buf = f.read() - decomp_size = len(buf) + decompr_size = len(buf) import zlib buf = zlib.compress(buf) + compr_size = len(buf) name = str(cs_file_count) header.write('\n') header.write('// ' + filepath_src_rel + '\n') - header.write('static const int _cs_' + name + '_compressed_size = ' + str(len(buf)) + ';\n') - header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decomp_size) + ';\n') + header.write('static const int _cs_' + name + '_compressed_size = ' + str(compr_size) + ';\n') + header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decompr_size) + ';\n') header.write('static const unsigned char _cs_' + name + '_compressed[] = { ') - for i, buf_idx in enumerate(range(len(buf))): + for i, buf_idx in enumerate(range(compr_size)): if i > 0: header.write(', ') header.write(byte_to_str(buf[buf_idx])) + header.write(' };\n') inserted_files += '\tr_files.insert("' + filepath_src_rel.replace('\\', '\\\\') + '", ' \ - 'CompressedFile(_cs_' + name + '_compressed_size, ' \ + 'GodotCsCompressedFile(_cs_' + name + '_compressed_size, ' \ '_cs_' + name + '_uncompressed_size, ' \ '_cs_' + name + '_compressed));\n' - header.write(' };\n') - header.write('\nstruct CompressedFile\n' '{\n' + header.write('\nstruct GodotCsCompressedFile\n' '{\n' '\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* data;\n' - '\n\tCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n' + '\n\tGodotCsCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n' '\t{\n' '\t\tcompressed_size = p_comp_size;\n' '\t\tuncompressed_size = p_uncomp_size;\n' - '\t\tdata = p_data;\n' '\t}\n' '\n\tCompressedFile() {}\n' '};\n' - '\nvoid get_compressed_files(Map<String, CompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n' + '\t\tdata = p_data;\n' '\t}\n' '\n\tGodotCsCompressedFile() {}\n' '};\n' + '\nvoid get_compressed_files(Map<String, GodotCsCompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n' ) header.write('\n#endif // TOOLS_ENABLED\n') header.write('\n#endif // CS_COMPRESSED_H\n') diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index c549640d61..9f0eb58896 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -1,10 +1,8 @@ -import imp import os import os.path import sys import subprocess -from distutils.version import LooseVersion from SCons.Script import Dir, Environment if os.name == 'nt': @@ -41,7 +39,7 @@ def copy_file(src_dir, dst_dir, name): dst_dir = Dir(dst_dir).abspath if not os.path.isdir(dst_dir): - os.mkdir(dst_dir) + os.makedirs(dst_dir) copy(src_path, dst_dir) @@ -58,6 +56,12 @@ def configure(env, env_mono): mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0'] + is_travis = os.environ.get('TRAVIS') == 'true' + + if is_travis: + # Travis CI may have a Mono version lower than 5.12 + 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']) @@ -65,6 +69,10 @@ def configure(env, env_mono): # 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 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') + if (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')) and not mono_prefix: print("WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead") @@ -79,9 +87,6 @@ def configure(env, env_mono): print('Found Mono root directory: ' + mono_root) - mono_version = mono_root_try_find_mono_version(mono_root) - configure_for_mono_version(env_mono, mono_version) - mono_lib_path = os.path.join(mono_root, 'lib') env.Append(LIBPATH=mono_lib_path) @@ -160,9 +165,6 @@ def configure(env, env_mono): if mono_root: print('Found Mono root directory: ' + mono_root) - mono_version = mono_root_try_find_mono_version(mono_root) - configure_for_mono_version(env_mono, mono_version) - mono_lib_path = os.path.join(mono_root, 'lib') env.Append(LIBPATH=mono_lib_path) @@ -173,7 +175,7 @@ def configure(env, env_mono): if not mono_lib: raise RuntimeError('Could not find mono library in: ' + mono_lib_path) - env_mono.Append(CPPFLAGS=['-D_REENTRANT']) + env_mono.Append(CPPDEFINES=['_REENTRANT']) if mono_static: mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a') @@ -188,7 +190,7 @@ def configure(env, env_mono): if is_apple: env.Append(LIBS=['iconv', 'pthread']) elif is_android: - env.Append(LIBS=['m', 'dl']) + pass # Nothing else: env.Append(LIBS=['m', 'rt', 'dl', 'pthread']) @@ -205,9 +207,6 @@ def configure(env, env_mono): # TODO: Add option to force using pkg-config print('Mono root directory not found. Using pkg-config instead') - mono_version = pkgconfig_try_find_mono_version() - configure_for_mono_version(env_mono, mono_version) - env.ParseConfig('pkg-config monosgen-2 --libs') env_mono.ParseConfig('pkg-config monosgen-2 --cflags') @@ -236,6 +235,14 @@ def configure(env, env_mono): 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/') + + # Copy the required shared libraries + copy_mono_shared_libs(env, mono_root, None) if copy_mono_root: if not mono_root: @@ -270,9 +277,8 @@ def make_template_dir(env, mono_root): # Copy etc/mono/ - if platform != 'android': - 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']) + 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 the required shared libraries @@ -390,17 +396,6 @@ def copy_mono_shared_libs(env, mono_root, target_mono_root_dir): copy_if_exists(os.path.join(mono_root, 'lib', lib_file_name), target_mono_lib_dir) -def configure_for_mono_version(env, mono_version): - if mono_version is None: - if os.getenv('MONO_VERSION'): - mono_version = os.getenv('MONO_VERSION') - else: - raise RuntimeError("Mono JIT compiler version not found; specify one manually with the 'MONO_VERSION' environment variable") - print('Found Mono JIT compiler version: ' + str(mono_version)) - if mono_version >= LooseVersion('5.12.0'): - env.Append(CPPFLAGS=['-DHAS_PENDING_EXCEPTIONS']) - - def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext): tmpenv = Environment() tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH')) @@ -410,36 +405,3 @@ def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext): if name_found and os.path.isdir(os.path.join(hint_dir, '..', 'include', 'mono-2.0')): return os.path.join(hint_dir, '..') return '' - - -def pkgconfig_try_find_mono_version(): - from compat import decode_utf8 - - lines = subprocess.check_output(['pkg-config', 'monosgen-2', '--modversion']).splitlines() - greater_version = None - for line in lines: - try: - version = LooseVersion(decode_utf8(line)) - if greater_version is None or version > greater_version: - greater_version = version - except ValueError: - pass - return greater_version - - -def mono_root_try_find_mono_version(mono_root): - from compat import decode_utf8 - - mono_bin = os.path.join(mono_root, 'bin') - if os.path.isfile(os.path.join(mono_bin, 'mono')): - mono_binary = os.path.join(mono_bin, 'mono') - elif os.path.isfile(os.path.join(mono_bin, 'mono.exe')): - mono_binary = os.path.join(mono_bin, 'mono.exe') - else: - return None - output = subprocess.check_output([mono_binary, '--version']) - first_line = decode_utf8(output.splitlines()[0]) - try: - return LooseVersion(first_line.split()[len('Mono JIT compiler version'.split())]) - except (ValueError, IndexError): - return None diff --git a/modules/mono/build_scripts/mono_reg_utils.py b/modules/mono/build_scripts/mono_reg_utils.py index 583708bf07..b2c48f0a61 100644 --- a/modules/mono/build_scripts/mono_reg_utils.py +++ b/modules/mono/build_scripts/mono_reg_utils.py @@ -116,5 +116,3 @@ def find_msbuild_tools_path_reg(): return value except (WindowsError, OSError): return '' - - return '' diff --git a/modules/mono/build_scripts/godotsharptools_build.py b/modules/mono/build_scripts/solution_builder.py index 17f9a990af..d1529a64d2 100644 --- a/modules/mono/build_scripts/godotsharptools_build.py +++ b/modules/mono/build_scripts/solution_builder.py @@ -1,8 +1,8 @@ -# Build GodotSharpTools solution import os -from SCons.Script import Builder, Dir + +verbose = False def find_nuget_unix(): @@ -108,10 +108,14 @@ def find_msbuild_windows(env): if not mono_root: raise RuntimeError('Cannot find mono root directory') - framework_path = os.path.join(mono_root, 'lib', 'mono', '4.5') mono_bin_dir = os.path.join(mono_root, 'bin') msbuild_mono = os.path.join(mono_bin_dir, 'msbuild.bat') + msbuild_tools_path = find_msbuild_tools_path_reg() + + if msbuild_tools_path: + return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), {}) + if os.path.isfile(msbuild_mono): # The (Csc/Vbc/Fsc)ToolExe environment variables are required when # building with Mono's MSBuild. They must point to the batch files @@ -121,24 +125,52 @@ def find_msbuild_windows(env): 'VbcToolExe': os.path.join(mono_bin_dir, 'vbc.bat'), 'FscToolExe': os.path.join(mono_bin_dir, 'fsharpc.bat') } - return (msbuild_mono, framework_path, mono_msbuild_env) + return (msbuild_mono, mono_msbuild_env) - msbuild_tools_path = find_msbuild_tools_path_reg() + return None - if msbuild_tools_path: - return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), framework_path, {}) - return None +def run_command(command, args, env_override=None, name=None): + def cmd_args_to_str(cmd_args): + return ' '.join([arg if not ' ' in arg else '"%s"' % arg for arg in cmd_args]) + args = [command] + args + + if name is None: + name = os.path.basename(command) + + if verbose: + print("Running '%s': %s" % (name, cmd_args_to_str(args))) -def mono_build_solution(source, target, env): import subprocess - from shutil import copyfile + try: + if env_override is None: + subprocess.check_call(args) + else: + subprocess.check_call(args, env=env_override) + except subprocess.CalledProcessError as e: + raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode)) + + +def nuget_restore(env, *args): + global verbose + verbose = env['verbose'] + + # Find NuGet + nuget_path = find_nuget_windows(env) if os.name == 'nt' else find_nuget_unix() + if nuget_path is None: + raise RuntimeError('Cannot find NuGet executable') + + print('NuGet path: ' + nuget_path) + + # Do NuGet restore + run_command(nuget_path, ['restore'] + list(args), name='nuget restore') - sln_path = os.path.abspath(str(source[0])) - target_path = os.path.abspath(str(target[0])) - framework_path = '' +def build_solution(env, solution_path, build_config, extra_msbuild_args=[]): + global verbose + verbose = env['verbose'] + msbuild_env = os.environ.copy() # Needed when running from Developer Command Prompt for VS @@ -151,8 +183,7 @@ def mono_build_solution(source, target, env): if msbuild_info is None: raise RuntimeError('Cannot find MSBuild executable') msbuild_path = msbuild_info[0] - framework_path = msbuild_info[1] - msbuild_env.update(msbuild_info[2]) + msbuild_env.update(msbuild_info[1]) else: msbuild_path = find_msbuild_unix('msbuild') if msbuild_path is None: @@ -175,64 +206,9 @@ def mono_build_solution(source, target, env): print('MSBuild path: ' + msbuild_path) - # Find NuGet - nuget_path = find_nuget_windows(env) if os.name == 'nt' else find_nuget_unix() - if nuget_path is None: - raise RuntimeError('Cannot find NuGet executable') - - print('NuGet path: ' + nuget_path) - - # Do NuGet restore - - try: - subprocess.check_call([nuget_path, 'restore', sln_path]) - except subprocess.CalledProcessError: - raise RuntimeError('GodotSharpTools: NuGet restore failed') - # Build solution - build_config = 'Release' - - msbuild_args = [ - msbuild_path, - sln_path, - '/p:Configuration=' + build_config, - ] - - if framework_path: - msbuild_args += ['/p:FrameworkPathOverride=' + framework_path] - - try: - subprocess.check_call(msbuild_args, env=msbuild_env) - except subprocess.CalledProcessError: - raise RuntimeError('GodotSharpTools: Build failed') - - # Copy files - - src_dir = os.path.abspath(os.path.join(sln_path, os.pardir, 'bin', build_config)) - dst_dir = os.path.abspath(os.path.join(target_path, os.pardir)) - asm_file = 'GodotSharpTools.dll' - - if not os.path.isdir(dst_dir): - if os.path.exists(dst_dir): - raise RuntimeError('Target directory is a file') - os.makedirs(dst_dir) - - copyfile(os.path.join(src_dir, asm_file), os.path.join(dst_dir, asm_file)) - - # Dependencies - copyfile(os.path.join(src_dir, "DotNet.Glob.dll"), os.path.join(dst_dir, "DotNet.Glob.dll")) - -def build(env_mono): - if not env_mono['tools']: - return - - output_dir = Dir('#bin').abspath - editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools') + msbuild_args = [solution_path, '/p:Configuration=' + build_config] + msbuild_args += extra_msbuild_args - mono_sln_builder = Builder(action=mono_build_solution) - env_mono.Append(BUILDERS={'MonoBuildSolution': mono_sln_builder}) - env_mono.MonoBuildSolution( - os.path.join(editor_tools_dir, 'GodotSharpTools.dll'), - 'editor/GodotSharpTools/GodotSharpTools.sln' - ) + run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name='msbuild') diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp new file mode 100644 index 0000000000..4a6637434a --- /dev/null +++ b/modules/mono/class_db_api_json.cpp @@ -0,0 +1,246 @@ +/*************************************************************************/ +/* class_db_api_json.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 "class_db_api_json.h" + +#ifdef DEBUG_METHODS_ENABLED + +#include "core/io/json.h" +#include "core/os/file_access.h" +#include "core/project_settings.h" +#include "core/version.h" + +void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { + Dictionary classes_dict; + + List<StringName> names; + + const StringName *k = NULL; + + while ((k = ClassDB::classes.next(k))) { + + names.push_back(*k); + } + //must be alphabetically sorted for hash to compute + names.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + + ClassDB::ClassInfo *t = ClassDB::classes.getptr(E->get()); + ERR_FAIL_COND(!t); + if (t->api != p_api || !t->exposed) + continue; + + Dictionary class_dict; + classes_dict[t->name] = class_dict; + + class_dict["inherits"] = t->inherits; + + { //methods + + List<StringName> snames; + + k = NULL; + + while ((k = t->method_map.next(k))) { + + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + Array methods; + + for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + Dictionary method_dict; + methods.push_back(method_dict); + + MethodBind *mb = t->method_map[F->get()]; + method_dict["name"] = mb->get_name(); + method_dict["argument_count"] = mb->get_argument_count(); + method_dict["return_type"] = mb->get_argument_type(-1); + + Array arguments; + method_dict["arguments"] = arguments; + + for (int i = 0; i < mb->get_argument_count(); i++) { + Dictionary argument_dict; + arguments.push_back(argument_dict); + const PropertyInfo info = mb->get_argument_info(i); + argument_dict["type"] = info.type; + argument_dict["name"] = info.name; + argument_dict["hint"] = info.hint; + argument_dict["hint_string"] = info.hint_string; + } + + method_dict["default_argument_count"] = mb->get_default_argument_count(); + + Array default_arguments; + method_dict["default_arguments"] = default_arguments; + + for (int i = 0; i < mb->get_default_argument_count(); i++) { + Dictionary default_argument_dict; + default_arguments.push_back(default_argument_dict); + //hash should not change, i hope for tis + Variant da = mb->get_default_argument(i); + default_argument_dict["value"] = da; + } + + method_dict["hint_flags"] = mb->get_hint_flags(); + } + + if (!methods.empty()) { + class_dict["methods"] = methods; + } + } + + { //constants + + List<StringName> snames; + + k = NULL; + + while ((k = t->constant_map.next(k))) { + + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + Array constants; + + for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + Dictionary constant_dict; + constants.push_back(constant_dict); + + constant_dict["name"] = F->get(); + constant_dict["value"] = t->constant_map[F->get()]; + } + + if (!constants.empty()) { + class_dict["constants"] = constants; + } + } + + { //signals + + List<StringName> snames; + + k = NULL; + + while ((k = t->signal_map.next(k))) { + + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + Array signals; + + for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + Dictionary signal_dict; + signals.push_back(signal_dict); + + MethodInfo &mi = t->signal_map[F->get()]; + signal_dict["name"] = F->get(); + + Array arguments; + signal_dict["arguments"] = arguments; + for (int i = 0; i < mi.arguments.size(); i++) { + Dictionary argument_dict; + arguments.push_back(argument_dict); + argument_dict["type"] = mi.arguments[i].type; + } + } + + if (!signals.empty()) { + class_dict["signals"] = signals; + } + } + + { //properties + + List<StringName> snames; + + k = NULL; + + while ((k = t->property_setget.next(k))) { + + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + Array properties; + + for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + Dictionary property_dict; + properties.push_back(property_dict); + + ClassDB::PropertySetGet *psg = t->property_setget.getptr(F->get()); + + property_dict["name"] = F->get(); + property_dict["setter"] = psg->setter; + property_dict["getter"] = psg->getter; + } + + if (!properties.empty()) { + class_dict["property_setget"] = properties; + } + } + + Array property_list; + + //property list + for (List<PropertyInfo>::Element *F = t->property_list.front(); F; F = F->next()) { + Dictionary property_dict; + property_list.push_back(property_dict); + + property_dict["name"] = F->get().name; + property_dict["type"] = F->get().type; + property_dict["hint"] = F->get().hint; + property_dict["hint_string"] = F->get().hint_string; + property_dict["usage"] = F->get().usage; + } + + if (!property_list.empty()) { + class_dict["property_list"] = property_list; + } + } + + FileAccessRef f = FileAccess::open(p_output_file, FileAccess::WRITE); + ERR_FAIL_COND(!f); + f->store_string(JSON::print(classes_dict, /*indent: */ "\t")); + f->close(); + + print_line(String() + "ClassDB API JSON written to: " + ProjectSettings::get_singleton()->globalize_path(p_output_file)); +} + +#endif // DEBUG_METHODS_ENABLED diff --git a/modules/mono/editor/dotnet_solution.h b/modules/mono/class_db_api_json.h index 18933364fa..ddfe2debea 100644 --- a/modules/mono/editor/dotnet_solution.h +++ b/modules/mono/class_db_api_json.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* dotnet_solution.h */ +/* class_db_api_json.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) */ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 */ @@ -28,36 +28,19 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef NET_SOLUTION_H -#define NET_SOLUTION_H +#ifndef CLASS_DB_API_JSON_H +#define CLASS_DB_API_JSON_H -#include "core/map.h" -#include "core/ustring.h" - -struct DotNetSolution { - String name; - - struct ProjectInfo { - String guid; - String relpath; // Must be relative to the solution directory - Vector<String> configs; - }; +// 'core/method_bind.h' defines DEBUG_METHODS_ENABLED, but it looks like we +// cannot include it here. That's why we include it through 'core/class_db.h'. +#include "core/class_db.h" - void add_new_project(const String &p_name, const ProjectInfo &p_project_info); - bool has_project(const String &p_name) const; - const ProjectInfo &get_project_info(const String &p_name) const; - bool remove_project(const String &p_name); +#ifdef DEBUG_METHODS_ENABLED - Error save(); - - bool set_path(const String &p_existing_path); - String get_path(); +#include "core/ustring.h" - DotNetSolution(const String &p_name); +void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api); -private: - String path; - Map<String, ProjectInfo> projects; -}; +#endif // DEBUG_METHODS_ENABLED -#endif // NET_SOLUTION_H +#endif // CLASS_DB_API_JSON_H diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 9522eaee77..846c84d222 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -42,9 +42,13 @@ #include "editor/bindings_generator.h" #include "editor/csharp_project.h" #include "editor/editor_node.h" -#include "editor/godotsharp_editor.h" #endif +#ifdef DEBUG_METHODS_ENABLED +#include "class_db_api_json.h" +#endif + +#include "editor/editor_internal_calls.h" #include "godotsharp_dirs.h" #include "mono_gd/gd_mono_class.h" #include "mono_gd/gd_mono_marshal.h" @@ -65,8 +69,8 @@ static bool _create_project_solution_if_needed() { if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) { // A solution does not yet exist, create a new one - CRASH_COND(GodotSharpEditor::get_singleton() == NULL); - return GodotSharpEditor::get_singleton()->call("_create_project_solution"); + CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == NULL); + return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution"); } return true; @@ -96,32 +100,36 @@ Error CSharpLanguage::execute_file(const String &p_path) { return OK; } -#ifdef TOOLS_ENABLED -void gdsharp_editor_init_callback() { +void CSharpLanguage::init() { - EditorNode *editor = EditorNode::get_singleton(); - editor->add_child(memnew(GodotSharpEditor(editor))); -} +#ifdef DEBUG_METHODS_ENABLED + if (OS::get_singleton()->get_cmdline_args().find("--class_db_to_json")) { + class_db_api_to_json("user://class_db_api.json", ClassDB::API_CORE); +#ifdef TOOLS_ENABLED + class_db_api_to_json("user://class_db_api_editor.json", ClassDB::API_EDITOR); +#endif + } #endif - -void CSharpLanguage::init() { gdmono = memnew(GDMono); gdmono->initialize(); -#ifndef MONO_GLUE_ENABLED - WARN_PRINT("This binary is built with `mono_glue=no` and cannot be used for scripting"); +#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) + // Generate bindings here, before loading assemblies. `initialize_load_assemblies` aborts + // the applications if the api assemblies or the main tools assembly is missing, but this + // is not a problem for BindingsGenerator as it only needs the tools project editor assembly. + List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); + BindingsGenerator::handle_cmdline_args(cmdline_args); #endif -#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) - if (gdmono->get_editor_tools_assembly() != NULL) { - List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); - BindingsGenerator::handle_cmdline_args(cmdline_args); - } +#ifndef MONO_GLUE_ENABLED + print_line("Run this binary with `--generate-mono-glue path/to/modules/mono/glue`"); #endif + gdmono->initialize_load_assemblies(); + #ifdef TOOLS_ENABLED - EditorNode::add_init_callback(&gdsharp_editor_init_callback); + EditorNode::add_init_callback(&_editor_init_callback); GLOBAL_DEF("mono/export/include_scripts_content", false); #endif @@ -621,7 +629,6 @@ void CSharpLanguage::frame() { if (exc) { GDMonoUtils::debug_unhandled_exception(exc); - GD_UNREACHABLE(); } } } @@ -664,7 +671,7 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft CRASH_COND(!Engine::get_singleton()->is_editor_hint()); #ifdef TOOLS_ENABLED - MonoReloadNode::get_singleton()->restart_reload_timer(); + get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer"); #endif #ifdef GD_MONO_HOT_RELOAD @@ -682,19 +689,20 @@ bool CSharpLanguage::is_assembly_reloading_needed() { GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + if (appname_safe.empty()) { + appname_safe = "UnnamedProject"; } - name += ".dll"; + appname_safe += ".dll"; if (proj_assembly) { String proj_asm_path = proj_assembly->get_path(); if (!FileAccess::exists(proj_assembly->get_path())) { // Maybe it wasn't loaded from the default path, so check this as well - proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name); + proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe); if (!FileAccess::exists(proj_asm_path)) return false; // No assembly to load } @@ -702,18 +710,10 @@ bool CSharpLanguage::is_assembly_reloading_needed() { if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) return false; // Already up to date } else { - if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name))) + if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe))) return false; // No assembly to load } -#ifdef TOOLS_ENABLED - if (!gdmono->get_core_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) - return false; // The core API assembly to load is invalidated - - if (!gdmono->get_editor_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) - return false; // The editor API assembly to load is invalidated -#endif - return true; } @@ -730,58 +730,93 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { SCOPED_MUTEX_LOCK(script_instances_mutex); for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) { - if (elem->self()->get_path().is_resource_file()) { - // Cast to CSharpScript to avoid being erased by accident - scripts.push_back(Ref<CSharpScript>(elem->self())); - } + // Cast to CSharpScript to avoid being erased by accident + scripts.push_back(Ref<CSharpScript>(elem->self())); } } List<Ref<CSharpScript> > to_reload; + // We need to keep reference instances alive during reloading + List<Ref<Reference> > ref_instances; + + for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) { + CSharpScriptBinding &script_binding = E->value(); + Reference *ref = Object::cast_to<Reference>(script_binding.owner); + if (ref) { + ref_instances.push_back(Ref<Reference>(ref)); + } + } + // As scripts are going to be reloaded, must proceed without locking here scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { - Ref<CSharpScript> &script = E->get(); to_reload.push_back(script); + if (script->get_path().empty()) { + script->tied_class_name_for_reload = script->script_class->get_name(); + script->tied_class_namespace_for_reload = script->script_class->get_namespace(); + } + // Script::instances are deleted during managed object disposal, which happens on domain finalize. // Only placeholders are kept. Therefore we need to keep a copy before that happens. for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) { - script->pending_reload_instances.insert(F->get()->get_instance_id()); + Object *obj = F->get(); + script->pending_reload_instances.insert(obj->get_instance_id()); + + Reference *ref = Object::cast_to<Reference>(obj); + if (ref) { + ref_instances.push_back(Ref<Reference>(ref)); + } } #ifdef TOOLS_ENABLED for (Set<PlaceHolderScriptInstance *>::Element *F = script->placeholders.front(); F; F = F->next()) { - script->pending_reload_instances.insert(F->get()->get_owner()->get_instance_id()); + Object *obj = F->get()->get_owner(); + script->pending_reload_instances.insert(obj->get_instance_id()); + + Reference *ref = Object::cast_to<Reference>(obj); + if (ref) { + ref_instances.push_back(Ref<Reference>(ref)); + } } #endif - // FIXME: What about references? Need to keep them alive if only managed code references them. - // Save state and remove script from instances Map<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state; - while (script->instances.front()) { - Object *obj = script->instances.front()->get(); - // Save instance info - CSharpScript::StateBackup state; + for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) { + Object *obj = F->get(); ERR_CONTINUE(!obj->get_script_instance()); - // TODO: Proper state backup (Not only variants, serialize managed state of scripts) - obj->get_script_instance()->get_property_state(state.properties); + CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance()); - Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle; - if (gchandle.is_valid()) - gchandle->release(); + // Call OnBeforeSerialize + if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) + obj->get_script_instance()->call_multilevel(string_names.on_before_serialize); + + // Save instance info + CSharpScript::StateBackup state; + + // TODO: Proper state backup (Not only variants, serialize managed state of scripts) + csi->get_properties_state_for_reloading(state.properties); owners_map[obj->get_instance_id()] = state; + } + } + + // After the state of all instances is saved, clear scripts and script instances + for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { + Ref<CSharpScript> &script = E->get(); + + while (script->instances.front()) { + Object *obj = script->instances.front()->get(); obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload) } @@ -824,26 +859,80 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { scr->pending_reload_state.erase(obj_id); } } + return; } + List<Ref<CSharpScript> > to_reload_state; + for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) { + Ref<CSharpScript> script = E->get(); - Ref<CSharpScript> scr = E->get(); + if (!script->get_path().empty()) { #ifdef TOOLS_ENABLED - scr->exports_invalidated = true; + script->exports_invalidated = true; +#endif + script->signals_invalidated = true; + + script->reload(p_soft_reload); + script->update_exports(); + + if (!script->valid) { + script->pending_reload_instances.clear(); + continue; + } + } else { + const StringName &class_namespace = script->tied_class_namespace_for_reload; + const StringName &class_name = script->tied_class_name_for_reload; + GDMonoAssembly *project_assembly = gdmono->get_project_assembly(); + + // Search in project and tools assemblies first as those are the most likely to have the class + GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : NULL); + +#ifdef TOOLS_ENABLED + if (!script_class) { + GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly(); + script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL); + } #endif - scr->signals_invalidated = true; - scr->reload(p_soft_reload); - scr->update_exports(); + + if (!script_class) { + script_class = gdmono->get_class(class_namespace, class_name); + } + + if (!script_class) { + // The class was removed, can't reload + script->pending_reload_instances.clear(); + continue; + } + + bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(script_class); + if (!obj_type) { + // The class no longer inherits Godot.Object, can't reload + script->pending_reload_instances.clear(); + continue; + } + + GDMonoClass *native = GDMonoUtils::get_class_native_base(script_class); + + CSharpScript::initialize_for_managed_type(script, script_class, native); + } + + String native_name = NATIVE_GDMONOCLASS_NAME(script->native); { - for (Set<ObjectID>::Element *F = scr->pending_reload_instances.front(); F; F = F->next()) { + for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) { ObjectID obj_id = F->get(); Object *obj = ObjectDB::get_instance(obj_id); if (!obj) { - scr->pending_reload_state.erase(obj_id); + script->pending_reload_state.erase(obj_id); + continue; + } + + if (!ClassDB::is_parent_class(obj->get_class_name(), native_name)) { + // No longer inherits the same compatible type, can't reload + script->pending_reload_state.erase(obj_id); continue; } @@ -855,28 +944,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // Non-placeholder script instances are removed in godot_icall_Object_Disposed. CRASH_COND(!si->is_placeholder()); - if (scr->is_tool() || ScriptServer::is_scripting_enabled()) { + if (script->is_tool() || ScriptServer::is_scripting_enabled()) { // Replace placeholder with a script instance - CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id]; + CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; // Backup placeholder script instance state before replacing it with a script instance si->get_property_state(state_backup.properties); - ScriptInstance *script_instance = scr->instance_create(obj); + ScriptInstance *script_instance = script->instance_create(obj); if (script_instance) { - scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si)); + script->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si)); obj->set_script_instance(script_instance); } - - // TODO: Restore serialized state - - for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) { - script_instance->set(G->get().first, G->get().second); - } - - scr->pending_reload_state.erase(obj_id); } continue; @@ -885,20 +966,42 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { CRASH_COND(si != NULL); #endif // Re-create script instance + obj->set_script(script.get_ref_ptr()); // will create the script instance as well + } + } - obj->set_script(scr.get_ref_ptr()); // will create the script instance as well + to_reload_state.push_back(script); + } - // TODO: Restore serialized state + for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) { + Ref<CSharpScript> script = E->get(); - for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) { - obj->get_script_instance()->set(G->get().first, G->get().second); - } + for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) { + ObjectID obj_id = F->get(); + Object *obj = ObjectDB::get_instance(obj_id); - scr->pending_reload_state.erase(obj_id); + if (!obj) { + script->pending_reload_state.erase(obj_id); + continue; + } + + ERR_CONTINUE(!obj->get_script_instance()); + + // TODO: Restore serialized state + + CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; + + for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) { + obj->get_script_instance()->set(G->get().first, G->get().second); } - scr->pending_reload_instances.clear(); + // Call OnAfterDeserialization + CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance()); + if (csi && csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) + obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize); } + + script->pending_reload_instances.clear(); } #ifdef TOOLS_ENABLED @@ -963,12 +1066,12 @@ void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const #ifdef TOOLS_ENABLED Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { - return GodotSharpEditor::get_singleton()->open_in_external_editor(p_script, p_line, p_col); + return (Error)(int)get_godotsharp_editor()->call("OpenInExternalEditor", p_script, p_line, p_col); } bool CSharpLanguage::overrides_external_editor() { - return GodotSharpEditor::get_singleton()->overrides_external_editor(); + return get_godotsharp_editor()->call("OverridesExternalEditor"); } #endif @@ -1026,6 +1129,34 @@ void CSharpLanguage::_on_scripts_domain_unloaded() { scripts_metadata_invalidated = true; } +#ifdef TOOLS_ENABLED +void CSharpLanguage::_editor_init_callback() { + + register_editor_internal_calls(); + + // Initialize GodotSharpEditor + + GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor"); + CRASH_COND(editor_klass == NULL); + + MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr()); + CRASH_COND(mono_object == NULL); + + MonoException *exc = NULL; + GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc); + UNHANDLED_EXCEPTION(exc); + + EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(GDMonoMarshal::mono_object_to_variant(mono_object)); + CRASH_COND(godotsharp_editor == NULL); + + // Enable it as a plugin + EditorNode::add_editor_plugin(godotsharp_editor); + godotsharp_editor->enable_plugin(); + + get_singleton()->godotsharp_editor = godotsharp_editor; +} +#endif + void CSharpLanguage::set_language_index(int p_idx) { ERR_FAIL_COND(lang_idx != -1); @@ -1083,6 +1214,10 @@ CSharpLanguage::CSharpLanguage() { lang_idx = -1; scripts_metadata_invalidated = true; + +#ifdef TOOLS_ENABLED + godotsharp_editor = NULL; +#endif } CSharpLanguage::~CSharpLanguage() { @@ -1138,6 +1273,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b r_script_binding.type_name = type_name; r_script_binding.wrapper_class = type_class; // cache r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object); + r_script_binding.owner = p_object; // Tie managed to unmanaged Reference *ref = Object::cast_to<Reference>(p_object); @@ -1222,6 +1358,9 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); Ref<MonoGCHandle> &gchandle = script_binding.gchandle; + if (!script_binding.inited) + return; + if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, @@ -1246,14 +1385,17 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { CRASH_COND(!ref_owner); #endif - int refcount = ref_owner->reference_get_count(); - void *data = p_object->get_script_instance_binding(get_language_index()); CRASH_COND(!data); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); Ref<MonoGCHandle> &gchandle = script_binding.gchandle; + int refcount = ref_owner->reference_get_count(); + + if (!script_binding.inited) + return refcount == 0; + if (refcount == 1 && gchandle.is_valid() && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. @@ -1416,6 +1558,31 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { return false; } +void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state) { + + List<PropertyInfo> pinfo; + get_property_list(&pinfo); + + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + Pair<StringName, Variant> state_pair; + state_pair.first = E->get().name; + + ManagedType managedType; + + GDMonoField *field = script->script_class->get_field(state_pair.first); + if (!field) + continue; // Properties ignored. We get the property baking fields instead. + + managedType = field->get_type(); + + if (GDMonoMarshal::managed_to_variant_type(managedType) != Variant::NIL) { // If we can marshal it + if (get(state_pair.first, state_pair.second)) { + r_state.push_back(state_pair); + } + } + } +} + void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) { @@ -1613,17 +1780,18 @@ MonoObject *CSharpInstance::_internal_new_managed() { ERR_FAIL_NULL_V(owner, NULL); ERR_FAIL_COND_V(script.is_null(), NULL); - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script->script_class->get_mono_ptr()); + MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr()); if (!mono_object) { // Important to clear this before destroying the script instance here script = Ref<CSharpScript>(); - owner = NULL; bool die = _unreference_owner_unsafe(); // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. CRASH_COND(die == true); + owner = NULL; + ERR_EXPLAIN("Failed to allocate memory for the object"); ERR_FAIL_V(NULL); } @@ -1939,7 +2107,16 @@ CSharpInstance::~CSharpInstance() { CRASH_COND(data == NULL); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); - CRASH_COND(!script_binding.inited); + + if (!script_binding.inited) { + SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->get_language_bind_mutex()); + + if (!script_binding.inited) { // Other thread may have set it up + // Already had a binding that needs to be setup + CSharpLanguage::get_singleton()->setup_csharp_script_binding(script_binding, owner); + CRASH_COND(!script_binding.inited); + } + } bool die = _unreference_owner_unsafe(); CRASH_COND(die == true); // The "instance binding" should be holding a reference @@ -1981,6 +2158,52 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List propnames.push_back(E->get()); } } + +void CSharpScript::_update_member_info_no_exports() { + + if (exports_invalidated) { + exports_invalidated = false; + + member_info.clear(); + + GDMonoClass *top = script_class; + + while (top && top != native) { + PropertyInfo prop_info; + bool exported; + + const Vector<GDMonoField *> &fields = top->get_all_fields(); + + for (int i = fields.size() - 1; i >= 0; i--) { + GDMonoField *field = fields[i]; + + if (_get_member_export(field, /* inspect export: */ false, prop_info, exported)) { + StringName member_name = field->get_name(); + + member_info[member_name] = prop_info; + exported_members_cache.push_front(prop_info); + exported_members_defval_cache[member_name] = Variant(); + } + } + + const Vector<GDMonoProperty *> &properties = top->get_all_properties(); + + for (int i = properties.size() - 1; i >= 0; i--) { + GDMonoProperty *property = properties[i]; + + if (_get_member_export(property, /* inspect export: */ false, prop_info, exported)) { + StringName member_name = property->get_name(); + + member_info[member_name] = prop_info; + exported_members_cache.push_front(prop_info); + exported_members_defval_cache[member_name] = Variant(); + } + } + + top = top->get_parent_class(); + } + } +} #endif bool CSharpScript::_update_exports() { @@ -2007,7 +2230,7 @@ bool CSharpScript::_update_exports() { // Here we create a temporary managed instance of the class to get the initial values - MonoObject *tmp_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr()); + MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); if (!tmp_object) { ERR_PRINT("Failed to allocate temporary MonoObject"); @@ -2048,18 +2271,18 @@ bool CSharpScript::_update_exports() { for (int i = fields.size() - 1; i >= 0; i--) { GDMonoField *field = fields[i]; - if (_get_member_export(field, prop_info, exported)) { - StringName name = field->get_name(); + if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) { + StringName member_name = field->get_name(); if (exported) { - member_info[name] = prop_info; + member_info[member_name] = prop_info; exported_members_cache.push_front(prop_info); if (tmp_object) { - exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); + exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); } } else { - member_info[name] = prop_info; + member_info[member_name] = prop_info; } } } @@ -2069,25 +2292,25 @@ bool CSharpScript::_update_exports() { for (int i = properties.size() - 1; i >= 0; i--) { GDMonoProperty *property = properties[i]; - if (_get_member_export(property, prop_info, exported)) { - StringName name = property->get_name(); + if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) { + StringName member_name = property->get_name(); if (exported) { - member_info[name] = prop_info; + member_info[member_name] = prop_info; exported_members_cache.push_front(prop_info); if (tmp_object) { MonoException *exc = NULL; MonoObject *ret = property->get_value(tmp_object, &exc); if (exc) { - exported_members_defval_cache[name] = Variant(); + exported_members_defval_cache[member_name] = Variant(); GDMonoUtils::debug_print_unhandled_exception(exc); } else { - exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(ret); + exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret); } } } else { - member_info[name] = prop_info; + member_info[member_name] = prop_info; } } } @@ -2196,7 +2419,7 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve * Returns false if there was an error, otherwise true. * If there was an error, r_prop_info and r_exported are not assigned any value. */ -bool CSharpScript::_get_member_export(IMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported) { +bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) { // Goddammit, C++. All I wanted was some nested functions. #define MEMBER_FULL_QUALIFIED_NAME(m_member) \ @@ -2221,26 +2444,30 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, PropertyInfo & CRASH_NOW(); } - Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type); - - if (!p_member->has_attribute(CACHED_CLASS(ExportAttribute))) { - r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); - r_exported = false; - return true; - } + bool exported = p_member->has_attribute(CACHED_CLASS(ExportAttribute)); if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) { GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); if (!property->has_getter()) { - ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + if (exported) + ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); return false; } if (!property->has_setter()) { - ERR_PRINTS("Set-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + if (exported) + ERR_PRINTS("Write-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); return false; } } + Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type); + + if (!p_inspect_export || !exported) { + r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); + r_exported = false; + return true; + } + MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); PropertyHint hint = PROPERTY_HINT_NONE; @@ -2460,33 +2687,58 @@ void CSharpScript::_bind_methods() { Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) { - // This method should not fail + // This method should not fail, only assertions allowed - CRASH_COND(!p_class); + CRASH_COND(p_class == NULL); - // TODO: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time + // TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time Ref<CSharpScript> script = memnew(CSharpScript); - script->name = p_class->get_name(); - script->script_class = p_class; - script->native = p_native; + initialize_for_managed_type(script, p_class, p_native); + + return script; +} + +void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) { + + // This method should not fail, only assertions allowed - CRASH_COND(script->native == NULL); + CRASH_COND(p_class == NULL); - GDMonoClass *base = script->script_class->get_parent_class(); + p_script->name = p_class->get_name(); + p_script->script_class = p_class; + p_script->native = p_native; + + CRASH_COND(p_script->native == NULL); + + GDMonoClass *base = p_script->script_class->get_parent_class(); + + if (base != p_script->native) + p_script->base = base; + + p_script->valid = true; + p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute)); + + if (!p_script->tool) { + GDMonoClass *nesting_class = p_script->script_class->get_nesting_class(); + p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); + } - if (base != script->native) - script->base = base; +#if TOOLS_ENABLED + if (!p_script->tool) { + p_script->tool = p_script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly(); + } +#endif #ifdef DEBUG_ENABLED // For debug builds, we must fetch from all native base methods as well. // Native base methods must be fetched before the current class. // Not needed if the script class itself is a native class. - if (script->script_class != script->native) { - GDMonoClass *native_top = script->native; + if (p_script->script_class != p_script->native) { + GDMonoClass *native_top = p_script->native; while (native_top) { - native_top->fetch_methods_with_godot_api_checks(script->native); + native_top->fetch_methods_with_godot_api_checks(p_script->native); if (native_top == CACHED_CLASS(GodotObject)) break; @@ -2496,18 +2748,19 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD } #endif - script->script_class->fetch_methods_with_godot_api_checks(script->native); + p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native); // Need to fetch method from base classes as well - GDMonoClass *top = script->script_class; - while (top && top != script->native) { - top->fetch_methods_with_godot_api_checks(script->native); + GDMonoClass *top = p_script->script_class; + while (top && top != p_script->native) { + top->fetch_methods_with_godot_api_checks(p_script->native); top = top->get_parent_class(); } - script->load_script_signals(script->script_class, script->native); - - return script; + p_script->load_script_signals(p_script->script_class, p_script->native); +#ifdef TOOLS_ENABLED + p_script->_update_member_info_no_exports(); +#endif } bool CSharpScript::can_instance() const { @@ -2515,7 +2768,8 @@ bool CSharpScript::can_instance() const { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - if (get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted... + // Hack to lower the risk of attached scripts not being added to the C# project + if (!get_path().empty() && get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted... if (_create_project_solution_if_needed()) { CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(), "Compile", @@ -2567,7 +2821,9 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); if (ctor == NULL) { if (p_argcount == 0) { - ERR_PRINTS("Cannot create script instance because the class does not define a parameterless constructor: " + get_path()); + String path = get_path(); + ERR_PRINTS("Cannot create script instance. The class '" + script_class->get_full_name() + + "' does not define a parameterless constructor." + (path.empty() ? String() : ". Path: " + path)); } ERR_EXPLAIN("Constructor not found"); @@ -2609,7 +2865,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg /* STEP 2, INITIALIZE AND CONSTRUCT */ - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr()); + MonoObject *mono_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); if (!mono_object) { // Important to clear this before destroying the script instance here @@ -2690,7 +2946,7 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { #endif if (native) { - String native_name = native->get_name(); + String native_name = NATIVE_GDMONOCLASS_NAME(native); if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { if (ScriptDebugger::get_singleton()) { CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); @@ -2816,11 +3072,22 @@ Error CSharpScript::reload(bool p_keep_state) { if (script_class) { #ifdef DEBUG_ENABLED - print_verbose("Found class " + script_class->get_namespace() + "." + script_class->get_name() + " for script " + get_path()); + print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); #endif tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute)); + if (!tool) { + GDMonoClass *nesting_class = script_class->get_nesting_class(); + tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); + } + +#if TOOLS_ENABLED + if (!tool) { + tool = script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly(); + } +#endif + native = GDMonoUtils::get_class_native_base(script_class); CRASH_COND(native == NULL); @@ -3018,7 +3285,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p #endif #ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint() && mono_domain_get() == NULL) { + MonoDomain *domain = mono_domain_get(); + if (Engine::get_singleton()->is_editor_hint() && domain == NULL) { CRASH_COND(Thread::get_caller_id() == Thread::get_main_id()); @@ -3026,8 +3294,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p // because this may be called by one of the editor's worker threads. // Attach this thread temporarily to reload the script. - if (SCRIPTS_DOMAIN) { - MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN); + if (domain) { + MonoThread *mono_thread = mono_thread_attach(domain); CRASH_COND(mono_thread == NULL); script->reload(); mono_thread_detach(mono_thread); @@ -3127,5 +3395,7 @@ CSharpLanguage::StringNameCache::StringNameCache() { _get_property_list = StaticCString::create("_get_property_list"); _notification = StaticCString::create("_notification"); _script_source = StaticCString::create("script/source"); + on_before_serialize = StaticCString::create("OnBeforeSerialize"); + on_after_deserialize = StaticCString::create("OnAfterDeserialize"); dotctor = StaticCString::create(".ctor"); } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index a2f1ec8f27..eb168f344d 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -41,6 +41,10 @@ #include "mono_gd/gd_mono_header.h" #include "mono_gd/gd_mono_internals.h" +#ifdef TOOLS_ENABLED +#include "editor/editor_plugin.h" +#endif + class CSharpScript; class CSharpInstance; class CSharpLanguage; @@ -92,6 +96,8 @@ class CSharpScript : public Script { Set<ObjectID> pending_reload_instances; Map<ObjectID, StateBackup> pending_reload_state; + StringName tied_class_name_for_reload; + StringName tied_class_namespace_for_reload; #endif String source; @@ -115,6 +121,7 @@ class CSharpScript : public Script { bool placeholder_fallback_enabled; bool exports_invalidated; void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames); + void _update_member_info_no_exports(); virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); #endif @@ -127,7 +134,7 @@ class CSharpScript : public Script { bool _update_exports(); #ifdef TOOLS_ENABLED - bool _get_member_export(IMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported); + bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported); static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string); #endif @@ -137,6 +144,7 @@ class CSharpScript : public Script { // Do not use unless you know what you are doing friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *); static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native); + static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native); protected: static void _bind_methods(); @@ -226,6 +234,8 @@ class CSharpInstance : public ScriptInstance { MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const; + void get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state); + public: MonoObject *get_mono_object() const; @@ -276,6 +286,7 @@ struct CSharpScriptBinding { StringName type_name; GDMonoClass *wrapper_class; Ref<MonoGCHandle> gchandle; + Object *owner; }; class CSharpLanguage : public ScriptLanguage { @@ -305,6 +316,8 @@ class CSharpLanguage : public ScriptLanguage { StringName _notification; StringName _script_source; StringName dotctor; // .ctor + StringName on_before_serialize; // OnBeforeSerialize + StringName on_after_deserialize; // OnAfterDeserialize StringNameCache(); }; @@ -324,6 +337,12 @@ class CSharpLanguage : public ScriptLanguage { friend class GDMono; void _on_scripts_domain_unloaded(); +#ifdef TOOLS_ENABLED + EditorPlugin *godotsharp_editor; + + static void _editor_init_callback(); +#endif + public: StringNameCache string_names; @@ -336,6 +355,10 @@ public: _FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; } +#ifdef TOOLS_ENABLED + _FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; } +#endif + static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle); static void release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle); diff --git a/modules/mono/editor/GodotSharpTools/.gitignore b/modules/mono/editor/GodotSharpTools/.gitignore deleted file mode 100644 index 296ad48834..0000000000 --- a/modules/mono/editor/GodotSharpTools/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# nuget packages -packages
\ No newline at end of file diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs deleted file mode 100644 index e5044feb75..0000000000 --- a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs +++ /dev/null @@ -1,425 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security; -using Microsoft.Build.Framework; - -namespace GodotSharpTools.Build -{ - public class BuildInstance : IDisposable - { - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode); - - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static string godot_icall_BuildInstance_get_MSBuildPath(); - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static string godot_icall_BuildInstance_get_MonoWindowsBinDir(); - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static bool godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows(); - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static bool godot_icall_BuildInstance_get_PrintBuildOutput(); - - private static string GetMSBuildPath() - { - string msbuildPath = godot_icall_BuildInstance_get_MSBuildPath(); - - if (msbuildPath == null) - throw new FileNotFoundException("Cannot find the MSBuild executable."); - - return msbuildPath; - } - - private static string MonoWindowsBinDir - { - get - { - string monoWinBinDir = godot_icall_BuildInstance_get_MonoWindowsBinDir(); - - if (monoWinBinDir == null) - throw new FileNotFoundException("Cannot find the Windows Mono binaries directory."); - - return monoWinBinDir; - } - } - - private static bool UsingMonoMSBuildOnWindows - { - get - { - return godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows(); - } - } - - private static bool PrintBuildOutput - { - get - { - return godot_icall_BuildInstance_get_PrintBuildOutput(); - } - } - - private string solution; - private string config; - - private Process process; - - private int exitCode; - public int ExitCode { get { return exitCode; } } - - public bool IsRunning { get { return process != null && !process.HasExited; } } - - public BuildInstance(string solution, string config) - { - this.solution = solution; - this.config = config; - } - - public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) - { - List<string> customPropertiesList = new List<string>(); - - if (customProperties != null) - customPropertiesList.AddRange(customProperties); - - string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList); - - ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs); - - bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput; - - if (!redirectOutput) // TODO: or if stdout verbose - Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); - - startInfo.RedirectStandardOutput = redirectOutput; - startInfo.RedirectStandardError = redirectOutput; - startInfo.UseShellExecute = false; - - if (UsingMonoMSBuildOnWindows) - { - // These environment variables are required for Mono's MSBuild to find the compilers. - // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. - string monoWinBinDir = MonoWindowsBinDir; - startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); - startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); - startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); - } - - // Needed when running from Developer Command Prompt for VS - RemovePlatformVariable(startInfo.EnvironmentVariables); - - using (Process process = new Process()) - { - process.StartInfo = startInfo; - - process.Start(); - - if (redirectOutput) - { - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - } - - process.WaitForExit(); - - exitCode = process.ExitCode; - } - - return true; - } - - public bool BuildAsync(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) - { - if (process != null) - throw new InvalidOperationException("Already in use"); - - List<string> customPropertiesList = new List<string>(); - - if (customProperties != null) - customPropertiesList.AddRange(customProperties); - - string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList); - - ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs); - - bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput; - - if (!redirectOutput) // TODO: or if stdout verbose - Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); - - startInfo.RedirectStandardOutput = redirectOutput; - startInfo.RedirectStandardError = redirectOutput; - startInfo.UseShellExecute = false; - - if (UsingMonoMSBuildOnWindows) - { - // These environment variables are required for Mono's MSBuild to find the compilers. - // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. - string monoWinBinDir = MonoWindowsBinDir; - startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); - startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); - startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); - } - - // Needed when running from Developer Command Prompt for VS - RemovePlatformVariable(startInfo.EnvironmentVariables); - - process = new Process(); - process.StartInfo = startInfo; - process.EnableRaisingEvents = true; - process.Exited += new EventHandler(BuildProcess_Exited); - - process.Start(); - - if (redirectOutput) - { - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - } - - return true; - } - - private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, List<string> customProperties) - { - string arguments = string.Format(@"""{0}"" /v:normal /t:Rebuild ""/p:{1}"" ""/l:{2},{3};{4}""", - solution, - "Configuration=" + config, - typeof(GodotBuildLogger).FullName, - loggerAssemblyPath, - loggerOutputDir - ); - - foreach (string customProperty in customProperties) - { - arguments += " /p:" + customProperty; - } - - return arguments; - } - - private void RemovePlatformVariable(StringDictionary environmentVariables) - { - // EnvironmentVariables is case sensitive? Seriously? - - List<string> platformEnvironmentVariables = new List<string>(); - - foreach (string env in environmentVariables.Keys) - { - if (env.ToUpper() == "PLATFORM") - platformEnvironmentVariables.Add(env); - } - - foreach (string env in platformEnvironmentVariables) - environmentVariables.Remove(env); - } - - private void BuildProcess_Exited(object sender, System.EventArgs e) - { - exitCode = process.ExitCode; - - godot_icall_BuildInstance_ExitCallback(solution, config, exitCode); - - Dispose(); - } - - private static bool IsDebugMSBuildRequested() - { - return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1"; - } - - public void Dispose() - { - if (process != null) - { - process.Dispose(); - process = null; - } - } - } - - public class GodotBuildLogger : ILogger - { - public string Parameters { get; set; } - public LoggerVerbosity Verbosity { get; set; } - - public void Initialize(IEventSource eventSource) - { - if (null == Parameters) - throw new LoggerException("Log directory was not set."); - - string[] parameters = Parameters.Split(new[] { ';' }); - - string logDir = parameters[0]; - - if (String.IsNullOrEmpty(logDir)) - throw new LoggerException("Log directory was not set."); - - if (parameters.Length > 1) - throw new LoggerException("Too many parameters passed."); - - string logFile = Path.Combine(logDir, "msbuild_log.txt"); - string issuesFile = Path.Combine(logDir, "msbuild_issues.csv"); - - try - { - if (!Directory.Exists(logDir)) - Directory.CreateDirectory(logDir); - - this.logStreamWriter = new StreamWriter(logFile); - this.issuesStreamWriter = new StreamWriter(issuesFile); - } - catch (Exception ex) - { - if - ( - ex is UnauthorizedAccessException - || ex is ArgumentNullException - || ex is PathTooLongException - || ex is DirectoryNotFoundException - || ex is NotSupportedException - || ex is ArgumentException - || ex is SecurityException - || ex is IOException - ) - { - throw new LoggerException("Failed to create log file: " + ex.Message); - } - else - { - // Unexpected failure - throw; - } - } - - eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted); - eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted); - eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised); - eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised); - eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised); - eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished); - } - - void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) - { - string line = String.Format("{0}({1},{2}): error {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message); - - if (e.ProjectFile.Length > 0) - line += string.Format(" [{0}]", e.ProjectFile); - - WriteLine(line); - - string errorLine = String.Format(@"error,{0},{1},{2},{3},{4},{5}", - e.File.CsvEscape(), e.LineNumber, e.ColumnNumber, - e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile.CsvEscape()); - issuesStreamWriter.WriteLine(errorLine); - } - - void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) - { - string line = String.Format("{0}({1},{2}): warning {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, e.ProjectFile); - - if (e.ProjectFile != null && e.ProjectFile.Length > 0) - line += string.Format(" [{0}]", e.ProjectFile); - - WriteLine(line); - - string warningLine = String.Format(@"warning,{0},{1},{2},{3},{4},{5}", - e.File.CsvEscape(), e.LineNumber, e.ColumnNumber, - e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty); - issuesStreamWriter.WriteLine(warningLine); - } - - void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) - { - // BuildMessageEventArgs adds Importance to BuildEventArgs - // Let's take account of the verbosity setting we've been passed in deciding whether to log the message - if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal)) - || (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal)) - || (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed)) - ) - { - WriteLineWithSenderAndMessage(String.Empty, e); - } - } - - void eventSource_TaskStarted(object sender, TaskStartedEventArgs e) - { - // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName - // To keep this log clean, this logger will ignore these events. - } - - void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) - { - WriteLine(e.Message); - indent++; - } - - void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) - { - indent--; - WriteLine(e.Message); - } - - /// <summary> - /// Write a line to the log, adding the SenderName - /// </summary> - private void WriteLineWithSender(string line, BuildEventArgs e) - { - if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/)) - { - // Well, if the sender name is MSBuild, let's leave it out for prettiness - WriteLine(line); - } - else - { - WriteLine(e.SenderName + ": " + line); - } - } - - /// <summary> - /// Write a line to the log, adding the SenderName and Message - /// (these parameters are on all MSBuild event argument objects) - /// </summary> - private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e) - { - if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/)) - { - // Well, if the sender name is MSBuild, let's leave it out for prettiness - WriteLine(line + e.Message); - } - else - { - WriteLine(e.SenderName + ": " + line + e.Message); - } - } - - private void WriteLine(string line) - { - for (int i = indent; i > 0; i--) - { - logStreamWriter.Write("\t"); - } - logStreamWriter.WriteLine(line); - } - - public void Shutdown() - { - logStreamWriter.Close(); - issuesStreamWriter.Close(); - } - - public bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) - { - return this.Verbosity >= checkVerbosity; - } - - private StreamWriter logStreamWriter; - private StreamWriter issuesStreamWriter; - private int indent; - } -} diff --git a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs b/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs deleted file mode 100644 index 44a43f0ddd..0000000000 --- a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace GodotSharpTools.Editor -{ - public static class GodotSharpExport - { - public static void _ExportBegin(string[] features, bool debug, string path, int flags) - { - var featureSet = new HashSet<string>(features); - - if (PlatformHasTemplateDir(featureSet)) - { - 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(GetTemplatesDir(), templateDirName); - - if (!Directory.Exists(templateDirPath)) - throw new FileNotFoundException("Data template directory not found"); - - string outputDir = new FileInfo(path).Directory.FullName; - - string outputDataDir = Path.Combine(outputDir, GetDataDirName()); - - 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))); - } - } - } - - public static bool PlatformHasTemplateDir(HashSet<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)); - } - - [MethodImpl(MethodImplOptions.InternalCall)] - extern static string GetTemplatesDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - extern static string GetDataDirName(); - } -} diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln b/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln deleted file mode 100644 index 5f7d0e8a39..0000000000 --- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln +++ /dev/null @@ -1,17 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharpTools", "GodotSharpTools.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs deleted file mode 100644 index a13f4fd6ef..0000000000 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using DotNet.Globbing; -using Microsoft.Build.Construction; - -namespace GodotSharpTools.Project -{ - public static class ProjectUtils - { - public static void AddItemToProjectChecked(string projectPath, string itemType, string include) - { - var dir = Directory.GetParent(projectPath).FullName; - var root = ProjectRootElement.Open(projectPath); - var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\"); - - if (root.AddItemChecked(itemType, normalizedInclude)) - root.Save(); - } - - private static string[] GetAllFilesRecursive(string rootDirectory, string mask) - { - string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories); - - // We want relative paths - for (int i = 0; i < files.Length; i++) { - files[i] = files[i].RelativeToPath(rootDirectory); - } - - return files; - } - - public static string[] GetIncludeFiles(string projectPath, string itemType) - { - var result = new List<string>(); - var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs"); - - GlobOptions globOptions = new GlobOptions(); - globOptions.Evaluation.CaseInsensitive = false; - - var root = ProjectRootElement.Open(projectPath); - - foreach (var itemGroup in root.ItemGroups) - { - if (itemGroup.Condition.Length != 0) - continue; - - foreach (var item in itemGroup.Items) - { - if (item.ItemType != itemType) - continue; - - string normalizedInclude = item.Include.NormalizePath(); - - var glob = Glob.Parse(normalizedInclude, globOptions); - - // TODO Check somehow if path has no blog to avoid the following loop... - - foreach (var existingFile in existingFiles) - { - if (glob.IsMatch(existingFile)) - { - result.Add(existingFile); - } - } - } - } - - return result.ToArray(); - } - } -} diff --git a/modules/mono/editor/GodotSharpTools/Utils/OS.cs b/modules/mono/editor/GodotSharpTools/Utils/OS.cs deleted file mode 100644 index 148e954e77..0000000000 --- a/modules/mono/editor/GodotSharpTools/Utils/OS.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace GodotSharpTools.Utils -{ - public static class OS - { - [MethodImpl(MethodImplOptions.InternalCall)] - extern static 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() - { - return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsOSX() - { - return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsServer() - { - return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsUWP() - { - return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsWindows() - { - return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsX11() - { - return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - static bool? IsUnixCache = null; - static readonly string[] UnixPlatforms = new string[] { HaikuName, OSXName, ServerName, X11Name }; - - public static bool IsUnix() - { - if (IsUnixCache.HasValue) - return IsUnixCache.Value; - - string osName = GetPlatformName(); - IsUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase)); - return IsUnixCache.Value; - } - } -} diff --git a/modules/mono/editor/GodotTools/.gitignore b/modules/mono/editor/GodotTools/.gitignore new file mode 100644 index 0000000000..48e2f914d8 --- /dev/null +++ b/modules/mono/editor/GodotTools/.gitignore @@ -0,0 +1,356 @@ +# Rider +.idea/ + +# Visual Studio Code +.vscode/ + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs new file mode 100644 index 0000000000..a0f6f1ff32 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -0,0 +1,186 @@ +using System; +using System.IO; +using System.Security; +using Microsoft.Build.Framework; +using GodotTools.Core; + +namespace GodotTools.BuildLogger +{ + public class GodotBuildLogger : ILogger + { + public static readonly string AssemblyPath = Path.GetFullPath(typeof(GodotBuildLogger).Assembly.Location); + + public string Parameters { get; set; } + public LoggerVerbosity Verbosity { get; set; } + + public void Initialize(IEventSource eventSource) + { + if (null == Parameters) + throw new LoggerException("Log directory was not set."); + + var parameters = Parameters.Split(new[] {';'}); + + string logDir = parameters[0]; + + if (string.IsNullOrEmpty(logDir)) + throw new LoggerException("Log directory was not set."); + + if (parameters.Length > 1) + throw new LoggerException("Too many parameters passed."); + + string logFile = Path.Combine(logDir, "msbuild_log.txt"); + string issuesFile = Path.Combine(logDir, "msbuild_issues.csv"); + + try + { + if (!Directory.Exists(logDir)) + Directory.CreateDirectory(logDir); + + logStreamWriter = new StreamWriter(logFile); + issuesStreamWriter = new StreamWriter(issuesFile); + } + catch (Exception ex) + { + if (ex is UnauthorizedAccessException + || ex is ArgumentNullException + || ex is PathTooLongException + || ex is DirectoryNotFoundException + || ex is NotSupportedException + || ex is ArgumentException + || ex is SecurityException + || ex is IOException) + { + throw new LoggerException("Failed to create log file: " + ex.Message); + } + else + { + // Unexpected failure + throw; + } + } + + eventSource.ProjectStarted += eventSource_ProjectStarted; + eventSource.TaskStarted += eventSource_TaskStarted; + eventSource.MessageRaised += eventSource_MessageRaised; + eventSource.WarningRaised += eventSource_WarningRaised; + eventSource.ErrorRaised += eventSource_ErrorRaised; + eventSource.ProjectFinished += eventSource_ProjectFinished; + } + + void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) + { + string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}"; + + if (e.ProjectFile.Length > 0) + line += $" [{e.ProjectFile}]"; + + WriteLine(line); + + string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + $@"{e.Code.CsvEscape()},{e.Message.CsvEscape()},{e.ProjectFile.CsvEscape()}"; + issuesStreamWriter.WriteLine(errorLine); + } + + void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) + { + string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}"; + + if (!string.IsNullOrEmpty(e.ProjectFile)) + line += $" [{e.ProjectFile}]"; + + WriteLine(line); + + string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber},{e.Code.CsvEscape()}," + + $@"{e.Message.CsvEscape()},{(e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty)}"; + issuesStreamWriter.WriteLine(warningLine); + } + + private void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) + { + // BuildMessageEventArgs adds Importance to BuildEventArgs + // Let's take account of the verbosity setting we've been passed in deciding whether to log the message + if (e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal) + || e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal) + || e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed)) + { + WriteLineWithSenderAndMessage(string.Empty, e); + } + } + + private void eventSource_TaskStarted(object sender, TaskStartedEventArgs e) + { + // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName + // To keep this log clean, this logger will ignore these events. + } + + private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) + { + WriteLine(e.Message); + indent++; + } + + private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) + { + indent--; + WriteLine(e.Message); + } + + /// <summary> + /// Write a line to the log, adding the SenderName + /// </summary> + private void WriteLineWithSender(string line, BuildEventArgs e) + { + if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase)) + { + // Well, if the sender name is MSBuild, let's leave it out for prettiness + WriteLine(line); + } + else + { + WriteLine(e.SenderName + ": " + line); + } + } + + /// <summary> + /// Write a line to the log, adding the SenderName and Message + /// (these parameters are on all MSBuild event argument objects) + /// </summary> + private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e) + { + if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase)) + { + // Well, if the sender name is MSBuild, let's leave it out for prettiness + WriteLine(line + e.Message); + } + else + { + WriteLine(e.SenderName + ": " + line + e.Message); + } + } + + private void WriteLine(string line) + { + for (int i = indent; i > 0; i--) + { + logStreamWriter.Write("\t"); + } + + logStreamWriter.WriteLine(line); + } + + public void Shutdown() + { + logStreamWriter.Close(); + issuesStreamWriter.Close(); + } + + private bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) + { + return Verbosity >= checkVerbosity; + } + + private StreamWriter logStreamWriter; + private StreamWriter issuesStreamWriter; + private int indent; + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj new file mode 100644 index 0000000000..f3ac353c0f --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GodotTools.BuildLogger</RootNamespace> + <AssemblyName>GodotTools.BuildLogger</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>portable</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>portable</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.Build.Framework" /> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="GodotBuildLogger.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> + <Project>{639e48bd-44e5-4091-8edd-22d36dc0768d}</Project> + <Name>GodotTools.Core</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..8717c4901e --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GodotTools.BuildLogger")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6CE9A984-37B1-4F8A-8FE9-609F05F071B3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj new file mode 100644 index 0000000000..f36b40f87c --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>GodotTools.Core</RootNamespace> + <AssemblyName>GodotTools.Core</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <Compile Include="ProcessExtensions.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="StringExtensions.cs" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs new file mode 100644 index 0000000000..43d40f2ad9 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace GodotTools.Core +{ + public static class ProcessExtensions + { + public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default(CancellationToken)) + { + var tcs = new TaskCompletionSource<bool>(); + + void ProcessExited(object sender, EventArgs e) + { + tcs.TrySetResult(true); + } + + process.EnableRaisingEvents = true; + process.Exited += ProcessExited; + + try + { + if (process.HasExited) + return; + + using (cancellationToken.Register(() => tcs.TrySetCanceled())) + { + await tcs.Task; + } + } + finally + { + process.Exited -= ProcessExited; + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..699ae6e741 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("GodotTools.Core")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/modules/mono/editor/GodotSharpTools/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index b0436d2f18..8cd7e76303 100644 --- a/modules/mono/editor/GodotSharpTools/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -1,7 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; -namespace GodotSharpTools +namespace GodotTools.Core { public static class StringExtensions { @@ -25,7 +26,7 @@ namespace GodotSharpTools path = path.Replace('\\', '/'); - string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + string[] parts = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim(); @@ -37,18 +38,40 @@ namespace GodotSharpTools public static bool IsAbsolutePath(this string path) { return path.StartsWith("/", StringComparison.Ordinal) || - path.StartsWith("\\", StringComparison.Ordinal) || - path.StartsWith(driveRoot, StringComparison.Ordinal); + path.StartsWith("\\", StringComparison.Ordinal) || + path.StartsWith(driveRoot, StringComparison.Ordinal); } public static string CsvEscape(this string value, char delimiter = ',') { - bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1; + bool hasSpecialChar = value.IndexOfAny(new char[] {'\"', '\n', '\r', delimiter}) != -1; if (hasSpecialChar) return "\"" + value.Replace("\"", "\"\"") + "\""; return value; } + + public static string ToSafeDirName(this string dirName, bool allowDirSeparator) + { + var invalidChars = new List<string> {":", "*", "?", "\"", "<", ">", "|"}; + + if (allowDirSeparator) + { + // Directory separators are allowed, but disallow ".." to avoid going up the filesystem + invalidChars.Add(".."); + } + else + { + invalidChars.Add("/"); + } + + string safeDirName = dirName.Replace("\\", "/").Trim(); + + foreach (string invalidChar in invalidChars) + safeDirName = safeDirName.Replace(invalidChar, "-"); + + return safeDirName; + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs new file mode 100644 index 0000000000..345a472185 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs @@ -0,0 +1,15 @@ +namespace GodotTools +{ + public static class ApiAssemblyNames + { + public const string SolutionName = "GodotSharp"; + public const string Core = "GodotSharp"; + public const string Editor = "GodotSharpEditor"; + } + + public enum ApiAssemblyType + { + Core, + Editor + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs new file mode 100644 index 0000000000..bfae2afc13 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.IO; + +namespace GodotTools.ProjectEditor +{ + public static class ApiSolutionGenerator + { + public static void GenerateApiSolution(string solutionDir, + string coreProjDir, IEnumerable<string> coreCompileItems, + string editorProjDir, IEnumerable<string> editorCompileItems) + { + var solution = new DotNetSolution(ApiAssemblyNames.SolutionName); + + solution.DirectoryPath = solutionDir; + + // GodotSharp project + + const string coreApiAssemblyName = ApiAssemblyNames.Core; + + string coreGuid = ProjectGenerator.GenCoreApiProject(coreProjDir, coreCompileItems); + + var coreProjInfo = new DotNetSolution.ProjectInfo + { + Guid = coreGuid, + PathRelativeToSolution = Path.Combine(coreApiAssemblyName, $"{coreApiAssemblyName}.csproj") + }; + coreProjInfo.Configs.Add("Debug"); + coreProjInfo.Configs.Add("Release"); + + solution.AddNewProject(coreApiAssemblyName, coreProjInfo); + + // GodotSharpEditor project + + const string editorApiAssemblyName = ApiAssemblyNames.Editor; + + string editorGuid = ProjectGenerator.GenEditorApiProject(editorProjDir, + $"../{coreApiAssemblyName}/{coreApiAssemblyName}.csproj", editorCompileItems); + + var editorProjInfo = new DotNetSolution.ProjectInfo(); + editorProjInfo.Guid = editorGuid; + editorProjInfo.PathRelativeToSolution = Path.Combine(editorApiAssemblyName, $"{editorApiAssemblyName}.csproj"); + editorProjInfo.Configs.Add("Debug"); + editorProjInfo.Configs.Add("Release"); + + solution.AddNewProject(editorApiAssemblyName, editorProjInfo); + + // Save solution + + solution.Save(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs new file mode 100644 index 0000000000..76cb249acf --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs @@ -0,0 +1,122 @@ +using GodotTools.Core; +using System.Collections.Generic; +using System.IO; + +namespace GodotTools.ProjectEditor +{ + public class DotNetSolution + { + private string directoryPath; + private readonly Dictionary<string, ProjectInfo> projects = new Dictionary<string, ProjectInfo>(); + + public string Name { get; } + + public string DirectoryPath + { + get => directoryPath; + set => directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value); + } + + public class ProjectInfo + { + public string Guid; + public string PathRelativeToSolution; + public List<string> Configs = new List<string>(); + } + + public void AddNewProject(string name, ProjectInfo projectInfo) + { + projects[name] = projectInfo; + } + + public bool HasProject(string name) + { + return projects.ContainsKey(name); + } + + public ProjectInfo GetProjectInfo(string name) + { + return projects[name]; + } + + public bool RemoveProject(string name) + { + return projects.Remove(name); + } + + public void Save() + { + if (!Directory.Exists(DirectoryPath)) + throw new FileNotFoundException("The solution directory does not exist."); + + string projectsDecl = string.Empty; + string slnPlatformsCfg = string.Empty; + string projPlatformsCfg = string.Empty; + + bool isFirstProject = true; + + foreach (var pair in projects) + { + string name = pair.Key; + ProjectInfo projectInfo = pair.Value; + + if (!isFirstProject) + projectsDecl += "\n"; + + projectsDecl += string.Format(ProjectDeclaration, + name, projectInfo.PathRelativeToSolution.Replace("/", "\\"), projectInfo.Guid); + + for (int i = 0; i < projectInfo.Configs.Count; i++) + { + string config = projectInfo.Configs[i]; + + if (i != 0 || !isFirstProject) + { + slnPlatformsCfg += "\n"; + projPlatformsCfg += "\n"; + } + + slnPlatformsCfg += string.Format(SolutionPlatformsConfig, config); + projPlatformsCfg += string.Format(ProjectPlatformsConfig, projectInfo.Guid, config); + } + + isFirstProject = false; + } + + string solutionPath = Path.Combine(DirectoryPath, Name + ".sln"); + string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg); + + File.WriteAllText(solutionPath, content); + } + + public DotNetSolution(string name) + { + Name = name; + } + + const string SolutionTemplate = +@"Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +{0} +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution +{1} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution +{2} + EndGlobalSection +EndGlobal +"; + + const string ProjectDeclaration = +@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}"" +EndProject"; + + const string SolutionPlatformsConfig = +@" {0}|Any CPU = {0}|Any CPU"; + + const string ProjectPlatformsConfig = +@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU + {{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU"; + } +} diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index 2871c041f5..08b8ba3946 100644 --- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -1,12 +1,12 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid> <OutputType>Library</OutputType> - <RootNamespace>GodotSharpTools</RootNamespace> - <AssemblyName>GodotSharpTools</AssemblyName> + <RootNamespace>GodotTools.ProjectEditor</RootNamespace> + <AssemblyName>GodotTools.ProjectEditor</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath> </PropertyGroup> @@ -21,7 +21,6 @@ <ConsolePause>false</ConsolePause> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>portable</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> @@ -31,25 +30,35 @@ <ItemGroup> <Reference Include="System" /> <Reference Include="Microsoft.Build" /> - <Reference Include="Microsoft.Build.Framework" /> <Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL"> - <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <!-- + When building Godot with 'mono_glue=no' SCons will build this project alone instead of the + entire solution. $(SolutionDir) is not defined in that case, so we need to workaround that. + We make SCons restore the NuGet packages in the project directory instead in this case. + --> + <HintPath Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <HintPath>$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> <!-- Are you happy CI? --> </Reference> </ItemGroup> <ItemGroup> - <Compile Include="StringExtensions.cs" /> - <Compile Include="Build\BuildSystem.cs" /> - <Compile Include="Editor\MonoDevelopInstance.cs" /> - <Compile Include="Project\ProjectExtensions.cs" /> - <Compile Include="Project\IdentifierUtils.cs" /> - <Compile Include="Project\ProjectGenerator.cs" /> - <Compile Include="Project\ProjectUtils.cs" /> + <Compile Include="ApiAssembliesInfo.cs" /> + <Compile Include="ApiSolutionGenerator.cs" /> + <Compile Include="DotNetSolution.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Utils\OS.cs" /> - <Compile Include="Editor\GodotSharpExport.cs" /> + <Compile Include="IdentifierUtils.cs" /> + <Compile Include="ProjectExtensions.cs" /> + <Compile Include="ProjectGenerator.cs" /> + <Compile Include="ProjectUtils.cs" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> + <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project> + <Name>GodotTools.Core</Name> + </ProjectReference> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> -</Project>
\ No newline at end of file +</Project> diff --git a/modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs index 83e2d2cf8d..f93eb9a1fa 100644 --- a/modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class IdentifierUtils { @@ -12,7 +12,7 @@ namespace GodotSharpTools.Project if (string.IsNullOrEmpty(qualifiedIdentifier)) throw new ArgumentException($"{nameof(qualifiedIdentifier)} cannot be empty", nameof(qualifiedIdentifier)); - string[] identifiers = qualifiedIdentifier.Split(new[] { '.' }); + string[] identifiers = qualifiedIdentifier.Split('.'); for (int i = 0; i < identifiers.Length; i++) { @@ -66,8 +66,6 @@ namespace GodotSharpTools.Project if (identifierBuilder.Length > startIndex || @char == '_') identifierBuilder.Append(@char); break; - default: - break; } } @@ -97,14 +95,14 @@ namespace GodotSharpTools.Project } else { - if (_doubleUnderscoreKeywords.Contains(value)) + if (DoubleUnderscoreKeywords.Contains(value)) return true; } - return _keywords.Contains(value); + return Keywords.Contains(value); } - static HashSet<string> _doubleUnderscoreKeywords = new HashSet<string> + private static readonly HashSet<string> DoubleUnderscoreKeywords = new HashSet<string> { "__arglist", "__makeref", @@ -112,7 +110,7 @@ namespace GodotSharpTools.Project "__refvalue", }; - static HashSet<string> _keywords = new HashSet<string> + private static readonly HashSet<string> Keywords = new HashSet<string> { "as", "do", diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs index 647d9ac81d..36961eb45e 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs @@ -1,8 +1,9 @@ +using GodotTools.Core; using System; using DotNet.Globbing; using Microsoft.Build.Construction; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class ProjectExtensions { diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index f4ab11a222..4f21871f1a 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -1,17 +1,19 @@ +using GodotTools.Core; using System; +using System.Collections.Generic; using System.IO; using Microsoft.Build.Construction; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class ProjectGenerator { - public const string CoreApiProjectName = "GodotSharp"; - public const string EditorApiProjectName = "GodotSharpEditor"; - const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"; - const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}"; + private const string CoreApiProjectName = "GodotSharp"; + private const string EditorApiProjectName = "GodotSharpEditor"; + private const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"; + private const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}"; - public static string GenCoreApiProject(string dir, string[] compileItems) + public static string GenCoreApiProject(string dir, IEnumerable<string> compileItems) { string path = Path.Combine(dir, CoreApiProjectName + ".csproj"); @@ -24,8 +26,8 @@ namespace GodotSharpTools.Project mainGroup.SetProperty("BaseIntermediateOutputPath", "obj"); GenAssemblyInfoFile(root, dir, CoreApiProjectName, - new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]" }, - new string[] { "System.Runtime.CompilerServices" }); + new[] {"[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]"}, + new[] {"System.Runtime.CompilerServices"}); foreach (var item in compileItems) { @@ -37,7 +39,7 @@ namespace GodotSharpTools.Project return CoreApiProjectGuid; } - public static string GenEditorApiProject(string dir, string coreApiProjPath, string[] compileItems) + public static string GenEditorApiProject(string dir, string coreApiProjPath, IEnumerable<string> compileItems) { string path = Path.Combine(dir, EditorApiProjectName + ".csproj"); @@ -64,7 +66,7 @@ namespace GodotSharpTools.Project return EditorApiProjectGuid; } - public static string GenGameProject(string dir, string name, string[] compileItems) + public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems) { string path = Path.Combine(dir, name + ".csproj"); @@ -74,6 +76,8 @@ namespace GodotSharpTools.Project mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)")); mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj")); mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)")); + mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'Release' "; + mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'Release' "; var toolsGroup = root.AddPropertyGroup(); toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' "; @@ -86,12 +90,12 @@ namespace GodotSharpTools.Project toolsGroup.AddProperty("ConsolePause", "false"); var coreApiRef = root.AddItem("Reference", CoreApiProjectName); - coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProjectName + ".dll")); + coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll")); coreApiRef.AddMetadata("Private", "False"); var editorApiRef = root.AddItem("Reference", EditorApiProjectName); editorApiRef.Condition = " '$(Configuration)' == 'Tools' "; - editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProjectName + ".dll")); + editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll")); editorApiRef.AddMetadata("Private", "False"); GenAssemblyInfoFile(root, dir, name); @@ -108,7 +112,6 @@ namespace GodotSharpTools.Project public static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null) { - string propertiesDir = Path.Combine(dir, "Properties"); if (!Directory.Exists(propertiesDir)) Directory.CreateDirectory(propertiesDir); @@ -124,12 +127,9 @@ namespace GodotSharpTools.Project string assemblyLinesText = string.Empty; if (assemblyLines != null) - { - foreach (var assemblyLine in assemblyLines) - assemblyLinesText += string.Join("\n", assemblyLines) + "\n"; - } + assemblyLinesText += string.Join("\n", assemblyLines) + "\n"; - string content = string.Format(assemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText); + string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText); string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs"); @@ -194,8 +194,8 @@ namespace GodotSharpTools.Project } } - private const string assemblyInfoTemplate = -@"using System.Reflection;{0} + private const string AssemblyInfoTemplate = + @"using System.Reflection;{0} // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs new file mode 100644 index 0000000000..1edc426e00 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -0,0 +1,175 @@ +using GodotTools.Core; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using DotNet.Globbing; +using Microsoft.Build.Construction; + +namespace GodotTools.ProjectEditor +{ + public static class ProjectUtils + { + public static void AddItemToProjectChecked(string projectPath, string itemType, string include) + { + var dir = Directory.GetParent(projectPath).FullName; + var root = ProjectRootElement.Open(projectPath); + Debug.Assert(root != null); + + var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\"); + + if (root.AddItemChecked(itemType, normalizedInclude)) + root.Save(); + } + + private static string[] GetAllFilesRecursive(string rootDirectory, string mask) + { + string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories); + + // We want relative paths + for (int i = 0; i < files.Length; i++) + { + files[i] = files[i].RelativeToPath(rootDirectory); + } + + return files; + } + + public static string[] GetIncludeFiles(string projectPath, string itemType) + { + var result = new List<string>(); + var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs"); + + var globOptions = new GlobOptions(); + globOptions.Evaluation.CaseInsensitive = false; + + var root = ProjectRootElement.Open(projectPath); + + foreach (var itemGroup in root.ItemGroups) + { + if (itemGroup.Condition.Length != 0) + continue; + + foreach (var item in itemGroup.Items) + { + if (item.ItemType != itemType) + continue; + + string normalizedInclude = item.Include.NormalizePath(); + + var glob = Glob.Parse(normalizedInclude, globOptions); + + // TODO Check somehow if path has no blob to avoid the following loop... + + foreach (var existingFile in existingFiles) + { + if (glob.IsMatch(existingFile)) + { + result.Add(existingFile); + } + } + } + } + + return result.ToArray(); + } + + /// Simple function to make sure the Api assembly references are configured correctly + public static void FixApiHintPath(string projectPath) + { + var root = ProjectRootElement.Open(projectPath); + Debug.Assert(root != null); + + bool dirty = false; + + void AddPropertyIfNotPresent(string name, string condition, string value) + { + if (root.PropertyGroups + .Any(g => g.Condition == string.Empty || g.Condition == condition && + g.Properties + .Any(p => p.Name == name && + p.Value == value && + (p.Condition == condition || g.Condition == condition)))) + { + return; + } + + root.AddProperty(name, value).Condition = condition; + dirty = true; + } + + AddPropertyIfNotPresent(name: "ApiConfiguration", + condition: " '$(Configuration)' != 'Release' ", + value: "Debug"); + AddPropertyIfNotPresent(name: "ApiConfiguration", + condition: " '$(Configuration)' == 'Release' ", + value: "Release"); + + void SetReferenceHintPath(string referenceName, string condition, string hintPath) + { + foreach (var itemGroup in root.ItemGroups.Where(g => + g.Condition == string.Empty || g.Condition == condition)) + { + var references = itemGroup.Items.Where(item => + item.ItemType == "Reference" && + item.Include == referenceName && + (item.Condition == condition || itemGroup.Condition == condition)); + + var referencesWithHintPath = references.Where(reference => + reference.Metadata.Any(m => m.Name == "HintPath")); + + if (referencesWithHintPath.Any(reference => reference.Metadata + .Any(m => m.Name == "HintPath" && m.Value == hintPath))) + { + // Found a Reference item with the right HintPath + return; + } + + var referenceWithHintPath = referencesWithHintPath.FirstOrDefault(); + if (referenceWithHintPath != null) + { + // Found a Reference item with a wrong HintPath + foreach (var metadata in referenceWithHintPath.Metadata.ToList() + .Where(m => m.Name == "HintPath")) + { + // Safe to remove as we duplicate with ToList() to loop + referenceWithHintPath.RemoveChild(metadata); + } + + referenceWithHintPath.AddMetadata("HintPath", hintPath); + dirty = true; + return; + } + + var referenceWithoutHintPath = references.FirstOrDefault(); + if (referenceWithoutHintPath != null) + { + // Found a Reference item without a HintPath + referenceWithoutHintPath.AddMetadata("HintPath", hintPath); + dirty = true; + return; + } + } + + // Found no Reference item at all. Add it. + root.AddItem("Reference", referenceName).Condition = condition; + dirty = true; + } + + const string coreProjectName = "GodotSharp"; + const string editorProjectName = "GodotSharpEditor"; + + const string coreCondition = ""; + const string editorCondition = " '$(Configuration)' == 'Tools' "; + + var coreHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{coreProjectName}.dll"; + var editorHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{editorProjectName}.dll"; + + SetReferenceHintPath(coreProjectName, coreCondition, coreHintPath); + SetReferenceHintPath(editorProjectName, editorCondition, editorHintPath); + + if (dirty) + root.Save(); + } + } +} diff --git a/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs index 7115d8fc71..09333850fc 100644 --- a/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs @@ -4,12 +4,12 @@ using System.Runtime.CompilerServices; // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. -[assembly: AssemblyTitle("GodotSharpTools")] +[assembly: AssemblyTitle("GodotTools.ProjectEditor")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("ignacio")] +[assembly: AssemblyCopyright("Godot Engine contributors")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/modules/mono/editor/GodotSharpTools/packages.config b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config index 2c7cb0bd4b..13915000e4 100644 --- a/modules/mono/editor/GodotSharpTools/packages.config +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <packages> <package id="DotNet.Glob" version="2.1.1" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln new file mode 100644 index 0000000000..6f7d44bec2 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -0,0 +1,35 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.ProjectEditor", "GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools", "GodotTools\GodotTools.csproj", "{27B00618-A6F2-4828-B922-05CAEB08C286}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotTools.Core\GodotTools.Core.csproj", "{639E48BD-44E5-4091-8EDD-22D36DC0768D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.Build.0 = Release|Any CPU + {27B00618-A6F2-4828-B922-05CAEB08C286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27B00618-A6F2-4828-B922-05CAEB08C286}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27B00618-A6F2-4828-B922-05CAEB08C286}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27B00618-A6F2-4828-B922-05CAEB08C286}.Release|Any CPU.Build.0 = Release|Any CPU + {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Release|Any CPU.Build.0 = Release|Any CPU + {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs new file mode 100644 index 0000000000..f849356919 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -0,0 +1,172 @@ +using GodotTools.Core; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using GodotTools.BuildLogger; +using GodotTools.Internals; +using GodotTools.Utils; +using Directory = System.IO.Directory; + +namespace GodotTools.Build +{ + public static class BuildSystem + { + private static string GetMsBuildPath() + { + string msbuildPath = MsBuildFinder.FindMsBuild(); + + if (msbuildPath == null) + throw new FileNotFoundException("Cannot find the MSBuild executable."); + + return msbuildPath; + } + + private static string MonoWindowsBinDir + { + get + { + string monoWinBinDir = Path.Combine(Internal.MonoWindowsInstallRoot, "bin"); + + if (!Directory.Exists(monoWinBinDir)) + throw new FileNotFoundException("Cannot find the Windows Mono install bin directory."); + + return monoWinBinDir; + } + } + + private static Godot.EditorSettings EditorSettings => + GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + + private static bool UsingMonoMsBuildOnWindows + { + get + { + if (OS.IsWindows()) + { + return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool") + == GodotSharpBuilds.BuildTool.MsBuildMono; + } + + return false; + } + } + + private static bool PrintBuildOutput => + (bool) EditorSettings.GetSetting("mono/builds/print_build_output"); + + private static Process LaunchBuild(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + { + var customPropertiesList = new List<string>(); + + if (customProperties != null) + customPropertiesList.AddRange(customProperties); + + string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList); + + var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs); + + bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput; + + if (!redirectOutput || Godot.OS.IsStdoutVerbose()) + Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); + + startInfo.RedirectStandardOutput = redirectOutput; + startInfo.RedirectStandardError = redirectOutput; + startInfo.UseShellExecute = false; + + if (UsingMonoMsBuildOnWindows) + { + // These environment variables are required for Mono's MSBuild to find the compilers. + // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. + string monoWinBinDir = MonoWindowsBinDir; + startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); + startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); + startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); + } + + // Needed when running from Developer Command Prompt for VS + RemovePlatformVariable(startInfo.EnvironmentVariables); + + var process = new Process {StartInfo = startInfo}; + + process.Start(); + + if (redirectOutput) + { + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + + return process; + } + + public static int Build(MonoBuildInfo monoBuildInfo) + { + return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration, + monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + } + + public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo) + { + return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration, + monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + } + + public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + { + using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties)) + { + process.WaitForExit(); + + return process.ExitCode; + } + } + + public static async Task<int> BuildAsync(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + { + using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties)) + { + await process.WaitForExitAsync(); + + return process.ExitCode; + } + } + + private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties) + { + string arguments = $@"""{solution}"" /v:normal /t:Rebuild ""/p:{"Configuration=" + config}"" " + + $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}"""; + + foreach (string customProperty in customProperties) + { + arguments += " /p:" + customProperty; + } + + return arguments; + } + + private static void RemovePlatformVariable(StringDictionary environmentVariables) + { + // EnvironmentVariables is case sensitive? Seriously? + + var platformEnvironmentVariables = new List<string>(); + + foreach (string env in environmentVariables.Keys) + { + if (env.ToUpper() == "PLATFORM") + platformEnvironmentVariables.Add(env); + } + + foreach (string env in platformEnvironmentVariables) + environmentVariables.Remove(env); + } + + private static bool IsDebugMsBuildRequested() + { + return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1"; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs new file mode 100644 index 0000000000..f0068385f4 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Godot; +using GodotTools.Internals; +using Directory = System.IO.Directory; +using Environment = System.Environment; +using File = System.IO.File; +using Path = System.IO.Path; +using OS = GodotTools.Utils.OS; + +namespace GodotTools.Build +{ + public static class MsBuildFinder + { + private static string _msbuildToolsPath = string.Empty; + private static string _msbuildUnixPath = string.Empty; + private static string _xbuildUnixPath = string.Empty; + + public static string FindMsBuild() + { + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); + + if (OS.IsWindows()) + { + switch (buildTool) + { + case GodotSharpBuilds.BuildTool.MsBuildVs: + { + if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _msbuildToolsPath = FindMsBuildToolsPathOnWindows(); + + if (_msbuildToolsPath.Empty()) + { + throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); + } + } + + if (!_msbuildToolsPath.EndsWith("\\")) + _msbuildToolsPath += "\\"; + + return Path.Combine(_msbuildToolsPath, "MSBuild.exe"); + } + + case GodotSharpBuilds.BuildTool.MsBuildMono: + { + string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat"); + + if (!File.Exists(msbuildPath)) + { + throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}"); + } + + return msbuildPath; + } + + case GodotSharpBuilds.BuildTool.XBuild: + { + string xbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "xbuild.bat"); + + if (!File.Exists(xbuildPath)) + { + throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameXbuild}'. Tried with path: {xbuildPath}"); + } + + return xbuildPath; + } + + default: + throw new IndexOutOfRangeException("Invalid build tool in editor settings"); + } + } + + if (OS.IsUnix()) + { + if (buildTool == GodotSharpBuilds.BuildTool.XBuild) + { + if (_xbuildUnixPath.Empty() || !File.Exists(_xbuildUnixPath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _xbuildUnixPath = FindBuildEngineOnUnix("msbuild"); + } + + if (_xbuildUnixPath.Empty()) + { + throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameXbuild}'"); + } + } + else + { + if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _msbuildUnixPath = FindBuildEngineOnUnix("msbuild"); + } + + if (_msbuildUnixPath.Empty()) + { + throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'"); + } + } + + return buildTool != GodotSharpBuilds.BuildTool.XBuild ? _msbuildUnixPath : _xbuildUnixPath; + } + + throw new PlatformNotSupportedException(); + } + + private static IEnumerable<string> MsBuildHintDirs + { + get + { + var result = new List<string>(); + + if (OS.IsOSX()) + { + result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/"); + result.Add("/usr/local/var/homebrew/linked/mono/bin/"); + } + + result.Add("/opt/novell/mono/bin/"); + + return result; + } + } + + private static string FindBuildEngineOnUnix(string name) + { + string ret = OS.PathWhich(name); + + if (!ret.Empty()) + return ret; + + string retFallback = OS.PathWhich($"{name}.exe"); + + if (!retFallback.Empty()) + return retFallback; + + foreach (string hintDir in MsBuildHintDirs) + { + string hintPath = Path.Combine(hintDir, name); + + if (File.Exists(hintPath)) + return hintPath; + } + + return string.Empty; + } + + private static string FindMsBuildToolsPathOnWindows() + { + if (!OS.IsWindows()) + throw new PlatformNotSupportedException(); + + // Try to find 15.0 with vswhere + + string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)"); + vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; + + var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"}; + + var outputArray = new Godot.Collections.Array<string>(); + int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs, + blocking: true, output: (Godot.Collections.Array) outputArray); + + if (exitCode == 0) + return string.Empty; + + if (outputArray.Count == 0) + return string.Empty; + + var lines = outputArray[0].Split('\n'); + + foreach (string line in lines) + { + int sepIdx = line.IndexOf(':'); + + if (sepIdx <= 0) + continue; + + string key = line.Substring(0, sepIdx); // No need to trim + + if (key != "installationPath") + continue; + + string value = line.Substring(sepIdx + 1).StripEdges(); + + if (value.Empty()) + throw new FormatException("installationPath value is empty"); + + if (!value.EndsWith("\\")) + value += "\\"; + + // Since VS2019, the directory is simply named "Current" + string msbuildDir = Path.Combine(value, "MSBuild\\Current\\Bin"); + + if (Directory.Exists(msbuildDir)) + return msbuildDir; + + // Directory name "15.0" is used in VS 2017 + return Path.Combine(value, "MSBuild\\15.0\\Bin"); + } + + return string.Empty; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs b/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs new file mode 100644 index 0000000000..4535ed7247 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs @@ -0,0 +1,127 @@ +using Godot; +using System; +using Godot.Collections; +using GodotTools.Internals; +using GodotTools.ProjectEditor; +using static GodotTools.Internals.Globals; +using File = GodotTools.Utils.File; +using Directory = GodotTools.Utils.Directory; + +namespace GodotTools +{ + public static class CSharpProject + { + public static string GenerateGameProject(string dir, string name) + { + try + { + return ProjectGenerator.GenGameProject(dir, name, compileItems: new string[] { }); + } + catch (Exception e) + { + GD.PushError(e.ToString()); + return string.Empty; + } + } + + public static void AddItem(string projectPath, string itemType, string include) + { + if (!(bool) GlobalDef("mono/project/auto_update_project", true)) + return; + + ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include); + } + + public static void FixApiHintPath(string projectPath) + { + try + { + ProjectUtils.FixApiHintPath(projectPath); + } + catch (Exception e) + { + GD.PushError(e.ToString()); + } + } + + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private static ulong ConvertToTimestamp(this DateTime value) + { + TimeSpan elapsedTime = value - Epoch; + return (ulong) elapsedTime.TotalSeconds; + } + + public static void GenerateScriptsMetadata(string projectPath, string outputPath) + { + if (File.Exists(outputPath)) + File.Delete(outputPath); + + var oldDict = Internal.GetScriptsMetadataOrNothing(); + var newDict = new Godot.Collections.Dictionary<string, object>(); + + foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile")) + { + string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath(); + + ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp(); + + if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar)) + { + var oldFileDict = (Dictionary) oldFileVar; + + if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime)) + { + if (storedModifiedTime == modifiedTime) + { + // No changes so no need to parse again + newDict[projectIncludeFile] = oldFileDict; + continue; + } + } + } + + ScriptClassParser.ParseFileOrThrow(projectIncludeFile, out var classes); + + string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile); + + var classDict = new Dictionary(); + + foreach (var classDecl in classes) + { + if (classDecl.BaseCount == 0) + continue; // Does not inherit nor implement anything, so it can't be a script class + + string classCmp = classDecl.Nested ? + classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) : + classDecl.Name; + + if (classCmp != searchName) + continue; + + classDict["namespace"] = classDecl.Namespace; + classDict["class_name"] = classDecl.Name; + classDict["nested"] = classDecl.Nested; + break; + } + + if (classDict.Count == 0) + continue; // Not found + + newDict[projectIncludeFile] = new Dictionary {["modified_time"] = $"{modifiedTime}", ["class"] = classDict}; + } + + if (newDict.Count > 0) + { + string json = JSON.Print(newDict); + + string baseDir = outputPath.GetBaseDir(); + + if (!Directory.Exists(baseDir)) + Directory.CreateDirectory(baseDir); + + File.WriteAllText(outputPath, json); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs new file mode 100644 index 0000000000..a884b0ead0 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using GodotTools.Build; +using GodotTools.Internals; +using GodotTools.Utils; +using static GodotTools.Internals.Globals; +using Error = Godot.Error; +using File = GodotTools.Utils.File; +using Directory = GodotTools.Utils.Directory; + +namespace GodotTools +{ + public static class GodotSharpBuilds + { + private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>(); + + public const string PropNameMsbuildMono = "MSBuild (Mono)"; + public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)"; + public const string PropNameXbuild = "xbuild (Deprecated)"; + + public const string MsBuildIssuesFileName = "msbuild_issues.csv"; + public const string MsBuildLogFileName = "msbuild_log.txt"; + + public enum BuildTool + { + MsBuildMono, + MsBuildVs, + XBuild // Deprecated + } + + private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo) + { + var issuesFile = GetIssuesFilePath(buildInfo); + + if (!File.Exists(issuesFile)) + return; + + File.Delete(issuesFile); + } + + private static string _ApiFolderName(ApiAssemblyType apiType) + { + ulong apiHash = apiType == ApiAssemblyType.Core ? + Internal.GetCoreApiHash() : + Internal.GetEditorApiHash(); + return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}"; + } + + private static void ShowBuildErrorDialog(string message) + { + GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error"); + GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab(); + } + + public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); + public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); + + private static string GetLogFilePath(MonoBuildInfo buildInfo) + { + return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName); + } + + private static string GetIssuesFilePath(MonoBuildInfo buildInfo) + { + return Path.Combine(Godot.ProjectSettings.LocalizePath(buildInfo.LogsDirPath), MsBuildIssuesFileName); + } + + private static void PrintVerbose(string text) + { + if (Godot.OS.IsStdoutVerbose()) + Godot.GD.Print(text); + } + + public static bool Build(MonoBuildInfo buildInfo) + { + if (BuildsInProgress.Contains(buildInfo)) + throw new InvalidOperationException("A build is already in progress"); + + BuildsInProgress.Add(buildInfo); + + try + { + MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + buildTab.OnBuildStart(); + + // Required in order to update the build tasks list + Internal.GodotMainIteration(); + + try + { + RemoveOldIssuesFile(buildInfo); + } + catch (IOException e) + { + buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + Console.Error.WriteLine(e); + } + + try + { + int exitCode = BuildSystem.Build(buildInfo); + + if (exitCode != 0) + PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); + + buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + + return exitCode == 0; + } + catch (Exception e) + { + buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + Console.Error.WriteLine(e); + return false; + } + } + finally + { + BuildsInProgress.Remove(buildInfo); + } + } + + public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo) + { + if (BuildsInProgress.Contains(buildInfo)) + throw new InvalidOperationException("A build is already in progress"); + + BuildsInProgress.Add(buildInfo); + + try + { + MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + + try + { + RemoveOldIssuesFile(buildInfo); + } + catch (IOException e) + { + buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + Console.Error.WriteLine(e); + } + + try + { + int exitCode = await BuildSystem.BuildAsync(buildInfo); + + if (exitCode != 0) + PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); + + buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + + return exitCode == 0; + } + catch (Exception e) + { + buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + Console.Error.WriteLine(e); + return false; + } + } + finally + { + BuildsInProgress.Remove(buildInfo); + } + } + + public static bool BuildApiSolution(string apiSlnDir, string config) + { + string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln"); + + string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config); + string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll"); + + string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config); + string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll"); + + if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile)) + return true; // The assemblies are in the output folder; assume the solution is already built + + var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config); + + // TODO Replace this global NoWarn with '#pragma warning' directives on generated files, + // once we start to actively document manually maintained C# classes + apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings + + if (Build(apiBuildInfo)) + return true; + + ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution."); + return false; + } + + public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines) + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return true; // No solution to build + + // Make sure to update the API assemblies if they happen to be missing. Just in + // case the user decided to delete them at some point after they were loaded. + Internal.UpdateApiAssembliesFromPrebuilt(); + + using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) + { + pr.Step("Building project solution", 0); + + var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config); + + // Add Godot defines + string constants = OS.IsWindows() ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\""; + + foreach (var godotDefine in godotDefines) + constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};"; + + if (Internal.GodotIsRealTDouble()) + constants += "GODOT_REAL_T_IS_DOUBLE;"; + + constants += OS.IsWindows() ? "\"" : "\\\""; + + buildInfo.CustomProperties.Add(constants); + + if (!Build(buildInfo)) + { + ShowBuildErrorDialog("Failed to build project solution"); + return false; + } + } + + return true; + } + + public static bool EditorBuildCallback() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return true; // No solution to build + + string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); + string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); + + CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + + if (File.Exists(editorScriptsMetadataPath)) + File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + + var godotDefines = new[] + { + Godot.OS.GetName(), + Internal.GodotIs32Bits() ? "32" : "64" + }; + + return BuildProjectBlocking("Tools", godotDefines); + } + + public static void Initialize() + { + // Build tool settings + + EditorDef("mono/builds/build_tool", OS.IsWindows() ? BuildTool.MsBuildVs : BuildTool.MsBuildMono); + + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + + editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = Godot.Variant.Type.Int, + ["name"] = "mono/builds/build_tool", + ["hint"] = Godot.PropertyHint.Enum, + ["hint_string"] = OS.IsWindows() ? + $"{PropNameMsbuildMono},{PropNameMsbuildVs},{PropNameXbuild}" : + $"{PropNameMsbuildMono},{PropNameXbuild}" + }); + + EditorDef("mono/builds/print_build_output", false); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs new file mode 100644 index 0000000000..9b5afb94a3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -0,0 +1,500 @@ +using Godot; +using GodotTools.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using GodotTools.Internals; +using GodotTools.ProjectEditor; +using static GodotTools.Internals.Globals; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; +using OS = GodotTools.Utils.OS; + +namespace GodotTools +{ + public class GodotSharpEditor : EditorPlugin, ISerializationListener + { + private EditorSettings editorSettings; + + private PopupMenu menuPopup; + + private AcceptDialog errorDialog; + private AcceptDialog aboutDialog; + private CheckBox aboutDialogCheckBox; + + private ToolButton bottomPanelBtn; + + private MonoDevelopInstance monoDevelopInstance; + private MonoDevelopInstance visualStudioForMacInstance; + + private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization + + public MonoBottomPanel MonoBottomPanel { get; private set; } + + private bool CreateProjectSolution() + { + using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2)) + { + pr.Step("Generating C# project...".TTR()); + + string resourceDir = ProjectSettings.GlobalizePath("res://"); + + string path = resourceDir; + string name = (string) ProjectSettings.GetSetting("application/config/name"); + if (name.Empty()) + name = "UnnamedProject"; + + string guid = CSharpProject.GenerateGameProject(path, name); + + if (guid.Length > 0) + { + var solution = new DotNetSolution(name) + { + DirectoryPath = path + }; + + var projectInfo = new DotNetSolution.ProjectInfo + { + Guid = guid, + PathRelativeToSolution = name + ".csproj", + Configs = new List<string> {"Debug", "Release", "Tools"} + }; + + solution.AddNewProject(name, projectInfo); + + try + { + solution.Save(); + } + catch (IOException e) + { + ShowErrorDialog("Failed to save solution. Exception message: ".TTR() + e.Message); + return false; + } + + // Make sure to update the API assemblies if they happen to be missing. Just in + // case the user decided to delete them at some point after they were loaded. + Internal.UpdateApiAssembliesFromPrebuilt(); + + pr.Step("Done".TTR()); + + // Here, after all calls to progress_task_step + CallDeferred(nameof(_RemoveCreateSlnMenuOption)); + } + else + { + ShowErrorDialog("Failed to create C# project.".TTR()); + } + + return true; + } + } + + private void _RemoveCreateSlnMenuOption() + { + menuPopup.RemoveItem(menuPopup.GetItemIndex((int) MenuOptions.CreateSln)); + bottomPanelBtn.Show(); + } + + private void _ShowAboutDialog() + { + bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start"); + aboutDialogCheckBox.Pressed = showOnStart; + aboutDialog.PopupCenteredMinsize(); + } + + private void _ToggleAboutDialogOnStart(bool enabled) + { + bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start"); + if (showOnStart != enabled) + editorSettings.SetSetting("mono/editor/show_info_on_start", enabled); + } + + private void _MenuOptionPressed(MenuOptions id) + { + switch (id) + { + case MenuOptions.CreateSln: + CreateProjectSolution(); + break; + case MenuOptions.AboutCSharp: + _ShowAboutDialog(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option"); + } + } + + private void _BuildSolutionPressed() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + { + if (!CreateProjectSolution()) + return; // Failed to create solution + } + + Instance.MonoBottomPanel.BuildProjectPressed(); + } + + public override void _Notification(int what) + { + base._Notification(what); + + if (what == NotificationReady) + { + bool showInfoDialog = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start"); + if (showInfoDialog) + { + aboutDialog.PopupExclusive = true; + _ShowAboutDialog(); + // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on. + aboutDialog.PopupExclusive = false; + } + } + } + + public enum MenuOptions + { + CreateSln, + AboutCSharp, + } + + public enum ExternalEditor + { + None, + VisualStudio, // TODO (Windows-only) + VisualStudioForMac, // Mac-only + MonoDevelop, + VsCode + } + + public void ShowErrorDialog(string message, string title = "Error") + { + errorDialog.WindowTitle = title; + errorDialog.DialogText = message; + errorDialog.PopupCenteredMinsize(); + } + + private static string _vsCodePath = string.Empty; + + private static readonly string[] VsCodeNames = + { + "code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss" + }; + + public Error OpenInExternalEditor(Script script, int line, int col) + { + var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor"); + + switch (editor) + { + case ExternalEditor.VsCode: + { + if (_vsCodePath.Empty() || !File.Exists(_vsCodePath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _vsCodePath = VsCodeNames.SelectFirstNotNull(OS.PathWhich, orElse: string.Empty); + } + + var args = new List<string>(); + + bool osxAppBundleInstalled = false; + + if (OS.IsOSX()) + { + // The package path is '/Applications/Visual Studio Code.app' + const string vscodeBundleId = "com.microsoft.VSCode"; + + osxAppBundleInstalled = Internal.IsOsxAppBundleInstalled(vscodeBundleId); + + if (osxAppBundleInstalled) + { + args.Add("-b"); + args.Add(vscodeBundleId); + + // The reusing of existing windows made by the 'open' command might not choose a wubdiw that is + // editing our folder. It's better to ask for a new window and let VSCode do the window management. + args.Add("-n"); + + // The open process must wait until the application finishes (which is instant in VSCode's case) + args.Add("--wait-apps"); + + args.Add("--args"); + } + } + + var resourcePath = ProjectSettings.GlobalizePath("res://"); + args.Add(resourcePath); + + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + if (line >= 0) + { + args.Add("-g"); + args.Add($"{scriptPath}:{line + 1}:{col}"); + } + else + { + args.Add(scriptPath); + } + + string command; + + if (OS.IsOSX()) + { + if (!osxAppBundleInstalled && _vsCodePath.Empty()) + { + GD.PushError("Cannot find code editor: VSCode"); + return Error.FileNotFound; + } + + command = osxAppBundleInstalled ? "/usr/bin/open" : _vsCodePath; + } + else + { + if (_vsCodePath.Empty()) + { + GD.PushError("Cannot find code editor: VSCode"); + return Error.FileNotFound; + } + + command = _vsCodePath; + } + + try + { + OS.RunProcess(command, args); + } + catch (Exception e) + { + GD.PushError($"Error when trying to run code editor: VSCode. Exception message: '{e.Message}'"); + } + + break; + } + + case ExternalEditor.VisualStudioForMac: + goto case ExternalEditor.MonoDevelop; + case ExternalEditor.MonoDevelop: + { + MonoDevelopInstance GetMonoDevelopInstance(string solutionPath) + { + if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac) + { + if (visualStudioForMacInstance == null) + visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac); + + return visualStudioForMacInstance; + } + + if (monoDevelopInstance == null) + monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop); + + return monoDevelopInstance; + } + + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + if (line >= 0) + scriptPath += $";{line + 1};{col}"; + + try + { + GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath); + } + catch (FileNotFoundException) + { + string editorName = editor == ExternalEditor.VisualStudioForMac ? "Visual Studio" : "MonoDevelop"; + GD.PushError($"Cannot find code editor: {editorName}"); + return Error.FileNotFound; + } + + break; + } + + case ExternalEditor.None: + return Error.Unavailable; + default: + throw new ArgumentOutOfRangeException(); + } + + return Error.Ok; + } + + public bool OverridesExternalEditor() + { + return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None; + } + + public override bool Build() + { + return GodotSharpBuilds.EditorBuildCallback(); + } + + public override void EnablePlugin() + { + base.EnablePlugin(); + + if (Instance != null) + throw new InvalidOperationException(); + Instance = this; + + var editorInterface = GetEditorInterface(); + var editorBaseControl = editorInterface.GetBaseControl(); + + editorSettings = editorInterface.GetEditorSettings(); + + errorDialog = new AcceptDialog(); + editorBaseControl.AddChild(errorDialog); + + MonoBottomPanel = new MonoBottomPanel(); + + bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono".TTR()); + + AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); + + menuPopup = new PopupMenu(); + menuPopup.Hide(); + menuPopup.SetAsToplevel(true); + + AddToolSubmenuItem("Mono", menuPopup); + + // TODO: Remove or edit this info dialog once Mono support is no longer in alpha + { + menuPopup.AddItem("About C# support".TTR(), (int) MenuOptions.AboutCSharp); + aboutDialog = new AcceptDialog(); + editorBaseControl.AddChild(aboutDialog); + aboutDialog.WindowTitle = "Important: C# support is not feature-complete"; + + // We don't use DialogText as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox + // we'll add. Instead we add containers and a new autowrapped Label inside. + + // Main VBoxContainer (icon + label on top, checkbox at bottom) + var aboutVBox = new VBoxContainer(); + aboutDialog.AddChild(aboutVBox); + + // HBoxContainer for icon + label + var aboutHBox = new HBoxContainer(); + aboutVBox.AddChild(aboutHBox); + + var aboutIcon = new TextureRect(); + aboutIcon.Texture = aboutIcon.GetIcon("NodeWarning", "EditorIcons"); + aboutHBox.AddChild(aboutIcon); + + var aboutLabel = new Label(); + aboutHBox.AddChild(aboutLabel); + aboutLabel.RectMinSize = new Vector2(600, 150) * EditorScale; + aboutLabel.SizeFlagsVertical = (int) Control.SizeFlags.ExpandFill; + aboutLabel.Autowrap = true; + aboutLabel.Text = + "C# support in Godot Engine is in late alpha stage and, while already usable, " + + "it is not meant for use in production.\n\n" + + "Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " + + "Bugs and usability issues will be addressed gradually over future releases, " + + "potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + + "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" + + " https://github.com/godotengine/godot/issues\n\n" + + "Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!"; + + EditorDef("mono/editor/show_info_on_start", true); + + // CheckBox in main container + aboutDialogCheckBox = new CheckBox {Text = "Show this warning when starting the editor"}; + aboutDialogCheckBox.Connect("toggled", this, nameof(_ToggleAboutDialogOnStart)); + aboutVBox.AddChild(aboutDialogCheckBox); + } + + if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) + { + // Make sure the existing project has Api assembly references configured correctly + CSharpProject.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); + } + else + { + bottomPanelBtn.Hide(); + menuPopup.AddItem("Create C# solution".TTR(), (int) MenuOptions.CreateSln); + } + + menuPopup.Connect("id_pressed", this, nameof(_MenuOptionPressed)); + + var buildButton = new ToolButton + { + Text = "Build", + HintTooltip = "Build solution", + FocusMode = Control.FocusModeEnum.None + }; + buildButton.Connect("pressed", this, nameof(_BuildSolutionPressed)); + AddControlToContainer(CustomControlContainer.Toolbar, buildButton); + + // External editor settings + EditorDef("mono/editor/external_editor", ExternalEditor.None); + + string settingsHintStr = "Disabled"; + + if (OS.IsWindows()) + { + settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + } + else if (OS.IsOSX()) + { + settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" + + $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + } + else if (OS.IsUnix()) + { + settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + } + + editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = Variant.Type.Int, + ["name"] = "mono/editor/external_editor", + ["hint"] = PropertyHint.Enum, + ["hint_string"] = settingsHintStr + }); + + // Export plugin + var exportPlugin = new GodotSharpExport(); + AddExportPlugin(exportPlugin); + exportPluginWeak = WeakRef(exportPlugin); + + GodotSharpBuilds.Initialize(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (exportPluginWeak != null) + { + // We need to dispose our export plugin before the editor destroys EditorSettings. + // 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.Dispose(); + } + } + + public void OnBeforeSerialize() + { + } + + public void OnAfterDeserialize() + { + Instance = this; + } + + // Singleton + + public static GodotSharpEditor Instance { get; private set; } + + private GodotSharpEditor() + { + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs new file mode 100644 index 0000000000..b80fe1fab7 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs @@ -0,0 +1,197 @@ +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, 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")}"); + CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); + + AddFile(scriptsMetadataPath, scriptsMetadataPath); + + // Turn export features into defines + var godotDefines = features; + + if (!GodotSharpBuilds.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 new file mode 100644 index 0000000000..01e8c87d14 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>GodotTools</RootNamespace> + <AssemblyName>GodotTools</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> + <GodotApiConfiguration>Debug</GodotApiConfiguration> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>portable</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="GodotSharp"> + <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath> + </Reference> + <Reference Include="GodotSharpEditor"> + <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharpEditor.dll</HintPath> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="Build\MsBuildFinder.cs" /> + <Compile Include="Internals\BindingsGenerator.cs" /> + <Compile Include="Internals\EditorProgress.cs" /> + <Compile Include="Internals\GodotSharpDirs.cs" /> + <Compile Include="Internals\Internal.cs" /> + <Compile Include="Internals\ScriptClassParser.cs" /> + <Compile Include="Internals\Globals.cs" /> + <Compile Include="MonoDevelopInstance.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Build\BuildSystem.cs" /> + <Compile Include="Utils\Directory.cs" /> + <Compile Include="Utils\File.cs" /> + <Compile Include="Utils\OS.cs" /> + <Compile Include="GodotSharpEditor.cs" /> + <Compile Include="GodotSharpBuilds.cs" /> + <Compile Include="HotReloadAssemblyWatcher.cs" /> + <Compile Include="MonoBuildInfo.cs" /> + <Compile Include="MonoBuildTab.cs" /> + <Compile Include="MonoBottomPanel.cs" /> + <Compile Include="GodotSharpExport.cs" /> + <Compile Include="CSharpProject.cs" /> + <Compile Include="Utils\CollectionExtensions.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj"> + <Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project> + <Name>GodotTools.BuildLogger</Name> + </ProjectReference> + <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj"> + <Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project> + <Name>GodotTools.ProjectEditor</Name> + </ProjectReference> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> + <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project> + <Name>GodotTools.Core</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Folder Include="Editor" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs new file mode 100644 index 0000000000..0f6f5ffadc --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -0,0 +1,48 @@ +using Godot; +using GodotTools.Internals; +using static GodotTools.Internals.Globals; + +namespace GodotTools +{ + public class HotReloadAssemblyWatcher : Node + { + private Timer watchTimer; + + public override void _Notification(int what) + { + if (what == MainLoop.NotificationWmFocusIn) + { + RestartTimer(); + + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + } + + private void TimerTimeout() + { + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + + public void RestartTimer() + { + watchTimer.Stop(); + watchTimer.Start(); + } + + public override void _Ready() + { + base._Ready(); + + watchTimer = new Timer + { + OneShot = false, + WaitTime = (float) EditorDef("mono/assembly_watch_interval_sec", 0.5) + }; + watchTimer.Connect("timeout", this, nameof(TimerTimeout)); + AddChild(watchTimer); + watchTimer.Start(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs new file mode 100644 index 0000000000..1daa5e138e --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs @@ -0,0 +1,87 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace GodotTools.Internals +{ + public class BindingsGenerator : IDisposable + { + class BindingsGeneratorSafeHandle : SafeHandle + { + public BindingsGeneratorSafeHandle(IntPtr handle) : base(IntPtr.Zero, true) + { + this.handle = handle; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + internal_Dtor(handle); + return true; + } + } + + private BindingsGeneratorSafeHandle safeHandle; + private bool disposed = false; + + public bool LogPrintEnabled + { + get => internal_LogPrintEnabled(GetPtr()); + set => internal_SetLogPrintEnabled(GetPtr(), value); + } + + public static uint Version => internal_Version(); + public static uint CsGlueVersion => internal_CsGlueVersion(); + + public Godot.Error GenerateCsApi(string outputDir) => internal_GenerateCsApi(GetPtr(), outputDir); + + internal IntPtr GetPtr() + { + if (disposed) + throw new ObjectDisposedException(GetType().FullName); + + return safeHandle.DangerousGetHandle(); + } + + public void Dispose() + { + if (disposed) + return; + + if (safeHandle != null && !safeHandle.IsInvalid) + { + safeHandle.Dispose(); + safeHandle = null; + } + + disposed = true; + } + + public BindingsGenerator() + { + safeHandle = new BindingsGeneratorSafeHandle(internal_Ctor()); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern IntPtr internal_Ctor(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_Dtor(IntPtr handle); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_LogPrintEnabled(IntPtr handle); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_SetLogPrintEnabled(IntPtr handle, bool enabled); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern Godot.Error internal_GenerateCsApi(IntPtr handle, string outputDir); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern uint internal_Version(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern uint internal_CsGlueVersion(); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs new file mode 100644 index 0000000000..70ba7c733a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.CompilerServices; +using Godot; + +namespace GodotTools.Internals +{ + public class EditorProgress : IDisposable + { + public string Task { get; } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_Create(string task, string label, int amount, bool canCancel); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_Dispose(string task); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_Step(string task, string state, int step, bool forceRefresh); + + public EditorProgress(string task, string label, int amount, bool canCancel = false) + { + Task = task; + internal_Create(task, label, amount, canCancel); + } + + ~EditorProgress() + { + // Should never rely on the GC to dispose EditorProgress. + // It should be disposed immediately when the task finishes. + GD.PushError("EditorProgress disposed by the Garbage Collector"); + Dispose(); + } + + public void Dispose() + { + internal_Dispose(Task); + GC.SuppressFinalize(this); + } + + public void Step(string state, int step = -1, bool forceRefresh = true) + { + internal_Step(Task, state, step, forceRefresh); + } + + public bool TryStep(string state, int step = -1, bool forceRefresh = true) + { + return internal_Step(Task, state, step, forceRefresh); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs new file mode 100644 index 0000000000..793f84fd77 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs @@ -0,0 +1,33 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace GodotTools.Internals +{ + public static class Globals + { + public static float EditorScale => internal_EditorScale(); + + public static object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) => + internal_GlobalDef(setting, defaultValue, restartIfChanged); + + public static object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) => + internal_EditorDef(setting, defaultValue, restartIfChanged); + + [SuppressMessage("ReSharper", "InconsistentNaming")] + public static string TTR(this string text) => internal_TTR(text); + + // Internal Calls + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern float internal_EditorScale(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern object internal_GlobalDef(string setting, object defaultValue, bool restartIfChanged); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern object internal_EditorDef(string setting, object defaultValue, bool restartIfChanged); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_TTR(string text); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs new file mode 100644 index 0000000000..ddf3b829b5 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -0,0 +1,91 @@ +using System.Runtime.CompilerServices; + +namespace GodotTools.Internals +{ + public static class GodotSharpDirs + { + public static string ResDataDir => internal_ResDataDir(); + public static string ResMetadataDir => internal_ResMetadataDir(); + public static string ResAssembliesBaseDir => internal_ResAssembliesBaseDir(); + public static string ResAssembliesDir => internal_ResAssembliesDir(); + public static string ResConfigDir => internal_ResConfigDir(); + public static string ResTempDir => internal_ResTempDir(); + public static string ResTempAssembliesBaseDir => internal_ResTempAssembliesBaseDir(); + public static string ResTempAssembliesDir => internal_ResTempAssembliesDir(); + + public static string MonoUserDir => internal_MonoUserDir(); + public static string MonoLogsDir => internal_MonoLogsDir(); + + #region Tools-only + public static string MonoSolutionsDir => internal_MonoSolutionsDir(); + public static string BuildLogsDirs => internal_BuildLogsDirs(); + + public static string ProjectSlnPath => internal_ProjectSlnPath(); + public static string ProjectCsProjPath => internal_ProjectCsProjPath(); + + public static string DataEditorToolsDir => internal_DataEditorToolsDir(); + public static string DataEditorPrebuiltApiDir => internal_DataEditorPrebuiltApiDir(); + #endregion + + public static string DataMonoEtcDir => internal_DataMonoEtcDir(); + public static string DataMonoLibDir => internal_DataMonoLibDir(); + + #region Windows-only + public static string DataMonoBinDir => internal_DataMonoBinDir(); + #endregion + + + #region Internal + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResDataDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResMetadataDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResAssembliesBaseDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResAssembliesDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResConfigDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResTempDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResTempAssembliesBaseDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResTempAssembliesDir(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoUserDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoLogsDir(); + + #region Tools-only + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoSolutionsDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_BuildLogsDirs(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ProjectSlnPath(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ProjectCsProjPath(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataEditorToolsDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataEditorPrebuiltApiDir(); + #endregion + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataMonoEtcDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataMonoLibDir(); + + #region Windows-only + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataMonoBinDir(); + #endregion + + #endregion + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs new file mode 100644 index 0000000000..9526dd3c6f --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -0,0 +1,99 @@ +using System; +using System.Runtime.CompilerServices; +using Godot; +using Godot.Collections; + +namespace GodotTools.Internals +{ + public static class Internal + { + public const string CSharpLanguageType = "CSharpScript"; + public const string CSharpLanguageExtension = "cs"; + + public static string UpdateApiAssembliesFromPrebuilt() => + internal_UpdateApiAssembliesFromPrebuilt(); + + public static string FullTemplatesDir => + internal_FullTemplatesDir(); + + public static string SimplifyGodotPath(this string path) => internal_SimplifyGodotPath(path); + + public static bool IsOsxAppBundleInstalled(string bundleId) => internal_IsOsxAppBundleInstalled(bundleId); + + public static bool GodotIs32Bits() => internal_GodotIs32Bits(); + + public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble(); + + public static void GodotMainIteration() => internal_GodotMainIteration(); + + public static ulong GetCoreApiHash() => internal_GetCoreApiHash(); + + public static ulong GetEditorApiHash() => internal_GetEditorApiHash(); + + public static bool IsAssembliesReloadingNeeded() => internal_IsAssembliesReloadingNeeded(); + + public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload); + + public static void ScriptEditorDebuggerReloadScripts() => internal_ScriptEditorDebuggerReloadScripts(); + + public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) => + internal_ScriptEditorEdit(resource, line, col, grabFocus); + + public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen(); + + public static Dictionary<string, object> GetScriptsMetadataOrNothing() => + internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>)); + + public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot(); + + // Internal Calls + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_UpdateApiAssembliesFromPrebuilt(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_FullTemplatesDir(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_SimplifyGodotPath(this string path); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_IsOsxAppBundleInstalled(string bundleId); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_GodotIs32Bits(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_GodotIsRealTDouble(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_GodotMainIteration(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern ulong internal_GetCoreApiHash(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern ulong internal_GetEditorApiHash(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_IsAssembliesReloadingNeeded(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_ReloadAssemblies(bool softReload); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_ScriptEditorDebuggerReloadScripts(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_EditorNodeShowScriptScreen(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoWindowsInstallRoot(); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs new file mode 100644 index 0000000000..2497d276a9 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Godot; +using Godot.Collections; + +namespace GodotTools.Internals +{ + public static class ScriptClassParser + { + public class ClassDecl + { + public string Name { get; } + public string Namespace { get; } + public bool Nested { get; } + public int BaseCount { get; } + + public ClassDecl(string name, string @namespace, bool nested, int baseCount) + { + Name = name; + Namespace = @namespace; + Nested = nested; + BaseCount = baseCount; + } + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes); + + public static void ParseFileOrThrow(string filePath, out IEnumerable<ClassDecl> classes) + { + var classesArray = new Array<Dictionary>(); + var error = internal_ParseFile(filePath, classesArray); + if (error != Error.Ok) + throw new Exception($"Failed to determine namespace and class for script: {filePath}. Parse error: {error}"); + + var classesList = new List<ClassDecl>(); + + foreach (var classDeclDict in classesArray) + { + classesList.Add(new ClassDecl( + (string) classDeclDict["name"], + (string) classDeclDict["namespace"], + (bool) classDeclDict["nested"], + (int) classDeclDict["base_count"] + )); + } + + classes = classesList; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs new file mode 100644 index 0000000000..53ff0891d5 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs @@ -0,0 +1,343 @@ +using Godot; +using System; +using System.IO; +using Godot.Collections; +using GodotTools.Internals; +using static GodotTools.Internals.Globals; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; + +namespace GodotTools +{ + public class MonoBottomPanel : VBoxContainer + { + private EditorInterface editorInterface; + + private TabContainer panelTabs; + + private VBoxContainer panelBuildsTab; + + private ItemList buildTabsList; + private TabContainer buildTabs; + + private ToolButton warningsBtn; + private ToolButton errorsBtn; + private Button viewLogBtn; + + private void _UpdateBuildTabsList() + { + buildTabsList.Clear(); + + int currentTab = buildTabs.CurrentTab; + + bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount(); + + for (int i = 0; i < buildTabs.GetChildCount(); i++) + { + var tab = (MonoBuildTab) buildTabs.GetChild(i); + + if (tab == null) + continue; + + string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution); + itemName += " [" + tab.BuildInfo.Configuration + "]"; + + buildTabsList.AddItem(itemName, tab.IconTexture); + + string itemTooltip = "Solution: " + tab.BuildInfo.Solution; + itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration; + itemTooltip += "\nStatus: "; + + if (tab.BuildExited) + itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored"; + else + itemTooltip += "Running"; + + if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error) + itemTooltip += $"\nErrors: {tab.ErrorCount}"; + + itemTooltip += $"\nWarnings: {tab.WarningCount}"; + + buildTabsList.SetItemTooltip(i, itemTooltip); + + if (noCurrentTab || currentTab == i) + { + buildTabsList.Select(i); + _BuildTabsItemSelected(i); + } + } + } + + public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo) + { + foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren())) + { + if (buildTab.BuildInfo.Equals(buildInfo)) + return buildTab; + } + + var newBuildTab = new MonoBuildTab(buildInfo); + AddBuildTab(newBuildTab); + + return newBuildTab; + } + + private void _BuildTabsItemSelected(int idx) + { + if (idx < 0 || idx >= buildTabs.GetTabCount()) + throw new IndexOutOfRangeException(); + + buildTabs.CurrentTab = idx; + if (!buildTabs.Visible) + buildTabs.Visible = true; + + warningsBtn.Visible = true; + errorsBtn.Visible = true; + viewLogBtn.Visible = true; + } + + private void _BuildTabsNothingSelected() + { + if (buildTabs.GetTabCount() != 0) + { + // just in case + buildTabs.Visible = false; + + // This callback is called when clicking on the empty space of the list. + // ItemList won't deselect the items automatically, so we must do it ourselves. + buildTabsList.UnselectAll(); + } + + warningsBtn.Visible = false; + errorsBtn.Visible = false; + viewLogBtn.Visible = false; + } + + private void _WarningsToggled(bool pressed) + { + int currentTab = buildTabs.CurrentTab; + + if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) + throw new InvalidOperationException("No tab selected"); + + var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + buildTab.WarningsVisible = pressed; + buildTab.UpdateIssuesList(); + } + + private void _ErrorsToggled(bool pressed) + { + int currentTab = buildTabs.CurrentTab; + + if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) + throw new InvalidOperationException("No tab selected"); + + var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + buildTab.ErrorsVisible = pressed; + buildTab.UpdateIssuesList(); + } + + public void BuildProjectPressed() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return; // No solution to build + + string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); + string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); + + CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + + if (File.Exists(editorScriptsMetadataPath)) + { + try + { + File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + } + catch (IOException e) + { + GD.PushError($"Failed to copy scripts metadata file. Exception message: {e.Message}"); + return; + } + } + + var godotDefines = new[] + { + OS.GetName(), + Internal.GodotIs32Bits() ? "32" : "64" + }; + + bool buildSuccess = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines); + + if (!buildSuccess) + return; + + // Notify running game for hot-reload + Internal.ScriptEditorDebuggerReloadScripts(); + + // Hot-reload in the editor + GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); + + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + + private void _ViewLogPressed() + { + if (!buildTabsList.IsAnythingSelected()) + return; + + var selectedItems = buildTabsList.GetSelectedItems(); + + if (selectedItems.Length != 1) + throw new InvalidOperationException($"Expected 1 selected item, got {selectedItems.Length}"); + + int selectedItem = selectedItems[0]; + + var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem); + + OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName)); + } + + public override void _Notification(int what) + { + base._Notification(what); + + if (what == EditorSettings.NotificationEditorSettingsChanged) + { + var editorBaseControl = editorInterface.GetBaseControl(); + panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); + } + } + + public void AddBuildTab(MonoBuildTab buildTab) + { + buildTabs.AddChild(buildTab); + RaiseBuildTab(buildTab); + } + + public void RaiseBuildTab(MonoBuildTab buildTab) + { + if (buildTab.GetParent() != buildTabs) + throw new InvalidOperationException("Build tab is not in the tabs list"); + + buildTabs.MoveChild(buildTab, 0); + _UpdateBuildTabsList(); + } + + public void ShowBuildTab() + { + for (int i = 0; i < panelTabs.GetTabCount(); i++) + { + if (panelTabs.GetTabControl(i) == panelBuildsTab) + { + panelTabs.CurrentTab = i; + GodotSharpEditor.Instance.MakeBottomPanelItemVisible(this); + return; + } + } + + GD.PushError("Builds tab not found"); + } + + public override void _Ready() + { + base._Ready(); + + editorInterface = GodotSharpEditor.Instance.GetEditorInterface(); + + var editorBaseControl = editorInterface.GetBaseControl(); + + SizeFlagsVertical = (int) SizeFlags.ExpandFill; + SetAnchorsAndMarginsPreset(LayoutPreset.Wide); + + panelTabs = new TabContainer + { + TabAlign = TabContainer.TabAlignEnum.Left, + RectMinSize = new Vector2(0, 228) * EditorScale, + SizeFlagsVertical = (int) SizeFlags.ExpandFill + }; + panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); + AddChild(panelTabs); + + { + // Builds tab + panelBuildsTab = new VBoxContainer + { + Name = "Builds".TTR(), + SizeFlagsHorizontal = (int) SizeFlags.ExpandFill + }; + panelTabs.AddChild(panelBuildsTab); + + var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill}; + panelBuildsTab.AddChild(toolBarHBox); + + var buildProjectBtn = new Button + { + Text = "Build Project".TTR(), + FocusMode = FocusModeEnum.None + }; + buildProjectBtn.Connect("pressed", this, nameof(BuildProjectPressed)); + toolBarHBox.AddChild(buildProjectBtn); + + toolBarHBox.AddSpacer(begin: false); + + warningsBtn = new ToolButton + { + Text = "Warnings".TTR(), + ToggleMode = true, + Pressed = true, + Visible = false, + FocusMode = FocusModeEnum.None + }; + warningsBtn.Connect("toggled", this, nameof(_WarningsToggled)); + toolBarHBox.AddChild(warningsBtn); + + errorsBtn = new ToolButton + { + Text = "Errors".TTR(), + ToggleMode = true, + Pressed = true, + Visible = false, + FocusMode = FocusModeEnum.None + }; + errorsBtn.Connect("toggled", this, nameof(_ErrorsToggled)); + toolBarHBox.AddChild(errorsBtn); + + toolBarHBox.AddSpacer(begin: false); + + viewLogBtn = new Button + { + Text = "View log".TTR(), + FocusMode = FocusModeEnum.None, + Visible = false + }; + viewLogBtn.Connect("pressed", this, nameof(_ViewLogPressed)); + toolBarHBox.AddChild(viewLogBtn); + + var hsc = new HSplitContainer + { + SizeFlagsHorizontal = (int) SizeFlags.ExpandFill, + SizeFlagsVertical = (int) SizeFlags.ExpandFill + }; + panelBuildsTab.AddChild(hsc); + + buildTabsList = new ItemList {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill}; + buildTabsList.Connect("item_selected", this, nameof(_BuildTabsItemSelected)); + buildTabsList.Connect("nothing_selected", this, nameof(_BuildTabsNothingSelected)); + hsc.AddChild(buildTabsList); + + buildTabs = new TabContainer + { + TabAlign = TabContainer.TabAlignEnum.Left, + SizeFlagsHorizontal = (int) SizeFlags.ExpandFill, + TabsVisible = false + }; + hsc.AddChild(buildTabs); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs new file mode 100644 index 0000000000..858e852392 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs @@ -0,0 +1,47 @@ +using System; +using Godot; +using Godot.Collections; +using GodotTools.Internals; +using Path = System.IO.Path; + +namespace GodotTools +{ + [Serializable] + public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization + { + public string Solution { get; } + public string Configuration { get; } + public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization + + public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}"); + + public override bool Equals(object obj) + { + if (obj is MonoBuildInfo other) + return other.Solution == Solution && other.Configuration == Configuration; + + return false; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 29 + Solution.GetHashCode(); + hash = hash * 29 + Configuration.GetHashCode(); + return hash; + } + } + + private MonoBuildInfo() + { + } + + public MonoBuildInfo(string solution, string configuration) + { + Solution = solution; + Configuration = configuration; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs new file mode 100644 index 0000000000..75fdacc0da --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs @@ -0,0 +1,260 @@ +using Godot; +using System; +using Godot.Collections; +using GodotTools.Internals; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; + +namespace GodotTools +{ + public class MonoBuildTab : VBoxContainer + { + public enum BuildResults + { + Error, + Success + } + + [Serializable] + private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization + { + public bool Warning { get; set; } + public string File { get; set; } + public int Line { get; set; } + public int Column { get; set; } + public string Code { get; set; } + public string Message { get; set; } + public string ProjectFile { get; set; } + } + + private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization + private ItemList issuesList; + + public bool BuildExited { get; private set; } = false; + + public BuildResults? BuildResult { get; private set; } = null; + + public int ErrorCount { get; private set; } = 0; + + public int WarningCount { get; private set; } = 0; + + public bool ErrorsVisible { get; set; } = true; + public bool WarningsVisible { get; set; } = true; + + public Texture IconTexture + { + get + { + if (!BuildExited) + return GetIcon("Stop", "EditorIcons"); + + if (BuildResult == BuildResults.Error) + return GetIcon("StatusError", "EditorIcons"); + + return GetIcon("StatusSuccess", "EditorIcons"); + } + } + + public MonoBuildInfo BuildInfo { get; private set; } + + private void _LoadIssuesFromFile(string csvFile) + { + using (var file = new Godot.File()) + { + Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read); + + if (openError != Error.Ok) + return; + + while (!file.EofReached()) + { + string[] csvColumns = file.GetCsvLine(); + + if (csvColumns.Length == 1 && csvColumns[0].Empty()) + return; + + if (csvColumns.Length != 7) + { + GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); + continue; + } + + var issue = new BuildIssue + { + Warning = csvColumns[0] == "warning", + File = csvColumns[1], + Line = int.Parse(csvColumns[2]), + Column = int.Parse(csvColumns[3]), + Code = csvColumns[4], + Message = csvColumns[5], + ProjectFile = csvColumns[6] + }; + + if (issue.Warning) + WarningCount += 1; + else + ErrorCount += 1; + + issues.Add(issue); + } + } + } + + private void _IssueActivated(int idx) + { + if (idx < 0 || idx >= issuesList.GetItemCount()) + throw new IndexOutOfRangeException("Item list index out of range"); + + // Get correct issue idx from issue list + int issueIndex = (int) issuesList.GetItemMetadata(idx); + + if (idx < 0 || idx >= issues.Count) + throw new IndexOutOfRangeException("Issue index out of range"); + + BuildIssue issue = issues[issueIndex]; + + if (issue.ProjectFile.Empty() && issue.File.Empty()) + return; + + string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir(); + + string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath()); + + if (!File.Exists(file)) + return; + + file = ProjectSettings.LocalizePath(file); + + if (file.StartsWith("res://")) + { + var script = (Script) ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType); + + if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column)) + Internal.EditorNodeShowScriptScreen(); + } + } + + public void UpdateIssuesList() + { + issuesList.Clear(); + + using (var warningIcon = GetIcon("Warning", "EditorIcons")) + using (var errorIcon = GetIcon("Error", "EditorIcons")) + { + for (int i = 0; i < issues.Count; i++) + { + BuildIssue issue = issues[i]; + + if (!(issue.Warning ? WarningsVisible : ErrorsVisible)) + continue; + + string tooltip = string.Empty; + tooltip += $"Message: {issue.Message}"; + + if (!issue.Code.Empty()) + tooltip += $"\nCode: {issue.Code}"; + + tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}"; + + string text = string.Empty; + + if (!issue.File.Empty()) + { + text += $"{issue.File}({issue.Line},{issue.Column}): "; + + tooltip += $"\nFile: {issue.File}"; + tooltip += $"\nLine: {issue.Line}"; + tooltip += $"\nColumn: {issue.Column}"; + } + + if (!issue.ProjectFile.Empty()) + tooltip += $"\nProject: {issue.ProjectFile}"; + + text += issue.Message; + + int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal); + string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx); + issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon); + + int index = issuesList.GetItemCount() - 1; + issuesList.SetItemTooltip(index, tooltip); + issuesList.SetItemMetadata(index, i); + } + } + } + + public void OnBuildStart() + { + BuildExited = false; + + issues.Clear(); + WarningCount = 0; + ErrorCount = 0; + UpdateIssuesList(); + + GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + } + + public void OnBuildExit(BuildResults result) + { + BuildExited = true; + BuildResult = result; + + _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName)); + UpdateIssuesList(); + + GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + } + + public void OnBuildExecFailed(string cause) + { + BuildExited = true; + BuildResult = BuildResults.Error; + + issuesList.Clear(); + + var issue = new BuildIssue {Message = cause, Warning = false}; + + ErrorCount += 1; + issues.Add(issue); + + UpdateIssuesList(); + + GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + } + + public void RestartBuild() + { + if (!BuildExited) + throw new InvalidOperationException("Build already started"); + + GodotSharpBuilds.RestartBuild(this); + } + + public void StopBuild() + { + if (!BuildExited) + throw new InvalidOperationException("Build is not in progress"); + + GodotSharpBuilds.StopBuild(this); + } + + public override void _Ready() + { + base._Ready(); + + issuesList = new ItemList {SizeFlagsVertical = (int) SizeFlags.ExpandFill}; + issuesList.Connect("item_activated", this, nameof(_IssueActivated)); + AddChild(issuesList); + } + + private MonoBuildTab() + { + } + + public MonoBuildTab(MonoBuildInfo buildInfo) + { + BuildInfo = buildInfo; + } + } +} diff --git a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs b/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs index fba4a8f65c..61a0a992ce 100644 --- a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs @@ -1,11 +1,12 @@ +using GodotTools.Core; using System; using System.IO; using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; +using GodotTools.Internals; +using GodotTools.Utils; -namespace GodotSharpTools.Editor +namespace GodotTools { public class MonoDevelopInstance { @@ -15,24 +16,24 @@ namespace GodotSharpTools.Editor VisualStudioForMac = 1 } - readonly string solutionFile; - readonly EditorId editorId; + private readonly string solutionFile; + private readonly EditorId editorId; - Process process; + private Process process; - public void Execute(string[] files) + public void Execute(params string[] files) { bool newWindow = process == null || process.HasExited; - List<string> args = new List<string>(); + var args = new List<string>(); string command; if (Utils.OS.IsOSX()) { - string bundleId = codeEditorBundleIds[editorId]; + string bundleId = BundleIds[editorId]; - if (IsApplicationBundleInstalled(bundleId)) + if (Internal.IsOsxAppBundleInstalled(bundleId)) { command = "open"; @@ -47,12 +48,12 @@ namespace GodotSharpTools.Editor } else { - command = codeEditorPaths[editorId]; + command = OS.PathWhich(ExecutableNames[editorId]); } } else { - command = codeEditorPaths[editorId]; + command = OS.PathWhich(ExecutableNames[editorId]); } args.Add("--ipc-tcp"); @@ -70,9 +71,12 @@ namespace GodotSharpTools.Editor args.Add("\"" + Path.GetFullPath(filePath.NormalizePath()) + cursor + "\""); } + if (command == null) + throw new FileNotFoundException(); + if (newWindow) { - process = Process.Start(new ProcessStartInfo() + process = Process.Start(new ProcessStartInfo { FileName = command, Arguments = string.Join(" ", args), @@ -81,12 +85,12 @@ namespace GodotSharpTools.Editor } else { - Process.Start(new ProcessStartInfo() + Process.Start(new ProcessStartInfo { FileName = command, Arguments = string.Join(" ", args), UseShellExecute = false - }); + })?.Dispose(); } } @@ -99,45 +103,42 @@ namespace GodotSharpTools.Editor this.editorId = editorId; } - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static bool IsApplicationBundleInstalled(string bundleId); - - static readonly IReadOnlyDictionary<EditorId, string> codeEditorPaths; - static readonly IReadOnlyDictionary<EditorId, string> codeEditorBundleIds; + private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames; + private static readonly IReadOnlyDictionary<EditorId, string> BundleIds; static MonoDevelopInstance() { if (Utils.OS.IsOSX()) { - codeEditorPaths = new Dictionary<EditorId, string> + ExecutableNames = new Dictionary<EditorId, string> { // Rely on PATH - { EditorId.MonoDevelop, "monodevelop" }, - { EditorId.VisualStudioForMac, "VisualStudio" } + {EditorId.MonoDevelop, "monodevelop"}, + {EditorId.VisualStudioForMac, "VisualStudio"} }; - codeEditorBundleIds = new Dictionary<EditorId, string> + BundleIds = new Dictionary<EditorId, string> { // TODO EditorId.MonoDevelop - { EditorId.VisualStudioForMac, "com.microsoft.visual-studio" } + {EditorId.VisualStudioForMac, "com.microsoft.visual-studio"} }; } else if (Utils.OS.IsWindows()) { - codeEditorPaths = new Dictionary<EditorId, string> + ExecutableNames = new Dictionary<EditorId, string> { // XamarinStudio is no longer a thing, and the latest version is quite old // MonoDevelop is available from source only on Windows. The recommendation // is to use Visual Studio instead. Since there are no official builds, we // will rely on custom MonoDevelop builds being added to PATH. - { EditorId.MonoDevelop, "MonoDevelop.exe" } + {EditorId.MonoDevelop, "MonoDevelop.exe"} }; } else if (Utils.OS.IsUnix()) { - codeEditorPaths = new Dictionary<EditorId, string> + ExecutableNames = new Dictionary<EditorId, string> { // Rely on PATH - { EditorId.MonoDevelop, "monodevelop" } + {EditorId.MonoDevelop, "monodevelop"} }; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f5fe85c722 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("GodotTools")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs new file mode 100644 index 0000000000..288c65de74 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace GodotTools.Utils +{ + public static class CollectionExtensions + { + public static T SelectFirstNotNull<T>(this IEnumerable<T> enumerable, Func<T, T> predicate, T orElse = null) + where T : class + { + foreach (T elem in enumerable) + { + T result = predicate(elem); + if (result != null) + return result; + } + + return orElse; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs new file mode 100644 index 0000000000..c67d48b92a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs @@ -0,0 +1,40 @@ +using System.IO; +using Godot; + +namespace GodotTools.Utils +{ + public static class Directory + { + private static string GlobalizePath(this string path) + { + return ProjectSettings.GlobalizePath(path); + } + + public static bool Exists(string path) + { + return System.IO.Directory.Exists(path.GlobalizePath()); + } + + /// Create directory recursively + public static DirectoryInfo CreateDirectory(string path) + { + return System.IO.Directory.CreateDirectory(path.GlobalizePath()); + } + + public static void Delete(string path, bool recursive) + { + System.IO.Directory.Delete(path.GlobalizePath(), recursive); + } + + + public static string[] GetDirectories(string path, string searchPattern, SearchOption searchOption) + { + return System.IO.Directory.GetDirectories(path.GlobalizePath(), searchPattern, searchOption); + } + + public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption) + { + return System.IO.Directory.GetFiles(path.GlobalizePath(), searchPattern, searchOption); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs new file mode 100644 index 0000000000..e1e2188edb --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs @@ -0,0 +1,43 @@ +using System; +using Godot; + +namespace GodotTools.Utils +{ + public static class File + { + private static string GlobalizePath(this string path) + { + return ProjectSettings.GlobalizePath(path); + } + + public static void WriteAllText(string path, string contents) + { + System.IO.File.WriteAllText(path.GlobalizePath(), contents); + } + + public static bool Exists(string path) + { + return System.IO.File.Exists(path.GlobalizePath()); + } + + public static DateTime GetLastWriteTime(string path) + { + return System.IO.File.GetLastWriteTime(path.GlobalizePath()); + } + + public static void Delete(string path) + { + System.IO.File.Delete(path.GlobalizePath()); + } + + public static void Copy(string sourceFileName, string destFileName) + { + System.IO.File.Copy(sourceFileName.GlobalizePath(), destFileName.GlobalizePath(), overwrite: true); + } + + public static byte[] ReadAllBytes(string path) + { + return System.IO.File.ReadAllBytes(path.GlobalizePath()); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs new file mode 100644 index 0000000000..e48b1115db --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace GodotTools.Utils +{ + public static class OS + { + [MethodImpl(MethodImplOptions.InternalCall)] + extern static 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() + { + return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsOSX() + { + return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsServer() + { + return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsUWP() + { + return UWPName.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 bool? _isUnixCache; + private static readonly string[] UnixPlatforms = {HaikuName, OSXName, ServerName, X11Name}; + + public static bool IsUnix() + { + if (_isUnixCache.HasValue) + return _isUnixCache.Value; + + string osName = GetPlatformName(); + _isUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase)); + return _isUnixCache.Value; + } + + public static char PathSep => IsWindows() ? ';' : ':'; + + public static string PathWhich(string name) + { + string[] windowsExts = IsWindows() ? Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) : null; + 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 + + 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; + } + } + + return null; + } + + public static void RunProcess(string command, IEnumerable<string> arguments) + { + string CmdLineArgsToString(IEnumerable<string> args) + { + return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); + } + + ProcessStartInfo startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; + + using (Process process = Process.Start(startInfo)) + { + if (process == null) + throw new Exception("No process was started"); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + } + } +} diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 2d618f7891..45037bf637 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -861,37 +861,33 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append("\n#pragma warning restore CS1591\n"); } -Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution) { - - String proj_dir = p_solution_dir.plus_file(CORE_API_ASSEMBLY_NAME); +Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_items) { DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(proj_dir)) { - Error err = da->make_dir_recursive(proj_dir); + if (!DirAccess::exists(p_proj_dir)) { + Error err = da->make_dir_recursive(p_proj_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - da->change_dir(proj_dir); + da->change_dir(p_proj_dir); da->make_dir("Core"); da->make_dir("ObjectType"); - String core_dir = path_join(proj_dir, "Core"); - String obj_type_dir = path_join(proj_dir, "ObjectType"); - - Vector<String> compile_items; + String core_dir = path::join(p_proj_dir, "Core"); + String obj_type_dir = path::join(p_proj_dir, "ObjectType"); // Generate source file for global scope constants and enums { StringBuilder constants_source; _generate_global_constants(constants_source); - String output_file = path_join(core_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs"); + String output_file = path::join(core_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs"); Error save_err = _save_file(output_file, constants_source); if (save_err != OK) return save_err; - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) { @@ -900,7 +896,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, if (itype.api_type == ClassDB::API_EDITOR) continue; - String output_file = path_join(obj_type_dir, itype.proxy_name + ".cs"); + String output_file = path::join(obj_type_dir, itype.proxy_name + ".cs"); Error err = _generate_cs_type(itype, output_file); if (err == ERR_SKIP) @@ -909,19 +905,19 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, if (err != OK) return err; - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } // Generate sources from compressed files - Map<String, CompressedFile> compressed_files; + Map<String, GodotCsCompressedFile> compressed_files; get_compressed_files(compressed_files); - for (Map<String, CompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) { + for (Map<String, GodotCsCompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) { const String &file_name = E->key(); - const CompressedFile &file_data = E->value(); + const GodotCsCompressedFile &file_data = E->value(); - String output_file = path_join(core_dir, file_name); + String output_file = path::join(core_dir, file_name); Vector<uint8_t> data; data.resize(file_data.uncompressed_size); @@ -939,7 +935,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, file->store_buffer(data.ptr(), data.size()); file->close(); - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } StringBuilder cs_icalls_content; @@ -975,49 +971,33 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); - String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs"); + String internal_methods_file = path::join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs"); Error err = _save_file(internal_methods_file, cs_icalls_content); if (err != OK) return err; - compile_items.push_back(internal_methods_file); - - String guid = CSharpProject::generate_core_api_project(proj_dir, compile_items); - - DotNetSolution::ProjectInfo proj_info; - proj_info.guid = guid; - proj_info.relpath = String(CORE_API_ASSEMBLY_NAME).plus_file(CORE_API_ASSEMBLY_NAME ".csproj"); - proj_info.configs.push_back("Debug"); - proj_info.configs.push_back("Release"); - - r_solution.add_new_project(CORE_API_ASSEMBLY_NAME, proj_info); - - _log("The solution and C# project for the Core API was generated successfully\n"); + r_compile_items.push_back(internal_methods_file); return OK; } -Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution) { - - String proj_dir = p_solution_dir.plus_file(EDITOR_API_ASSEMBLY_NAME); +Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items) { DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(proj_dir)) { - Error err = da->make_dir_recursive(proj_dir); + if (!DirAccess::exists(p_proj_dir)) { + Error err = da->make_dir_recursive(p_proj_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - da->change_dir(proj_dir); + da->change_dir(p_proj_dir); da->make_dir("Core"); da->make_dir("ObjectType"); - String core_dir = path_join(proj_dir, "Core"); - String obj_type_dir = path_join(proj_dir, "ObjectType"); - - Vector<String> compile_items; + String core_dir = path::join(p_proj_dir, "Core"); + String obj_type_dir = path::join(p_proj_dir, "ObjectType"); for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) { const TypeInterface &itype = E.get(); @@ -1025,7 +1005,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir if (itype.api_type != ClassDB::API_EDITOR) continue; - String output_file = path_join(obj_type_dir, itype.proxy_name + ".cs"); + String output_file = path::join(obj_type_dir, itype.proxy_name + ".cs"); Error err = _generate_cs_type(itype, output_file); if (err == ERR_SKIP) @@ -1034,7 +1014,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir if (err != OK) return err; - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } StringBuilder cs_icalls_content; @@ -1071,64 +1051,62 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); - String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs"); + String internal_methods_file = path::join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs"); Error err = _save_file(internal_methods_file, cs_icalls_content); if (err != OK) return err; - compile_items.push_back(internal_methods_file); - - String guid = CSharpProject::generate_editor_api_project(proj_dir, "../" CORE_API_ASSEMBLY_NAME "/" CORE_API_ASSEMBLY_NAME ".csproj", compile_items); - - DotNetSolution::ProjectInfo proj_info; - proj_info.guid = guid; - proj_info.relpath = String(EDITOR_API_ASSEMBLY_NAME).plus_file(EDITOR_API_ASSEMBLY_NAME ".csproj"); - proj_info.configs.push_back("Debug"); - proj_info.configs.push_back("Release"); - - r_solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, proj_info); - - _log("The solution and C# project for the Editor API was generated successfully\n"); + r_compile_items.push_back(internal_methods_file); return OK; } Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { + String output_dir = path::abspath(path::realpath(p_output_dir)); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(p_output_dir)) { - Error err = da->make_dir_recursive(p_output_dir); + if (!DirAccess::exists(output_dir)) { + Error err = da->make_dir_recursive(output_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - DotNetSolution solution(API_SOLUTION_NAME); + Error proj_err; - if (!solution.set_path(p_output_dir)) - return ERR_FILE_NOT_FOUND; + // Generate GodotSharp source files - Error proj_err; + String core_proj_dir = output_dir.plus_file(CORE_API_ASSEMBLY_NAME); + Vector<String> core_compile_items; - proj_err = generate_cs_core_project(p_output_dir, solution); + proj_err = generate_cs_core_project(core_proj_dir, core_compile_items); if (proj_err != OK) { ERR_PRINT("Generation of the Core API C# project failed"); return proj_err; } - proj_err = generate_cs_editor_project(p_output_dir, solution); + // Generate GodotSharpEditor source files + + String editor_proj_dir = output_dir.plus_file(EDITOR_API_ASSEMBLY_NAME); + Vector<String> editor_compile_items; + + proj_err = generate_cs_editor_project(editor_proj_dir, editor_compile_items); if (proj_err != OK) { ERR_PRINT("Generation of the Editor API C# project failed"); return proj_err; } - Error sln_error = solution.save(); - if (sln_error != OK) { - ERR_PRINT("Failed to save API solution"); - return sln_error; + // Generate solution + + if (!CSharpProject::generate_api_solution(output_dir, + core_proj_dir, core_compile_items, editor_proj_dir, editor_compile_items)) { + return ERR_CANT_CREATE; } + _log("The solution for the Godot API was generated successfully\n"); + return OK; } @@ -1311,8 +1289,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(MEMBER_BEGIN "private static Godot.Object singleton;\n"); output.append(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3 "get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5 - "singleton = Engine.GetSingleton(" BINDINGS_NATIVE_NAME_FIELD ");\n" INDENT4 - "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); + "singleton = Engine.GetSingleton(typeof("); + output.append(itype.proxy_name); + output.append(").Name);\n" INDENT4 "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); output.append(itype.name); @@ -1883,7 +1862,7 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { output.append("\n#endif // MONO_GLUE_ENABLED\n"); - Error save_err = _save_file(path_join(p_output_dir, "mono_glue.gen.cpp"), output); + Error save_err = _save_file(path::join(p_output_dir, "mono_glue.gen.cpp"), output); if (save_err != OK) return save_err; @@ -2213,7 +2192,7 @@ void BindingsGenerator::_populate_object_type_interfaces() { itype.base_name = ClassDB::get_parent_class(type_cname); itype.is_singleton = Engine::get_singleton()->has_singleton(itype.proxy_name); - itype.is_instantiable = ClassDB::can_instance(type_cname) && !itype.is_singleton; + itype.is_instantiable = class_info->creation_func && !itype.is_singleton; itype.is_reference = ClassDB::is_parent_class(type_cname, name_cache.type_Reference); itype.memory_own = itype.is_reference; @@ -2347,6 +2326,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { imethod.return_type.is_enum = true; } else if (return_info.class_name != StringName()) { imethod.return_type.cname = return_info.class_name; + if (!imethod.is_virtual && ClassDB::is_parent_class(return_info.class_name, name_cache.type_Reference) && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE) { + /* clang-format off */ + ERR_PRINTS("Return type is reference but hint is not " _STR(PROPERTY_HINT_RESOURCE_TYPE) "." + " Are you returning a reference type by pointer? Method: " + itype.name + "." + imethod.name); + /* clang-format on */ + ERR_FAIL(); + } } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { imethod.return_type.cname = return_info.hint_string; } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { @@ -3018,36 +3004,49 @@ void BindingsGenerator::_initialize() { void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) { const int NUM_OPTIONS = 2; - String mono_glue_option = "--generate-mono-glue"; - String cs_api_option = "--generate-cs-api"; + String generate_all_glue_option = "--generate-mono-glue"; + String generate_cs_glue_option = "--generate-mono-cs-glue"; + String generate_cpp_glue_option = "--generate-mono-cpp-glue"; - String mono_glue_path; - String cs_api_path; + String glue_dir_path; + String cs_dir_path; + String cpp_dir_path; int options_left = NUM_OPTIONS; const List<String>::Element *elem = p_cmdline_args.front(); while (elem && options_left) { - if (elem->get() == mono_glue_option) { + if (elem->get() == generate_all_glue_option) { + const List<String>::Element *path_elem = elem->next(); + + if (path_elem) { + glue_dir_path = path_elem->get(); + elem = elem->next(); + } else { + ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to {GODOT_ROOT}/modules/mono/glue)"); + } + + --options_left; + } else if (elem->get() == generate_cs_glue_option) { const List<String>::Element *path_elem = elem->next(); if (path_elem) { - mono_glue_path = path_elem->get(); + cs_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(mono_glue_option + ": No output directory specified"); + ERR_PRINTS(generate_cs_glue_option + ": No output directory specified"); } --options_left; - } else if (elem->get() == cs_api_option) { + } else if (elem->get() == generate_cpp_glue_option) { const List<String>::Element *path_elem = elem->next(); if (path_elem) { - cs_api_path = path_elem->get(); + cpp_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(cs_api_option + ": No output directory specified"); + ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified"); } --options_left; @@ -3056,18 +3055,26 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) elem = elem->next(); } - if (mono_glue_path.length() || cs_api_path.length()) { + if (glue_dir_path.length() || cs_dir_path.length() || cpp_dir_path.length()) { BindingsGenerator bindings_generator; bindings_generator.set_log_print_enabled(true); - if (mono_glue_path.length()) { - if (bindings_generator.generate_glue(mono_glue_path) != OK) - ERR_PRINTS(mono_glue_option + ": Failed to generate mono glue"); + if (glue_dir_path.length()) { + if (bindings_generator.generate_glue(glue_dir_path) != OK) + ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue"); + + if (bindings_generator.generate_cs_api(glue_dir_path.plus_file("Managed/Generated")) != OK) + ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API"); + } + + if (cs_dir_path.length()) { + if (bindings_generator.generate_cs_api(cs_dir_path) != OK) + ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API"); } - if (cs_api_path.length()) { - if (bindings_generator.generate_cs_api(cs_api_path) != OK) - ERR_PRINTS(cs_api_option + ": Failed to generate the C# API"); + if (cpp_dir_path.length()) { + if (bindings_generator.generate_glue(cpp_dir_path) != OK) + ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue"); } // Exit once done diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index ffc73a7e3e..8be51a6c55 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -33,7 +33,6 @@ #include "core/class_db.h" #include "core/string_builder.h" -#include "dotnet_solution.h" #include "editor/doc/doc_data.h" #include "editor/editor_help.h" @@ -614,12 +613,13 @@ class BindingsGenerator { void _initialize(); public: - Error generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution); - Error generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution); + Error generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_files); + Error generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items); Error generate_cs_api(const String &p_output_dir); Error generate_glue(const String &p_output_dir); - void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; } + _FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; } + _FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; } static uint32_t get_version(); diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index fe79286556..d88b08c646 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -44,66 +44,54 @@ namespace CSharpProject { -String generate_core_api_project(const String &p_dir, const Vector<String> &p_files) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); - - Variant dir = p_dir; - Variant compile_items = p_files; - const Variant *args[2] = { &dir, &compile_items }; +bool generate_api_solution_impl(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items, + const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items, + GDMonoAssembly *p_tools_project_editor_assembly) { + + GDMonoClass *klass = p_tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ApiSolutionGenerator"); + + Variant solution_dir = p_solution_dir; + Variant core_proj_dir = p_core_proj_dir; + Variant core_compile_items = p_core_compile_items; + Variant editor_proj_dir = p_editor_proj_dir; + Variant editor_compile_items = p_editor_compile_items; + const Variant *args[5] = { &solution_dir, &core_proj_dir, &core_compile_items, &editor_proj_dir, &editor_compile_items }; MonoException *exc = NULL; - MonoObject *ret = klass->get_method("GenCoreApiProject", 2)->invoke(NULL, args, &exc); + klass->get_method("GenerateApiSolution", 5)->invoke(NULL, args, &exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(String()); + ERR_FAIL_V(false); } - return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); + return true; } -String generate_editor_api_project(const String &p_dir, const String &p_core_proj_path, const Vector<String> &p_files) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); - - Variant dir = p_dir; - Variant core_proj_path = p_core_proj_path; - Variant compile_items = p_files; - const Variant *args[3] = { &dir, &core_proj_path, &compile_items }; - MonoException *exc = NULL; - MonoObject *ret = klass->get_method("GenEditorApiProject", 3)->invoke(NULL, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(String()); - } - - return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); -} +bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items, + const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items) { -String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files) { + if (GDMono::get_singleton()->get_tools_project_editor_assembly()) { + return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items, + p_editor_proj_dir, p_editor_compile_items, + GDMono::get_singleton()->get_tools_project_editor_assembly()); + } else { + MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.ApiSolutionGenerationDomain"); + CRASH_COND(temp_domain == NULL); + _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain); - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + _GDMONO_SCOPE_DOMAIN_(temp_domain); - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); + GDMonoAssembly *tools_project_editor_assembly = NULL; - Variant dir = p_dir; - Variant name = p_name; - Variant compile_items = p_files; - const Variant *args[3] = { &dir, &name, &compile_items }; - MonoException *exc = NULL; - MonoObject *ret = klass->get_method("GenGameProject", 3)->invoke(NULL, args, &exc); + if (!GDMono::get_singleton()->load_assembly("GodotTools.ProjectEditor", &tools_project_editor_assembly)) { + ERR_EXPLAIN("Failed to load assembly: 'GodotTools.ProjectEditor'"); + ERR_FAIL_V(false); + } - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(String()); + return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items, + p_editor_proj_dir, p_editor_compile_items, + tools_project_editor_assembly); } - - return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); } void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) { @@ -111,9 +99,9 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str if (!GLOBAL_DEF("mono/project/auto_update_project", true)) return; - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly(); - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils"); + GDMonoClass *klass = tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ProjectUtils"); Variant project_path = p_project_path; Variant item_type = p_item_type; @@ -128,126 +116,4 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str } } -Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - if (FileAccess::exists(p_output_path)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - Error rm_err = da->remove(p_output_path); - - ERR_EXPLAIN("Failed to remove old scripts metadata file"); - ERR_FAIL_COND_V(rm_err != OK, rm_err); - } - - GDMonoClass *project_utils = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils"); - - void *args[2] = { - GDMonoMarshal::mono_string_from_godot(p_project_path), - GDMonoMarshal::mono_string_from_godot("Compile") - }; - - MonoException *exc = NULL; - MonoArray *ret = (MonoArray *)project_utils->get_method("GetIncludeFiles", 2)->invoke_raw(NULL, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(FAILED); - } - - PoolStringArray project_files = GDMonoMarshal::mono_array_to_PoolStringArray(ret); - PoolStringArray::Read r = project_files.read(); - - Dictionary old_dict = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing(); - Dictionary new_dict; - - for (int i = 0; i < project_files.size(); i++) { - const String &project_file = ("res://" + r[i]).simplify_path(); - - uint64_t modified_time = FileAccess::get_modified_time(project_file); - - const Variant *old_file_var = old_dict.getptr(project_file); - if (old_file_var) { - Dictionary old_file_dict = old_file_var->operator Dictionary(); - - if (old_file_dict["modified_time"].operator uint64_t() == modified_time) { - // No changes so no need to parse again - new_dict[project_file] = old_file_dict; - continue; - } - } - - ScriptClassParser scp; - Error err = scp.parse_file(project_file); - if (err != OK) { - ERR_PRINTS("Parse error: " + scp.get_error()); - ERR_EXPLAIN("Failed to determine namespace and class for script: " + project_file); - ERR_FAIL_V(err); - } - - Vector<ScriptClassParser::ClassDecl> classes = scp.get_classes(); - - bool found = false; - Dictionary class_dict; - - String search_name = project_file.get_file().get_basename(); - - for (int j = 0; j < classes.size(); j++) { - const ScriptClassParser::ClassDecl &class_decl = classes[j]; - - if (class_decl.base.size() == 0) - continue; // Does not inherit nor implement anything, so it can't be a script class - - String class_cmp; - - if (class_decl.nested) { - class_cmp = class_decl.name.get_slice(".", class_decl.name.get_slice_count(".") - 1); - } else { - class_cmp = class_decl.name; - } - - if (class_cmp != search_name) - continue; - - class_dict["namespace"] = class_decl.namespace_; - class_dict["class_name"] = class_decl.name; - class_dict["nested"] = class_decl.nested; - - found = true; - break; - } - - if (found) { - Dictionary file_dict; - file_dict["modified_time"] = modified_time; - file_dict["class"] = class_dict; - new_dict[project_file] = file_dict; - } - } - - if (new_dict.size()) { - String json = JSON::print(new_dict, "", false); - - String base_dir = p_output_path.get_base_dir(); - - if (!DirAccess::exists(base_dir)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - - Error err = da->make_dir_recursive(base_dir); - ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); - } - - Error ferr; - FileAccess *f = FileAccess::open(p_output_path, FileAccess::WRITE, &ferr); - ERR_EXPLAIN("Cannot open file for writing: " + p_output_path); - ERR_FAIL_COND_V(ferr != OK, ferr); - f->store_string(json); - f->flush(); - f->close(); - memdelete(f); - } - - return OK; -} - } // namespace CSharpProject diff --git a/modules/mono/editor/csharp_project.h b/modules/mono/editor/csharp_project.h index b08c9090c7..b42762cea2 100644 --- a/modules/mono/editor/csharp_project.h +++ b/modules/mono/editor/csharp_project.h @@ -35,14 +35,11 @@ namespace CSharpProject { -String generate_core_api_project(const String &p_dir, const Vector<String> &p_files = Vector<String>()); -String generate_editor_api_project(const String &p_dir, const String &p_core_proj_path, const Vector<String> &p_files = Vector<String>()); -String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files = Vector<String>()); +bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items, + const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items); void add_item(const String &p_project_path, const String &p_item_type, const String &p_include); -Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path); - } // namespace CSharpProject #endif // CSHARP_PROJECT_H diff --git a/modules/mono/editor/dotnet_solution.cpp b/modules/mono/editor/dotnet_solution.cpp deleted file mode 100644 index 324752cafc..0000000000 --- a/modules/mono/editor/dotnet_solution.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/*************************************************************************/ -/* dotnet_solution.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 "dotnet_solution.h" - -#include "core/os/dir_access.h" -#include "core/os/file_access.h" - -#include "../utils/path_utils.h" -#include "../utils/string_utils.h" -#include "csharp_project.h" - -#define SOLUTION_TEMPLATE \ - "Microsoft Visual Studio Solution File, Format Version 12.00\n" \ - "# Visual Studio 2012\n" \ - "%0\n" \ - "Global\n" \ - "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n" \ - "%1\n" \ - "\tEndGlobalSection\n" \ - "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n" \ - "%2\n" \ - "\tEndGlobalSection\n" \ - "EndGlobal\n" - -#define PROJECT_DECLARATION "Project(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"%0\", \"%1\", \"{%2}\"\nEndProject" - -#define SOLUTION_PLATFORMS_CONFIG "\t%0|Any CPU = %0|Any CPU" - -#define PROJECT_PLATFORMS_CONFIG \ - "\t\t{%0}.%1|Any CPU.ActiveCfg = %1|Any CPU\n" \ - "\t\t{%0}.%1|Any CPU.Build.0 = %1|Any CPU" - -void DotNetSolution::add_new_project(const String &p_name, const ProjectInfo &p_project_info) { - projects[p_name] = p_project_info; -} - -bool DotNetSolution::has_project(const String &p_name) const { - return projects.find(p_name) != NULL; -} - -const DotNetSolution::ProjectInfo &DotNetSolution::get_project_info(const String &p_name) const { - return projects[p_name]; -} - -bool DotNetSolution::remove_project(const String &p_name) { - return projects.erase(p_name); -} - -Error DotNetSolution::save() { - bool dir_exists = DirAccess::exists(path); - ERR_EXPLAIN("The directory does not exist."); - ERR_FAIL_COND_V(!dir_exists, ERR_FILE_NOT_FOUND); - - String projs_decl; - String sln_platform_cfg; - String proj_platform_cfg; - - for (Map<String, ProjectInfo>::Element *E = projects.front(); E; E = E->next()) { - const String &name = E->key(); - const ProjectInfo &proj_info = E->value(); - - bool is_front = E == projects.front(); - - if (!is_front) - projs_decl += "\n"; - - projs_decl += sformat(PROJECT_DECLARATION, name, proj_info.relpath.replace("/", "\\"), proj_info.guid); - - for (int i = 0; i < proj_info.configs.size(); i++) { - const String &config = proj_info.configs[i]; - - if (i != 0 || !is_front) { - sln_platform_cfg += "\n"; - proj_platform_cfg += "\n"; - } - - sln_platform_cfg += sformat(SOLUTION_PLATFORMS_CONFIG, config); - proj_platform_cfg += sformat(PROJECT_PLATFORMS_CONFIG, proj_info.guid, config); - } - } - - String content = sformat(SOLUTION_TEMPLATE, projs_decl, sln_platform_cfg, proj_platform_cfg); - - FileAccess *file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE); - ERR_FAIL_NULL_V(file, ERR_FILE_CANT_WRITE); - file->store_string(content); - file->close(); - memdelete(file); - - return OK; -} - -bool DotNetSolution::set_path(const String &p_existing_path) { - if (p_existing_path.is_abs_path()) { - path = p_existing_path; - } else { - String abspath; - if (!rel_path_to_abs(p_existing_path, abspath)) - return false; - path = abspath; - } - - return true; -} - -String DotNetSolution::get_path() { - return path; -} - -DotNetSolution::DotNetSolution(const String &p_name) { - name = p_name; -} diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp new file mode 100644 index 0000000000..0014aaca70 --- /dev/null +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -0,0 +1,427 @@ +/*************************************************************************/ +/* editor_internal_calls.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 "editor_internal_calls.h" + +#include "core/os/os.h" +#include "core/version.h" +#include "editor/editor_node.h" +#include "editor/plugins/script_editor_plugin.h" +#include "editor/script_editor_debugger.h" +#include "main/main.h" + +#include "../csharp_script.h" +#include "../glue/cs_glue_version.gen.h" +#include "../godotsharp_dirs.h" +#include "../mono_gd/gd_mono_marshal.h" +#include "../utils/osx_utils.h" +#include "bindings_generator.h" +#include "godotsharp_export.h" +#include "script_class_parser.h" + +MonoString *godot_icall_GodotSharpDirs_ResDataDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResMetadataDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_metadata_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResAssembliesBaseDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_base_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResAssembliesDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResConfigDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_config_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResTempDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_base_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_MonoUserDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_user_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_MonoLogsDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_logs_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_DataMonoEtcDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_etc_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_DataMonoLibDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_lib_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() { +#ifdef WINDOWS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir()); +#else + return NULL; +#endif +} + +void godot_icall_EditorProgress_Create(MonoString *p_task, MonoString *p_label, int32_t p_amount, MonoBoolean p_can_cancel) { + String task = GDMonoMarshal::mono_string_to_godot(p_task); + String label = GDMonoMarshal::mono_string_to_godot(p_label); + EditorNode::progress_add_task(task, label, p_amount, (bool)p_can_cancel); +} + +void godot_icall_EditorProgress_Dispose(MonoString *p_task) { + String task = GDMonoMarshal::mono_string_to_godot(p_task); + EditorNode::progress_end_task(task); +} + +MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_state, int32_t p_step, MonoBoolean p_force_refresh) { + String task = GDMonoMarshal::mono_string_to_godot(p_task); + String state = GDMonoMarshal::mono_string_to_godot(p_state); + return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh); +} + +BindingsGenerator *godot_icall_BindingsGenerator_Ctor() { + return memnew(BindingsGenerator); +} + +void godot_icall_BindingsGenerator_Dtor(BindingsGenerator *p_handle) { + memdelete(p_handle); +} + +MonoBoolean godot_icall_BindingsGenerator_LogPrintEnabled(BindingsGenerator *p_handle) { + return p_handle->is_log_print_enabled(); +} + +void godot_icall_BindingsGenerator_SetLogPrintEnabled(BindingsGenerator p_handle, MonoBoolean p_enabled) { + p_handle.set_log_print_enabled(p_enabled); +} + +int32_t godot_icall_BindingsGenerator_GenerateCsApi(BindingsGenerator *p_handle, MonoString *p_output_dir) { + String output_dir = GDMonoMarshal::mono_string_to_godot(p_output_dir); + return p_handle->generate_cs_api(output_dir); +} + +uint32_t godot_icall_BindingsGenerator_Version() { + return BindingsGenerator::get_version(); +} + +uint32_t godot_icall_BindingsGenerator_CsGlueVersion() { + return CS_GLUE_VERSION; +} + +int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes) { + String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath); + + ScriptClassParser scp; + Error err = scp.parse_file(filepath); + if (err == OK) { + Array classes = GDMonoMarshal::mono_object_to_variant(p_classes); + const Vector<ScriptClassParser::ClassDecl> &class_decls = scp.get_classes(); + + for (int i = 0; i < class_decls.size(); i++) { + const ScriptClassParser::ClassDecl &classDecl = class_decls[i]; + + Dictionary classDeclDict; + classDeclDict["name"] = classDecl.name; + classDeclDict["namespace"] = classDecl.namespace_; + classDeclDict["nested"] = classDecl.nested; + classDeclDict["base_count"] = classDecl.base.size(); + classes.push_back(classDeclDict); + } + } + return err; +} + +uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString *p_project_dll_name, MonoString *p_project_dll_src_path, + MonoString *p_build_config, MonoString *p_custom_lib_dir, MonoObject *r_dependencies) { + String project_dll_name = GDMonoMarshal::mono_string_to_godot(p_project_dll_name); + String project_dll_src_path = GDMonoMarshal::mono_string_to_godot(p_project_dll_src_path); + String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config); + String custom_lib_dir = GDMonoMarshal::mono_string_to_godot(p_custom_lib_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); +} + +float godot_icall_Globals_EditorScale() { + return EDSCALE; +} + +MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { + String setting = GDMonoMarshal::mono_string_to_godot(p_setting); + Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); + Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed); + return GDMonoMarshal::variant_to_mono_object(result); +} + +MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { + String setting = GDMonoMarshal::mono_string_to_godot(p_setting); + Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); + Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed); + return GDMonoMarshal::variant_to_mono_object(result); +} + +MonoString *godot_icall_Globals_TTR(MonoString *p_text) { + String text = GDMonoMarshal::mono_string_to_godot(p_text); + return GDMonoMarshal::mono_string_from_godot(TTR(text)); +} + +MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt() { + String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt(); + return GDMonoMarshal::mono_string_from_godot(error_str); +} + +MonoString *godot_icall_Internal_FullTemplatesDir() { + String full_templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); + return GDMonoMarshal::mono_string_from_godot(full_templates_dir); +} + +MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) { + String path = GDMonoMarshal::mono_string_to_godot(p_path); + return GDMonoMarshal::mono_string_from_godot(path.simplify_path()); +} + +MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id) { +#ifdef OSX_ENABLED + String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id); + return (MonoBoolean)osx_is_app_bundle_installed; +#else + (void)p_bundle_id; // UNUSED + return (MonoBoolean) false; +#endif +} + +MonoBoolean godot_icall_Internal_GodotIs32Bits() { + return sizeof(void *) == 4; +} + +MonoBoolean godot_icall_Internal_GodotIsRealTDouble() { +#ifdef REAL_T_IS_DOUBLE + return (MonoBoolean) true; +#else + return (MonoBoolean) false; +#endif +} + +void godot_icall_Internal_GodotMainIteration() { + Main::iteration(); +} + +uint64_t godot_icall_Internal_GetCoreApiHash() { + return ClassDB::get_api_hash(ClassDB::API_CORE); +} + +uint64_t godot_icall_Internal_GetEditorApiHash() { + return ClassDB::get_api_hash(ClassDB::API_EDITOR); +} + +MonoBoolean godot_icall_Internal_IsAssembliesReloadingNeeded() { +#ifdef GD_MONO_HOT_RELOAD + return (MonoBoolean)CSharpLanguage::get_singleton()->is_assembly_reloading_needed(); +#else + return (MonoBoolean) false; +#endif +} + +void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) { +#ifdef GD_MONO_HOT_RELOAD + _GodotSharp::get_singleton()->call_deferred("_reload_assemblies", (bool)p_soft_reload); +#endif +} + +void godot_icall_Internal_ScriptEditorDebuggerReloadScripts() { + ScriptEditor::get_singleton()->get_debugger()->reload_scripts(); +} + +MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) { + Ref<Resource> resource = GDMonoMarshal::mono_object_to_variant(p_resource); + return (MonoBoolean)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus); +} + +void godot_icall_Internal_EditorNodeShowScriptScreen() { + EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); +} + +MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) { + Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing(); + + MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype); + + uint32_t type_encoding = mono_type_get_type(dict_type); + MonoClass *type_class_raw = mono_class_from_mono_type(dict_type); + GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw); + + return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class)); +} + +MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { +#ifdef WINDOWS_ENABLED + String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir; + return GDMonoMarshal::mono_string_from_godot(install_root_dir); +#else + return NULL; +#endif +} + +MonoString *godot_icall_Utils_OS_GetPlatformName() { + String os_name = OS::get_singleton()->get_name(); + return GDMonoMarshal::mono_string_from_godot(os_name); +} + +void register_editor_internal_calls() { + + // GodotSharpDirs + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", (void *)godot_icall_GodotSharpDirs_ResDataDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", (void *)godot_icall_GodotSharpDirs_ResMetadataDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesBaseDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", (void *)godot_icall_GodotSharpDirs_ResConfigDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", (void *)godot_icall_GodotSharpDirs_ResTempDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", (void *)godot_icall_GodotSharpDirs_MonoUserDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", (void *)godot_icall_GodotSharpDirs_MonoLogsDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", (void *)godot_icall_GodotSharpDirs_MonoSolutionsDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", (void *)godot_icall_GodotSharpDirs_BuildLogsDirs); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", (void *)godot_icall_GodotSharpDirs_ProjectSlnPath); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", (void *)godot_icall_GodotSharpDirs_ProjectCsProjPath); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", (void *)godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", (void *)godot_icall_GodotSharpDirs_DataMonoEtcDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", (void *)godot_icall_GodotSharpDirs_DataMonoLibDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", (void *)godot_icall_GodotSharpDirs_DataMonoBinDir); + + // EditorProgress + mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", (void *)godot_icall_EditorProgress_Create); + mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", (void *)godot_icall_EditorProgress_Dispose); + mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", (void *)godot_icall_EditorProgress_Step); + + // BiningsGenerator + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Ctor", (void *)godot_icall_BindingsGenerator_Ctor); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Dtor", (void *)godot_icall_BindingsGenerator_Dtor); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_LogPrintEnabled", (void *)godot_icall_BindingsGenerator_LogPrintEnabled); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_SetLogPrintEnabled", (void *)godot_icall_BindingsGenerator_SetLogPrintEnabled); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_GenerateCsApi", (void *)godot_icall_BindingsGenerator_GenerateCsApi); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Version", (void *)godot_icall_BindingsGenerator_Version); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_CsGlueVersion", (void *)godot_icall_BindingsGenerator_CsGlueVersion); + + // 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); + + // Internals + mono_add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", (void *)godot_icall_Internal_UpdateApiAssembliesFromPrebuilt); + mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir); + mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath); + mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", (void *)godot_icall_Internal_GetCoreApiHash); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", (void *)godot_icall_Internal_GetEditorApiHash); + mono_add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", (void *)godot_icall_Internal_IsAssembliesReloadingNeeded); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", (void *)godot_icall_Internal_ReloadAssemblies); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebuggerReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebuggerReloadScripts); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing); + mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot); + + // Globals + mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale); + mono_add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", (void *)godot_icall_Globals_GlobalDef); + mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", (void *)godot_icall_Globals_EditorDef); + mono_add_internal_call("GodotTools.Internals.Globals::internal_TTR", (void *)godot_icall_Globals_TTR); + + // Utils.OS + mono_add_internal_call("GodotTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName); +} diff --git a/modules/mono/editor/monodevelop_instance.h b/modules/mono/editor/editor_internal_calls.h index 3b3af9607b..1682da66e5 100644 --- a/modules/mono/editor/monodevelop_instance.h +++ b/modules/mono/editor/editor_internal_calls.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* monodevelop_instance.h */ +/* editor_internal_calls.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,29 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef MONODEVELOP_INSTANCE_H -#define MONODEVELOP_INSTANCE_H +#ifndef EDITOR_INTERNAL_CALL_H +#define EDITOR_INTERNAL_CALL_H -#include "core/reference.h" +void register_editor_internal_calls(); -#include "../mono_gc_handle.h" -#include "../mono_gd/gd_mono_method.h" - -class MonoDevelopInstance { - - Ref<MonoGCHandle> gc_handle; - GDMonoMethod *execute_method; - -public: - enum EditorId { - MONODEVELOP = 0, - VISUALSTUDIO_FOR_MAC = 1 - }; - - void execute(const Vector<String> &p_files); - void execute(const String &p_file); - - MonoDevelopInstance(const String &p_solution, EditorId p_editor_id); -}; - -#endif // MONODEVELOP_INSTANCE_H +#endif // EDITOR_INTERNAL_CALL_H diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp deleted file mode 100644 index a962d6df27..0000000000 --- a/modules/mono/editor/godotsharp_builds.cpp +++ /dev/null @@ -1,632 +0,0 @@ -/*************************************************************************/ -/* godotsharp_builds.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 "godotsharp_builds.h" - -#include "core/os/os.h" -#include "core/vector.h" -#include "main/main.h" - -#include "../glue/cs_glue_version.gen.h" -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../utils/path_utils.h" -#include "bindings_generator.h" -#include "csharp_project.h" -#include "godotsharp_editor.h" - -#define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)" -#define PROP_NAME_MSBUILD_VS "MSBuild (VS Build Tools)" -#define PROP_NAME_XBUILD "xbuild (Deprecated)" - -void godot_icall_BuildInstance_ExitCallback(MonoString *p_solution, MonoString *p_config, int p_exit_code) { - - String solution = GDMonoMarshal::mono_string_to_godot(p_solution); - String config = GDMonoMarshal::mono_string_to_godot(p_config); - GodotSharpBuilds::get_singleton()->build_exit_callback(MonoBuildInfo(solution, config), p_exit_code); -} - -static Vector<const char *> _get_msbuild_hint_dirs() { - Vector<const char *> ret; -#ifdef OSX_ENABLED - ret.push_back("/Library/Frameworks/Mono.framework/Versions/Current/bin/"); - ret.push_back("/usr/local/var/homebrew/linked/mono/bin/"); -#endif - ret.push_back("/opt/novell/mono/bin/"); - return ret; -} - -#ifdef UNIX_ENABLED -String _find_build_engine_on_unix(const String &p_name) { - String ret = path_which(p_name); - - if (ret.length()) - return ret; - - String ret_fallback = path_which(p_name + ".exe"); - if (ret_fallback.length()) - return ret_fallback; - - static Vector<const char *> locations = _get_msbuild_hint_dirs(); - - for (int i = 0; i < locations.size(); i++) { - String hint_path = locations[i] + p_name; - - if (FileAccess::exists(hint_path)) { - return hint_path; - } - } - - return String(); -} -#endif - -MonoString *godot_icall_BuildInstance_get_MSBuildPath() { - - GodotSharpBuilds::BuildTool build_tool = GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))); - -#if defined(WINDOWS_ENABLED) - switch (build_tool) { - case GodotSharpBuilds::MSBUILD_VS: { - static String msbuild_tools_path; - - if (msbuild_tools_path.empty() || !FileAccess::exists(msbuild_tools_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - msbuild_tools_path = MonoRegUtils::find_msbuild_tools_path(); - - if (msbuild_tools_path.empty()) { - ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Tried with path: " + msbuild_tools_path); - return NULL; - } - } - - if (!msbuild_tools_path.ends_with("\\")) - msbuild_tools_path += "\\"; - - return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe"); - } break; - case GodotSharpBuilds::MSBUILD_MONO: { - String msbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("msbuild.bat"); - - if (!FileAccess::exists(msbuild_path)) { - ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_MONO "'. Tried with path: " + msbuild_path); - return NULL; - } - - return GDMonoMarshal::mono_string_from_godot(msbuild_path); - } break; - case GodotSharpBuilds::XBUILD: { - String xbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("xbuild.bat"); - - if (!FileAccess::exists(xbuild_path)) { - ERR_PRINTS("Cannot find executable for '" PROP_NAME_XBUILD "'. Tried with path: " + xbuild_path); - return NULL; - } - - return GDMonoMarshal::mono_string_from_godot(xbuild_path); - } break; - default: - ERR_EXPLAIN("You don't deserve to live"); - CRASH_NOW(); - } -#elif defined(UNIX_ENABLED) - static String msbuild_path; - static String xbuild_path; - - if (build_tool == GodotSharpBuilds::XBUILD) { - if (xbuild_path.empty() || !FileAccess::exists(xbuild_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - xbuild_path = _find_build_engine_on_unix("msbuild"); - } - - if (xbuild_path.empty()) { - ERR_PRINT("Cannot find binary for '" PROP_NAME_XBUILD "'"); - return NULL; - } - } else { - if (msbuild_path.empty() || !FileAccess::exists(msbuild_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - msbuild_path = _find_build_engine_on_unix("msbuild"); - } - - if (msbuild_path.empty()) { - ERR_PRINT("Cannot find binary for '" PROP_NAME_MSBUILD_MONO "'"); - return NULL; - } - } - - return GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path); -#else - (void)build_tool; // UNUSED - - ERR_EXPLAIN("Not implemented on this platform"); - ERR_FAIL_V(NULL); -#endif -} - -MonoString *godot_icall_BuildInstance_get_MonoWindowsBinDir() { - -#if defined(WINDOWS_ENABLED) - const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info(); - if (mono_reg_info.bin_dir.length()) { - return GDMonoMarshal::mono_string_from_godot(mono_reg_info.bin_dir); - } - - ERR_EXPLAIN("Cannot find Mono's binaries directory in the registry"); - ERR_FAIL_V(NULL); -#else - return NULL; -#endif -} - -MonoBoolean godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows() { - -#if defined(WINDOWS_ENABLED) - return GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))) == GodotSharpBuilds::MSBUILD_MONO; -#else - return false; -#endif -} - -MonoBoolean godot_icall_BuildInstance_get_PrintBuildOutput() { - - return (bool)EDITOR_GET("mono/builds/print_build_output"); -} - -void GodotSharpBuilds::register_internal_calls() { - - static bool registered = false; - ERR_FAIL_COND(registered); - registered = true; - - mono_add_internal_call("GodotSharpTools.Build.BuildSystem::godot_icall_BuildInstance_ExitCallback", (void *)godot_icall_BuildInstance_ExitCallback); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildPath", (void *)godot_icall_BuildInstance_get_MSBuildPath); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MonoWindowsBinDir", (void *)godot_icall_BuildInstance_get_MonoWindowsBinDir); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows", (void *)godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_PrintBuildOutput", (void *)godot_icall_BuildInstance_get_PrintBuildOutput); -} - -void GodotSharpBuilds::show_build_error_dialog(const String &p_message) { - - GodotSharpEditor::get_singleton()->show_error_dialog(p_message, "Build error"); - MonoBottomPanel::get_singleton()->show_build_tab(); -} - -bool GodotSharpBuilds::build_api_sln(const String &p_api_sln_dir, const String &p_config) { - - String api_sln_file = p_api_sln_dir.plus_file(API_SOLUTION_NAME ".sln"); - - String core_api_assembly_dir = p_api_sln_dir.plus_file(CORE_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config); - String core_api_assembly_file = core_api_assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - - String editor_api_assembly_dir = p_api_sln_dir.plus_file(EDITOR_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config); - String editor_api_assembly_file = editor_api_assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - - if (!FileAccess::exists(core_api_assembly_file) || !FileAccess::exists(editor_api_assembly_file)) { - MonoBuildInfo api_build_info(api_sln_file, p_config); - // TODO Replace this global NoWarn with '#pragma warning' directives on generated files, - // once we start to actively document manually maintained C# classes - api_build_info.custom_props.push_back("NoWarn=1591"); // Ignore missing documentation warnings - - if (!GodotSharpBuilds::get_singleton()->build(api_build_info)) { - show_build_error_dialog("Failed to build " API_SOLUTION_NAME " solution."); - return false; - } - } - - return true; -} - -bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) { - - // Create destination directory if needed - if (!DirAccess::exists(p_dst_dir)) { - DirAccess *da = DirAccess::create_for_path(p_dst_dir); - Error err = da->make_dir_recursive(p_dst_dir); - memdelete(da); - - if (err != OK) { - show_build_error_dialog("Failed to create destination directory for the API assemblies. Error: " + itos(err)); - return false; - } - } - - String assembly_file = p_assembly_name + ".dll"; - String assembly_src = p_src_dir.plus_file(assembly_file); - String assembly_dst = p_dst_dir.plus_file(assembly_file); - - if (!FileAccess::exists(assembly_dst) || - FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) || - GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - - String xml_file = p_assembly_name + ".xml"; - if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK) - WARN_PRINTS("Failed to copy " + xml_file); - - String pdb_file = p_assembly_name + ".pdb"; - if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK) - WARN_PRINTS("Failed to copy " + pdb_file); - - Error err = da->copy(assembly_src, assembly_dst); - - if (err != OK) { - show_build_error_dialog("Failed to copy " + assembly_file); - return false; - } - - GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false); - } - - return true; -} - -String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) { - - uint64_t api_hash = p_api_type == APIAssembly::API_CORE ? - GDMono::get_singleton()->get_api_core_hash() : - GDMono::get_singleton()->get_api_editor_hash(); - return String::num_uint64(api_hash) + - "_" + String::num_uint64(BindingsGenerator::get_version()) + - "_" + String::num_uint64(CS_GLUE_VERSION); -} - -bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) { - - String api_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; - - String editor_prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir(); - String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - - if (FileAccess::exists(editor_prebuilt_api_dir.plus_file(api_name + ".dll"))) { - EditorProgress pr("mono_copy_prebuilt_api_assembly", "Copying prebuilt " + api_name + " assembly...", 1); - pr.step("Copying " + api_name + " assembly", 0); - return GodotSharpBuilds::copy_api_assembly(editor_prebuilt_api_dir, res_assemblies_dir, api_name, p_api_type); - } - - String api_build_config = "Release"; - - EditorProgress pr("mono_build_release_" API_SOLUTION_NAME, "Building " API_SOLUTION_NAME " solution...", 3); - - pr.step("Generating " API_SOLUTION_NAME " solution", 0); - - String api_sln_dir = GodotSharpDirs::get_mono_solutions_dir() - .plus_file(_api_folder_name(APIAssembly::API_CORE)); - - String api_sln_file = api_sln_dir.plus_file(API_SOLUTION_NAME ".sln"); - - if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) { - BindingsGenerator bindings_generator; - - if (!OS::get_singleton()->is_stdout_verbose()) { - bindings_generator.set_log_print_enabled(false); - } - - Error err = bindings_generator.generate_cs_api(api_sln_dir); - if (err != OK) { - show_build_error_dialog("Failed to generate " API_SOLUTION_NAME " solution. Error: " + itos(err)); - return false; - } - } - - pr.step("Building " API_SOLUTION_NAME " solution", 1); - - if (!GodotSharpBuilds::build_api_sln(api_sln_dir, api_build_config)) - return false; - - pr.step("Copying " + api_name + " assembly", 2); - - // Copy the built assembly to the assemblies directory - String api_assembly_dir = api_sln_dir.plus_file(api_name).plus_file("bin").plus_file(api_build_config); - if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name, p_api_type)) - return false; - - return true; -} - -bool GodotSharpBuilds::build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines) { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) - return true; // No solution to build - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) - return false; - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) - return false; - - EditorProgress pr("mono_project_debug_build", "Building project solution...", 1); - pr.step("Building project solution", 0); - - MonoBuildInfo build_info(GodotSharpDirs::get_project_sln_path(), p_config); - - // Add Godot defines -#ifdef WINDOWS_ENABLED - String constants = "GodotDefineConstants=\""; -#else - String constants = "GodotDefineConstants=\\\""; -#endif - - for (int i = 0; i < p_godot_defines.size(); i++) { - constants += "GODOT_" + p_godot_defines[i].to_upper().replace("-", "_").replace(" ", "_").replace(";", "_") + ";"; - } - -#ifdef REAL_T_IS_DOUBLE - constants += "GODOT_REAL_T_IS_DOUBLE;"; -#endif - -#ifdef WINDOWS_ENABLED - constants += "\""; -#else - constants += "\\\""; -#endif - build_info.custom_props.push_back(constants); - - if (!GodotSharpBuilds::get_singleton()->build(build_info)) { - GodotSharpBuilds::show_build_error_dialog("Failed to build project solution"); - return false; - } - - return true; -} - -bool GodotSharpBuilds::editor_build_callback() { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) - return true; // No solution to build - - String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor"); - String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player"); - - Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor); - ERR_FAIL_COND_V(metadata_err != OK, false); - - if (FileAccess::exists(scripts_metadata_path_editor)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player); - - ERR_EXPLAIN("Failed to copy scripts metadata file"); - ERR_FAIL_COND_V(copy_err != OK, false); - } - - Vector<String> godot_defines; - godot_defines.push_back(OS::get_singleton()->get_name()); - godot_defines.push_back(sizeof(void *) == 4 ? "32" : "64"); - return build_project_blocking("Tools", godot_defines); -} - -GodotSharpBuilds *GodotSharpBuilds::singleton = NULL; - -void GodotSharpBuilds::build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code) { - - BuildProcess *match = builds.getptr(p_build_info); - ERR_FAIL_NULL(match); - - BuildProcess &bp = *match; - bp.on_exit(p_exit_code); -} - -void GodotSharpBuilds::restart_build(MonoBuildTab *p_build_tab) { -} - -void GodotSharpBuilds::stop_build(MonoBuildTab *p_build_tab) { -} - -bool GodotSharpBuilds::build(const MonoBuildInfo &p_build_info) { - - BuildProcess *match = builds.getptr(p_build_info); - - if (match) { - BuildProcess &bp = *match; - bp.start(true); - return bp.exit_code == 0; - } else { - BuildProcess bp = BuildProcess(p_build_info); - bp.start(true); - builds.set(p_build_info, bp); - return bp.exit_code == 0; - } -} - -bool GodotSharpBuilds::build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) { - - BuildProcess *match = builds.getptr(p_build_info); - - if (match) { - BuildProcess &bp = *match; - bp.start(); - return !bp.exited; // failed to start - } else { - BuildProcess bp = BuildProcess(p_build_info, p_callback); - bp.start(); - builds.set(p_build_info, bp); - return !bp.exited; // failed to start - } -} - -GodotSharpBuilds::GodotSharpBuilds() { - - singleton = this; - - EditorNode::get_singleton()->add_build_callback(&GodotSharpBuilds::editor_build_callback); - - // Build tool settings - EditorSettings *ed_settings = EditorSettings::get_singleton(); - -#ifdef WINDOWS_ENABLED - EDITOR_DEF("mono/builds/build_tool", MSBUILD_VS); -#else - EDITOR_DEF("mono/builds/build_tool", MSBUILD_MONO); -#endif - - ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM, - PROP_NAME_MSBUILD_MONO -#ifdef WINDOWS_ENABLED - "," PROP_NAME_MSBUILD_VS -#endif - "," PROP_NAME_XBUILD)); - - EDITOR_DEF("mono/builds/print_build_output", false); -} - -GodotSharpBuilds::~GodotSharpBuilds() { - - singleton = NULL; -} - -void GodotSharpBuilds::BuildProcess::on_exit(int p_exit_code) { - - exited = true; - exit_code = p_exit_code; - build_tab->on_build_exit(p_exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR); - build_instance.unref(); - - if (exit_callback) - exit_callback(exit_code); -} - -void GodotSharpBuilds::BuildProcess::start(bool p_blocking) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - exit_code = -1; - - String log_dirpath = build_info.get_log_dirpath(); - - if (build_tab) { - build_tab->on_build_start(); - } else { - build_tab = memnew(MonoBuildTab(build_info, log_dirpath)); - MonoBottomPanel::get_singleton()->add_build_tab(build_tab); - } - - if (p_blocking) { - // Required in order to update the build tasks list - Main::iteration(); - } - - if (!exited) { - exited = true; - String message = "Tried to start build process, but it is already running"; - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - - exited = false; - - // Remove old issues file - - String issues_file = get_msbuild_issues_filename(); - DirAccessRef d = DirAccess::create_for_path(log_dirpath); - if (d->file_exists(issues_file)) { - Error err = d->remove(issues_file); - if (err != OK) { - exited = true; - String file_path = ProjectSettings::get_singleton()->localize_path(log_dirpath).plus_file(issues_file); - String message = "Cannot remove issues file: " + file_path; - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - } - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Build", "BuildInstance"); - - MonoObject *mono_object = mono_object_new(mono_domain_get(), klass->get_mono_ptr()); - - // Construct - - Variant solution = build_info.solution; - Variant config = build_info.configuration; - - const Variant *ctor_args[2] = { &solution, &config }; - - MonoException *exc = NULL; - GDMonoMethod *ctor = klass->get_method(".ctor", 2); - ctor->invoke(mono_object, ctor_args, &exc); - - if (exc) { - exited = true; - GDMonoUtils::debug_unhandled_exception(exc); - String message = "The build constructor threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc); - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - - // Call Build - - String logger_assembly_path = GDMono::get_singleton()->get_editor_tools_assembly()->get_path(); - Variant logger_assembly = ProjectSettings::get_singleton()->globalize_path(logger_assembly_path); - Variant logger_output_dir = log_dirpath; - Variant custom_props = build_info.custom_props; - - const Variant *args[3] = { &logger_assembly, &logger_output_dir, &custom_props }; - - exc = NULL; - GDMonoMethod *build_method = klass->get_method(p_blocking ? "Build" : "BuildAsync", 3); - build_method->invoke(mono_object, args, &exc); - - if (exc) { - exited = true; - GDMonoUtils::debug_unhandled_exception(exc); - String message = "The build method threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc); - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - - // Build returned - - if (p_blocking) { - exited = true; - exit_code = klass->get_field("exitCode")->get_int_value(mono_object); - - if (exit_code != 0) { - String log_filepath = build_info.get_log_dirpath().plus_file(get_msbuild_log_filename()); - print_verbose("MSBuild exited with code: " + itos(exit_code) + ". Log file: " + log_filepath); - } - - build_tab->on_build_exit(exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR); - } else { - build_instance = MonoGCHandle::create_strong(mono_object); - exited = false; - } -} - -GodotSharpBuilds::BuildProcess::BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) : - build_info(p_build_info), - build_tab(NULL), - exit_callback(p_callback), - exited(true), - exit_code(-1) { -} diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h deleted file mode 100644 index 2e9050e12e..0000000000 --- a/modules/mono/editor/godotsharp_builds.h +++ /dev/null @@ -1,103 +0,0 @@ -/*************************************************************************/ -/* godotsharp_builds.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 GODOTSHARP_BUILDS_H -#define GODOTSHARP_BUILDS_H - -#include "../mono_gd/gd_mono.h" -#include "mono_bottom_panel.h" -#include "mono_build_info.h" - -typedef void (*GodotSharpBuild_ExitCallback)(int); - -class GodotSharpBuilds { - -private: - struct BuildProcess { - Ref<MonoGCHandle> build_instance; - MonoBuildInfo build_info; - MonoBuildTab *build_tab; - GodotSharpBuild_ExitCallback exit_callback; - bool exited; - int exit_code; - - void on_exit(int p_exit_code); - void start(bool p_blocking = false); - - BuildProcess() {} - BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL); - }; - - HashMap<MonoBuildInfo, BuildProcess, MonoBuildInfo::Hasher> builds; - - static String _api_folder_name(APIAssembly::Type p_api_type); - - static GodotSharpBuilds *singleton; - -public: - enum BuildTool { - MSBUILD_MONO, -#ifdef WINDOWS_ENABLED - MSBUILD_VS, -#endif - XBUILD // Deprecated - }; - - _FORCE_INLINE_ static GodotSharpBuilds *get_singleton() { return singleton; } - - static void register_internal_calls(); - - static void show_build_error_dialog(const String &p_message); - - static const char *get_msbuild_issues_filename() { return "msbuild_issues.csv"; } - static const char *get_msbuild_log_filename() { return "msbuild_log.txt"; } - - void build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code); - - void restart_build(MonoBuildTab *p_build_tab); - void stop_build(MonoBuildTab *p_build_tab); - - bool build(const MonoBuildInfo &p_build_info); - bool build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL); - - static bool build_api_sln(const String &p_api_sln_dir, const String &p_config); - static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type); - - static bool make_api_assembly(APIAssembly::Type p_api_type); - - static bool build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines); - - static bool editor_build_callback(); - - GodotSharpBuilds(); - ~GodotSharpBuilds(); -}; - -#endif // GODOTSHARP_BUILDS_H diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp deleted file mode 100644 index 9d42528927..0000000000 --- a/modules/mono/editor/godotsharp_editor.cpp +++ /dev/null @@ -1,581 +0,0 @@ -/*************************************************************************/ -/* godotsharp_editor.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 "godotsharp_editor.h" - -#include "core/message_queue.h" -#include "core/os/os.h" -#include "core/project_settings.h" -#include "scene/gui/control.h" -#include "scene/main/node.h" - -#include "../csharp_script.h" -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../utils/path_utils.h" -#include "bindings_generator.h" -#include "csharp_project.h" -#include "dotnet_solution.h" -#include "godotsharp_export.h" - -#ifdef OSX_ENABLED -#include "../utils/osx_utils.h" -#endif - -#ifdef WINDOWS_ENABLED -#include "../utils/mono_reg_utils.h" -#endif - -GodotSharpEditor *GodotSharpEditor::singleton = NULL; - -bool GodotSharpEditor::_create_project_solution() { - - EditorProgress pr("create_csharp_solution", TTR("Generating solution..."), 2); - - pr.step(TTR("Generating C# project...")); - - String path = OS::get_singleton()->get_resource_dir(); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; - } - - String guid = CSharpProject::generate_game_project(path, name); - - if (guid.length()) { - - DotNetSolution solution(name); - - if (!solution.set_path(path)) { - show_error_dialog(TTR("Failed to create solution.")); - return false; - } - - DotNetSolution::ProjectInfo proj_info; - proj_info.guid = guid; - proj_info.relpath = name + ".csproj"; - proj_info.configs.push_back("Debug"); - proj_info.configs.push_back("Release"); - proj_info.configs.push_back("Tools"); - - solution.add_new_project(name, proj_info); - - Error sln_error = solution.save(); - - if (sln_error != OK) { - show_error_dialog(TTR("Failed to save solution.")); - return false; - } - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) - return false; - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) - return false; - - pr.step(TTR("Done")); - - // Here, after all calls to progress_task_step - call_deferred("_remove_create_sln_menu_option"); - - } else { - show_error_dialog(TTR("Failed to create C# project.")); - } - - return true; -} - -void GodotSharpEditor::_make_api_solutions_if_needed() { - // I'm sick entirely of ProgressDialog - - static int attempts_left = 100; - - if (MessageQueue::get_singleton()->is_flushing() || !SceneTree::get_singleton()) { - ERR_FAIL_COND(attempts_left == 0); // You've got to be kidding - - if (SceneTree::get_singleton()) { - SceneTree::get_singleton()->connect("idle_frame", this, "_make_api_solutions_if_needed", Vector<Variant>()); - } else { - call_deferred("_make_api_solutions_if_needed"); - } - - attempts_left--; - return; - } - - // Recursion guard needed because signals don't play well with ProgressDialog either, but unlike - // the message queue, with signals the collateral damage should be minimal in the worst case. - static bool recursion_guard = false; - if (!recursion_guard) { - recursion_guard = true; - - // Oneshot signals don't play well with ProgressDialog either, so we do it this way instead - SceneTree::get_singleton()->disconnect("idle_frame", this, "_make_api_solutions_if_needed"); - - _make_api_solutions_if_needed_impl(); - - recursion_guard = false; - } -} - -void GodotSharpEditor::_make_api_solutions_if_needed_impl() { - // If the project has a solution and C# project make sure the API assemblies are present and up to date - String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - - if (!FileAccess::exists(res_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll")) || - GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) { - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) - return; - } - - if (!FileAccess::exists(res_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll")) || - GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) { - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) - return; // Redundant? I don't think so - } -} - -void GodotSharpEditor::_remove_create_sln_menu_option() { - - menu_popup->remove_item(menu_popup->get_item_index(MENU_CREATE_SLN)); - - bottom_panel_btn->show(); -} - -void GodotSharpEditor::_show_about_dialog() { - - bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start"); - about_dialog_checkbox->set_pressed(show_on_start); - about_dialog->popup_centered_minsize(); -} - -void GodotSharpEditor::_toggle_about_dialog_on_start(bool p_enabled) { - - bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start"); - if (show_on_start != p_enabled) { - EditorSettings::get_singleton()->set_setting("mono/editor/show_info_on_start", p_enabled); - } -} - -void GodotSharpEditor::_build_solution_pressed() { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) { - if (!_create_project_solution()) - return; // Failed to create solution - } - - MonoBottomPanel::get_singleton()->call("_build_project_pressed"); -} - -void GodotSharpEditor::_menu_option_pressed(int p_id) { - - switch (p_id) { - case MENU_CREATE_SLN: { - - _create_project_solution(); - } break; - case MENU_ABOUT_CSHARP: { - - _show_about_dialog(); - } break; - default: - ERR_FAIL(); - } -} - -void GodotSharpEditor::_notification(int p_notification) { - - switch (p_notification) { - - case NOTIFICATION_READY: { - - bool show_info_dialog = EDITOR_GET("mono/editor/show_info_on_start"); - if (show_info_dialog) { - about_dialog->set_exclusive(true); - _show_about_dialog(); - // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive then. - about_dialog->set_exclusive(false); - } - } - } -} - -void GodotSharpEditor::_bind_methods() { - - ClassDB::bind_method(D_METHOD("_build_solution_pressed"), &GodotSharpEditor::_build_solution_pressed); - ClassDB::bind_method(D_METHOD("_create_project_solution"), &GodotSharpEditor::_create_project_solution); - ClassDB::bind_method(D_METHOD("_make_api_solutions_if_needed"), &GodotSharpEditor::_make_api_solutions_if_needed); - ClassDB::bind_method(D_METHOD("_remove_create_sln_menu_option"), &GodotSharpEditor::_remove_create_sln_menu_option); - ClassDB::bind_method(D_METHOD("_toggle_about_dialog_on_start"), &GodotSharpEditor::_toggle_about_dialog_on_start); - ClassDB::bind_method(D_METHOD("_menu_option_pressed", "id"), &GodotSharpEditor::_menu_option_pressed); -} - -MonoBoolean godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled(MonoString *p_bundle_id) { -#ifdef OSX_ENABLED - return (MonoBoolean)osx_is_app_bundle_installed(GDMonoMarshal::mono_string_to_godot(p_bundle_id)); -#else - (void)p_bundle_id; // UNUSED - ERR_FAIL_V(false); -#endif -} - -MonoString *godot_icall_Utils_OS_GetPlatformName() { - return GDMonoMarshal::mono_string_from_godot(OS::get_singleton()->get_name()); -} - -void GodotSharpEditor::register_internal_calls() { - - static bool registered = false; - ERR_FAIL_COND(registered); - registered = true; - - mono_add_internal_call("GodotSharpTools.Editor.MonoDevelopInstance::IsApplicationBundleInstalled", (void *)godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled); - mono_add_internal_call("GodotSharpTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName); - - GodotSharpBuilds::register_internal_calls(); - GodotSharpExport::register_internal_calls(); -} - -void GodotSharpEditor::show_error_dialog(const String &p_message, const String &p_title) { - - error_dialog->set_title(p_title); - error_dialog->set_text(p_message); - error_dialog->popup_centered_minsize(); -} - -Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { - - ExternalEditor editor = ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))); - - switch (editor) { - case EDITOR_VSCODE: { - static String vscode_path; - - if (vscode_path.empty() || !FileAccess::exists(vscode_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - bool found = false; - - // TODO: Use initializer lists once C++11 is allowed - - static Vector<String> vscode_names; - if (vscode_names.empty()) { - vscode_names.push_back("code"); - vscode_names.push_back("code-oss"); - vscode_names.push_back("vscode"); - vscode_names.push_back("vscode-oss"); - vscode_names.push_back("visual-studio-code"); - vscode_names.push_back("visual-studio-code-oss"); - } - for (int i = 0; i < vscode_names.size(); i++) { - vscode_path = path_which(vscode_names[i]); - if (!vscode_path.empty()) { - found = true; - break; - } - } - - if (!found) - vscode_path.clear(); // Not found, clear so next time the empty() check is enough - } - - List<String> args; - -#ifdef OSX_ENABLED - // The package path is '/Applications/Visual Studio Code.app' - static const String vscode_bundle_id = "com.microsoft.VSCode"; - static bool osx_app_bundle_installed = osx_is_app_bundle_installed(vscode_bundle_id); - - if (osx_app_bundle_installed) { - args.push_back("-b"); - args.push_back(vscode_bundle_id); - - // The reusing of existing windows made by the 'open' command might not choose a wubdiw that is - // editing our folder. It's better to ask for a new window and let VSCode do the window management. - args.push_back("-n"); - - // The open process must wait until the application finishes (which is instant in VSCode's case) - args.push_back("--wait-apps"); - - args.push_back("--args"); - } -#endif - - args.push_back(ProjectSettings::get_singleton()->get_resource_path()); - - String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path()); - - if (p_line >= 0) { - args.push_back("-g"); - args.push_back(script_path + ":" + itos(p_line + 1) + ":" + itos(p_col)); - } else { - args.push_back(script_path); - } - -#ifdef OSX_ENABLED - ERR_EXPLAIN("Cannot find code editor: VSCode"); - ERR_FAIL_COND_V(!osx_app_bundle_installed && vscode_path.empty(), ERR_FILE_NOT_FOUND); - - String command = osx_app_bundle_installed ? "/usr/bin/open" : vscode_path; -#else - ERR_EXPLAIN("Cannot find code editor: VSCode"); - ERR_FAIL_COND_V(vscode_path.empty(), ERR_FILE_NOT_FOUND); - - String command = vscode_path; -#endif - - Error err = OS::get_singleton()->execute(command, args, false); - - if (err != OK) { - ERR_PRINT("Error when trying to execute code editor: VSCode"); - return err; - } - } break; -#ifdef OSX_ENABLED - case EDITOR_VISUALSTUDIO_MAC: - // [[fallthrough]]; -#endif - case EDITOR_MONODEVELOP: { -#ifdef OSX_ENABLED - bool is_visualstudio = editor == EDITOR_VISUALSTUDIO_MAC; - - MonoDevelopInstance **instance = is_visualstudio ? - &visualstudio_mac_instance : - &monodevelop_instance; - - MonoDevelopInstance::EditorId editor_id = is_visualstudio ? - MonoDevelopInstance::VISUALSTUDIO_FOR_MAC : - MonoDevelopInstance::MONODEVELOP; -#else - MonoDevelopInstance **instance = &monodevelop_instance; - MonoDevelopInstance::EditorId editor_id = MonoDevelopInstance::MONODEVELOP; -#endif - - if (!*instance) - *instance = memnew(MonoDevelopInstance(GodotSharpDirs::get_project_sln_path(), editor_id)); - - String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path()); - - if (p_line >= 0) { - script_path += ";" + itos(p_line + 1) + ";" + itos(p_col); - } - - (*instance)->execute(script_path); - } break; - default: - return ERR_UNAVAILABLE; - } - - return OK; -} - -bool GodotSharpEditor::overrides_external_editor() { - - return ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))) != EDITOR_NONE; -} - -GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { - - singleton = this; - - monodevelop_instance = NULL; -#ifdef OSX_ENABLED - visualstudio_mac_instance = NULL; -#endif - - editor = p_editor; - - error_dialog = memnew(AcceptDialog); - editor->get_gui_base()->add_child(error_dialog); - - bottom_panel_btn = editor->add_bottom_panel_item(TTR("Mono"), memnew(MonoBottomPanel(editor))); - - godotsharp_builds = memnew(GodotSharpBuilds); - - editor->add_child(memnew(MonoReloadNode)); - - menu_popup = memnew(PopupMenu); - menu_popup->hide(); - menu_popup->set_as_toplevel(true); - menu_popup->set_pass_on_modal_close_click(false); - - editor->add_tool_submenu_item("Mono", menu_popup); - - // TODO: Remove or edit this info dialog once Mono support is no longer in alpha - { - menu_popup->add_item(TTR("About C# support"), MENU_ABOUT_CSHARP); - about_dialog = memnew(AcceptDialog); - editor->get_gui_base()->add_child(about_dialog); - about_dialog->set_title("Important: C# support is not feature-complete"); - - // We don't use set_text() as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox - // we'll add. Instead we add containers and a new autowrapped Label inside. - - // Main VBoxContainer (icon + label on top, checkbox at bottom) - VBoxContainer *about_vbc = memnew(VBoxContainer); - about_dialog->add_child(about_vbc); - - // HBoxContainer for icon + label - HBoxContainer *about_hbc = memnew(HBoxContainer); - about_vbc->add_child(about_hbc); - - TextureRect *about_icon = memnew(TextureRect); - about_hbc->add_child(about_icon); - Ref<Texture> about_icon_tex = about_icon->get_icon("NodeWarning", "EditorIcons"); - about_icon->set_texture(about_icon_tex); - - Label *about_label = memnew(Label); - about_hbc->add_child(about_label); - about_label->set_custom_minimum_size(Size2(600, 150) * EDSCALE); - about_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); - about_label->set_autowrap(true); - String about_text = - String("C# support in Godot Engine is in late alpha stage and, while already usable, ") + - "it is not meant for use in production.\n\n" + - "Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " + - "Bugs and usability issues will be addressed gradually over future releases, " + - "potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + - "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" + - " https://github.com/godotengine/godot/issues\n\n" + - "Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!"; - about_label->set_text(about_text); - - EDITOR_DEF("mono/editor/show_info_on_start", true); - - // CheckBox in main container - about_dialog_checkbox = memnew(CheckBox); - about_vbc->add_child(about_dialog_checkbox); - about_dialog_checkbox->set_text("Show this warning when starting the editor"); - about_dialog_checkbox->connect("toggled", this, "_toggle_about_dialog_on_start"); - } - - String sln_path = GodotSharpDirs::get_project_sln_path(); - String csproj_path = GodotSharpDirs::get_project_csproj_path(); - - if (FileAccess::exists(sln_path) && FileAccess::exists(csproj_path)) { - // Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized. - call_deferred("_make_api_solutions_if_needed"); - } else { - bottom_panel_btn->hide(); - menu_popup->add_item(TTR("Create C# solution"), MENU_CREATE_SLN); - } - - menu_popup->connect("id_pressed", this, "_menu_option_pressed"); - - ToolButton *build_button = memnew(ToolButton); - build_button->set_text("Build"); - build_button->set_tooltip("Build solution"); - build_button->set_focus_mode(Control::FOCUS_NONE); - build_button->connect("pressed", this, "_build_solution_pressed"); - editor->get_menu_hb()->add_child(build_button); - - // External editor settings - EditorSettings *ed_settings = EditorSettings::get_singleton(); - EDITOR_DEF("mono/editor/external_editor", EDITOR_NONE); - - String settings_hint_str = "Disabled"; - -#if defined(WINDOWS_ENABLED) - settings_hint_str += ",MonoDevelop,Visual Studio Code"; -#elif defined(OSX_ENABLED) - settings_hint_str += ",Visual Studio,MonoDevelop,Visual Studio Code"; -#elif defined(UNIX_ENABLED) - settings_hint_str += ",MonoDevelop,Visual Studio Code"; -#endif - - ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/editor/external_editor", PROPERTY_HINT_ENUM, settings_hint_str)); - - // Export plugin - Ref<GodotSharpExport> godotsharp_export; - godotsharp_export.instance(); - EditorExport::get_singleton()->add_export_plugin(godotsharp_export); -} - -GodotSharpEditor::~GodotSharpEditor() { - - singleton = NULL; - - memdelete(godotsharp_builds); - - if (monodevelop_instance) { - memdelete(monodevelop_instance); - monodevelop_instance = NULL; - } -} - -MonoReloadNode *MonoReloadNode::singleton = NULL; - -void MonoReloadNode::_reload_timer_timeout() { - - if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { - CSharpLanguage::get_singleton()->reload_assemblies(false); - } -} - -void MonoReloadNode::restart_reload_timer() { - - reload_timer->stop(); - reload_timer->start(); -} - -void MonoReloadNode::_bind_methods() { - - ClassDB::bind_method(D_METHOD("_reload_timer_timeout"), &MonoReloadNode::_reload_timer_timeout); -} - -void MonoReloadNode::_notification(int p_what) { - switch (p_what) { - case MainLoop::NOTIFICATION_WM_FOCUS_IN: { - restart_reload_timer(); - if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { - CSharpLanguage::get_singleton()->reload_assemblies(false); - } - } break; - default: { - } break; - }; -} - -MonoReloadNode::MonoReloadNode() { - - singleton = this; - - reload_timer = memnew(Timer); - add_child(reload_timer); - reload_timer->set_one_shot(false); - reload_timer->set_wait_time(EDITOR_DEF("mono/assembly_watch_interval_sec", 0.5)); - reload_timer->connect("timeout", this, "_reload_timer_timeout"); - reload_timer->start(); -} - -MonoReloadNode::~MonoReloadNode() { - - singleton = NULL; -} diff --git a/modules/mono/editor/godotsharp_editor.h b/modules/mono/editor/godotsharp_editor.h deleted file mode 100644 index d5bd8ba126..0000000000 --- a/modules/mono/editor/godotsharp_editor.h +++ /dev/null @@ -1,134 +0,0 @@ -/*************************************************************************/ -/* godotsharp_editor.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 GODOTSHARP_EDITOR_H -#define GODOTSHARP_EDITOR_H - -#include "godotsharp_builds.h" -#include "monodevelop_instance.h" - -class GodotSharpEditor : public Node { - GDCLASS(GodotSharpEditor, Node); - - EditorNode *editor; - - MenuButton *menu_button; - PopupMenu *menu_popup; - - AcceptDialog *error_dialog; - AcceptDialog *about_dialog; - CheckBox *about_dialog_checkbox; - - ToolButton *bottom_panel_btn; - - GodotSharpBuilds *godotsharp_builds; - - MonoDevelopInstance *monodevelop_instance; -#ifdef OSX_ENABLED - MonoDevelopInstance *visualstudio_mac_instance; -#endif - - bool _create_project_solution(); - void _make_api_solutions_if_needed(); - void _make_api_solutions_if_needed_impl(); - - void _remove_create_sln_menu_option(); - void _show_about_dialog(); - void _toggle_about_dialog_on_start(bool p_enabled); - - void _menu_option_pressed(int p_id); - - void _build_solution_pressed(); - - static GodotSharpEditor *singleton; - -protected: - void _notification(int p_notification); - static void _bind_methods(); - -public: - enum MenuOptions { - MENU_CREATE_SLN, - MENU_ABOUT_CSHARP, - }; - - enum ExternalEditor { - EDITOR_NONE, -#if defined(WINDOWS_ENABLED) - //EDITOR_VISUALSTUDIO, // TODO - EDITOR_MONODEVELOP, - EDITOR_VSCODE -#elif defined(OSX_ENABLED) - EDITOR_VISUALSTUDIO_MAC, - EDITOR_MONODEVELOP, - EDITOR_VSCODE -#elif defined(UNIX_ENABLED) - EDITOR_MONODEVELOP, - EDITOR_VSCODE -#endif - }; - - _FORCE_INLINE_ static GodotSharpEditor *get_singleton() { return singleton; } - - static void register_internal_calls(); - - void show_error_dialog(const String &p_message, const String &p_title = "Error"); - - Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col); - bool overrides_external_editor(); - - GodotSharpEditor(EditorNode *p_editor); - ~GodotSharpEditor(); -}; - -class MonoReloadNode : public Node { - GDCLASS(MonoReloadNode, Node); - - Timer *reload_timer; - - void _reload_timer_timeout(); - - static MonoReloadNode *singleton; - -protected: - static void _bind_methods(); - - void _notification(int p_what); - -public: - _FORCE_INLINE_ static MonoReloadNode *get_singleton() { return singleton; } - - void restart_reload_timer(); - - MonoReloadNode(); - ~MonoReloadNode(); -}; - -#endif // GODOTSHARP_EDITOR_H diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 126178125f..020bb70a08 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -30,179 +30,28 @@ #include "godotsharp_export.h" -#include "core/version.h" +#include <mono/metadata/image.h> -#include "../csharp_script.h" -#include "../godotsharp_defs.h" -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "csharp_project.h" -#include "godotsharp_builds.h" +#include "../mono_gd/gd_mono.h" +#include "../mono_gd/gd_mono_assembly.h" -static MonoString *godot_icall_GodotSharpExport_GetTemplatesDir() { - String current_version = VERSION_FULL_CONFIG; - String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(current_version); - return GDMonoMarshal::mono_string_from_godot(ProjectSettings::get_singleton()->globalize_path(templates_dir)); -} - -static MonoString *godot_icall_GodotSharpExport_GetDataDirName() { - String appname = ProjectSettings::get_singleton()->get("application/config/name"); - String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - return GDMonoMarshal::mono_string_from_godot("data_" + appname_safe); -} - -void GodotSharpExport::register_internal_calls() { - static bool registered = false; - ERR_FAIL_COND(registered); - registered = true; - - mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetTemplatesDir", (void *)godot_icall_GodotSharpExport_GetTemplatesDir); - mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetDataDirName", (void *)godot_icall_GodotSharpExport_GetDataDirName); -} - -void GodotSharpExport::_export_file(const String &p_path, const String &p_type, const Set<String> &) { - - if (p_type != CSharpLanguage::get_singleton()->get_type()) - return; - - ERR_FAIL_COND(p_path.get_extension() != CSharpLanguage::get_singleton()->get_extension()); - - // TODO what if the source file is not part of the game's C# project - - if (!GLOBAL_GET("mono/export/include_scripts_content")) { - // We don't want to include the source code on exported games - add_file(p_path, Vector<uint8_t>(), false); - skip(); - } -} - -void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags) { - - // TODO right now there is no way to stop the export process with an error - - ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - ERR_FAIL_NULL(TOOLS_DOMAIN); - ERR_FAIL_NULL(GDMono::get_singleton()->get_editor_tools_assembly()); - - if (FileAccess::exists(GodotSharpDirs::get_project_sln_path())) { - String build_config = p_debug ? "Debug" : "Release"; - - String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata." + String(p_debug ? "debug" : "release")); - Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path); - ERR_FAIL_COND(metadata_err != OK); - - ERR_FAIL_COND(!_add_file(scripts_metadata_path, scripts_metadata_path)); - - // Turn export features into defines - Vector<String> godot_defines; - for (Set<String>::Element *E = p_features.front(); E; E = E->next()) { - godot_defines.push_back(E->get()); - } - ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config, godot_defines)); - - // Add dependency assemblies - - Map<String, String> dependencies; - - String project_dll_name = ProjectSettings::get_singleton()->get("application/config/name"); - if (project_dll_name.empty()) { - project_dll_name = "UnnamedProject"; - } - - String project_dll_src_dir = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config); - String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name + ".dll"); - dependencies.insert(project_dll_name, project_dll_src_path); - - { - MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain"); - ERR_FAIL_NULL(export_domain); - _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); - - _GDMONO_SCOPE_DOMAIN_(export_domain); +String get_assemblyref_name(MonoImage *p_image, int index) { + const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF); - GDMonoAssembly *scripts_assembly = NULL; - bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name, - project_dll_src_path, &scripts_assembly, /* refonly: */ true); + uint32_t cols[MONO_ASSEMBLYREF_SIZE]; - ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name); - ERR_FAIL_COND(!load_success); + mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE); - Vector<String> search_dirs; - String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); - String android_bcl_dir = templates_dir.plus_file("android-bcl"); - - String custom_lib_dir; - - if (p_features.find("Android") && DirAccess::exists(android_bcl_dir)) { - custom_lib_dir = android_bcl_dir; - } - - GDMonoAssembly::fill_search_dirs(search_dirs, build_config, custom_lib_dir); - - Error depend_error = _get_assembly_dependencies(scripts_assembly, search_dirs, dependencies); - ERR_FAIL_COND(depend_error != OK); - } - - for (Map<String, String>::Element *E = dependencies.front(); E; E = E->next()) { - String depend_src_path = E->value(); - String depend_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(depend_src_path.get_file()); - ERR_FAIL_COND(!_add_file(depend_src_path, depend_dst_path)); - } - } - - // Mono specific export template extras (data dir) - - GDMonoClass *export_class = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "GodotSharpExport"); - ERR_FAIL_NULL(export_class); - GDMonoMethod *export_begin_method = export_class->get_method("_ExportBegin", 4); - ERR_FAIL_NULL(export_begin_method); - - MonoArray *features = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_features.size()); - int i = 0; - for (const Set<String>::Element *E = p_features.front(); E; E = E->next()) { - MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E->get()); - mono_array_setref(features, i, boxed); - i++; - } - - MonoBoolean debug = p_debug; - MonoString *path = GDMonoMarshal::mono_string_from_godot(p_path); - uint32_t flags = p_flags; - void *args[4] = { features, &debug, path, &flags }; - MonoException *exc = NULL; - export_begin_method->invoke_raw(NULL, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL(); - } -} - -bool GodotSharpExport::_add_file(const String &p_src_path, const String &p_dst_path, bool p_remap) { - - FileAccessRef f = FileAccess::open(p_src_path, FileAccess::READ); - ERR_FAIL_COND_V(!f, false); - - Vector<uint8_t> data; - data.resize(f->get_len()); - f->get_buffer(data.ptrw(), data.size()); - - add_file(p_dst_path, data, p_remap); - - return true; + return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])); } -Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies) { - +Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) { MonoImage *image = p_assembly->get_image(); for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) { - MonoAssemblyName *ref_aname = aname_prealloc; - mono_assembly_get_assemblyref(image, i, ref_aname); - String ref_name = mono_assembly_name_get_name(ref_aname); + String ref_name = get_assemblyref_name(image, i); - if (r_dependencies.find(ref_name)) + if (r_dependencies.has(ref_name)) continue; GDMonoAssembly *ref_assembly = NULL; @@ -241,9 +90,9 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c ERR_FAIL_V(ERR_CANT_RESOLVE); } - r_dependencies.insert(ref_name, ref_assembly->get_path()); + r_dependencies[ref_name] = ref_assembly->get_path(); - Error err = _get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); + Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); if (err != OK) { ERR_EXPLAIN("Cannot load one of the dependencies for the assembly: " + ref_name); ERR_FAIL_V(err); @@ -253,14 +102,22 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c return OK; } -GodotSharpExport::GodotSharpExport() { - // MonoAssemblyName is an incomplete type (internal to mono), so we can't allocate it ourselves. - // There isn't any api to allocate an empty one either, so we need to do it this way. - aname_prealloc = mono_assembly_name_new("whatever"); - mono_assembly_name_free(aname_prealloc); // "it does not frees the object itself, only the name members" (typo included) -} +Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies) { + MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain"); + ERR_FAIL_NULL_V(export_domain, FAILED); + _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); + + _GDMONO_SCOPE_DOMAIN_(export_domain); + + GDMonoAssembly *scripts_assembly = NULL; + bool load_success = GDMono::get_singleton()->load_assembly_from(p_project_dll_name, + p_project_dll_src_path, &scripts_assembly, /* refonly: */ true); + + ERR_EXPLAIN("Cannot load assembly (refonly): " + p_project_dll_name); + ERR_FAIL_COND_V(!load_success, ERR_CANT_RESOLVE); + + Vector<String> search_dirs; + GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir); -GodotSharpExport::~GodotSharpExport() { - if (aname_prealloc) - mono_free(aname_prealloc); + return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies); } diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h index 4dc8ea75d5..8d121a6bc3 100644 --- a/modules/mono/editor/godotsharp_export.h +++ b/modules/mono/editor/godotsharp_export.h @@ -31,29 +31,19 @@ #ifndef GODOTSHARP_EXPORT_H #define GODOTSHARP_EXPORT_H -#include <mono/metadata/image.h> - -#include "editor/editor_export.h" +#include "core/dictionary.h" +#include "core/error_list.h" +#include "core/ustring.h" #include "../mono_gd/gd_mono_header.h" -class GodotSharpExport : public EditorExportPlugin { - - MonoAssemblyName *aname_prealloc; - - bool _add_file(const String &p_src_path, const String &p_dst_path, bool p_remap = false); - - Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies); - -protected: - virtual void _export_file(const String &p_path, const String &p_type, const Set<String> &p_features); - virtual void _export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags); +namespace GodotSharpExport { -public: - static void register_internal_calls(); +Error get_exported_assembly_dependencies(const String &p_project_dll_name, + const String &p_project_dll_src_path, const String &p_build_config, + const String &p_custom_lib_dir, Dictionary &r_dependencies); +Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies); - GodotSharpExport(); - ~GodotSharpExport(); -}; +} // namespace GodotSharpExport #endif // GODOTSHARP_EXPORT_H diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp deleted file mode 100644 index 5d9e39b6c2..0000000000 --- a/modules/mono/editor/mono_bottom_panel.cpp +++ /dev/null @@ -1,528 +0,0 @@ -/*************************************************************************/ -/* mono_bottom_panel.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 "mono_bottom_panel.h" - -#include "editor/plugins/script_editor_plugin.h" -#include "editor/script_editor_debugger.h" - -#include "../csharp_script.h" -#include "../godotsharp_dirs.h" -#include "csharp_project.h" -#include "godotsharp_editor.h" - -MonoBottomPanel *MonoBottomPanel::singleton = NULL; - -void MonoBottomPanel::_update_build_tabs_list() { - - build_tabs_list->clear(); - - int current_tab = build_tabs->get_current_tab(); - - bool no_current_tab = current_tab < 0 || current_tab >= build_tabs->get_tab_count(); - - for (int i = 0; i < build_tabs->get_child_count(); i++) { - - MonoBuildTab *tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(i)); - - if (tab) { - String item_name = tab->build_info.solution.get_file().get_basename(); - item_name += " [" + tab->build_info.configuration + "]"; - - build_tabs_list->add_item(item_name, tab->get_icon_texture()); - - String item_tooltip = "Solution: " + tab->build_info.solution; - item_tooltip += "\nConfiguration: " + tab->build_info.configuration; - item_tooltip += "\nStatus: "; - - if (tab->build_exited) { - item_tooltip += tab->build_result == MonoBuildTab::RESULT_SUCCESS ? "Succeeded" : "Errored"; - } else { - item_tooltip += "Running"; - } - - if (!tab->build_exited || tab->build_result == MonoBuildTab::RESULT_ERROR) { - item_tooltip += "\nErrors: " + itos(tab->error_count); - } - - item_tooltip += "\nWarnings: " + itos(tab->warning_count); - - build_tabs_list->set_item_tooltip(i, item_tooltip); - - if (no_current_tab || current_tab == i) { - build_tabs_list->select(i); - _build_tabs_item_selected(i); - } - } - } -} - -void MonoBottomPanel::add_build_tab(MonoBuildTab *p_build_tab) { - - build_tabs->add_child(p_build_tab); - raise_build_tab(p_build_tab); -} - -void MonoBottomPanel::raise_build_tab(MonoBuildTab *p_build_tab) { - - ERR_FAIL_COND(p_build_tab->get_parent() != build_tabs); - build_tabs->move_child(p_build_tab, 0); - _update_build_tabs_list(); -} - -void MonoBottomPanel::show_build_tab() { - - for (int i = 0; i < panel_tabs->get_tab_count(); i++) { - if (panel_tabs->get_tab_control(i) == panel_builds_tab) { - panel_tabs->set_current_tab(i); - editor->make_bottom_panel_item_visible(this); - return; - } - } - - ERR_PRINT("Builds tab not found"); -} - -void MonoBottomPanel::_build_tabs_item_selected(int p_idx) { - - ERR_FAIL_INDEX(p_idx, build_tabs->get_tab_count()); - - build_tabs->set_current_tab(p_idx); - if (!build_tabs->is_visible()) - build_tabs->set_visible(true); - - warnings_btn->set_visible(true); - errors_btn->set_visible(true); - view_log_btn->set_visible(true); -} - -void MonoBottomPanel::_build_tabs_nothing_selected() { - - if (build_tabs->get_tab_count() != 0) { // just in case - build_tabs->set_visible(false); - - // This callback is called when clicking on the empty space of the list. - // ItemList won't deselect the items automatically, so we must do it ourselves. - build_tabs_list->unselect_all(); - } - - warnings_btn->set_visible(false); - errors_btn->set_visible(false); - view_log_btn->set_visible(false); -} - -void MonoBottomPanel::_warnings_toggled(bool p_pressed) { - - int current_tab = build_tabs->get_current_tab(); - ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count()); - MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab)); - build_tab->warnings_visible = p_pressed; - build_tab->_update_issues_list(); -} - -void MonoBottomPanel::_errors_toggled(bool p_pressed) { - - int current_tab = build_tabs->get_current_tab(); - ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count()); - MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab)); - build_tab->errors_visible = p_pressed; - build_tab->_update_issues_list(); -} - -void MonoBottomPanel::_build_project_pressed() { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) - return; // No solution to build - - String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor"); - String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player"); - - Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor); - ERR_FAIL_COND(metadata_err != OK); - - if (FileAccess::exists(scripts_metadata_path_editor)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player); - - ERR_EXPLAIN("Failed to copy scripts metadata file"); - ERR_FAIL_COND(copy_err != OK); - } - - Vector<String> godot_defines; - godot_defines.push_back(OS::get_singleton()->get_name()); - godot_defines.push_back((sizeof(void *) == 4 ? "32" : "64")); - bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools", godot_defines); - - if (build_success) { - // Notify running game for hot-reload - ScriptEditor::get_singleton()->get_debugger()->reload_scripts(); - - // Hot-reload in the editor - MonoReloadNode::get_singleton()->restart_reload_timer(); - - if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { - CSharpLanguage::get_singleton()->reload_assemblies(false); - } - } -} - -void MonoBottomPanel::_view_log_pressed() { - - if (build_tabs_list->is_anything_selected()) { - Vector<int> selected_items = build_tabs_list->get_selected_items(); - CRASH_COND(selected_items.size() != 1); - int selected_item = selected_items[0]; - - MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_tab_control(selected_item)); - ERR_FAIL_NULL(build_tab); - - String log_dirpath = build_tab->get_build_info().get_log_dirpath(); - - OS::get_singleton()->shell_open(log_dirpath.plus_file(GodotSharpBuilds::get_msbuild_log_filename())); - } -} - -void MonoBottomPanel::_notification(int p_what) { - - switch (p_what) { - - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); - panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); - panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); - } break; - } -} - -void MonoBottomPanel::_bind_methods() { - - ClassDB::bind_method(D_METHOD("_build_project_pressed"), &MonoBottomPanel::_build_project_pressed); - ClassDB::bind_method(D_METHOD("_view_log_pressed"), &MonoBottomPanel::_view_log_pressed); - ClassDB::bind_method(D_METHOD("_warnings_toggled", "pressed"), &MonoBottomPanel::_warnings_toggled); - ClassDB::bind_method(D_METHOD("_errors_toggled", "pressed"), &MonoBottomPanel::_errors_toggled); - ClassDB::bind_method(D_METHOD("_build_tabs_item_selected", "idx"), &MonoBottomPanel::_build_tabs_item_selected); - ClassDB::bind_method(D_METHOD("_build_tabs_nothing_selected"), &MonoBottomPanel::_build_tabs_nothing_selected); -} - -MonoBottomPanel::MonoBottomPanel(EditorNode *p_editor) { - - singleton = this; - - editor = p_editor; - - set_v_size_flags(SIZE_EXPAND_FILL); - set_anchors_and_margins_preset(Control::PRESET_WIDE); - - panel_tabs = memnew(TabContainer); - panel_tabs->set_tab_align(TabContainer::ALIGN_LEFT); - panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); - panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); - panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); - panel_tabs->set_custom_minimum_size(Size2(0, 228) * EDSCALE); - panel_tabs->set_v_size_flags(SIZE_EXPAND_FILL); - add_child(panel_tabs); - - { // Builds - panel_builds_tab = memnew(VBoxContainer); - panel_builds_tab->set_name(TTR("Builds")); - panel_builds_tab->set_h_size_flags(SIZE_EXPAND_FILL); - panel_tabs->add_child(panel_builds_tab); - - HBoxContainer *toolbar_hbc = memnew(HBoxContainer); - toolbar_hbc->set_h_size_flags(SIZE_EXPAND_FILL); - panel_builds_tab->add_child(toolbar_hbc); - - Button *build_project_btn = memnew(Button); - build_project_btn->set_text(TTR("Build Project")); - build_project_btn->set_focus_mode(FOCUS_NONE); - build_project_btn->connect("pressed", this, "_build_project_pressed"); - toolbar_hbc->add_child(build_project_btn); - - toolbar_hbc->add_spacer(); - - warnings_btn = memnew(ToolButton); - warnings_btn->set_text(TTR("Warnings")); - warnings_btn->set_toggle_mode(true); - warnings_btn->set_pressed(true); - warnings_btn->set_visible(false); - warnings_btn->set_focus_mode(FOCUS_NONE); - warnings_btn->connect("toggled", this, "_warnings_toggled"); - toolbar_hbc->add_child(warnings_btn); - - errors_btn = memnew(ToolButton); - errors_btn->set_text(TTR("Errors")); - errors_btn->set_toggle_mode(true); - errors_btn->set_pressed(true); - errors_btn->set_visible(false); - errors_btn->set_focus_mode(FOCUS_NONE); - errors_btn->connect("toggled", this, "_errors_toggled"); - toolbar_hbc->add_child(errors_btn); - - toolbar_hbc->add_spacer(); - - view_log_btn = memnew(Button); - view_log_btn->set_text(TTR("View log")); - view_log_btn->set_focus_mode(FOCUS_NONE); - view_log_btn->set_visible(false); - view_log_btn->connect("pressed", this, "_view_log_pressed"); - toolbar_hbc->add_child(view_log_btn); - - HSplitContainer *hsc = memnew(HSplitContainer); - hsc->set_h_size_flags(SIZE_EXPAND_FILL); - hsc->set_v_size_flags(SIZE_EXPAND_FILL); - panel_builds_tab->add_child(hsc); - - build_tabs_list = memnew(ItemList); - build_tabs_list->set_h_size_flags(SIZE_EXPAND_FILL); - build_tabs_list->connect("item_selected", this, "_build_tabs_item_selected"); - build_tabs_list->connect("nothing_selected", this, "_build_tabs_nothing_selected"); - hsc->add_child(build_tabs_list); - - build_tabs = memnew(TabContainer); - build_tabs->set_tab_align(TabContainer::ALIGN_LEFT); - build_tabs->set_h_size_flags(SIZE_EXPAND_FILL); - build_tabs->set_tabs_visible(false); - hsc->add_child(build_tabs); - } -} - -MonoBottomPanel::~MonoBottomPanel() { - - singleton = NULL; -} - -void MonoBuildTab::_load_issues_from_file(const String &p_csv_file) { - - FileAccessRef f = FileAccess::open(p_csv_file, FileAccess::READ); - - if (!f) - return; - - while (!f->eof_reached()) { - Vector<String> csv_line = f->get_csv_line(); - - if (csv_line.size() == 1 && csv_line[0].empty()) - return; - - ERR_CONTINUE(csv_line.size() != 7); - - BuildIssue issue; - issue.warning = csv_line[0] == "warning"; - issue.file = csv_line[1]; - issue.line = csv_line[2].to_int(); - issue.column = csv_line[3].to_int(); - issue.code = csv_line[4]; - issue.message = csv_line[5]; - issue.project_file = csv_line[6]; - - if (issue.warning) - warning_count += 1; - else - error_count += 1; - - issues.push_back(issue); - } -} - -void MonoBuildTab::_update_issues_list() { - - issues_list->clear(); - - Ref<Texture> warning_icon = get_icon("Warning", "EditorIcons"); - Ref<Texture> error_icon = get_icon("Error", "EditorIcons"); - - for (int i = 0; i < issues.size(); i++) { - - const BuildIssue &issue = issues[i]; - - if (!(issue.warning ? warnings_visible : errors_visible)) - continue; - - String tooltip; - tooltip += String("Message: ") + issue.message; - - if (issue.code.length()) { - tooltip += String("\nCode: ") + issue.code; - } - - tooltip += String("\nType: ") + (issue.warning ? "warning" : "error"); - - String text; - - if (issue.file.length()) { - String sline = String::num_int64(issue.line); - String scolumn = String::num_int64(issue.column); - - text += issue.file + "("; - text += sline + ","; - text += scolumn + "): "; - - tooltip += "\nFile: " + issue.file; - tooltip += "\nLine: " + sline; - tooltip += "\nColumn: " + scolumn; - } - - if (issue.project_file.length()) { - tooltip += "\nProject: " + issue.project_file; - } - - text += issue.message; - - int line_break_idx = text.find("\n"); - issues_list->add_item(line_break_idx == -1 ? text : text.substr(0, line_break_idx), - issue.warning ? warning_icon : error_icon); - int index = issues_list->get_item_count() - 1; - issues_list->set_item_tooltip(index, tooltip); - issues_list->set_item_metadata(index, i); - } -} - -Ref<Texture> MonoBuildTab::get_icon_texture() const { - - if (build_exited) { - if (build_result == RESULT_ERROR) { - return get_icon("StatusError", "EditorIcons"); - } else { - return get_icon("StatusSuccess", "EditorIcons"); - } - } else { - return get_icon("Stop", "EditorIcons"); - } -} - -MonoBuildInfo MonoBuildTab::get_build_info() { - - return build_info; -} - -void MonoBuildTab::on_build_start() { - - build_exited = false; - - issues.clear(); - warning_count = 0; - error_count = 0; - _update_issues_list(); - - MonoBottomPanel::get_singleton()->raise_build_tab(this); -} - -void MonoBuildTab::on_build_exit(BuildResult result) { - - build_exited = true; - build_result = result; - - _load_issues_from_file(logs_dir.plus_file(GodotSharpBuilds::get_msbuild_issues_filename())); - _update_issues_list(); - - MonoBottomPanel::get_singleton()->raise_build_tab(this); -} - -void MonoBuildTab::on_build_exec_failed(const String &p_cause) { - - build_exited = true; - build_result = RESULT_ERROR; - - issues_list->clear(); - - BuildIssue issue; - issue.message = p_cause; - issue.warning = false; - - error_count += 1; - issues.push_back(issue); - - _update_issues_list(); - - MonoBottomPanel::get_singleton()->raise_build_tab(this); -} - -void MonoBuildTab::restart_build() { - - ERR_FAIL_COND(!build_exited); - GodotSharpBuilds::get_singleton()->restart_build(this); -} - -void MonoBuildTab::stop_build() { - - ERR_FAIL_COND(build_exited); - GodotSharpBuilds::get_singleton()->stop_build(this); -} - -void MonoBuildTab::_issue_activated(int p_idx) { - - ERR_FAIL_INDEX(p_idx, issues_list->get_item_count()); - - // Get correct issue idx from issue list - int issue_idx = this->issues_list->get_item_metadata(p_idx); - - ERR_FAIL_INDEX(issue_idx, issues.size()); - - const BuildIssue &issue = issues[issue_idx]; - - if (issue.project_file.empty() && issue.file.empty()) - return; - - String project_dir = issue.project_file.length() ? issue.project_file.get_base_dir() : build_info.solution.get_base_dir(); - - String file = project_dir.simplify_path().plus_file(issue.file.simplify_path()); - - if (!FileAccess::exists(file)) - return; - - file = ProjectSettings::get_singleton()->localize_path(file); - - if (file.begins_with("res://")) { - Ref<Script> script = ResourceLoader::load(file, CSharpLanguage::get_singleton()->get_type()); - - if (script.is_valid() && ScriptEditor::get_singleton()->edit(script, issue.line, issue.column)) { - EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); - } - } -} - -void MonoBuildTab::_bind_methods() { - - ClassDB::bind_method("_issue_activated", &MonoBuildTab::_issue_activated); -} - -MonoBuildTab::MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir) : - build_exited(false), - issues_list(memnew(ItemList)), - error_count(0), - warning_count(0), - errors_visible(true), - warnings_visible(true), - logs_dir(p_logs_dir), - build_info(p_build_info) { - issues_list->set_v_size_flags(SIZE_EXPAND_FILL); - issues_list->connect("item_activated", this, "_issue_activated"); - add_child(issues_list); -} diff --git a/modules/mono/editor/mono_bottom_panel.h b/modules/mono/editor/mono_bottom_panel.h deleted file mode 100644 index 9b362e51df..0000000000 --- a/modules/mono/editor/mono_bottom_panel.h +++ /dev/null @@ -1,150 +0,0 @@ -/*************************************************************************/ -/* mono_bottom_panel.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 MONO_BOTTOM_PANEL_H -#define MONO_BOTTOM_PANEL_H - -#include "editor/editor_node.h" -#include "scene/gui/control.h" - -#include "mono_build_info.h" - -class MonoBuildTab; - -class MonoBottomPanel : public VBoxContainer { - - GDCLASS(MonoBottomPanel, VBoxContainer); - - EditorNode *editor; - - TabContainer *panel_tabs; - - VBoxContainer *panel_builds_tab; - - ItemList *build_tabs_list; - TabContainer *build_tabs; - - ToolButton *warnings_btn; - ToolButton *errors_btn; - Button *view_log_btn; - - void _update_build_tabs_list(); - - void _build_tabs_item_selected(int p_idx); - void _build_tabs_nothing_selected(); - - void _warnings_toggled(bool p_pressed); - void _errors_toggled(bool p_pressed); - - void _build_project_pressed(); - void _view_log_pressed(); - - static MonoBottomPanel *singleton; - -protected: - void _notification(int p_what); - - static void _bind_methods(); - -public: - _FORCE_INLINE_ static MonoBottomPanel *get_singleton() { return singleton; } - - void add_build_tab(MonoBuildTab *p_build_tab); - void raise_build_tab(MonoBuildTab *p_build_tab); - - void show_build_tab(); - - MonoBottomPanel(EditorNode *p_editor = NULL); - ~MonoBottomPanel(); -}; - -class MonoBuildTab : public VBoxContainer { - - GDCLASS(MonoBuildTab, VBoxContainer); - -public: - enum BuildResult { - RESULT_ERROR, - RESULT_SUCCESS - }; - - struct BuildIssue { - bool warning; - String file; - int line; - int column; - String code; - String message; - String project_file; - }; - -private: - friend class MonoBottomPanel; - - bool build_exited; - BuildResult build_result; - - Vector<BuildIssue> issues; - ItemList *issues_list; - - int error_count; - int warning_count; - - bool errors_visible; - bool warnings_visible; - - String logs_dir; - - MonoBuildInfo build_info; - - void _load_issues_from_file(const String &p_csv_file); - void _update_issues_list(); - - void _issue_activated(int p_idx); - -protected: - static void _bind_methods(); - -public: - Ref<Texture> get_icon_texture() const; - - MonoBuildInfo get_build_info(); - - void on_build_start(); - void on_build_exit(BuildResult result); - void on_build_exec_failed(const String &p_cause); - - void restart_build(); - void stop_build(); - - MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir); -}; - -#endif // MONO_BOTTOM_PANEL_H diff --git a/modules/mono/editor/mono_build_info.cpp b/modules/mono/editor/mono_build_info.cpp deleted file mode 100644 index b386c06435..0000000000 --- a/modules/mono/editor/mono_build_info.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/*************************************************************************/ -/* mono_build_info.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 "mono_build_info.h" - -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_utils.h" - -uint32_t MonoBuildInfo::Hasher::hash(const MonoBuildInfo &p_key) { - - uint32_t hash = 0; - - GDMonoUtils::hash_combine(hash, p_key.solution.hash()); - GDMonoUtils::hash_combine(hash, p_key.configuration.hash()); - - return hash; -} - -bool MonoBuildInfo::operator==(const MonoBuildInfo &p_b) const { - - return p_b.solution == solution && p_b.configuration == configuration; -} - -String MonoBuildInfo::get_log_dirpath() { - - return GodotSharpDirs::get_build_logs_dir().plus_file(solution.md5_text() + "_" + configuration); -} - -MonoBuildInfo::MonoBuildInfo() {} - -MonoBuildInfo::MonoBuildInfo(const String &p_solution, const String &p_config) { - - solution = p_solution; - configuration = p_config; -} diff --git a/modules/mono/glue/Managed/.gitignore b/modules/mono/glue/Managed/.gitignore new file mode 100644 index 0000000000..146421cac8 --- /dev/null +++ b/modules/mono/glue/Managed/.gitignore @@ -0,0 +1,2 @@ +# Generated Godot API solution folder +Generated diff --git a/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs b/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs index 2398e10135..1bf6d5199a 100644 --- a/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs +++ b/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs @@ -2,27 +2,27 @@ using System; namespace Godot { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class RemoteAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class SyncAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class MasterAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class PuppetAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class SlaveAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class RemoteSyncAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class MasterSyncAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class PuppetSyncAttribute : Attribute {} } diff --git a/modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs b/modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs new file mode 100644 index 0000000000..c3fa2f3e82 --- /dev/null +++ b/modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs @@ -0,0 +1,8 @@ +namespace Godot +{ + public interface ISerializationListener + { + void OnBeforeSerialize(); + void OnAfterDeserialize(); + } +} diff --git a/modules/mono/glue/Managed/Files/Mathf.cs b/modules/mono/glue/Managed/Files/Mathf.cs index 2d8c63fe7f..6c1a51fcf9 100644 --- a/modules/mono/glue/Managed/Files/Mathf.cs +++ b/modules/mono/glue/Managed/Files/Mathf.cs @@ -185,6 +185,12 @@ namespace Godot return from + (to - from) * 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; + } + public static real_t Log(real_t s) { return (real_t)Math.Log(s); diff --git a/modules/mono/glue/Managed/Files/StringExtensions.cs b/modules/mono/glue/Managed/Files/StringExtensions.cs index c194facd0b..6045c83e95 100644 --- a/modules/mono/glue/Managed/Files/StringExtensions.cs +++ b/modules/mono/glue/Managed/Files/StringExtensions.cs @@ -98,6 +98,66 @@ namespace Godot } // <summary> + // Return the amount of substrings in string. + // </summary> + public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0) + { + if (what.Length == 0) + { + return 0; + } + + int len = instance.Length; + int slen = what.Length; + + if (len < slen) + { + return 0; + } + + string str; + + if (from >= 0 && to >= 0) + { + if (to == 0) + { + to = len; + } + else if (from >= to) + { + return 0; + } + if (from == 0 && to == len) + { + str = instance; + } + else + { + str = instance.Substring(from, to - from); + } + } + else + { + return 0; + } + + int c = 0; + int idx; + + do + { + idx = str.IndexOf(what, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + if (idx != -1) + { + str = str.Substring(idx + slen); + ++c; + } + } while (idx != -1); + + return c; + } + + // <summary> // Return a copy of the string with special characters escaped using the C language standard. // </summary> public static string CEscape(this string instance) @@ -299,14 +359,14 @@ namespace Godot if (basepos != -1) { var end = basepos + 3; - rs = instance.Substring(end, instance.Length); + rs = instance.Substring(end); @base = instance.Substring(0, end); } else { if (instance.BeginsWith("/")) { - rs = instance.Substring(1, instance.Length); + rs = instance.Substring(1); @base = "/"; } else @@ -333,7 +393,7 @@ namespace Godot if (sep == -1) return instance; - return instance.Substring(sep + 1, instance.Length); + return instance.Substring(sep + 1); } // <summary> @@ -911,7 +971,8 @@ namespace Godot // </summary> public static string Substr(this string instance, int from, int len) { - return instance.Substring(from, len); + int max = instance.Length - from; + return instance.Substring(from, len > max ? max : len); } // <summary> diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp index 75b2dfce9a..6d85f55b97 100644 --- a/modules/mono/glue/base_object_glue.cpp +++ b/modules/mono/glue/base_object_glue.cpp @@ -219,7 +219,18 @@ MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString * } MonoString *godot_icall_Object_ToString(Object *p_ptr) { - return GDMonoMarshal::mono_string_from_godot(Variant(p_ptr).operator String()); +#ifdef DEBUG_ENABLED + // Cannot happen in C#; would get an ObjectDisposedException instead. + CRASH_COND(p_ptr == NULL); + + if (ScriptDebugger::get_singleton() && !Object::cast_to<Reference>(p_ptr)) { // Only if debugging! + // Cannot happen either in C#; the handle is nullified when the object is destroyed + CRASH_COND(!ObjectDB::instance_validate(p_ptr)); + } +#endif + + String result = "[" + p_ptr->get_class() + ":" + itos(p_ptr->get_instance_id()) + "]"; + return GDMonoMarshal::mono_string_from_godot(result); } void godot_register_object_icalls() { diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp index 47239f1260..e67c8b9ad9 100644 --- a/modules/mono/glue/collections_glue.cpp +++ b/modules/mono/glue/collections_glue.cpp @@ -46,7 +46,7 @@ void godot_icall_Array_Dtor(Array *ptr) { } MonoObject *godot_icall_Array_At(Array *ptr, int index) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return NULL; } @@ -54,7 +54,7 @@ MonoObject *godot_icall_Array_At(Array *ptr, int index) { } MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_encoding, GDMonoClass *type_class) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return NULL; } @@ -62,7 +62,7 @@ MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_en } void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return; } @@ -124,7 +124,7 @@ MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item) { } void godot_icall_Array_RemoveAt(Array *ptr, int index) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return; } diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp index a5c72160d7..e9373fb486 100644 --- a/modules/mono/glue/string_glue.cpp +++ b/modules/mono/glue/string_glue.cpp @@ -68,12 +68,12 @@ MonoString *godot_icall_String_sha256_text(MonoString *p_str) { } void godot_register_string_icalls() { - mono_add_internal_call("Godot.String::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer); - mono_add_internal_call("Godot.String::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text); - mono_add_internal_call("Godot.String::godot_icall_String_rfind", (void *)godot_icall_String_rfind); - mono_add_internal_call("Godot.String::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn); - mono_add_internal_call("Godot.String::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer); - mono_add_internal_call("Godot.String::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", (void *)godot_icall_String_rfind); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h index 0d3b96d789..4ad4088514 100644 --- a/modules/mono/godotsharp_defs.h +++ b/modules/mono/godotsharp_defs.h @@ -39,7 +39,8 @@ #define API_SOLUTION_NAME "GodotSharp" #define CORE_API_ASSEMBLY_NAME "GodotSharp" #define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor" -#define EDITOR_TOOLS_ASSEMBLY_NAME "GodotSharpTools" +#define TOOLS_ASSEMBLY_NAME "GodotTools" +#define TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME "GodotTools.ProjectEditor" #define BINDINGS_CLASS_NATIVECALLS "NativeCalls" #define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls" diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 09a1fc6fbc..4b2525c692 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -39,6 +39,10 @@ #include "editor/editor_settings.h" #endif +#ifdef __ANDROID__ +#include "utils/android_utils.h" +#endif + namespace GodotSharpDirs { String _get_expected_build_config() { @@ -55,6 +59,20 @@ String _get_expected_build_config() { #endif } +String _get_expected_api_build_config() { +#ifdef TOOLS_ENABLED + return "Debug"; +#else + +#ifdef DEBUG_ENABLED + return "Debug"; +#else + return "Release"; +#endif + +#endif +} + String _get_mono_user_dir() { #ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { @@ -84,6 +102,7 @@ class _GodotSharpDirs { public: String res_data_dir; String res_metadata_dir; + String res_assemblies_base_dir; String res_assemblies_dir; String res_config_dir; String res_temp_dir; @@ -114,7 +133,8 @@ private: _GodotSharpDirs() { res_data_dir = "res://.mono"; res_metadata_dir = res_data_dir.plus_file("metadata"); - res_assemblies_dir = res_data_dir.plus_file("assemblies"); + res_assemblies_base_dir = res_data_dir.plus_file("assemblies"); + res_assemblies_dir = res_assemblies_base_dir.plus_file(_get_expected_api_build_config()); res_config_dir = res_data_dir.plus_file("etc").plus_file("mono"); // TODO use paths from csproj @@ -129,15 +149,16 @@ private: mono_solutions_dir = mono_user_dir.plus_file("solutions"); build_logs_dir = mono_user_dir.plus_file("build_logs"); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + if (appname_safe.empty()) { + appname_safe = "UnnamedProject"; } String base_path = ProjectSettings::get_singleton()->globalize_path("res://"); - sln_filepath = base_path.plus_file(name + ".sln"); - csproj_filepath = base_path.plus_file(name + ".csproj"); + sln_filepath = base_path.plus_file(appname_safe + ".sln"); + csproj_filepath = base_path.plus_file(appname_safe + ".csproj"); #endif String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir(); @@ -150,7 +171,12 @@ 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(); +#else data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); +#endif #ifdef WINDOWS_ENABLED data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); @@ -173,15 +199,21 @@ private: #else - String appname = OS::get_singleton()->get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/name")); - String data_dir_root = exe_dir.plus_file("data_" + appname); + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + String data_dir_root = exe_dir.plus_file("data_" + appname_safe); if (!DirAccess::exists(data_dir_root)) { data_dir_root = exe_dir.plus_file("data_Godot"); } 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(); +#else data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); +#endif #ifdef WINDOWS_ENABLED data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); @@ -215,6 +247,10 @@ String get_res_metadata_dir() { return _GodotSharpDirs::get_singleton().res_metadata_dir; } +String get_res_assemblies_base_dir() { + return _GodotSharpDirs::get_singleton().res_assemblies_base_dir; +} + String get_res_assemblies_dir() { return _GodotSharpDirs::get_singleton().res_assemblies_dir; } diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h index 556df959e2..ff51888d1c 100644 --- a/modules/mono/godotsharp_dirs.h +++ b/modules/mono/godotsharp_dirs.h @@ -37,6 +37,7 @@ namespace GodotSharpDirs { String get_res_data_dir(); String get_res_metadata_dir(); +String get_res_assemblies_base_dir(); String get_res_assemblies_dir(); String get_res_config_dir(); String get_res_temp_dir(); diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 7699e0d0cd..8b9813f472 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -52,13 +52,12 @@ #include "gd_mono_utils.h" #ifdef TOOLS_ENABLED -#include "../editor/godotsharp_editor.h" #include "main/main.h" #endif -#define OUT_OF_SYNC_ERR_MESSAGE(m_assembly_name) "The assembly '" m_assembly_name "' is out of sync. " \ - "This error is expected if you just upgraded to a newer Godot version. " \ - "Building the project will update the assembly to the correct version." +#ifdef ANDROID_ENABLED +#include "android_mono_config.gen.h" +#endif GDMono *GDMono::singleton = NULL; @@ -95,7 +94,7 @@ void gdmono_profiler_init() { #ifdef DEBUG_ENABLED -static bool _wait_for_debugger_msecs(uint32_t p_msecs) { +bool _wait_for_debugger_msecs(uint32_t p_msecs) { do { if (mono_is_debugger_attached()) @@ -125,16 +124,17 @@ void gdmono_debug_init() { bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false); int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000); + CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8(); + #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint() || ProjectSettings::get_singleton()->get_resource_path().empty() || Main::is_project_manager()) { - return; + if (da_args.size() == 0) + return; } #endif - CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8(); - if (da_args.length() == 0) { da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) + ",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n")) @@ -203,6 +203,10 @@ void GDMono::initialize() { print_verbose("Mono: Initializing module..."); + char *runtime_build_info = mono_get_runtime_build_info(); + print_verbose("Mono JIT compiler version " + String(runtime_build_info)); + mono_free(runtime_build_info); + #ifdef DEBUG_METHODS_ENABLED _initialize_and_check_api_hashes(); #endif @@ -233,9 +237,9 @@ void GDMono::initialize() { locations.push_back("/usr/local/var/homebrew/linked/mono/"); for (int i = 0; i < locations.size(); i++) { - String hint_assembly_rootdir = path_join(locations[i], "lib"); - String hint_mscorlib_path = path_join(hint_assembly_rootdir, "mono", "4.5", "mscorlib.dll"); - String hint_config_dir = path_join(locations[i], "etc"); + String hint_assembly_rootdir = path::join(locations[i], "lib"); + String hint_mscorlib_path = path::join(hint_assembly_rootdir, "mono", "4.5", "mscorlib.dll"); + String hint_config_dir = path::join(locations[i], "etc"); if (FileAccess::exists(hint_mscorlib_path) && DirAccess::exists(hint_config_dir)) { assembly_rootdir = hint_assembly_rootdir; @@ -279,6 +283,18 @@ void GDMono::initialize() { add_mono_shared_libs_dir_to_path(); + { + PropertyInfo exc_policy_prop = PropertyInfo(Variant::INT, "mono/unhandled_exception_policy", PROPERTY_HINT_ENUM, + vformat("Terminate Application:%s,Log Error:%s", (int)POLICY_TERMINATE_APP, (int)POLICY_LOG_ERROR)); + unhandled_exception_policy = (UnhandledExceptionPolicy)(int)GLOBAL_DEF(exc_policy_prop.name, (int)POLICY_TERMINATE_APP); + ProjectSettings::get_singleton()->set_custom_property_info(exc_policy_prop.name, exc_policy_prop); + + if (Engine::get_singleton()->is_editor_hint()) { + // Unhandled exceptions should not terminate the editor + unhandled_exception_policy = POLICY_LOG_ERROR; + } + } + GDMonoAssembly::initialize(); gdmono_profiler_init(); @@ -287,31 +303,18 @@ void GDMono::initialize() { gdmono_debug_init(); #endif +#ifdef ANDROID_ENABLED + mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data()); +#else mono_config_parse(NULL); +#endif mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL); #ifndef TOOLS_ENABLED - if (!DirAccess::exists("res://.mono")) { - // 'res://.mono/' is missing so there is nothing to load. We don't need to initialize mono, but - // we still do so unless mscorlib is missing (which is the case for projects that don't use C#). - - String mscorlib_fname("mscorlib.dll"); - - Vector<String> search_dirs; - GDMonoAssembly::fill_search_dirs(search_dirs); - - bool found = false; - for (int i = 0; i < search_dirs.size(); i++) { - if (FileAccess::exists(search_dirs[i].plus_file(mscorlib_fname))) { - found = true; - break; - } - } - - if (!found) - return; // mscorlib is missing, do not initialize mono - } + // Export templates only load the Mono runtime if the project uses it + if (!DirAccess::exists("res://.mono")) + return; #endif root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); @@ -331,18 +334,6 @@ void GDMono::initialize() { ERR_EXPLAIN("Mono: Failed to load mscorlib assembly"); ERR_FAIL_COND(!_load_corlib_assembly()); -#ifdef TOOLS_ENABLED - // The tools domain must be loaded here, before the scripts domain. - // Otherwise domain unload on the scripts domain will hang indefinitely. - - ERR_EXPLAIN("Mono: Failed to load tools domain"); - ERR_FAIL_COND(_load_tools_domain() != OK); - - // TODO move to editor init callback, and do it lazily when required before editor init (e.g.: bindings generation) - ERR_EXPLAIN("Mono: Failed to load Editor Tools assembly"); - ERR_FAIL_COND(!_load_editor_tools_assembly()); -#endif - ERR_EXPLAIN("Mono: Failed to load scripts domain"); ERR_FAIL_COND(_load_scripts_domain() != OK); @@ -354,56 +345,48 @@ void GDMono::initialize() { _register_internal_calls(); - // The following assemblies are not required at initialization -#ifdef MONO_GLUE_ENABLED - if (_load_api_assemblies()) { - // Everything is fine with the api assemblies, load the project assembly - _load_project_assembly(); - } else { - if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)) -#ifdef TOOLS_ENABLED - || (editor_api_assembly && editor_api_assembly_out_of_sync) + print_verbose("Mono: INITIALIZED"); +} + +void GDMono::initialize_load_assemblies() { + +#ifndef MONO_GLUE_ENABLED + ERR_EXPLAIN("Mono: This binary was built with `mono_glue=no`; cannot load assemblies"); + CRASH_NOW(); #endif - ) { -#ifdef TOOLS_ENABLED - // The assembly was successfully loaded, but the full api could not be cached. - // This is most likely an outdated assembly loaded because of an invalid version in the - // metadata, so we invalidate the version in the metadata and unload the script domain. - - if (core_api_assembly_out_of_sync) { - ERR_PRINT(OUT_OF_SYNC_ERR_MESSAGE(CORE_API_ASSEMBLY_NAME)); - metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); - } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) { - ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed"); - metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); - } - if (editor_api_assembly_out_of_sync) { - ERR_PRINT(OUT_OF_SYNC_ERR_MESSAGE(EDITOR_API_ASSEMBLY_NAME)); - metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true); - } + // Load assemblies. The API and tools assemblies are required, + // the application is aborted if these assemblies cannot be loaded. - print_line("Mono: Proceeding to unload scripts domain because of invalid API assemblies."); + _load_api_assemblies(); - Error err = _unload_scripts_domain(); - if (err != OK) { - WARN_PRINT("Mono: Failed to unload scripts domain"); - } -#else - ERR_PRINT("The loaded API assembly is invalid"); - CRASH_NOW(); -#endif // TOOLS_ENABLED - } +#if defined(TOOLS_ENABLED) + if (!_load_tools_assemblies()) { + ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); + CRASH_NOW(); } -#else - print_verbose("Mono: Glue disabled, ignoring script assemblies."); -#endif // MONO_GLUE_ENABLED +#endif - print_verbose("Mono: INITIALIZED"); + // Load the project's main assembly. This doesn't necessarily need to succeed. + // The game may not be using .NET at all, or if the project does use .NET and + // we're running in the editor, it may just happen to be it wasn't built yet. + if (!_load_project_assembly()) { + if (OS::get_singleton()->is_stdout_verbose()) + print_error("Mono: Failed to load project assembly"); + } +} + +bool GDMono::_are_api_assemblies_out_of_sync() { + bool out_of_sync = core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated); +#ifdef TOOLS_ENABLED + if (!out_of_sync) + out_of_sync = editor_api_assembly && editor_api_assembly_out_of_sync; +#endif + return out_of_sync; } -#ifdef MONO_GLUE_ENABLED namespace GodotSharpBindings { +#ifdef MONO_GLUE_ENABLED uint64_t get_core_api_hash(); #ifdef TOOLS_ENABLED @@ -412,17 +395,33 @@ uint64_t get_editor_api_hash(); uint32_t get_bindings_version(); void register_generated_icalls(); -} // namespace GodotSharpBindings -#endif -void GDMono::_register_internal_calls() { -#ifdef MONO_GLUE_ENABLED - GodotSharpBindings::register_generated_icalls(); -#endif +#else +uint64_t get_core_api_hash() { + CRASH_NOW(); + GD_UNREACHABLE(); +} #ifdef TOOLS_ENABLED - GodotSharpEditor::register_internal_calls(); +uint64_t get_editor_api_hash() { + CRASH_NOW(); + GD_UNREACHABLE(); +} #endif +uint32_t get_bindings_version() { + CRASH_NOW(); + GD_UNREACHABLE(); +} + +void register_generated_icalls() { + /* Fine, just do nothing */ +} + +#endif // MONO_GLUE_ENABLED +} // namespace GodotSharpBindings + +void GDMono::_register_internal_calls() { + GodotSharpBindings::register_generated_icalls(); } void GDMono::_initialize_and_check_api_hashes() { @@ -561,29 +560,111 @@ bool GDMono::_load_corlib_assembly() { return success; } -bool GDMono::_load_core_api_assembly() { +#ifdef TOOLS_ENABLED +bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) { - if (core_api_assembly) - return true; + bool &api_assembly_out_of_sync = (p_api_type == APIAssembly::API_CORE) ? + GDMono::get_singleton()->core_api_assembly_out_of_sync : + GDMono::get_singleton()->editor_api_assembly_out_of_sync; -#ifdef TOOLS_ENABLED - if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) { - print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated"); - return false; + String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); + String dst_dir = GodotSharpDirs::get_res_assemblies_dir(); + + String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; + + // Create destination directory if needed + if (!DirAccess::exists(dst_dir)) { + DirAccess *da = DirAccess::create_for_path(dst_dir); + Error err = da->make_dir_recursive(dst_dir); + memdelete(da); + + if (err != OK) { + ERR_PRINTS("Failed to create destination directory for the API assemblies. Error: " + itos(err)); + return false; + } } + + String assembly_file = assembly_name + ".dll"; + String assembly_src = src_dir.plus_file(assembly_file); + String assembly_dst = dst_dir.plus_file(assembly_file); + + if (!FileAccess::exists(assembly_dst) || api_assembly_out_of_sync) { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + String xml_file = assembly_name + ".xml"; + if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK) + WARN_PRINTS("Failed to copy " + xml_file); + + String pdb_file = assembly_name + ".pdb"; + if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK) + WARN_PRINTS("Failed to copy " + pdb_file); + + Error err = da->copy(assembly_src, assembly_dst); + + if (err != OK) { + ERR_PRINTS("Failed to copy " + assembly_file); + return false; + } + + api_assembly_out_of_sync = false; + } + + return true; +} + +String GDMono::update_api_assemblies_from_prebuilt() { + +#define FAIL_REASON(m_out_of_sync, m_prebuilt_exists) \ + ( \ + (m_out_of_sync ? \ + String("The assembly is invalidated") : \ + String("The assembly was not found")) + \ + (m_prebuilt_exists ? \ + String(" and the prebuilt assemblies are missing") : \ + String(" and we failed to copy the prebuilt assemblies"))) + + bool api_assembly_out_of_sync = core_api_assembly_out_of_sync || editor_api_assembly_out_of_sync; + + String core_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + String editor_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + + if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) + return String(); // No update needed + + print_verbose("Updating API assemblies"); + + String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); + String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + + if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) + return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false); + + // Copy the prebuilt Api + if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE) || !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR)) + return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true); + + return String(); // Updated successfully + +#undef FAIL_REASON +} #endif - String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll"); +bool GDMono::_load_core_api_assembly() { - if (!FileAccess::exists(assembly_path)) - return false; + if (core_api_assembly) + return true; - bool success = load_assembly_from(CORE_API_ASSEMBLY_NAME, - assembly_path, - &core_api_assembly); +#ifdef TOOLS_ENABLED + // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date + String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + bool success = FileAccess::exists(assembly_path) && + load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly); +#else + bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly); +#endif if (success) { -#ifdef MONO_GLUE_ENABLED APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(core_api_assembly, APIAssembly::API_CORE); core_api_assembly_out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash || GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || @@ -593,9 +674,8 @@ bool GDMono::_load_core_api_assembly() { _install_trace_listener(); } -#else - GDMonoUtils::update_godot_api_cache(); -#endif + } else { + core_api_assembly_out_of_sync = false; } return success; @@ -607,68 +687,25 @@ bool GDMono::_load_editor_api_assembly() { if (editor_api_assembly) return true; - if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) { - print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated"); - return false; - } - + // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - - if (!FileAccess::exists(assembly_path)) - return false; - - bool success = load_assembly_from(EDITOR_API_ASSEMBLY_NAME, - assembly_path, - &editor_api_assembly); + bool success = FileAccess::exists(assembly_path) && + load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly); if (success) { -#ifdef MONO_GLUE_ENABLED APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(editor_api_assembly, APIAssembly::API_EDITOR); editor_api_assembly_out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash || GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || CS_GLUE_VERSION != api_assembly_ver.cs_glue_version; -#endif - } - - return success; -} -#endif - -#ifdef TOOLS_ENABLED -bool GDMono::_load_editor_tools_assembly() { - - if (editor_tools_assembly) - return true; - - _GDMONO_SCOPE_DOMAIN_(tools_domain) - - return load_assembly(EDITOR_TOOLS_ASSEMBLY_NAME, &editor_tools_assembly); -} -#endif - -bool GDMono::_load_project_assembly() { - - if (project_assembly) - return true; - - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; - } - - bool success = load_assembly(name, &project_assembly); - - if (success) { - mono_assembly_set_main(project_assembly->get_assembly()); } else { - if (OS::get_singleton()->is_stdout_verbose()) - print_error("Mono: Failed to load project assembly"); + editor_api_assembly_out_of_sync = false; } return success; } +#endif -bool GDMono::_load_api_assemblies() { +bool GDMono::_try_load_api_assemblies() { if (!_load_core_api_assembly()) { if (OS::get_singleton()->is_stdout_verbose()) @@ -676,9 +713,6 @@ bool GDMono::_load_api_assemblies() { return false; } - if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated) - return false; - #ifdef TOOLS_ENABLED if (!_load_editor_api_assembly()) { if (OS::get_singleton()->is_stdout_verbose()) @@ -690,89 +724,118 @@ bool GDMono::_load_api_assemblies() { return false; #endif + // Check if the core API assembly is out of sync only after trying to load the + // editor API assembly. Otherwise, if both assemblies are out of sync, we would + // only update the former as we won't know the latter also needs to be updated. + if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated) + return false; + return true; } -void GDMono::_install_trace_listener() { - -#ifdef DEBUG_ENABLED - // Install the trace listener now before the project assembly is loaded - typedef void (*DebuggingUtils_InstallTraceListener)(MonoObject **); - MonoException *exc = NULL; - GDMonoClass *debug_utils = core_api_assembly->get_class(BINDINGS_NAMESPACE, "DebuggingUtils"); - DebuggingUtils_InstallTraceListener install_func = - (DebuggingUtils_InstallTraceListener)debug_utils->get_method_thunk("InstallTraceListener"); - install_func((MonoObject **)&exc); - if (exc) { - ERR_PRINT("Failed to install System.Diagnostics.Trace listener"); - GDMonoUtils::debug_print_unhandled_exception(exc); - } -#endif -} +void GDMono::_load_api_assemblies() { + if (!_try_load_api_assemblies()) { #ifdef TOOLS_ENABLED -String GDMono::_get_api_assembly_metadata_path() { + // The API assemblies are out of sync. Fine, try one more time, but this time + // update them from the prebuilt assemblies directory before trying to load them. - return GodotSharpDirs::get_res_metadata_dir().plus_file("api_assemblies.cfg"); -} - -void GDMono::metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated) { - - String section = APIAssembly::to_string(p_api_type); - String path = _get_api_assembly_metadata_path(); + // 1. Unload the scripts domain + if (_unload_scripts_domain() != OK) { + ERR_EXPLAIN("Mono: Failed to unload scripts domain"); + CRASH_NOW(); + } - Ref<ConfigFile> metadata; - metadata.instance(); - metadata->load(path); + // 2. Update the API assemblies + String update_error = update_api_assemblies_from_prebuilt(); + if (!update_error.empty()) { + ERR_EXPLAIN(update_error); + CRASH_NOW(); + } - metadata->set_value(section, "invalidated", p_invalidated); + // 3. Load the scripts domain again + if (_load_scripts_domain() != OK) { + ERR_EXPLAIN("Mono: Failed to load scripts domain"); + CRASH_NOW(); + } - String assembly_path = GodotSharpDirs::get_res_assemblies_dir() - .plus_file(p_api_type == APIAssembly::API_CORE ? - CORE_API_ASSEMBLY_NAME ".dll" : - EDITOR_API_ASSEMBLY_NAME ".dll"); + // 4. Try loading the updated assemblies + if (!_try_load_api_assemblies()) { + // welp... too bad + + 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) { + ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed"); + } + + if (editor_api_assembly_out_of_sync) { + ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync"); + } + + CRASH_NOW(); + } else { + ERR_EXPLAIN("Failed to load one of the API assemblies"); + CRASH_NOW(); + } + } +#else + ERR_EXPLAIN("Failed to load one of the API assemblies"); + CRASH_NOW(); +#endif + } +} - ERR_FAIL_COND(!FileAccess::exists(assembly_path)); +#ifdef TOOLS_ENABLED +bool GDMono::_load_tools_assemblies() { - uint64_t modified_time = FileAccess::get_modified_time(assembly_path); + if (tools_assembly && tools_project_editor_assembly) + return true; - metadata->set_value(section, "invalidated_asm_modified_time", String::num_uint64(modified_time)); + bool success = load_assembly(TOOLS_ASSEMBLY_NAME, &tools_assembly) && + load_assembly(TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME, &tools_project_editor_assembly); - String dir = path.get_base_dir(); - if (!DirAccess::exists(dir)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND(!da); - Error err = da->make_dir_recursive(ProjectSettings::get_singleton()->globalize_path(dir)); - ERR_FAIL_COND(err != OK); - } - - Error save_err = metadata->save(path); - ERR_FAIL_COND(save_err != OK); + return success; } +#endif -bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type) { +bool GDMono::_load_project_assembly() { - String section = APIAssembly::to_string(p_api_type); + if (project_assembly) + return true; - Ref<ConfigFile> metadata; - metadata.instance(); - metadata->load(_get_api_assembly_metadata_path()); + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + if (appname_safe.empty()) { + appname_safe = "UnnamedProject"; + } - String assembly_path = GodotSharpDirs::get_res_assemblies_dir() - .plus_file(p_api_type == APIAssembly::API_CORE ? - CORE_API_ASSEMBLY_NAME ".dll" : - EDITOR_API_ASSEMBLY_NAME ".dll"); + bool success = load_assembly(appname_safe, &project_assembly); - if (!FileAccess::exists(assembly_path)) - return false; + if (success) { + mono_assembly_set_main(project_assembly->get_assembly()); + } - uint64_t modified_time = FileAccess::get_modified_time(assembly_path); + return success; +} - uint64_t stored_modified_time = metadata->get_value(section, "invalidated_asm_modified_time", 0); +void GDMono::_install_trace_listener() { - return metadata->get_value(section, "invalidated", false) && modified_time <= stored_modified_time; -} +#ifdef DEBUG_ENABLED + // Install the trace listener now before the project assembly is loaded + typedef void (*DebuggingUtils_InstallTraceListener)(MonoObject **); + MonoException *exc = NULL; + GDMonoClass *debug_utils = core_api_assembly->get_class(BINDINGS_NAMESPACE, "DebuggingUtils"); + DebuggingUtils_InstallTraceListener install_func = + (DebuggingUtils_InstallTraceListener)debug_utils->get_method_thunk("InstallTraceListener"); + install_func((MonoObject **)&exc); + if (exc) { + ERR_PRINT("Failed to install System.Diagnostics.Trace listener"); + GDMonoUtils::debug_print_unhandled_exception(exc); + } #endif +} Error GDMono::_load_scripts_domain() { @@ -817,11 +880,8 @@ Error GDMono::_unload_scripts_domain() { project_assembly = NULL; #ifdef TOOLS_ENABLED editor_api_assembly = NULL; -#endif - - core_api_assembly_out_of_sync = false; -#ifdef TOOLS_ENABLED - editor_api_assembly_out_of_sync = false; + tools_assembly = NULL; + tools_project_editor_assembly = NULL; #endif MonoDomain *domain = scripts_domain; @@ -839,22 +899,6 @@ Error GDMono::_unload_scripts_domain() { return OK; } -#ifdef TOOLS_ENABLED -Error GDMono::_load_tools_domain() { - - ERR_FAIL_COND_V(tools_domain != NULL, ERR_BUG); - - print_verbose("Mono: Loading tools domain..."); - - tools_domain = GDMonoUtils::create_domain("GodotEngine.ToolsDomain"); - - ERR_EXPLAIN("Mono: Could not create tools app domain"); - ERR_FAIL_NULL_V(tools_domain, ERR_CANT_CREATE); - - return OK; -} -#endif - #ifdef GD_MONO_HOT_RELOAD Error GDMono::reload_scripts_domain() { @@ -876,52 +920,25 @@ Error GDMono::reload_scripts_domain() { return err; } -#ifdef MONO_GLUE_ENABLED - if (!_load_api_assemblies()) { - if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)) -#ifdef TOOLS_ENABLED - || (editor_api_assembly && editor_api_assembly_out_of_sync) -#endif - ) { -#ifdef TOOLS_ENABLED - // The assembly was successfully loaded, but the full api could not be cached. - // This is most likely an outdated assembly loaded because of an invalid version in the - // metadata, so we invalidate the version in the metadata and unload the script domain. - - if (core_api_assembly_out_of_sync) { - ERR_PRINT(OUT_OF_SYNC_ERR_MESSAGE(CORE_API_ASSEMBLY_NAME)); - metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); - } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) { - ERR_PRINT("The loaded Core API assembly is in sync, but the cache update failed"); - metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); - } - - if (editor_api_assembly_out_of_sync) { - ERR_PRINT(OUT_OF_SYNC_ERR_MESSAGE(EDITOR_API_ASSEMBLY_NAME)); - metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true); - } + // Load assemblies. The API and tools assemblies are required, + // the application is aborted if these assemblies cannot be loaded. - err = _unload_scripts_domain(); - if (err != OK) { - WARN_PRINT("Mono: Failed to unload scripts domain"); - } + _load_api_assemblies(); - return ERR_CANT_RESOLVE; -#else - ERR_PRINT("The loaded API assembly is invalid"); - CRASH_NOW(); -#endif - } else { - return ERR_CANT_OPEN; - } +#if defined(TOOLS_ENABLED) + if (!_load_tools_assemblies()) { + ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); + CRASH_NOW(); } +#endif + // Load the project's main assembly. Here, during hot-reloading, we do + // consider failing to load the project's main assembly to be an error. + // However, unlike the API and tools assemblies, the application can continue working. if (!_load_project_assembly()) { + print_error("Mono: Failed to load project assembly"); return ERR_CANT_OPEN; } -#else - print_verbose("Mono: Glue disabled, ignoring script assemblies."); -#endif // MONO_GLUE_ENABLED return OK; } @@ -930,7 +947,7 @@ Error GDMono::reload_scripts_domain() { Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { CRASH_COND(p_domain == NULL); - CRASH_COND(p_domain == SCRIPTS_DOMAIN); // Should use _unload_scripts_domain() instead + CRASH_COND(p_domain == GDMono::get_singleton()->get_scripts_domain()); // Should use _unload_scripts_domain() instead String domain_name = mono_domain_get_friendly_name(p_domain); @@ -947,18 +964,12 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { _domain_assemblies_cleanup(mono_domain_get_id(p_domain)); -#ifdef TOOLS_ENABLED - if (p_domain == tools_domain) { - editor_tools_assembly = NULL; - } -#endif - MonoException *exc = NULL; mono_domain_try_unload(p_domain, (MonoObject **)&exc); if (exc) { ERR_PRINTS("Exception thrown when unloading domain `" + domain_name + "`"); - GDMonoUtils::debug_unhandled_exception(exc); + GDMonoUtils::debug_print_unhandled_exception(exc); return FAILED; } @@ -989,6 +1000,22 @@ GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) { return NULL; } +GDMonoClass *GDMono::get_class(const StringName &p_namespace, const StringName &p_name) { + + uint32_t domain_id = mono_domain_get_id(mono_domain_get()); + HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id]; + + const String *k = NULL; + while ((k = domain_assemblies.next(k))) { + GDMonoAssembly *assembly = domain_assemblies.get(*k); + GDMonoClass *klass = assembly->get_class(p_namespace, p_name); + if (klass) + return klass; + } + + return NULL; +} + void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) { HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[p_domain_id]; @@ -1029,9 +1056,6 @@ GDMono::GDMono() { root_domain = NULL; scripts_domain = NULL; -#ifdef TOOLS_ENABLED - tools_domain = NULL; -#endif core_api_assembly_out_of_sync = false; #ifdef TOOLS_ENABLED @@ -1043,28 +1067,21 @@ GDMono::GDMono() { project_assembly = NULL; #ifdef TOOLS_ENABLED editor_api_assembly = NULL; - editor_tools_assembly = NULL; + tools_assembly = NULL; + tools_project_editor_assembly = NULL; #endif api_core_hash = 0; #ifdef TOOLS_ENABLED api_editor_hash = 0; #endif + + unhandled_exception_policy = POLICY_TERMINATE_APP; } GDMono::~GDMono() { if (is_runtime_initialized()) { - -#ifdef TOOLS_ENABLED - if (tools_domain) { - Error err = finalize_and_unload_domain(tools_domain); - if (err != OK) { - ERR_PRINT("Mono: Failed to unload tools domain"); - } - } -#endif - if (scripts_domain) { Error err = _unload_scripts_domain(); if (err != OK) { @@ -1119,14 +1136,14 @@ int32_t _GodotSharp::get_domain_id() { int32_t _GodotSharp::get_scripts_domain_id() { - MonoDomain *domain = SCRIPTS_DOMAIN; + MonoDomain *domain = GDMono::get_singleton()->get_scripts_domain(); CRASH_COND(!domain); // User must check if scripts domain is loaded before calling this method return mono_domain_get_id(domain); } bool _GodotSharp::is_scripts_domain_loaded() { - return GDMono::get_singleton()->is_runtime_initialized() && SCRIPTS_DOMAIN != NULL; + return GDMono::get_singleton()->is_runtime_initialized() && GDMono::get_singleton()->get_scripts_domain() != NULL; } bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) { @@ -1148,7 +1165,7 @@ bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) { if (!p_domain) return true; - if (p_domain == SCRIPTS_DOMAIN && GDMono::get_singleton()->is_finalizing_scripts_domain()) + if (p_domain == GDMono::get_singleton()->get_scripts_domain() && GDMono::get_singleton()->is_finalizing_scripts_domain()) return true; return mono_domain_is_unloading(p_domain); } @@ -1163,6 +1180,12 @@ bool _GodotSharp::is_runtime_initialized() { return GDMono::get_singleton()->is_runtime_initialized(); } +void _GodotSharp::_reload_assemblies(bool p_soft_reload) { +#ifdef GD_MONO_HOT_RELOAD + CSharpLanguage::get_singleton()->reload_assemblies(p_soft_reload); +#endif +} + void _GodotSharp::_bind_methods() { ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread); @@ -1175,6 +1198,7 @@ void _GodotSharp::_bind_methods() { ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &_GodotSharp::is_runtime_shutting_down); ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &_GodotSharp::is_runtime_initialized); + ClassDB::bind_method(D_METHOD("_reload_assemblies"), &_GodotSharp::_reload_assemblies); } _GodotSharp::_GodotSharp() { diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 95340edcca..c5bcce4fa1 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -78,21 +78,20 @@ struct Version { String to_string(Type p_type); } // namespace APIAssembly -#define SCRIPTS_DOMAIN GDMono::get_singleton()->get_scripts_domain() -#ifdef TOOLS_ENABLED -#define TOOLS_DOMAIN GDMono::get_singleton()->get_tools_domain() -#endif - class GDMono { +public: + enum UnhandledExceptionPolicy { + POLICY_TERMINATE_APP, + POLICY_LOG_ERROR + }; + +private: bool runtime_initialized; bool finalizing_scripts_domain; MonoDomain *root_domain; MonoDomain *scripts_domain; -#ifdef TOOLS_ENABLED - MonoDomain *tools_domain; -#endif bool core_api_assembly_out_of_sync; #ifdef TOOLS_ENABLED @@ -104,26 +103,28 @@ class GDMono { GDMonoAssembly *project_assembly; #ifdef TOOLS_ENABLED GDMonoAssembly *editor_api_assembly; - GDMonoAssembly *editor_tools_assembly; + GDMonoAssembly *tools_assembly; + GDMonoAssembly *tools_project_editor_assembly; #endif HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies; + UnhandledExceptionPolicy unhandled_exception_policy; + void _domain_assemblies_cleanup(uint32_t p_domain_id); + bool _are_api_assemblies_out_of_sync(); + bool _load_corlib_assembly(); bool _load_core_api_assembly(); #ifdef TOOLS_ENABLED bool _load_editor_api_assembly(); - bool _load_editor_tools_assembly(); + bool _load_tools_assemblies(); #endif bool _load_project_assembly(); - bool _load_api_assemblies(); - -#ifdef TOOLS_ENABLED - String _get_api_assembly_metadata_path(); -#endif + bool _try_load_api_assemblies(); + void _load_api_assemblies(); void _install_trace_listener(); @@ -132,10 +133,6 @@ class GDMono { Error _load_scripts_domain(); Error _unload_scripts_domain(); -#ifdef TOOLS_ENABLED - Error _load_tools_domain(); -#endif - uint64_t api_core_hash; #ifdef TOOLS_ENABLED uint64_t api_editor_hash; @@ -168,13 +165,15 @@ public: #endif #ifdef TOOLS_ENABLED - void metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated); - bool metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type); + bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type); + String update_api_assemblies_from_prebuilt(); #endif static GDMono *get_singleton() { return singleton; } - static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data); + GD_NORETURN static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data); + + UnhandledExceptionPolicy get_unhandled_exception_policy() const { return unhandled_exception_policy; } // Do not use these, unless you know what you're doing void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly); @@ -185,16 +184,14 @@ public: _FORCE_INLINE_ bool is_finalizing_scripts_domain() { return finalizing_scripts_domain; } _FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; } -#ifdef TOOLS_ENABLED - _FORCE_INLINE_ MonoDomain *get_tools_domain() { return tools_domain; } -#endif _FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; } _FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly; } _FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; } #ifdef TOOLS_ENABLED _FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_editor_tools_assembly() const { return editor_tools_assembly; } + _FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; } + _FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; } #endif #if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) @@ -202,6 +199,7 @@ public: #endif GDMonoClass *get_class(MonoClass *p_raw_class); + GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name); #ifdef GD_MONO_HOT_RELOAD Error reload_scripts_domain(); @@ -214,6 +212,7 @@ public: Error finalize_and_unload_domain(MonoDomain *p_domain); void initialize(); + void initialize_load_assemblies(); GDMono(); ~GDMono(); @@ -276,6 +275,8 @@ class _GodotSharp : public Object { List<NodePath *> np_delete_queue; List<RID *> rid_delete_queue; + void _reload_assemblies(bool p_soft_reload); + protected: static _GodotSharp *singleton; static void _bind_methods(); diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index f1f0015ac9..761c7f6fcb 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -67,11 +67,22 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir()); } - r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); + if (p_custom_config.empty()) { + r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); + } else { + String api_config = p_custom_config == "Release" ? "Release" : "Debug"; + r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config)); + } + + r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir()); r_search_dirs.push_back(OS::get_singleton()->get_resource_dir()); r_search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir()); + #ifdef TOOLS_ENABLED r_search_dirs.push_back(GodotSharpDirs::get_data_editor_tools_dir()); + + // For GodotTools to find the api assemblies + r_search_dirs.push_back(GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug")); #endif } @@ -270,7 +281,18 @@ Error GDMonoAssembly::load(bool p_refonly) { Vector<uint8_t> data = FileAccess::get_file_as_array(path); ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ); - String image_filename = ProjectSettings::get_singleton()->globalize_path(path); + String image_filename; + +#ifdef ANDROID_ENABLED + if (path.begins_with("res://")) { + image_filename = path.substr(6, path.length()); + } else { + image_filename = ProjectSettings::get_singleton()->globalize_path(path); + } +#else + // FIXME: globalize_path does not work on exported games + image_filename = ProjectSettings::get_singleton()->globalize_path(path); +#endif MonoImageOpenStatus status = MONO_IMAGE_OK; diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp index 4342f46109..1c10d3c8eb 100644 --- a/modules/mono/mono_gd/gd_mono_class.cpp +++ b/modules/mono/mono_gd/gd_mono_class.cpp @@ -41,7 +41,7 @@ String GDMonoClass::get_full_name(MonoClass *p_mono_class) { MonoException *exc = NULL; MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return GDMonoMarshal::mono_string_to_godot(str); } @@ -74,16 +74,13 @@ bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const { } GDMonoClass *GDMonoClass::get_parent_class() { + MonoClass *parent_mono_class = mono_class_get_parent(mono_class); + return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : NULL; +} - if (assembly) { - MonoClass *parent_mono_class = mono_class_get_parent(mono_class); - - if (parent_mono_class) { - return GDMono::get_singleton()->get_class(parent_mono_class); - } - } - - return NULL; +GDMonoClass *GDMonoClass::get_nesting_class() { + MonoClass *nesting_type = mono_class_get_nesting_type(mono_class); + return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : NULL; } #ifdef TOOLS_ENABLED diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h index 249422b844..40e1574927 100644 --- a/modules/mono/mono_gd/gd_mono_class.h +++ b/modules/mono/mono_gd/gd_mono_class.h @@ -121,6 +121,7 @@ public: _FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; } GDMonoClass *get_parent_class(); + GDMonoClass *get_nesting_class(); #ifdef TOOLS_ENABLED Vector<MonoClassField *> get_enum_fields(); diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp index 2e79f87625..3999658f93 100644 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -315,7 +315,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ // The order in which we check the following interfaces is very important (dictionaries and generics first) - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type()); + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type()); MonoReflectionType *key_reftype, *value_reftype; if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) { @@ -340,9 +340,15 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); - mono_field_set_value(p_object, mono_field, managed); - break; + if (GDMonoUtils::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; + } else { + MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array()); + mono_field_set_value(p_object, mono_field, managed); + break; + } } ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + type_class->get_name()); @@ -450,7 +456,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } break; case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type()); + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type()); if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), type.type_class); @@ -489,9 +495,15 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); - mono_field_set_value(p_object, mono_field, managed); - break; + if (GDMonoUtils::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; + } else { + MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array()); + mono_field_set_value(p_object, mono_field, managed); + break; + } } } break; diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index cb28efb4e5..e50e3b0794 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -74,15 +74,14 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass); script_binding.wrapper_class = klass; script_binding.gchandle = MonoGCHandle::create_strong(managed); + script_binding.owner = unmanaged; - Reference *kref = Object::cast_to<Reference>(unmanaged); - if (kref) { + if (ref) { // Unsafe refcount increment. The managed instance also counts as a reference. // This way if the unmanaged world has no references to our owner // but the managed instance is alive, the refcount will be 1 instead of 0. // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) - - kref->reference(); + ref->reference(); } // The object was just created, no script instance binding should have been attached @@ -109,9 +108,18 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { void unhandled_exception(MonoException *p_exc) { mono_unhandled_exception((MonoObject *)p_exc); // prints the exception as well - // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders - GDMono::unhandled_exception_hook((MonoObject *)p_exc, NULL); - GD_UNREACHABLE(); + + if (GDMono::get_singleton()->get_unhandled_exception_policy() == GDMono::POLICY_TERMINATE_APP) { + // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders + GDMono::unhandled_exception_hook((MonoObject *)p_exc, NULL); + GD_UNREACHABLE(); + } else { +#ifdef DEBUG_ENABLED + GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc); + if (ScriptDebugger::get_singleton()) + ScriptDebugger::get_singleton()->idle_poll(); +#endif + } } } // namespace GDMonoInternals diff --git a/modules/mono/mono_gd/gd_mono_internals.h b/modules/mono/mono_gd/gd_mono_internals.h index 2d77bde27c..0d82723913 100644 --- a/modules/mono/mono_gd/gd_mono_internals.h +++ b/modules/mono/mono_gd/gd_mono_internals.h @@ -45,7 +45,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged); * Do not call this function directly. * Use GDMonoUtils::debug_unhandled_exception(MonoException *) instead. */ -GD_NORETURN void unhandled_exception(MonoException *p_exc); +void unhandled_exception(MonoException *p_exc); } // namespace GDMonoInternals diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index 87157ed233..42102ed835 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -159,7 +159,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { // The order in which we check the following interfaces is very important (dictionaries and generics first) - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type()); + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type()); if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) { return Variant::DICTIONARY; @@ -179,7 +179,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { } break; case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type()); + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { return Variant::DICTIONARY; @@ -217,7 +217,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type) { switch (p_array_type.type_encoding) { case MONO_TYPE_GENERICINST: { - MonoReflectionType *array_reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_array_type.type_class->get_mono_type()); + MonoReflectionType *array_reftype = mono_type_get_object(mono_domain_get(), p_array_type.type_class->get_mono_type()); if (GDMonoUtils::Marshal::type_is_generic_array(array_reftype)) { MonoReflectionType *elem_reftype; @@ -244,7 +244,7 @@ bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_ bool try_get_dictionary_key_value_types(const ManagedType &p_dictionary_type, ManagedType &r_key_type, ManagedType &r_value_type) { switch (p_dictionary_type.type_encoding) { case MONO_TYPE_GENERICINST: { - MonoReflectionType *dict_reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_dictionary_type.type_class->get_mono_type()); + MonoReflectionType *dict_reftype = mono_type_get_object(mono_domain_get(), p_dictionary_type.type_class->get_mono_type()); if (GDMonoUtils::Marshal::type_is_generic_dictionary(dict_reftype)) { MonoReflectionType *key_reftype; @@ -539,7 +539,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty // The order in which we check the following interfaces is very important (dictionaries and generics first) - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type()); + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type()); MonoReflectionType *key_reftype, *value_reftype; if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) { @@ -558,7 +558,11 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); + if (GDMonoUtils::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()); + } } } break; case MONO_TYPE_OBJECT: { @@ -652,7 +656,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } break; case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type()); + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), p_type.type_class); @@ -681,7 +685,11 @@ 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))) { - return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); + if (GDMonoUtils::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()); + } } } break; } break; @@ -831,20 +839,20 @@ 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); - UNLIKELY_UNHANDLED_EXCEPTION(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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return ptr ? Variant(*ptr) : Variant(); } // The order in which we check the following interfaces is very important (dictionaries and generics first) - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type()); + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type()); if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) { return GDMonoUtils::Marshal::generic_idictionary_to_dictionary(p_obj); @@ -864,19 +872,19 @@ Variant mono_object_to_variant(MonoObject *p_obj) { } break; case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type()); + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type()); if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { MonoException *exc = NULL; MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return *unbox<Dictionary *>(ret); } if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { MonoException *exc = NULL; MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return *unbox<Array *>(ret); } diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index 1b32f1126e..7afdfc8ac8 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -37,6 +37,10 @@ #include "core/project_settings.h" #include "core/reference.h" +#ifdef TOOLS_ENABLED +#include "editor/script_editor_debugger.h" +#endif + #include "../csharp_script.h" #include "../utils/macros.h" #include "../utils/mutex_utils.h" @@ -125,6 +129,7 @@ void MonoCache::clear_godot_api_cache() { class_Array = NULL; class_Dictionary = NULL; class_MarshalUtils = NULL; + class_ISerializationListener = NULL; #ifdef DEBUG_ENABLED class_DebuggingUtils = NULL; @@ -242,6 +247,7 @@ void update_godot_api_cache() { 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)); @@ -302,7 +308,7 @@ void update_godot_api_cache() { #endif // TODO Move to CSharpLanguage::init() and do handle disposal - MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr()); + 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); @@ -371,7 +377,6 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) { // This way if the unmanaged world has no references to our owner // but the managed instance is alive, the refcount will be 1 instead of 0. // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) - ref->reference(); } @@ -384,7 +389,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(SCRIPTS_DOMAIN); + MonoThread *mono_thread = mono_thread_attach(mono_domain_get()); ERR_FAIL_NULL(mono_thread); } @@ -448,17 +453,12 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) { } MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object) { - String object_type = p_object->get_class_name(); - - if (object_type[0] == '_') - object_type = object_type.substr(1, object_type.length()); - - if (!ClassDB::is_parent_class(object_type, p_native)) { + if (!ClassDB::is_parent_class(p_object->get_class_name(), p_native)) { ERR_EXPLAIN("Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'"); ERR_FAIL_V(NULL); } - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr()); + MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); ERR_FAIL_NULL_V(mono_object, NULL); CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object); @@ -470,7 +470,7 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa } MonoObject *create_managed_from(const NodePath &p_from) { - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(NodePath)); + MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(NodePath)); ERR_FAIL_NULL_V(mono_object, NULL); // Construct @@ -482,7 +482,7 @@ MonoObject *create_managed_from(const NodePath &p_from) { } MonoObject *create_managed_from(const RID &p_from) { - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(RID)); + MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(RID)); ERR_FAIL_NULL_V(mono_object, NULL); // Construct @@ -494,7 +494,7 @@ MonoObject *create_managed_from(const RID &p_from) { } MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) { - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr()); + MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); ERR_FAIL_NULL_V(mono_object, NULL); // Search constructor that takes a pointer as parameter @@ -518,13 +518,13 @@ MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) { MonoException *exc = NULL; GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return mono_object; } MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) { - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr()); + MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); ERR_FAIL_NULL_V(mono_object, NULL); // Search constructor that takes a pointer as parameter @@ -548,7 +548,7 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) MonoException *exc = NULL; GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return mono_object; } @@ -600,8 +600,14 @@ void debug_print_unhandled_exception(MonoException *p_exc) { void debug_send_unhandled_exception_error(MonoException *p_exc) { #ifdef DEBUG_ENABLED - if (!ScriptDebugger::get_singleton()) + if (!ScriptDebugger::get_singleton()) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + ERR_PRINTS(GDMonoUtils::get_exception_name_and_message(p_exc)); + } +#endif return; + } _TLS_RECURSION_GUARD_; @@ -625,7 +631,7 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { if (unexpected_exc) { GDMonoInternals::unhandled_exception(unexpected_exc); - GD_UNREACHABLE(); + return; } Vector<ScriptLanguage::StackInfo> _si; @@ -659,7 +665,6 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { void debug_unhandled_exception(MonoException *p_exc) { GDMonoInternals::unhandled_exception(p_exc); // prints the exception as well - GD_UNREACHABLE(); } void print_unhandled_exception(MonoException *p_exc) { @@ -667,19 +672,17 @@ void print_unhandled_exception(MonoException *p_exc) { } void set_pending_exception(MonoException *p_exc) { -#ifdef HAS_PENDING_EXCEPTIONS +#ifdef NO_PENDING_EXCEPTIONS + debug_unhandled_exception(p_exc); +#else if (get_runtime_invoke_count() == 0) { debug_unhandled_exception(p_exc); - GD_UNREACHABLE(); } if (!mono_runtime_set_pending_exception(p_exc, false)) { ERR_PRINTS("Exception thrown from managed code, but it could not be set as pending:"); GDMonoUtils::debug_print_unhandled_exception(p_exc); } -#else - debug_unhandled_exception(p_exc); - GD_UNREACHABLE(); #endif } @@ -755,113 +758,137 @@ void dispose(MonoObject *p_mono_object, MonoException **r_exc) { namespace Marshal { -MonoBoolean type_is_generic_array(MonoReflectionType *p_reftype) { +#ifdef MONO_GLUE_ENABLED +#ifdef TOOLS_ENABLED +#define NO_GLUE_RET(m_ret) \ + { \ + if (!mono_cache.godot_api_cache_updated) return m_ret; \ + } +#else +#define NO_GLUE_RET(m_ret) \ + {} +#endif +#else +#define NO_GLUE_RET(m_ret) \ + { return m_ret; } +#endif + +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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - return res; + UNHANDLED_EXCEPTION(exc); + return (bool)res; } -MonoBoolean type_is_generic_dictionary(MonoReflectionType *p_reftype) { +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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - return res; + 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); - UNLIKELY_UNHANDLED_EXCEPTION(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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); } -MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) { +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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - return res; + UNHANDLED_EXCEPTION(exc); + return (bool)res; } -MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) { +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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - return res; + UNHANDLED_EXCEPTION(exc); + return (bool)res; } -MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype) { +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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - return res; + UNHANDLED_EXCEPTION(exc); + return (bool)res; } -MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) { +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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - return res; + UNHANDLED_EXCEPTION(exc); + return (bool)res; } 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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return result; } 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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return result; } 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); - UNLIKELY_UNHANDLED_EXCEPTION(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); - UNLIKELY_UNHANDLED_EXCEPTION(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); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype))); } } // namespace Marshal -// namespace Marshal - } // namespace GDMonoUtils diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index 00e1ffdd31..d73743bf0b 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -41,7 +41,7 @@ #include "core/object.h" #include "core/reference.h" -#define UNLIKELY_UNHANDLED_EXCEPTION(m_exc) \ +#define UNHANDLED_EXCEPTION(m_exc) \ if (unlikely(m_exc != NULL)) { \ GDMonoUtils::debug_unhandled_exception(m_exc); \ GD_UNREACHABLE(); \ @@ -78,16 +78,16 @@ typedef void (*GenericIDictionaryToDictionary)(MonoObject *, Dictionary *, MonoE namespace Marshal { -MonoBoolean type_is_generic_array(MonoReflectionType *p_reftype); -MonoBoolean type_is_generic_dictionary(MonoReflectionType *p_reftype); +bool type_is_generic_array(MonoReflectionType *p_reftype); +bool type_is_generic_dictionary(MonoReflectionType *p_reftype); void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype); void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype); -MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype); -MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype); -MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype); -MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype); +bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype); +bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype); +bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype); +bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype); GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype); GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype); @@ -157,6 +157,7 @@ struct MonoCache { GDMonoClass *class_Array; GDMonoClass *class_Dictionary; GDMonoClass *class_MarshalUtils; + GDMonoClass *class_ISerializationListener; #ifdef DEBUG_ENABLED GDMonoClass *class_DebuggingUtils; @@ -235,10 +236,19 @@ 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); } @@ -279,7 +289,7 @@ void set_exception_message(MonoException *p_exc, String message); void debug_print_unhandled_exception(MonoException *p_exc); void debug_send_unhandled_exception_error(MonoException *p_exc); -GD_NORETURN void debug_unhandled_exception(MonoException *p_exc); +void debug_unhandled_exception(MonoException *p_exc); void print_unhandled_exception(MonoException *p_exc); /** diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index 0e1739b754..54d73c971f 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -91,7 +91,7 @@ Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argc set_completed(true); int signal_argc = p_argcount - 1; - MonoArray *signal_args = mono_array_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(MonoObject), signal_argc); + MonoArray *signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), signal_argc); for (int i = 0; i < signal_argc; i++) { MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_args[i]); diff --git a/modules/mono/editor/monodevelop_instance.cpp b/modules/mono/utils/android_utils.cpp index 3caa56d1d0..7dd67e3b8e 100644 --- a/modules/mono/editor/monodevelop_instance.cpp +++ b/modules/mono/utils/android_utils.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* monodevelop_instance.cpp */ +/* android_utils.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,58 +28,41 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "monodevelop_instance.h" +#include "android_utils.h" -#include "../mono_gd/gd_mono.h" -#include "../mono_gd/gd_mono_class.h" +#ifdef __ANDROID__ -void MonoDevelopInstance::execute(const Vector<String> &p_files) { +#include "platform/android/thread_jandroid.h" - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) +namespace GDMonoUtils { +namespace Android { - ERR_FAIL_NULL(execute_method); - ERR_FAIL_COND(gc_handle.is_null()); +String get_app_native_lib_dir() { + JNIEnv *env = ThreadAndroid::get_env(); - MonoException *exc = NULL; + jclass activityThreadClass = env->FindClass("android/app/ActivityThread"); + jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;"); + jobject activityThread = env->CallStaticObjectMethod(activityThreadClass, currentActivityThread); + jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;"); + jobject ctx = env->CallObjectMethod(activityThread, getApplication); - Variant files = p_files; - const Variant *args[1] = { &files }; - execute_method->invoke(gc_handle->get_target(), args, &exc); + jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); + jobject applicationInfo = env->CallObjectMethod(ctx, getApplicationInfo); + jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;"); + jstring nativeLibraryDir = (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField); - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL(); - } -} + String result; -void MonoDevelopInstance::execute(const String &p_file) { + const char *const nativeLibraryDir_utf8 = env->GetStringUTFChars(nativeLibraryDir, NULL); + if (nativeLibraryDir_utf8) { + result.parse_utf8(nativeLibraryDir_utf8); + env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDir_utf8); + } - Vector<String> files; - files.push_back(p_file); - execute(files); + return result; } -MonoDevelopInstance::MonoDevelopInstance(const String &p_solution, EditorId p_editor_id) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "MonoDevelopInstance"); - - MonoObject *obj = mono_object_new(TOOLS_DOMAIN, klass->get_mono_ptr()); +} // namespace Android +} // namespace GDMonoUtils - GDMonoMethod *ctor = klass->get_method(".ctor", 2); - MonoException *exc = NULL; - - Variant solution = p_solution; - Variant editor_id = p_editor_id; - const Variant *args[2] = { &solution, &editor_id }; - ctor->invoke(obj, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL(); - } - - gc_handle = MonoGCHandle::create_strong(obj); - execute_method = klass->get_method("Execute", 1); -} +#endif // __ANDROID__ diff --git a/modules/mono/editor/mono_build_info.h b/modules/mono/utils/android_utils.h index b0ae2ed52e..f911c3fdfe 100644 --- a/modules/mono/editor/mono_build_info.h +++ b/modules/mono/utils/android_utils.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* mono_build_info.h */ +/* android_utils.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,28 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef MONO_BUILD_INFO_H -#define MONO_BUILD_INFO_H +#ifndef ANDROID_UTILS_H +#define ANDROID_UTILS_H -#include "core/ustring.h" -#include "core/vector.h" - -struct MonoBuildInfo { +#ifdef __ANDROID__ - struct Hasher { - static uint32_t hash(const MonoBuildInfo &p_key); - }; +#include "core/ustring.h" - String solution; - String configuration; - Vector<String> custom_props; +namespace GDMonoUtils { +namespace Android { - bool operator==(const MonoBuildInfo &p_b) const; +String get_app_native_lib_dir(); - String get_log_dirpath(); +} // namespace Android +} // namespace GDMonoUtils - MonoBuildInfo(); - MonoBuildInfo(const String &p_solution, const String &p_config); -}; +#endif // __ANDROID__ -#endif // MONO_BUILD_INFO_H +#endif // ANDROID_UTILS_H diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index 6e431f51e7..20863b1afe 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -36,16 +36,21 @@ #include "core/project_settings.h" #ifdef WINDOWS_ENABLED +#include <windows.h> + #define ENV_PATH_SEP ";" #else -#define ENV_PATH_SEP ":" #include <limits.h> +#include <unistd.h> + +#define ENV_PATH_SEP ":" #endif #include <stdlib.h> -String path_which(const String &p_name) { +namespace path { +String find_executable(const String &p_name) { #ifdef WINDOWS_ENABLED Vector<String> exts = OS::get_singleton()->get_environment("PATHEXT").split(ENV_PATH_SEP, false); #endif @@ -55,7 +60,7 @@ String path_which(const String &p_name) { return String(); for (int i = 0; i < env_path.size(); i++) { - String p = path_join(env_path[i], p_name); + String p = path::join(env_path[i], p_name); #ifdef WINDOWS_ENABLED for (int j = 0; j < exts.size(); j++) { @@ -73,42 +78,96 @@ String path_which(const String &p_name) { return String(); } -void fix_path(const String &p_path, String &r_out) { - r_out = p_path.replace("\\", "/"); +String cwd() { +#ifdef WINDOWS_ENABLED + const DWORD expected_size = ::GetCurrentDirectoryW(0, NULL); + + String buffer; + buffer.resize((int)expected_size); + if (::GetCurrentDirectoryW(expected_size, buffer.ptrw()) == 0) + return "."; + + return buffer.simplify_path(); +#else + char buffer[PATH_MAX]; + if (::getcwd(buffer, sizeof(buffer)) == NULL) + return "."; + + String result; + if (result.parse_utf8(buffer)) + return "."; - while (true) { // in case of using 2 or more slash - String compare = r_out.replace("//", "/"); - if (r_out == compare) - break; - else - r_out = compare; + return result.simplify_path(); +#endif +} + +String abspath(const String &p_path) { + if (p_path.is_abs_path()) { + return p_path.simplify_path(); + } else { + return path::join(path::cwd(), p_path).simplify_path(); } } -bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path) { +String realpath(const String &p_path) { #ifdef WINDOWS_ENABLED - CharType ret[_MAX_PATH]; - if (::_wfullpath(ret, p_existing_path.c_str(), _MAX_PATH)) { - String abspath = String(ret).replace("\\", "/"); - int pos = abspath.find(":/"); - if (pos != -1) { - r_abs_path = abspath.substr(pos - 1, abspath.length()); - } else { - r_abs_path = abspath; - } - return true; - } -#else - char *resolved_path = ::realpath(p_existing_path.utf8().get_data(), NULL); - if (resolved_path) { - String retstr; - bool success = !retstr.parse_utf8(resolved_path); - ::free(resolved_path); - if (success) { - r_abs_path = retstr; - return true; - } + // Open file without read/write access + HANDLE hFile = ::CreateFileW(p_path.c_str(), 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + return p_path; + + const DWORD expected_size = ::GetFinalPathNameByHandleW(hFile, NULL, 0, FILE_NAME_NORMALIZED); + + if (expected_size == 0) { + ::CloseHandle(hFile); + return p_path; } + + String buffer; + buffer.resize((int)expected_size); + ::GetFinalPathNameByHandleW(hFile, buffer.ptrw(), expected_size, FILE_NAME_NORMALIZED); + + ::CloseHandle(hFile); + return buffer.simplify_path(); +#elif UNIX_ENABLED + char *resolved_path = ::realpath(p_path.utf8().get_data(), NULL); + + if (!resolved_path) + return p_path; + + String result; + bool parse_ok = result.parse_utf8(resolved_path); + ::free(resolved_path); + + if (parse_ok) + return p_path; + + return result.simplify_path(); #endif - return false; } + +String join(const String &p_a, const String &p_b) { + if (p_a.empty()) + return p_b; + + const CharType a_last = p_a[p_a.length() - 1]; + if ((a_last == '/' || a_last == '\\') || + (p_b.size() > 0 && (p_b[0] == '/' || p_b[0] == '\\'))) { + return p_a + p_b; + } + + return p_a + "/" + p_b; +} + +String join(const String &p_a, const String &p_b, const String &p_c) { + return path::join(path::join(p_a, p_b), p_c); +} + +String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d) { + return path::join(path::join(path::join(p_a, p_b), p_c), p_d); +} + +} // namespace path diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h index 69edf4deb7..ca25bc09f7 100644 --- a/modules/mono/utils/path_utils.h +++ b/modules/mono/utils/path_utils.h @@ -31,24 +31,32 @@ #ifndef PATH_UTILS_H #define PATH_UTILS_H +#include "core/string_builder.h" #include "core/ustring.h" -_FORCE_INLINE_ String path_join(const String &e1, const String &e2) { - return e1.plus_file(e2); -} +namespace path { -_FORCE_INLINE_ String path_join(const String &e1, const String &e2, const String &e3) { - return e1.plus_file(e2).plus_file(e3); -} +String join(const String &p_a, const String &p_b); +String join(const String &p_a, const String &p_b, const String &p_c); +String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d); -_FORCE_INLINE_ String path_join(const String &e1, const String &e2, const String &e3, const String &e4) { - return e1.plus_file(e2).plus_file(e3).plus_file(e4); -} +String find_executable(const String &p_name); -String path_which(const String &p_name); +/// Returns a normalized absolute path to the current working directory +String cwd(); -void fix_path(const String &p_path, String &r_out); +/** + * Obtains a normalized absolute path to p_path. Symbolic links are + * not resolved. The path p_path might not exist in the file system. + */ +String abspath(const String &p_path); -bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path); +/** + * Obtains a normalized path to p_path with symbolic links resolved. + * The resulting path might be either a relative or an absolute path. + */ +String realpath(const String &p_path); + +} // namespace path #endif // PATH_UTILS_H diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 877122985d..2b014c2a45 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -44,7 +44,7 @@ int sfind(const String &p_text, int p_from) { int src_len = 2; int len = p_text.length(); - if (src_len == 0 || len == 0) + if (len == 0) return -1; const CharType *src = p_text.c_str(); diff --git a/modules/opensimplex/doc_classes/NoiseTexture.xml b/modules/opensimplex/doc_classes/NoiseTexture.xml index 4826b6cd2a..4b59a380f5 100644 --- a/modules/opensimplex/doc_classes/NoiseTexture.xml +++ b/modules/opensimplex/doc_classes/NoiseTexture.xml @@ -20,7 +20,7 @@ <member name="height" type="int" setter="set_height" getter="get_height" default="512"> Height of the generated texture. </member> - <member name="noise" type="OpenSimplexNoise" setter="set_noise" getter="get_noise" default="null"> + <member name="noise" type="OpenSimplexNoise" setter="set_noise" getter="get_noise"> The [OpenSimplexNoise] instance used to generate the noise. </member> <member name="seamless" type="bool" setter="set_seamless" getter="get_seamless" default="false"> diff --git a/modules/opus/SCsub b/modules/opus/SCsub index a4a431bab7..1db5b0987e 100644 --- a/modules/opus/SCsub +++ b/modules/opus/SCsub @@ -139,7 +139,7 @@ if env['builtin_opus']: opus_sources_silk = [] if env["platform"] in ["android", "iphone", "javascript"]: - env_opus.Append(CPPFLAGS=["-DFIXED_POINT"]) + env_opus.Append(CPPDEFINES=["FIXED_POINT"]) opus_sources_silk = [ "silk/fixed/LTP_analysis_filter_FIX.c", "silk/fixed/LTP_scale_ctrl_FIX.c", @@ -208,7 +208,7 @@ if env['builtin_opus']: if env['builtin_libogg']: env_opus.Prepend(CPPPATH=["#thirdparty/libogg"]) - env_opus.Append(CPPFLAGS=["-DHAVE_CONFIG_H"]) + env_opus.Append(CPPDEFINES=["HAVE_CONFIG_H"]) thirdparty_include_paths = [ "", @@ -222,14 +222,14 @@ if env['builtin_opus']: if env["platform"] == "android": if ("android_arch" in env and env["android_arch"] == "armv7"): - env_opus.Append(CPPFLAGS=["-DOPUS_ARM_OPT"]) + env_opus.Append(CPPDEFINES=["OPUS_ARM_OPT"]) elif ("android_arch" in env and env["android_arch"] == "arm64v8"): - env_opus.Append(CPPFLAGS=["-DOPUS_ARM64_OPT"]) + env_opus.Append(CPPDEFINES=["OPUS_ARM64_OPT"]) elif env["platform"] == "iphone": if ("arch" in env and env["arch"] == "arm"): - env_opus.Append(CPPFLAGS=["-DOPUS_ARM_OPT"]) + env_opus.Append(CPPDEFINES=["OPUS_ARM_OPT"]) elif ("arch" in env and env["arch"] == "arm64"): - env_opus.Append(CPPFLAGS=["-DOPUS_ARM64_OPT"]) + env_opus.Append(CPPDEFINES=["OPUS_ARM64_OPT"]) env_thirdparty = env_opus.Clone() env_thirdparty.disable_warnings() diff --git a/modules/opus/audio_stream_opus.cpp b/modules/opus/audio_stream_opus.cpp index 70d0f770d8..615081d818 100644 --- a/modules/opus/audio_stream_opus.cpp +++ b/modules/opus/audio_stream_opus.cpp @@ -280,7 +280,7 @@ int AudioStreamPlaybackOpus::mix(int16_t *p_buffer, int p_frames) { int todo = p_frames; - if (todo == 0 || todo < MIN_MIX) { + if (todo < MIN_MIX) { break; } diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp index 8f6ffcc83f..8b1f21d95d 100644 --- a/modules/pvr/texture_loader_pvr.cpp +++ b/modules/pvr/texture_loader_pvr.cpp @@ -153,7 +153,7 @@ RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path, ERR_FAIL_V(RES()); } - w = PoolVector<uint8_t>::Write(); + w.release(); int tex_flags = Texture::FLAG_FILTER | Texture::FLAG_REPEAT; @@ -655,8 +655,8 @@ static void _pvrtc_decompress(Image *p_img) { decompress_pvrtc((PVRTCBlock *)r.ptr(), _2bit, p_img->get_width(), p_img->get_height(), 0, (unsigned char *)w.ptr()); - w = PoolVector<uint8_t>::Write(); - r = PoolVector<uint8_t>::Read(); + w.release(); + r.release(); bool make_mipmaps = p_img->has_mipmaps(); p_img->create(p_img->get_width(), p_img->get_height(), false, Image::FORMAT_RGBA8, newdata); diff --git a/modules/recast/register_types.cpp b/modules/recast/register_types.cpp index 247d7f6144..44129fbb61 100644 --- a/modules/recast/register_types.cpp +++ b/modules/recast/register_types.cpp @@ -40,7 +40,14 @@ void register_recast_types() { #ifdef TOOLS_ENABLED EditorPlugins::add_by_type<NavigationMeshEditorPlugin>(); _nav_mesh_generator = memnew(EditorNavigationMeshGenerator); + + ClassDB::APIType prev_api = ClassDB::get_current_api(); + ClassDB::set_current_api(ClassDB::API_EDITOR); + ClassDB::register_class<EditorNavigationMeshGenerator>(); + + ClassDB::set_current_api(prev_api); + Engine::get_singleton()->add_singleton(Engine::Singleton("NavigationMeshGenerator", EditorNavigationMeshGenerator::get_singleton())); #endif } diff --git a/modules/regex/SCsub b/modules/regex/SCsub index 65f354ffa7..1be5af02a5 100644 --- a/modules/regex/SCsub +++ b/modules/regex/SCsub @@ -9,10 +9,10 @@ if env['builtin_pcre2']: jit_blacklist = ['javascript', 'uwp'] thirdparty_dir = '#thirdparty/pcre2/src/' - thirdparty_flags = ['-DPCRE2_STATIC', '-DHAVE_CONFIG_H'] + thirdparty_flags = ['PCRE2_STATIC', 'HAVE_CONFIG_H'] if 'platform' in env and env['platform'] not in jit_blacklist: - thirdparty_flags.append('-DSUPPORT_JIT') + thirdparty_flags.append('SUPPORT_JIT') thirdparty_sources = [ "pcre2_auto_possess.c", @@ -33,6 +33,7 @@ if env['builtin_pcre2']: "pcre2_newline.c", "pcre2_ord2utf.c", "pcre2_pattern_info.c", + "pcre2_script_run.c", "pcre2_serialize.c", "pcre2_string_utils.c", "pcre2_study.c", @@ -47,17 +48,17 @@ if env['builtin_pcre2']: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_regex.Prepend(CPPPATH=[thirdparty_dir]) - env_regex.Append(CPPFLAGS=thirdparty_flags) + env_regex.Append(CPPDEFINES=thirdparty_flags) def pcre2_builtin(width): env_pcre2 = env_regex.Clone() env_pcre2.disable_warnings() env_pcre2["OBJSUFFIX"] = "_" + width + env_pcre2["OBJSUFFIX"] env_pcre2.add_source_files(env.modules_sources, thirdparty_sources) - env_pcre2.Append(CPPFLAGS=["-DPCRE2_CODE_UNIT_WIDTH=" + width]) + env_pcre2.Append(CPPDEFINES=[("PCRE2_CODE_UNIT_WIDTH", width)]) pcre2_builtin("16") pcre2_builtin("32") -env_regex.Append(CPPFLAGS=["-DPCRE2_CODE_UNIT_WIDTH=0"]) +env_regex.Append(CPPDEFINES=[("PCRE2_CODE_UNIT_WIDTH", 0)]) env_regex.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/regex/doc_classes/RegExMatch.xml b/modules/regex/doc_classes/RegExMatch.xml index e279607d13..6dec9fc516 100644 --- a/modules/regex/doc_classes/RegExMatch.xml +++ b/modules/regex/doc_classes/RegExMatch.xml @@ -48,9 +48,7 @@ </method> </methods> <members> - <member name="names" type="Dictionary" setter="" getter="get_names" default="{ - -}"> + <member name="names" type="Dictionary" setter="" getter="get_names" default="{}"> A dictionary of named groups and its corresponding group number. Only groups with that were matched are included. If multiple groups have the same name, that name would refer to the first matching one. </member> <member name="strings" type="Array" setter="" getter="get_strings" default="[ ]"> diff --git a/modules/squish/image_compress_squish.cpp b/modules/squish/image_compress_squish.cpp index 4f38357aa1..64f4c169cb 100644 --- a/modules/squish/image_compress_squish.cpp +++ b/modules/squish/image_compress_squish.cpp @@ -198,8 +198,8 @@ void image_compress_squish(Image *p_image, float p_lossy_quality, Image::Compres h = MAX(h / 2, 1); } - rb = PoolVector<uint8_t>::Read(); - wb = PoolVector<uint8_t>::Write(); + rb.release(); + wb.release(); p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp index b5f4718c72..0922471500 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp @@ -188,7 +188,7 @@ void AudioStreamOGGVorbis::set_data(const PoolVector<uint8_t> &p_data) { ogg_stream = stb_vorbis_open_memory((const unsigned char *)src_datar.ptr(), src_data_len, &error, &ogg_alloc); if (!ogg_stream && error == VORBIS_outofmem) { - w = PoolVector<char>::Write(); + w.release(); alloc_try *= 2; } else { diff --git a/modules/svg/SCsub b/modules/svg/SCsub index 90bfe22abb..9324c1634b 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -16,7 +16,7 @@ env_svg.Prepend(CPPPATH=[thirdparty_dir]) # FIXME: Needed in editor/editor_themes.cpp for now, but ideally there # shouldn't be a dependency on modules/ and its own 3rd party deps. env.Prepend(CPPPATH=[thirdparty_dir]) -env.Append(CPPFLAGS=["-DSVG_ENABLED"]) +env.Append(CPPDEFINES=["SVG_ENABLED"]) env_thirdparty = env_svg.Clone() env_thirdparty.disable_warnings() diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp index e36844a1bc..b0cd648734 100644 --- a/modules/svg/image_loader_svg.cpp +++ b/modules/svg/image_loader_svg.cpp @@ -123,7 +123,7 @@ Error ImageLoaderSVG::_create_image(Ref<Image> p_image, const PoolVector<uint8_t rasterizer.rasterize(svg_image, 0, 0, p_scale * upscale, (unsigned char *)dw.ptr(), w, h, w * 4); - dw = PoolVector<uint8_t>::Write(); + dw.release(); p_image->create(w, h, false, Image::FORMAT_RGBA8, dst_image); if (upsample) p_image->shrink_x2(); diff --git a/modules/tga/image_loader_tga.cpp b/modules/tga/image_loader_tga.cpp index a3c0f5ded7..6ee408d472 100644 --- a/modules/tga/image_loader_tga.cpp +++ b/modules/tga/image_loader_tga.cpp @@ -199,7 +199,7 @@ Error ImageLoaderTGA::convert_to_image(Ref<Image> p_image, const uint8_t *p_buff } } - image_data_w = PoolVector<uint8_t>::Write(); + image_data_w.release(); p_image->create(width, height, 0, Image::FORMAT_RGBA8, image_data); diff --git a/modules/theora/SCsub b/modules/theora/SCsub index 785eca4c41..ff65d2f8ec 100644 --- a/modules/theora/SCsub +++ b/modules/theora/SCsub @@ -66,7 +66,7 @@ if env['builtin_libtheora']: thirdparty_sources += thirdparty_sources_x86_vc if (env["x86_libtheora_opt_gcc"] or env["x86_libtheora_opt_vc"]): - env_theora.Append(CPPFLAGS=["-DOC_X86_ASM"]) + env_theora.Append(CPPDEFINES=["OC_X86_ASM"]) thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index ae542713ea..6a1b463305 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -499,7 +499,6 @@ void VideoStreamPlaybackTheora::update(float p_delta) { /*If we are too slow, reduce the pp level.*/ pp_inc = pp_level > 0 ? -1 : 0; } - } else { } } else { diff --git a/modules/tinyexr/image_loader_tinyexr.cpp b/modules/tinyexr/image_loader_tinyexr.cpp index a9340b1498..74a584821a 100644 --- a/modules/tinyexr/image_loader_tinyexr.cpp +++ b/modules/tinyexr/image_loader_tinyexr.cpp @@ -235,7 +235,7 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f p_image->create(exr_image.width, exr_image.height, false, format, imgdata); - w = PoolVector<uint8_t>::Write(); + w.release(); FreeEXRHeader(&exr_header); FreeEXRImage(&exr_image); diff --git a/modules/upnp/SCsub b/modules/upnp/SCsub index ec1d7f44d5..3f56a69594 100644 --- a/modules/upnp/SCsub +++ b/modules/upnp/SCsub @@ -26,8 +26,8 @@ if env['builtin_miniupnpc']: thirdparty_sources = [thirdparty_dir + "miniupnpc/" + file for file in thirdparty_sources] env_upnp.Prepend(CPPPATH=[thirdparty_dir]) - env_upnp.Append(CPPFLAGS=["-DMINIUPNP_STATICLIB"]) - env_upnp.Append(CPPFLAGS=["-DMINIUPNPC_SET_SOCKET_TIMEOUT"]) + env_upnp.Append(CPPDEFINES=["MINIUPNP_STATICLIB"]) + env_upnp.Append(CPPDEFINES=["MINIUPNPC_SET_SOCKET_TIMEOUT"]) env_thirdparty = env_upnp.Clone() env_thirdparty.disable_warnings() diff --git a/modules/vhacd/SCsub b/modules/vhacd/SCsub index 161aed2eb9..685976dc33 100644 --- a/modules/vhacd/SCsub +++ b/modules/vhacd/SCsub @@ -10,26 +10,21 @@ env_vhacd = env_modules.Clone() thirdparty_dir = "#thirdparty/vhacd/" thirdparty_sources = [ -"src/vhacdManifoldMesh.cpp", -"src/FloatMath.cpp", -"src/vhacdMesh.cpp", -"src/vhacdICHull.cpp", -"src/vhacdVolume.cpp", -"src/VHACD-ASYNC.cpp", -"src/btAlignedAllocator.cpp", -"src/vhacdRaycastMesh.cpp", -"src/VHACD.cpp", -"src/btConvexHullComputer.cpp" + "src/vhacdManifoldMesh.cpp", + "src/FloatMath.cpp", + "src/vhacdMesh.cpp", + "src/vhacdICHull.cpp", + "src/vhacdVolume.cpp", + "src/VHACD-ASYNC.cpp", + "src/btAlignedAllocator.cpp", + "src/vhacdRaycastMesh.cpp", + "src/VHACD.cpp", + "src/btConvexHullComputer.cpp" ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_vhacd.Prepend(CPPPATH=[thirdparty_dir + "/inc"]) -env_vhacd.Append(CPPFLAGS=["-DGODOT_ENET"]) - -# upstream uses c++11 -if not env.msvc: - env_vhacd.Append(CXXFLAGS="-std=c++11") env_thirdparty = env_vhacd.Clone() env_thirdparty.disable_warnings() diff --git a/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml b/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml index 470a3a5e35..9e3670ec35 100644 --- a/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml +++ b/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml @@ -213,7 +213,11 @@ return t * t * (3.0 - 2.0 * t) [/codeblock] </constant> - <constant name="FUNC_MAX" value="65" enum="BuiltinFunc"> + <constant name="MATH_POSMOD" value="65" enum="BuiltinFunc"> + </constant> + <constant name="MATH_LERP_ANGLE" value="66" enum="BuiltinFunc"> + </constant> + <constant name="FUNC_MAX" value="67" enum="BuiltinFunc"> Represents the size of the [enum BuiltinFunc] enum. </constant> </constants> diff --git a/modules/visual_script/doc_classes/VisualScriptPreload.xml b/modules/visual_script/doc_classes/VisualScriptPreload.xml index 05ed0ad1e5..b3b39691c9 100644 --- a/modules/visual_script/doc_classes/VisualScriptPreload.xml +++ b/modules/visual_script/doc_classes/VisualScriptPreload.xml @@ -15,7 +15,7 @@ <methods> </methods> <members> - <member name="resource" type="Resource" setter="set_preload" getter="get_preload" default="null"> + <member name="resource" type="Resource" setter="set_preload" getter="get_preload"> The [Resource] to load. </member> </members> diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index df5bb9ca2e..4425565afa 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -1487,7 +1487,7 @@ Variant VisualScriptInstance::_call_internal(const StringName &p_method, void *p Variant **output_args = (Variant **)(input_args + max_input_args); int flow_max = f->flow_stack_size; int *flow_stack = flow_max ? (int *)(output_args + max_output_args) : (int *)NULL; - int *pass_stack = flow_stack + flow_max; + int *pass_stack = flow_stack ? (int *)(flow_stack + flow_max) : (int *)NULL; String error_str; @@ -1905,7 +1905,7 @@ Variant VisualScriptInstance::call(const StringName &p_method, const Variant **p Variant **output_args = (Variant **)(input_args + max_input_args); int flow_max = f->flow_stack_size; int *flow_stack = flow_max ? (int *)(output_args + max_output_args) : (int *)NULL; - int *pass_stack = flow_stack + flow_max; + int *pass_stack = flow_stack ? (int *)(flow_stack + flow_max) : (int *)NULL; for (int i = 0; i < f->node_count; i++) { sequence_bits[i] = false; //all starts as false @@ -2629,8 +2629,6 @@ void VisualScriptLanguage::debug_get_globals(List<String> *p_locals, List<Varian } String VisualScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) { - if (_debug_parse_err_node >= 0) - return ""; return ""; } diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp index 75b79f8929..8088a71198 100644 --- a/modules/visual_script/visual_script_builtin_funcs.cpp +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -104,6 +104,8 @@ const char *VisualScriptBuiltinFunc::func_name[VisualScriptBuiltinFunc::FUNC_MAX "bytes2var", "color_named", "smoothstep", + "posmod", + "lerp_angle", }; VisualScriptBuiltinFunc::BuiltinFunc VisualScriptBuiltinFunc::find_function(const String &p_string) { @@ -192,6 +194,7 @@ int VisualScriptBuiltinFunc::get_func_argument_count(BuiltinFunc p_func) { case MATH_ATAN2: case MATH_FMOD: case MATH_FPOSMOD: + case MATH_POSMOD: case MATH_POW: case MATH_EASE: case MATH_STEPIFY: @@ -205,6 +208,7 @@ int VisualScriptBuiltinFunc::get_func_argument_count(BuiltinFunc p_func) { case COLORN: return 2; case MATH_LERP: + case MATH_LERP_ANGLE: case MATH_INVERSE_LERP: case MATH_SMOOTHSTEP: case MATH_MOVE_TOWARD: @@ -261,7 +265,16 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const case MATH_ASIN: case MATH_ACOS: case MATH_ATAN: - case MATH_SQRT: { + case MATH_SQRT: + case MATH_FLOOR: + case MATH_CEIL: + case MATH_ROUND: + case MATH_ABS: + case MATH_SIGN: + case MATH_LOG: + case MATH_EXP: + case MATH_ISNAN: + case MATH_ISINF: { return PropertyInfo(Variant::REAL, "s"); } break; case MATH_ATAN2: { @@ -271,32 +284,25 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const return PropertyInfo(Variant::REAL, "x"); } break; case MATH_FMOD: - case MATH_FPOSMOD: { + case MATH_FPOSMOD: + case LOGIC_MAX: + case LOGIC_MIN: { if (p_idx == 0) - return PropertyInfo(Variant::REAL, "x"); + return PropertyInfo(Variant::REAL, "a"); else - return PropertyInfo(Variant::REAL, "y"); + return PropertyInfo(Variant::REAL, "b"); } break; - case MATH_FLOOR: - case MATH_CEIL: - case MATH_ROUND: - case MATH_ABS: - case MATH_SIGN: { - return PropertyInfo(Variant::REAL, "s"); - + case MATH_POSMOD: { + if (p_idx == 0) + return PropertyInfo(Variant::INT, "a"); + else + return PropertyInfo(Variant::INT, "b"); } break; - case MATH_POW: { if (p_idx == 0) - return PropertyInfo(Variant::REAL, "x"); + return PropertyInfo(Variant::REAL, "base"); else - return PropertyInfo(Variant::REAL, "y"); - } break; - case MATH_LOG: - case MATH_EXP: - case MATH_ISNAN: - case MATH_ISINF: { - return PropertyInfo(Variant::REAL, "s"); + return PropertyInfo(Variant::REAL, "exp"); } break; case MATH_EASE: { if (p_idx == 0) @@ -313,15 +319,10 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const else return PropertyInfo(Variant::REAL, "steps"); } break; - case MATH_LERP: { - if (p_idx == 0) - return PropertyInfo(Variant::REAL, "from"); - else if (p_idx == 1) - return PropertyInfo(Variant::REAL, "to"); - else - return PropertyInfo(Variant::REAL, "weight"); - } break; - case MATH_INVERSE_LERP: { + case MATH_LERP: + case MATH_LERP_ANGLE: + case MATH_INVERSE_LERP: + case MATH_SMOOTHSTEP: { if (p_idx == 0) return PropertyInfo(Variant::REAL, "from"); else if (p_idx == 1) @@ -341,14 +342,6 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const else return PropertyInfo(Variant::REAL, "ostop"); } break; - case MATH_SMOOTHSTEP: { - if (p_idx == 0) - return PropertyInfo(Variant::REAL, "from"); - else if (p_idx == 1) - return PropertyInfo(Variant::REAL, "to"); - else - return PropertyInfo(Variant::REAL, "weight"); - } break; case MATH_MOVE_TOWARD: { if (p_idx == 0) return PropertyInfo(Variant::REAL, "from"); @@ -365,12 +358,8 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const else return PropertyInfo(Variant::REAL, "step"); } break; - case MATH_RANDOMIZE: { - - } break; - case MATH_RAND: { - - } break; + case MATH_RANDOMIZE: + case MATH_RAND: case MATH_RANDF: { } break; @@ -380,9 +369,7 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const else return PropertyInfo(Variant::REAL, "to"); } break; - case MATH_SEED: { - return PropertyInfo(Variant::INT, "seed"); - } break; + case MATH_SEED: case MATH_RANDSEED: { return PropertyInfo(Variant::INT, "seed"); } break; @@ -418,26 +405,7 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const else return PropertyInfo(Variant::INT, "max"); } break; - case MATH_WRAPF: { - if (p_idx == 0) - return PropertyInfo(Variant::REAL, "value"); - else if (p_idx == 1) - return PropertyInfo(Variant::REAL, "min"); - else - return PropertyInfo(Variant::REAL, "max"); - } break; - case LOGIC_MAX: { - if (p_idx == 0) - return PropertyInfo(Variant::REAL, "a"); - else - return PropertyInfo(Variant::REAL, "b"); - } break; - case LOGIC_MIN: { - if (p_idx == 0) - return PropertyInfo(Variant::REAL, "a"); - else - return PropertyInfo(Variant::REAL, "b"); - } break; + case MATH_WRAPF: case LOGIC_CLAMP: { if (p_idx == 0) return PropertyInfo(Variant::REAL, "value"); @@ -450,20 +418,15 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const return PropertyInfo(Variant::INT, "value"); } break; case OBJ_WEAKREF: { - return PropertyInfo(Variant::OBJECT, "source"); - } break; case FUNC_FUNCREF: { - if (p_idx == 0) return PropertyInfo(Variant::OBJECT, "instance"); else return PropertyInfo(Variant::STRING, "funcname"); - } break; case TYPE_CONVERT: { - if (p_idx == 0) return PropertyInfo(Variant::NIL, "what"); else @@ -471,45 +434,24 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const } break; case TYPE_OF: { return PropertyInfo(Variant::NIL, "what"); - } break; case TYPE_EXISTS: { - return PropertyInfo(Variant::STRING, "type"); - } break; case TEXT_CHAR: { - return PropertyInfo(Variant::INT, "ascii"); - - } break; - case TEXT_STR: { - - return PropertyInfo(Variant::NIL, "value"); - - } break; - case TEXT_PRINT: { - - return PropertyInfo(Variant::NIL, "value"); - - } break; - case TEXT_PRINTERR: { - return PropertyInfo(Variant::NIL, "value"); - } break; + case TEXT_STR: + case TEXT_PRINT: + case TEXT_PRINTERR: case TEXT_PRINTRAW: { - return PropertyInfo(Variant::NIL, "value"); - - } break; - case VAR_TO_STR: { - return PropertyInfo(Variant::NIL, "var"); - } break; case STR_TO_VAR: { return PropertyInfo(Variant::STRING, "string"); } break; + case VAR_TO_STR: case VAR_TO_BYTES: { if (p_idx == 0) return PropertyInfo(Variant::NIL, "var"); @@ -525,12 +467,10 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const return PropertyInfo(Variant::BOOL, "allow_objects"); } break; case COLORN: { - if (p_idx == 0) return PropertyInfo(Variant::STRING, "name"); else return PropertyInfo(Variant::REAL, "alpha"); - } break; case FUNC_MAX: { } @@ -561,6 +501,7 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons case MATH_CEIL: { t = Variant::REAL; } break; + case MATH_POSMOD: case MATH_ROUND: { t = Variant::INT; } break; @@ -587,6 +528,7 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons } break; case MATH_STEPIFY: case MATH_LERP: + case MATH_LERP_ANGLE: case MATH_INVERSE_LERP: case MATH_RANGE_LERP: case MATH_SMOOTHSTEP: @@ -806,6 +748,12 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in VALIDATE_ARG_NUM(1); *r_return = Math::fposmod((double)*p_inputs[0], (double)*p_inputs[1]); } break; + case VisualScriptBuiltinFunc::MATH_POSMOD: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *r_return = Math::posmod((int)*p_inputs[0], (int)*p_inputs[1]); + } break; case VisualScriptBuiltinFunc::MATH_FLOOR: { VALIDATE_ARG_NUM(0); @@ -905,6 +853,13 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in VALIDATE_ARG_NUM(2); *r_return = Math::lerp((double)*p_inputs[0], (double)*p_inputs[1], (double)*p_inputs[2]); } break; + case VisualScriptBuiltinFunc::MATH_LERP_ANGLE: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + *r_return = Math::lerp_angle((double)*p_inputs[0], (double)*p_inputs[1], (double)*p_inputs[2]); + } break; case VisualScriptBuiltinFunc::MATH_INVERSE_LERP: { VALIDATE_ARG_NUM(0); @@ -1417,6 +1372,8 @@ void VisualScriptBuiltinFunc::_bind_methods() { BIND_ENUM_CONSTANT(BYTES_TO_VAR); BIND_ENUM_CONSTANT(COLORN); BIND_ENUM_CONSTANT(MATH_SMOOTHSTEP); + BIND_ENUM_CONSTANT(MATH_POSMOD); + BIND_ENUM_CONSTANT(MATH_LERP_ANGLE); BIND_ENUM_CONSTANT(FUNC_MAX); } @@ -1454,6 +1411,7 @@ void register_visual_script_builtin_func_node() { VisualScriptLanguage::singleton->add_register_func("functions/built_in/sqrt", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SQRT>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/fmod", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_FMOD>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/fposmod", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_FPOSMOD>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/posmod", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_POSMOD>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/floor", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_FLOOR>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/ceil", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_CEIL>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/round", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ROUND>); @@ -1469,6 +1427,7 @@ void register_visual_script_builtin_func_node() { VisualScriptLanguage::singleton->add_register_func("functions/built_in/decimals", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DECIMALS>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/stepify", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_STEPIFY>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/lerp", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LERP>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/lerp_angle", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LERP_ANGLE>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/inverse_lerp", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_INVERSE_LERP>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/range_lerp", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RANGE_LERP>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/smoothstep", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SMOOTHSTEP>); diff --git a/modules/visual_script/visual_script_builtin_funcs.h b/modules/visual_script/visual_script_builtin_funcs.h index f009f49b5b..cf475d675d 100644 --- a/modules/visual_script/visual_script_builtin_funcs.h +++ b/modules/visual_script/visual_script_builtin_funcs.h @@ -104,6 +104,8 @@ public: BYTES_TO_VAR, COLORN, MATH_SMOOTHSTEP, + MATH_POSMOD, + MATH_LERP_ANGLE, FUNC_MAX }; diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 4579644d49..603c5a7f3c 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -341,74 +341,74 @@ static Color _color_from_type(Variant::Type p_type, bool dark_theme = true) { Color color; if (dark_theme) switch (p_type) { - case Variant::NIL: color = Color::html("#69ecbd"); break; - - case Variant::BOOL: color = Color::html("#8da6f0"); break; - case Variant::INT: color = Color::html("#7dc6ef"); break; - case Variant::REAL: color = Color::html("#61daf4"); break; - case Variant::STRING: color = Color::html("#6ba7ec"); break; - - case Variant::VECTOR2: color = Color::html("#bd91f1"); break; - case Variant::RECT2: color = Color::html("#f191a5"); break; - case Variant::VECTOR3: color = Color::html("#d67dee"); break; - case Variant::TRANSFORM2D: color = Color::html("#c4ec69"); break; - case Variant::PLANE: color = Color::html("#f77070"); break; - case Variant::QUAT: color = Color::html("#ec69a3"); break; - case Variant::AABB: color = Color::html("#ee7991"); break; - case Variant::BASIS: color = Color::html("#e3ec69"); break; - case Variant::TRANSFORM: color = Color::html("#f6a86e"); break; - - case Variant::COLOR: color = Color::html("#9dff70"); break; - case Variant::NODE_PATH: color = Color::html("#6993ec"); break; - case Variant::_RID: color = Color::html("#69ec9a"); break; - case Variant::OBJECT: color = Color::html("#79f3e8"); break; - case Variant::DICTIONARY: color = Color::html("#77edb1"); break; - - case Variant::ARRAY: color = Color::html("#e0e0e0"); break; - case Variant::POOL_BYTE_ARRAY: color = Color::html("#aaf4c8"); break; - case Variant::POOL_INT_ARRAY: color = Color::html("#afdcf5"); break; - case Variant::POOL_REAL_ARRAY: color = Color::html("#97e7f8"); break; - case Variant::POOL_STRING_ARRAY: color = Color::html("#9dc4f2"); break; - case Variant::POOL_VECTOR2_ARRAY: color = Color::html("#d1b3f5"); break; - case Variant::POOL_VECTOR3_ARRAY: color = Color::html("#df9bf2"); break; - case Variant::POOL_COLOR_ARRAY: color = Color::html("#e9ff97"); break; + case Variant::NIL: color = Color(0.41, 0.93, 0.74); break; + + case Variant::BOOL: color = Color(0.55, 0.65, 0.94); break; + case Variant::INT: color = Color(0.49, 0.78, 0.94); break; + case Variant::REAL: color = Color(0.38, 0.85, 0.96); break; + case Variant::STRING: color = Color(0.42, 0.65, 0.93); break; + + case Variant::VECTOR2: color = Color(0.74, 0.57, 0.95); break; + case Variant::RECT2: color = Color(0.95, 0.57, 0.65); break; + case Variant::VECTOR3: color = Color(0.84, 0.49, 0.93); break; + case Variant::TRANSFORM2D: color = Color(0.77, 0.93, 0.41); break; + case Variant::PLANE: color = Color(0.97, 0.44, 0.44); break; + case Variant::QUAT: color = Color(0.93, 0.41, 0.64); break; + case Variant::AABB: color = Color(0.93, 0.47, 0.57); break; + case Variant::BASIS: color = Color(0.89, 0.93, 0.41); break; + case Variant::TRANSFORM: color = Color(0.96, 0.66, 0.43); break; + + case Variant::COLOR: color = Color(0.62, 1.0, 0.44); break; + case Variant::NODE_PATH: color = Color(0.41, 0.58, 0.93); break; + case Variant::_RID: color = Color(0.41, 0.93, 0.6); break; + case Variant::OBJECT: color = Color(0.47, 0.95, 0.91); break; + case Variant::DICTIONARY: color = Color(0.47, 0.93, 0.69); break; + + case Variant::ARRAY: color = Color(0.88, 0.88, 0.88); break; + case Variant::POOL_BYTE_ARRAY: color = Color(0.67, 0.96, 0.78); break; + case Variant::POOL_INT_ARRAY: color = Color(0.69, 0.86, 0.96); break; + case Variant::POOL_REAL_ARRAY: color = Color(0.59, 0.91, 0.97); break; + case Variant::POOL_STRING_ARRAY: color = Color(0.62, 0.77, 0.95); break; + case Variant::POOL_VECTOR2_ARRAY: color = Color(0.82, 0.7, 0.96); break; + case Variant::POOL_VECTOR3_ARRAY: color = Color(0.87, 0.61, 0.95); break; + case Variant::POOL_COLOR_ARRAY: color = Color(0.91, 1.0, 0.59); break; default: color.set_hsv(p_type / float(Variant::VARIANT_MAX), 0.7, 0.7); } else switch (p_type) { - case Variant::NIL: color = Color::html("#25e3a0"); break; - - case Variant::BOOL: color = Color::html("#6d8eeb"); break; - case Variant::INT: color = Color::html("#4fb2e9"); break; - case Variant::REAL: color = Color::html("#27ccf0"); break; - case Variant::STRING: color = Color::html("#4690e7"); break; - - case Variant::VECTOR2: color = Color::html("#ad76ee"); break; - case Variant::RECT2: color = Color::html("#ee758e"); break; - case Variant::VECTOR3: color = Color::html("#dc6aed"); break; - case Variant::TRANSFORM2D: color = Color::html("#96ce1a"); break; - case Variant::PLANE: color = Color::html("#f77070"); break; - case Variant::QUAT: color = Color::html("#ec69a3"); break; - case Variant::AABB: color = Color::html("#ee7991"); break; - case Variant::BASIS: color = Color::html("#b2bb19"); break; - case Variant::TRANSFORM: color = Color::html("#f49047"); break; - - case Variant::COLOR: color = Color::html("#3cbf00"); break; - case Variant::NODE_PATH: color = Color::html("#6993ec"); break; - case Variant::_RID: color = Color::html("#2ce573"); break; - case Variant::OBJECT: color = Color::html("#12d5c3"); break; - case Variant::DICTIONARY: color = Color::html("#57e99f"); break; - - case Variant::ARRAY: color = Color::html("#737373"); break; - case Variant::POOL_BYTE_ARRAY: color = Color::html("#61ea98"); break; - case Variant::POOL_INT_ARRAY: color = Color::html("#61baeb"); break; - case Variant::POOL_REAL_ARRAY: color = Color::html("#40d3f2"); break; - case Variant::POOL_STRING_ARRAY: color = Color::html("#609fea"); break; - case Variant::POOL_VECTOR2_ARRAY: color = Color::html("#9d5dea"); break; - case Variant::POOL_VECTOR3_ARRAY: color = Color::html("#ca5aea"); break; - case Variant::POOL_COLOR_ARRAY: color = Color::html("#92ba00"); break; + case Variant::NIL: color = Color(0.15, 0.89, 0.63); break; + + case Variant::BOOL: color = Color(0.43, 0.56, 0.92); break; + case Variant::INT: color = Color(0.31, 0.7, 0.91); break; + case Variant::REAL: color = Color(0.15, 0.8, 0.94); break; + case Variant::STRING: color = Color(0.27, 0.56, 0.91); break; + + case Variant::VECTOR2: color = Color(0.68, 0.46, 0.93); break; + case Variant::RECT2: color = Color(0.93, 0.46, 0.56); break; + case Variant::VECTOR3: color = Color(0.86, 0.42, 0.93); break; + case Variant::TRANSFORM2D: color = Color(0.59, 0.81, 0.1); break; + case Variant::PLANE: color = Color(0.97, 0.44, 0.44); break; + case Variant::QUAT: color = Color(0.93, 0.41, 0.64); break; + case Variant::AABB: color = Color(0.93, 0.47, 0.57); break; + case Variant::BASIS: color = Color(0.7, 0.73, 0.1); break; + case Variant::TRANSFORM: color = Color(0.96, 0.56, 0.28); break; + + case Variant::COLOR: color = Color(0.24, 0.75, 0.0); break; + case Variant::NODE_PATH: color = Color(0.41, 0.58, 0.93); break; + case Variant::_RID: color = Color(0.17, 0.9, 0.45); break; + case Variant::OBJECT: color = Color(0.07, 0.84, 0.76); break; + case Variant::DICTIONARY: color = Color(0.34, 0.91, 0.62); break; + + case Variant::ARRAY: color = Color(0.45, 0.45, 0.45); break; + case Variant::POOL_BYTE_ARRAY: color = Color(0.38, 0.92, 0.6); break; + case Variant::POOL_INT_ARRAY: color = Color(0.38, 0.73, 0.92); break; + case Variant::POOL_REAL_ARRAY: color = Color(0.25, 0.83, 0.95); break; + case Variant::POOL_STRING_ARRAY: color = Color(0.38, 0.62, 0.92); break; + case Variant::POOL_VECTOR2_ARRAY: color = Color(0.62, 0.36, 0.92); break; + case Variant::POOL_VECTOR3_ARRAY: color = Color(0.79, 0.35, 0.92); break; + case Variant::POOL_COLOR_ARRAY: color = Color(0.57, 0.73, 0.0); break; default: color.set_hsv(p_type / float(Variant::VARIANT_MAX), 0.3, 0.3); @@ -3054,19 +3054,19 @@ void VisualScriptEditor::_notification(int p_what) { List<Pair<String, Color> > colors; if (dark_theme) { - colors.push_back(Pair<String, Color>("flow_control", Color::html("#f4f4f4"))); - colors.push_back(Pair<String, Color>("functions", Color::html("#f58581"))); - colors.push_back(Pair<String, Color>("data", Color::html("#80f6cf"))); - colors.push_back(Pair<String, Color>("operators", Color::html("#ab97df"))); - colors.push_back(Pair<String, Color>("custom", Color::html("#80bbf6"))); - colors.push_back(Pair<String, Color>("constants", Color::html("#f680b0"))); + colors.push_back(Pair<String, Color>("flow_control", Color(0.96, 0.96, 0.96))); + colors.push_back(Pair<String, Color>("functions", Color(0.96, 0.52, 0.51))); + colors.push_back(Pair<String, Color>("data", Color(0.5, 0.96, 0.81))); + colors.push_back(Pair<String, Color>("operators", Color(0.67, 0.59, 0.87))); + colors.push_back(Pair<String, Color>("custom", Color(0.5, 0.73, 0.96))); + colors.push_back(Pair<String, Color>("constants", Color(0.96, 0.5, 0.69))); } else { - colors.push_back(Pair<String, Color>("flow_control", Color::html("#424242"))); - colors.push_back(Pair<String, Color>("functions", Color::html("#f26661"))); - colors.push_back(Pair<String, Color>("data", Color::html("#13bb83"))); - colors.push_back(Pair<String, Color>("operators", Color::html("#8265d0"))); - colors.push_back(Pair<String, Color>("custom", Color::html("#4ea0f2"))); - colors.push_back(Pair<String, Color>("constants", Color::html("#f02f7d"))); + colors.push_back(Pair<String, Color>("flow_control", Color(0.26, 0.26, 0.26))); + colors.push_back(Pair<String, Color>("functions", Color(0.95, 0.4, 0.38))); + colors.push_back(Pair<String, Color>("data", Color(0.07, 0.73, 0.51))); + colors.push_back(Pair<String, Color>("operators", Color(0.51, 0.4, 0.82))); + colors.push_back(Pair<String, Color>("custom", Color(0.31, 0.63, 0.95))); + colors.push_back(Pair<String, Color>("constants", Color(0.94, 0.18, 0.49))); } for (List<Pair<String, Color> >::Element *E = colors.front(); E; E = E->next()) { @@ -3670,7 +3670,6 @@ VisualScriptEditor::VisualScriptEditor() { new_virtual_method_select = memnew(VisualScriptPropertySelector); add_child(new_virtual_method_select); new_virtual_method_select->connect("selected", this, "_selected_new_virtual_method"); - new_virtual_method_select->get_cancel(); member_popup = memnew(PopupMenu); add_child(member_popup); diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index f8cb6cfa3c..c330fa1bc0 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -570,7 +570,6 @@ void VisualScriptFunctionCall::_validate_property(PropertyInfo &property) const Node *bnode = _get_base_node(); if (bnode) { property.hint_string = bnode->get_path(); //convert to loong string - } else { } } } @@ -1027,7 +1026,6 @@ void VisualScriptPropertySet::_adjust_input_index(PropertyInfo &pinfo) const { } PropertyInfo VisualScriptPropertySet::get_input_value_port_info(int p_idx) const { - if (call_mode == CALL_MODE_INSTANCE || call_mode == CALL_MODE_BASIC_TYPE) { if (p_idx == 0) { PropertyInfo pi; @@ -1035,8 +1033,16 @@ PropertyInfo VisualScriptPropertySet::get_input_value_port_info(int p_idx) const pi.name = (call_mode == CALL_MODE_INSTANCE ? String("instance") : Variant::get_type_name(basic_type).to_lower()); _adjust_input_index(pi); return pi; - } else { - p_idx--; + } + } + + List<PropertyInfo> props; + ClassDB::get_property_list(_get_base_type(), &props, true); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (E->get().name == property) { + PropertyInfo pinfo = PropertyInfo(E->get().type, "value", PROPERTY_HINT_TYPE_STRING, E->get().hint_string); + _adjust_input_index(pinfo); + return pinfo; } } @@ -1050,6 +1056,13 @@ PropertyInfo VisualScriptPropertySet::get_output_value_port_info(int p_idx) cons if (call_mode == CALL_MODE_BASIC_TYPE) { return PropertyInfo(basic_type, "out"); } else if (call_mode == CALL_MODE_INSTANCE) { + List<PropertyInfo> props; + ClassDB::get_property_list(_get_base_type(), &props, true); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (E->get().name == property) { + return PropertyInfo(E->get().type, "pass", PROPERTY_HINT_TYPE_STRING, E->get().hint_string); + } + } return PropertyInfo(Variant::OBJECT, "pass", PROPERTY_HINT_TYPE_STRING, get_base_type()); } else { return PropertyInfo(); @@ -1352,7 +1365,6 @@ void VisualScriptPropertySet::_validate_property(PropertyInfo &property) const { Node *bnode = _get_base_node(); if (bnode) { property.hint_string = bnode->get_path(); //convert to loong string - } else { } } } @@ -1794,22 +1806,18 @@ PropertyInfo VisualScriptPropertyGet::get_input_value_port_info(int p_idx) const pi.type = (call_mode == CALL_MODE_INSTANCE ? Variant::OBJECT : basic_type); pi.name = (call_mode == CALL_MODE_INSTANCE ? String("instance") : Variant::get_type_name(basic_type).to_lower()); return pi; - } else { - p_idx--; } } return PropertyInfo(); } PropertyInfo VisualScriptPropertyGet::get_output_value_port_info(int p_idx) const { - - if (index != StringName()) { - - Variant v; - Variant::CallError ce; - v = Variant::construct(type_cache, NULL, 0, ce); - Variant i = v.get(index); - return PropertyInfo(i.get_type(), "value." + String(index)); + List<PropertyInfo> props; + ClassDB::get_property_list(_get_base_type(), &props, true); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (E->get().name == property) { + return PropertyInfo(E->get().type, "value." + String(index)); + } } return PropertyInfo(type_cache, "value"); @@ -2073,7 +2081,6 @@ void VisualScriptPropertyGet::_validate_property(PropertyInfo &property) const { Node *bnode = _get_base_node(); if (bnode) { property.hint_string = bnode->get_path(); //convert to loong string - } else { } } } diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 1b0e41b2de..3b0210597b 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -197,7 +197,6 @@ String VisualScriptFunction::get_output_sequence_port_text(int p_port) const { PropertyInfo VisualScriptFunction::get_input_value_port_info(int p_idx) const { ERR_FAIL_V(PropertyInfo()); - return PropertyInfo(); } PropertyInfo VisualScriptFunction::get_output_value_port_info(int p_idx) const { @@ -418,7 +417,7 @@ PropertyInfo VisualScriptOperator::get_input_value_port_info(int p_idx) const { { Variant::NIL, Variant::NIL } //OP_IN, }; - ERR_FAIL_INDEX_V(p_idx, Variant::OP_MAX, PropertyInfo()); + ERR_FAIL_INDEX_V(p_idx, 2, PropertyInfo()); PropertyInfo pinfo; pinfo.name = p_idx == 0 ? "A" : "B"; diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp index 1e7ed3019c..41828f040e 100644 --- a/modules/visual_script/visual_script_property_selector.cpp +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -405,7 +405,7 @@ void VisualScriptPropertySelector::_item_selected() { String name = item->get_metadata(0); String class_type; - if (type) { + if (type != Variant::NIL) { class_type = Variant::get_type_name(type); } else { diff --git a/modules/visual_script/visual_script_yield_nodes.cpp b/modules/visual_script/visual_script_yield_nodes.cpp index 962560cc96..ebd0f0b3cb 100644 --- a/modules/visual_script/visual_script_yield_nodes.cpp +++ b/modules/visual_script/visual_script_yield_nodes.cpp @@ -431,7 +431,6 @@ void VisualScriptYieldSignal::_validate_property(PropertyInfo &property) const { Node *bnode = _get_base_node(); if (bnode) { property.hint_string = bnode->get_path(); //convert to loong string - } else { } } } diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index e652abbe6a..2f4a45f108 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -103,7 +103,7 @@ int AudioStreamPlaybackOGGVorbis::mix(int16_t *p_buffer, int p_frames) { int todo = p_frames; - if (todo == 0 || todo < MIN_MIX) { + if (todo < MIN_MIX) { break; } diff --git a/modules/webm/SCsub b/modules/webm/SCsub index e57437229f..32e6727656 100644 --- a/modules/webm/SCsub +++ b/modules/webm/SCsub @@ -17,10 +17,6 @@ thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_webm.Prepend(CPPPATH=[thirdparty_dir, thirdparty_dir + "libwebm/"]) -# upstream uses c++11 -if (not env_webm.msvc): - env_webm.Append(CXXFLAGS="-std=c++11") - # also requires libogg, libvorbis and libopus if env['builtin_libogg']: env_webm.Prepend(CPPPATH=["#thirdparty/libogg"]) diff --git a/modules/webm/libvpx/SCsub b/modules/webm/libvpx/SCsub index c76585013c..14fa6c1268 100644 --- a/modules/webm/libvpx/SCsub +++ b/modules/webm/libvpx/SCsub @@ -323,7 +323,7 @@ if webm_cpu_x86: elif cpu_bits == '64': env_libvpx["ASCPU"] = 'X86_64' - env_libvpx.Append(CPPFLAGS=['-DWEBM_X86ASM']) + env_libvpx.Append(CPPDEFINES=['WEBM_X86ASM']) webm_simd_optimizations = True @@ -337,7 +337,7 @@ if webm_cpu_arm: env_libvpx["ASFLAGS"] = '' env_libvpx["ASCOM"] = '$AS $ASFLAGS -o $TARGET $SOURCES' - env_libvpx.Append(CPPFLAGS=['-DWEBM_ARMASM']) + env_libvpx.Append(CPPDEFINES=['WEBM_ARMASM']) webm_simd_optimizations = True diff --git a/modules/webp/image_loader_webp.cpp b/modules/webp/image_loader_webp.cpp index 928a0dcbd3..630c15f140 100644 --- a/modules/webp/image_loader_webp.cpp +++ b/modules/webp/image_loader_webp.cpp @@ -71,7 +71,7 @@ static PoolVector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_q w[3] = 'P'; copymem(&w[4], dst_buff, dst_size); free(dst_buff); - w = PoolVector<uint8_t>::Write(); + w.release(); return dst; } @@ -110,7 +110,7 @@ static Ref<Image> _webp_lossy_unpack(const PoolVector<uint8_t> &p_buffer) { //ERR_EXPLAIN("Error decoding webp! - "+p_file); ERR_FAIL_COND_V(errdec, Ref<Image>()); - dst_w = PoolVector<uint8_t>::Write(); + dst_w.release(); Ref<Image> img = memnew(Image(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image)); return img; @@ -137,7 +137,7 @@ Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p } else { errdec = WebPDecodeRGBInto(p_buffer, p_buffer_len, dst_w.ptr(), datasize, 3 * features.width) == NULL; } - dst_w = PoolVector<uint8_t>::Write(); + dst_w.release(); //ERR_EXPLAIN("Error decoding webp!"); ERR_FAIL_COND_V(errdec, ERR_FILE_CORRUPT); @@ -171,8 +171,6 @@ Error ImageLoaderWEBP::load_image(Ref<Image> p_image, FileAccess *f, bool p_forc Error err = webp_load_image_from_buffer(p_image.ptr(), w.ptr(), src_image_len); - w = PoolVector<uint8_t>::Write(); - return err; } diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub index d9e60eb6f1..033169411f 100644 --- a/modules/websocket/SCsub +++ b/modules/websocket/SCsub @@ -5,89 +5,26 @@ Import('env_modules') # Thirdparty source files -env_lws = env_modules.Clone() - -if env['builtin_libwebsockets'] and not env["platform"] == "javascript": # already builtin for javascript - thirdparty_dir = "#thirdparty/libwebsockets/" - helper_dir = "win32helpers/" - thirdparty_sources = [ - - "core/alloc.c", - "core/context.c", - "core/libwebsockets.c", - "core/output.c", - "core/pollfd.c", - "core/service.c", - - "event-libs/poll/poll.c", - - "misc/base64-decode.c", - "misc/lejp.c", - "misc/sha-1.c", - - "roles/h1/ops-h1.c", - "roles/http/header.c", - "roles/http/client/client.c", - "roles/http/client/client-handshake.c", - "roles/http/server/fops-zip.c", - "roles/http/server/lejp-conf.c", - "roles/http/server/parsers.c", - "roles/http/server/server.c", - "roles/listen/ops-listen.c", - "roles/pipe/ops-pipe.c", - "roles/raw/ops-raw.c", - - "roles/ws/client-ws.c", - "roles/ws/client-parser-ws.c", - "roles/ws/ops-ws.c", - "roles/ws/server-ws.c", - - "tls/tls.c", - "tls/tls-client.c", - "tls/tls-server.c", - - "tls/mbedtls/wrapper/library/ssl_cert.c", - "tls/mbedtls/wrapper/library/ssl_pkey.c", - "tls/mbedtls/wrapper/library/ssl_stack.c", - "tls/mbedtls/wrapper/library/ssl_methods.c", - "tls/mbedtls/wrapper/library/ssl_lib.c", - "tls/mbedtls/wrapper/library/ssl_x509.c", - "tls/mbedtls/wrapper/platform/ssl_port.c", - "tls/mbedtls/wrapper/platform/ssl_pm.c", - "tls/mbedtls/lws-genhash.c", - "tls/mbedtls/mbedtls-client.c", - "tls/mbedtls/lws-genrsa.c", - "tls/mbedtls/ssl.c", - "tls/mbedtls/mbedtls-server.c" +env_ws = env_modules.Clone() + +if env['builtin_wslay'] and not env["platform"] == "javascript": # already builtin for javascript + wslay_dir = "#thirdparty/wslay/" + wslay_sources = [ + "wslay_net.c", + "wslay_event.c", + "wslay_queue.c", + "wslay_stack.c", + "wslay_frame.c", ] - - if env["platform"] == "android": # Builtin getifaddrs - thirdparty_sources += ["misc/getifaddrs.c"] - - if env["platform"] == "windows" or env["platform"] == "uwp": # Winsock - thirdparty_sources += ["plat/lws-plat-win.c", helper_dir + "getopt.c", helper_dir + "getopt_long.c", helper_dir + "gettimeofday.c"] - else: # Unix socket - thirdparty_sources += ["plat/lws-plat-unix.c"] - - thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - - env_lws.Prepend(CPPPATH=[thirdparty_dir]) - - if env['builtin_mbedtls']: - mbedtls_includes = "#thirdparty/mbedtls/include" - env_lws.Prepend(CPPPATH=[mbedtls_includes]) - - wrapper_includes = ["#thirdparty/libwebsockets/tls/mbedtls/wrapper/include/" + inc for inc in ["internal", "openssl", "platform", ""]] - env_lws.Prepend(CPPPATH=wrapper_includes) - + wslay_sources = [wslay_dir + s for s in wslay_sources] + env_ws.Prepend(CPPPATH=[wslay_dir + "includes/"]) + env_ws.Append(CPPDEFINES=["HAVE_CONFIG_H"]) if env["platform"] == "windows" or env["platform"] == "uwp": - env_lws.Prepend(CPPPATH=[thirdparty_dir + helper_dir]) - - if env["platform"] == "uwp": - env_lws.Append(CPPFLAGS=["/DLWS_MINGW_SUPPORT"]) - - env_thirdparty = env_lws.Clone() - env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) - -env_lws.add_source_files(env.modules_sources, "*.cpp") + env_ws.Append(CPPDEFINES=["HAVE_WINSOCK2_H"]) + else: + env_ws.Append(CPPDEFINES=["HAVE_NETINET_IN_H"]) + env_wslay = env_ws.Clone() + env_wslay.disable_warnings() + env_wslay.add_source_files(env.modules_sources, wslay_sources) + +env_ws.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml index 0d425ad1dd..c3baf9de83 100644 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -22,7 +22,7 @@ <argument index="2" name="gd_mp_api" type="bool" default="false"> </argument> <description> - Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. + Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. If the list empty (default), no sub-protocol will be requested. If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a network peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted. If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.) on the [WebSocketPeer] returned via [code]get_peer(1)[/code] and not on this object directly (e.g. [code]get_peer(1).put_packet(data)[/code]). </description> diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index 1af5e403e6..63318e5874 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -69,7 +69,7 @@ </argument> <description> Starts listening on the given port. - You can specify the desired subprotocols via the "protocols" array. If the list empty (default), "binary" will be used. + You can specify the desired subprotocols via the "protocols" array. If the list empty (default), no sub-protocol will be requested. If [code]true[/code] is passed as [code]gd_mp_api[/code], the server will behave like a network peer for the [MultiplayerAPI], connections from non-Godot clients will not work, and [signal data_received] will not be emitted. If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.), on the [WebSocketPeer] returned via [code]get_peer(id)[/code] to communicate with the peer with given [code]id[/code] (e.g. [code]get_peer(id).get_available_packet_count[/code]). </description> diff --git a/modules/websocket/lws_client.cpp b/modules/websocket/lws_client.cpp deleted file mode 100644 index f139a4ef66..0000000000 --- a/modules/websocket/lws_client.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/*************************************************************************/ -/* lws_client.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. */ -/*************************************************************************/ - -#ifndef JAVASCRIPT_ENABLED - -#include "lws_client.h" -#include "core/io/ip.h" -#include "core/project_settings.h" -#if defined(LWS_OPENSSL_SUPPORT) -#include "core/io/stream_peer_ssl.h" -#include "tls/mbedtls/wrapper/include/openssl/ssl.h" -#endif - -Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { - - ERR_FAIL_COND_V(context != NULL, FAILED); - - IP_Address addr; - - if (!p_host.is_valid_ip_address()) { - addr = IP::get_singleton()->resolve_hostname(p_host); - } else { - addr = p_host; - } - - ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); - - // Prepare protocols - _lws_make_protocols(this, &LWSClient::_lws_gd_callback, p_protocols, &_lws_ref); - - // Init lws client - struct lws_context_creation_info info; - struct lws_client_connect_info i; - - memset(&i, 0, sizeof i); - memset(&info, 0, sizeof info); - - info.port = CONTEXT_PORT_NO_LISTEN; - info.protocols = _lws_ref->lws_structs; - info.gid = -1; - info.uid = -1; - //info.ws_ping_pong_interval = 5; - info.user = _lws_ref; -#if defined(LWS_OPENSSL_SUPPORT) - info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; -#endif - context = lws_create_context(&info); - - if (context == NULL) { - _lws_free_ref(_lws_ref); - _lws_ref = NULL; - ERR_EXPLAIN("Unable to create lws context"); - ERR_FAIL_V(FAILED); - } - - i.context = context; - if (p_protocols.size() > 0) - i.protocol = _lws_ref->lws_names; - else - i.protocol = NULL; - - if (p_ssl) { - i.ssl_connection = LCCSCF_USE_SSL; - if (!verify_ssl) - i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; - } else { - i.ssl_connection = 0; - } - - // These CharStrings needs to survive till we call lws_client_connect_via_info - CharString addr_ch = ((String)addr).ascii(); - CharString host_ch = p_host.utf8(); - CharString path_ch = p_path.utf8(); - i.address = addr_ch.get_data(); - i.host = host_ch.get_data(); - i.path = path_ch.get_data(); - i.port = p_port; - - lws_client_connect_via_info(&i); - - return OK; -}; - -int LWSClient::get_max_packet_size() const { - return (1 << _out_buf_size) - PROTO_SIZE; -} - -void LWSClient::poll() { - - _lws_poll(); -} - -int LWSClient::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { - - Ref<LWSPeer> peer = static_cast<Ref<LWSPeer> >(_peer); - LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; - - switch (reason) { -#if defined(LWS_OPENSSL_SUPPORT) - case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: { - PoolByteArray arr = StreamPeerSSL::get_project_cert_array(); - if (arr.size() > 0) - SSL_CTX_add_client_CA((SSL_CTX *)user, d2i_X509(NULL, &arr.read()[0], arr.size())); - else if (verify_ssl) - WARN_PRINTS("No CA cert specified in project settings, SSL will not work"); - } break; -#endif - case LWS_CALLBACK_CLIENT_ESTABLISHED: - peer->set_wsi(wsi, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); - peer_data->peer_id = 0; - peer_data->force_close = false; - peer_data->clean_close = false; - _on_connect(lws_get_protocol(wsi)->name); - break; - - case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: - _on_error(); - destroy_context(); - return -1; // We should close the connection (would probably happen anyway) - - case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: { - int code; - String reason2 = peer->get_close_reason(in, len, code); - peer_data->clean_close = true; - _on_close_request(code, reason2); - return 0; - } - - case LWS_CALLBACK_CLIENT_CLOSED: - peer->close(); - destroy_context(); - _on_disconnect(peer_data->clean_close); - return 0; // We can end here - - case LWS_CALLBACK_CLIENT_RECEIVE: - peer->read_wsi(in, len); - if (peer->get_available_packet_count() > 0) - _on_peer_packet(); - break; - - case LWS_CALLBACK_CLIENT_WRITEABLE: - if (peer_data->force_close) { - peer->send_close_status(wsi); - return -1; - } - - peer->write_wsi(); - break; - - default: - break; - } - - return 0; -} - -Ref<WebSocketPeer> LWSClient::get_peer(int p_peer_id) const { - - return _peer; -} - -NetworkedMultiplayerPeer::ConnectionStatus LWSClient::get_connection_status() const { - - if (context == NULL) - return CONNECTION_DISCONNECTED; - - if (_peer->is_connected_to_host()) - return CONNECTION_CONNECTED; - - return CONNECTION_CONNECTING; -} - -void LWSClient::disconnect_from_host(int p_code, String p_reason) { - - if (context == NULL) - return; - - _peer->close(p_code, p_reason); -}; - -IP_Address LWSClient::get_connected_host() const { - - return IP_Address(); -}; - -uint16_t LWSClient::get_connected_port() const { - - return 1025; -}; - -Error LWSClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); - ERR_FAIL_COND_V(context != NULL, FAILED); - - _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; - _in_pkt_size = nearest_shift(p_in_packets - 1); - _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; - _out_pkt_size = nearest_shift(p_out_packets - 1); - return OK; -} - -LWSClient::LWSClient() { - _in_buf_size = nearest_shift((int)GLOBAL_GET(WSC_IN_BUF) - 1) + 10; - _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_IN_PKT) - 1); - _out_buf_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_BUF) - 1) + 10; - _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_PKT) - 1); - - context = NULL; - _lws_ref = NULL; - _peer = Ref<LWSPeer>(memnew(LWSPeer)); -}; - -LWSClient::~LWSClient() { - - invalidate_lws_ref(); // We do not want any more callback - disconnect_from_host(); - destroy_context(); - _peer = Ref<LWSPeer>(); -}; - -#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_helper.cpp b/modules/websocket/lws_helper.cpp deleted file mode 100644 index a652779960..0000000000 --- a/modules/websocket/lws_helper.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/*************************************************************************/ -/* lws_helper.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. */ -/*************************************************************************/ - -#if !defined(JAVASCRIPT_ENABLED) - -#include "lws_helper.h" - -_LWSRef *_lws_create_ref(void *obj) { - - _LWSRef *out = (_LWSRef *)memalloc(sizeof(_LWSRef)); - out->is_destroying = false; - out->free_context = false; - out->is_polling = false; - out->obj = obj; - out->is_valid = true; - out->lws_structs = NULL; - out->lws_names = NULL; - return out; -} - -void _lws_free_ref(_LWSRef *ref) { - // Free strings and structs - memfree(ref->lws_structs); - memfree(ref->lws_names); - // Free ref - memfree(ref); -} - -bool _lws_destroy(struct lws_context *context, _LWSRef *ref) { - if (context == NULL || ref->is_destroying) - return false; - - if (ref->is_polling) { - ref->free_context = true; - return false; - } - - ref->is_destroying = true; - lws_context_destroy(context); - _lws_free_ref(ref); - return true; -} - -bool _lws_poll(struct lws_context *context, _LWSRef *ref) { - - ERR_FAIL_COND_V(context == NULL, false); - ERR_FAIL_COND_V(ref == NULL, false); - - ref->is_polling = true; - lws_service(context, 0); - ref->is_polling = false; - - if (!ref->free_context) - return false; // Nothing to do - - bool is_valid = ref->is_valid; // Might have been destroyed by poll - - _lws_destroy(context, ref); // Will destroy context and ref - - return is_valid; // If the object should NULL its context and ref -} - -/* - * Prepare the protocol_structs to be fed to context. - * Also prepare the protocol string used by the client. - */ -void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, PoolVector<String> p_names, _LWSRef **r_lws_ref) { - // The input strings might go away after this call, we need to copy them. - // We will clear them when destroying the context. - int i; - int len = p_names.size(); - size_t data_size = sizeof(struct LWSPeer::PeerData); - PoolVector<String>::Read pnr = p_names.read(); - - // This is a reference connecting the object with lws keep track of status, mallocs, etc. - // Must survive as long the context. - // Must be freed manually when context creation fails. - _LWSRef *ref = _lws_create_ref(p_obj); - - // LWS protocol structs. - ref->lws_structs = (struct lws_protocols *)memalloc(sizeof(struct lws_protocols) * (len + 2)); - memset(ref->lws_structs, 0, sizeof(struct lws_protocols) * (len + 2)); - - CharString strings = p_names.join(",").ascii(); - int str_len = strings.length(); - - // Joined string of protocols, double the size: comma separated first, NULL separated last - ref->lws_names = (char *)memalloc((str_len + 1) * 2); // Plus the terminator - - char *names_ptr = ref->lws_names; - struct lws_protocols *structs_ptr = ref->lws_structs; - - // Comma separated protocols string to be used in client Sec-WebSocket-Protocol header - if (str_len > 0) - copymem(names_ptr, strings.get_data(), str_len); - names_ptr[str_len] = '\0'; // NULL terminator - - // NULL terminated protocol strings to be used in protocol structs - if (str_len > 0) - copymem(&names_ptr[str_len + 1], strings.get_data(), str_len); - names_ptr[(str_len * 2) + 1] = '\0'; // NULL terminator - int pos = str_len + 1; - - // The first protocol is the default for any http request (before upgrade). - // It is also used as the websocket protocol when no subprotocol is specified. - structs_ptr[0].name = "default"; - structs_ptr[0].callback = p_callback; - structs_ptr[0].per_session_data_size = data_size; - structs_ptr[0].rx_buffer_size = LWS_BUF_SIZE; - structs_ptr[0].tx_packet_size = LWS_PACKET_SIZE; - // Add user defined protocols - for (i = 0; i < len; i++) { - structs_ptr[i + 1].name = (const char *)&names_ptr[pos]; - structs_ptr[i + 1].callback = p_callback; - structs_ptr[i + 1].per_session_data_size = data_size; - structs_ptr[i + 1].rx_buffer_size = LWS_BUF_SIZE; - structs_ptr[i + 1].tx_packet_size = LWS_PACKET_SIZE; - pos += pnr[i].ascii().length() + 1; - names_ptr[pos - 1] = '\0'; - } - // Add protocols terminator - structs_ptr[len + 1].name = NULL; - structs_ptr[len + 1].callback = NULL; - structs_ptr[len + 1].per_session_data_size = 0; - structs_ptr[len + 1].rx_buffer_size = 0; - - *r_lws_ref = ref; -} - -#endif diff --git a/modules/websocket/lws_helper.h b/modules/websocket/lws_helper.h deleted file mode 100644 index 265dc4e6ad..0000000000 --- a/modules/websocket/lws_helper.h +++ /dev/null @@ -1,111 +0,0 @@ -/*************************************************************************/ -/* lws_helper.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 LWS_HELPER_H -#define LWS_HELPER_H - -#define LWS_BUF_SIZE 65536 -#define LWS_PACKET_SIZE LWS_BUF_SIZE - -#include "core/io/stream_peer.h" -#include "core/os/os.h" -#include "core/reference.h" -#include "core/ring_buffer.h" -#include "lws_peer.h" - -struct _LWSRef { - bool free_context; - bool is_polling; - bool is_valid; - bool is_destroying; - void *obj; - struct lws_protocols *lws_structs; - char *lws_names; -}; - -_LWSRef *_lws_create_ref(void *obj); -void _lws_free_ref(_LWSRef *ref); -bool _lws_destroy(struct lws_context *context, _LWSRef *ref); -bool _lws_poll(struct lws_context *context, _LWSRef *ref); -void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, PoolVector<String> p_names, _LWSRef **r_lws_ref); - -/* clang-format off */ -#define LWS_HELPER(CNAME) \ -protected: \ - struct _LWSRef *_lws_ref; \ - struct lws_context *context; \ - bool _keep_servicing; \ - \ - static int _lws_gd_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { \ - \ - if (wsi == NULL) { \ - return 0; \ - } \ - \ - struct _LWSRef *ref = (struct _LWSRef *)lws_context_user(lws_get_context(wsi)); \ - if (!ref->is_valid) \ - return 0; \ - CNAME *helper = (CNAME *)ref->obj; \ - helper->_keep_servicing = true; \ - return helper->_handle_cb(wsi, reason, user, in, len); \ - } \ - \ - void invalidate_lws_ref() { \ - if (_lws_ref != NULL) \ - _lws_ref->is_valid = false; \ - } \ - \ - void destroy_context() { \ - if (_lws_destroy(context, _lws_ref)) { \ - context = NULL; \ - _lws_ref = NULL; \ - } \ - } \ - \ -public: \ - virtual int _handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); \ - \ - void _lws_poll() { \ - ERR_FAIL_COND(context == NULL); \ - do { \ - _keep_servicing = false; \ - if (::_lws_poll(context, _lws_ref)) { \ - context = NULL; \ - _lws_ref = NULL; \ - break; \ - } \ - } while (_keep_servicing); \ - } \ - \ -protected: - -/* clang-format on */ - -#endif // LWS_HELPER_H diff --git a/modules/websocket/lws_peer.cpp b/modules/websocket/lws_peer.cpp deleted file mode 100644 index a7c85450fa..0000000000 --- a/modules/websocket/lws_peer.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/*************************************************************************/ -/* lws_peer.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. */ -/*************************************************************************/ - -#ifndef JAVASCRIPT_ENABLED - -#include "lws_peer.h" - -#include "core/io/ip.h" - -// Needed for socket_helpers on Android at least. UNIXes has it, just include if not windows -#if !defined(WINDOWS_ENABLED) -#include <netinet/in.h> -#include <sys/socket.h> -#endif - -#include "drivers/unix/net_socket_posix.h" - -void LWSPeer::set_wsi(struct lws *p_wsi, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size) { - ERR_FAIL_COND(wsi != NULL); - - _in_buffer.resize(p_in_pkt_size, p_in_buf_size); - _out_buffer.resize(p_out_pkt_size, p_out_buf_size); - _packet_buffer.resize((1 << MAX(p_in_buf_size, p_out_buf_size)) + LWS_PRE); - wsi = p_wsi; -}; - -void LWSPeer::set_write_mode(WriteMode p_mode) { - write_mode = p_mode; -} - -LWSPeer::WriteMode LWSPeer::get_write_mode() const { - return write_mode; -} - -Error LWSPeer::read_wsi(void *in, size_t len) { - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - if (lws_is_first_fragment(wsi)) - _in_size = 0; - else if (_in_size == -1) // Trash this frame - return ERR_FILE_CORRUPT; - - Error err = _in_buffer.write_packet((const uint8_t *)in, len, NULL); - - if (err != OK) { - _in_buffer.discard_payload(_in_size); - _in_size = -1; - ERR_FAIL_V(err); - } - - _in_size += len; - - if (lws_is_final_fragment(wsi)) { - uint8_t is_string = lws_frame_is_binary(wsi) ? 0 : 1; - err = _in_buffer.write_packet(NULL, _in_size, &is_string); - if (err != OK) { - _in_buffer.discard_payload(_in_size); - _in_size = -1; - ERR_FAIL_V(err); - } - } - - return OK; -} - -Error LWSPeer::write_wsi() { - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - PoolVector<uint8_t> tmp; - int count = _out_buffer.packets_left(); - - if (count == 0) - return OK; - - int read = 0; - uint8_t is_string = 0; - PoolVector<uint8_t>::Write rw = _packet_buffer.write(); - _out_buffer.read_packet(&(rw[LWS_PRE]), _packet_buffer.size() - LWS_PRE, &is_string, read); - - enum lws_write_protocol mode = is_string ? LWS_WRITE_TEXT : LWS_WRITE_BINARY; - lws_write(wsi, &(rw[LWS_PRE]), read, mode); - - if (count > 1) - lws_callback_on_writable(wsi); // we want to write more! - - return OK; -} - -Error LWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - uint8_t is_string = write_mode == WRITE_MODE_TEXT; - _out_buffer.write_packet(p_buffer, p_buffer_size, &is_string); - lws_callback_on_writable(wsi); // notify that we want to write - return OK; -}; - -Error LWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - - r_buffer_size = 0; - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - if (_in_buffer.packets_left() == 0) - return ERR_UNAVAILABLE; - - int read = 0; - PoolVector<uint8_t>::Write rw = _packet_buffer.write(); - _in_buffer.read_packet(rw.ptr(), _packet_buffer.size(), &_is_string, read); - - *r_buffer = rw.ptr(); - r_buffer_size = read; - - return OK; -}; - -int LWSPeer::get_available_packet_count() const { - - if (!is_connected_to_host()) - return 0; - - return _in_buffer.packets_left(); -}; - -bool LWSPeer::was_string_packet() const { - - return _is_string; -}; - -bool LWSPeer::is_connected_to_host() const { - - return wsi != NULL; -}; - -String LWSPeer::get_close_reason(void *in, size_t len, int &r_code) { - String s; - r_code = 0; - if (len < 2) // From docs this should not happen - return s; - - const uint8_t *b = (const uint8_t *)in; - r_code = b[0] << 8 | b[1]; - - if (len > 2) { - const char *utf8 = (const char *)&b[2]; - s.parse_utf8(utf8, len - 2); - } - return s; -} - -void LWSPeer::send_close_status(struct lws *p_wsi) { - if (close_code == -1) - return; - - int len = close_reason.size(); - ERR_FAIL_COND(len > 123); // Maximum allowed reason size in bytes - - lws_close_status code = (lws_close_status)close_code; - unsigned char *reason = len > 0 ? (unsigned char *)close_reason.utf8().ptrw() : NULL; - - lws_close_reason(p_wsi, code, reason, len); - - close_code = -1; - close_reason = ""; -} - -void LWSPeer::close(int p_code, String p_reason) { - if (wsi != NULL) { - close_code = p_code; - close_reason = p_reason; - PeerData *data = ((PeerData *)lws_wsi_user(wsi)); - data->force_close = true; - data->clean_close = true; - lws_callback_on_writable(wsi); // Notify that we want to disconnect - } else { - close_code = -1; - close_reason = ""; - } - wsi = NULL; - _in_buffer.clear(); - _out_buffer.clear(); - _in_size = 0; - _is_string = 0; - _packet_buffer.resize(0); -}; - -IP_Address LWSPeer::get_connected_host() const { - - ERR_FAIL_COND_V(!is_connected_to_host(), IP_Address()); - - IP_Address ip; - uint16_t port = 0; - - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); - - int fd = lws_get_socket_fd(wsi); - ERR_FAIL_COND_V(fd == -1, IP_Address()); - - int ret = getpeername(fd, (struct sockaddr *)&addr, &len); - ERR_FAIL_COND_V(ret != 0, IP_Address()); - - NetSocketPosix::_set_ip_port(&addr, ip, port); - - return ip; -}; - -uint16_t LWSPeer::get_connected_port() const { - - ERR_FAIL_COND_V(!is_connected_to_host(), 0); - - IP_Address ip; - uint16_t port = 0; - - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); - - int fd = lws_get_socket_fd(wsi); - ERR_FAIL_COND_V(fd == -1, 0); - - int ret = getpeername(fd, (struct sockaddr *)&addr, &len); - ERR_FAIL_COND_V(ret != 0, 0); - - NetSocketPosix::_set_ip_port(&addr, ip, port); - - return port; -}; - -LWSPeer::LWSPeer() { - wsi = NULL; - write_mode = WRITE_MODE_BINARY; - close(); -}; - -LWSPeer::~LWSPeer() { - - close(); -}; - -#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_server.cpp b/modules/websocket/lws_server.cpp deleted file mode 100644 index 482c02dc60..0000000000 --- a/modules/websocket/lws_server.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/*************************************************************************/ -/* lws_server.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef JAVASCRIPT_ENABLED - -#include "lws_server.h" -#include "core/os/os.h" -#include "core/project_settings.h" - -Error LWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { - - ERR_FAIL_COND_V(context != NULL, FAILED); - - _is_multiplayer = gd_mp_api; - - struct lws_context_creation_info info; - memset(&info, 0, sizeof info); - - // Prepare lws protocol structs - _lws_make_protocols(this, &LWSServer::_lws_gd_callback, p_protocols, &_lws_ref); - - info.port = p_port; - info.user = _lws_ref; - info.protocols = _lws_ref->lws_structs; - info.gid = -1; - info.uid = -1; - //info.ws_ping_pong_interval = 5; - - context = lws_create_context(&info); - - if (context == NULL) { - _lws_free_ref(_lws_ref); - _lws_ref = NULL; - ERR_EXPLAIN("Unable to create LWS context"); - ERR_FAIL_V(FAILED); - } - - return OK; -} - -bool LWSServer::is_listening() const { - return context != NULL; -} - -int LWSServer::get_max_packet_size() const { - return (1 << _out_buf_size) - PROTO_SIZE; -} - -int LWSServer::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { - - LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; - - switch (reason) { - case LWS_CALLBACK_HTTP: - // no http for now - // closing immediately returning -1; - return -1; - - case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: - // check header here? - break; - - case LWS_CALLBACK_ESTABLISHED: { - int32_t id = _gen_unique_id(); - - Ref<LWSPeer> peer = Ref<LWSPeer>(memnew(LWSPeer)); - peer->set_wsi(wsi, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); - _peer_map[id] = peer; - - peer_data->peer_id = id; - peer_data->force_close = false; - peer_data->clean_close = false; - _on_connect(id, lws_get_protocol(wsi)->name); - break; - } - - case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: { - if (peer_data == NULL) - return 0; - - int32_t id = peer_data->peer_id; - if (_peer_map.has(id)) { - int code; - Ref<LWSPeer> peer = _peer_map[id]; - String reason2 = peer->get_close_reason(in, len, code); - peer_data->clean_close = true; - _on_close_request(id, code, reason2); - } - return 0; - } - - case LWS_CALLBACK_CLOSED: { - if (peer_data == NULL) - return 0; - int32_t id = peer_data->peer_id; - bool clean = peer_data->clean_close; - if (_peer_map.has(id)) { - _peer_map[id]->close(); - _peer_map.erase(id); - } - _on_disconnect(id, clean); - return 0; // we can end here - } - - case LWS_CALLBACK_RECEIVE: { - int32_t id = peer_data->peer_id; - if (_peer_map.has(id)) { - static_cast<Ref<LWSPeer> >(_peer_map[id])->read_wsi(in, len); - if (_peer_map[id]->get_available_packet_count() > 0) - _on_peer_packet(id); - } - break; - } - - case LWS_CALLBACK_SERVER_WRITEABLE: { - int id = peer_data->peer_id; - if (peer_data->force_close) { - if (_peer_map.has(id)) { - Ref<LWSPeer> peer = _peer_map[id]; - peer->send_close_status(wsi); - } - return -1; - } - - if (_peer_map.has(id)) - static_cast<Ref<LWSPeer> >(_peer_map[id])->write_wsi(); - break; - } - - default: - break; - } - - return 0; -} - -void LWSServer::stop() { - if (context == NULL) - return; - - _peer_map.clear(); - destroy_context(); - context = NULL; -} - -bool LWSServer::has_peer(int p_id) const { - return _peer_map.has(p_id); -} - -Ref<WebSocketPeer> LWSServer::get_peer(int p_id) const { - ERR_FAIL_COND_V(!has_peer(p_id), NULL); - return _peer_map[p_id]; -} - -IP_Address LWSServer::get_peer_address(int p_peer_id) const { - ERR_FAIL_COND_V(!has_peer(p_peer_id), IP_Address()); - - return _peer_map[p_peer_id]->get_connected_host(); -} - -int LWSServer::get_peer_port(int p_peer_id) const { - ERR_FAIL_COND_V(!has_peer(p_peer_id), 0); - - return _peer_map[p_peer_id]->get_connected_port(); -} - -void LWSServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { - ERR_FAIL_COND(!has_peer(p_peer_id)); - - get_peer(p_peer_id)->close(p_code, p_reason); -} - -Error LWSServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); - ERR_FAIL_COND_V(context != NULL, FAILED); - - _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; - _in_pkt_size = nearest_shift(p_in_packets - 1); - _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; - _out_pkt_size = nearest_shift(p_out_packets - 1); - return OK; -} - -LWSServer::LWSServer() { - _in_buf_size = nearest_shift((int)GLOBAL_GET(WSS_IN_BUF) - 1) + 10; - _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_IN_PKT) - 1); - _out_buf_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_BUF) - 1) + 10; - _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_PKT) - 1); - context = NULL; - _lws_ref = NULL; -} - -LWSServer::~LWSServer() { - invalidate_lws_ref(); // we do not want any more callbacks - stop(); -} - -#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp index 39bf3de982..1c808f0d5c 100644 --- a/modules/websocket/register_types.cpp +++ b/modules/websocket/register_types.cpp @@ -37,9 +37,8 @@ #include "emws_peer.h" #include "emws_server.h" #else -#include "lws_client.h" -#include "lws_peer.h" -#include "lws_server.h" +#include "wsl_client.h" +#include "wsl_server.h" #endif void register_websocket_types() { @@ -64,9 +63,9 @@ void register_websocket_types() { EMWSClient::make_default(); EMWSServer::make_default(); #else - LWSPeer::make_default(); - LWSClient::make_default(); - LWSServer::make_default(); + WSLPeer::make_default(); + WSLClient::make_default(); + WSLServer::make_default(); #endif ClassDB::register_virtual_class<WebSocketMultiplayerPeer>(); diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 23cbf916eb..e24cb850ec 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -191,7 +191,7 @@ void WebSocketMultiplayerPeer::_send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_ty p_peer->put_packet(&(message.read()[0]), message.size()); } -PoolVector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint32_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size) { +PoolVector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint8_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size) { PoolVector<uint8_t> out; out.resize(PROTO_SIZE + p_data_size); diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index 7fd97a6595..e3ab0784ab 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -41,7 +41,7 @@ class WebSocketMultiplayerPeer : public NetworkedMultiplayerPeer { GDCLASS(WebSocketMultiplayerPeer, NetworkedMultiplayerPeer); private: - PoolVector<uint8_t> _make_pkt(uint32_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size); + PoolVector<uint8_t> _make_pkt(uint8_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size); void _store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size); Error _server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size); diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp new file mode 100644 index 0000000000..86374e8f80 --- /dev/null +++ b/modules/websocket/wsl_client.cpp @@ -0,0 +1,363 @@ +/*************************************************************************/ +/* wsl_client.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. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_ENABLED + +#include "wsl_client.h" +#include "core/io/ip.h" +#include "core/project_settings.h" + +void WSLClient::_do_handshake() { + if (_requested < _request.size() - 1) { + int sent = 0; + Error err = _connection->put_partial_data(((const uint8_t *)_request.get_data() + _requested), _request.size() - _requested - 1, sent); + // Sending handshake failed + if (err != OK) { + disconnect_from_host(); + _on_error(); + return; + } + _requested += sent; + + } else { + int read = 0; + while (true) { + if (_resp_pos >= WSL_MAX_HEADER_SIZE) { + // Header is too big + disconnect_from_host(); + _on_error(); + ERR_EXPLAIN("Response headers too big"); + ERR_FAIL(); + } + Error err = _connection->get_partial_data(&_resp_buf[_resp_pos], 1, read); + if (err == ERR_FILE_EOF) { + // We got a disconnect. + disconnect_from_host(); + _on_error(); + return; + } else if (err != OK) { + // Got some error. + disconnect_from_host(); + _on_error(); + return; + } else if (read != 1) { + // Busy, wait next poll. + break; + } + // Check "\r\n\r\n" header terminator + char *r = (char *)_resp_buf; + int l = _resp_pos; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + r[l - 3] = '\0'; + String protocol; + // Response is over, verify headers and create peer. + if (!_verify_headers(protocol)) { + disconnect_from_host(); + _on_error(); + ERR_EXPLAIN("Invalid response headers"); + ERR_FAIL(); + } + // Create peer. + WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); + data->obj = this; + data->conn = _connection; + data->is_server = false; + data->id = 1; + _peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); + _on_connect(protocol); + break; + } + _resp_pos += 1; + } + } +} + +bool WSLClient::_verify_headers(String &r_protocol) { + String s = (char *)_resp_buf; + Vector<String> psa = s.split("\r\n"); + int len = psa.size(); + if (len < 4) { + ERR_EXPLAIN("Not enough response headers."); + ERR_FAIL_V(false); + } + + Vector<String> req = psa[0].split(" ", false); + if (req.size() < 2) { + ERR_EXPLAIN("Invalid protocol or status code."); + ERR_FAIL_V(false); + } + // Wrong protocol + if (req[0] != "HTTP/1.1" || req[1] != "101") { + ERR_EXPLAIN("Invalid protocol or status code."); + ERR_FAIL_V(false); + } + + Map<String, String> headers; + for (int i = 1; i < len; i++) { + Vector<String> header = psa[i].split(":", false, 1); + if (header.size() != 2) { + ERR_EXPLAIN("Invalid header -> " + psa[i]); + ERR_FAIL_V(false); + } + String name = header[0].to_lower(); + String value = header[1].strip_edges(); + if (headers.has(name)) + headers[name] += "," + value; + else + headers[name] = value; + } + +#define _WLS_EXPLAIN(NAME, VALUE) \ + ERR_EXPLAIN("Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'"); +#define _WLS_CHECK(NAME, VALUE) \ + _WLS_EXPLAIN(NAME, VALUE); \ + ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false); +#define _WLS_CHECK_NC(NAME, VALUE) \ + _WLS_EXPLAIN(NAME, VALUE); \ + ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME] != VALUE, false); + _WLS_CHECK("connection", "upgrade"); + _WLS_CHECK("upgrade", "websocket"); + _WLS_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key)); + if (_protocols.size() == 0) { + // We didn't request a custom protocol + ERR_FAIL_COND_V(headers.has("sec-websocket-protocol"), false); + } else { + ERR_FAIL_COND_V(!headers.has("sec-websocket-protocol"), false); + r_protocol = headers["sec-websocket-protocol"]; + bool valid = false; + for (int i = 0; i < _protocols.size(); i++) { + if (_protocols[i] != r_protocol) + continue; + valid = true; + break; + } + if (!valid) + return false; + } +#undef _WLS_CHECK_NC +#undef _WLS_CHECK +#undef _WLS_EXPLAIN + + return true; +} + +Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { + + ERR_FAIL_COND_V(_connection.is_valid(), ERR_ALREADY_IN_USE); + + _peer = Ref<WSLPeer>(memnew(WSLPeer)); + IP_Address addr; + + if (!p_host.is_valid_ip_address()) { + addr = IP::get_singleton()->resolve_hostname(p_host); + } else { + addr = p_host; + } + + ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); + + String port = ""; + if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { + port = ":" + itos(p_port); + } + + Error err = _tcp->connect_to_host(addr, p_port); + if (err != OK) { + _on_error(); + _tcp->disconnect_from_host(); + return err; + } + _connection = _tcp; + _use_ssl = p_ssl; + _host = p_host; + _protocols = p_protocols; + + _key = WSLPeer::generate_key(); + // TODO custom extra headers (allow overriding this too?) + String request = "GET " + p_path + " HTTP/1.1\r\n"; + request += "Host: " + p_host + port + "\r\n"; + request += "Upgrade: websocket\r\n"; + request += "Connection: Upgrade\r\n"; + request += "Sec-WebSocket-Key: " + _key + "\r\n"; + request += "Sec-WebSocket-Version: 13\r\n"; + if (p_protocols.size() > 0) { + request += "Sec-WebSocket-Protocol: "; + for (int i = 0; i < p_protocols.size(); i++) { + if (i != 0) + request += ","; + request += p_protocols[i]; + } + request += "\r\n"; + } + request += "\r\n"; + _request = request.utf8(); + + return OK; +} + +int WSLClient::get_max_packet_size() const { + return (1 << _out_buf_size) - PROTO_SIZE; +} + +void WSLClient::poll() { + if (_peer->is_connected_to_host()) { + _peer->poll(); + if (!_peer->is_connected_to_host()) { + _on_disconnect(_peer->close_code != -1); + disconnect_from_host(); + } + return; + } + + if (_connection.is_null()) + return; // Not connected. + + switch (_tcp->get_status()) { + case StreamPeerTCP::STATUS_NONE: + // Clean close + _on_error(); + disconnect_from_host(); + break; + case StreamPeerTCP::STATUS_CONNECTED: { + Ref<StreamPeerSSL> ssl; + if (_use_ssl) { + if (_connection == _tcp) { + // Start SSL handshake + ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); + ERR_EXPLAIN("SSL is not available in this build"); + ERR_FAIL_COND(ssl.is_null()); + ssl->set_blocking_handshake_enabled(false); + if (ssl->connect_to_stream(_tcp, verify_ssl, _host) != OK) { + _on_error(); + disconnect_from_host(); + return; + } + _connection = ssl; + } else { + ssl = static_cast<Ref<StreamPeerSSL> >(_connection); + ERR_FAIL_COND(ssl.is_null()); // Bug? + ssl->poll(); + } + if (ssl->get_status() == StreamPeerSSL::STATUS_HANDSHAKING) + return; // Need more polling. + else if (ssl->get_status() != StreamPeerSSL::STATUS_CONNECTED) { + _on_error(); + disconnect_from_host(); + return; // Error. + } + } + // Do websocket handshake. + _do_handshake(); + } break; + case StreamPeerTCP::STATUS_ERROR: + _on_error(); + disconnect_from_host(); + break; + case StreamPeerTCP::STATUS_CONNECTING: + break; // Wait for connection + } +} + +Ref<WebSocketPeer> WSLClient::get_peer(int p_peer_id) const { + + ERR_FAIL_COND_V(p_peer_id != 1, NULL); + + return _peer; +} + +NetworkedMultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const { + + if (_peer->is_connected_to_host()) + return CONNECTION_CONNECTED; + + if (_tcp->is_connected_to_host()) + return CONNECTION_CONNECTING; + + return CONNECTION_DISCONNECTED; +} + +void WSLClient::disconnect_from_host(int p_code, String p_reason) { + + _peer->close(p_code, p_reason); + _connection = Ref<StreamPeer>(NULL); + _tcp = Ref<StreamPeerTCP>(memnew(StreamPeerTCP)); + + _key = ""; + _host = ""; + _protocols.resize(0); + _use_ssl = false; + + _request = ""; + _requested = 0; + + memset(_resp_buf, 0, sizeof(_resp_buf)); + _resp_pos = 0; +} + +IP_Address WSLClient::get_connected_host() const { + + return IP_Address(); +} + +uint16_t WSLClient::get_connected_port() const { + + return 1025; +} + +Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { + ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); + ERR_FAIL_COND_V(_connection.is_valid(), FAILED); + + _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; + _in_pkt_size = nearest_shift(p_in_packets - 1); + _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; + _out_pkt_size = nearest_shift(p_out_packets - 1); + return OK; +} + +WSLClient::WSLClient() { + _in_buf_size = nearest_shift((int)GLOBAL_GET(WSC_IN_BUF) - 1) + 10; + _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_IN_PKT) - 1); + _out_buf_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_BUF) - 1) + 10; + _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_PKT) - 1); + + _peer.instance(); + _tcp.instance(); + disconnect_from_host(); +} + +WSLClient::~WSLClient() { + + _peer->close_now(); + _peer->invalidate(); + disconnect_from_host(); +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_client.h b/modules/websocket/wsl_client.h index df4aabec7f..57dfd635b7 100644 --- a/modules/websocket/lws_client.h +++ b/modules/websocket/wsl_client.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_client.h */ +/* wsl_client.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,21 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LWSCLIENT_H -#define LWSCLIENT_H +#ifndef WSLCLIENT_H +#define WSLCLIENT_H #ifndef JAVASCRIPT_ENABLED #include "core/error_list.h" -#include "lws_helper.h" -#include "lws_peer.h" +#include "core/io/stream_peer_ssl.h" +#include "core/io/stream_peer_tcp.h" #include "websocket_client.h" +#include "wsl_peer.h" +#include "wslay/wslay.h" -class LWSClient : public WebSocketClient { +class WSLClient : public WebSocketClient { - GDCIIMPL(LWSClient, WebSocketClient); - - LWS_HELPER(LWSClient); + GDCIIMPL(WSLClient, WebSocketClient); private: int _in_buf_size; @@ -50,6 +50,26 @@ private: int _out_buf_size; int _out_pkt_size; + Ref<WSLPeer> _peer; + Ref<StreamPeerTCP> _tcp; + Ref<StreamPeer> _connection; + + CharString _request; + int _requested; + + uint8_t _resp_buf[WSL_MAX_HEADER_SIZE]; + int _resp_pos; + + String _response; + + String _key; + String _host; + PoolVector<String> _protocols; + bool _use_ssl; + + void _do_handshake(); + bool _verify_headers(String &r_protocol); + public: 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, PoolVector<String> p_protocol = PoolVector<String>()); @@ -61,10 +81,10 @@ public: virtual ConnectionStatus get_connection_status() const; virtual void poll(); - LWSClient(); - ~LWSClient(); + WSLClient(); + ~WSLClient(); }; #endif // JAVASCRIPT_ENABLED -#endif // LWSCLIENT_H +#endif // WSLCLIENT_H diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp new file mode 100644 index 0000000000..b11bd2b70f --- /dev/null +++ b/modules/websocket/wsl_peer.cpp @@ -0,0 +1,339 @@ +/*************************************************************************/ +/* lws_peer.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. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_ENABLED + +#include "wsl_peer.h" + +#include "wsl_client.h" +#include "wsl_server.h" + +#include "core/math/crypto_core.h" +#include "core/math/random_number_generator.h" +#include "core/os/os.h" + +String WSLPeer::generate_key() { + // Random key + RandomNumberGenerator rng; + rng.set_seed(OS::get_singleton()->get_unix_time()); + PoolVector<uint8_t> bkey; + int len = 16; // 16 bytes, as per RFC + bkey.resize(len); + PoolVector<uint8_t>::Write w = bkey.write(); + for (int i = 0; i < len; i++) { + w[i] = (uint8_t)rng.randi_range(0, 255); + } + return CryptoCore::b64_encode_str(&w[0], len); +} + +String WSLPeer::compute_key_response(String p_key) { + String key = p_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // Magic UUID as per RFC + Vector<uint8_t> sha = key.sha1_buffer(); + return CryptoCore::b64_encode_str(sha.ptr(), sha.size()); +} + +void WSLPeer::_wsl_destroy(struct PeerData **p_data) { + if (!p_data || !(*p_data)) + return; + struct PeerData *data = *p_data; + if (data->polling) { + data->destroy = true; + return; + } + wslay_event_context_free(data->ctx); + memdelete(data); + *p_data = NULL; +} + +bool WSLPeer::_wsl_poll(struct PeerData *p_data) { + p_data->polling = true; + int err = 0; + if ((err = wslay_event_recv(p_data->ctx)) != 0 || (err = wslay_event_send(p_data->ctx)) != 0) { + print_verbose("Websocket (wslay) poll error: " + itos(err)); + p_data->destroy = true; + } + p_data->polling = false; + + if (p_data->destroy || (wslay_event_get_close_sent(p_data->ctx) && wslay_event_get_close_received(p_data->ctx))) { + bool valid = p_data->valid; + _wsl_destroy(&p_data); + return valid; + } + return false; +} + +ssize_t wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data) { + struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; + if (!peer_data->valid) { + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + Ref<StreamPeer> conn = peer_data->conn; + int read = 0; + Error err = conn->get_partial_data(data, len, read); + if (err != OK) { + print_verbose("Websocket get data error: " + itos(err) + ", read (should be 0!): " + itos(read)); + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + if (read == 0) { + wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK); + return -1; + } + return read; +} + +ssize_t wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data) { + struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; + if (!peer_data->valid) { + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + Ref<StreamPeer> conn = peer_data->conn; + int sent = 0; + Error err = conn->put_partial_data(data, len, sent); + if (err != OK) { + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + if (sent == 0) { + wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK); + return -1; + } + return sent; +} + +int wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) { + RandomNumberGenerator rng; + // TODO maybe use crypto in the future? + rng.set_seed(OS::get_singleton()->get_unix_time()); + for (unsigned int i = 0; i < len; i++) { + buf[i] = (uint8_t)rng.randi_range(0, 255); + } + return 0; +} + +void wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { + struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; + if (!peer_data->valid) { + return; + } + WSLPeer *peer = (WSLPeer *)peer_data->peer; + + if (peer->parse_message(arg) != OK) + return; + + if (peer_data->is_server) { + WSLServer *helper = (WSLServer *)peer_data->obj; + helper->_on_peer_packet(peer_data->id); + } else { + WSLClient *helper = (WSLClient *)peer_data->obj; + helper->_on_peer_packet(); + } +} + +wslay_event_callbacks wsl_callbacks = { + wsl_recv_callback, + wsl_send_callback, + wsl_genmask_callback, + NULL, /* on_frame_recv_start_callback */ + NULL, /* on_frame_recv_callback */ + NULL, /* on_frame_recv_end_callback */ + wsl_msg_recv_callback +}; + +Error WSLPeer::parse_message(const wslay_event_on_msg_recv_arg *arg) { + uint8_t is_string = 0; + if (arg->opcode == WSLAY_TEXT_FRAME) { + is_string = 1; + } else if (arg->opcode == WSLAY_CONNECTION_CLOSE) { + close_code = arg->status_code; + size_t len = arg->msg_length; + close_reason = ""; + if (len > 2 /* first 2 bytes = close code */) { + close_reason.parse_utf8((char *)arg->msg + 2, len - 2); + } + if (!wslay_event_get_close_sent(_data->ctx)) { + if (_data->is_server) { + WSLServer *helper = (WSLServer *)_data->obj; + helper->_on_close_request(_data->id, close_code, close_reason); + } else { + WSLClient *helper = (WSLClient *)_data->obj; + helper->_on_close_request(close_code, close_reason); + } + } + return ERR_FILE_EOF; + } else if (arg->opcode != WSLAY_BINARY_FRAME) { + // Ping or pong + return ERR_SKIP; + } + _in_buffer.write_packet(arg->msg, arg->msg_length, &is_string); + return OK; +} + +void WSLPeer::make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size) { + ERR_FAIL_COND(_data != NULL); + ERR_FAIL_COND(p_data == NULL); + + _in_buffer.resize(p_in_pkt_size, p_in_buf_size); + _packet_buffer.resize((1 << MAX(p_in_buf_size, p_out_buf_size))); + + _data = p_data; + _data->peer = this; + _data->valid = true; + _connection = Ref<StreamPeer>(_data->conn); + + if (_data->is_server) + 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)); +} + +void WSLPeer::set_write_mode(WriteMode p_mode) { + write_mode = p_mode; +} + +WSLPeer::WriteMode WSLPeer::get_write_mode() const { + return write_mode; +} + +void WSLPeer::poll() { + if (!_data) + return; + + if (_wsl_poll(_data)) { + _data = NULL; + } +} + +Error WSLPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + struct wslay_event_msg msg; // Should I use fragmented? + msg.opcode = write_mode == WRITE_MODE_TEXT ? WSLAY_TEXT_FRAME : WSLAY_BINARY_FRAME; + msg.msg = p_buffer; + msg.msg_length = p_buffer_size; + + wslay_event_queue_msg(_data->ctx, &msg); + return OK; +} + +Error WSLPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + + r_buffer_size = 0; + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + if (_in_buffer.packets_left() == 0) + return ERR_UNAVAILABLE; + + int read = 0; + PoolVector<uint8_t>::Write rw = _packet_buffer.write(); + _in_buffer.read_packet(rw.ptr(), _packet_buffer.size(), &_is_string, read); + + *r_buffer = rw.ptr(); + r_buffer_size = read; + + return OK; +} + +int WSLPeer::get_available_packet_count() const { + + if (!is_connected_to_host()) + return 0; + + return _in_buffer.packets_left(); +} + +bool WSLPeer::was_string_packet() const { + + return _is_string; +} + +bool WSLPeer::is_connected_to_host() const { + + return _data != NULL; +} + +void WSLPeer::close_now() { + close(1000, ""); + _wsl_destroy(&_data); +} + +void WSLPeer::close(int p_code, String p_reason) { + if (_data && !wslay_event_get_close_sent(_data->ctx)) { + CharString cs = p_reason.utf8(); + wslay_event_queue_close(_data->ctx, p_code, (uint8_t *)cs.ptr(), cs.size()); + wslay_event_send(_data->ctx); + } + + _in_buffer.clear(); + _packet_buffer.resize(0); +} + +IP_Address WSLPeer::get_connected_host() const { + + ERR_FAIL_COND_V(!is_connected_to_host(), IP_Address()); + + IP_Address ip; + return ip; +} + +uint16_t WSLPeer::get_connected_port() const { + + ERR_FAIL_COND_V(!is_connected_to_host(), 0); + + uint16_t port = 0; + return port; +} + +void WSLPeer::invalidate() { + if (_data) + _data->valid = false; +} + +WSLPeer::WSLPeer() { + _data = NULL; + _is_string = 0; + close_code = -1; + write_mode = WRITE_MODE_BINARY; +} + +WSLPeer::~WSLPeer() { + + close(); + invalidate(); + _wsl_destroy(&_data); + _data = NULL; +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_peer.h b/modules/websocket/wsl_peer.h index 6771c13094..d51b304fe1 100644 --- a/modules/websocket/lws_peer.h +++ b/modules/websocket/wsl_peer.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_peer.h */ +/* wsl_peer.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,50 +28,76 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LWSPEER_H -#define LWSPEER_H +#ifndef WSLPEER_H +#define WSLPEER_H #ifndef JAVASCRIPT_ENABLED #include "core/error_list.h" #include "core/io/packet_peer.h" #include "core/ring_buffer.h" -#include "libwebsockets.h" -#include "lws_config.h" #include "packet_buffer.h" #include "websocket_peer.h" +#include "wslay/wslay.h" -class LWSPeer : public WebSocketPeer { +#define WSL_MAX_HEADER_SIZE 4096 - GDCIIMPL(LWSPeer, WebSocketPeer); +class WSLPeer : public WebSocketPeer { + + GDCIIMPL(WSLPeer, WebSocketPeer); + +public: + struct PeerData { + bool polling; + bool destroy; + bool valid; + bool is_server; + void *obj; + void *peer; + Ref<StreamPeer> conn; + int id; + wslay_event_context_ptr ctx; + + PeerData() { + polling = false; + destroy = false; + valid = false; + is_server = false; + id = 1; + ctx = NULL; + obj = NULL; + peer = NULL; + } + }; + + static String compute_key_response(String p_key); + static String generate_key(); private: - int _in_size; + static bool _wsl_poll(struct PeerData *p_data); + static void _wsl_destroy(struct PeerData **p_data); + + Ref<StreamPeer> _connection; + struct PeerData *_data; uint8_t _is_string; // Our packet info is just a boolean (is_string), using uint8_t for it. PacketBuffer<uint8_t> _in_buffer; - PacketBuffer<uint8_t> _out_buffer; PoolVector<uint8_t> _packet_buffer; - struct lws *wsi; WriteMode write_mode; +public: int close_code; String close_reason; - -public: - struct PeerData { - uint32_t peer_id; - bool force_close; - bool clean_close; - }; + void poll(); // Used by client and server. virtual int get_available_packet_count() const; virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); virtual int get_max_packet_size() const { return _packet_buffer.size(); }; + virtual void close_now(); virtual void close(int p_code = 1000, String p_reason = ""); virtual bool is_connected_to_host() const; virtual IP_Address get_connected_host() const; @@ -81,14 +107,12 @@ public: virtual void set_write_mode(WriteMode p_mode); virtual bool was_string_packet() const; - void set_wsi(struct lws *wsi, unsigned int _in_buf_size, unsigned int _in_pkt_size, unsigned int _out_buf_size, unsigned int _out_pkt_size); - Error read_wsi(void *in, size_t len); - Error write_wsi(); - void send_close_status(struct lws *wsi); - String get_close_reason(void *in, size_t len, int &r_code); + void make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size); + Error parse_message(const wslay_event_on_msg_recv_arg *arg); + void invalidate(); - LWSPeer(); - ~LWSPeer(); + WSLPeer(); + ~WSLPeer(); }; #endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp new file mode 100644 index 0000000000..0d09a4d74e --- /dev/null +++ b/modules/websocket/wsl_server.cpp @@ -0,0 +1,302 @@ +/*************************************************************************/ +/* lws_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_ENABLED + +#include "wsl_server.h" +#include "core/os/os.h" +#include "core/project_settings.h" + +WSLServer::PendingPeer::PendingPeer() { + time = 0; + has_request = false; + response_sent = 0; + req_pos = 0; + memset(req_buf, 0, sizeof(req_buf)); +} + +bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) { + Vector<String> psa = String((char *)req_buf).split("\r\n"); + int len = psa.size(); + if (len < 4) { + ERR_EXPLAIN("Not enough response headers."); + ERR_FAIL_V(false); + } + + Vector<String> req = psa[0].split(" ", false); + if (req.size() < 2) { + ERR_EXPLAIN("Invalid protocol or status code."); + ERR_FAIL_V(false); + } + // Wrong protocol + if (req[0] != "GET" || req[2] != "HTTP/1.1") { + ERR_EXPLAIN("Invalid method or HTTP version."); + ERR_FAIL_V(false); + } + + Map<String, String> headers; + for (int i = 1; i < len; i++) { + Vector<String> header = psa[i].split(":", false, 1); + if (header.size() != 2) { + ERR_EXPLAIN("Invalid header -> " + psa[i]); + ERR_FAIL_V(false); + } + String name = header[0].to_lower(); + String value = header[1].strip_edges(); + if (headers.has(name)) + headers[name] += "," + value; + else + headers[name] = value; + } +#define _WLS_CHECK(NAME, VALUE) \ + ERR_EXPLAIN("Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'"); \ + ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false); +#define _WLS_CHECK_EX(NAME) \ + ERR_EXPLAIN("Missing header '" + String(NAME) + "'."); \ + ERR_FAIL_COND_V(!headers.has(NAME), false); + _WLS_CHECK("upgrade", "websocket"); + _WLS_CHECK("sec-websocket-version", "13"); + _WLS_CHECK_EX("sec-websocket-key"); + _WLS_CHECK_EX("connection"); +#undef _WLS_CHECK_EX +#undef _WLS_CHECK + key = headers["sec-websocket-key"]; + if (headers.has("sec-websocket-protocol")) { + Vector<String> protos = headers["sec-websocket-protocol"].split(","); + for (int i = 0; i < protos.size(); i++) { + // Check if we have the given protocol + for (int j = 0; j < p_protocols.size(); j++) { + if (protos[i] != p_protocols[j]) + continue; + protocol = protos[i]; + break; + } + // Found a protocol + if (protocol != "") + break; + } + if (protocol == "") // Invalid protocol(s) requested + return false; + } else if (p_protocols.size() > 0) // No protocol requested, but we need one + return false; + return true; +} + +Error WSLServer::PendingPeer::do_handshake(PoolStringArray p_protocols) { + if (OS::get_singleton()->get_ticks_msec() - time > WSL_SERVER_TIMEOUT) + return ERR_TIMEOUT; + if (!has_request) { + int read = 0; + while (true) { + if (req_pos >= WSL_MAX_HEADER_SIZE) { + // Header is too big + ERR_EXPLAIN("Response headers too big"); + ERR_FAIL_V(ERR_OUT_OF_MEMORY); + } + Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); + if (err != OK) // Got an error + return FAILED; + else if (read != 1) // Busy, wait next poll + return ERR_BUSY; + char *r = (char *)req_buf; + int l = req_pos; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + r[l - 3] = '\0'; + if (!_parse_request(p_protocols)) { + return FAILED; + } + String s = "HTTP/1.1 101 Switching Protocols\r\n"; + s += "Upgrade: websocket\r\n"; + s += "Connection: Upgrade\r\n"; + s += "Sec-WebSocket-Accept: " + WSLPeer::compute_key_response(key) + "\r\n"; + if (protocol != "") + s += "Sec-WebSocket-Protocol: " + protocol + "\r\n"; + s += "\r\n"; + response = s.utf8(); + has_request = true; + break; + } + req_pos += 1; + } + } + if (has_request && response_sent < response.size() - 1) { + int sent = 0; + Error err = connection->put_partial_data((const uint8_t *)response.get_data() + response_sent, response.size() - response_sent - 1, sent); + if (err != OK) { + return err; + } + response_sent += sent; + } + if (response_sent < response.size() - 1) + return ERR_BUSY; + return OK; +} + +Error WSLServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { + ERR_FAIL_COND_V(is_listening(), ERR_ALREADY_IN_USE); + + _is_multiplayer = gd_mp_api; + _protocols = p_protocols; + _server->listen(p_port); + + return OK; +} + +void WSLServer::poll() { + + List<int> remove_ids; + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + Ref<WSLPeer> peer = (WSLPeer *)E->get().ptr(); + peer->poll(); + if (!peer->is_connected_to_host()) { + _on_disconnect(E->key(), peer->close_code != -1); + remove_ids.push_back(E->key()); + } + } + for (List<int>::Element *E = remove_ids.front(); E; E = E->next()) { + _peer_map.erase(E->get()); + } + remove_ids.clear(); + + List<Ref<PendingPeer> > remove_peers; + for (List<Ref<PendingPeer> >::Element *E = _pending.front(); E; E = E->next()) { + Ref<PendingPeer> ppeer = E->get(); + Error err = ppeer->do_handshake(_protocols); + if (err == ERR_BUSY) { + continue; + } else if (err != OK) { + remove_peers.push_back(ppeer); + continue; + } + // Creating new peer + int32_t id = _gen_unique_id(); + + WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); + data->obj = this; + data->conn = ppeer->connection; + data->is_server = true; + data->id = id; + + Ref<WSLPeer> ws_peer = memnew(WSLPeer); + ws_peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); + + _peer_map[id] = ws_peer; + remove_peers.push_back(ppeer); + _on_connect(id, ppeer->protocol); + } + for (List<Ref<PendingPeer> >::Element *E = remove_peers.front(); E; E = E->next()) { + _pending.erase(E->get()); + } + remove_peers.clear(); + + if (!_server->is_listening()) + return; + + while (_server->is_connection_available()) { + Ref<StreamPeer> conn = _server->take_connection(); + if (is_refusing_new_connections()) + continue; // Conn will go out-of-scope and be closed. + + Ref<PendingPeer> peer = memnew(PendingPeer); + peer->connection = conn; + peer->time = OS::get_singleton()->get_ticks_msec(); + _pending.push_back(peer); + } +} + +bool WSLServer::is_listening() const { + return _server->is_listening(); +} + +int WSLServer::get_max_packet_size() const { + return (1 << _out_buf_size) - PROTO_SIZE; +} + +void WSLServer::stop() { + _server->stop(); + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + Ref<WSLPeer> peer = (WSLPeer *)E->get().ptr(); + peer->close_now(); + } + _pending.clear(); + _peer_map.clear(); +} + +bool WSLServer::has_peer(int p_id) const { + return _peer_map.has(p_id); +} + +Ref<WebSocketPeer> WSLServer::get_peer(int p_id) const { + ERR_FAIL_COND_V(!has_peer(p_id), NULL); + return _peer_map[p_id]; +} + +IP_Address WSLServer::get_peer_address(int p_peer_id) const { + ERR_FAIL_COND_V(!has_peer(p_peer_id), IP_Address()); + + return _peer_map[p_peer_id]->get_connected_host(); +} + +int WSLServer::get_peer_port(int p_peer_id) const { + ERR_FAIL_COND_V(!has_peer(p_peer_id), 0); + + return _peer_map[p_peer_id]->get_connected_port(); +} + +void WSLServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { + ERR_FAIL_COND(!has_peer(p_peer_id)); + + get_peer(p_peer_id)->close(p_code, p_reason); +} + +Error WSLServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { + ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); + ERR_FAIL_COND_V(_server->is_listening(), FAILED); + + _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; + _in_pkt_size = nearest_shift(p_in_packets - 1); + _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; + _out_pkt_size = nearest_shift(p_out_packets - 1); + return OK; +} + +WSLServer::WSLServer() { + _in_buf_size = nearest_shift((int)GLOBAL_GET(WSS_IN_BUF) - 1) + 10; + _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_IN_PKT) - 1); + _out_buf_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_BUF) - 1) + 10; + _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_PKT) - 1); + _server.instance(); +} + +WSLServer::~WSLServer() { + stop(); +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_server.h b/modules/websocket/wsl_server.h index b331852d26..2ceb941073 100644 --- a/modules/websocket/lws_server.h +++ b/modules/websocket/wsl_server.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_server.h */ +/* wsl_server.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,29 +28,55 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LWSSERVER_H -#define LWSSERVER_H +#ifndef WSLSERVER_H +#define WSLSERVER_H #ifndef JAVASCRIPT_ENABLED -#include "core/reference.h" -#include "lws_helper.h" -#include "lws_peer.h" #include "websocket_server.h" +#include "wsl_peer.h" -class LWSServer : public WebSocketServer { +#include "core/io/stream_peer_tcp.h" +#include "core/io/tcp_server.h" - GDCIIMPL(LWSServer, WebSocketServer); +#define WSL_SERVER_TIMEOUT 1000 - LWS_HELPER(LWSServer); +class WSLServer : public WebSocketServer { + + GDCIIMPL(WSLServer, WebSocketServer); private: - Map<int, Ref<LWSPeer> > peer_map; + class PendingPeer : public Reference { + + private: + bool _parse_request(const PoolStringArray p_protocols); + + public: + Ref<StreamPeer> connection; + + int time; + uint8_t req_buf[WSL_MAX_HEADER_SIZE]; + int req_pos; + String key; + String protocol; + bool has_request; + CharString response; + int response_sent; + + PendingPeer(); + + Error do_handshake(const PoolStringArray p_protocols); + }; + int _in_buf_size; int _in_pkt_size; int _out_buf_size; int _out_pkt_size; + List<Ref<PendingPeer> > _pending; + Ref<TCP_Server> _server; + PoolStringArray _protocols; + 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); @@ -62,12 +88,12 @@ public: IP_Address get_peer_address(int p_peer_id) const; int get_peer_port(int p_peer_id) const; void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = ""); - virtual void poll() { _lws_poll(); } + virtual void poll(); - LWSServer(); - ~LWSServer(); + WSLServer(); + ~WSLServer(); }; #endif // JAVASCRIPT_ENABLED -#endif // LWSSERVER_H +#endif // WSLSERVER_H diff --git a/modules/xatlas_unwrap/SCsub b/modules/xatlas_unwrap/SCsub index 50e3cb1551..b242fd4673 100644 --- a/modules/xatlas_unwrap/SCsub +++ b/modules/xatlas_unwrap/SCsub @@ -15,10 +15,6 @@ if env['builtin_xatlas']: env_xatlas_unwrap.Prepend(CPPPATH=[thirdparty_dir]) - # upstream uses c++11 - if (not env.msvc): - env_xatlas_unwrap.Append(CXXFLAGS="-std=c++11") - env_thirdparty = env_xatlas_unwrap.Clone() env_thirdparty.disable_warnings() env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp index 903b57f017..c18aa04336 100644 --- a/modules/xatlas_unwrap/register_types.cpp +++ b/modules/xatlas_unwrap/register_types.cpp @@ -39,57 +39,37 @@ extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const flo bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, const int *p_face_materials, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y) { //set up input mesh - xatlas::InputMesh input_mesh; - input_mesh.indexData = malloc(sizeof(int) * p_index_count); + xatlas::MeshDecl input_mesh; + input_mesh.indexData = p_indices; input_mesh.indexCount = p_index_count; - input_mesh.indexFormat = xatlas::IndexFormat::Float; //really xatlas? - input_mesh.faceMaterialData = (uint16_t *)malloc(sizeof(uint16_t) * p_index_count); - - for (int i = 0; i < p_index_count; i++) { - int *index = (int *)input_mesh.indexData; - index[i] = p_indices[i]; - } - for (int i = 0; i < p_index_count / 3; i++) { - uint16_t *mat_index = (uint16_t *)input_mesh.faceMaterialData; - mat_index[i] = p_face_materials[i]; - } + input_mesh.indexFormat = xatlas::IndexFormat::UInt32; input_mesh.vertexCount = p_vertex_count; - input_mesh.vertexPositionData = malloc(sizeof(float) * p_vertex_count * 3); + input_mesh.vertexPositionData = p_vertices; input_mesh.vertexPositionStride = sizeof(float) * 3; - input_mesh.vertexNormalData = malloc(sizeof(float) * p_vertex_count * 3); - input_mesh.vertexNormalStride = sizeof(float) * 3; - - //material is a better hint than this i guess? + input_mesh.vertexNormalData = p_normals; + input_mesh.vertexNormalStride = sizeof(uint32_t) * 3; input_mesh.vertexUvData = NULL; input_mesh.vertexUvStride = 0; - for (int i = 0; i < p_vertex_count * 3; i++) { - float *vertex_ptr = (float *)input_mesh.vertexPositionData; - float *normal_ptr = (float *)input_mesh.vertexNormalData; + xatlas::ChartOptions chart_options; + xatlas::PackOptions pack_options; - vertex_ptr[i] = p_vertices[i]; - normal_ptr[i] = p_normals[i]; - } - - xatlas::CharterOptions chart_options; - xatlas::PackerOptions pack_options; - - pack_options.method = xatlas::PackMethod::TexelArea; - pack_options.texelArea = 1.0 / p_texel_size; - pack_options.quality = 3; + pack_options.maxChartSize = 4096; + pack_options.bruteForce = true; + pack_options.texelsPerUnit = 1.0 / p_texel_size; xatlas::Atlas *atlas = xatlas::Create(); - printf("adding mesh..\n"); - xatlas::AddMeshError err = xatlas::AddMesh(atlas, input_mesh); - ERR_EXPLAINC(xatlas::StringForEnum(err.code)); - ERR_FAIL_COND_V(err.code != xatlas::AddMeshErrorCode::Success, false); + 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); - printf("generate..\n"); - xatlas::Generate(atlas, chart_options, pack_options); + printf("Generate..\n"); + xatlas::Generate(atlas, chart_options, NULL, pack_options); - *r_size_hint_x = xatlas::GetWidth(atlas); - *r_size_hint_y = xatlas::GetHeight(atlas); + *r_size_hint_x = atlas->width; + *r_size_hint_y = atlas->height; float w = *r_size_hint_x; float h = *r_size_hint_y; @@ -98,39 +78,33 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver return false; //could not bake } - const xatlas::OutputMesh *const *output_meshes = xatlas::GetOutputMeshes(atlas); - - const xatlas::OutputMesh *output = output_meshes[0]; + const xatlas::Mesh &output = atlas->meshes[0]; - *r_vertex = (int *)malloc(sizeof(int) * output->vertexCount); - *r_uv = (float *)malloc(sizeof(float) * output->vertexCount * 2); - *r_index = (int *)malloc(sizeof(int) * output->indexCount); + *r_vertex = (int *)malloc(sizeof(int) * output.vertexCount); + *r_uv = (float *)malloc(sizeof(float) * output.vertexCount * 2); + *r_index = (int *)malloc(sizeof(int) * output.indexCount); float max_x = 0; float max_y = 0; - for (uint32_t i = 0; i < output->vertexCount; i++) { - (*r_vertex)[i] = output->vertexArray[i].xref; - (*r_uv)[i * 2 + 0] = output->vertexArray[i].uv[0] / w; - (*r_uv)[i * 2 + 1] = output->vertexArray[i].uv[1] / h; - max_x = MAX(max_x, output->vertexArray[i].uv[0]); - max_y = MAX(max_y, output->vertexArray[i].uv[1]); + for (uint32_t i = 0; i < output.vertexCount; i++) { + (*r_vertex)[i] = output.vertexArray[i].xref; + (*r_uv)[i * 2 + 0] = output.vertexArray[i].uv[0] / w; + (*r_uv)[i * 2 + 1] = output.vertexArray[i].uv[1] / h; + max_x = MAX(max_x, output.vertexArray[i].uv[0]); + max_y = MAX(max_y, output.vertexArray[i].uv[1]); } - printf("final texsize: %f,%f - max %f,%f\n", w, h, max_x, max_y); - *r_vertex_count = output->vertexCount; + printf("Final texture size: %f,%f - max %f,%f\n", w, h, max_x, max_y); + *r_vertex_count = output.vertexCount; - for (uint32_t i = 0; i < output->indexCount; i++) { - (*r_index)[i] = output->indexArray[i]; + for (uint32_t i = 0; i < output.indexCount; i++) { + (*r_index)[i] = output.indexArray[i]; } - *r_index_count = output->indexCount; + *r_index_count = output.indexCount; //xatlas::Destroy(atlas); - free((void *)input_mesh.indexData); - free((void *)input_mesh.vertexPositionData); - free((void *)input_mesh.vertexNormalData); - free((void *)input_mesh.faceMaterialData); - printf("done"); + printf("Done\n"); return true; } |