diff options
Diffstat (limited to 'modules')
417 files changed, 25476 insertions, 8934 deletions
diff --git a/modules/SCsub b/modules/SCsub index 74a5267355..67f5893db4 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -9,7 +9,6 @@ Export('env_modules') env.modules_sources = [ "register_module_types.gen.cpp", ] -Export('env') for x in env.module_list: if (x in env.disabled_modules): @@ -20,7 +19,6 @@ for x in env.module_list: if env.split_modules: env.split_lib("modules", env_lib = env_modules) else: - lib = env_modules.add_library("modules", env.modules_sources) env.Prepend(LIBS=[lib]) diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index 919731b52b..063508a25f 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -42,12 +42,9 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, if (err == OK) { size_t index = 0; - size_t width = - static_cast<size_t>(p_header.bmp_info_header.bmp_width < 0 ? -p_header.bmp_info_header.bmp_width : p_header.bmp_info_header.bmp_width); - size_t height = - static_cast<size_t>(p_header.bmp_info_header.bmp_height < 0 ? -p_header.bmp_info_header.bmp_height : p_header.bmp_info_header.bmp_height); - size_t bits_per_pixel = - static_cast<size_t>(p_header.bmp_info_header.bmp_bit_count); + size_t width = (size_t)p_header.bmp_info_header.bmp_width; + 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 != 0) { err = FAILED; diff --git a/modules/bmp/image_loader_bmp.h b/modules/bmp/image_loader_bmp.h index 3fa7481287..e0e50859fc 100644 --- a/modules/bmp/image_loader_bmp.h +++ b/modules/bmp/image_loader_bmp.h @@ -31,7 +31,7 @@ #ifndef IMAGE_LOADER_BMP_H #define IMAGE_LOADER_BMP_H -#include "io/image_loader.h" +#include "core/io/image_loader.h" class ImageLoaderBMP : public ImageFormatLoader { protected: diff --git a/modules/bullet/SCsub b/modules/bullet/SCsub index d8d0b930a5..11ce18449b 100644 --- a/modules/bullet/SCsub +++ b/modules/bullet/SCsub @@ -68,11 +68,13 @@ if env['builtin_bullet']: , "BulletCollision/CollisionShapes/btEmptyShape.cpp" , "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.cpp" , "BulletCollision/CollisionShapes/btMinkowskiSumShape.cpp" + , "BulletCollision/CollisionShapes/btMiniSDF.cpp" , "BulletCollision/CollisionShapes/btMultimaterialTriangleMeshShape.cpp" , "BulletCollision/CollisionShapes/btMultiSphereShape.cpp" , "BulletCollision/CollisionShapes/btOptimizedBvh.cpp" , "BulletCollision/CollisionShapes/btPolyhedralConvexShape.cpp" , "BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.cpp" + , "BulletCollision/CollisionShapes/btSdfCollisionShape.cpp" , "BulletCollision/CollisionShapes/btShapeHull.cpp" , "BulletCollision/CollisionShapes/btSphereShape.cpp" , "BulletCollision/CollisionShapes/btStaticPlaneShape.cpp" @@ -184,8 +186,12 @@ if env['builtin_bullet']: thirdparty_sources = [thirdparty_dir + file for file in bullet2_src] - env_bullet.add_source_files(env.modules_sources, thirdparty_sources) env_bullet.Append(CPPPATH=[thirdparty_dir]) + env_thirdparty = env_bullet.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + + # Godot source files env_bullet.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/bullet/area_bullet.cpp b/modules/bullet/area_bullet.cpp index bfb452d109..a0486443c2 100644 --- a/modules/bullet/area_bullet.cpp +++ b/modules/bullet/area_bullet.cpp @@ -30,6 +30,7 @@ #include "area_bullet.h" +#include "bullet_physics_server.h" #include "bullet_types_converter.h" #include "bullet_utilities.h" #include "collision_object_bullet.h" @@ -45,7 +46,6 @@ AreaBullet::AreaBullet() : RigidCollisionObjectBullet(CollisionObjectBullet::TYPE_AREA), monitorable(true), - isScratched(false), spOv_mode(PhysicsServer::AREA_SPACE_OVERRIDE_DISABLED), spOv_gravityPoint(false), spOv_gravityPointDistanceScale(0), @@ -54,10 +54,11 @@ AreaBullet::AreaBullet() : spOv_gravityMag(10), spOv_linearDump(0.1), spOv_angularDump(1), - spOv_priority(0) { + spOv_priority(0), + isScratched(false) { btGhost = bulletnew(btGhostObject); - btGhost->setCollisionShape(compoundShape); + reload_shapes(); setupBulletCollisionObject(btGhost); /// Collision objects with a callback still have collision response with dynamic rigid bodies. /// In order to use collision objects as trigger, you have to disable the collision response. @@ -80,7 +81,7 @@ void AreaBullet::dispatch_callbacks() { // Reverse order because I've to remove EXIT objects for (int i = overlappingObjects.size() - 1; 0 <= i; --i) { - OverlappingObjectData &otherObj = overlappingObjects[i]; + OverlappingObjectData &otherObj = overlappingObjects.write[i]; switch (otherObj.state) { case OVERLAP_STATE_ENTER: @@ -93,6 +94,9 @@ void AreaBullet::dispatch_callbacks() { otherObj.object->on_exit_area(this); overlappingObjects.remove(i); // Remove after callback break; + case OVERLAP_STATE_DIRTY: + case OVERLAP_STATE_INSIDE: + break; } } } @@ -162,6 +166,11 @@ bool AreaBullet::is_monitoring() const { return get_godot_object_flags() & GOF_IS_MONITORING_AREA; } +void AreaBullet::main_shape_changed() { + CRASH_COND(!get_main_shape()) + btGhost->setCollisionShape(get_main_shape()); +} + void AreaBullet::reload_body() { if (space) { space->remove_area(this); @@ -199,13 +208,13 @@ void AreaBullet::add_overlap(CollisionObjectBullet *p_otherObject) { void AreaBullet::put_overlap_as_exit(int p_index) { scratch(); - overlappingObjects[p_index].state = OVERLAP_STATE_EXIT; + overlappingObjects.write[p_index].state = OVERLAP_STATE_EXIT; } void AreaBullet::put_overlap_as_inside(int p_index) { // This check is required to be sure this body was inside if (OVERLAP_STATE_DIRTY == overlappingObjects[p_index].state) { - overlappingObjects[p_index].state = OVERLAP_STATE_INSIDE; + overlappingObjects.write[p_index].state = OVERLAP_STATE_INSIDE; } } @@ -236,7 +245,7 @@ void AreaBullet::set_param(PhysicsServer::AreaParameter p_param, const Variant & set_spOv_gravityPointAttenuation(p_value); break; default: - print_line("The Bullet areas doesn't suppot this param: " + itos(p_param)); + WARN_PRINTS("Area doesn't support this parameter in the Bullet backend: " + itos(p_param)); } } @@ -259,7 +268,7 @@ Variant AreaBullet::get_param(PhysicsServer::AreaParameter p_param) const { case PhysicsServer::AREA_PARAM_GRAVITY_POINT_ATTENUATION: return spOv_gravityPointAttenuation; default: - print_line("The Bullet areas doesn't suppot this param: " + itos(p_param)); + WARN_PRINTS("Area doesn't support this parameter in the Bullet backend: " + itos(p_param)); return Variant(); } } diff --git a/modules/bullet/area_bullet.h b/modules/bullet/area_bullet.h index b2046c684e..1c5962cfe3 100644 --- a/modules/bullet/area_bullet.h +++ b/modules/bullet/area_bullet.h @@ -142,6 +142,7 @@ public: _FORCE_INLINE_ void set_spOv_priority(int p_priority) { spOv_priority = p_priority; } _FORCE_INLINE_ int get_spOv_priority() { return spOv_priority; } + virtual void main_shape_changed(); virtual void reload_body(); virtual void set_space(SpaceBullet *p_space); @@ -156,6 +157,7 @@ public: virtual void on_collision_filters_change(); virtual void on_collision_checker_start() {} + virtual void on_collision_checker_end() { isTransformChanged = false; } void add_overlap(CollisionObjectBullet *p_otherObject); void put_overlap_as_exit(int p_index); diff --git a/modules/bullet/btRayShape.cpp b/modules/bullet/btRayShape.cpp index 8707096038..6cc63d79ce 100644 --- a/modules/bullet/btRayShape.cpp +++ b/modules/bullet/btRayShape.cpp @@ -30,7 +30,7 @@ #include "btRayShape.h" -#include "math/math_funcs.h" +#include "core/math/math_funcs.h" #include <LinearMath/btAabbUtil2.h> diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp index 6246a295ec..7bc731e75e 100644 --- a/modules/bullet/bullet_physics_server.cpp +++ b/modules/bullet/bullet_physics_server.cpp @@ -31,8 +31,8 @@ #include "bullet_physics_server.h" #include "bullet_utilities.h" -#include "class_db.h" #include "cone_twist_joint_bullet.h" +#include "core/class_db.h" #include "core/error_macros.h" #include "core/ustring.h" #include "generic_6dof_joint_bullet.h" @@ -74,12 +74,6 @@ body->get_space()->add_constraint(joint, joint->is_disabled_collisions_between_bodies()); // <--------------- Joint creation asserts -btEmptyShape *BulletPhysicsServer::emptyShape(ShapeBullet::create_shape_empty()); - -btEmptyShape *BulletPhysicsServer::get_empty_shape() { - return emptyShape; -} - void BulletPhysicsServer::_bind_methods() { //ClassDB::bind_method(D_METHOD("DoTest"), &BulletPhysicsServer::DoTest); } @@ -89,9 +83,7 @@ BulletPhysicsServer::BulletPhysicsServer() : active(true), active_spaces_count(0) {} -BulletPhysicsServer::~BulletPhysicsServer() { - bulletdelete(emptyShape); -} +BulletPhysicsServer::~BulletPhysicsServer() {} RID BulletPhysicsServer::shape_create(ShapeType p_shape) { ShapeBullet *shape = NULL; @@ -113,6 +105,10 @@ RID BulletPhysicsServer::shape_create(ShapeType p_shape) { shape = bulletnew(CapsuleShapeBullet); } break; + case SHAPE_CYLINDER: { + + shape = bulletnew(CylinderShapeBullet); + } break; case SHAPE_CONVEX_POLYGON: { shape = bulletnew(ConvexPolygonShapeBullet); @@ -159,13 +155,25 @@ Variant BulletPhysicsServer::shape_get_data(RID p_shape) const { return shape->get_data(); } +void BulletPhysicsServer::shape_set_margin(RID p_shape, real_t p_margin) { + ShapeBullet *shape = shape_owner.get(p_shape); + ERR_FAIL_COND(!shape); + shape->set_margin(p_margin); +} + +real_t BulletPhysicsServer::shape_get_margin(RID p_shape) const { + ShapeBullet *shape = shape_owner.get(p_shape); + ERR_FAIL_COND_V(!shape, 0.0); + return shape->get_margin(); +} + real_t BulletPhysicsServer::shape_get_custom_solver_bias(RID p_shape) const { //WARN_PRINT("Bias not supported by Bullet physics engine"); return 0.; } RID BulletPhysicsServer::space_create() { - SpaceBullet *space = bulletnew(SpaceBullet(false)); + SpaceBullet *space = bulletnew(SpaceBullet); CreateThenReturnRID(space_owner, space); } @@ -322,7 +330,7 @@ Transform BulletPhysicsServer::area_get_shape_transform(RID p_area, int p_shape_ void BulletPhysicsServer::area_remove_shape(RID p_area, int p_shape_idx) { AreaBullet *area = area_owner.get(p_area); ERR_FAIL_COND(!area); - return area->remove_shape(p_shape_idx); + return area->remove_shape_full(p_shape_idx); } void BulletPhysicsServer::area_clear_shapes(RID p_area) { @@ -330,7 +338,7 @@ void BulletPhysicsServer::area_clear_shapes(RID p_area) { ERR_FAIL_COND(!area); for (int i = area->get_shape_count(); 0 < i; --i) - area->remove_shape(0); + area->remove_shape_full(0); } void BulletPhysicsServer::area_set_shape_disabled(RID p_area, int p_shape_idx, bool p_disabled) { @@ -551,7 +559,7 @@ void BulletPhysicsServer::body_remove_shape(RID p_body, int p_shape_idx) { RigidBodyBullet *body = rigid_body_owner.get(p_body); ERR_FAIL_COND(!body); - body->remove_shape(p_shape_idx); + body->remove_shape_full(p_shape_idx); } void BulletPhysicsServer::body_clear_shapes(RID p_body) { @@ -563,9 +571,6 @@ void BulletPhysicsServer::body_clear_shapes(RID p_body) { void BulletPhysicsServer::body_attach_object_instance_id(RID p_body, uint32_t p_ID) { CollisionObjectBullet *body = get_collisin_object(p_body); - if (!body) { - body = soft_body_owner.get(p_body); - } ERR_FAIL_COND(!body); body->set_instance_id(p_ID); @@ -706,6 +711,34 @@ Vector3 BulletPhysicsServer::body_get_applied_torque(RID p_body) const { return body->get_applied_torque(); } +void BulletPhysicsServer::body_add_central_force(RID p_body, const Vector3 &p_force) { + RigidBodyBullet *body = rigid_body_owner.get(p_body); + ERR_FAIL_COND(!body); + + body->apply_central_force(p_force); +} + +void BulletPhysicsServer::body_add_force(RID p_body, const Vector3 &p_force, const Vector3 &p_pos) { + RigidBodyBullet *body = rigid_body_owner.get(p_body); + ERR_FAIL_COND(!body); + + body->apply_force(p_force, p_pos); +} + +void BulletPhysicsServer::body_add_torque(RID p_body, const Vector3 &p_torque) { + RigidBodyBullet *body = rigid_body_owner.get(p_body); + ERR_FAIL_COND(!body); + + body->apply_torque(p_torque); +} + +void BulletPhysicsServer::body_apply_central_impulse(RID p_body, const Vector3 &p_impulse) { + RigidBodyBullet *body = rigid_body_owner.get(p_body); + ERR_FAIL_COND(!body); + + body->apply_central_impulse(p_impulse); +} + void BulletPhysicsServer::body_apply_impulse(RID p_body, const Vector3 &p_pos, const Vector3 &p_impulse) { RigidBodyBullet *body = rigid_body_owner.get(p_body); ERR_FAIL_COND(!body); @@ -832,12 +865,20 @@ PhysicsDirectBodyState *BulletPhysicsServer::body_get_direct_state(RID p_body) { return BulletPhysicsDirectBodyState::get_singleton(body); } -bool BulletPhysicsServer::body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result) { +bool BulletPhysicsServer::body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result, bool p_exclude_raycast_shapes) { RigidBodyBullet *body = rigid_body_owner.get(p_body); ERR_FAIL_COND_V(!body, false); ERR_FAIL_COND_V(!body->get_space(), false); - return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, r_result); + return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, r_result, p_exclude_raycast_shapes); +} + +int BulletPhysicsServer::body_test_ray_separation(RID p_body, const Transform &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, float p_margin) { + RigidBodyBullet *body = rigid_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0); + ERR_FAIL_COND_V(!body->get_space(), 0); + + return body->get_space()->test_ray_separation(body, p_transform, p_infinite_inertia, r_recover_motion, r_results, p_result_max, p_margin); } RID BulletPhysicsServer::soft_body_create(bool p_init_sleeping) { @@ -849,6 +890,13 @@ RID BulletPhysicsServer::soft_body_create(bool p_init_sleeping) { CreateThenReturnRID(soft_body_owner, body); } +void BulletPhysicsServer::soft_body_update_visual_server(RID p_body, class SoftBodyVisualServerHandler *p_visual_server_handler) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + + body->update_visual_server(p_visual_server_handler); +} + void BulletPhysicsServer::soft_body_set_space(RID p_body, RID p_space) { SoftBodyBullet *body = soft_body_owner.get(p_body); ERR_FAIL_COND(!body); @@ -875,11 +923,11 @@ RID BulletPhysicsServer::soft_body_get_space(RID p_body) const { return space->get_self(); } -void BulletPhysicsServer::soft_body_set_trimesh_body_shape(RID p_body, PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num) { +void BulletPhysicsServer::soft_body_set_mesh(RID p_body, const REF &p_mesh) { SoftBodyBullet *body = soft_body_owner.get(p_body); ERR_FAIL_COND(!body); - body->set_trimesh_body_shape(p_indices, p_vertices, p_triangles_num); + body->set_soft_mesh(p_mesh); } void BulletPhysicsServer::soft_body_set_collision_layer(RID p_body, uint32_t p_layer) { @@ -945,11 +993,13 @@ void BulletPhysicsServer::soft_body_get_collision_exceptions(RID p_body, List<RI } void BulletPhysicsServer::soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) { - print_line("TODO MUST BE IMPLEMENTED"); + // FIXME: Must be implemented. + WARN_PRINT("soft_body_state is not implemented yet in Bullet backend."); } Variant BulletPhysicsServer::soft_body_get_state(RID p_body, BodyState p_state) const { - print_line("TODO MUST BE IMPLEMENTED"); + // FIXME: Must be implemented. + WARN_PRINT("soft_body_state is not implemented yet in Bullet backend."); return Variant(); } @@ -957,14 +1007,16 @@ void BulletPhysicsServer::soft_body_set_transform(RID p_body, const Transform &p SoftBodyBullet *body = soft_body_owner.get(p_body); ERR_FAIL_COND(!body); - body->set_transform(p_transform); + body->set_soft_transform(p_transform); } -Transform BulletPhysicsServer::soft_body_get_transform(RID p_body) const { +Vector3 BulletPhysicsServer::soft_body_get_vertex_position(RID p_body, int vertex_index) const { const SoftBodyBullet *body = soft_body_owner.get(p_body); - ERR_FAIL_COND_V(!body, Transform()); + Vector3 pos; + ERR_FAIL_COND_V(!body, pos); - return body->get_transform(); + body->get_node_position(vertex_index, pos); + return pos; } void BulletPhysicsServer::soft_body_set_ray_pickable(RID p_body, bool p_enable) { @@ -979,6 +1031,154 @@ bool BulletPhysicsServer::soft_body_is_ray_pickable(RID p_body) const { return body->is_ray_pickable(); } +void BulletPhysicsServer::soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_simulation_precision(p_simulation_precision); +} + +int BulletPhysicsServer::soft_body_get_simulation_precision(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_simulation_precision(); +} + +void BulletPhysicsServer::soft_body_set_total_mass(RID p_body, real_t p_total_mass) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_total_mass(p_total_mass); +} + +real_t BulletPhysicsServer::soft_body_get_total_mass(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_total_mass(); +} + +void BulletPhysicsServer::soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_linear_stiffness(p_stiffness); +} + +real_t BulletPhysicsServer::soft_body_get_linear_stiffness(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_linear_stiffness(); +} + +void BulletPhysicsServer::soft_body_set_areaAngular_stiffness(RID p_body, real_t p_stiffness) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_areaAngular_stiffness(p_stiffness); +} + +real_t BulletPhysicsServer::soft_body_get_areaAngular_stiffness(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_areaAngular_stiffness(); +} + +void BulletPhysicsServer::soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_volume_stiffness(p_stiffness); +} + +real_t BulletPhysicsServer::soft_body_get_volume_stiffness(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_volume_stiffness(); +} + +void BulletPhysicsServer::soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_pressure_coefficient(p_pressure_coefficient); +} + +real_t BulletPhysicsServer::soft_body_get_pressure_coefficient(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_pressure_coefficient(); +} + +void BulletPhysicsServer::soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + return body->set_pose_matching_coefficient(p_pose_matching_coefficient); +} + +real_t BulletPhysicsServer::soft_body_get_pose_matching_coefficient(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_pose_matching_coefficient(); +} + +void BulletPhysicsServer::soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_damping_coefficient(p_damping_coefficient); +} + +real_t BulletPhysicsServer::soft_body_get_damping_coefficient(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_damping_coefficient(); +} + +void BulletPhysicsServer::soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_drag_coefficient(p_drag_coefficient); +} + +real_t BulletPhysicsServer::soft_body_get_drag_coefficient(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_drag_coefficient(); +} + +void BulletPhysicsServer::soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_node_position(p_point_index, p_global_position); +} + +Vector3 BulletPhysicsServer::soft_body_get_point_global_position(RID p_body, int p_point_index) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, Vector3(0., 0., 0.)); + Vector3 pos; + body->get_node_position(p_point_index, pos); + return pos; +} + +Vector3 BulletPhysicsServer::soft_body_get_point_offset(RID p_body, int p_point_index) const { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, Vector3()); + Vector3 res; + body->get_node_offset(p_point_index, res); + return res; +} + +void BulletPhysicsServer::soft_body_remove_all_pinned_points(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->reset_all_node_mass(); +} + +void BulletPhysicsServer::soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_node_mass(p_point_index, p_pin ? 0 : 1); +} + +bool BulletPhysicsServer::soft_body_is_point_pinned(RID p_body, int p_point_index) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_node_mass(p_point_index); +} + PhysicsServer::JointType BulletPhysicsServer::joint_get_type(RID p_joint) const { JointBullet *joint = joint_owner.get(p_joint); ERR_FAIL_COND_V(!joint, JOINT_PIN); @@ -1233,7 +1433,7 @@ RID BulletPhysicsServer::joint_create_generic_6dof(RID p_body_A, const Transform ERR_FAIL_COND_V(body_A == body_B, RID()); - JointBullet *joint = bulletnew(Generic6DOFJointBullet(body_A, body_B, p_local_frame_A, p_local_frame_B, true)); + JointBullet *joint = bulletnew(Generic6DOFJointBullet(body_A, body_B, p_local_frame_A, p_local_frame_B)); AddJointToSpace(body_A, joint); CreateThenReturnRID(joint_owner, joint); @@ -1271,6 +1471,22 @@ bool BulletPhysicsServer::generic_6dof_joint_get_flag(RID p_joint, Vector3::Axis return generic_6dof_joint->get_flag(p_axis, p_flag); } +void BulletPhysicsServer::generic_6dof_joint_set_precision(RID p_joint, int p_precision) { + JointBullet *joint = joint_owner.get(p_joint); + ERR_FAIL_COND(!joint); + ERR_FAIL_COND(joint->get_type() != JOINT_6DOF); + Generic6DOFJointBullet *generic_6dof_joint = static_cast<Generic6DOFJointBullet *>(joint); + generic_6dof_joint->set_precision(p_precision); +} + +int BulletPhysicsServer::generic_6dof_joint_get_precision(RID p_joint) { + JointBullet *joint = joint_owner.get(p_joint); + ERR_FAIL_COND_V(!joint, 0); + ERR_FAIL_COND_V(joint->get_type() != JOINT_6DOF, 0); + Generic6DOFJointBullet *generic_6dof_joint = static_cast<Generic6DOFJointBullet *>(joint); + return generic_6dof_joint->get_precision(); +} + void BulletPhysicsServer::free(RID p_rid) { if (shape_owner.owns(p_rid)) { @@ -1278,7 +1494,7 @@ void BulletPhysicsServer::free(RID p_rid) { // Notify the shape is configured for (Map<ShapeOwnerBullet *, int>::Element *element = shape->get_owners().front(); element; element = element->next()) { - static_cast<ShapeOwnerBullet *>(element->key())->remove_shape(shape); + static_cast<ShapeOwnerBullet *>(element->key())->remove_shape_full(shape); } shape_owner.free(p_rid); @@ -1289,7 +1505,7 @@ void BulletPhysicsServer::free(RID p_rid) { body->set_space(NULL); - body->remove_all_shapes(true); + body->remove_all_shapes(true, true); rigid_body_owner.free(p_rid); bulletdelete(body); @@ -1309,7 +1525,7 @@ void BulletPhysicsServer::free(RID p_rid) { area->set_space(NULL); - area->remove_all_shapes(true); + area->remove_all_shapes(true, true); area_owner.free(p_rid); bulletdelete(area); diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h index e931915bba..0cea3f5ba6 100644 --- a/modules/bullet/bullet_physics_server.h +++ b/modules/bullet/bullet_physics_server.h @@ -32,8 +32,8 @@ #define BULLET_PHYSICS_SERVER_H #include "area_bullet.h" +#include "core/rid.h" #include "joint_bullet.h" -#include "rid.h" #include "rigid_body_bullet.h" #include "servers/physics_server.h" #include "shape_bullet.h" @@ -60,13 +60,6 @@ class BulletPhysicsServer : public PhysicsServer { mutable RID_Owner<SoftBodyBullet> soft_body_owner; mutable RID_Owner<JointBullet> joint_owner; -private: - /// This is used when a collision shape is not active, so the bullet compound shapes index are always sync with godot index - static btEmptyShape *emptyShape; - -public: - static btEmptyShape *get_empty_shape(); - protected: static void _bind_methods(); @@ -99,6 +92,9 @@ public: virtual ShapeType shape_get_type(RID p_shape) const; virtual Variant shape_get_data(RID p_shape) const; + virtual void shape_set_margin(RID p_shape, real_t p_margin); + virtual real_t shape_get_margin(RID p_shape) const; + /// Not supported virtual void shape_set_custom_solver_bias(RID p_shape, real_t p_bias); /// Not supported @@ -225,6 +221,11 @@ public: virtual void body_set_applied_torque(RID p_body, const Vector3 &p_torque); virtual Vector3 body_get_applied_torque(RID p_body) const; + virtual void body_add_central_force(RID p_body, const Vector3 &p_force); + virtual void body_add_force(RID p_body, const Vector3 &p_force, const Vector3 &p_pos); + virtual void body_add_torque(RID p_body, const Vector3 &p_torque); + + virtual void body_apply_central_impulse(RID p_body, const Vector3 &p_impulse); virtual void body_apply_impulse(RID p_body, const Vector3 &p_pos, const Vector3 &p_impulse); virtual void body_apply_torque_impulse(RID p_body, const Vector3 &p_impulse); virtual void body_set_axis_velocity(RID p_body, const Vector3 &p_axis_velocity); @@ -253,16 +254,19 @@ public: // this function only works on physics process, errors and returns null otherwise virtual PhysicsDirectBodyState *body_get_direct_state(RID p_body); - virtual bool body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result = NULL); + virtual bool body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result = NULL, bool p_exclude_raycast_shapes = true); + virtual int body_test_ray_separation(RID p_body, const Transform &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, float p_margin = 0.001); /* SOFT BODY API */ virtual RID soft_body_create(bool p_init_sleeping = false); + virtual void soft_body_update_visual_server(RID p_body, class SoftBodyVisualServerHandler *p_visual_server_handler); + virtual void soft_body_set_space(RID p_body, RID p_space); virtual RID soft_body_get_space(RID p_body) const; - virtual void soft_body_set_trimesh_body_shape(RID p_body, PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num); + virtual void soft_body_set_mesh(RID p_body, const REF &p_mesh); virtual void soft_body_set_collision_layer(RID p_body, uint32_t p_layer); virtual uint32_t soft_body_get_collision_layer(RID p_body) const; @@ -277,12 +281,49 @@ public: virtual void soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant); virtual Variant soft_body_get_state(RID p_body, BodyState p_state) const; + /// Special function. This function has bad performance virtual void soft_body_set_transform(RID p_body, const Transform &p_transform); - virtual Transform soft_body_get_transform(RID p_body) const; + virtual Vector3 soft_body_get_vertex_position(RID p_body, int vertex_index) const; virtual void soft_body_set_ray_pickable(RID p_body, bool p_enable); virtual bool soft_body_is_ray_pickable(RID p_body) const; + virtual void soft_body_set_simulation_precision(RID p_body, int p_simulation_precision); + virtual int soft_body_get_simulation_precision(RID p_body); + + virtual void soft_body_set_total_mass(RID p_body, real_t p_total_mass); + virtual real_t soft_body_get_total_mass(RID p_body); + + virtual void soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness); + virtual real_t soft_body_get_linear_stiffness(RID p_body); + + virtual void soft_body_set_areaAngular_stiffness(RID p_body, real_t p_stiffness); + virtual real_t soft_body_get_areaAngular_stiffness(RID p_body); + + virtual void soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness); + virtual real_t soft_body_get_volume_stiffness(RID p_body); + + virtual void soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient); + virtual real_t soft_body_get_pressure_coefficient(RID p_body); + + virtual void soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient); + virtual real_t soft_body_get_pose_matching_coefficient(RID p_body); + + virtual void soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient); + virtual real_t soft_body_get_damping_coefficient(RID p_body); + + virtual void soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient); + virtual real_t soft_body_get_drag_coefficient(RID p_body); + + virtual void soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position); + virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index); + + virtual Vector3 soft_body_get_point_offset(RID p_body, int p_point_index) const; + + virtual void soft_body_remove_all_pinned_points(RID p_body); + virtual void soft_body_pin_point(RID p_body, int p_point_index, bool p_pin); + virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index); + /* JOINT API */ virtual JointType joint_get_type(RID p_joint) const; @@ -334,6 +375,9 @@ public: virtual void generic_6dof_joint_set_flag(RID p_joint, Vector3::Axis p_axis, G6DOFJointAxisFlag p_flag, bool p_enable); virtual bool generic_6dof_joint_get_flag(RID p_joint, Vector3::Axis p_axis, G6DOFJointAxisFlag p_flag); + virtual void generic_6dof_joint_set_precision(RID p_joint, int precision); + virtual int generic_6dof_joint_get_precision(RID p_joint); + /* MISC */ virtual void free(RID p_rid); @@ -356,6 +400,8 @@ public: virtual void flush_queries(); virtual void finish(); + virtual bool is_flushing_queries() const { return false; } + virtual int get_process_info(ProcessInfo p_info); CollisionObjectBullet *get_collisin_object(RID p_object) const; diff --git a/modules/bullet/bullet_types_converter.cpp b/modules/bullet/bullet_types_converter.cpp index a0fe598227..f9b7126173 100644 --- a/modules/bullet/bullet_types_converter.cpp +++ b/modules/bullet/bullet_types_converter.cpp @@ -28,8 +28,6 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#pragma once - #include "bullet_types_converter.h" /** diff --git a/modules/bullet/bullet_utilities.h b/modules/bullet/bullet_utilities.h index 2841dfbe69..553c1d0384 100644 --- a/modules/bullet/bullet_utilities.h +++ b/modules/bullet/bullet_utilities.h @@ -35,13 +35,12 @@ @author AndreaCatania */ -#pragma once - #define bulletnew(cl) \ new cl #define bulletdelete(cl) \ - delete cl; \ - cl = NULL; - + { \ + delete cl; \ + cl = NULL; \ + } #endif diff --git a/modules/bullet/collision_object_bullet.cpp b/modules/bullet/collision_object_bullet.cpp index 57e4db708e..441fa7c8af 100644 --- a/modules/bullet/collision_object_bullet.cpp +++ b/modules/bullet/collision_object_bullet.cpp @@ -43,8 +43,7 @@ @author AndreaCatania */ -#define enableDynamicAabbTree true -#define initialChildCapacity 1 +#define enableDynamicAabbTree false CollisionObjectBullet::ShapeWrapper::~ShapeWrapper() {} @@ -53,19 +52,30 @@ void CollisionObjectBullet::ShapeWrapper::set_transform(const Transform &p_trans G_TO_B(p_transform, transform); UNSCALE_BT_BASIS(transform); } + void CollisionObjectBullet::ShapeWrapper::set_transform(const btTransform &p_transform) { transform = p_transform; } +void CollisionObjectBullet::ShapeWrapper::claim_bt_shape(const btVector3 &body_scale) { + if (!bt_shape) { + if (active) + bt_shape = shape->create_bt_shape(scale * body_scale); + else + bt_shape = ShapeBullet::create_shape_empty(); + } +} + CollisionObjectBullet::CollisionObjectBullet(Type p_type) : RIDBullet(), - space(NULL), type(p_type), collisionsEnabled(true), m_isStatic(false), bt_collision_object(NULL), body_scale(1., 1., 1.), - force_shape_reset(false) {} + force_shape_reset(false), + space(NULL), + isTransformChanged(false) {} CollisionObjectBullet::~CollisionObjectBullet() { // Remove all overlapping, notify is not required since godot take care of it @@ -83,7 +93,7 @@ bool equal(real_t first, real_t second) { void CollisionObjectBullet::set_body_scale(const Vector3 &p_new_scale) { if (!equal(p_new_scale[0], body_scale[0]) || !equal(p_new_scale[1], body_scale[1]) || !equal(p_new_scale[2], body_scale[2])) { body_scale = p_new_scale; - on_body_scale_changed(); + body_scale_changed(); } } @@ -93,7 +103,7 @@ btVector3 CollisionObjectBullet::get_bt_body_scale() const { return s; } -void CollisionObjectBullet::on_body_scale_changed() { +void CollisionObjectBullet::body_scale_changed() { force_shape_reset = true; } @@ -107,10 +117,13 @@ void CollisionObjectBullet::setupBulletCollisionObject(btCollisionObject *p_coll bt_collision_object->setUserIndex(type); // Force the enabling of collision and avoid problems set_collision_enabled(collisionsEnabled); + p_collisionObject->setCollisionFlags(p_collisionObject->getCollisionFlags() | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); } void CollisionObjectBullet::add_collision_exception(const CollisionObjectBullet *p_ignoreCollisionObject) { exceptions.insert(p_ignoreCollisionObject->get_self()); + if (!bt_collision_object) + return; bt_collision_object->setIgnoreCollisionCheck(p_ignoreCollisionObject->bt_collision_object, true); if (space) space->get_broadphase()->getOverlappingPairCache()->cleanProxyFromPairs(bt_collision_object->getBroadphaseHandle(), space->get_dispatcher()); @@ -176,66 +189,65 @@ Transform CollisionObjectBullet::get_transform() const { void CollisionObjectBullet::set_transform__bullet(const btTransform &p_global_transform) { bt_collision_object->setWorldTransform(p_global_transform); + notify_transform_changed(); } const btTransform &CollisionObjectBullet::get_transform__bullet() const { return bt_collision_object->getWorldTransform(); } +void CollisionObjectBullet::notify_transform_changed() { + isTransformChanged = true; +} + RigidCollisionObjectBullet::RigidCollisionObjectBullet(Type p_type) : CollisionObjectBullet(p_type), - compoundShape(bulletnew(btCompoundShape(enableDynamicAabbTree, initialChildCapacity))) { + mainShape(NULL) { } RigidCollisionObjectBullet::~RigidCollisionObjectBullet() { - remove_all_shapes(true); - bt_collision_object->setCollisionShape(NULL); - bulletdelete(compoundShape); -} - -/* Not used -void RigidCollisionObjectBullet::_internal_replaceShape(btCollisionShape *p_old_shape, btCollisionShape *p_new_shape) { - bool at_least_one_was_changed = false; - btTransform old_transf; - // Inverse because I need remove the shapes - // Fetch all shapes to be sure to remove all shapes - for (int i = compoundShape->getNumChildShapes() - 1; 0 <= i; --i) { - if (compoundShape->getChildShape(i) == p_old_shape) { - - old_transf = compoundShape->getChildTransform(i); - compoundShape->removeChildShapeByIndex(i); - compoundShape->addChildShape(old_transf, p_new_shape); - at_least_one_was_changed = true; - } - } - - if (at_least_one_was_changed) { - on_shapes_changed(); + remove_all_shapes(true, true); + if (mainShape && mainShape->isCompound()) { + bulletdelete(mainShape); } -}*/ +} void RigidCollisionObjectBullet::add_shape(ShapeBullet *p_shape, const Transform &p_transform) { shapes.push_back(ShapeWrapper(p_shape, p_transform, true)); p_shape->add_owner(this); - on_shapes_changed(); + reload_shapes(); } void RigidCollisionObjectBullet::set_shape(int p_index, ShapeBullet *p_shape) { - ShapeWrapper &shp = shapes[p_index]; + ShapeWrapper &shp = shapes.write[p_index]; shp.shape->remove_owner(this); p_shape->add_owner(this); shp.shape = p_shape; - on_shapes_changed(); + reload_shapes(); } -void RigidCollisionObjectBullet::set_shape_transform(int p_index, const Transform &p_transform) { - ERR_FAIL_INDEX(p_index, get_shape_count()); +int RigidCollisionObjectBullet::get_shape_count() const { + return shapes.size(); +} + +ShapeBullet *RigidCollisionObjectBullet::get_shape(int p_index) const { + return shapes[p_index].shape; +} - shapes[p_index].set_transform(p_transform); - on_shape_changed(shapes[p_index].shape); +btCollisionShape *RigidCollisionObjectBullet::get_bt_shape(int p_index) const { + return shapes[p_index].bt_shape; } -void RigidCollisionObjectBullet::remove_shape(ShapeBullet *p_shape) { +int RigidCollisionObjectBullet::find_shape(ShapeBullet *p_shape) const { + const int size = shapes.size(); + for (int i = 0; i < size; ++i) { + if (shapes[i].shape == p_shape) + return i; + } + return -1; +} + +void RigidCollisionObjectBullet::remove_shape_full(ShapeBullet *p_shape) { // Remove the shape, all the times it appears // Reverse order required for delete. for (int i = shapes.size() - 1; 0 <= i; --i) { @@ -244,35 +256,36 @@ void RigidCollisionObjectBullet::remove_shape(ShapeBullet *p_shape) { shapes.remove(i); } } - on_shapes_changed(); + reload_shapes(); } -void RigidCollisionObjectBullet::remove_shape(int p_index) { +void RigidCollisionObjectBullet::remove_shape_full(int p_index) { ERR_FAIL_INDEX(p_index, get_shape_count()); internal_shape_destroy(p_index); shapes.remove(p_index); - on_shapes_changed(); + reload_shapes(); } -void RigidCollisionObjectBullet::remove_all_shapes(bool p_permanentlyFromThisBody) { +void RigidCollisionObjectBullet::remove_all_shapes(bool p_permanentlyFromThisBody, bool p_force_not_reload) { // Reverse order required for delete. for (int i = shapes.size() - 1; 0 <= i; --i) { internal_shape_destroy(i, p_permanentlyFromThisBody); } shapes.clear(); - on_shapes_changed(); + if (!p_force_not_reload) + reload_shapes(); } -int RigidCollisionObjectBullet::get_shape_count() const { - return shapes.size(); -} +void RigidCollisionObjectBullet::set_shape_transform(int p_index, const Transform &p_transform) { + ERR_FAIL_INDEX(p_index, get_shape_count()); -ShapeBullet *RigidCollisionObjectBullet::get_shape(int p_index) const { - return shapes[p_index].shape; + shapes.write[p_index].set_transform(p_transform); + // Note, enableDynamicAabbTree is false because on transform change compound is destroyed + reload_shapes(); } -btCollisionShape *RigidCollisionObjectBullet::get_bt_shape(int p_index) const { - return shapes[p_index].bt_shape; +const btTransform &RigidCollisionObjectBullet::get_bt_shape_transform(int p_index) const { + return shapes[p_index].transform; } Transform RigidCollisionObjectBullet::get_shape_transform(int p_index) const { @@ -281,72 +294,84 @@ Transform RigidCollisionObjectBullet::get_shape_transform(int p_index) const { return trs; } -void RigidCollisionObjectBullet::on_shape_changed(const ShapeBullet *const p_shape) { - const int size = shapes.size(); - for (int i = 0; i < size; ++i) { - if (shapes[i].shape == p_shape) { - bulletdelete(shapes[i].bt_shape); - } +void RigidCollisionObjectBullet::set_shape_disabled(int p_index, bool p_disabled) { + shapes.write[p_index].active = !p_disabled; + shape_changed(p_index); +} + +bool RigidCollisionObjectBullet::is_shape_disabled(int p_index) { + return !shapes[p_index].active; +} + +void RigidCollisionObjectBullet::shape_changed(int p_shape_index) { + ShapeWrapper &shp = shapes.write[p_shape_index]; + if (shp.bt_shape == mainShape) { + mainShape = NULL; } - on_shapes_changed(); + bulletdelete(shp.bt_shape); + reload_shapes(); } -void RigidCollisionObjectBullet::on_shapes_changed() { - int i; +void RigidCollisionObjectBullet::reload_shapes() { - // Remove all shapes, reverse order for performance reason (Array resize) - for (i = compoundShape->getNumChildShapes() - 1; 0 <= i; --i) { - compoundShape->removeChildShapeByIndex(i); + if (mainShape && mainShape->isCompound()) { + // Destroy compound + bulletdelete(mainShape); } + mainShape = NULL; + ShapeWrapper *shpWrapper; - const int shapes_size = shapes.size(); + const int shape_count = shapes.size(); // Reset shape if required if (force_shape_reset) { - for (i = 0; i < shapes_size; ++i) { - shpWrapper = &shapes[i]; + for (int i(0); i < shape_count; ++i) { + shpWrapper = &shapes.write[i]; bulletdelete(shpWrapper->bt_shape); } force_shape_reset = false; } - // Insert all shapes - btVector3 body_scale(get_bt_body_scale()); - for (i = 0; i < shapes_size; ++i) { - shpWrapper = &shapes[i]; - if (shpWrapper->active) { - if (!shpWrapper->bt_shape) { - shpWrapper->bt_shape = shpWrapper->shape->create_bt_shape(shpWrapper->scale * body_scale); - } - - btTransform scaled_shape_transform(shpWrapper->transform); - scaled_shape_transform.getOrigin() *= body_scale; - compoundShape->addChildShape(scaled_shape_transform, shpWrapper->bt_shape); - } else { - compoundShape->addChildShape(btTransform(), BulletPhysicsServer::get_empty_shape()); + const btVector3 body_scale(get_bt_body_scale()); + + // Try to optimize by not using compound + if (1 == shape_count) { + shpWrapper = &shapes.write[0]; + if (shpWrapper->transform.getOrigin().isZero() && shpWrapper->transform.getBasis() == shpWrapper->transform.getBasis().getIdentity()) { + shpWrapper->claim_bt_shape(body_scale); + mainShape = shpWrapper->bt_shape; + main_shape_changed(); + return; } } - compoundShape->recalculateLocalAabb(); -} + // Optimization not possible use a compound shape + btCompoundShape *compoundShape = bulletnew(btCompoundShape(enableDynamicAabbTree, shape_count)); -void RigidCollisionObjectBullet::set_shape_disabled(int p_index, bool p_disabled) { - shapes[p_index].active = !p_disabled; - on_shapes_changed(); -} + for (int i(0); i < shape_count; ++i) { + shpWrapper = &shapes.write[i]; + shpWrapper->claim_bt_shape(body_scale); + btTransform scaled_shape_transform(shpWrapper->transform); + scaled_shape_transform.getOrigin() *= body_scale; + compoundShape->addChildShape(scaled_shape_transform, shpWrapper->bt_shape); + } -bool RigidCollisionObjectBullet::is_shape_disabled(int p_index) { - return !shapes[p_index].active; + compoundShape->recalculateLocalAabb(); + mainShape = compoundShape; + main_shape_changed(); } -void RigidCollisionObjectBullet::on_body_scale_changed() { - CollisionObjectBullet::on_body_scale_changed(); - on_shapes_changed(); +void RigidCollisionObjectBullet::body_scale_changed() { + CollisionObjectBullet::body_scale_changed(); + reload_shapes(); } void RigidCollisionObjectBullet::internal_shape_destroy(int p_index, bool p_permanentlyFromThisBody) { - ShapeWrapper &shp = shapes[p_index]; + ShapeWrapper &shp = shapes.write[p_index]; shp.shape->remove_owner(this, p_permanentlyFromThisBody); + if (shp.bt_shape == mainShape) { + mainShape = NULL; + } bulletdelete(shp.bt_shape); } diff --git a/modules/bullet/collision_object_bullet.h b/modules/bullet/collision_object_bullet.h index 506976eabf..4a0c805ce5 100644 --- a/modules/bullet/collision_object_bullet.h +++ b/modules/bullet/collision_object_bullet.h @@ -31,11 +31,11 @@ #ifndef COLLISION_OBJECT_BULLET_H #define COLLISION_OBJECT_BULLET_H +#include "core/math/transform.h" +#include "core/math/vector3.h" +#include "core/object.h" #include "core/vset.h" -#include "object.h" #include "shape_owner_bullet.h" -#include "transform.h" -#include "vector3.h" #include <LinearMath/btTransform.h> @@ -109,6 +109,8 @@ public: void set_transform(const Transform &p_transform); void set_transform(const btTransform &p_transform); + + void claim_bt_shape(const btVector3 &body_scale); }; protected: @@ -130,6 +132,7 @@ protected: /// New area is added when overlap with new area (AreaBullet::addOverlap), then is removed when it exit (CollisionObjectBullet::onExitArea) /// This array is used mainly to know which area hold the pointer of this object Vector<AreaBullet *> areasOverlapped; + bool isTransformChanged; public: CollisionObjectBullet(Type p_type); @@ -155,7 +158,7 @@ public: void set_body_scale(const Vector3 &p_new_scale); const Vector3 &get_body_scale() const { return body_scale; } btVector3 get_bt_body_scale() const; - virtual void on_body_scale_changed(); + virtual void body_scale_changed(); void add_collision_exception(const CollisionObjectBullet *p_ignoreCollisionObject); void remove_collision_exception(const CollisionObjectBullet *p_ignoreCollisionObject); @@ -183,8 +186,9 @@ public: virtual void reload_body() = 0; virtual void set_space(SpaceBullet *p_space) = 0; _FORCE_INLINE_ SpaceBullet *get_space() const { return space; } - /// This is an event that is called when a collision checker starts + virtual void on_collision_checker_start() = 0; + virtual void on_collision_checker_end() = 0; virtual void dispatch_callbacks() = 0; @@ -195,7 +199,6 @@ public: virtual void on_enter_area(AreaBullet *p_area) = 0; virtual void on_exit_area(AreaBullet *p_area); - /// GodotObjectFlags void set_godot_object_flags(int flags); int get_godot_object_flags() const; @@ -203,14 +206,14 @@ public: Transform get_transform() const; virtual void set_transform__bullet(const btTransform &p_global_transform); virtual const btTransform &get_transform__bullet() const; + + bool is_transform_changed() const { return isTransformChanged; } + virtual void notify_transform_changed(); }; class RigidCollisionObjectBullet : public CollisionObjectBullet, public ShapeOwnerBullet { protected: - /// This is required to combine some shapes together. - /// Since Godot allow to have multiple shapes for each body with custom relative location, - /// each body will attach the shapes using this class even if there is only one shape. - btCompoundShape *compoundShape; + btCollisionShape *mainShape; Vector<ShapeWrapper> shapes; public: @@ -219,28 +222,34 @@ public: _FORCE_INLINE_ const Vector<ShapeWrapper> &get_shapes_wrappers() const { return shapes; } - /// This is used to set new shape or replace existing - //virtual void _internal_replaceShape(btCollisionShape *p_old_shape, btCollisionShape *p_new_shape) = 0; + _FORCE_INLINE_ btCollisionShape *get_main_shape() const { return mainShape; } + void add_shape(ShapeBullet *p_shape, const Transform &p_transform = Transform()); void set_shape(int p_index, ShapeBullet *p_shape); - void set_shape_transform(int p_index, const Transform &p_transform); - virtual void remove_shape(ShapeBullet *p_shape); - void remove_shape(int p_index); - void remove_all_shapes(bool p_permanentlyFromThisBody = false); - - virtual void on_shape_changed(const ShapeBullet *const p_shape); - virtual void on_shapes_changed(); - _FORCE_INLINE_ btCompoundShape *get_compound_shape() const { return compoundShape; } int get_shape_count() const; ShapeBullet *get_shape(int p_index) const; btCollisionShape *get_bt_shape(int p_index) const; + + int find_shape(ShapeBullet *p_shape) const; + + virtual void remove_shape_full(ShapeBullet *p_shape); + void remove_shape_full(int p_index); + void remove_all_shapes(bool p_permanentlyFromThisBody = false, bool p_force_not_reload = false); + + void set_shape_transform(int p_index, const Transform &p_transform); + + const btTransform &get_bt_shape_transform(int p_index) const; Transform get_shape_transform(int p_index) const; void set_shape_disabled(int p_index, bool p_disabled); bool is_shape_disabled(int p_index); - virtual void on_body_scale_changed(); + virtual void shape_changed(int p_shape_index); + virtual void reload_shapes(); + + virtual void main_shape_changed() = 0; + virtual void body_scale_changed(); private: void internal_shape_destroy(int p_index, bool p_permanentlyFromThisBody = false); diff --git a/modules/bullet/cone_twist_joint_bullet.cpp b/modules/bullet/cone_twist_joint_bullet.cpp index 472ad3b52c..ecacce0bee 100644 --- a/modules/bullet/cone_twist_joint_bullet.cpp +++ b/modules/bullet/cone_twist_joint_bullet.cpp @@ -64,26 +64,6 @@ ConeTwistJointBullet::ConeTwistJointBullet(RigidBodyBullet *rbA, RigidBodyBullet setup(coneConstraint); } -void ConeTwistJointBullet::set_angular_only(bool angularOnly) { - coneConstraint->setAngularOnly(angularOnly); -} - -void ConeTwistJointBullet::set_limit(real_t _swingSpan1, real_t _swingSpan2, real_t _twistSpan, real_t _softness, real_t _biasFactor, real_t _relaxationFactor) { - coneConstraint->setLimit(_swingSpan1, _swingSpan2, _twistSpan, _softness, _biasFactor, _relaxationFactor); -} - -int ConeTwistJointBullet::get_solve_twist_limit() { - return coneConstraint->getSolveTwistLimit(); -} - -int ConeTwistJointBullet::get_solve_swing_limit() { - return coneConstraint->getSolveSwingLimit(); -} - -real_t ConeTwistJointBullet::get_twist_limit_sign() { - return coneConstraint->getTwistLimitSign(); -} - void ConeTwistJointBullet::set_param(PhysicsServer::ConeTwistJointParam p_param, real_t p_value) { switch (p_param) { case PhysicsServer::CONE_TWIST_JOINT_SWING_SPAN: @@ -103,7 +83,9 @@ void ConeTwistJointBullet::set_param(PhysicsServer::ConeTwistJointParam p_param, coneConstraint->setLimit(coneConstraint->getSwingSpan1(), coneConstraint->getSwingSpan2(), coneConstraint->getTwistSpan(), coneConstraint->getLimitSoftness(), coneConstraint->getBiasFactor(), p_value); break; default: - WARN_PRINT("This parameter is not supported by Bullet engine"); + ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); + WARN_DEPRECATED + break; } } @@ -120,7 +102,8 @@ real_t ConeTwistJointBullet::get_param(PhysicsServer::ConeTwistJointParam p_para case PhysicsServer::CONE_TWIST_JOINT_RELAXATION: return coneConstraint->getRelaxationFactor(); default: - WARN_PRINT("This parameter is not supported by Bullet engine"); + ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); + WARN_DEPRECATED; return 0; } } diff --git a/modules/bullet/cone_twist_joint_bullet.h b/modules/bullet/cone_twist_joint_bullet.h index bd6eb49196..d6040fd6ec 100644 --- a/modules/bullet/cone_twist_joint_bullet.h +++ b/modules/bullet/cone_twist_joint_bullet.h @@ -47,14 +47,6 @@ public: virtual PhysicsServer::JointType get_type() const { return PhysicsServer::JOINT_CONE_TWIST; } - void set_angular_only(bool angularOnly); - - void set_limit(real_t _swingSpan1, real_t _swingSpan2, real_t _twistSpan, real_t _softness = 0.8f, real_t _biasFactor = 0.3f, real_t _relaxationFactor = 1.0f); - int get_solve_twist_limit(); - - int get_solve_swing_limit(); - real_t get_twist_limit_sign(); - void set_param(PhysicsServer::ConeTwistJointParam p_param, real_t p_value); real_t get_param(PhysicsServer::ConeTwistJointParam p_param) const; }; diff --git a/modules/bullet/generic_6dof_joint_bullet.cpp b/modules/bullet/generic_6dof_joint_bullet.cpp index adfad7803f..812dcd2d56 100644 --- a/modules/bullet/generic_6dof_joint_bullet.cpp +++ b/modules/bullet/generic_6dof_joint_bullet.cpp @@ -34,13 +34,13 @@ #include "bullet_utilities.h" #include "rigid_body_bullet.h" -#include <BulletDynamics/ConstraintSolver/btGeneric6DofConstraint.h> +#include <BulletDynamics/ConstraintSolver/btGeneric6DofSpring2Constraint.h> /** @author AndreaCatania */ -Generic6DOFJointBullet::Generic6DOFJointBullet(RigidBodyBullet *rbA, RigidBodyBullet *rbB, const Transform &frameInA, const Transform &frameInB, bool useLinearReferenceFrameA) : +Generic6DOFJointBullet::Generic6DOFJointBullet(RigidBodyBullet *rbA, RigidBodyBullet *rbB, const Transform &frameInA, const Transform &frameInB) : JointBullet() { Transform scaled_AFrame(frameInA.scaled(rbA->get_body_scale())); @@ -58,9 +58,9 @@ Generic6DOFJointBullet::Generic6DOFJointBullet(RigidBodyBullet *rbA, RigidBodyBu btTransform btFrameB; G_TO_B(scaled_BFrame, btFrameB); - sixDOFConstraint = bulletnew(btGeneric6DofConstraint(*rbA->get_bt_rigid_body(), *rbB->get_bt_rigid_body(), btFrameA, btFrameB, useLinearReferenceFrameA)); + sixDOFConstraint = bulletnew(btGeneric6DofSpring2Constraint(*rbA->get_bt_rigid_body(), *rbB->get_bt_rigid_body(), btFrameA, btFrameB)); } else { - sixDOFConstraint = bulletnew(btGeneric6DofConstraint(*rbA->get_bt_rigid_body(), btFrameA, useLinearReferenceFrameA)); + sixDOFConstraint = bulletnew(btGeneric6DofSpring2Constraint(*rbA->get_bt_rigid_body(), btFrameA)); } setup(sixDOFConstraint); @@ -123,20 +123,11 @@ void Generic6DOFJointBullet::set_param(Vector3::Axis p_axis, PhysicsServer::G6DO switch (p_param) { case PhysicsServer::G6DOF_JOINT_LINEAR_LOWER_LIMIT: limits_lower[0][p_axis] = p_value; - set_flag(p_axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT, flags[p_axis][p_param]); // Reload bullet parameter + set_flag(p_axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT, flags[p_axis][PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT]); // Reload bullet parameter break; case PhysicsServer::G6DOF_JOINT_LINEAR_UPPER_LIMIT: limits_upper[0][p_axis] = p_value; - set_flag(p_axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT, flags[p_axis][p_param]); // Reload bullet parameter - break; - case PhysicsServer::G6DOF_JOINT_LINEAR_LIMIT_SOFTNESS: - sixDOFConstraint->getTranslationalLimitMotor()->m_limitSoftness = p_value; - break; - case PhysicsServer::G6DOF_JOINT_LINEAR_RESTITUTION: - sixDOFConstraint->getTranslationalLimitMotor()->m_restitution = p_value; - break; - case PhysicsServer::G6DOF_JOINT_LINEAR_DAMPING: - sixDOFConstraint->getTranslationalLimitMotor()->m_damping = p_value; + set_flag(p_axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT, flags[p_axis][PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT]); // Reload bullet parameter break; case PhysicsServer::G6DOF_JOINT_LINEAR_MOTOR_TARGET_VELOCITY: sixDOFConstraint->getTranslationalLimitMotor()->m_targetVelocity.m_floats[p_axis] = p_value; @@ -144,26 +135,26 @@ void Generic6DOFJointBullet::set_param(Vector3::Axis p_axis, PhysicsServer::G6DO case PhysicsServer::G6DOF_JOINT_LINEAR_MOTOR_FORCE_LIMIT: sixDOFConstraint->getTranslationalLimitMotor()->m_maxMotorForce.m_floats[p_axis] = p_value; break; + case PhysicsServer::G6DOF_JOINT_LINEAR_SPRING_DAMPING: + sixDOFConstraint->getTranslationalLimitMotor()->m_springDamping.m_floats[p_axis] = p_value; + break; + case PhysicsServer::G6DOF_JOINT_LINEAR_SPRING_STIFFNESS: + sixDOFConstraint->getTranslationalLimitMotor()->m_springStiffness.m_floats[p_axis] = p_value; + break; + case PhysicsServer::G6DOF_JOINT_LINEAR_SPRING_EQUILIBRIUM_POINT: + sixDOFConstraint->getTranslationalLimitMotor()->m_equilibriumPoint.m_floats[p_axis] = p_value; + break; case PhysicsServer::G6DOF_JOINT_ANGULAR_LOWER_LIMIT: limits_lower[1][p_axis] = p_value; - set_flag(p_axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT, flags[p_axis][p_param]); // Reload bullet parameter + set_flag(p_axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT, flags[p_axis][PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT]); // Reload bullet parameter break; case PhysicsServer::G6DOF_JOINT_ANGULAR_UPPER_LIMIT: limits_upper[1][p_axis] = p_value; - set_flag(p_axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT, flags[p_axis][p_param]); // Reload bullet parameter - break; - case PhysicsServer::G6DOF_JOINT_ANGULAR_LIMIT_SOFTNESS: - sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_limitSoftness = p_value; - break; - case PhysicsServer::G6DOF_JOINT_ANGULAR_DAMPING: - sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_damping = p_value; + set_flag(p_axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT, flags[p_axis][PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT]); // Reload bullet parameter break; case PhysicsServer::G6DOF_JOINT_ANGULAR_RESTITUTION: sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_bounce = p_value; break; - case PhysicsServer::G6DOF_JOINT_ANGULAR_FORCE_LIMIT: - sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_maxLimitForce = p_value; - break; case PhysicsServer::G6DOF_JOINT_ANGULAR_ERP: sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_stopERP = p_value; break; @@ -171,10 +162,21 @@ void Generic6DOFJointBullet::set_param(Vector3::Axis p_axis, PhysicsServer::G6DO sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_targetVelocity = p_value; break; case PhysicsServer::G6DOF_JOINT_ANGULAR_MOTOR_FORCE_LIMIT: - sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_maxLimitForce = p_value; + sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_maxMotorForce = p_value; + break; + case PhysicsServer::G6DOF_JOINT_ANGULAR_SPRING_STIFFNESS: + sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_springStiffness = p_value; + break; + case PhysicsServer::G6DOF_JOINT_ANGULAR_SPRING_DAMPING: + sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_springDamping = p_value; + break; + case PhysicsServer::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT: + sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_equilibriumPoint = p_value; break; default: - WARN_PRINT("This parameter is not supported"); + ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); + WARN_DEPRECATED + break; } } @@ -185,37 +187,38 @@ real_t Generic6DOFJointBullet::get_param(Vector3::Axis p_axis, PhysicsServer::G6 return limits_lower[0][p_axis]; case PhysicsServer::G6DOF_JOINT_LINEAR_UPPER_LIMIT: return limits_upper[0][p_axis]; - case PhysicsServer::G6DOF_JOINT_LINEAR_LIMIT_SOFTNESS: - return sixDOFConstraint->getTranslationalLimitMotor()->m_limitSoftness; - case PhysicsServer::G6DOF_JOINT_LINEAR_RESTITUTION: - return sixDOFConstraint->getTranslationalLimitMotor()->m_restitution; - case PhysicsServer::G6DOF_JOINT_LINEAR_DAMPING: - return sixDOFConstraint->getTranslationalLimitMotor()->m_damping; case PhysicsServer::G6DOF_JOINT_LINEAR_MOTOR_TARGET_VELOCITY: return sixDOFConstraint->getTranslationalLimitMotor()->m_targetVelocity.m_floats[p_axis]; case PhysicsServer::G6DOF_JOINT_LINEAR_MOTOR_FORCE_LIMIT: return sixDOFConstraint->getTranslationalLimitMotor()->m_maxMotorForce.m_floats[p_axis]; + case PhysicsServer::G6DOF_JOINT_LINEAR_SPRING_DAMPING: + return sixDOFConstraint->getTranslationalLimitMotor()->m_springDamping.m_floats[p_axis]; + case PhysicsServer::G6DOF_JOINT_LINEAR_SPRING_STIFFNESS: + return sixDOFConstraint->getTranslationalLimitMotor()->m_springStiffness.m_floats[p_axis]; + case PhysicsServer::G6DOF_JOINT_LINEAR_SPRING_EQUILIBRIUM_POINT: + return sixDOFConstraint->getTranslationalLimitMotor()->m_equilibriumPoint.m_floats[p_axis]; case PhysicsServer::G6DOF_JOINT_ANGULAR_LOWER_LIMIT: return limits_lower[1][p_axis]; case PhysicsServer::G6DOF_JOINT_ANGULAR_UPPER_LIMIT: return limits_upper[1][p_axis]; - case PhysicsServer::G6DOF_JOINT_ANGULAR_LIMIT_SOFTNESS: - return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_limitSoftness; - case PhysicsServer::G6DOF_JOINT_ANGULAR_DAMPING: - return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_damping; case PhysicsServer::G6DOF_JOINT_ANGULAR_RESTITUTION: return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_bounce; - case PhysicsServer::G6DOF_JOINT_ANGULAR_FORCE_LIMIT: - return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_maxLimitForce; case PhysicsServer::G6DOF_JOINT_ANGULAR_ERP: return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_stopERP; case PhysicsServer::G6DOF_JOINT_ANGULAR_MOTOR_TARGET_VELOCITY: return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_targetVelocity; case PhysicsServer::G6DOF_JOINT_ANGULAR_MOTOR_FORCE_LIMIT: - return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_maxLimitForce; + return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_maxMotorForce; + case PhysicsServer::G6DOF_JOINT_ANGULAR_SPRING_STIFFNESS: + return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_springStiffness; + case PhysicsServer::G6DOF_JOINT_ANGULAR_SPRING_DAMPING: + return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_springDamping; + case PhysicsServer::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT: + return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_equilibriumPoint; default: - WARN_PRINT("This parameter is not supported"); - return 0.; + ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); + WARN_DEPRECATED; + return 0; } } @@ -245,14 +248,28 @@ void Generic6DOFJointBullet::set_flag(Vector3::Axis p_axis, PhysicsServer::G6DOF case PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_MOTOR: sixDOFConstraint->getTranslationalLimitMotor()->m_enableMotor[p_axis] = flags[p_axis][p_flag]; break; + case PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_SPRING: + sixDOFConstraint->getTranslationalLimitMotor()->m_enableSpring[p_axis] = p_value; + break; + case PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_SPRING: + sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_enableSpring = p_value; + break; default: - WARN_PRINT("This flag is not supported by Bullet engine"); - return; + ERR_EXPLAIN("This flag " + itos(p_flag) + " is deprecated"); + WARN_DEPRECATED + break; } } bool Generic6DOFJointBullet::get_flag(Vector3::Axis p_axis, PhysicsServer::G6DOFJointAxisFlag p_flag) const { ERR_FAIL_INDEX_V(p_axis, 3, false); - return flags[p_axis][p_flag]; } + +void Generic6DOFJointBullet::set_precision(int p_precision) { + sixDOFConstraint->setOverrideNumSolverIterations(MAX(1, p_precision)); +} + +int Generic6DOFJointBullet::get_precision() const { + return sixDOFConstraint->getOverrideNumSolverIterations(); +} diff --git a/modules/bullet/generic_6dof_joint_bullet.h b/modules/bullet/generic_6dof_joint_bullet.h index ad06582eac..848c3a10cd 100644 --- a/modules/bullet/generic_6dof_joint_bullet.h +++ b/modules/bullet/generic_6dof_joint_bullet.h @@ -40,7 +40,7 @@ class RigidBodyBullet; class Generic6DOFJointBullet : public JointBullet { - class btGeneric6DofConstraint *sixDOFConstraint; + class btGeneric6DofSpring2Constraint *sixDOFConstraint; // First is linear second is angular Vector3 limits_lower[2]; @@ -48,7 +48,7 @@ class Generic6DOFJointBullet : public JointBullet { bool flags[3][PhysicsServer::G6DOF_JOINT_FLAG_MAX]; public: - Generic6DOFJointBullet(RigidBodyBullet *rbA, RigidBodyBullet *rbB, const Transform &frameInA, const Transform &frameInB, bool useLinearReferenceFrameA); + Generic6DOFJointBullet(RigidBodyBullet *rbA, RigidBodyBullet *rbB, const Transform &frameInA, const Transform &frameInB); virtual PhysicsServer::JointType get_type() const { return PhysicsServer::JOINT_6DOF; } @@ -68,6 +68,9 @@ public: void set_flag(Vector3::Axis p_axis, PhysicsServer::G6DOFJointAxisFlag p_flag, bool p_value); bool get_flag(Vector3::Axis p_axis, PhysicsServer::G6DOFJointAxisFlag p_flag) const; + + void set_precision(int p_precision); + int get_precision() const; }; #endif diff --git a/modules/bullet/godot_collision_configuration.cpp b/modules/bullet/godot_collision_configuration.cpp index f4bb9acbd7..919c3152d7 100644 --- a/modules/bullet/godot_collision_configuration.cpp +++ b/modules/bullet/godot_collision_configuration.cpp @@ -94,3 +94,59 @@ btCollisionAlgorithmCreateFunc *GodotCollisionConfiguration::getClosestPointsAlg return btDefaultCollisionConfiguration::getClosestPointsAlgorithmCreateFunc(proxyType0, proxyType1); } } + +GodotSoftCollisionConfiguration::GodotSoftCollisionConfiguration(const btDiscreteDynamicsWorld *world, const btDefaultCollisionConstructionInfo &constructionInfo) : + btSoftBodyRigidBodyCollisionConfiguration(constructionInfo) { + + void *mem = NULL; + + mem = btAlignedAlloc(sizeof(GodotRayWorldAlgorithm::CreateFunc), 16); + m_rayWorldCF = new (mem) GodotRayWorldAlgorithm::CreateFunc(world); + + mem = btAlignedAlloc(sizeof(GodotRayWorldAlgorithm::SwappedCreateFunc), 16); + m_swappedRayWorldCF = new (mem) GodotRayWorldAlgorithm::SwappedCreateFunc(world); +} + +GodotSoftCollisionConfiguration::~GodotSoftCollisionConfiguration() { + m_rayWorldCF->~btCollisionAlgorithmCreateFunc(); + btAlignedFree(m_rayWorldCF); + + m_swappedRayWorldCF->~btCollisionAlgorithmCreateFunc(); + btAlignedFree(m_swappedRayWorldCF); +} + +btCollisionAlgorithmCreateFunc *GodotSoftCollisionConfiguration::getCollisionAlgorithmCreateFunc(int proxyType0, int proxyType1) { + + if (CUSTOM_CONVEX_SHAPE_TYPE == proxyType0 && CUSTOM_CONVEX_SHAPE_TYPE == proxyType1) { + + // This collision is not supported + return m_emptyCreateFunc; + } else if (CUSTOM_CONVEX_SHAPE_TYPE == proxyType0) { + + return m_rayWorldCF; + } else if (CUSTOM_CONVEX_SHAPE_TYPE == proxyType1) { + + return m_swappedRayWorldCF; + } else { + + return btSoftBodyRigidBodyCollisionConfiguration::getCollisionAlgorithmCreateFunc(proxyType0, proxyType1); + } +} + +btCollisionAlgorithmCreateFunc *GodotSoftCollisionConfiguration::getClosestPointsAlgorithmCreateFunc(int proxyType0, int proxyType1) { + + if (CUSTOM_CONVEX_SHAPE_TYPE == proxyType0 && CUSTOM_CONVEX_SHAPE_TYPE == proxyType1) { + + // This collision is not supported + return m_emptyCreateFunc; + } else if (CUSTOM_CONVEX_SHAPE_TYPE == proxyType0) { + + return m_rayWorldCF; + } else if (CUSTOM_CONVEX_SHAPE_TYPE == proxyType1) { + + return m_swappedRayWorldCF; + } else { + + return btSoftBodyRigidBodyCollisionConfiguration::getClosestPointsAlgorithmCreateFunc(proxyType0, proxyType1); + } +} diff --git a/modules/bullet/godot_collision_configuration.h b/modules/bullet/godot_collision_configuration.h index 9b30ad0c62..11012c5f6d 100644 --- a/modules/bullet/godot_collision_configuration.h +++ b/modules/bullet/godot_collision_configuration.h @@ -32,6 +32,7 @@ #define GODOT_COLLISION_CONFIGURATION_H #include <BulletCollision/CollisionDispatch/btDefaultCollisionConfiguration.h> +#include <BulletSoftBody/btSoftBodyRigidBodyCollisionConfiguration.h> /** @author AndreaCatania @@ -50,4 +51,16 @@ public: virtual btCollisionAlgorithmCreateFunc *getCollisionAlgorithmCreateFunc(int proxyType0, int proxyType1); virtual btCollisionAlgorithmCreateFunc *getClosestPointsAlgorithmCreateFunc(int proxyType0, int proxyType1); }; + +class GodotSoftCollisionConfiguration : public btSoftBodyRigidBodyCollisionConfiguration { + btCollisionAlgorithmCreateFunc *m_rayWorldCF; + btCollisionAlgorithmCreateFunc *m_swappedRayWorldCF; + +public: + GodotSoftCollisionConfiguration(const btDiscreteDynamicsWorld *world, const btDefaultCollisionConstructionInfo &constructionInfo = btDefaultCollisionConstructionInfo()); + virtual ~GodotSoftCollisionConfiguration(); + + virtual btCollisionAlgorithmCreateFunc *getCollisionAlgorithmCreateFunc(int proxyType0, int proxyType1); + virtual btCollisionAlgorithmCreateFunc *getClosestPointsAlgorithmCreateFunc(int proxyType0, int proxyType1); +}; #endif diff --git a/modules/bullet/godot_collision_dispatcher.h b/modules/bullet/godot_collision_dispatcher.h index 2e5a6c2732..1faaa68626 100644 --- a/modules/bullet/godot_collision_dispatcher.h +++ b/modules/bullet/godot_collision_dispatcher.h @@ -31,7 +31,7 @@ #ifndef GODOT_COLLISION_DISPATCHER_H #define GODOT_COLLISION_DISPATCHER_H -#include "int_types.h" +#include "core/int_types.h" #include <btBulletDynamicsCommon.h> diff --git a/modules/bullet/godot_motion_state.h b/modules/bullet/godot_motion_state.h index fa58e86589..5844ef8bf3 100644 --- a/modules/bullet/godot_motion_state.h +++ b/modules/bullet/godot_motion_state.h @@ -82,7 +82,7 @@ public: virtual void setWorldTransform(const btTransform &worldTrans) { bodyCurrentWorldTransform = worldTrans; - owner->scratch(); + owner->notify_transform_changed(); } public: diff --git a/modules/bullet/godot_ray_world_algorithm.cpp b/modules/bullet/godot_ray_world_algorithm.cpp index 53d0ab7e3c..27ee44d1bd 100644 --- a/modules/bullet/godot_ray_world_algorithm.cpp +++ b/modules/bullet/godot_ray_world_algorithm.cpp @@ -49,9 +49,9 @@ GodotRayWorldAlgorithm::SwappedCreateFunc::SwappedCreateFunc(const btDiscreteDyn GodotRayWorldAlgorithm::GodotRayWorldAlgorithm(const btDiscreteDynamicsWorld *world, btPersistentManifold *mf, const btCollisionAlgorithmConstructionInfo &ci, const btCollisionObjectWrapper *body0Wrap, const btCollisionObjectWrapper *body1Wrap, bool isSwapped) : btActivatingCollisionAlgorithm(ci, body0Wrap, body1Wrap), + m_world(world), m_manifoldPtr(mf), m_ownManifold(false), - m_world(world), m_isSwapped(isSwapped) {} GodotRayWorldAlgorithm::~GodotRayWorldAlgorithm() { diff --git a/modules/bullet/godot_result_callbacks.cpp b/modules/bullet/godot_result_callbacks.cpp index 197550d686..0117bb375f 100644 --- a/modules/bullet/godot_result_callbacks.cpp +++ b/modules/bullet/godot_result_callbacks.cpp @@ -30,14 +30,23 @@ #include "godot_result_callbacks.h" +#include "area_bullet.h" #include "bullet_types_converter.h" #include "collision_object_bullet.h" #include "rigid_body_bullet.h" +#include <BulletCollision/CollisionDispatch/btInternalEdgeUtility.h> /** @author AndreaCatania */ +bool godotContactAddedCallback(btManifoldPoint &cp, const btCollisionObjectWrapper *colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper *colObj1Wrap, int partId1, int index1) { + if (!colObj1Wrap->getCollisionObject()->getCollisionShape()->isCompound()) { + btAdjustInternalEdgeContacts(cp, colObj1Wrap, colObj0Wrap, partId1, index1); + } + return true; +} + bool GodotFilterCallback::test_collision_filters(uint32_t body0_collision_layer, uint32_t body0_collision_mask, uint32_t body1_collision_layer, uint32_t body1_collision_mask) { return body0_collision_layer & body1_collision_mask || body1_collision_layer & body0_collision_mask; } @@ -51,11 +60,23 @@ bool GodotClosestRayResultCallback::needsCollision(btBroadphaseProxy *proxy0) co if (needs) { btCollisionObject *btObj = static_cast<btCollisionObject *>(proxy0->m_clientObject); CollisionObjectBullet *gObj = static_cast<CollisionObjectBullet *>(btObj->getUserPointer()); - if (m_pickRay && gObj->is_ray_pickable()) { - return true; - } else if (m_exclude->has(gObj->get_self())) { + + if (CollisionObjectBullet::TYPE_AREA == gObj->getType()) { + if (!collide_with_areas) + return false; + } else { + if (!collide_with_bodies) + return false; + } + + if (m_pickRay && !gObj->is_ray_pickable()) { + return false; + } + + if (m_exclude->has(gObj->get_self())) { return false; } + return true; } else { return false; @@ -81,6 +102,9 @@ bool GodotAllConvexResultCallback::needsCollision(btBroadphaseProxy *proxy0) con } btScalar GodotAllConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult &convexResult, bool normalInWorldSpace) { + if (count >= m_resultMax) + return 1; // not used by bullet + CollisionObjectBullet *gObj = static_cast<CollisionObjectBullet *>(convexResult.m_hitCollisionObject->getUserPointer()); PhysicsDirectSpaceState::ShapeResult &result = m_results[count]; @@ -124,6 +148,15 @@ bool GodotClosestConvexResultCallback::needsCollision(btBroadphaseProxy *proxy0) if (needs) { btCollisionObject *btObj = static_cast<btCollisionObject *>(proxy0->m_clientObject); CollisionObjectBullet *gObj = static_cast<CollisionObjectBullet *>(btObj->getUserPointer()); + + if (CollisionObjectBullet::TYPE_AREA == gObj->getType()) { + if (!collide_with_areas) + return false; + } else { + if (!collide_with_bodies) + return false; + } + if (m_exclude->has(gObj->get_self())) { return false; } @@ -134,16 +167,30 @@ bool GodotClosestConvexResultCallback::needsCollision(btBroadphaseProxy *proxy0) } btScalar GodotClosestConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult &convexResult, bool normalInWorldSpace) { - btScalar res = btCollisionWorld::ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); - m_shapeId = convexResult.m_localShapeInfo->m_triangleIndex; // "m_triangleIndex" Is a odd name but contains the compound shape ID - return res; + if (convexResult.m_localShapeInfo) + m_shapeId = convexResult.m_localShapeInfo->m_triangleIndex; // "m_triangleIndex" Is a odd name but contains the compound shape ID + else + m_shapeId = 0; + return btCollisionWorld::ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); } bool GodotAllContactResultCallback::needsCollision(btBroadphaseProxy *proxy0) const { + if (m_count >= m_resultMax) + return false; + const bool needs = GodotFilterCallback::test_collision_filters(m_collisionFilterGroup, m_collisionFilterMask, proxy0->m_collisionFilterGroup, proxy0->m_collisionFilterMask); if (needs) { btCollisionObject *btObj = static_cast<btCollisionObject *>(proxy0->m_clientObject); CollisionObjectBullet *gObj = static_cast<CollisionObjectBullet *>(btObj->getUserPointer()); + + if (CollisionObjectBullet::TYPE_AREA == gObj->getType()) { + if (!collide_with_areas) + return false; + } else { + if (!collide_with_bodies) + return false; + } + if (m_exclude->has(gObj->get_self())) { return false; } @@ -189,6 +236,15 @@ bool GodotContactPairContactResultCallback::needsCollision(btBroadphaseProxy *pr if (needs) { btCollisionObject *btObj = static_cast<btCollisionObject *>(proxy0->m_clientObject); CollisionObjectBullet *gObj = static_cast<CollisionObjectBullet *>(btObj->getUserPointer()); + + if (CollisionObjectBullet::TYPE_AREA == gObj->getType()) { + if (!collide_with_areas) + return false; + } else { + if (!collide_with_bodies) + return false; + } + if (m_exclude->has(gObj->get_self())) { return false; } @@ -199,6 +255,8 @@ bool GodotContactPairContactResultCallback::needsCollision(btBroadphaseProxy *pr } btScalar GodotContactPairContactResultCallback::addSingleResult(btManifoldPoint &cp, const btCollisionObjectWrapper *colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper *colObj1Wrap, int partId1, int index1) { + if (m_count >= m_resultMax) + return 1; // not used by bullet if (m_self_object == colObj0Wrap->getCollisionObject()) { B_TO_G(cp.m_localPointA, m_results[m_count * 2 + 0]); // Local contact @@ -218,6 +276,15 @@ bool GodotRestInfoContactResultCallback::needsCollision(btBroadphaseProxy *proxy if (needs) { btCollisionObject *btObj = static_cast<btCollisionObject *>(proxy0->m_clientObject); CollisionObjectBullet *gObj = static_cast<CollisionObjectBullet *>(btObj->getUserPointer()); + + if (CollisionObjectBullet::TYPE_AREA == gObj->getType()) { + if (!collide_with_areas) + return false; + } else { + if (!collide_with_bodies) + return false; + } + if (m_exclude->has(gObj->get_self())) { return false; } @@ -260,10 +327,19 @@ void GodotDeepPenetrationContactResultCallback::addContactPoint(const btVector3 if (m_penetration_distance > depth) { // Has penetration? - bool isSwapped = m_manifoldPtr->getBody0() != m_body0Wrap->getCollisionObject(); + const bool isSwapped = m_manifoldPtr->getBody0() != m_body0Wrap->getCollisionObject(); m_penetration_distance = depth; m_other_compound_shape_index = isSwapped ? m_index0 : m_index1; - m_pointNormalWorld = isSwapped ? normalOnBInWorld * -1 : normalOnBInWorld; m_pointWorld = isSwapped ? (pointInWorldOnB + (normalOnBInWorld * depth)) : pointInWorldOnB; + + const btCollisionObjectWrapper *bw0 = m_body0Wrap; + if (isSwapped) + bw0 = m_body1Wrap; + + if (bw0->getCollisionShape()->getShapeType() == CUSTOM_CONVEX_SHAPE_TYPE) { + m_pointNormalWorld = bw0->m_worldTransform.getBasis().transpose() * btVector3(0, 0, 1); + } else { + m_pointNormalWorld = isSwapped ? normalOnBInWorld * -1 : normalOnBInWorld; + } } } diff --git a/modules/bullet/godot_result_callbacks.h b/modules/bullet/godot_result_callbacks.h index 363051f24c..73e1fc9627 100644 --- a/modules/bullet/godot_result_callbacks.h +++ b/modules/bullet/godot_result_callbacks.h @@ -42,6 +42,9 @@ class RigidBodyBullet; +/// This callback is injected inside bullet server and allow me to smooth contacts against trimesh +bool godotContactAddedCallback(btManifoldPoint &cp, const btCollisionObjectWrapper *colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper *colObj1Wrap, int partId1, int index1); + /// This class is required to implement custom collision behaviour in the broadphase struct GodotFilterCallback : public btOverlapFilterCallback { static bool test_collision_filters(uint32_t body0_collision_layer, uint32_t body0_collision_mask, uint32_t body1_collision_layer, uint32_t body1_collision_mask); @@ -56,17 +59,25 @@ struct GodotClosestRayResultCallback : public btCollisionWorld::ClosestRayResult bool m_pickRay; int m_shapeId; + bool collide_with_bodies; + bool collide_with_areas; + public: - GodotClosestRayResultCallback(const btVector3 &rayFromWorld, const btVector3 &rayToWorld, const Set<RID> *p_exclude) : + GodotClosestRayResultCallback(const btVector3 &rayFromWorld, const btVector3 &rayToWorld, const Set<RID> *p_exclude, bool p_collide_with_bodies, bool p_collide_with_areas) : btCollisionWorld::ClosestRayResultCallback(rayFromWorld, rayToWorld), m_exclude(p_exclude), m_pickRay(false), - m_shapeId(0) {} + m_shapeId(0), + collide_with_bodies(p_collide_with_bodies), + collide_with_areas(p_collide_with_areas) {} virtual bool needsCollision(btBroadphaseProxy *proxy0) const; virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult &rayResult, bool normalInWorldSpace) { - m_shapeId = rayResult.m_localShapeInfo->m_triangleIndex; // "m_triangleIndex" Is a odd name but contains the compound shape ID + if (rayResult.m_localShapeInfo) + m_shapeId = rayResult.m_localShapeInfo->m_triangleIndex; // "m_triangleIndex" Is a odd name but contains the compound shape ID + else + m_shapeId = 0; return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } }; @@ -76,13 +87,13 @@ struct GodotAllConvexResultCallback : public btCollisionWorld::ConvexResultCallb public: PhysicsDirectSpaceState::ShapeResult *m_results; int m_resultMax; - int count; const Set<RID> *m_exclude; + int count; GodotAllConvexResultCallback(PhysicsDirectSpaceState::ShapeResult *p_results, int p_resultMax, const Set<RID> *p_exclude) : m_results(p_results), - m_exclude(p_exclude), m_resultMax(p_resultMax), + m_exclude(p_exclude), count(0) {} virtual bool needsCollision(btBroadphaseProxy *proxy0) const; @@ -108,9 +119,15 @@ public: const Set<RID> *m_exclude; int m_shapeId; - GodotClosestConvexResultCallback(const btVector3 &convexFromWorld, const btVector3 &convexToWorld, const Set<RID> *p_exclude) : + bool collide_with_bodies; + bool collide_with_areas; + + GodotClosestConvexResultCallback(const btVector3 &convexFromWorld, const btVector3 &convexToWorld, const Set<RID> *p_exclude, bool p_collide_with_bodies, bool p_collide_with_areas) : btCollisionWorld::ClosestConvexResultCallback(convexFromWorld, convexToWorld), - m_exclude(p_exclude) {} + m_exclude(p_exclude), + m_shapeId(0), + collide_with_bodies(p_collide_with_bodies), + collide_with_areas(p_collide_with_areas) {} virtual bool needsCollision(btBroadphaseProxy *proxy0) const; @@ -122,15 +139,20 @@ public: const btCollisionObject *m_self_object; PhysicsDirectSpaceState::ShapeResult *m_results; int m_resultMax; - int m_count; const Set<RID> *m_exclude; + int m_count; - GodotAllContactResultCallback(btCollisionObject *p_self_object, PhysicsDirectSpaceState::ShapeResult *p_results, int p_resultMax, const Set<RID> *p_exclude) : + bool collide_with_bodies; + bool collide_with_areas; + + GodotAllContactResultCallback(btCollisionObject *p_self_object, PhysicsDirectSpaceState::ShapeResult *p_results, int p_resultMax, const Set<RID> *p_exclude, bool p_collide_with_bodies, bool p_collide_with_areas) : m_self_object(p_self_object), m_results(p_results), - m_exclude(p_exclude), m_resultMax(p_resultMax), - m_count(0) {} + m_exclude(p_exclude), + m_count(0), + collide_with_bodies(p_collide_with_bodies), + collide_with_areas(p_collide_with_areas) {} virtual bool needsCollision(btBroadphaseProxy *proxy0) const; @@ -143,15 +165,20 @@ public: const btCollisionObject *m_self_object; Vector3 *m_results; int m_resultMax; - int m_count; const Set<RID> *m_exclude; + int m_count; - GodotContactPairContactResultCallback(btCollisionObject *p_self_object, Vector3 *p_results, int p_resultMax, const Set<RID> *p_exclude) : + bool collide_with_bodies; + bool collide_with_areas; + + GodotContactPairContactResultCallback(btCollisionObject *p_self_object, Vector3 *p_results, int p_resultMax, const Set<RID> *p_exclude, bool p_collide_with_bodies, bool p_collide_with_areas) : m_self_object(p_self_object), m_results(p_results), - m_exclude(p_exclude), m_resultMax(p_resultMax), - m_count(0) {} + m_exclude(p_exclude), + m_count(0), + collide_with_bodies(p_collide_with_bodies), + collide_with_areas(p_collide_with_areas) {} virtual bool needsCollision(btBroadphaseProxy *proxy0) const; @@ -162,18 +189,22 @@ struct GodotRestInfoContactResultCallback : public btCollisionWorld::ContactResu public: const btCollisionObject *m_self_object; PhysicsDirectSpaceState::ShapeRestInfo *m_result; + const Set<RID> *m_exclude; bool m_collided; real_t m_min_distance; const btCollisionObject *m_rest_info_collision_object; btVector3 m_rest_info_bt_point; - const Set<RID> *m_exclude; + bool collide_with_bodies; + bool collide_with_areas; - GodotRestInfoContactResultCallback(btCollisionObject *p_self_object, PhysicsDirectSpaceState::ShapeRestInfo *p_result, const Set<RID> *p_exclude) : + GodotRestInfoContactResultCallback(btCollisionObject *p_self_object, PhysicsDirectSpaceState::ShapeRestInfo *p_result, const Set<RID> *p_exclude, bool p_collide_with_bodies, bool p_collide_with_areas) : m_self_object(p_self_object), m_result(p_result), m_exclude(p_exclude), m_collided(false), - m_min_distance(0) {} + m_min_distance(0), + collide_with_bodies(p_collide_with_bodies), + collide_with_areas(p_collide_with_areas) {} virtual bool needsCollision(btBroadphaseProxy *proxy0) const; diff --git a/modules/bullet/hinge_joint_bullet.cpp b/modules/bullet/hinge_joint_bullet.cpp index 97ea7ca3df..3a4459a581 100644 --- a/modules/bullet/hinge_joint_bullet.cpp +++ b/modules/bullet/hinge_joint_bullet.cpp @@ -95,11 +95,6 @@ real_t HingeJointBullet::get_hinge_angle() { void HingeJointBullet::set_param(PhysicsServer::HingeJointParam p_param, real_t p_value) { switch (p_param) { - case PhysicsServer::HINGE_JOINT_BIAS: - if (0 < p_value) { - print_line("The Bullet Hinge Joint doesn't support bias, So it's always 0"); - } - break; case PhysicsServer::HINGE_JOINT_LIMIT_UPPER: hingeConstraint->setLimit(hingeConstraint->getLowerLimit(), p_value, hingeConstraint->getLimitSoftness(), hingeConstraint->getLimitBiasFactor(), hingeConstraint->getLimitRelaxationFactor()); break; @@ -122,7 +117,9 @@ void HingeJointBullet::set_param(PhysicsServer::HingeJointParam p_param, real_t hingeConstraint->setMaxMotorImpulse(p_value); break; default: - WARN_PRINTS("The Bullet Hinge Joint doesn't support this parameter: " + itos(p_param) + ", value: " + itos(p_value)); + ERR_EXPLAIN("The HingeJoint parameter " + itos(p_param) + " is deprecated."); + WARN_DEPRECATED + break; } } @@ -146,7 +143,8 @@ real_t HingeJointBullet::get_param(PhysicsServer::HingeJointParam p_param) const case PhysicsServer::HINGE_JOINT_MOTOR_MAX_IMPULSE: return hingeConstraint->getMaxMotorImpulse(); default: - WARN_PRINTS("The Bullet Hinge Joint doesn't support this parameter: " + itos(p_param)); + ERR_EXPLAIN("The HingeJoint parameter " + itos(p_param) + " is deprecated."); + WARN_DEPRECATED; return 0; } } @@ -161,6 +159,7 @@ void HingeJointBullet::set_flag(PhysicsServer::HingeJointFlag p_flag, bool p_val case PhysicsServer::HINGE_JOINT_FLAG_ENABLE_MOTOR: hingeConstraint->enableMotor(p_value); break; + case PhysicsServer::HINGE_JOINT_FLAG_MAX: break; // Can't happen, but silences warning } } diff --git a/modules/bullet/pin_joint_bullet.cpp b/modules/bullet/pin_joint_bullet.cpp index c4e5b8cdbe..183a7e75b9 100644 --- a/modules/bullet/pin_joint_bullet.cpp +++ b/modules/bullet/pin_joint_bullet.cpp @@ -85,7 +85,8 @@ real_t PinJointBullet::get_param(PhysicsServer::PinJointParam p_param) const { case PhysicsServer::PIN_JOINT_IMPULSE_CLAMP: return p2pConstraint->m_setting.m_impulseClamp; default: - WARN_PRINTS("This get parameter is not supported"); + ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); + WARN_DEPRECATED return 0; } } diff --git a/modules/bullet/register_types.cpp b/modules/bullet/register_types.cpp index b119b7720f..31e5f6784e 100644 --- a/modules/bullet/register_types.cpp +++ b/modules/bullet/register_types.cpp @@ -31,20 +31,27 @@ #include "register_types.h" #include "bullet_physics_server.h" -#include "class_db.h" +#include "core/class_db.h" +#include "core/project_settings.h" /** @author AndreaCatania */ +#ifndef _3D_DISABLED PhysicsServer *_createBulletPhysicsCallback() { return memnew(BulletPhysicsServer); } +#endif void register_bullet_types() { - +#ifndef _3D_DISABLED PhysicsServerManager::register_server("Bullet", &_createBulletPhysicsCallback); PhysicsServerManager::set_default_server("Bullet", 1); + + GLOBAL_DEF("physics/3d/active_soft_world", true); + ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/active_soft_world", PropertyInfo(Variant::BOOL, "physics/3d/active_soft_world")); +#endif } void unregister_bullet_types() { diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index 2494063c22..9dd04100ed 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -126,6 +126,10 @@ void BulletPhysicsDirectBodyState::add_torque(const Vector3 &p_torque) { body->apply_torque(p_torque); } +void BulletPhysicsDirectBodyState::apply_central_impulse(const Vector3 &p_j) { + body->apply_central_impulse(p_j); +} + void BulletPhysicsDirectBodyState::apply_impulse(const Vector3 &p_pos, const Vector3 &p_j) { body->apply_impulse(p_pos, p_j); } @@ -154,6 +158,10 @@ Vector3 BulletPhysicsDirectBodyState::get_contact_local_normal(int p_contact_idx return body->collisions[p_contact_idx].hitNormal; } +float BulletPhysicsDirectBodyState::get_contact_impulse(int p_contact_idx) const { + return body->collisions[p_contact_idx].appliedImpulse; +} + int BulletPhysicsDirectBodyState::get_contact_local_shape(int p_contact_idx) const { return body->collisions[p_contact_idx].local_shape; } @@ -175,7 +183,7 @@ int BulletPhysicsDirectBodyState::get_contact_collider_shape(int p_contact_idx) } Vector3 BulletPhysicsDirectBodyState::get_contact_collider_velocity_at_position(int p_contact_idx) const { - RigidBodyBullet::CollisionData &colDat = body->collisions[p_contact_idx]; + RigidBodyBullet::CollisionData &colDat = body->collisions.write[p_contact_idx]; btVector3 hitLocation; G_TO_B(colDat.hitLocalLocation, hitLocation); @@ -220,19 +228,20 @@ void RigidBodyBullet::KinematicUtilities::copyAllOwnerShapes() { continue; } - shapes[i].transform = shape_wrapper->transform; - shapes[i].transform.getOrigin() *= owner_scale; + shapes.write[i].transform = shape_wrapper->transform; + shapes.write[i].transform.getOrigin() *= owner_scale; switch (shape_wrapper->shape->get_type()) { case PhysicsServer::SHAPE_SPHERE: case PhysicsServer::SHAPE_BOX: case PhysicsServer::SHAPE_CAPSULE: + case PhysicsServer::SHAPE_CYLINDER: case PhysicsServer::SHAPE_CONVEX_POLYGON: case PhysicsServer::SHAPE_RAY: { - shapes[i].shape = static_cast<btConvexShape *>(shape_wrapper->shape->create_bt_shape(owner_scale * shape_wrapper->scale, safe_margin)); + shapes.write[i].shape = static_cast<btConvexShape *>(shape_wrapper->shape->create_bt_shape(owner_scale * shape_wrapper->scale, safe_margin)); } break; default: WARN_PRINT("This shape is not supported to be kinematic!"); - shapes[i].shape = NULL; + shapes.write[i].shape = NULL; } } } @@ -240,7 +249,7 @@ void RigidBodyBullet::KinematicUtilities::copyAllOwnerShapes() { void RigidBodyBullet::KinematicUtilities::just_delete_shapes(int new_size) { for (int i = shapes.size() - 1; 0 <= i; --i) { if (shapes[i].shape) { - bulletdelete(shapes[i].shape); + bulletdelete(shapes.write[i].shape); } } shapes.resize(new_size); @@ -250,29 +259,31 @@ RigidBodyBullet::RigidBodyBullet() : RigidCollisionObjectBullet(CollisionObjectBullet::TYPE_RIGID_BODY), kinematic_utilities(NULL), locked_axis(0), - gravity_scale(1), mass(1), + gravity_scale(1), linearDamp(0), angularDamp(0), can_sleep(true), omit_forces_integration(false), - force_integration_callback(NULL), - isTransformChanged(false), - previousActiveState(true), + can_integrate_forces(false), maxCollisionsDetection(0), collisionsCount(0), + prev_collision_count(0), maxAreasWhereIam(10), areaWhereIamCount(0), countGravityPointSpaces(0), - isScratchedSpaceOverrideModificator(false) { + isScratchedSpaceOverrideModificator(false), + previousActiveState(true), + force_integration_callback(NULL) { godotMotionState = bulletnew(GodotMotionState(this)); // Initial properties const btVector3 localInertia(0, 0, 0); - btRigidBody::btRigidBodyConstructionInfo cInfo(mass, godotMotionState, compoundShape, localInertia); + btRigidBody::btRigidBodyConstructionInfo cInfo(mass, godotMotionState, NULL, localInertia); btBody = bulletnew(btRigidBody(cInfo)); + reload_shapes(); setupBulletCollisionObject(btBody); set_mode(PhysicsServer::BODY_MODE_RIGID); @@ -280,9 +291,12 @@ RigidBodyBullet::RigidBodyBullet() : areasWhereIam.resize(maxAreasWhereIam); for (int i = areasWhereIam.size() - 1; 0 <= i; --i) { - areasWhereIam[i] = NULL; + areasWhereIam.write[i] = NULL; } btBody->setSleepingThresholds(0.2, 0.2); + + prev_collision_traces = &collision_traces_1; + curr_collision_traces = &collision_traces_2; } RigidBodyBullet::~RigidBodyBullet() { @@ -305,17 +319,24 @@ void RigidBodyBullet::destroy_kinematic_utilities() { } } +void RigidBodyBullet::main_shape_changed() { + CRASH_COND(!get_main_shape()) + btBody->setCollisionShape(get_main_shape()); + set_continuous_collision_detection(is_continuous_collision_detection_enabled()); // Reset +} + void RigidBodyBullet::reload_body() { if (space) { space->remove_rigid_body(this); - space->add_rigid_body(this); + if (get_main_shape()) + space->add_rigid_body(this); } } void RigidBodyBullet::set_space(SpaceBullet *p_space) { // Clear the old space if there is one if (space) { - isTransformChanged = false; + can_integrate_forces = false; // Remove all eventual constraints assert_no_constraints(); @@ -332,8 +353,8 @@ void RigidBodyBullet::set_space(SpaceBullet *p_space) { } void RigidBodyBullet::dispatch_callbacks() { - /// The check isTransformChanged is necessary in order to call integrated forces only when the first transform is sent - if ((btBody->isActive() || previousActiveState != btBody->isActive()) && force_integration_callback && isTransformChanged) { + /// The check isFirstTransformChanged is necessary in order to call integrated forces only when the first transform is sent + if ((btBody->isKinematicObject() || btBody->isActive() || previousActiveState != btBody->isActive()) && force_integration_callback && can_integrate_forces) { if (omit_forces_integration) btBody->clearForces(); @@ -382,10 +403,6 @@ void RigidBodyBullet::set_force_integration_callback(ObjectID p_id, const String } } -void RigidBodyBullet::scratch() { - isTransformChanged = true; -} - void RigidBodyBullet::scratch_space_override_modificator() { isScratchedSpaceOverrideModificator = true; } @@ -397,27 +414,50 @@ void RigidBodyBullet::on_collision_filters_change() { } void RigidBodyBullet::on_collision_checker_start() { + + prev_collision_count = collisionsCount; collisionsCount = 0; + + // Swap array + Vector<RigidBodyBullet *> *s = prev_collision_traces; + prev_collision_traces = curr_collision_traces; + curr_collision_traces = s; +} + +void RigidBodyBullet::on_collision_checker_end() { + // Always true if active and not a static or kinematic body + isTransformChanged = btBody->isActive() && !btBody->isStaticOrKinematicObject(); } -bool RigidBodyBullet::add_collision_object(RigidBodyBullet *p_otherObject, const Vector3 &p_hitWorldLocation, const Vector3 &p_hitLocalLocation, const Vector3 &p_hitNormal, int p_other_shape_index, int p_local_shape_index) { +bool RigidBodyBullet::add_collision_object(RigidBodyBullet *p_otherObject, const Vector3 &p_hitWorldLocation, const Vector3 &p_hitLocalLocation, const Vector3 &p_hitNormal, const float &p_appliedImpulse, int p_other_shape_index, int p_local_shape_index) { if (collisionsCount >= maxCollisionsDetection) { return false; } - CollisionData &cd = collisions[collisionsCount]; + CollisionData &cd = collisions.write[collisionsCount]; cd.hitLocalLocation = p_hitLocalLocation; cd.otherObject = p_otherObject; cd.hitWorldLocation = p_hitWorldLocation; cd.hitNormal = p_hitNormal; + cd.appliedImpulse = p_appliedImpulse; cd.other_object_shape = p_other_shape_index; cd.local_shape = p_local_shape_index; + curr_collision_traces->write[collisionsCount] = p_otherObject; + ++collisionsCount; return true; } +bool RigidBodyBullet::was_colliding(RigidBodyBullet *p_other_object) { + for (int i = prev_collision_count - 1; 0 <= i; --i) { + if ((*prev_collision_traces)[i] == p_other_object) + return true; + } + return false; +} + void RigidBodyBullet::assert_no_constraints() { if (btBody->getNumConstraintRefs()) { WARN_PRINT("A body with a joints is destroyed. Please check the implementation in order to destroy the joint before the body."); @@ -501,7 +541,7 @@ real_t RigidBodyBullet::get_param(PhysicsServer::BodyParameter p_param) const { void RigidBodyBullet::set_mode(PhysicsServer::BodyMode p_mode) { // This is necessary to block force_integration untile next move - isTransformChanged = false; + can_integrate_forces = false; destroy_kinematic_utilities(); // The mode change is relevant to its mass switch (p_mode) { @@ -516,20 +556,18 @@ void RigidBodyBullet::set_mode(PhysicsServer::BodyMode p_mode) { reload_axis_lock(); _internal_set_mass(0); break; - case PhysicsServer::BODY_MODE_RIGID: { + case PhysicsServer::BODY_MODE_RIGID: mode = PhysicsServer::BODY_MODE_RIGID; reload_axis_lock(); _internal_set_mass(0 == mass ? 1 : mass); scratch_space_override_modificator(); break; - } - case PhysicsServer::BODY_MODE_CHARACTER: { + case PhysicsServer::BODY_MODE_CHARACTER: mode = PhysicsServer::BODY_MODE_CHARACTER; reload_axis_lock(); _internal_set_mass(0 == mass ? 1 : mass); scratch_space_override_modificator(); break; - } } btBody->setAngularVelocity(btVector3(0, 0, 0)); @@ -701,15 +739,19 @@ void RigidBodyBullet::set_continuous_collision_detection(bool p_enable) { if (p_enable) { // This threshold enable CCD if the object moves more than // 1 meter in one simulation frame - btBody->setCcdMotionThreshold(1); + btBody->setCcdMotionThreshold(0.1); /// Calculate using the rule writte below the CCD swept sphere radius /// CCD works on an embedded sphere of radius, make sure this radius /// is embedded inside the convex objects, preferably smaller: /// for an object of dimensions 1 meter, try 0.2 - btVector3 center; btScalar radius; - btBody->getCollisionShape()->getBoundingSphere(center, radius); + if (btBody->getCollisionShape()) { + btVector3 center; + btBody->getCollisionShape()->getBoundingSphere(center, radius); + } else { + radius = 0; + } btBody->setCcdSweptSphereRadius(radius * 0.2); } else { btBody->setCcdMotionThreshold(0.); @@ -718,7 +760,7 @@ void RigidBodyBullet::set_continuous_collision_detection(bool p_enable) { } bool RigidBodyBullet::is_continuous_collision_detection_enabled() const { - return 0. != btBody->getCcdMotionThreshold(); + return 0. < btBody->getCcdMotionThreshold(); } void RigidBodyBullet::set_linear_velocity(const Vector3 &p_velocity) { @@ -751,10 +793,12 @@ Vector3 RigidBodyBullet::get_angular_velocity() const { void RigidBodyBullet::set_transform__bullet(const btTransform &p_global_transform) { if (mode == PhysicsServer::BODY_MODE_KINEMATIC) { + if (space) + btBody->setLinearVelocity((p_global_transform.getOrigin() - btBody->getWorldTransform().getOrigin()) / space->get_delta_time()); // The kinematic use MotionState class godotMotionState->moveBody(p_global_transform); } - btBody->setWorldTransform(p_global_transform); + CollisionObjectBullet::set_transform__bullet(p_global_transform); } const btTransform &RigidBodyBullet::get_transform__bullet() const { @@ -767,15 +811,20 @@ const btTransform &RigidBodyBullet::get_transform__bullet() const { } } -void RigidBodyBullet::on_shapes_changed() { - RigidCollisionObjectBullet::on_shapes_changed(); +void RigidBodyBullet::reload_shapes() { + RigidCollisionObjectBullet::reload_shapes(); const btScalar invMass = btBody->getInvMass(); const btScalar mass = invMass == 0 ? 0 : 1 / invMass; - btVector3 inertia; - btBody->getCollisionShape()->calculateLocalInertia(mass, inertia); - btBody->setMassProps(mass, inertia); + if (mainShape) { + // inertia initialised zero here because some of bullet's collision + // shapes incorrectly do not set the vector in calculateLocalIntertia. + // Arbitrary zero is preferable to undefined behaviour. + btVector3 inertia(0, 0, 0); + mainShape->calculateLocalInertia(mass, inertia); + btBody->setMassProps(mass, inertia); + } btBody->updateInertiaTensor(); reload_kinematic_shapes(); @@ -794,15 +843,15 @@ void RigidBodyBullet::on_enter_area(AreaBullet *p_area) { if (NULL == areasWhereIam[i]) { // This area has the highest priority - areasWhereIam[i] = p_area; + areasWhereIam.write[i] = p_area; break; } else { if (areasWhereIam[i]->get_spOv_priority() > p_area->get_spOv_priority()) { // The position was found, just shift all elements for (int j = i; j < areaWhereIamCount; ++j) { - areasWhereIam[j + 1] = areasWhereIam[j]; + areasWhereIam.write[j + 1] = areasWhereIam[j]; } - areasWhereIam[i] = p_area; + areasWhereIam.write[i] = p_area; break; } } @@ -824,9 +873,9 @@ void RigidBodyBullet::on_exit_area(AreaBullet *p_area) { bool wasTheAreaFound = false; for (int i = 0; i < areaWhereIamCount; ++i) { if (p_area == areasWhereIam[i]) { - // The area was fount, just shift down all elements + // The area was found, just shift down all elements for (int j = i; j < areaWhereIamCount; ++j) { - areasWhereIam[j] = areasWhereIam[j + 1]; + areasWhereIam.write[j] = areasWhereIam[j + 1]; } wasTheAreaFound = true; break; @@ -839,7 +888,7 @@ void RigidBodyBullet::on_exit_area(AreaBullet *p_area) { } --areaWhereIamCount; - areasWhereIam[areaWhereIamCount] = NULL; // Even if this is not required, I clear the last element to be safe + areasWhereIam.write[areaWhereIamCount] = NULL; // Even if this is not required, I clear the last element to be safe if (PhysicsServer::AREA_SPACE_OVERRIDE_DISABLED != p_area->get_spOv_mode()) { scratch_space_override_modificator(); } @@ -899,10 +948,10 @@ void RigidBodyBullet::reload_space_override_modificator() { } switch (currentArea->get_spOv_mode()) { - ///case PhysicsServer::AREA_SPACE_OVERRIDE_DISABLED: - /// This area does not affect gravity/damp. These are generally areas - /// that exist only to detect collisions, and objects entering or exiting them. - /// break; + case PhysicsServer::AREA_SPACE_OVERRIDE_DISABLED: + /// This area does not affect gravity/damp. These are generally areas + /// that exist only to detect collisions, and objects entering or exiting them. + break; case PhysicsServer::AREA_SPACE_OVERRIDE_COMBINE: /// This area adds its gravity/damp values to whatever has been /// calculated so far. This way, many overlapping areas can combine @@ -961,6 +1010,11 @@ void RigidBodyBullet::reload_kinematic_shapes() { kinematic_utilities->copyAllOwnerShapes(); } +void RigidBodyBullet::notify_transform_changed() { + RigidCollisionObjectBullet::notify_transform_changed(); + can_integrate_forces = true; +} + void RigidBodyBullet::_internal_set_mass(real_t p_mass) { btVector3 localInertia(0, 0, 0); @@ -976,7 +1030,8 @@ void RigidBodyBullet::_internal_set_mass(real_t p_mass) { return; m_isStatic = false; - compoundShape->calculateLocalInertia(p_mass, localInertia); + if (mainShape) + mainShape->calculateLocalInertia(p_mass, localInertia); if (PhysicsServer::BODY_MODE_RIGID == mode) { diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h index b9511243c7..0696073d21 100644 --- a/modules/bullet/rigid_body_bullet.h +++ b/modules/bullet/rigid_body_bullet.h @@ -113,6 +113,7 @@ public: virtual void add_central_force(const Vector3 &p_force); virtual void add_force(const Vector3 &p_force, const Vector3 &p_pos); virtual void add_torque(const Vector3 &p_torque); + virtual void apply_central_impulse(const Vector3 &p_impulse); virtual void apply_impulse(const Vector3 &p_pos, const Vector3 &p_j); virtual void apply_torque_impulse(const Vector3 &p_j); @@ -123,6 +124,7 @@ public: virtual Vector3 get_contact_local_position(int p_contact_idx) const; virtual Vector3 get_contact_local_normal(int p_contact_idx) const; + virtual float get_contact_impulse(int p_contact_idx) const; virtual int get_contact_local_shape(int p_contact_idx) const; virtual RID get_contact_collider(int p_contact_idx) const; @@ -149,6 +151,7 @@ public: Vector3 hitLocalLocation; Vector3 hitWorldLocation; Vector3 hitNormal; + float appliedImpulse; }; struct ForceIntegrationCallback { @@ -199,11 +202,18 @@ private: real_t angularDamp; bool can_sleep; bool omit_forces_integration; + bool can_integrate_forces; Vector<CollisionData> collisions; + Vector<RigidBodyBullet *> collision_traces_1; + Vector<RigidBodyBullet *> collision_traces_2; + Vector<RigidBodyBullet *> *prev_collision_traces; + Vector<RigidBodyBullet *> *curr_collision_traces; + // these parameters are used to avoid vector resize int maxCollisionsDetection; int collisionsCount; + int prev_collision_count; Vector<AreaBullet *> areasWhereIam; // these parameters are used to avoid vector resize @@ -213,7 +223,6 @@ private: int countGravityPointSpaces; bool isScratchedSpaceOverrideModificator; - bool isTransformChanged; bool previousActiveState; // Last check state ForceIntegrationCallback *force_integration_callback; @@ -224,31 +233,42 @@ public: void init_kinematic_utilities(); void destroy_kinematic_utilities(); - _FORCE_INLINE_ class KinematicUtilities *get_kinematic_utilities() const { return kinematic_utilities; } + _FORCE_INLINE_ KinematicUtilities *get_kinematic_utilities() const { return kinematic_utilities; } _FORCE_INLINE_ btRigidBody *get_bt_rigid_body() { return btBody; } + virtual void main_shape_changed(); virtual void reload_body(); virtual void set_space(SpaceBullet *p_space); virtual void dispatch_callbacks(); void set_force_integration_callback(ObjectID p_id, const StringName &p_method, const Variant &p_udata = Variant()); - void scratch(); void scratch_space_override_modificator(); virtual void on_collision_filters_change(); virtual void on_collision_checker_start(); + virtual void on_collision_checker_end(); + void set_max_collisions_detection(int p_maxCollisionsDetection) { + + ERR_FAIL_COND(0 > p_maxCollisionsDetection); + maxCollisionsDetection = p_maxCollisionsDetection; + collisions.resize(p_maxCollisionsDetection); + collision_traces_1.resize(p_maxCollisionsDetection); + collision_traces_2.resize(p_maxCollisionsDetection); + collisionsCount = 0; + prev_collision_count = MIN(prev_collision_count, p_maxCollisionsDetection); } int get_max_collisions_detection() { return maxCollisionsDetection; } bool can_add_collision() { return collisionsCount < maxCollisionsDetection; } - bool add_collision_object(RigidBodyBullet *p_otherObject, const Vector3 &p_hitWorldLocation, const Vector3 &p_hitLocalLocation, const Vector3 &p_hitNormal, int p_other_shape_index, int p_local_shape_index); + bool add_collision_object(RigidBodyBullet *p_otherObject, const Vector3 &p_hitWorldLocation, const Vector3 &p_hitLocalLocation, const Vector3 &p_hitNormal, const float &p_appliedImpulse, int p_other_shape_index, int p_local_shape_index); + bool was_colliding(RigidBodyBullet *p_other_object); void assert_no_constraints(); @@ -298,7 +318,7 @@ public: virtual void set_transform__bullet(const btTransform &p_global_transform); virtual const btTransform &get_transform__bullet() const; - virtual void on_shapes_changed(); + virtual void reload_shapes(); virtual void on_enter_area(AreaBullet *p_area); virtual void on_exit_area(AreaBullet *p_area); @@ -307,6 +327,8 @@ public: /// Kinematic void reload_kinematic_shapes(); + virtual void notify_transform_changed(); + private: void _internal_set_mass(real_t p_mass); }; diff --git a/modules/bullet/shape_bullet.cpp b/modules/bullet/shape_bullet.cpp index 76d9614465..2027d8e1eb 100644 --- a/modules/bullet/shape_bullet.cpp +++ b/modules/bullet/shape_bullet.cpp @@ -34,8 +34,10 @@ #include "bullet_physics_server.h" #include "bullet_types_converter.h" #include "bullet_utilities.h" +#include "core/project_settings.h" #include "shape_owner_bullet.h" +#include <BulletCollision/CollisionDispatch/btInternalEdgeUtility.h> #include <BulletCollision/CollisionShapes/btConvexPointCloudShape.h> #include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h> #include <btBulletCollisionCommon.h> @@ -44,25 +46,27 @@ @author AndreaCatania */ -ShapeBullet::ShapeBullet() {} +ShapeBullet::ShapeBullet() : + margin(0.04) {} ShapeBullet::~ShapeBullet() {} -btCollisionShape *ShapeBullet::create_bt_shape(const Vector3 &p_implicit_scale, real_t p_margin) { +btCollisionShape *ShapeBullet::create_bt_shape(const Vector3 &p_implicit_scale, real_t p_extra_edge) { btVector3 s; G_TO_B(p_implicit_scale, s); - return create_bt_shape(s, p_margin); + return create_bt_shape(s, p_extra_edge); } btCollisionShape *ShapeBullet::prepare(btCollisionShape *p_btShape) const { p_btShape->setUserPointer(const_cast<ShapeBullet *>(this)); - p_btShape->setMargin(0.); + p_btShape->setMargin(margin); return p_btShape; } void ShapeBullet::notifyShapeChanged() { for (Map<ShapeOwnerBullet *, int>::Element *E = owners.front(); E; E = E->next()) { - static_cast<ShapeOwnerBullet *>(E->key())->on_shape_changed(this); + ShapeOwnerBullet *owner = static_cast<ShapeOwnerBullet *>(E->key()); + owner->shape_changed(owner->find_shape(this)); } } @@ -93,6 +97,15 @@ const Map<ShapeOwnerBullet *, int> &ShapeBullet::get_owners() const { return owners; } +void ShapeBullet::set_margin(real_t p_margin) { + margin = p_margin; + notifyShapeChanged(); +} + +real_t ShapeBullet::get_margin() const { + return margin; +} + btEmptyShape *ShapeBullet::create_shape_empty() { return bulletnew(btEmptyShape); } @@ -113,6 +126,10 @@ btCapsuleShapeZ *ShapeBullet::create_shape_capsule(btScalar radius, btScalar hei return bulletnew(btCapsuleShapeZ(radius, height)); } +btCylinderShape *ShapeBullet::create_shape_cylinder(btScalar radius, btScalar height) { + return bulletnew(btCylinderShape(btVector3(radius, height / 2.0, radius))); +} + btConvexPointCloudShape *ShapeBullet::create_shape_convex(btAlignedObjectArray<btVector3> &p_vertices, const btVector3 &p_local_scaling) { return bulletnew(btConvexPointCloudShape(&p_vertices[0], p_vertices.size(), p_local_scaling)); } @@ -162,7 +179,7 @@ void PlaneShapeBullet::setup(const Plane &p_plane) { notifyShapeChanged(); } -btCollisionShape *PlaneShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) { +btCollisionShape *PlaneShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) { btVector3 btPlaneNormal; G_TO_B(plane.normal, btPlaneNormal); return prepare(PlaneShapeBullet::create_shape_plane(btPlaneNormal, plane.d)); @@ -190,8 +207,8 @@ void SphereShapeBullet::setup(real_t p_radius) { notifyShapeChanged(); } -btCollisionShape *SphereShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) { - return prepare(ShapeBullet::create_shape_sphere(radius * p_implicit_scale[0] + p_margin)); +btCollisionShape *SphereShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) { + return prepare(ShapeBullet::create_shape_sphere(radius * p_implicit_scale[0] + p_extra_edge)); } /* Box */ @@ -217,8 +234,8 @@ void BoxShapeBullet::setup(const Vector3 &p_half_extents) { notifyShapeChanged(); } -btCollisionShape *BoxShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) { - return prepare(ShapeBullet::create_shape_box((half_extents * p_implicit_scale) + btVector3(p_margin, p_margin, p_margin))); +btCollisionShape *BoxShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) { + return prepare(ShapeBullet::create_shape_box((half_extents * p_implicit_scale) + btVector3(p_extra_edge, p_extra_edge, p_extra_edge))); } /* Capsule */ @@ -250,8 +267,41 @@ void CapsuleShapeBullet::setup(real_t p_height, real_t p_radius) { notifyShapeChanged(); } -btCollisionShape *CapsuleShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) { - return prepare(ShapeBullet::create_shape_capsule(radius * p_implicit_scale[0] + p_margin, height * p_implicit_scale[1] + p_margin)); +btCollisionShape *CapsuleShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) { + return prepare(ShapeBullet::create_shape_capsule(radius * p_implicit_scale[0] + p_extra_edge, height * p_implicit_scale[1] + p_extra_edge)); +} + +/* Cylinder */ + +CylinderShapeBullet::CylinderShapeBullet() : + ShapeBullet() {} + +void CylinderShapeBullet::set_data(const Variant &p_data) { + Dictionary d = p_data; + ERR_FAIL_COND(!d.has("radius")); + ERR_FAIL_COND(!d.has("height")); + setup(d["height"], d["radius"]); +} + +Variant CylinderShapeBullet::get_data() const { + Dictionary d; + d["radius"] = radius; + d["height"] = height; + return d; +} + +PhysicsServer::ShapeType CylinderShapeBullet::get_type() const { + return PhysicsServer::SHAPE_CYLINDER; +} + +void CylinderShapeBullet::setup(real_t p_height, real_t p_radius) { + radius = p_radius; + height = p_height; + notifyShapeChanged(); +} + +btCollisionShape *CylinderShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) { + return prepare(ShapeBullet::create_shape_cylinder(radius * p_implicit_scale[0] + p_margin, height * p_implicit_scale[1] + p_margin)); } /* Convex polygon */ @@ -267,7 +317,7 @@ void ConvexPolygonShapeBullet::get_vertices(Vector<Vector3> &out_vertices) { const int n_of_vertices = vertices.size(); out_vertices.resize(n_of_vertices); for (int i = n_of_vertices - 1; 0 <= i; --i) { - B_TO_G(vertices[i], out_vertices[i]); + B_TO_G(vertices[i], out_vertices.write[i]); } } @@ -292,11 +342,13 @@ void ConvexPolygonShapeBullet::setup(const Vector<Vector3> &p_vertices) { notifyShapeChanged(); } -btCollisionShape *ConvexPolygonShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) { +btCollisionShape *ConvexPolygonShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) { + if (!vertices.size()) + // This is necessary since 0 vertices + return prepare(ShapeBullet::create_shape_empty()); btCollisionShape *cs(ShapeBullet::create_shape_convex(vertices)); cs->setLocalScaling(p_implicit_scale); prepare(cs); - cs->setMargin(p_margin); return cs; } @@ -309,7 +361,8 @@ ConcavePolygonShapeBullet::ConcavePolygonShapeBullet() : ConcavePolygonShapeBullet::~ConcavePolygonShapeBullet() { if (meshShape) { delete meshShape->getMeshInterface(); - delete meshShape; + delete meshShape->getTriangleInfoMap(); + bulletdelete(meshShape); } faces = PoolVector<Vector3>(); } @@ -331,6 +384,7 @@ void ConcavePolygonShapeBullet::setup(PoolVector<Vector3> p_faces) { if (meshShape) { /// Clear previous created shape delete meshShape->getMeshInterface(); + delete meshShape->getTriangleInfoMap(); bulletdelete(meshShape); } int src_face_count = faces.size(); @@ -348,16 +402,22 @@ void ConcavePolygonShapeBullet::setup(PoolVector<Vector3> p_faces) { btVector3 supVec_1; btVector3 supVec_2; for (int i = 0; i < src_face_count; ++i) { - G_TO_B(facesr[i * 3], supVec_0); + G_TO_B(facesr[i * 3 + 0], supVec_0); G_TO_B(facesr[i * 3 + 1], supVec_1); G_TO_B(facesr[i * 3 + 2], supVec_2); - shapeInterface->addTriangle(supVec_0, supVec_1, supVec_2); + // Inverted from standard godot otherwise btGenerateInternalEdgeInfo generates wrong edge info + shapeInterface->addTriangle(supVec_2, supVec_1, supVec_0); } const bool useQuantizedAabbCompression = true; meshShape = bulletnew(btBvhTriangleMeshShape(shapeInterface, useQuantizedAabbCompression)); + + if (GLOBAL_DEF("physics/3d/smooth_trimesh_collision", false)) { + btTriangleInfoMap *triangleInfoMap = new btTriangleInfoMap(); + btGenerateInternalEdgeInfo(meshShape, triangleInfoMap); + } } else { meshShape = NULL; ERR_PRINT("The faces count are 0, the mesh shape cannot be created"); @@ -365,14 +425,14 @@ void ConcavePolygonShapeBullet::setup(PoolVector<Vector3> p_faces) { notifyShapeChanged(); } -btCollisionShape *ConcavePolygonShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) { +btCollisionShape *ConcavePolygonShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) { btCollisionShape *cs = ShapeBullet::create_shape_concave(meshShape); if (!cs) // This is necessary since if 0 faces the creation of concave return NULL cs = ShapeBullet::create_shape_empty(); cs->setLocalScaling(p_implicit_scale); prepare(cs); - cs->setMargin(p_margin); + cs->setMargin(0); return cs; } @@ -401,7 +461,47 @@ void HeightMapShapeBullet::set_data(const Variant &p_data) { int l_width = d["width"]; int l_depth = d["depth"]; - PoolVector<real_t> l_heights = d["heights"]; + + // TODO This code will need adjustments if real_t is set to `double`, + // because that precision is unnecessary for a heightmap and Bullet doesn't support it... + + PoolVector<real_t> l_heights; + Variant l_heights_v = d["heights"]; + + if (l_heights_v.get_type() == Variant::POOL_REAL_ARRAY) { + // Ready-to-use heights can be passed + + l_heights = l_heights_v; + + } else if (l_heights_v.get_type() == Variant::OBJECT) { + // If an image is passed, we have to convert it to a format Bullet supports. + // this would be expensive to do with a script, so it's nice to have it here. + + Ref<Image> l_image = l_heights_v; + ERR_FAIL_COND(l_image.is_null()); + + // Float is the only common format between Godot and Bullet that can be used for decent collision. + // (Int16 would be nice too but we still don't have it) + // We could convert here automatically but it's better to not be intrusive and let the caller do it if necessary. + ERR_FAIL_COND(l_image->get_format() != Image::FORMAT_RF); + + PoolByteArray im_data = l_image->get_data(); + + l_heights.resize(l_image->get_width() * l_image->get_width()); + + PoolRealArray::Write w = l_heights.write(); + PoolByteArray::Read r = im_data.read(); + float *rp = (float *)r.ptr(); + // At this point, `rp` could be used directly for Bullet, but I don't know how safe it would be. + + for (int i = 0; i < l_heights.size(); ++i) { + w[i] = rp[i]; + } + + } else { + ERR_EXPLAIN("Expected PoolRealArray or float Image."); + ERR_FAIL(); + } ERR_FAIL_COND(l_width <= 0); ERR_FAIL_COND(l_depth <= 0); @@ -437,19 +537,8 @@ PhysicsServer::ShapeType HeightMapShapeBullet::get_type() const { void HeightMapShapeBullet::setup(PoolVector<real_t> &p_heights, int p_width, int p_depth, real_t p_min_height, real_t p_max_height) { // TODO cell size must be tweaked using localScaling, which is a shared property for all Bullet shapes - { // Copy - - // TODO If Godot supported 16-bit integer image format, we could share the same memory block for heightfields - // without having to copy anything, optimizing memory and loading performance (Bullet only reads and doesn't take ownership of the data). - - const int heights_size = p_heights.size(); - heights.resize(heights_size); - PoolVector<real_t>::Read p_heights_r = p_heights.read(); - PoolVector<real_t>::Write heights_w = heights.write(); - for (int i = heights_size - 1; 0 <= i; --i) { - heights_w[i] = p_heights_r[i]; - } - } + // If this array is resized outside of here, it should be preserved due to CoW + heights = p_heights; width = p_width; depth = p_depth; @@ -458,11 +547,10 @@ void HeightMapShapeBullet::setup(PoolVector<real_t> &p_heights, int p_width, int notifyShapeChanged(); } -btCollisionShape *HeightMapShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) { +btCollisionShape *HeightMapShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) { btCollisionShape *cs(ShapeBullet::create_shape_height_field(heights, width, depth, min_height, max_height)); cs->setLocalScaling(p_implicit_scale); prepare(cs); - cs->setMargin(p_margin); return cs; } @@ -496,6 +584,6 @@ void RayShapeBullet::setup(real_t p_length, bool p_slips_on_slope) { notifyShapeChanged(); } -btCollisionShape *RayShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) { - return prepare(ShapeBullet::create_shape_ray(length * p_implicit_scale[1] + p_margin, slips_on_slope)); +btCollisionShape *RayShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) { + return prepare(ShapeBullet::create_shape_ray(length * p_implicit_scale[1] + p_extra_edge, slips_on_slope)); } diff --git a/modules/bullet/shape_bullet.h b/modules/bullet/shape_bullet.h index abeea0f9ce..9a1c8f5bfa 100644 --- a/modules/bullet/shape_bullet.h +++ b/modules/bullet/shape_bullet.h @@ -31,8 +31,8 @@ #ifndef SHAPE_BULLET_H #define SHAPE_BULLET_H +#include "core/math/geometry.h" #include "core/variant.h" -#include "geometry.h" #include "rid_bullet.h" #include "servers/physics_server.h" @@ -52,6 +52,7 @@ class btBvhTriangleMeshShape; class ShapeBullet : public RIDBullet { Map<ShapeOwnerBullet *, int> owners; + real_t margin; protected: /// return self @@ -62,14 +63,17 @@ public: ShapeBullet(); virtual ~ShapeBullet(); - btCollisionShape *create_bt_shape(const Vector3 &p_implicit_scale, real_t p_margin = 0); - virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0) = 0; + btCollisionShape *create_bt_shape(const Vector3 &p_implicit_scale, real_t p_extra_edge = 0); + virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0) = 0; void add_owner(ShapeOwnerBullet *p_owner); void remove_owner(ShapeOwnerBullet *p_owner, bool p_permanentlyFromThisBody = false); bool is_owner(ShapeOwnerBullet *p_owner) const; const Map<ShapeOwnerBullet *, int> &get_owners() const; + void set_margin(real_t p_margin); + real_t get_margin() const; + /// Setup the shape virtual void set_data(const Variant &p_data) = 0; virtual Variant get_data() const = 0; @@ -82,6 +86,7 @@ public: static class btSphereShape *create_shape_sphere(btScalar radius); static class btBoxShape *create_shape_box(const btVector3 &boxHalfExtents); static class btCapsuleShapeZ *create_shape_capsule(btScalar radius, btScalar height); + static class btCylinderShape *create_shape_cylinder(btScalar radius, btScalar height); /// IMPORTANT: Remember to delete the shape interface by calling: delete my_shape->getMeshInterface(); static class btConvexPointCloudShape *create_shape_convex(btAlignedObjectArray<btVector3> &p_vertices, const btVector3 &p_local_scaling = btVector3(1, 1, 1)); static class btScaledBvhTriangleMeshShape *create_shape_concave(btBvhTriangleMeshShape *p_mesh_shape, const btVector3 &p_local_scaling = btVector3(1, 1, 1)); @@ -99,7 +104,7 @@ public: virtual void set_data(const Variant &p_data); virtual Variant get_data() const; virtual PhysicsServer::ShapeType get_type() const; - virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0); + virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0); private: void setup(const Plane &p_plane); @@ -116,7 +121,7 @@ public: virtual void set_data(const Variant &p_data); virtual Variant get_data() const; virtual PhysicsServer::ShapeType get_type() const; - virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0); + virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0); private: void setup(real_t p_radius); @@ -133,7 +138,7 @@ public: virtual void set_data(const Variant &p_data); virtual Variant get_data() const; virtual PhysicsServer::ShapeType get_type() const; - virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0); + virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0); private: void setup(const Vector3 &p_half_extents); @@ -152,6 +157,25 @@ public: virtual void set_data(const Variant &p_data); virtual Variant get_data() const; virtual PhysicsServer::ShapeType get_type() const; + virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0); + +private: + void setup(real_t p_height, real_t p_radius); +}; + +class CylinderShapeBullet : public ShapeBullet { + + real_t height; + real_t radius; + +public: + CylinderShapeBullet(); + + _FORCE_INLINE_ real_t get_height() { return height; } + _FORCE_INLINE_ real_t get_radius() { return radius; } + virtual void set_data(const Variant &p_data); + virtual Variant get_data() const; + virtual PhysicsServer::ShapeType get_type() const; virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0); private: @@ -169,7 +193,7 @@ public: void get_vertices(Vector<Vector3> &out_vertices); virtual Variant get_data() const; virtual PhysicsServer::ShapeType get_type() const; - virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0); + virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0); private: void setup(const Vector<Vector3> &p_vertices); @@ -187,7 +211,7 @@ public: virtual void set_data(const Variant &p_data); virtual Variant get_data() const; virtual PhysicsServer::ShapeType get_type() const; - virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0); + virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0); private: void setup(PoolVector<Vector3> p_faces); @@ -207,7 +231,7 @@ public: virtual void set_data(const Variant &p_data); virtual Variant get_data() const; virtual PhysicsServer::ShapeType get_type() const; - virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0); + virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0); private: void setup(PoolVector<real_t> &p_heights, int p_width, int p_depth, real_t p_min_height, real_t p_max_height); @@ -224,7 +248,7 @@ public: virtual void set_data(const Variant &p_data); virtual Variant get_data() const; virtual PhysicsServer::ShapeType get_type() const; - virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0); + virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0); private: void setup(real_t p_length, bool p_slips_on_slope); diff --git a/modules/bullet/shape_owner_bullet.h b/modules/bullet/shape_owner_bullet.h index 29d42d12f2..72ddbc482c 100644 --- a/modules/bullet/shape_owner_bullet.h +++ b/modules/bullet/shape_owner_bullet.h @@ -45,11 +45,10 @@ class CollisionObjectBullet; /// E.G. BodyShape is a child of this class ShapeOwnerBullet { public: - /// This is used to set new shape or replace existing - //virtual void _internal_replaceShape(btCollisionShape *p_old_shape, btCollisionShape *p_new_shape) = 0; - virtual void on_shape_changed(const ShapeBullet *const p_shape) = 0; - virtual void on_shapes_changed() = 0; - virtual void remove_shape(class ShapeBullet *p_shape) = 0; + virtual int find_shape(ShapeBullet *p_shape) const = 0; + virtual void shape_changed(int p_shape_index) = 0; + virtual void reload_shapes() = 0; + virtual void remove_shape_full(class ShapeBullet *p_shape) = 0; virtual ~ShapeOwnerBullet() {} }; #endif diff --git a/modules/bullet/slider_joint_bullet.cpp b/modules/bullet/slider_joint_bullet.cpp index 9e1cd23989..9016ec3bf5 100644 --- a/modules/bullet/slider_joint_bullet.cpp +++ b/modules/bullet/slider_joint_bullet.cpp @@ -366,6 +366,7 @@ void SliderJointBullet::set_param(PhysicsServer::SliderJointParam p_param, real_ case PhysicsServer::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS: setSoftnessOrthoAng(p_value); break; case PhysicsServer::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION: setRestitutionOrthoAng(p_value); break; case PhysicsServer::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING: setDampingOrthoAng(p_value); break; + case PhysicsServer::SLIDER_JOINT_MAX: break; // Can't happen, but silences warning } } diff --git a/modules/bullet/soft_body_bullet.cpp b/modules/bullet/soft_body_bullet.cpp index 5c20eb73f1..94f350210f 100644 --- a/modules/bullet/soft_body_bullet.cpp +++ b/modules/bullet/soft_body_bullet.cpp @@ -32,42 +32,24 @@ #include "bullet_types_converter.h" #include "bullet_utilities.h" -#include "scene/3d/immediate_geometry.h" +#include "scene/3d/soft_body.h" #include "space_bullet.h" -/** - @author AndreaCatania -*/ - SoftBodyBullet::SoftBodyBullet() : CollisionObjectBullet(CollisionObjectBullet::TYPE_SOFT_BODY), - mass(1), - simulation_precision(5), - stiffness(0.5f), - pressure_coefficient(50), - damping_coefficient(0.005), - drag_coefficient(0.005), bt_soft_body(NULL), - soft_shape_type(SOFT_SHAPETYPE_NONE), isScratched(false), - soft_body_shape_data(NULL) { - - test_geometry = memnew(ImmediateGeometry); - - red_mat = Ref<SpatialMaterial>(memnew(SpatialMaterial)); - red_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true); - red_mat->set_line_width(20.0); - red_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - red_mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - red_mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true); - red_mat->set_albedo(Color(1, 0, 0, 1)); - test_geometry->set_material_override(red_mat); - - test_is_in_scene = false; -} + simulation_precision(5), + total_mass(1.), + linear_stiffness(0.5), + areaAngular_stiffness(0.5), + volume_stiffness(0.5), + pressure_coefficient(0.), + pose_matching_coefficient(0.), + damping_coefficient(0.01), + drag_coefficient(0.) {} SoftBodyBullet::~SoftBodyBullet() { - bulletdelete(soft_body_shape_data); } void SoftBodyBullet::reload_body() { @@ -80,8 +62,6 @@ void SoftBodyBullet::reload_body() { void SoftBodyBullet::set_space(SpaceBullet *p_space) { if (space) { isScratched = false; - - // Remove this object from the physics world space->remove_soft_body(this); } @@ -90,86 +70,175 @@ void SoftBodyBullet::set_space(SpaceBullet *p_space) { if (space) { space->add_soft_body(this); } - - reload_soft_body(); } -void SoftBodyBullet::dispatch_callbacks() { - if (!bt_soft_body) { +void SoftBodyBullet::on_enter_area(AreaBullet *p_area) {} + +void SoftBodyBullet::on_exit_area(AreaBullet *p_area) {} + +void SoftBodyBullet::update_visual_server(SoftBodyVisualServerHandler *p_visual_server_handler) { + if (!bt_soft_body) return; + + /// Update visual server vertices + const btSoftBody::tNodeArray &nodes(bt_soft_body->m_nodes); + const int nodes_count = nodes.size(); + + const Vector<int> *vs_indices; + const void *vertex_position; + const void *vertex_normal; + + for (int vertex_index = 0; vertex_index < nodes_count; ++vertex_index) { + vertex_position = reinterpret_cast<const void *>(&nodes[vertex_index].m_x); + vertex_normal = reinterpret_cast<const void *>(&nodes[vertex_index].m_n); + + vs_indices = &indices_table[vertex_index]; + + const int vs_indices_size(vs_indices->size()); + for (int x = 0; x < vs_indices_size; ++x) { + p_visual_server_handler->set_vertex((*vs_indices)[x], vertex_position); + p_visual_server_handler->set_normal((*vs_indices)[x], vertex_normal); + } } - if (!test_is_in_scene) { - test_is_in_scene = true; - SceneTree::get_singleton()->get_current_scene()->add_child(test_geometry); + /// Generate AABB + btVector3 aabb_min; + btVector3 aabb_max; + bt_soft_body->getAabb(aabb_min, aabb_max); + + btVector3 size(aabb_max - aabb_min); + + AABB aabb; + B_TO_G(aabb_min, aabb.position); + B_TO_G(size, aabb.size); + + p_visual_server_handler->set_aabb(aabb); +} + +void SoftBodyBullet::set_soft_mesh(const Ref<Mesh> &p_mesh) { + + if (p_mesh.is_null()) + soft_mesh.unref(); + else + soft_mesh = p_mesh; + + if (soft_mesh.is_null()) { + + destroy_soft_body(); + return; } - test_geometry->clear(); - test_geometry->begin(Mesh::PRIMITIVE_LINES, NULL); - bool first = true; - Vector3 pos; - for (int i = 0; i < bt_soft_body->m_nodes.size(); ++i) { - const btSoftBody::Node &n = bt_soft_body->m_nodes[i]; - B_TO_G(n.m_x, pos); - test_geometry->add_vertex(pos); - if (!first) { - test_geometry->add_vertex(pos); - } else { - first = false; - } + Array arrays = soft_mesh->surface_get_arrays(0); + ERR_FAIL_COND(!(soft_mesh->surface_get_format(0) & VS::ARRAY_FORMAT_INDEX)); + set_trimesh_body_shape(arrays[VS::ARRAY_INDEX], arrays[VS::ARRAY_VERTEX]); +} + +void SoftBodyBullet::destroy_soft_body() { + + if (!bt_soft_body) + return; + + if (space) { + /// Remove from world before deletion + space->remove_soft_body(this); } - test_geometry->end(); + + destroyBulletCollisionObject(); + bt_soft_body = NULL; +} + +void SoftBodyBullet::set_soft_transform(const Transform &p_transform) { + reset_all_node_positions(); + move_all_nodes(p_transform); } -void SoftBodyBullet::on_collision_filters_change() { +void SoftBodyBullet::move_all_nodes(const Transform &p_transform) { + if (!bt_soft_body) + return; + btTransform bt_transf; + G_TO_B(p_transform, bt_transf); + bt_soft_body->transform(bt_transf); } -void SoftBodyBullet::on_collision_checker_start() { +void SoftBodyBullet::set_node_position(int p_node_index, const Vector3 &p_global_position) { + btVector3 bt_pos; + G_TO_B(p_global_position, bt_pos); + set_node_position(p_node_index, bt_pos); } -void SoftBodyBullet::on_enter_area(AreaBullet *p_area) { +void SoftBodyBullet::set_node_position(int p_node_index, const btVector3 &p_global_position) { + if (bt_soft_body) { + bt_soft_body->m_nodes[p_node_index].m_x = p_global_position; + } } -void SoftBodyBullet::on_exit_area(AreaBullet *p_area) { +void SoftBodyBullet::get_node_position(int p_node_index, Vector3 &r_position) const { + if (bt_soft_body) { + B_TO_G(bt_soft_body->m_nodes[p_node_index].m_x, r_position); + } } -void SoftBodyBullet::set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num) { +void SoftBodyBullet::get_node_offset(int p_node_index, Vector3 &r_offset) const { + if (soft_mesh.is_null()) + return; + + Array arrays = soft_mesh->surface_get_arrays(0); + PoolVector<Vector3> vertices(arrays[VS::ARRAY_VERTEX]); - TrimeshSoftShapeData *shape_data = bulletnew(TrimeshSoftShapeData); - shape_data->m_triangles_indices = p_indices; - shape_data->m_vertices = p_vertices; - shape_data->m_triangles_num = p_triangles_num; + if (0 <= p_node_index && vertices.size() > p_node_index) { + r_offset = vertices[p_node_index]; + } +} - set_body_shape_data(shape_data, SOFT_SHAPE_TYPE_TRIMESH); - reload_soft_body(); +void SoftBodyBullet::get_node_offset(int p_node_index, btVector3 &r_offset) const { + Vector3 off; + get_node_offset(p_node_index, off); + G_TO_B(off, r_offset); } -void SoftBodyBullet::set_body_shape_data(SoftShapeData *p_soft_shape_data, SoftShapeType p_type) { - bulletdelete(soft_body_shape_data); - soft_body_shape_data = p_soft_shape_data; - soft_shape_type = p_type; +void SoftBodyBullet::set_node_mass(int node_index, btScalar p_mass) { + if (0 >= p_mass) { + pin_node(node_index); + } else { + unpin_node(node_index); + } + if (bt_soft_body) { + bt_soft_body->setMass(node_index, p_mass); + } } -void SoftBodyBullet::set_transform(const Transform &p_transform) { - transform = p_transform; +btScalar SoftBodyBullet::get_node_mass(int node_index) const { if (bt_soft_body) { - // TODO the softbody set new transform considering the current transform as center of world - // like if it's local transform, so I must fix this by setting nwe transform considering the old - btTransform bt_trans; - G_TO_B(transform, bt_trans); - //bt_soft_body->transform(bt_trans); + return bt_soft_body->getMass(node_index); + } else { + return -1 == search_node_pinned(node_index) ? 1 : 0; } } -const Transform &SoftBodyBullet::get_transform() const { - return transform; +void SoftBodyBullet::reset_all_node_mass() { + if (bt_soft_body) { + for (int i = pinned_nodes.size() - 1; 0 <= i; --i) { + bt_soft_body->setMass(pinned_nodes[i], 1); + } + } + pinned_nodes.resize(0); } -void SoftBodyBullet::get_first_node_origin(btVector3 &p_out_origin) const { - if (bt_soft_body && bt_soft_body->m_nodes.size()) { - p_out_origin = bt_soft_body->m_nodes[0].m_x; - } else { - p_out_origin.setZero(); +void SoftBodyBullet::reset_all_node_positions() { + if (soft_mesh.is_null()) + return; + + Array arrays = soft_mesh->surface_get_arrays(0); + PoolVector<Vector3> vs_vertices(arrays[VS::ARRAY_VERTEX]); + PoolVector<Vector3>::Read vs_vertices_read = vs_vertices.read(); + + for (int vertex_index = bt_soft_body->m_nodes.size() - 1; 0 <= vertex_index; --vertex_index) { + + G_TO_B(vs_vertices_read[indices_table[vertex_index][0]], bt_soft_body->m_nodes[vertex_index].m_x); + + bt_soft_body->m_nodes[vertex_index].m_q = bt_soft_body->m_nodes[vertex_index].m_x; + bt_soft_body->m_nodes[vertex_index].m_v = btVector3(0, 0, 0); + bt_soft_body->m_nodes[vertex_index].m_f = btVector3(0, 0, 0); } } @@ -181,22 +250,34 @@ void SoftBodyBullet::set_activation_state(bool p_active) { } } -void SoftBodyBullet::set_mass(real_t p_val) { +void SoftBodyBullet::set_total_mass(real_t p_val) { if (0 >= p_val) { p_val = 1; } - mass = p_val; + total_mass = p_val; + if (bt_soft_body) { + bt_soft_body->setTotalMass(total_mass); + } +} + +void SoftBodyBullet::set_linear_stiffness(real_t p_val) { + linear_stiffness = p_val; + if (bt_soft_body) { + mat0->m_kLST = linear_stiffness; + } +} + +void SoftBodyBullet::set_areaAngular_stiffness(real_t p_val) { + areaAngular_stiffness = p_val; if (bt_soft_body) { - bt_soft_body->setTotalMass(mass); + mat0->m_kAST = areaAngular_stiffness; } } -void SoftBodyBullet::set_stiffness(real_t p_val) { - stiffness = p_val; +void SoftBodyBullet::set_volume_stiffness(real_t p_val) { + volume_stiffness = p_val; if (bt_soft_body) { - mat0->m_kAST = stiffness; - mat0->m_kLST = stiffness; - mat0->m_kVST = stiffness; + mat0->m_kVST = volume_stiffness; } } @@ -204,6 +285,9 @@ void SoftBodyBullet::set_simulation_precision(int p_val) { simulation_precision = p_val; if (bt_soft_body) { bt_soft_body->m_cfg.piterations = simulation_precision; + bt_soft_body->m_cfg.viterations = simulation_precision; + bt_soft_body->m_cfg.diterations = simulation_precision; + bt_soft_body->m_cfg.citerations = simulation_precision; } } @@ -214,6 +298,13 @@ void SoftBodyBullet::set_pressure_coefficient(real_t p_val) { } } +void SoftBodyBullet::set_pose_matching_coefficient(real_t p_val) { + pose_matching_coefficient = p_val; + if (bt_soft_body) { + bt_soft_body->m_cfg.kMT = pose_matching_coefficient; + } +} + void SoftBodyBullet::set_damping_coefficient(real_t p_val) { damping_coefficient = p_val; if (bt_soft_body) { @@ -228,89 +319,156 @@ void SoftBodyBullet::set_drag_coefficient(real_t p_val) { } } -void SoftBodyBullet::reload_soft_body() { - +void SoftBodyBullet::set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices) { + /// Assert the current soft body is destroyed destroy_soft_body(); - create_soft_body(); - if (bt_soft_body) { + /// Parse visual server indices to physical indices. + /// Merge all overlapping vertices and create a map of physical vertices to visual server - // TODO the softbody set new transform considering the current transform as center of world - // like if it's local transform, so I must fix this by setting nwe transform considering the old - btTransform bt_trans; - G_TO_B(transform, bt_trans); - bt_soft_body->transform(bt_trans); + { + /// This is the map of visual server indices to physics indices (So it's the inverse of idices_map), Thanks to it I don't need make a heavy search in the indices_map + Vector<int> vs_indices_to_physics_table; - bt_soft_body->generateBendingConstraints(2, mat0); - mat0->m_kAST = stiffness; - mat0->m_kLST = stiffness; - mat0->m_kVST = stiffness; + { // Map vertices + indices_table.resize(0); - bt_soft_body->m_cfg.piterations = simulation_precision; - bt_soft_body->m_cfg.kDP = damping_coefficient; - bt_soft_body->m_cfg.kDG = drag_coefficient; - bt_soft_body->m_cfg.kPR = pressure_coefficient; - bt_soft_body->setTotalMass(mass); - } - if (space) { - // TODO remove this please - space->add_soft_body(this); - } -} + int index = 0; + Map<Vector3, int> unique_vertices; -void SoftBodyBullet::create_soft_body() { - if (!space || !soft_body_shape_data) { - return; - } - ERR_FAIL_COND(!space->is_using_soft_world()); - switch (soft_shape_type) { - case SOFT_SHAPE_TYPE_TRIMESH: { - TrimeshSoftShapeData *trimesh_data = static_cast<TrimeshSoftShapeData *>(soft_body_shape_data); - - Vector<int> indices; - Vector<btScalar> vertices; - - int i; - const int indices_size = trimesh_data->m_triangles_indices.size(); - const int vertices_size = trimesh_data->m_vertices.size(); - indices.resize(indices_size); - vertices.resize(vertices_size * 3); - - PoolVector<int>::Read i_r = trimesh_data->m_triangles_indices.read(); - for (i = 0; i < indices_size; ++i) { - indices[i] = i_r[i]; + const int vs_vertices_size(p_vertices.size()); + + PoolVector<Vector3>::Read p_vertices_read = p_vertices.read(); + + for (int vs_vertex_index = 0; vs_vertex_index < vs_vertices_size; ++vs_vertex_index) { + + Map<Vector3, int>::Element *e = unique_vertices.find(p_vertices_read[vs_vertex_index]); + int vertex_id; + if (e) { + // Already rxisting + vertex_id = e->value(); + } else { + // Create new one + unique_vertices[p_vertices_read[vs_vertex_index]] = vertex_id = index++; + indices_table.push_back(Vector<int>()); + } + + indices_table.write[vertex_id].push_back(vs_vertex_index); + vs_indices_to_physics_table.push_back(vertex_id); + } + } + + const int indices_map_size(indices_table.size()); + + Vector<btScalar> bt_vertices; + + { // Parse vertices to bullet + + bt_vertices.resize(indices_map_size * 3); + PoolVector<Vector3>::Read p_vertices_read = p_vertices.read(); + + for (int i = 0; i < indices_map_size; ++i) { + bt_vertices.write[3 * i + 0] = p_vertices_read[indices_table[i][0]].x; + bt_vertices.write[3 * i + 1] = p_vertices_read[indices_table[i][0]].y; + bt_vertices.write[3 * i + 2] = p_vertices_read[indices_table[i][0]].z; } - i_r = PoolVector<int>::Read(); + } + + Vector<int> bt_triangles; + const int triangles_size(p_indices.size() / 3); + + { // Parse indices + + bt_triangles.resize(triangles_size * 3); + + PoolVector<int>::Read p_indices_read = p_indices.read(); - PoolVector<Vector3>::Read f_r = trimesh_data->m_vertices.read(); - for (int j = i = 0; i < vertices_size; ++i, j += 3) { - vertices[j + 0] = f_r[i][0]; - vertices[j + 1] = f_r[i][1]; - vertices[j + 2] = f_r[i][2]; + for (int i = 0; i < triangles_size; ++i) { + bt_triangles.write[3 * i + 0] = vs_indices_to_physics_table[p_indices_read[3 * i + 2]]; + bt_triangles.write[3 * i + 1] = vs_indices_to_physics_table[p_indices_read[3 * i + 1]]; + bt_triangles.write[3 * i + 2] = vs_indices_to_physics_table[p_indices_read[3 * i + 0]]; } - f_r = PoolVector<Vector3>::Read(); + } - bt_soft_body = btSoftBodyHelpers::CreateFromTriMesh(*space->get_soft_body_world_info(), vertices.ptr(), indices.ptr(), trimesh_data->m_triangles_num); - } break; - default: - ERR_PRINT("Shape type not supported"); - return; + btSoftBodyWorldInfo fake_world_info; + bt_soft_body = btSoftBodyHelpers::CreateFromTriMesh(fake_world_info, &bt_vertices[0], &bt_triangles[0], triangles_size, false); + setup_soft_body(); } +} + +void SoftBodyBullet::setup_soft_body() { + + if (!bt_soft_body) + return; + // Soft body setup setupBulletCollisionObject(bt_soft_body); - bt_soft_body->getCollisionShape()->setMargin(0.001f); + bt_soft_body->m_worldInfo = NULL; // Remove fake world info + bt_soft_body->getCollisionShape()->setMargin(0.01); bt_soft_body->setCollisionFlags(bt_soft_body->getCollisionFlags() & (~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT))); + + // Space setup + if (space) { + space->add_soft_body(this); + } + mat0 = bt_soft_body->appendMaterial(); + + // Assign soft body data + bt_soft_body->generateBendingConstraints(2, mat0); + + mat0->m_kLST = linear_stiffness; + mat0->m_kAST = areaAngular_stiffness; + mat0->m_kVST = volume_stiffness; + + // Clusters allow to have Soft vs Soft collision but doesn't work well right now + + //bt_soft_body->m_cfg.kSRHR_CL = 1;// Soft vs rigid hardness [0,1] (cluster only) + //bt_soft_body->m_cfg.kSKHR_CL = 1;// Soft vs kinematic hardness [0,1] (cluster only) + //bt_soft_body->m_cfg.kSSHR_CL = 1;// Soft vs soft hardness [0,1] (cluster only) + //bt_soft_body->m_cfg.kSR_SPLT_CL = 1; // Soft vs rigid impulse split [0,1] (cluster only) + //bt_soft_body->m_cfg.kSK_SPLT_CL = 1; // Soft vs kinematic impulse split [0,1] (cluster only) + //bt_soft_body->m_cfg.kSS_SPLT_CL = 1; // Soft vs Soft impulse split [0,1] (cluster only) + //bt_soft_body->m_cfg.collisions = btSoftBody::fCollision::CL_SS + btSoftBody::fCollision::CL_RS + btSoftBody::fCollision::VF_SS; + //bt_soft_body->generateClusters(64); + + bt_soft_body->m_cfg.piterations = simulation_precision; + bt_soft_body->m_cfg.viterations = simulation_precision; + bt_soft_body->m_cfg.diterations = simulation_precision; + bt_soft_body->m_cfg.citerations = simulation_precision; + bt_soft_body->m_cfg.kDP = damping_coefficient; + bt_soft_body->m_cfg.kDG = drag_coefficient; + bt_soft_body->m_cfg.kPR = pressure_coefficient; + bt_soft_body->m_cfg.kMT = pose_matching_coefficient; + bt_soft_body->setTotalMass(total_mass); + + btSoftBodyHelpers::ReoptimizeLinkOrder(bt_soft_body); + bt_soft_body->updateBounds(); + + // Set pinned nodes + for (int i = pinned_nodes.size() - 1; 0 <= i; --i) { + bt_soft_body->setMass(pinned_nodes[i], 0); + } } -void SoftBodyBullet::destroy_soft_body() { - if (space) { - /// This step is required to assert that the body is not into the world during deletion - /// This step is required since to change the body shape the body must be re-created. - /// Here is handled the case when the body is assigned into a world and the body - /// shape is changed. - space->remove_soft_body(this); +void SoftBodyBullet::pin_node(int p_node_index) { + if (-1 == search_node_pinned(p_node_index)) { + pinned_nodes.push_back(p_node_index); } - destroyBulletCollisionObject(); - bt_soft_body = NULL; +} + +void SoftBodyBullet::unpin_node(int p_node_index) { + const int id = search_node_pinned(p_node_index); + if (-1 != id) { + pinned_nodes.remove(id); + } +} + +int SoftBodyBullet::search_node_pinned(int p_node_index) const { + for (int i = pinned_nodes.size() - 1; 0 <= i; --i) { + if (p_node_index == pinned_nodes[i]) { + return i; + } + } + return -1; } diff --git a/modules/bullet/soft_body_bullet.h b/modules/bullet/soft_body_bullet.h index 9895643b84..d04bfca046 100644 --- a/modules/bullet/soft_body_bullet.h +++ b/modules/bullet/soft_body_bullet.h @@ -40,7 +40,10 @@ #define x11_None 0L #endif -#include <BulletSoftBody/btSoftBodyHelpers.h> +#include "BulletSoftBody/btSoftBodyHelpers.h" +#include "collision_object_bullet.h" +#include "scene/resources/mesh.h" +#include "servers/physics_server.h" #ifdef x11_None /// This is required to re add the macro None defined by x11 compiler @@ -52,39 +55,34 @@ @author AndreaCatania */ -struct SoftShapeData {}; -struct TrimeshSoftShapeData : public SoftShapeData { - PoolVector<int> m_triangles_indices; - PoolVector<Vector3> m_vertices; - int m_triangles_num; -}; - class SoftBodyBullet : public CollisionObjectBullet { -public: - enum SoftShapeType { - SOFT_SHAPETYPE_NONE = 0, - SOFT_SHAPE_TYPE_TRIMESH - }; private: btSoftBody *bt_soft_body; + Vector<Vector<int> > indices_table; btSoftBody::Material *mat0; // This is just a copy of pointer managed by btSoftBody - SoftShapeType soft_shape_type; bool isScratched; - SoftShapeData *soft_body_shape_data; + Ref<Mesh> soft_mesh; - Transform transform; int simulation_precision; - real_t mass; - real_t stiffness; // [0,1] + real_t total_mass; + real_t linear_stiffness; // [0,1] + real_t areaAngular_stiffness; // [0,1] + real_t volume_stiffness; // [0,1] real_t pressure_coefficient; // [-inf,+inf] + real_t pose_matching_coefficient; // [0,1] real_t damping_coefficient; // [0,1] real_t drag_coefficient; // [0,1] + Vector<int> pinned_nodes; - class ImmediateGeometry *test_geometry; // TODO remove this please - Ref<SpatialMaterial> red_mat; // TODO remove this please - bool test_is_in_scene; // TODO remove this please + // Other property to add + //btScalar kVC; // Volume conversation coefficient [0,+inf] + //btScalar kDF; // Dynamic friction coefficient [0,1] + //btScalar kMT; // Pose matching coefficient [0,1] + //btScalar kCHR; // Rigid contacts hardness [0,1] + //btScalar kKHR; // Kinetic contacts hardness [0,1] + //btScalar kSHR; // Soft contacts hardness [0,1] public: SoftBodyBullet(); @@ -93,47 +91,73 @@ public: virtual void reload_body(); virtual void set_space(SpaceBullet *p_space); - virtual void dispatch_callbacks(); - virtual void on_collision_filters_change(); - virtual void on_collision_checker_start(); + virtual void dispatch_callbacks() {} + virtual void on_collision_filters_change() {} + virtual void on_collision_checker_start() {} + virtual void on_collision_checker_end() {} virtual void on_enter_area(AreaBullet *p_area); virtual void on_exit_area(AreaBullet *p_area); _FORCE_INLINE_ btSoftBody *get_bt_soft_body() const { return bt_soft_body; } - void set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num); - void set_body_shape_data(SoftShapeData *p_soft_shape_data, SoftShapeType p_type); + void update_visual_server(class SoftBodyVisualServerHandler *p_visual_server_handler); - void set_transform(const Transform &p_transform); - /// This function doesn't return the exact COM transform. - /// It returns the origin only of first node (vertice) of current soft body - /// --- - /// The soft body doesn't have a fixed center of mass, but is a group of nodes (vertices) - /// that each has its own position in the world. - /// For this reason return the correct COM is not so simple and must be calculate - /// Check this to improve this function http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=8803 - const Transform &get_transform() const; - void get_first_node_origin(btVector3 &p_out_origin) const; + void set_soft_mesh(const Ref<Mesh> &p_mesh); + void destroy_soft_body(); + + // Special function. This function has bad performance + void set_soft_transform(const Transform &p_transform); + + void move_all_nodes(const Transform &p_transform); + void set_node_position(int node_index, const Vector3 &p_global_position); + void set_node_position(int node_index, const btVector3 &p_global_position); + void get_node_position(int node_index, Vector3 &r_position) const; + // Heavy function, Please cache this info + void get_node_offset(int node_index, Vector3 &r_offset) const; + // Heavy function, Please cache this info + void get_node_offset(int node_index, btVector3 &r_offset) const; + + void set_node_mass(int node_index, btScalar p_mass); + btScalar get_node_mass(int node_index) const; + void reset_all_node_mass(); + void reset_all_node_positions(); void set_activation_state(bool p_active); - void set_mass(real_t p_val); - _FORCE_INLINE_ real_t get_mass() const { return mass; } - void set_stiffness(real_t p_val); - _FORCE_INLINE_ real_t get_stiffness() const { return stiffness; } + void set_total_mass(real_t p_val); + _FORCE_INLINE_ real_t get_total_mass() const { return total_mass; } + + void set_linear_stiffness(real_t p_val); + _FORCE_INLINE_ real_t get_linear_stiffness() const { return linear_stiffness; } + + void set_areaAngular_stiffness(real_t p_val); + _FORCE_INLINE_ real_t get_areaAngular_stiffness() const { return areaAngular_stiffness; } + + void set_volume_stiffness(real_t p_val); + _FORCE_INLINE_ real_t get_volume_stiffness() const { return volume_stiffness; } + void set_simulation_precision(int p_val); _FORCE_INLINE_ int get_simulation_precision() const { return simulation_precision; } + void set_pressure_coefficient(real_t p_val); _FORCE_INLINE_ real_t get_pressure_coefficient() const { return pressure_coefficient; } + + void set_pose_matching_coefficient(real_t p_val); + _FORCE_INLINE_ real_t get_pose_matching_coefficient() const { return pose_matching_coefficient; } + void set_damping_coefficient(real_t p_val); _FORCE_INLINE_ real_t get_damping_coefficient() const { return damping_coefficient; } + void set_drag_coefficient(real_t p_val); _FORCE_INLINE_ real_t get_drag_coefficient() const { return drag_coefficient; } private: - void reload_soft_body(); - void create_soft_body(); - void destroy_soft_body(); + void set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices); + void setup_soft_body(); + + void pin_node(int p_node_index); + void unpin_node(int p_node_index); + int search_node_pinned(int p_node_index) const; }; #endif // SOFT_BODY_BULLET_H diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index 971fd39509..fed12cd5ed 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -34,12 +34,13 @@ #include "bullet_types_converter.h" #include "bullet_utilities.h" #include "constraint_bullet.h" +#include "core/project_settings.h" +#include "core/ustring.h" #include "godot_collision_configuration.h" #include "godot_collision_dispatcher.h" #include "rigid_body_bullet.h" #include "servers/physics_server.h" #include "soft_body_bullet.h" -#include "ustring.h" #include <BulletCollision/CollisionDispatch/btCollisionObject.h> #include <BulletCollision/CollisionDispatch/btGhostObject.h> @@ -60,7 +61,7 @@ BulletPhysicsDirectSpaceState::BulletPhysicsDirectSpaceState(SpaceBullet *p_spac PhysicsDirectSpaceState(), space(p_space) {} -int BulletPhysicsDirectSpaceState::intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude, uint32_t p_collision_mask) { +int BulletPhysicsDirectSpaceState::intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas) { if (p_result_max <= 0) return 0; @@ -68,13 +69,13 @@ int BulletPhysicsDirectSpaceState::intersect_point(const Vector3 &p_point, Shape btVector3 bt_point; G_TO_B(p_point, bt_point); - btSphereShape sphere_point(0.f); + btSphereShape sphere_point(0.001f); btCollisionObject collision_object_point; collision_object_point.setCollisionShape(&sphere_point); collision_object_point.setWorldTransform(btTransform(btQuaternion::getIdentity(), bt_point)); // Setup query - GodotAllContactResultCallback btResult(&collision_object_point, r_results, p_result_max, &p_exclude); + GodotAllContactResultCallback btResult(&collision_object_point, r_results, p_result_max, &p_exclude, p_collide_with_bodies, p_collide_with_areas); btResult.m_collisionFilterGroup = 0; btResult.m_collisionFilterMask = p_collision_mask; space->dynamicsWorld->contactTest(&collision_object_point, btResult); @@ -83,7 +84,7 @@ int BulletPhysicsDirectSpaceState::intersect_point(const Vector3 &p_point, Shape return btResult.m_count; } -bool BulletPhysicsDirectSpaceState::intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude, uint32_t p_collision_mask, bool p_pick_ray) { +bool BulletPhysicsDirectSpaceState::intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas, bool p_pick_ray) { btVector3 btVec_from; btVector3 btVec_to; @@ -92,7 +93,7 @@ bool BulletPhysicsDirectSpaceState::intersect_ray(const Vector3 &p_from, const V G_TO_B(p_to, btVec_to); // setup query - GodotClosestRayResultCallback btResult(btVec_from, btVec_to, &p_exclude); + GodotClosestRayResultCallback btResult(btVec_from, btVec_to, &p_exclude, p_collide_with_bodies, p_collide_with_areas); btResult.m_collisionFilterGroup = 0; btResult.m_collisionFilterMask = p_collision_mask; btResult.m_pickRay = p_pick_ray; @@ -116,7 +117,7 @@ bool BulletPhysicsDirectSpaceState::intersect_ray(const Vector3 &p_from, const V } } -int BulletPhysicsDirectSpaceState::intersect_shape(const RID &p_shape, const Transform &p_xform, float p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude, uint32_t p_collision_mask) { +int BulletPhysicsDirectSpaceState::intersect_shape(const RID &p_shape, const Transform &p_xform, float p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas) { if (p_result_max <= 0) return 0; @@ -138,7 +139,7 @@ int BulletPhysicsDirectSpaceState::intersect_shape(const RID &p_shape, const Tra collision_object.setCollisionShape(btConvex); collision_object.setWorldTransform(bt_xform); - GodotAllContactResultCallback btQuery(&collision_object, r_results, p_result_max, &p_exclude); + GodotAllContactResultCallback btQuery(&collision_object, r_results, p_result_max, &p_exclude, p_collide_with_bodies, p_collide_with_areas); btQuery.m_collisionFilterGroup = 0; btQuery.m_collisionFilterMask = p_collision_mask; btQuery.m_closestDistanceThreshold = 0; @@ -149,14 +150,14 @@ int BulletPhysicsDirectSpaceState::intersect_shape(const RID &p_shape, const Tra return btQuery.m_count; } -bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transform &p_xform, const Vector3 &p_motion, float p_margin, float &p_closest_safe, float &p_closest_unsafe, const Set<RID> &p_exclude, uint32_t p_collision_mask, ShapeRestInfo *r_info) { +bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transform &p_xform, const Vector3 &p_motion, float p_margin, float &r_closest_safe, float &r_closest_unsafe, const Set<RID> &p_exclude, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas, ShapeRestInfo *r_info) { ShapeBullet *shape = space->get_physics_server()->get_shape_owner()->get(p_shape); btCollisionShape *btShape = shape->create_bt_shape(p_xform.basis.get_scale(), p_margin); if (!btShape->isConvex()) { bulletdelete(btShape); ERR_PRINTS("The shape is not a convex shape, then is not supported: shape type: " + itos(shape->get_type())); - return 0; + return false; } btConvexShape *bt_convex_shape = static_cast<btConvexShape *>(btShape); @@ -170,14 +171,19 @@ bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transf btTransform bt_xform_to(bt_xform_from); bt_xform_to.getOrigin() += bt_motion; - GodotClosestConvexResultCallback btResult(bt_xform_from.getOrigin(), bt_xform_to.getOrigin(), &p_exclude); + GodotClosestConvexResultCallback btResult(bt_xform_from.getOrigin(), bt_xform_to.getOrigin(), &p_exclude, p_collide_with_bodies, p_collide_with_areas); btResult.m_collisionFilterGroup = 0; btResult.m_collisionFilterMask = p_collision_mask; - space->dynamicsWorld->convexSweepTest(bt_convex_shape, bt_xform_from, bt_xform_to, btResult, 0.002); + space->dynamicsWorld->convexSweepTest(bt_convex_shape, bt_xform_from, bt_xform_to, btResult, space->dynamicsWorld->getDispatchInfo().m_allowedCcdPenetration); + + r_closest_unsafe = 1.0; + r_closest_safe = 1.0; if (btResult.hasHit()) { - p_closest_safe = p_closest_unsafe = btResult.m_closestHitFraction; + const btScalar l = bt_motion.length(); + r_closest_unsafe = btResult.m_closestHitFraction; + r_closest_safe = MAX(r_closest_unsafe - (1 - ((l - 0.01) / l)), 0); if (r_info) { if (btCollisionObject::CO_RIGID_BODY == btResult.m_hitCollisionObject->getInternalType()) { B_TO_G(static_cast<const btRigidBody *>(btResult.m_hitCollisionObject)->getVelocityInLocalPoint(btResult.m_hitPointWorld), r_info->linear_velocity); @@ -192,11 +198,11 @@ bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transf } bulletdelete(bt_convex_shape); - return btResult.hasHit(); + return true; // Mean success } /// Returns the list of contacts pairs in this order: Local contact, other body contact -bool BulletPhysicsDirectSpaceState::collide_shape(RID p_shape, const Transform &p_shape_xform, float p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude, uint32_t p_collision_mask) { +bool BulletPhysicsDirectSpaceState::collide_shape(RID p_shape, const Transform &p_shape_xform, float p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas) { if (p_result_max <= 0) return 0; @@ -218,7 +224,7 @@ bool BulletPhysicsDirectSpaceState::collide_shape(RID p_shape, const Transform & collision_object.setCollisionShape(btConvex); collision_object.setWorldTransform(bt_xform); - GodotContactPairContactResultCallback btQuery(&collision_object, r_results, p_result_max, &p_exclude); + GodotContactPairContactResultCallback btQuery(&collision_object, r_results, p_result_max, &p_exclude, p_collide_with_bodies, p_collide_with_areas); btQuery.m_collisionFilterGroup = 0; btQuery.m_collisionFilterMask = p_collision_mask; btQuery.m_closestDistanceThreshold = 0; @@ -230,7 +236,7 @@ bool BulletPhysicsDirectSpaceState::collide_shape(RID p_shape, const Transform & return btQuery.m_count; } -bool BulletPhysicsDirectSpaceState::rest_info(RID p_shape, const Transform &p_shape_xform, float p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude, uint32_t p_collision_mask) { +bool BulletPhysicsDirectSpaceState::rest_info(RID p_shape, const Transform &p_shape_xform, float p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas) { ShapeBullet *shape = space->get_physics_server()->get_shape_owner()->get(p_shape); @@ -250,7 +256,7 @@ bool BulletPhysicsDirectSpaceState::rest_info(RID p_shape, const Transform &p_sh collision_object.setCollisionShape(btConvex); collision_object.setWorldTransform(bt_xform); - GodotRestInfoContactResultCallback btQuery(&collision_object, r_info, &p_exclude); + GodotRestInfoContactResultCallback btQuery(&collision_object, r_info, &p_exclude, p_collide_with_bodies, p_collide_with_areas); btQuery.m_collisionFilterGroup = 0; btQuery.m_collisionFilterMask = p_collision_mask; btQuery.m_closestDistanceThreshold = 0; @@ -292,11 +298,10 @@ Vector3 BulletPhysicsDirectSpaceState::get_closest_point_to_object_volume(RID p_ bool shapes_found = false; - btCompoundShape *compound = rigid_object->get_compound_shape(); - for (int i = compound->getNumChildShapes() - 1; 0 <= i; --i) { - shape = compound->getChildShape(i); + for (int i = rigid_object->get_shape_count() - 1; 0 <= i; --i) { + shape = rigid_object->get_bt_shape(i); if (shape->isConvex()) { - child_transform = compound->getChildTransform(i); + child_transform = rigid_object->get_bt_shape_transform(i); convex_shape = static_cast<btConvexShape *>(shape); input.m_transformB = body_transform * child_transform; @@ -325,20 +330,21 @@ Vector3 BulletPhysicsDirectSpaceState::get_closest_point_to_object_volume(RID p_ } } -SpaceBullet::SpaceBullet(bool p_create_soft_world) : +SpaceBullet::SpaceBullet() : broadphase(NULL), + collisionConfiguration(NULL), dispatcher(NULL), solver(NULL), - collisionConfiguration(NULL), dynamicsWorld(NULL), soft_body_world_info(NULL), ghostPairCallback(NULL), godotFilterCallback(NULL), gravityDirection(0, -1, 0), gravityMagnitude(10), - contactDebugCount(0) { + contactDebugCount(0), + delta_time(0.) { - create_empty_world(p_create_soft_world); + create_empty_world(GLOBAL_DEF("physics/3d/active_soft_world", true)); direct_access = memnew(BulletPhysicsDirectSpaceState(this)); } @@ -355,6 +361,7 @@ void SpaceBullet::flush_queries() { } void SpaceBullet::step(real_t p_delta_time) { + delta_time = p_delta_time; dynamicsWorld->stepSimulation(p_delta_time, 0, 0); } @@ -483,6 +490,7 @@ void SpaceBullet::reload_collision_filters(RigidBodyBullet *p_body) { void SpaceBullet::add_soft_body(SoftBodyBullet *p_body) { if (is_using_soft_world()) { if (p_body->get_bt_soft_body()) { + p_body->get_bt_soft_body()->m_worldInfo = get_soft_body_world_info(); static_cast<btSoftRigidDynamicsWorld *>(dynamicsWorld)->addSoftBody(p_body->get_bt_soft_body(), p_body->get_collision_layer(), p_body->get_collision_mask()); } } else { @@ -494,6 +502,7 @@ void SpaceBullet::remove_soft_body(SoftBodyBullet *p_body) { if (is_using_soft_world()) { if (p_body->get_bt_soft_body()) { static_cast<btSoftRigidDynamicsWorld *>(dynamicsWorld)->removeSoftBody(p_body->get_bt_soft_body()); + p_body->get_bt_soft_body()->m_worldInfo = NULL; } } } @@ -531,17 +540,20 @@ void onBulletPreTickCallback(btDynamicsWorld *p_dynamicsWorld, btScalar timeStep void onBulletTickCallback(btDynamicsWorld *p_dynamicsWorld, btScalar timeStep) { - // Notify all Collision objects the collision checker is started const btCollisionObjectArray &colObjArray = p_dynamicsWorld->getCollisionObjectArray(); + + // Notify all Collision objects the collision checker is started for (int i = colObjArray.size() - 1; 0 <= i; --i) { - CollisionObjectBullet *colObj = static_cast<CollisionObjectBullet *>(colObjArray[i]->getUserPointer()); - assert(NULL != colObj); - colObj->on_collision_checker_start(); + static_cast<CollisionObjectBullet *>(colObjArray[i]->getUserPointer())->on_collision_checker_start(); } SpaceBullet *sb = static_cast<SpaceBullet *>(p_dynamicsWorld->getWorldUserInfo()); sb->check_ghost_overlaps(); sb->check_body_collision(); + + for (int i = colObjArray.size() - 1; 0 <= i; --i) { + static_cast<CollisionObjectBullet *>(colObjArray[i]->getUserPointer())->on_collision_checker_end(); + } } BulletPhysicsDirectSpaceState *SpaceBullet::get_direct_state() { @@ -549,14 +561,19 @@ BulletPhysicsDirectSpaceState *SpaceBullet::get_direct_state() { } btScalar calculateGodotCombinedRestitution(const btCollisionObject *body0, const btCollisionObject *body1) { - return MAX(body0->getRestitution(), body1->getRestitution()); + + return CLAMP(body0->getRestitution() + body1->getRestitution(), 0, 1); +} + +btScalar calculateGodotCombinedFriction(const btCollisionObject *body0, const btCollisionObject *body1) { + + return ABS(MIN(body0->getFriction(), body1->getFriction())); } void SpaceBullet::create_empty_world(bool p_create_soft_world) { gjk_epa_pen_solver = bulletnew(btGjkEpaPenetrationDepthSolver); gjk_simplex_solver = bulletnew(btVoronoiSimplexSolver); - gjk_simplex_solver->setEqualVertexThreshold(0.f); void *world_mem; if (p_create_soft_world) { @@ -566,7 +583,7 @@ void SpaceBullet::create_empty_world(bool p_create_soft_world) { } if (p_create_soft_world) { - collisionConfiguration = bulletnew(btSoftBodyRigidBodyCollisionConfiguration); + collisionConfiguration = bulletnew(GodotSoftCollisionConfiguration(static_cast<btDiscreteDynamicsWorld *>(world_mem))); } else { collisionConfiguration = bulletnew(GodotCollisionConfiguration(static_cast<btDiscreteDynamicsWorld *>(world_mem))); } @@ -585,6 +602,8 @@ void SpaceBullet::create_empty_world(bool p_create_soft_world) { ghostPairCallback = bulletnew(btGhostPairCallback); godotFilterCallback = bulletnew(GodotFilterCallback); gCalculateCombinedRestitutionCallback = &calculateGodotCombinedRestitution; + gCalculateCombinedFrictionCallback = &calculateGodotCombinedFriction; + gContactAddedCallback = &godotContactAddedCallback; dynamicsWorld->setWorldUserInfo(this); @@ -629,11 +648,10 @@ void SpaceBullet::destroy_world() { void SpaceBullet::check_ghost_overlaps() { /// Algorithm support variables - btConvexShape *other_body_shape; + btCollisionShape *other_body_shape; btConvexShape *area_shape; btGjkPairDetector::ClosestPointInput gjk_input; AreaBullet *area; - RigidCollisionObjectBullet *otherObject; int x(-1), i(-1), y(-1), z(-1), indexOverlap(-1); /// For each areas @@ -645,7 +663,7 @@ void SpaceBullet::check_ghost_overlaps() { /// 1. Reset all states for (i = area->overlappingObjects.size() - 1; 0 <= i; --i) { - AreaBullet::OverlappingObjectData &otherObj = area->overlappingObjects[i]; + AreaBullet::OverlappingObjectData &otherObj = area->overlappingObjects.write[i]; // This check prevent the overwrite of ENTER state // if this function is called more times before dispatchCallbacks if (otherObj.state != AreaBullet::OVERLAP_STATE_ENTER) { @@ -660,41 +678,67 @@ void SpaceBullet::check_ghost_overlaps() { // For each overlapping for (i = ghostOverlaps.size() - 1; 0 <= i; --i) { - if (ghostOverlaps[i]->getUserIndex() == CollisionObjectBullet::TYPE_AREA) { - if (!static_cast<AreaBullet *>(ghostOverlaps[i]->getUserPointer())->is_monitorable()) - continue; - } else if (ghostOverlaps[i]->getUserIndex() != CollisionObjectBullet::TYPE_RIGID_BODY) + btCollisionObject *overlapped_bt_co = ghostOverlaps[i]; + RigidCollisionObjectBullet *otherObject = static_cast<RigidCollisionObjectBullet *>(overlapped_bt_co->getUserPointer()); + + if (!area->is_transform_changed() && !otherObject->is_transform_changed()) continue; - otherObject = static_cast<RigidCollisionObjectBullet *>(ghostOverlaps[i]->getUserPointer()); + if (overlapped_bt_co->getUserIndex() == CollisionObjectBullet::TYPE_AREA) { + if (!static_cast<AreaBullet *>(overlapped_bt_co->getUserPointer())->is_monitorable()) + continue; + } else if (overlapped_bt_co->getUserIndex() != CollisionObjectBullet::TYPE_RIGID_BODY) + continue; bool hasOverlap = false; // For each area shape - for (y = area->get_compound_shape()->getNumChildShapes() - 1; 0 <= y; --y) { - if (!area->get_compound_shape()->getChildShape(y)->isConvex()) + for (y = area->get_shape_count() - 1; 0 <= y; --y) { + if (!area->get_bt_shape(y)->isConvex()) continue; - gjk_input.m_transformA = area->get_transform__bullet() * area->get_compound_shape()->getChildTransform(y); - area_shape = static_cast<btConvexShape *>(area->get_compound_shape()->getChildShape(y)); + gjk_input.m_transformA = area->get_transform__bullet() * area->get_bt_shape_transform(y); + area_shape = static_cast<btConvexShape *>(area->get_bt_shape(y)); // For each other object shape - for (z = otherObject->get_compound_shape()->getNumChildShapes() - 1; 0 <= z; --z) { + for (z = otherObject->get_shape_count() - 1; 0 <= z; --z) { + + other_body_shape = static_cast<btCollisionShape *>(otherObject->get_bt_shape(z)); + gjk_input.m_transformB = otherObject->get_transform__bullet() * otherObject->get_bt_shape_transform(z); - if (!otherObject->get_compound_shape()->getChildShape(z)->isConvex()) - continue; + if (other_body_shape->isConvex()) { - other_body_shape = static_cast<btConvexShape *>(otherObject->get_compound_shape()->getChildShape(z)); - gjk_input.m_transformB = otherObject->get_transform__bullet() * otherObject->get_compound_shape()->getChildTransform(z); + btPointCollector result; + btGjkPairDetector gjk_pair_detector(area_shape, static_cast<btConvexShape *>(other_body_shape), gjk_simplex_solver, gjk_epa_pen_solver); + gjk_pair_detector.getClosestPoints(gjk_input, result, 0); + + if (0 >= result.m_distance) { + hasOverlap = true; + goto collision_found; + } + + } else { - btPointCollector result; - btGjkPairDetector gjk_pair_detector(area_shape, other_body_shape, gjk_simplex_solver, gjk_epa_pen_solver); - gjk_pair_detector.getClosestPoints(gjk_input, result, 0); + btCollisionObjectWrapper obA(NULL, area_shape, area->get_bt_ghost(), gjk_input.m_transformA, -1, y); + btCollisionObjectWrapper obB(NULL, other_body_shape, otherObject->get_bt_collision_object(), gjk_input.m_transformB, -1, z); - if (0 >= result.m_distance) { - hasOverlap = true; - goto collision_found; + btCollisionAlgorithm *algorithm = dispatcher->findAlgorithm(&obA, &obB, NULL, BT_CONTACT_POINT_ALGORITHMS); + + if (!algorithm) + continue; + + GodotDeepPenetrationContactResultCallback contactPointResult(&obA, &obB); + algorithm->processCollision(&obA, &obB, dynamicsWorld->getDispatchInfo(), &contactPointResult); + + algorithm->~btCollisionAlgorithm(); + dispatcher->freeCollisionAlgorithm(algorithm); + + if (contactPointResult.hasHit()) { + hasOverlap = true; + goto collision_found; + } } + } // ~For each other object shape } // ~For each area shape @@ -742,38 +786,47 @@ void SpaceBullet::check_body_collision() { } const int numContacts = contactManifold->getNumContacts(); + + /// Since I don't need report all contacts for these objects, + /// So report only the first #define REPORT_ALL_CONTACTS 0 #if REPORT_ALL_CONTACTS for (int j = 0; j < numContacts; j++) { btManifoldPoint &pt = contactManifold->getContactPoint(j); #else - // Since I don't need report all contacts for these objects, I'll report only the first if (numContacts) { btManifoldPoint &pt = contactManifold->getContactPoint(0); #endif - Vector3 collisionWorldPosition; - Vector3 collisionLocalPosition; - Vector3 normalOnB; - B_TO_G(pt.m_normalWorldOnB, normalOnB); - - if (bodyA->can_add_collision()) { - B_TO_G(pt.getPositionWorldOnB(), collisionWorldPosition); - /// pt.m_localPointB Doesn't report the exact point in local space - B_TO_G(pt.getPositionWorldOnB() - contactManifold->getBody1()->getWorldTransform().getOrigin(), collisionLocalPosition); - bodyA->add_collision_object(bodyB, collisionWorldPosition, collisionLocalPosition, normalOnB, pt.m_index1, pt.m_index0); - } - if (bodyB->can_add_collision()) { - B_TO_G(pt.getPositionWorldOnA(), collisionWorldPosition); - /// pt.m_localPointA Doesn't report the exact point in local space - B_TO_G(pt.getPositionWorldOnA() - contactManifold->getBody0()->getWorldTransform().getOrigin(), collisionLocalPosition); - bodyB->add_collision_object(bodyA, collisionWorldPosition, collisionLocalPosition, normalOnB * -1, pt.m_index0, pt.m_index1); - } + if ( + pt.getDistance() <= 0.0 || + bodyA->was_colliding(bodyB) || + bodyB->was_colliding(bodyA)) { + + Vector3 collisionWorldPosition; + Vector3 collisionLocalPosition; + Vector3 normalOnB; + float appliedImpulse = pt.m_appliedImpulse; + B_TO_G(pt.m_normalWorldOnB, normalOnB); + + if (bodyA->can_add_collision()) { + B_TO_G(pt.getPositionWorldOnB(), collisionWorldPosition); + /// pt.m_localPointB Doesn't report the exact point in local space + B_TO_G(pt.getPositionWorldOnB() - contactManifold->getBody1()->getWorldTransform().getOrigin(), collisionLocalPosition); + bodyA->add_collision_object(bodyB, collisionWorldPosition, collisionLocalPosition, normalOnB, appliedImpulse, pt.m_index1, pt.m_index0); + } + if (bodyB->can_add_collision()) { + B_TO_G(pt.getPositionWorldOnA(), collisionWorldPosition); + /// pt.m_localPointA Doesn't report the exact point in local space + B_TO_G(pt.getPositionWorldOnA() - contactManifold->getBody0()->getWorldTransform().getOrigin(), collisionLocalPosition); + bodyB->add_collision_object(bodyA, collisionWorldPosition, collisionLocalPosition, normalOnB * -1, appliedImpulse * -1, pt.m_index0, pt.m_index1); + } #ifdef DEBUG_ENABLED - if (is_debugging_contacts()) { - add_debug_contact(collisionWorldPosition); - } + if (is_debugging_contacts()) { + add_debug_contact(collisionWorldPosition); + } #endif + } } } } @@ -807,7 +860,7 @@ static Ref<SpatialMaterial> red_mat; static Ref<SpatialMaterial> blue_mat; #endif -bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer::MotionResult *r_result) { +bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer::MotionResult *r_result, bool p_exclude_raycast_shapes) { #if debug_test_motion /// Yes I know this is not good, but I've used it as fast debugging hack. @@ -882,6 +935,12 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f // Skip no convex shape continue; } + + if (p_exclude_raycast_shapes && p_body->get_bt_shape(shIndex)->getShapeType() == CUSTOM_CONVEX_SHAPE_TYPE) { + // Skip rayshape in order to implement custom separation process + continue; + } + btConvexShape *convex_shape_test(static_cast<btConvexShape *>(p_body->get_bt_shape(shIndex))); btTransform shape_world_from = body_transform * p_body->get_kinematic_utilities()->shapes[shIndex].transform; @@ -912,11 +971,11 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f btVector3 __rec(0, 0, 0); RecoverResult r_recover_result; - has_penetration = recover_from_penetration(p_body, body_transform, 0, p_infinite_inertia, __rec, &r_recover_result); + has_penetration = recover_from_penetration(p_body, body_transform, 1, p_infinite_inertia, __rec, &r_recover_result); // Parse results if (r_result) { - B_TO_G(motion + initial_recover_motion, r_result->motion); + B_TO_G(motion + initial_recover_motion + __rec, r_result->motion); if (has_penetration) { @@ -952,6 +1011,39 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f return has_penetration; } +int SpaceBullet::test_ray_separation(RigidBodyBullet *p_body, const Transform &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, PhysicsServer::SeparationResult *r_results, int p_result_max, float p_margin) { + + btTransform body_transform; + G_TO_B(p_transform, body_transform); + UNSCALE_BT_BASIS(body_transform); + + btVector3 recover_motion(0, 0, 0); + + int rays_found = 0; + + for (int t(RECOVERING_MOVEMENT_CYCLES); 0 < t; --t) { + int last_ray_index = recover_from_penetration_ray(p_body, body_transform, RECOVERING_MOVEMENT_SCALE, p_infinite_inertia, p_result_max, recover_motion, r_results); + + rays_found = MAX(last_ray_index, rays_found); + if (!rays_found) { + break; + } else { + body_transform.getOrigin() += recover_motion; + } + } + + //optimize results (remove non colliding) + for (int i = 0; i < rays_found; i++) { + if (r_results[i].collision_depth >= 0) { + rays_found--; + SWAP(r_results[i], r_results[rays_found]); + } + } + + B_TO_G(recover_motion, r_recover_motion); + return rays_found; +} + struct RecoverPenetrationBroadPhaseCallback : public btBroadphaseAabbCallback { private: const btCollisionObject *self_collision_object; @@ -1008,6 +1100,11 @@ bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTran continue; } + if (kin_shape.shape->getShapeType() == CUSTOM_CONVEX_SHAPE_TYPE) { + // Skip rayshape in order to implement custom separation process + continue; + } + body_shape_position = p_body_position * kin_shape.transform; body_shape_position_recovered = body_shape_position; body_shape_position_recovered.getOrigin() += r_delta_recover_movement; @@ -1110,7 +1207,6 @@ bool SpaceBullet::RFP_convex_world_test(const btConvexShape *p_shapeA, const btC if (contactPointResult.hasHit()) { r_delta_recover_movement += contactPointResult.m_pointNormalWorld * (contactPointResult.m_penetration_distance * -1 * p_recover_movement_scale); - if (r_recover_result) { if (contactPointResult.m_penetration_distance < r_recover_result->penetration_distance) { r_recover_result->hasPenetration = true; @@ -1126,3 +1222,79 @@ bool SpaceBullet::RFP_convex_world_test(const btConvexShape *p_shapeA, const btC } return false; } + +int SpaceBullet::recover_from_penetration_ray(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, int p_result_max, btVector3 &r_delta_recover_movement, PhysicsServer::SeparationResult *r_results) { + + RecoverPenetrationBroadPhaseCallback recover_broad_result(p_body->get_bt_collision_object(), p_body->get_collision_layer(), p_body->get_collision_mask()); + + btTransform body_shape_position; + btTransform body_shape_position_recovered; + + // Broad phase support + btVector3 minAabb, maxAabb; + + int ray_index = 0; + + // For each shape + for (int kinIndex = p_body->get_kinematic_utilities()->shapes.size() - 1; 0 <= kinIndex; --kinIndex) { + + recover_broad_result.reset(); + + if (ray_index >= p_result_max) { + break; + } + + const RigidBodyBullet::KinematicShape &kin_shape(p_body->get_kinematic_utilities()->shapes[kinIndex]); + if (!kin_shape.is_active()) { + continue; + } + + if (kin_shape.shape->getShapeType() != CUSTOM_CONVEX_SHAPE_TYPE) { + continue; + } + + body_shape_position = p_body_position * kin_shape.transform; + body_shape_position_recovered = body_shape_position; + body_shape_position_recovered.getOrigin() += r_delta_recover_movement; + + kin_shape.shape->getAabb(body_shape_position_recovered, minAabb, maxAabb); + dynamicsWorld->getBroadphase()->aabbTest(minAabb, maxAabb, recover_broad_result); + + for (int i = recover_broad_result.result_collision_objects.size() - 1; 0 <= i; --i) { + btCollisionObject *otherObject = recover_broad_result.result_collision_objects[i]; + if (p_infinite_inertia && !otherObject->isStaticOrKinematicObject()) { + otherObject->activate(); // Force activation of hitten rigid, soft body + continue; + } else if (!p_body->get_bt_collision_object()->checkCollideWith(otherObject) || !otherObject->checkCollideWith(p_body->get_bt_collision_object())) + continue; + + if (otherObject->getCollisionShape()->isCompound()) { + + // Each convex shape + btCompoundShape *cs = static_cast<btCompoundShape *>(otherObject->getCollisionShape()); + for (int x = cs->getNumChildShapes() - 1; 0 <= x; --x) { + + RecoverResult r_recover_result; + if (RFP_convex_world_test(kin_shape.shape, cs->getChildShape(x), p_body->get_bt_collision_object(), otherObject, kinIndex, x, body_shape_position, otherObject->getWorldTransform() * cs->getChildTransform(x), p_recover_movement_scale, r_delta_recover_movement, &r_recover_result)) { + + const btRigidBody *btRigid = static_cast<const btRigidBody *>(otherObject); + CollisionObjectBullet *collisionObject = static_cast<CollisionObjectBullet *>(otherObject->getUserPointer()); + + r_results[ray_index].collision_depth = r_recover_result.penetration_distance; + B_TO_G(r_recover_result.pointWorld, r_results[ray_index].collision_point); + B_TO_G(r_recover_result.normal, r_results[ray_index].collision_normal); + B_TO_G(btRigid->getVelocityInLocalPoint(r_recover_result.pointWorld - btRigid->getWorldTransform().getOrigin()), r_results[ray_index].collider_velocity); + r_results[ray_index].collision_local_shape = kinIndex; + r_results[ray_index].collider_id = collisionObject->get_instance_id(); + r_results[ray_index].collider = collisionObject->get_self(); + r_results[ray_index].collider_shape = r_recover_result.other_compound_shape_index; + } + } + } + } + + ++ray_index; + } + + return ray_index; +} diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h index a6c2786878..c3d55cbbb1 100644 --- a/modules/bullet/space_bullet.h +++ b/modules/bullet/space_bullet.h @@ -57,7 +57,7 @@ class btDiscreteDynamicsWorld; class btEmptyShape; class btGhostPairCallback; class btSoftRigidDynamicsWorld; -class btSoftBodyWorldInfo; +struct btSoftBodyWorldInfo; class ConstraintBullet; class CollisionObjectBullet; class RigidBodyBullet; @@ -65,6 +65,8 @@ class SpaceBullet; class SoftBodyBullet; class btGjkEpaPenetrationDepthSolver; +extern ContactAddedCallback gContactAddedCallback; + class BulletPhysicsDirectSpaceState : public PhysicsDirectSpaceState { GDCLASS(BulletPhysicsDirectSpaceState, PhysicsDirectSpaceState) private: @@ -73,18 +75,18 @@ private: public: BulletPhysicsDirectSpaceState(SpaceBullet *p_space); - virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF); - virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_pick_ray = false); - virtual int intersect_shape(const RID &p_shape, const Transform &p_xform, float p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF); - virtual bool cast_motion(const RID &p_shape, const Transform &p_xform, const Vector3 &p_motion, float p_margin, float &p_closest_safe, float &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, ShapeRestInfo *r_info = NULL); + virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); + virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false); + virtual int intersect_shape(const RID &p_shape, const Transform &p_xform, float p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); + virtual bool cast_motion(const RID &p_shape, const Transform &p_xform, const Vector3 &p_motion, float p_margin, float &r_closest_safe, float &r_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = NULL); /// Returns the list of contacts pairs in this order: Local contact, other body contact - virtual bool collide_shape(RID p_shape, const Transform &p_shape_xform, float p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF); - virtual bool rest_info(RID p_shape, const Transform &p_shape_xform, float p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF); + virtual bool collide_shape(RID p_shape, const Transform &p_shape_xform, float p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); + virtual bool rest_info(RID p_shape, const Transform &p_shape_xform, float p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); virtual Vector3 get_closest_point_to_object_volume(RID p_object, const Vector3 p_point) const; }; class SpaceBullet : public RIDBullet { -private: + friend class AreaBullet; friend void onBulletTickCallback(btDynamicsWorld *world, btScalar timeStep); friend class BulletPhysicsDirectSpaceState; @@ -94,9 +96,9 @@ private: btCollisionDispatcher *dispatcher; btConstraintSolver *solver; btDiscreteDynamicsWorld *dynamicsWorld; + btSoftBodyWorldInfo *soft_body_world_info; btGhostPairCallback *ghostPairCallback; GodotFilterCallback *godotFilterCallback; - btSoftBodyWorldInfo *soft_body_world_info; btGjkEpaPenetrationDepthSolver *gjk_epa_pen_solver; btVoronoiSimplexSolver *gjk_simplex_solver; @@ -109,12 +111,14 @@ private: Vector<Vector3> contactDebug; int contactDebugCount; + real_t delta_time; public: - SpaceBullet(bool p_create_soft_world); + SpaceBullet(); virtual ~SpaceBullet(); void flush_queries(); + real_t get_delta_time() { return delta_time; } void step(real_t p_delta_time); _FORCE_INLINE_ btBroadphaseInterface *get_broadphase() { return broadphase; } @@ -162,7 +166,7 @@ public: contactDebugCount = 0; } _FORCE_INLINE_ void add_debug_contact(const Vector3 &p_contact) { - if (contactDebugCount < contactDebug.size()) contactDebug[contactDebugCount++] = p_contact; + if (contactDebugCount < contactDebug.size()) contactDebug.write[contactDebugCount++] = p_contact; } _FORCE_INLINE_ Vector<Vector3> get_debug_contacts() { return contactDebug; } _FORCE_INLINE_ int get_debug_contact_count() { return contactDebugCount; } @@ -172,7 +176,8 @@ public: void update_gravity(); - bool test_body_motion(RigidBodyBullet *p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer::MotionResult *r_result); + bool test_body_motion(RigidBodyBullet *p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer::MotionResult *r_result, bool p_exclude_raycast_shapes); + int test_ray_separation(RigidBodyBullet *p_body, const Transform &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, PhysicsServer::SeparationResult *r_results, int p_result_max, float p_margin); private: void create_empty_world(bool p_create_soft_world); @@ -206,5 +211,7 @@ private: /// This is an API that recover a kinematic object from penetration /// Using this we leave Bullet to select the best algorithm, For example GJK in case we have Convex Convex, or a Bullet accelerated algorithm bool RFP_convex_world_test(const btConvexShape *p_shapeA, const btCollisionShape *p_shapeB, btCollisionObject *p_objectA, btCollisionObject *p_objectB, int p_shapeId_A, int p_shapeId_B, const btTransform &p_transformA, const btTransform &p_transformB, btScalar p_recover_movement_scale, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = NULL); + + int recover_from_penetration_ray(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, int p_result_max, btVector3 &r_delta_recover_movement, PhysicsServer::SeparationResult *r_results); }; #endif diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp index 4e6e701bfd..88f3c0338a 100644 --- a/modules/csg/csg.cpp +++ b/modules/csg/csg.cpp @@ -29,10 +29,10 @@ /*************************************************************************/ #include "csg.h" -#include "face3.h" -#include "geometry.h" -#include "os/os.h" -#include "sort.h" +#include "core/math/face3.h" +#include "core/math/geometry.h" +#include "core/os/os.h" +#include "core/sort.h" #include "thirdparty/misc/triangulator.h" void CSGBrush::clear() { @@ -62,7 +62,7 @@ void CSGBrush::build_from_faces(const PoolVector<Vector3> &p_vertices, const Poo faces.resize(p_vertices.size() / 3); for (int i = 0; i < faces.size(); i++) { - Face &f = faces[i]; + Face &f = faces.write[i]; f.vertices[0] = rv[i * 3 + 0]; f.vertices[1] = rv[i * 3 + 1]; f.vertices[2] = rv[i * 3 + 2]; @@ -101,7 +101,7 @@ void CSGBrush::build_from_faces(const PoolVector<Vector3> &p_vertices, const Poo materials.resize(material_map.size()); for (Map<Ref<Material>, int>::Element *E = material_map.front(); E; E = E->next()) { - materials[E->get()] = E->key(); + materials.write[E->get()] = E->key(); } _regen_face_aabbs(); @@ -111,10 +111,10 @@ void CSGBrush::_regen_face_aabbs() { for (int i = 0; i < faces.size(); i++) { - faces[i].aabb.position = faces[i].vertices[0]; - faces[i].aabb.expand_to(faces[i].vertices[1]); - faces[i].aabb.expand_to(faces[i].vertices[2]); - faces[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision erros + faces.write[i].aabb.position = faces[i].vertices[0]; + faces.write[i].aabb.expand_to(faces[i].vertices[1]); + faces.write[i].aabb.expand_to(faces[i].vertices[2]); + faces.write[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision erros } } @@ -125,7 +125,7 @@ void CSGBrush::copy_from(const CSGBrush &p_brush, const Transform &p_xform) { for (int i = 0; i < faces.size(); i++) { for (int j = 0; j < 3; j++) { - faces[i].vertices[j] = p_xform.xform(p_brush.faces[i].vertices[j]); + faces.write[i].vertices[j] = p_xform.xform(p_brush.faces[i].vertices[j]); } } @@ -292,12 +292,12 @@ void CSGBrushOperation::BuildPoly::_clip_segment(const CSGBrush *p_brush, int p_ for (int j = 0; j < 2; j++) { if (edges[i].points[0] == segment_idx[0] || edges[i].points[1] == segment_idx[1] || edges[i].points[0] == segment_idx[1] || edges[i].points[1] == segment_idx[0]) { - edge_valid = false; //segment has this point, cant check against this + edge_valid = false; //segment has this point, can't check against this break; } } - if (!edge_valid) //already hit a point in this edge, so dont test it + if (!edge_valid) //already hit a point in this edge, so don't test it continue; //see if either points are within the edge isntead of crossing it @@ -341,7 +341,7 @@ void CSGBrushOperation::BuildPoly::_clip_segment(const CSGBrush *p_brush, int p_ new_edge.points[0] = edges[i].points[0]; new_edge.points[1] = point_idx; new_edge.outer = edges[i].outer; - edges[i].points[0] = point_idx; + edges.write[i].points[0] = point_idx; edges.insert(i, new_edge); i++; //skip newly inserted edge base_edges++; //will need an extra one in the base triangle @@ -573,7 +573,7 @@ void CSGBrushOperation::_collision_callback(const CSGBrush *A, int p_face_a, Map } } - //if we are still here, it means they most likely intersect, so create BuildPolys if they dont existy + //if we are still here, it means they most likely intersect, so create BuildPolys if they don't exist BuildPoly *poly_a = NULL; @@ -637,7 +637,7 @@ void CSGBrushOperation::_add_poly_points(const BuildPoly &p_poly, int p_edge, in int to_point = e.edge_point; int current_edge = e.edge; - edge_process[e.edge] = true; //mark as processed + edge_process.write[e.edge] = true; //mark as processed int limit = p_poly.points.size() * 4; //avoid infinite recursion @@ -708,7 +708,7 @@ void CSGBrushOperation::_add_poly_points(const BuildPoly &p_poly, int p_edge, in prev_point = to_point; to_point = next_point; - edge_process[next_edge] = true; //mark this edge as processed + edge_process.write[next_edge] = true; //mark this edge as processed current_edge = next_edge; limit--; @@ -750,7 +750,7 @@ void CSGBrushOperation::_add_poly_outline(const BuildPoly &p_poly, int p_from_po t2d.affine_invert(); - float max_angle; + float max_angle = 0; int next_point_angle = -1; for (int i = 0; i < vertex_process[to_point].size(); i++) { @@ -792,20 +792,20 @@ void CSGBrushOperation::_merge_poly(MeshMerge &mesh, int p_face_idx, const Build //none processed by default for (int i = 0; i < edge_process.size(); i++) { - edge_process[i] = false; + edge_process.write[i] = false; } //put edges in points, so points can go through them for (int i = 0; i < p_poly.edges.size(); i++) { - vertex_process[p_poly.edges[i].points[0]].push_back(i); - vertex_process[p_poly.edges[i].points[1]].push_back(i); + vertex_process.write[p_poly.edges[i].points[0]].push_back(i); + vertex_process.write[p_poly.edges[i].points[1]].push_back(i); } Vector<PolyPoints> polys; //process points that were not processed for (int i = 0; i < edge_process.size(); i++) { - if (edge_process[i] == true) + if (edge_process[i]) continue; //already processed int intersect_poly = -1; @@ -854,7 +854,7 @@ void CSGBrushOperation::_merge_poly(MeshMerge &mesh, int p_face_idx, const Build _add_poly_outline(p_poly, p_poly.edges[i].points[0], p_poly.edges[i].points[1], vertex_process, outline); if (outline.size() > 1) { - polys[intersect_poly].holes.push_back(outline); + polys.write[intersect_poly].holes.push_back(outline); } } _add_poly_points(p_poly, i, p_poly.edges[i].points[0], p_poly.edges[i].points[1], vertex_process, edge_process, polys); @@ -896,7 +896,7 @@ void CSGBrushOperation::_merge_poly(MeshMerge &mesh, int p_face_idx, const Build Vector2 to = p_poly.points[to_idx].point; with_outline_vertex = l; - //try agaisnt outline (other points) first + //try against outline (other points) first valid = true; @@ -915,7 +915,7 @@ void CSGBrushOperation::_merge_poly(MeshMerge &mesh, int p_face_idx, const Build if (!valid) continue; - //try agaisnt all holes including self + //try against all holes including self for (int m = 0; m < polys[i].holes.size(); m++) { @@ -953,18 +953,18 @@ void CSGBrushOperation::_merge_poly(MeshMerge &mesh, int p_face_idx, const Build //duplicate point int insert_at = with_outline_vertex; - polys[i].points.insert(insert_at, polys[i].points[insert_at]); + polys.write[i].points.insert(insert_at, polys[i].points[insert_at]); insert_at++; //insert all others, outline should be backwards (must check) int holesize = polys[i].holes[j].size(); for (int k = 0; k <= holesize; k++) { int idx = (from_hole_vertex + k) % holesize; - polys[i].points.insert(insert_at, polys[i].holes[j][idx]); + polys.write[i].points.insert(insert_at, polys[i].holes[j][idx]); insert_at++; } added_hole = true; - polys[i].holes.remove(j); + polys.write[i].holes.remove(j); break; //got rid of hole, break and continue } } @@ -980,7 +980,7 @@ void CSGBrushOperation::_merge_poly(MeshMerge &mesh, int p_face_idx, const Build Vector<Vector2> vertices; vertices.resize(polys[i].points.size()); for (int j = 0; j < vertices.size(); j++) { - vertices[j] = p_poly.points[polys[i].points[j]].point; + vertices.write[j] = p_poly.points[polys[i].points[j]].point; } Vector<int> indices = Geometry::triangulate_polygon(vertices); @@ -1267,7 +1267,7 @@ void CSGBrushOperation::MeshMerge::mark_inside_faces() { int intersections = _bvh_count_intersections(bvh, max_depth, max_alloc - 1, center, target, i); if (intersections & 1) { - faces[i].inside = true; + faces.write[i].inside = true; } } } @@ -1419,13 +1419,13 @@ void CSGBrushOperation::merge_brushes(Operation p_operation, const CSGBrush &p_A if (mesh_merge.faces[i].inside) continue; for (int j = 0; j < 3; j++) { - result.faces[outside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; - result.faces[outside_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + result.faces.write[outside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + result.faces.write[outside_count].uvs[j] = mesh_merge.faces[i].uvs[j]; } - result.faces[outside_count].smooth = mesh_merge.faces[i].smooth; - result.faces[outside_count].invert = mesh_merge.faces[i].invert; - result.faces[outside_count].material = mesh_merge.faces[i].material_idx; + result.faces.write[outside_count].smooth = mesh_merge.faces[i].smooth; + result.faces.write[outside_count].invert = mesh_merge.faces[i].invert; + result.faces.write[outside_count].material = mesh_merge.faces[i].material_idx; outside_count++; } @@ -1451,13 +1451,13 @@ void CSGBrushOperation::merge_brushes(Operation p_operation, const CSGBrush &p_A if (!mesh_merge.faces[i].inside) continue; for (int j = 0; j < 3; j++) { - result.faces[inside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; - result.faces[inside_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + result.faces.write[inside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + result.faces.write[inside_count].uvs[j] = mesh_merge.faces[i].uvs[j]; } - result.faces[inside_count].smooth = mesh_merge.faces[i].smooth; - result.faces[inside_count].invert = mesh_merge.faces[i].invert; - result.faces[inside_count].material = mesh_merge.faces[i].material_idx; + result.faces.write[inside_count].smooth = mesh_merge.faces[i].smooth; + result.faces.write[inside_count].invert = mesh_merge.faces[i].invert; + result.faces.write[inside_count].material = mesh_merge.faces[i].material_idx; inside_count++; } @@ -1489,19 +1489,19 @@ void CSGBrushOperation::merge_brushes(Operation p_operation, const CSGBrush &p_A continue; for (int j = 0; j < 3; j++) { - result.faces[face_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; - result.faces[face_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + result.faces.write[face_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + result.faces.write[face_count].uvs[j] = mesh_merge.faces[i].uvs[j]; } if (mesh_merge.faces[i].from_b) { //invert facing of insides of B - SWAP(result.faces[face_count].vertices[1], result.faces[face_count].vertices[2]); - SWAP(result.faces[face_count].uvs[1], result.faces[face_count].uvs[2]); + SWAP(result.faces.write[face_count].vertices[1], result.faces.write[face_count].vertices[2]); + SWAP(result.faces.write[face_count].uvs[1], result.faces.write[face_count].uvs[2]); } - result.faces[face_count].smooth = mesh_merge.faces[i].smooth; - result.faces[face_count].invert = mesh_merge.faces[i].invert; - result.faces[face_count].material = mesh_merge.faces[i].material_idx; + result.faces.write[face_count].smooth = mesh_merge.faces[i].smooth; + result.faces.write[face_count].invert = mesh_merge.faces[i].invert; + result.faces.write[face_count].material = mesh_merge.faces[i].material_idx; face_count++; } @@ -1513,6 +1513,6 @@ void CSGBrushOperation::merge_brushes(Operation p_operation, const CSGBrush &p_A //updatelist of materials result.materials.resize(mesh_merge.materials.size()); for (const Map<Ref<Material>, int>::Element *E = mesh_merge.materials.front(); E; E = E->next()) { - result.materials[E->get()] = E->key(); + result.materials.write[E->get()] = E->key(); } } diff --git a/modules/csg/csg.h b/modules/csg/csg.h index 53303a6533..5d6432eca8 100644 --- a/modules/csg/csg.h +++ b/modules/csg/csg.h @@ -31,15 +31,15 @@ #ifndef CSG_H #define CSG_H -#include "aabb.h" -#include "dvector.h" -#include "map.h" -#include "math_2d.h" -#include "oa_hash_map.h" -#include "plane.h" +#include "core/dvector.h" +#include "core/map.h" +#include "core/math/aabb.h" +#include "core/math/plane.h" +#include "core/math/rect2.h" +#include "core/math/transform.h" +#include "core/math/vector3.h" +#include "core/oa_hash_map.h" #include "scene/resources/material.h" -#include "transform.h" -#include "vector3.h" struct CSGBrush { diff --git a/modules/csg/csg_gizmos.cpp b/modules/csg/csg_gizmos.cpp index 2150320c4a..5864d02615 100644 --- a/modules/csg/csg_gizmos.cpp +++ b/modules/csg/csg_gizmos.cpp @@ -32,7 +32,16 @@ /////////// -String CSGShapeSpatialGizmo::get_handle_name(int p_idx) const { +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); + create_handle_material("handles"); +} + +String CSGShapeSpatialGizmoPlugin::get_handle_name(const EditorSpatialGizmo *p_gizmo, int p_idx) const { + + CSGShape *cs = Object::cast_to<CSGShape>(p_gizmo->get_spatial_node()); if (Object::cast_to<CSGSphere>(cs)) { @@ -57,7 +66,9 @@ String CSGShapeSpatialGizmo::get_handle_name(int p_idx) const { return ""; } -Variant CSGShapeSpatialGizmo::get_handle_value(int p_idx) const { +Variant CSGShapeSpatialGizmoPlugin::get_handle_value(EditorSpatialGizmo *p_gizmo, int p_idx) const { + + CSGShape *cs = Object::cast_to<CSGShape>(p_gizmo->get_spatial_node()); if (Object::cast_to<CSGSphere>(cs)) { @@ -89,10 +100,12 @@ Variant CSGShapeSpatialGizmo::get_handle_value(int p_idx) const { return Variant(); } -void CSGShapeSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 &p_point) { +void CSGShapeSpatialGizmoPlugin::set_handle(EditorSpatialGizmo *p_gizmo, int p_idx, Camera *p_camera, const Point2 &p_point) { + + CSGShape *cs = Object::cast_to<CSGShape>(p_gizmo->get_spatial_node()); Transform gt = cs->get_global_transform(); - gt.orthonormalize(); + //gt.orthonormalize(); Transform gi = gt.affine_inverse(); Vector3 ray_from = p_camera->project_ray_origin(p_point); @@ -126,9 +139,9 @@ void CSGShapeSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 d = 0.001; switch (p_idx) { - case 0: s->set_width(d); break; - case 1: s->set_height(d); break; - case 2: s->set_depth(d); break; + case 0: s->set_width(d * 2); break; + case 1: s->set_height(d * 2); break; + case 2: s->set_depth(d * 2); break; } } @@ -170,7 +183,9 @@ void CSGShapeSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 s->set_outer_radius(d); } } -void CSGShapeSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) { +void CSGShapeSpatialGizmoPlugin::commit_handle(EditorSpatialGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) { + + CSGShape *cs = Object::cast_to<CSGShape>(p_gizmo->get_spatial_node()); if (Object::cast_to<CSGSphere>(cs)) { CSGSphere *s = Object::cast_to<CSGSphere>(cs); @@ -200,7 +215,7 @@ void CSGShapeSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bo UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); ur->create_action(TTR("Change Box Shape Extents")); static const char *method[3] = { "set_width", "set_height", "set_depth" }; - float current; + float current = 0; switch (p_idx) { case 0: current = s->get_width(); break; case 1: current = s->get_height(); break; @@ -260,12 +275,26 @@ void CSGShapeSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bo ur->commit_action(); } } -void CSGShapeSpatialGizmo::redraw() { +bool CSGShapeSpatialGizmoPlugin::has_gizmo(Spatial *p_spatial) { + return Object::cast_to<CSGSphere>(p_spatial) || Object::cast_to<CSGBox>(p_spatial) || Object::cast_to<CSGCylinder>(p_spatial) || Object::cast_to<CSGTorus>(p_spatial) || Object::cast_to<CSGMesh>(p_spatial) || Object::cast_to<CSGPolygon>(p_spatial); +} + +String CSGShapeSpatialGizmoPlugin::get_name() const { + return "CSGShapes"; +} + +bool CSGShapeSpatialGizmoPlugin::is_selectable_when_hidden() const { + return true; +} + +void CSGShapeSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { + + CSGShape *cs = Object::cast_to<CSGShape>(p_gizmo->get_spatial_node()); - clear(); + p_gizmo->clear(); - Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/csg"); - Ref<Material> material = create_material("shape_material", gizmo_color); + Ref<Material> material = get_material("shape_material", p_gizmo); + Ref<Material> handles_material = get_material("handles"); PoolVector<Vector3> faces = cs->get_brush_faces(); @@ -278,14 +307,14 @@ void CSGShapeSpatialGizmo::redraw() { int f = i / 6; for (int j = 0; j < 3; j++) { int j_n = (j + 1) % 3; - lines[i + j * 2 + 0] = r[f * 3 + j]; - lines[i + j * 2 + 1] = r[f * 3 + j_n]; + lines.write[i + j * 2 + 0] = r[f * 3 + j]; + lines.write[i + j * 2 + 1] = r[f * 3 + j_n]; } } } - add_lines(lines, material); - add_collision_segments(lines); + p_gizmo->add_lines(lines, material); + p_gizmo->add_collision_segments(lines); if (Object::cast_to<CSGSphere>(cs)) { CSGSphere *s = Object::cast_to<CSGSphere>(cs); @@ -293,17 +322,17 @@ void CSGShapeSpatialGizmo::redraw() { float r = s->get_radius(); Vector<Vector3> handles; handles.push_back(Vector3(r, 0, 0)); - add_handles(handles); + p_gizmo->add_handles(handles, handles_material); } if (Object::cast_to<CSGBox>(cs)) { CSGBox *s = Object::cast_to<CSGBox>(cs); Vector<Vector3> handles; - handles.push_back(Vector3(s->get_width(), 0, 0)); - handles.push_back(Vector3(0, s->get_height(), 0)); - handles.push_back(Vector3(0, 0, s->get_depth())); - add_handles(handles); + handles.push_back(Vector3(s->get_width() * 0.5, 0, 0)); + handles.push_back(Vector3(0, s->get_height() * 0.5, 0)); + handles.push_back(Vector3(0, 0, s->get_depth() * 0.5)); + p_gizmo->add_handles(handles, handles_material); } if (Object::cast_to<CSGCylinder>(cs)) { @@ -312,7 +341,7 @@ void CSGShapeSpatialGizmo::redraw() { Vector<Vector3> handles; handles.push_back(Vector3(s->get_radius(), 0, 0)); handles.push_back(Vector3(0, s->get_height() * 0.5, 0)); - add_handles(handles); + p_gizmo->add_handles(handles, handles_material); } if (Object::cast_to<CSGTorus>(cs)) { @@ -321,25 +350,11 @@ void CSGShapeSpatialGizmo::redraw() { Vector<Vector3> handles; handles.push_back(Vector3(s->get_inner_radius(), 0, 0)); handles.push_back(Vector3(s->get_outer_radius(), 0, 0)); - add_handles(handles); + p_gizmo->add_handles(handles, handles_material); } } -CSGShapeSpatialGizmo::CSGShapeSpatialGizmo(CSGShape *p_cs) { - - cs = p_cs; - set_spatial_node(p_cs); -} - -Ref<SpatialEditorGizmo> EditorPluginCSG::create_spatial_gizmo(Spatial *p_spatial) { - if (Object::cast_to<CSGSphere>(p_spatial) || Object::cast_to<CSGBox>(p_spatial) || Object::cast_to<CSGCylinder>(p_spatial) || Object::cast_to<CSGTorus>(p_spatial) || Object::cast_to<CSGMesh>(p_spatial) || Object::cast_to<CSGPolygon>(p_spatial)) { - Ref<CSGShapeSpatialGizmo> csg = memnew(CSGShapeSpatialGizmo(Object::cast_to<CSGShape>(p_spatial))); - return csg; - } - - return Ref<SpatialEditorGizmo>(); -} EditorPluginCSG::EditorPluginCSG(EditorNode *p_editor) { - - EDITOR_DEF("editors/3d_gizmos/gizmo_colors/csg", Color(0.2, 0.5, 1, 0.1)); + Ref<CSGShapeSpatialGizmoPlugin> gizmo_plugin = Ref<CSGShapeSpatialGizmoPlugin>(memnew(CSGShapeSpatialGizmoPlugin)); + SpatialEditor::get_singleton()->register_gizmo_plugin(gizmo_plugin); } diff --git a/modules/csg/csg_gizmos.h b/modules/csg/csg_gizmos.h index 68e916823b..d65d1f58c1 100644 --- a/modules/csg/csg_gizmos.h +++ b/modules/csg/csg_gizmos.h @@ -35,25 +35,27 @@ #include "editor/editor_plugin.h" #include "editor/spatial_editor_gizmos.h" -class CSGShapeSpatialGizmo : public EditorSpatialGizmo { +class CSGShapeSpatialGizmoPlugin : public EditorSpatialGizmoPlugin { - GDCLASS(CSGShapeSpatialGizmo, EditorSpatialGizmo); - - CSGShape *cs; + GDCLASS(CSGShapeSpatialGizmoPlugin, EditorSpatialGizmoPlugin); public: - virtual String get_handle_name(int p_idx) const; - virtual Variant get_handle_value(int p_idx) const; - virtual void set_handle(int p_idx, Camera *p_camera, const Point2 &p_point); - virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false); - void redraw(); - CSGShapeSpatialGizmo(CSGShape *p_cs = NULL); + bool has_gizmo(Spatial *p_spatial); + String get_name() const; + bool is_selectable_when_hidden() const; + void redraw(EditorSpatialGizmo *p_gizmo); + + String get_handle_name(const EditorSpatialGizmo *p_gizmo, int p_idx) const; + Variant get_handle_value(EditorSpatialGizmo *p_gizmo, int p_idx) const; + void set_handle(EditorSpatialGizmo *p_gizmo, int p_idx, Camera *p_camera, const Point2 &p_point); + void commit_handle(EditorSpatialGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel); + + CSGShapeSpatialGizmoPlugin(); }; class EditorPluginCSG : public EditorPlugin { GDCLASS(EditorPluginCSG, EditorPlugin) public: - virtual Ref<SpatialEditorGizmo> create_spatial_gizmo(Spatial *p_spatial); EditorPluginCSG(EditorNode *p_editor); }; diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 5f13474d2c..f4b061f494 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -47,6 +47,7 @@ void CSGShape::set_use_collision(bool p_enable) { PhysicsServer::get_singleton()->body_set_state(root_collision_instance, PhysicsServer::BODY_STATE_TRANSFORM, get_global_transform()); PhysicsServer::get_singleton()->body_add_shape(root_collision_instance, root_collision_shape->get_rid()); PhysicsServer::get_singleton()->body_set_space(root_collision_instance, get_world()->get_space()); + PhysicsServer::get_singleton()->body_attach_object_instance_id(root_collision_instance, get_instance_id()); _make_dirty(); //force update } else { PhysicsServer::get_singleton()->free(root_collision_instance); @@ -159,9 +160,62 @@ CSGBrush *CSGShape::_get_brush() { return brush; } -void CSGShape::_update_shape() { +int CSGShape::mikktGetNumFaces(const SMikkTSpaceContext *pContext) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + return surface.vertices.size() / 3; +} + +int CSGShape::mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace) { + // always 3 + return 3; +} + +void CSGShape::mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + Vector3 v = surface.verticesw[iFace * 3 + iVert]; + fvPosOut[0] = v.x; + fvPosOut[1] = v.y; + fvPosOut[2] = v.z; +} + +void CSGShape::mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); - //print_line("updating shape for " + String(get_path())); + Vector3 n = surface.normalsw[iFace * 3 + iVert]; + fvNormOut[0] = n.x; + fvNormOut[1] = n.y; + fvNormOut[2] = n.z; +} + +void CSGShape::mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + Vector2 t = surface.uvsw[iFace * 3 + iVert]; + fvTexcOut[0] = t.x; + fvTexcOut[1] = t.y; +} + +void CSGShape::mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert) { + + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + int i = iFace * 3 + iVert; + Vector3 normal = surface.normalsw[i]; + Vector3 tangent = Vector3(fvTangent[0], fvTangent[1], fvTangent[2]); + Vector3 bitangent = Vector3(-fvBiTangent[0], -fvBiTangent[1], -fvBiTangent[2]); // for some reason these are reversed, something with the coordinate system in Godot + float d = bitangent.dot(normal.cross(tangent)); + + i *= 4; + surface.tansw[i++] = tangent.x; + surface.tansw[i++] = tangent.y; + surface.tansw[i++] = tangent.z; + surface.tansw[i++] = d < 0 ? -1 : 1; +} + +void CSGShape::_update_shape() { if (parent) return; @@ -177,7 +231,7 @@ void CSGShape::_update_shape() { Vector<int> face_count; face_count.resize(n->materials.size() + 1); for (int i = 0; i < face_count.size(); i++) { - face_count[i] = 0; + face_count.write[i] = 0; } for (int i = 0; i < n->faces.size(); i++) { @@ -200,7 +254,7 @@ void CSGShape::_update_shape() { } } - face_count[idx]++; + face_count.write[idx]++; } Vector<ShapeUpdateSurface> surfaces; @@ -210,18 +264,24 @@ void CSGShape::_update_shape() { //create arrays for (int i = 0; i < surfaces.size(); i++) { - surfaces[i].vertices.resize(face_count[i] * 3); - surfaces[i].normals.resize(face_count[i] * 3); - surfaces[i].uvs.resize(face_count[i] * 3); - surfaces[i].last_added = 0; + surfaces.write[i].vertices.resize(face_count[i] * 3); + surfaces.write[i].normals.resize(face_count[i] * 3); + surfaces.write[i].uvs.resize(face_count[i] * 3); + if (calculate_tangents) { + surfaces.write[i].tans.resize(face_count[i] * 3 * 4); + } + surfaces.write[i].last_added = 0; if (i != surfaces.size() - 1) { - surfaces[i].material = n->materials[i]; + surfaces.write[i].material = n->materials[i]; } - surfaces[i].verticesw = surfaces[i].vertices.write(); - surfaces[i].normalsw = surfaces[i].normals.write(); - surfaces[i].uvsw = surfaces[i].uvs.write(); + surfaces.write[i].verticesw = surfaces.write[i].vertices.write(); + surfaces.write[i].normalsw = surfaces.write[i].normals.write(); + surfaces.write[i].uvsw = surfaces.write[i].uvs.write(); + if (calculate_tangents) { + surfaces.write[i].tansw = surfaces.write[i].tans.write(); + } } //fill arrays @@ -276,12 +336,22 @@ void CSGShape::_update_shape() { normal = -normal; } - surfaces[idx].verticesw[last + order[j]] = v; - surfaces[idx].uvsw[last + order[j]] = n->faces[i].uvs[j]; - surfaces[idx].normalsw[last + order[j]] = normal; + int k = last + order[j]; + surfaces[idx].verticesw[k] = v; + surfaces[idx].uvsw[k] = n->faces[i].uvs[j]; + surfaces[idx].normalsw[k] = normal; + + if (calculate_tangents) { + // zero out our tangents for now + k *= 4; + surfaces[idx].tansw[k++] = 0.0; + surfaces[idx].tansw[k++] = 0.0; + surfaces[idx].tansw[k++] = 0.0; + surfaces[idx].tansw[k++] = 0.0; + } } - surfaces[idx].last_added += 3; + surfaces.write[idx].last_added += 3; } } @@ -289,20 +359,43 @@ void CSGShape::_update_shape() { //create surfaces for (int i = 0; i < surfaces.size(); i++) { + // calculate tangents for this surface + bool have_tangents = calculate_tangents; + if (have_tangents) { + SMikkTSpaceInterface mkif; + mkif.m_getNormal = mikktGetNormal; + mkif.m_getNumFaces = mikktGetNumFaces; + mkif.m_getNumVerticesOfFace = mikktGetNumVerticesOfFace; + mkif.m_getPosition = mikktGetPosition; + mkif.m_getTexCoord = mikktGetTexCoord; + mkif.m_setTSpace = mikktSetTSpaceDefault; + mkif.m_setTSpaceBasic = NULL; + + SMikkTSpaceContext msc; + msc.m_pInterface = &mkif; + msc.m_pUserData = &surfaces.write[i]; + have_tangents = genTangSpaceDefault(&msc); + } - surfaces[i].verticesw = PoolVector<Vector3>::Write(); - surfaces[i].normalsw = PoolVector<Vector3>::Write(); - surfaces[i].uvsw = PoolVector<Vector2>::Write(); + // 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(); if (surfaces[i].last_added == 0) continue; + // and convert to surface array Array array; array.resize(Mesh::ARRAY_MAX); array[Mesh::ARRAY_VERTEX] = surfaces[i].vertices; array[Mesh::ARRAY_NORMAL] = surfaces[i].normals; array[Mesh::ARRAY_TEX_UV] = surfaces[i].uvs; + if (have_tangents) { + array[Mesh::ARRAY_TANGENT] = surfaces[i].tans; + } int idx = root_mesh->get_surface_count(); root_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array); @@ -365,6 +458,7 @@ void CSGShape::_notification(int p_what) { PhysicsServer::get_singleton()->body_set_state(root_collision_instance, PhysicsServer::BODY_STATE_TRANSFORM, get_global_transform()); PhysicsServer::get_singleton()->body_add_shape(root_collision_instance, root_collision_shape->get_rid()); PhysicsServer::get_singleton()->body_set_space(root_collision_instance, get_world()->get_space()); + PhysicsServer::get_singleton()->body_attach_object_instance_id(root_collision_instance, get_instance_id()); } _make_dirty(); @@ -372,7 +466,6 @@ void CSGShape::_notification(int p_what) { if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { - //print_line("local xform changed"); if (parent) { parent->_make_dirty(); } @@ -403,6 +496,15 @@ CSGShape::Operation CSGShape::get_operation() const { return operation; } +void CSGShape::set_calculate_tangents(bool p_calculate_tangents) { + calculate_tangents = p_calculate_tangents; + _make_dirty(); +} + +bool CSGShape::is_calculating_tangents() const { + return calculate_tangents; +} + void CSGShape::_validate_property(PropertyInfo &property) const { if (is_inside_tree() && property.name.begins_with("use_collision") && !is_root_shape()) { //hide collision if not root @@ -424,9 +526,13 @@ void CSGShape::_bind_methods() { ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CSGShape::set_snap); ClassDB::bind_method(D_METHOD("get_snap"), &CSGShape::get_snap); + ClassDB::bind_method(D_METHOD("set_calculate_tangents", "enabled"), &CSGShape::set_calculate_tangents); + ClassDB::bind_method(D_METHOD("is_calculating_tangents"), &CSGShape::is_calculating_tangents); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_collision"), "set_use_collision", "is_using_collision"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "snap", PROPERTY_HINT_RANGE, "0.0001,1,0.001"), "set_snap", "get_snap"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents"); BIND_ENUM_CONSTANT(OPERATION_UNION); BIND_ENUM_CONSTANT(OPERATION_INTERSECTION); @@ -441,6 +547,7 @@ CSGShape::CSGShape() { use_collision = false; operation = OPERATION_UNION; snap = 0.001; + calculate_tangents = true; } CSGShape::~CSGShape() { @@ -524,6 +631,11 @@ CSGBrush *CSGMesh::_build_brush() { Array arrays = mesh->surface_get_arrays(i); + if (arrays.size() == 0) { + _make_dirty(); + ERR_FAIL_COND_V(arrays.size() == 0, NULL); + } + PoolVector<Vector3> avertices = arrays[Mesh::ARRAY_VERTEX]; if (avertices.size() == 0) continue; @@ -596,8 +708,8 @@ CSGBrush *CSGMesh::_build_brush() { mw[j / 3] = mat; } } else { - int is = vertices.size(); - int as = avertices.size(); + int as = vertices.size(); + int is = avertices.size(); vertices.resize(as + is); smooth.resize((as + is) / 3); @@ -641,7 +753,6 @@ CSGBrush *CSGMesh::_build_brush() { } } - //print_line("total vertices? " + itos(vertices.size())); if (vertices.size() == 0) return NULL; @@ -921,7 +1032,7 @@ CSGBrush *CSGBox::_build_brush() { int face = 0; - Vector3 vertex_mul(width, height, depth); + Vector3 vertex_mul(width * 0.5, height * 0.5, depth * 0.5); { @@ -1055,9 +1166,9 @@ Ref<Material> CSGBox::get_material() const { CSGBox::CSGBox() { // defaults - width = 1.0; - height = 1.0; - depth = 1.0; + width = 2.0; + height = 2.0; + depth = 2.0; } /////////////// @@ -1544,6 +1655,24 @@ CSGBrush *CSGPolygon::_build_brush() { Path *path = NULL; Ref<Curve3D> curve; + // get bounds for our polygon + Vector2 final_polygon_min; + Vector2 final_polygon_max; + for (int i = 0; i < final_polygon.size(); i++) { + Vector2 p = final_polygon[i]; + if (i == 0) { + final_polygon_min = p; + final_polygon_max = final_polygon_min; + } else { + if (p.x < final_polygon_min.x) final_polygon_min.x = p.x; + if (p.y < final_polygon_min.y) final_polygon_min.y = p.y; + + if (p.x > final_polygon_max.x) final_polygon_max.x = p.x; + if (p.y > final_polygon_max.y) final_polygon_max.y = p.y; + } + } + Vector2 final_polygon_size = final_polygon_max - final_polygon_min; + if (mode == MODE_PATH) { if (!has_node(path_node)) return NULL; @@ -1577,7 +1706,7 @@ CSGBrush *CSGPolygon::_build_brush() { } CSGBrush *brush = memnew(CSGBrush); - int face_count; + int face_count = 0; switch (mode) { case MODE_DEPTH: face_count = triangles.size() * 2 / 3 + (final_polygon.size()) * 2; break; @@ -1585,7 +1714,11 @@ CSGBrush *CSGPolygon::_build_brush() { case MODE_PATH: { float bl = curve->get_baked_length(); int splits = MAX(2, Math::ceil(bl / path_interval)); - face_count = triangles.size() * 2 / 3 + splits * final_polygon.size() * 2; + if (path_joined) { + face_count = splits * final_polygon.size() * 2; + } else { + face_count = triangles.size() * 2 / 3 + splits * final_polygon.size() * 2; + } } break; } @@ -1631,6 +1764,10 @@ CSGBrush *CSGPolygon::_build_brush() { v.z -= depth; } facesw[face * 3 + k] = v; + uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size; + if (i == 0) { + uvsw[face * 3 + k].x = 1.0 - uvsw[face * 3 + k].x; /* flip x */ + } } smoothw[face] = false; @@ -1762,6 +1899,7 @@ CSGBrush *CSGPolygon::_build_brush() { Vector2 p = final_polygon[triangles[j + src[k]]]; Vector3 v = Vector3(p.x, p.y, 0); facesw[face * 3 + k] = v; + uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size; } smoothw[face] = false; @@ -1779,6 +1917,8 @@ CSGBrush *CSGPolygon::_build_brush() { Vector2 p = final_polygon[triangles[j + src[k]]]; Vector3 v = Vector3(normali_n.x * p.x, p.y, normali_n.z * p.x); facesw[face * 3 + k] = v; + uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size; + uvsw[face * 3 + k].x = 1.0 - uvsw[face * 3 + k].x; /* flip x */ } smoothw[face] = false; @@ -1793,8 +1933,14 @@ CSGBrush *CSGPolygon::_build_brush() { float bl = curve->get_baked_length(); int splits = MAX(2, Math::ceil(bl / path_interval)); + float u1 = 0.0; + float u2 = path_continuous_u ? 0.0 : 1.0; - Transform path_to_this = get_global_transform().affine_inverse() * path->get_global_transform(); + Transform path_to_this; + if (!path_local) { + // center on paths origin + path_to_this = get_global_transform().affine_inverse() * path->get_global_transform(); + } Transform prev_xf; @@ -1812,6 +1958,9 @@ CSGBrush *CSGPolygon::_build_brush() { for (int i = 0; i <= splits; i++) { float ofs = i * path_interval; + if (i == splits && path_joined) { + ofs = 0.0; + } Transform xf; xf.origin = curve->interpolate_baked(ofs); @@ -1836,6 +1985,11 @@ CSGBrush *CSGPolygon::_build_brush() { xf = path_to_this * xf; if (i > 0) { + if (path_continuous_u) { + u1 = u2; + u2 += (prev_xf.origin - xf.origin).length(); + }; + //put triangles where they belong //add triangles for depth for (int j = 0; j < final_polygon.size(); j++) { @@ -1850,10 +2004,10 @@ CSGBrush *CSGPolygon::_build_brush() { }; Vector2 u[4] = { - Vector2(0, 0), - Vector2(0, 1), - Vector2(1, 1), - Vector2(1, 0) + Vector2(u1, 1), + Vector2(u1, 0), + Vector2(u2, 0), + Vector2(u2, 1) }; // face 1 @@ -1888,7 +2042,7 @@ CSGBrush *CSGPolygon::_build_brush() { } } - if (i == 0) { + if (i == 0 && !path_joined) { for (int j = 0; j < triangles.size(); j += 3) { for (int k = 0; k < 3; k++) { @@ -1896,6 +2050,7 @@ CSGBrush *CSGPolygon::_build_brush() { Vector2 p = final_polygon[triangles[j + src[k]]]; Vector3 v = Vector3(p.x, p.y, 0); facesw[face * 3 + k] = xf.xform(v); + uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size; } smoothw[face] = false; @@ -1905,7 +2060,7 @@ CSGBrush *CSGPolygon::_build_brush() { } } - if (i == splits) { + if (i == splits && !path_joined) { for (int j = 0; j < triangles.size(); j += 3) { for (int k = 0; k < 3; k++) { @@ -1913,6 +2068,8 @@ CSGBrush *CSGPolygon::_build_brush() { Vector2 p = final_polygon[triangles[j + src[k]]]; Vector3 v = Vector3(p.x, p.y, 0); facesw[face * 3 + k] = xf.xform(v); + uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size; + uvsw[face * 3 + k].x = 1.0 - uvsw[face * 3 + k].x; /* flip x */ } smoothw[face] = false; @@ -1937,6 +2094,9 @@ CSGBrush *CSGPolygon::_build_brush() { } else { aabb.expand_to(facesw[i]); } + + // invert UVs on the Y-axis OpenGL = upside down + uvsw[i].y = 1.0 - uvsw[i].y; } } @@ -2003,6 +2163,15 @@ void CSGPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("set_path_rotation", "mode"), &CSGPolygon::set_path_rotation); ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGPolygon::get_path_rotation); + ClassDB::bind_method(D_METHOD("set_path_local", "enable"), &CSGPolygon::set_path_local); + ClassDB::bind_method(D_METHOD("is_path_local"), &CSGPolygon::is_path_local); + + ClassDB::bind_method(D_METHOD("set_path_continuous_u", "enable"), &CSGPolygon::set_path_continuous_u); + ClassDB::bind_method(D_METHOD("is_path_continuous_u"), &CSGPolygon::is_path_continuous_u); + + ClassDB::bind_method(D_METHOD("set_path_joined", "enable"), &CSGPolygon::set_path_joined); + ClassDB::bind_method(D_METHOD("is_path_joined"), &CSGPolygon::is_path_joined); + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGPolygon::set_material); ClassDB::bind_method(D_METHOD("get_material"), &CSGPolygon::get_material); @@ -2023,6 +2192,9 @@ void CSGPolygon::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "path_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Path"), "set_path_node", "get_path_node"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "path_interval", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_path_interval", "get_path_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "path_rotation", PROPERTY_HINT_ENUM, "Polygon,Path,PathFollow"), "set_path_rotation", "get_path_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_local"), "set_path_local", "is_path_local"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_continuous_u"), "set_path_continuous_u", "is_path_continuous_u"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_joined"), "set_path_joined", "is_path_joined"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); @@ -2067,6 +2239,15 @@ float CSGPolygon::get_depth() const { return depth; } +void CSGPolygon::set_path_continuous_u(bool p_enable) { + path_continuous_u = p_enable; + _make_dirty(); +} + +bool CSGPolygon::is_path_continuous_u() const { + return path_continuous_u; +} + void CSGPolygon::set_spin_degrees(const float p_spin_degrees) { ERR_FAIL_COND(p_spin_degrees < 0.01 || p_spin_degrees > 360); spin_degrees = p_spin_degrees; @@ -2119,6 +2300,26 @@ CSGPolygon::PathRotation CSGPolygon::get_path_rotation() const { return path_rotation; } +void CSGPolygon::set_path_local(bool p_enable) { + path_local = p_enable; + _make_dirty(); + update_gizmo(); +} + +bool CSGPolygon::is_path_local() const { + return path_local; +} + +void CSGPolygon::set_path_joined(bool p_enable) { + path_joined = p_enable; + _make_dirty(); + update_gizmo(); +} + +bool CSGPolygon::is_path_joined() const { + return path_joined; +} + void CSGPolygon::set_smooth_faces(const bool p_smooth_faces) { smooth_faces = p_smooth_faces; _make_dirty(); @@ -2160,5 +2361,8 @@ CSGPolygon::CSGPolygon() { smooth_faces = false; path_interval = 1; path_rotation = PATH_ROTATION_PATH; + path_local = false; + path_continuous_u = false; + path_joined = false; path_cache = NULL; } diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index cbb5c7e041..7326f3d36a 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -36,6 +36,7 @@ #include "csg.h" #include "scene/3d/visual_instance.h" #include "scene/resources/concave_polygon_shape.h" +#include "thirdparty/misc/mikktspace.h" class CSGShape : public VisualInstance { GDCLASS(CSGShape, VisualInstance); @@ -63,6 +64,8 @@ private: Ref<ConcavePolygonShape> root_collision_shape; RID root_collision_instance; + bool calculate_tangents; + Ref<ArrayMesh> root_mesh; struct Vector3Hasher { @@ -78,14 +81,25 @@ private: PoolVector<Vector3> vertices; PoolVector<Vector3> normals; PoolVector<Vector2> uvs; + PoolVector<float> tans; Ref<Material> material; int last_added; PoolVector<Vector3>::Write verticesw; PoolVector<Vector3>::Write normalsw; PoolVector<Vector2>::Write uvsw; + PoolVector<float>::Write tansw; }; + //mikktspace callbacks + static int mikktGetNumFaces(const SMikkTSpaceContext *pContext); + static int mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace); + static void mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert); + static void mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert); + static void mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert); + static void mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert); + void _update_shape(); protected: @@ -115,6 +129,9 @@ public: void set_snap(float p_snap); float get_snap() const; + void set_calculate_tangents(bool p_calculate_tangents); + bool is_calculating_tangents() const; + bool is_root_shape() const; CSGShape(); ~CSGShape(); @@ -334,10 +351,13 @@ private: NodePath path_node; float path_interval; PathRotation path_rotation; + bool path_local; Node *path_cache; bool smooth_faces; + bool path_continuous_u; + bool path_joined; bool _is_editable_3d_polygon() const; bool _has_editable_3d_polygon_no_depth() const; @@ -375,6 +395,15 @@ public: void set_path_rotation(PathRotation p_rotation); PathRotation get_path_rotation() const; + void set_path_local(bool p_enable); + bool is_path_local() const; + + void set_path_continuous_u(bool p_enable); + bool is_path_continuous_u() const; + + void set_path_joined(bool p_enable); + bool is_path_joined() const; + void set_smooth_faces(bool p_smooth_faces); bool get_smooth_faces() const; diff --git a/modules/csg/doc_classes/CSGBox.xml b/modules/csg/doc_classes/CSGBox.xml index 80455fda80..5ec7b5089d 100644 --- a/modules/csg/doc_classes/CSGBox.xml +++ b/modules/csg/doc_classes/CSGBox.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGBox" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + A CSG Box shape. </brief_description> <description> + This node allows you to create a box for use with the CSG system. </description> <tutorials> </tutorials> @@ -12,12 +14,16 @@ </methods> <members> <member name="depth" type="float" setter="set_depth" getter="get_depth"> + Depth of the box measured from the center of the box. </member> <member name="height" type="float" setter="set_height" getter="get_height"> + Height of the box measured from the center of the box. </member> <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"> + Width of the box measured from the center of the box. </member> </members> <constants> diff --git a/modules/csg/doc_classes/CSGCombiner.xml b/modules/csg/doc_classes/CSGCombiner.xml index b2265d7703..69c5df5840 100644 --- a/modules/csg/doc_classes/CSGCombiner.xml +++ b/modules/csg/doc_classes/CSGCombiner.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGCombiner" inherits="CSGShape" category="Core" version="3.1"> <brief_description> + A CSG node that allows you to combine other CSG modifiers. </brief_description> <description> + For complex arrangements of shapes, it is sometimes needed to add structure to your CSG nodes. The CSGCombiner node allows you to create this structure. The node encapsulates the result of the CSG operations of its children. In this way, it is possible to do operations on one set of shapes that are children of one CSGCombiner node, and a set of separate operations on a second set of shapes that are children of a second CSGCombiner node, and then do an operation that takes the two end results as its input to create the final shape. </description> <tutorials> </tutorials> diff --git a/modules/csg/doc_classes/CSGCylinder.xml b/modules/csg/doc_classes/CSGCylinder.xml index 0cab26ad3d..92b170ed1f 100644 --- a/modules/csg/doc_classes/CSGCylinder.xml +++ b/modules/csg/doc_classes/CSGCylinder.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGCylinder" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + A CSG Cylinder shape. </brief_description> <description> + This node allows you to create a cylinder (or cone) for use with the CSG system. </description> <tutorials> </tutorials> @@ -12,16 +14,22 @@ </methods> <members> <member name="cone" type="bool" setter="set_cone" getter="is_cone"> + If true a cone is created, the [member radius] will only apply to one side. </member> <member name="height" type="float" setter="set_height" getter="get_height"> + The height of the cylinder. </member> <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"> + The radius of the cylinder. </member> <member name="sides" type="int" setter="set_sides" getter="get_sides"> + The number of sides of the cylinder, the higher this number the more detail there will be in the cylinder. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + If true the normals of the cylinder are set to give a smooth effect making the cylinder seem rounded. When false the cylinder will have a flat shaded look. </member> </members> <constants> diff --git a/modules/csg/doc_classes/CSGMesh.xml b/modules/csg/doc_classes/CSGMesh.xml index e5c3e5ccf3..58e2bc1c4b 100644 --- a/modules/csg/doc_classes/CSGMesh.xml +++ b/modules/csg/doc_classes/CSGMesh.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGMesh" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + A CSG Mesh shape that uses a mesh resource. </brief_description> <description> + This CSG node allows you to use any mesh resource as a CSG shape, provided it is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more then two faces. </description> <tutorials> </tutorials> @@ -12,6 +14,7 @@ </methods> <members> <member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh"> + The mesh resource to use as a CSG shape. </member> </members> <constants> diff --git a/modules/csg/doc_classes/CSGPolygon.xml b/modules/csg/doc_classes/CSGPolygon.xml index 379c512d6a..a33e5557cb 100644 --- a/modules/csg/doc_classes/CSGPolygon.xml +++ b/modules/csg/doc_classes/CSGPolygon.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGPolygon" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + Extrudes a 2D polygon shape to create a 3D mesh. </brief_description> <description> + This node takes a 2D polygon shape and extrudes it to create a 3D mesh. </description> <tutorials> </tutorials> @@ -12,38 +14,63 @@ </methods> <members> <member name="depth" type="float" setter="set_depth" getter="get_depth"> + Extrusion depth when [member mode] is [constant MODE_DEPTH]. </member> <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"> + Extrusion mode. + </member> + <member name="path_continuous_u" type="bool" setter="set_path_continuous_u" getter="is_path_continuous_u"> + If true the u component of our uv will continuously increase in unison with the distance traveled along our path when [member mode] is [constant MODE_PATH]. </member> <member name="path_interval" type="float" setter="set_path_interval" getter="get_path_interval"> + Interval at which a new extrusion slice is added along the path when [member mode] is [constant MODE_PATH]. + </member> + <member name="path_joined" type="bool" setter="set_path_joined" getter="is_path_joined"> + If true the start and end of our path are joined together ensuring there is no seam when [member mode] is [constant MODE_PATH]. + </member> + <member name="path_local" type="bool" setter="set_path_local" getter="is_path_local"> + If false we extrude centered on our path, if true we extrude in relation to the position of our CSGPolygon when [member mode] is [constant MODE_PATH]. </member> <member name="path_node" type="NodePath" setter="set_path_node" getter="get_path_node"> + The [Shape] object containing the path along which we extrude when [member mode] is [constant MODE_PATH]. </member> <member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon.PathRotation"> + The method by which each slice is rotated along the path when [member mode] is [constant MODE_PATH]. </member> <member name="polygon" type="PoolVector2Array" setter="set_polygon" getter="get_polygon"> + Point array that defines the shape that we'll extrude. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + Generates smooth normals so smooth shading is applied to our mesh. </member> <member name="spin_degrees" type="float" setter="set_spin_degrees" getter="get_spin_degrees"> + Degrees to rotate our extrusion for each slice when [member mode] is [constant MODE_SPIN]. </member> <member name="spin_sides" type="int" setter="set_spin_sides" getter="get_spin_sides"> + Number of extrusion when [member mode] is [constant MODE_SPIN]. </member> </members> <constants> <constant name="MODE_DEPTH" value="0" enum="Mode"> + Shape is extruded to [member depth]. </constant> <constant name="MODE_SPIN" value="1" enum="Mode"> + Shape is extruded by rotating it around an axis. </constant> <constant name="MODE_PATH" value="2" enum="Mode"> + Shape is extruded along a path set by a [Shape] set in [member path_node]. </constant> <constant name="PATH_ROTATION_POLYGON" value="0" enum="PathRotation"> + Slice is not rotated. </constant> <constant name="PATH_ROTATION_PATH" value="1" enum="PathRotation"> + Slice is rotated around the up vector of the path. </constant> <constant name="PATH_ROTATION_PATH_FOLLOW" value="2" enum="PathRotation"> + Slice is rotate to match the path exactly. </constant> </constants> </class> diff --git a/modules/csg/doc_classes/CSGPrimitive.xml b/modules/csg/doc_classes/CSGPrimitive.xml index bf41c40f22..2591bab7e3 100644 --- a/modules/csg/doc_classes/CSGPrimitive.xml +++ b/modules/csg/doc_classes/CSGPrimitive.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGPrimitive" inherits="CSGShape" category="Core" version="3.1"> <brief_description> + Base class for CSG primitives. </brief_description> <description> </description> @@ -12,6 +13,7 @@ </methods> <members> <member name="invert_faces" type="bool" setter="set_invert_faces" getter="is_inverting_faces"> + Invert the faces of the mesh. </member> </members> <constants> diff --git a/modules/csg/doc_classes/CSGShape.xml b/modules/csg/doc_classes/CSGShape.xml index cf236a4207..ac3c2342fc 100644 --- a/modules/csg/doc_classes/CSGShape.xml +++ b/modules/csg/doc_classes/CSGShape.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGShape" inherits="VisualInstance" category="Core" version="3.1"> <brief_description> + The CSG base class. </brief_description> <description> + This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot. </description> <tutorials> </tutorials> @@ -13,23 +15,32 @@ <return type="bool"> </return> <description> + Returns true if this is a root shape and is thus the object that is rendered. </description> </method> </methods> <members> + <member name="calculate_tangents" type="bool" setter="set_calculate_tangents" getter="is_calculating_tangents"> + Calculate tangents for the CSG shape which allows the use of normal maps. This is only applied on the root shape, this setting is ignored on any child. + </member> <member name="operation" type="int" setter="set_operation" getter="get_operation" enum="CSGShape.Operation"> + The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent. </member> <member name="snap" type="float" setter="set_snap" getter="get_snap"> </member> <member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision"> + Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. </member> </members> <constants> <constant name="OPERATION_UNION" value="0" enum="Operation"> + Geometry of both primitives is merged, intersecting geometry is removed. </constant> <constant name="OPERATION_INTERSECTION" value="1" enum="Operation"> + 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. </constant> </constants> </class> diff --git a/modules/csg/doc_classes/CSGSphere.xml b/modules/csg/doc_classes/CSGSphere.xml index 520368506e..a0069879cb 100644 --- a/modules/csg/doc_classes/CSGSphere.xml +++ b/modules/csg/doc_classes/CSGSphere.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGSphere" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + A CSG Sphere shape. </brief_description> <description> + This node allows you to create a sphere for use with the CSG system. </description> <tutorials> </tutorials> @@ -12,14 +14,19 @@ </methods> <members> <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"> + Number of vertical slices for the sphere. </member> <member name="radius" type="float" setter="set_radius" getter="get_radius"> + Radius of the sphere. </member> <member name="rings" type="int" setter="set_rings" getter="get_rings"> + Number of horizontal slices for the sphere. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + If true the normals of the sphere are set to give a smooth effect making the sphere seem rounded. When false the sphere will have a flat shaded look. </member> </members> <constants> diff --git a/modules/csg/doc_classes/CSGTorus.xml b/modules/csg/doc_classes/CSGTorus.xml index 58bbef2600..187d71a2fa 100644 --- a/modules/csg/doc_classes/CSGTorus.xml +++ b/modules/csg/doc_classes/CSGTorus.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGTorus" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + A CSG Torus shape. </brief_description> <description> + This node allows you to create a torus for use with the CSG system. </description> <tutorials> </tutorials> @@ -12,16 +14,22 @@ </methods> <members> <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius"> + The inner radius of the torus. </member> <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"> + The outer radius of the torus. </member> <member name="ring_sides" type="int" setter="set_ring_sides" getter="get_ring_sides"> + The number of edges each ring of the torus is constructed of. </member> <member name="sides" type="int" setter="set_sides" getter="get_sides"> + The number of slices the torus is constructed of. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + If true the normals of the torus are set to give a smooth effect making the torus seem rounded. When false the torus will have a flat shaded look. </member> </members> <constants> diff --git a/modules/cvtt/SCsub b/modules/cvtt/SCsub new file mode 100644 index 0000000000..fcc69d8371 --- /dev/null +++ b/modules/cvtt/SCsub @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_cvtt = env_modules.Clone() + +# Thirdparty source files +if env['builtin_squish']: + thirdparty_dir = "#thirdparty/cvtt/" + thirdparty_sources = [ + "ConvectionKernels.cpp" + ] + + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_cvtt.Append(CPPPATH=[thirdparty_dir]) + + env_thirdparty = env_cvtt.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + +# Godot source files +env_cvtt.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/cvtt/config.py b/modules/cvtt/config.py new file mode 100644 index 0000000000..098f1eafa9 --- /dev/null +++ b/modules/cvtt/config.py @@ -0,0 +1,5 @@ +def can_build(env, platform): + return env['tools'] + +def configure(env): + pass diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp new file mode 100644 index 0000000000..732b9cf733 --- /dev/null +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -0,0 +1,391 @@ +/*************************************************************************/ +/* image_compress_cvtt.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 "image_compress_cvtt.h" + +#include "core/os/os.h" +#include "core/os/thread.h" +#include "core/print_string.h" + +#include <ConvectionKernels.h> + +struct CVTTCompressionJobParams { + bool is_hdr; + bool is_signed; + int bytes_per_pixel; + + cvtt::Options options; +}; + +struct CVTTCompressionRowTask { + const uint8_t *in_mm_bytes; + uint8_t *out_mm_bytes; + int y_start; + int width; + int height; +}; + +struct CVTTCompressionJobQueue { + CVTTCompressionJobParams job_params; + const CVTTCompressionRowTask *job_tasks; + uint32_t num_tasks; + uint32_t current_task; +}; + +static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const CVTTCompressionRowTask &p_row_task) { + const uint8_t *in_bytes = p_row_task.in_mm_bytes; + uint8_t *out_bytes = p_row_task.out_mm_bytes; + int w = p_row_task.width; + int h = p_row_task.height; + + int y_start = p_row_task.y_start; + int y_end = y_start + 4; + + int bytes_per_pixel = p_job_params.bytes_per_pixel; + bool is_hdr = p_job_params.is_hdr; + bool is_signed = p_job_params.is_signed; + + cvtt::PixelBlockU8 input_blocks_ldr[cvtt::NumParallelBlocks]; + cvtt::PixelBlockF16 input_blocks_hdr[cvtt::NumParallelBlocks]; + + for (int x_start = 0; x_start < w; x_start += 4 * cvtt::NumParallelBlocks) { + int x_end = x_start + 4 * cvtt::NumParallelBlocks; + + for (int y = y_start; y < y_end; y++) { + int first_input_element = (y - y_start) * 4; + const uint8_t *row_start; + if (y >= h) { + row_start = in_bytes + (h - 1) * (w * bytes_per_pixel); + } else { + row_start = in_bytes + y * (w * bytes_per_pixel); + } + + for (int x = x_start; x < x_end; x++) { + const uint8_t *pixel_start; + if (x >= w) { + pixel_start = row_start + (w - 1) * bytes_per_pixel; + } else { + pixel_start = row_start + x * bytes_per_pixel; + } + + int block_index = (x - x_start) / 4; + int block_element = (x - x_start) % 4 + first_input_element; + if (is_hdr) { + memcpy(input_blocks_hdr[block_index].m_pixels[block_element], pixel_start, bytes_per_pixel); + input_blocks_hdr[block_index].m_pixels[block_element][3] = 0x3c00; // 1.0 (unused) + } else { + memcpy(input_blocks_ldr[block_index].m_pixels[block_element], pixel_start, bytes_per_pixel); + } + } + } + + uint8_t output_blocks[16 * cvtt::NumParallelBlocks]; + + if (is_hdr) { + if (is_signed) { + cvtt::Kernels::EncodeBC6HS(output_blocks, input_blocks_hdr, p_job_params.options); + } else { + cvtt::Kernels::EncodeBC6HU(output_blocks, input_blocks_hdr, p_job_params.options); + } + } else { + cvtt::Kernels::EncodeBC7(output_blocks, input_blocks_ldr, p_job_params.options); + } + + unsigned int num_real_blocks = ((w - x_start) + 3) / 4; + if (num_real_blocks > cvtt::NumParallelBlocks) { + num_real_blocks = cvtt::NumParallelBlocks; + } + + memcpy(out_bytes, output_blocks, 16 * num_real_blocks); + out_bytes += 16 * num_real_blocks; + } +} + +static void _digest_job_queue(void *p_job_queue) { + CVTTCompressionJobQueue *job_queue = static_cast<CVTTCompressionJobQueue *>(p_job_queue); + + for (uint32_t next_task = atomic_increment(&job_queue->current_task); next_task <= job_queue->num_tasks; next_task = atomic_increment(&job_queue->current_task)) { + _digest_row_task(job_queue->job_params, job_queue->job_tasks[next_task - 1]); + } +} + +void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::CompressSource p_source) { + + if (p_image->get_format() >= Image::FORMAT_BPTC_RGBA) + return; //do not compress, already compressed + + int w = p_image->get_width(); + int h = p_image->get_height(); + + bool is_ldr = (p_image->get_format() <= Image::FORMAT_RGBA8); + bool is_hdr = (p_image->get_format() == Image::FORMAT_RGBH); + + if (!is_ldr && !is_hdr) { + return; // Not a usable source format + } + + cvtt::Options options; + uint32_t flags = cvtt::Flags::Fastest; + + if (p_lossy_quality > 0.85) + flags = cvtt::Flags::Ultra; + else if (p_lossy_quality > 0.75) + flags = cvtt::Flags::Better; + else if (p_lossy_quality > 0.55) + flags = cvtt::Flags::Default; + else if (p_lossy_quality > 0.35) + flags = cvtt::Flags::Fast; + else if (p_lossy_quality > 0.15) + flags = cvtt::Flags::Faster; + + flags |= cvtt::Flags::BC7_RespectPunchThrough; + + if (p_source == Image::COMPRESS_SOURCE_NORMAL) { + flags |= cvtt::Flags::Uniform; + } + + Image::Format target_format = Image::FORMAT_BPTC_RGBA; + + bool is_signed = false; + if (is_hdr) { + PoolVector<uint8_t>::Read rb = p_image->get_data().read(); + + const uint16_t *source_data = reinterpret_cast<const uint16_t *>(&rb[0]); + int pixel_element_count = w * h * 3; + for (int i = 0; i < pixel_element_count; i++) { + if ((source_data[i] & 0x8000) != 0 && (source_data[i] & 0x7fff) != 0) { + is_signed = true; + break; + } + } + + target_format = is_signed ? Image::FORMAT_BPTC_RGBF : Image::FORMAT_BPTC_RGBFU; + } else { + p_image->convert(Image::FORMAT_RGBA8); //still uses RGBA to convert + } + + PoolVector<uint8_t>::Read rb = p_image->get_data().read(); + + PoolVector<uint8_t> data; + int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); + int mm_count = p_image->has_mipmaps() ? Image::get_image_required_mipmaps(w, h, target_format) : 0; + data.resize(target_size); + int shift = Image::get_format_pixel_rshift(target_format); + + PoolVector<uint8_t>::Write wb = data.write(); + + int dst_ofs = 0; + + CVTTCompressionJobQueue job_queue; + job_queue.job_params.is_hdr = is_hdr; + job_queue.job_params.is_signed = is_signed; + job_queue.job_params.options = options; + job_queue.job_params.bytes_per_pixel = is_hdr ? 6 : 4; + +#ifdef NO_THREADS + int num_job_threads = 0; +#else + int num_job_threads = OS::get_singleton()->can_use_threads() ? (OS::get_singleton()->get_processor_count() - 1) : 0; +#endif + + PoolVector<CVTTCompressionRowTask> tasks; + + for (int i = 0; i <= mm_count; i++) { + + int bw = w % 4 != 0 ? w + (4 - w % 4) : w; + int bh = h % 4 != 0 ? h + (4 - h % 4) : h; + + int src_ofs = p_image->get_mipmap_offset(i); + + const uint8_t *in_bytes = &rb[src_ofs]; + uint8_t *out_bytes = &wb[dst_ofs]; + + for (int y_start = 0; y_start < h; y_start += 4) { + CVTTCompressionRowTask row_task; + row_task.width = w; + row_task.height = h; + row_task.y_start = y_start; + row_task.in_mm_bytes = in_bytes; + row_task.out_mm_bytes = out_bytes; + + if (num_job_threads > 0) { + tasks.push_back(row_task); + } else { + _digest_row_task(job_queue.job_params, row_task); + } + + out_bytes += 16 * (bw / 4); + } + + dst_ofs += (MAX(4, bw) * MAX(4, bh)) >> shift; + w = MAX(w / 2, 1); + h = MAX(h / 2, 1); + } + + if (num_job_threads > 0) { + PoolVector<Thread *> threads; + threads.resize(num_job_threads); + + PoolVector<Thread *>::Write threads_wb = threads.write(); + + PoolVector<CVTTCompressionRowTask>::Read tasks_rb = tasks.read(); + + job_queue.job_tasks = &tasks_rb[0]; + job_queue.current_task = 0; + job_queue.num_tasks = static_cast<uint32_t>(tasks.size()); + + for (int i = 0; i < num_job_threads; i++) { + threads_wb[i] = Thread::create(_digest_job_queue, &job_queue); + } + _digest_job_queue(&job_queue); + + for (int i = 0; i < num_job_threads; i++) { + Thread::wait_to_finish(threads_wb[i]); + memdelete(threads_wb[i]); + } + } + + p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); +} + +void image_decompress_cvtt(Image *p_image) { + + Image::Format target_format; + bool is_signed = false; + bool is_hdr = false; + + Image::Format input_format = p_image->get_format(); + + switch (input_format) { + case Image::FORMAT_BPTC_RGBA: + target_format = Image::FORMAT_RGBA8; + break; + case Image::FORMAT_BPTC_RGBF: + case Image::FORMAT_BPTC_RGBFU: + target_format = Image::FORMAT_RGBH; + is_signed = (input_format == Image::FORMAT_BPTC_RGBF); + is_hdr = true; + break; + default: + return; // Invalid input format + }; + + int w = p_image->get_width(); + int h = p_image->get_height(); + + PoolVector<uint8_t>::Read rb = p_image->get_data().read(); + + PoolVector<uint8_t> data; + int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); + int mm_count = p_image->get_mipmap_count(); + data.resize(target_size); + + PoolVector<uint8_t>::Write wb = data.write(); + + int bytes_per_pixel = is_hdr ? 6 : 4; + + int dst_ofs = 0; + + for (int i = 0; i <= mm_count; i++) { + + int src_ofs = p_image->get_mipmap_offset(i); + + const uint8_t *in_bytes = &rb[src_ofs]; + uint8_t *out_bytes = &wb[dst_ofs]; + + cvtt::PixelBlockU8 output_blocks_ldr[cvtt::NumParallelBlocks]; + cvtt::PixelBlockF16 output_blocks_hdr[cvtt::NumParallelBlocks]; + + for (int y_start = 0; y_start < h; y_start += 4) { + int y_end = y_start + 4; + + for (int x_start = 0; x_start < w; x_start += 4 * cvtt::NumParallelBlocks) { + int x_end = x_start + 4 * cvtt::NumParallelBlocks; + + uint8_t input_blocks[16 * cvtt::NumParallelBlocks]; + memset(input_blocks, 0, sizeof(input_blocks)); + + unsigned int num_real_blocks = ((w - x_start) + 3) / 4; + if (num_real_blocks > cvtt::NumParallelBlocks) { + num_real_blocks = cvtt::NumParallelBlocks; + } + + memcpy(input_blocks, in_bytes, 16 * num_real_blocks); + in_bytes += 16 * num_real_blocks; + + if (is_hdr) { + if (is_signed) { + cvtt::Kernels::DecodeBC6HS(output_blocks_hdr, input_blocks); + } else { + cvtt::Kernels::DecodeBC6HU(output_blocks_hdr, input_blocks); + } + } else { + cvtt::Kernels::DecodeBC7(output_blocks_ldr, input_blocks); + } + + for (int y = y_start; y < y_end; y++) { + int first_input_element = (y - y_start) * 4; + uint8_t *row_start; + if (y >= h) { + row_start = out_bytes + (h - 1) * (w * bytes_per_pixel); + } else { + row_start = out_bytes + y * (w * bytes_per_pixel); + } + + for (int x = x_start; x < x_end; x++) { + uint8_t *pixel_start; + if (x >= w) { + pixel_start = row_start + (w - 1) * bytes_per_pixel; + } else { + pixel_start = row_start + x * bytes_per_pixel; + } + + int block_index = (x - x_start) / 4; + int block_element = (x - x_start) % 4 + first_input_element; + if (is_hdr) { + memcpy(pixel_start, output_blocks_hdr[block_index].m_pixels[block_element], bytes_per_pixel); + } else { + memcpy(pixel_start, output_blocks_ldr[block_index].m_pixels[block_element], bytes_per_pixel); + } + } + } + } + } + + dst_ofs += w * h * bytes_per_pixel; + w >>= 1; + h >>= 1; + } + + rb = PoolVector<uint8_t>::Read(); + wb = PoolVector<uint8_t>::Write(); + + p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); +} diff --git a/modules/cvtt/image_compress_cvtt.h b/modules/cvtt/image_compress_cvtt.h new file mode 100644 index 0000000000..fe912ad3bb --- /dev/null +++ b/modules/cvtt/image_compress_cvtt.h @@ -0,0 +1,39 @@ +/*************************************************************************/ +/* image_compress_cvtt.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef IMAGE_COMPRESS_CVTT_H +#define IMAGE_COMPRESS_CVTT_H + +#include "core/image.h" + +void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::CompressSource p_source); +void image_decompress_cvtt(Image *p_image); + +#endif // IMAGE_COMPRESS_CVTT_H diff --git a/modules/cvtt/register_types.cpp b/modules/cvtt/register_types.cpp new file mode 100644 index 0000000000..c96fbbf340 --- /dev/null +++ b/modules/cvtt/register_types.cpp @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* register_types.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 "register_types.h" + +#ifdef TOOLS_ENABLED + +#include "image_compress_cvtt.h" + +void register_cvtt_types() { + + Image::set_compress_bptc_func(image_compress_cvtt); + Image::_image_decompress_bptc = image_decompress_cvtt; +} + +void unregister_cvtt_types() {} + +#endif diff --git a/modules/cvtt/register_types.h b/modules/cvtt/register_types.h new file mode 100644 index 0000000000..73de9728d3 --- /dev/null +++ b/modules/cvtt/register_types.h @@ -0,0 +1,34 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifdef TOOLS_ENABLED +void register_cvtt_types(); +void unregister_cvtt_types(); +#endif diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp index 9424080b6d..53e9773791 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -29,7 +29,7 @@ /*************************************************************************/ #include "texture_loader_dds.h" -#include "os/file_access.h" +#include "core/os/file_access.h" enum { DDS_MAGIC = 0x20534444, @@ -108,8 +108,8 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, uint32_t magic = f->get_32(); uint32_t hsize = f->get_32(); uint32_t flags = f->get_32(); - uint32_t width = f->get_32(); uint32_t height = f->get_32(); + uint32_t width = f->get_32(); uint32_t pitch = f->get_32(); /* uint32_t depth = */ f->get_32(); uint32_t mipmaps = f->get_32(); @@ -217,8 +217,6 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, if (!(flags & DDSD_MIPMAPCOUNT)) mipmaps = 1; - //print_line("found format: "+String(dds_format_info[dds_format].name)); - PoolVector<uint8_t> src_data; const DDSFormatInfo &info = dds_format_info[dds_format]; diff --git a/modules/dds/texture_loader_dds.h b/modules/dds/texture_loader_dds.h index 14d99ff506..4e2593c744 100644 --- a/modules/dds/texture_loader_dds.h +++ b/modules/dds/texture_loader_dds.h @@ -31,7 +31,7 @@ #ifndef TEXTURE_LOADER_DDS_H #define TEXTURE_LOADER_DDS_H -#include "io/resource_loader.h" +#include "core/io/resource_loader.h" #include "scene/resources/texture.h" class ResourceFormatDDS : public ResourceFormatLoader { diff --git a/modules/enet/SCsub b/modules/enet/SCsub index 7caeafa1d6..a57a4b29ea 100644 --- a/modules/enet/SCsub +++ b/modules/enet/SCsub @@ -21,8 +21,11 @@ if env['builtin_enet']: ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_enet.add_source_files(env.modules_sources, thirdparty_sources) env_enet.Append(CPPPATH=[thirdparty_dir]) env_enet.Append(CPPFLAGS=["-DGODOT_ENET"]) + env_thirdparty = env_enet.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_enet.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml index fab4b05da9..e057a435ac 100644 --- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml +++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml @@ -7,7 +7,7 @@ A PacketPeer implementation that should be passed to [method SceneTree.set_network_peer] after being initialized as either a client or server. Events can then be handled by connecting to [SceneTree] signals. </description> <tutorials> - <link>http://docs.godotengine.org/en/3.0/tutorials/networking/high_level_multiplayer.html</link> + <link>https://docs.godotengine.org/en/latest/tutorials/networking/high_level_multiplayer.html</link> <link>http://enet.bespin.org/usergroup0.html</link> </tutorials> <demos> @@ -36,7 +36,7 @@ <argument index="4" name="client_port" type="int" default="0"> </argument> <description> - Create client that connects to a server at [code]address[/code] using specified [code]port[/code]. The given address needs to be either a fully qualified domain nome (e.g. [code]www.example.com[/code]) or an IP address in IPv4 or IPv6 format (e.g. [code]192.168.1.1[/code]). The [code]port[/code] is the port the server is listening on. The [code]in_bandwidth[/code] and [code]out_bandwidth[/code] parameters can be used to limit the incoming and outgoing bandwidth to the given number of bytes per second. The default of 0 means unlimited bandwidth. Note that ENet will strategically drop packets on specific sides of a connection between peers to ensure the peer's bandwidth is not overwhelmed. The bandwidth parameters also determine the window size of a connection which limits the amount of reliable packets that may be in transit at any given time. Returns [code]OK[/code] if a client was created, [code]ERR_ALREADY_IN_USE[/code] if this NetworkedMultiplayerEnet instance already has an open connection (in which case you need to call [method close_connection] first) or [code]ERR_CANT_CREATE[/code] if the client could not be created. If [code]client_port[/code] is specified, the client will also listen to the given port, this is useful in some NAT traveral technique. + Create client that connects to a server at [code]address[/code] using specified [code]port[/code]. The given address needs to be either a fully qualified domain nome (e.g. [code]www.example.com[/code]) or an IP address in IPv4 or IPv6 format (e.g. [code]192.168.1.1[/code]). The [code]port[/code] is the port the server is listening on. The [code]in_bandwidth[/code] and [code]out_bandwidth[/code] parameters can be used to limit the incoming and outgoing bandwidth to the given number of bytes per second. The default of 0 means unlimited bandwidth. Note that ENet will strategically drop packets on specific sides of a connection between peers to ensure the peer's bandwidth is not overwhelmed. The bandwidth parameters also determine the window size of a connection which limits the amount of reliable packets that may be in transit at any given time. Returns [code]OK[/code] if a client was created, [code]ERR_ALREADY_IN_USE[/code] if this NetworkedMultiplayerEnet instance already has an open connection (in which case you need to call [method close_connection] first) or [code]ERR_CANT_CREATE[/code] if the client could not be created. If [code]client_port[/code] is specified, the client will also listen to the given port, this is useful in some NAT traversal technique. </description> </method> <method name="create_server"> @@ -76,7 +76,7 @@ <return type="int"> </return> <description> - Returns the channel of the next packet that will be retrieved via [method PacketPeer.get_packet_peer] + Returns the channel of the next packet that will be retrieved via [method PacketPeer.get_packet] </description> </method> <method name="get_peer_address" qualifiers="const"> diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp index 88768829d7..7b5fd854ff 100644 --- a/modules/enet/networked_multiplayer_enet.cpp +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -29,9 +29,9 @@ /*************************************************************************/ #include "networked_multiplayer_enet.h" -#include "io/ip.h" -#include "io/marshalls.h" -#include "os/os.h" +#include "core/io/ip.h" +#include "core/io/marshalls.h" +#include "core/os/os.h" void NetworkedMultiplayerENet::set_transfer_mode(TransferMode p_mode) { @@ -100,8 +100,8 @@ Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int host = enet_host_create(&address /* the address to bind the server host to */, p_max_clients /* allow up to 32 clients and/or outgoing connections */, channel_count /* allow up to channel_count to be used */, - p_in_bandwidth /* limit incoming bandwith if > 0 */, - p_out_bandwidth /* limit outgoing bandwith if > 0 */); + p_in_bandwidth /* limit incoming bandwidth if > 0 */, + p_out_bandwidth /* limit outgoing bandwidth if > 0 */); ERR_FAIL_COND_V(!host, ERR_CANT_CREATE); @@ -144,14 +144,14 @@ Error NetworkedMultiplayerENet::create_client(const String &p_address, int p_por host = enet_host_create(&c_client /* create a client host */, 1 /* only allow 1 outgoing connection */, channel_count /* allow up to channel_count to be used */, - p_in_bandwidth /* limit incoming bandwith if > 0 */, - p_out_bandwidth /* limit outgoing bandwith if > 0 */); + p_in_bandwidth /* limit incoming bandwidth if > 0 */, + p_out_bandwidth /* limit outgoing bandwidth if > 0 */); } else { host = enet_host_create(NULL /* create a client host */, 1 /* only allow 1 outgoing connection */, channel_count /* allow up to channel_count to be used */, - p_in_bandwidth /* limit incoming bandwith if > 0 */, - p_out_bandwidth /* limit outgoing bandwith if > 0 */); + p_in_bandwidth /* limit incoming bandwidth if > 0 */, + p_out_bandwidth /* limit outgoing bandwidth if > 0 */); } ERR_FAIL_COND_V(!host, ERR_CANT_CREATE); @@ -207,13 +207,13 @@ void NetworkedMultiplayerENet::poll() { _pop_current_packet(); ENetEvent event; - /* Wait up to 1000 milliseconds for an event. */ + /* Keep servicing until there are no available events left in queue. */ while (true) { if (!host || !active) // Might have been disconnected while emitting a notification return; - int ret = enet_host_service(host, &event, 1); + int ret = enet_host_service(host, &event, 0); if (ret < 0) { // Error, do something? @@ -293,7 +293,7 @@ void NetworkedMultiplayerENet::poll() { encode_uint32(*id, &packet->data[4]); enet_peer_send(E->get(), SYSCH_CONFIG, packet); } - } else if (!server) { + } else { emit_signal("server_disconnected"); close_connection(); return; @@ -666,7 +666,7 @@ size_t NetworkedMultiplayerENet::enet_compress(void *context, const ENetBuffer * while (total) { for (size_t i = 0; i < inBufferCount; i++) { int to_copy = MIN(total, int(inBuffers[i].dataLength)); - copymem(&enet->src_compressor_mem[ofs], inBuffers[i].data, to_copy); + copymem(&enet->src_compressor_mem.write[ofs], inBuffers[i].data, to_copy); ofs += to_copy; total -= to_copy; } diff --git a/modules/enet/networked_multiplayer_enet.h b/modules/enet/networked_multiplayer_enet.h index 705807d429..a2b35f2395 100644 --- a/modules/enet/networked_multiplayer_enet.h +++ b/modules/enet/networked_multiplayer_enet.h @@ -31,8 +31,8 @@ #ifndef NETWORKED_MULTIPLAYER_ENET_H #define NETWORKED_MULTIPLAYER_ENET_H -#include "io/compression.h" -#include "io/networked_multiplayer_peer.h" +#include "core/io/compression.h" +#include "core/io/networked_multiplayer_peer.h" #include <enet/enet.h> diff --git a/modules/enet/register_types.cpp b/modules/enet/register_types.cpp index cabaeb692a..cde70e8d5c 100644 --- a/modules/enet/register_types.cpp +++ b/modules/enet/register_types.cpp @@ -29,7 +29,7 @@ /*************************************************************************/ #include "register_types.h" -#include "error_macros.h" +#include "core/error_macros.h" #include "networked_multiplayer_enet.h" static bool enet_ok = false; diff --git a/modules/etc/SCsub b/modules/etc/SCsub index 31d8f00ef3..d2c77d6e3c 100644 --- a/modules/etc/SCsub +++ b/modules/etc/SCsub @@ -27,16 +27,20 @@ thirdparty_sources = [ ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env_etc.add_source_files(env.modules_sources, thirdparty_sources) env_etc.Append(CPPPATH=[thirdparty_dir]) -# Godot source files -env_etc.add_source_files(env.modules_sources, "*.cpp") - # upstream uses c++11 -if (not env_etc.msvc): +if not env.msvc: env_etc.Append(CCFLAGS="-std=c++11") -# -ffast-math seems to be incompatible with ec2comp on recent versions of + +# -ffast-math seems to be incompatible with etc2comp on recent versions of # GCC and Clang if '-ffast-math' in env_etc['CCFLAGS']: env_etc['CCFLAGS'].remove('-ffast-math') + +env_thirdparty = env_etc.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + +# Godot source files +env_etc.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/etc/image_etc.cpp b/modules/etc/image_etc.cpp index 8a674bc8c1..fbbc765bf2 100644 --- a/modules/etc/image_etc.cpp +++ b/modules/etc/image_etc.cpp @@ -31,10 +31,10 @@ #include "image_etc.h" #include "Etc.h" #include "EtcFilter.h" -#include "image.h" -#include "os/copymem.h" -#include "os/os.h" -#include "print_string.h" +#include "core/image.h" +#include "core/os/copymem.h" +#include "core/os/os.h" +#include "core/print_string.h" static Image::Format _get_etc2_mode(Image::DetectChannels format) { switch (format) { @@ -47,13 +47,14 @@ static Image::Format _get_etc2_mode(Image::DetectChannels format) { case Image::DETECTED_RGB: return Image::FORMAT_ETC2_RGB8; - default: + case Image::DETECTED_RGBA: return Image::FORMAT_ETC2_RGBA8; - // TODO: would be nice if we could use FORMAT_ETC2_RGB8A1 for FORMAT_RGBA5551 + // TODO: would be nice if we could use FORMAT_ETC2_RGB8A1 for FORMAT_RGBA5551 + default: + // TODO: Kept for compatibility, but should be investigated whether it's correct or if it should error out + return Image::FORMAT_ETC2_RGBA8; } - - ERR_FAIL_COND_V(true, Image::FORMAT_MAX); } static Etc::Image::Format _image_format_to_etc2comp_format(Image::Format format) { @@ -81,23 +82,43 @@ static Etc::Image::Format _image_format_to_etc2comp_format(Image::Format format) case Image::FORMAT_ETC2_RGB8A1: return Etc::Image::Format::RGB8A1; - } - - ERR_FAIL_COND_V(true, Etc::Image::Format::UNKNOWN); -} -static void _decompress_etc1(Image *p_img) { - // not implemented, to be removed -} - -static void _decompress_etc2(Image *p_img) { - // not implemented, to be removed + default: + ERR_FAIL_V(Etc::Image::Format::UNKNOWN); + } } static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_format, Image::CompressSource p_source) { Image::Format img_format = p_img->get_format(); Image::DetectChannels detected_channels = p_img->get_detected_channels(); + if (p_source == Image::COMPRESS_SOURCE_LAYERED) { + //keep what comes in + switch (p_img->get_format()) { + case Image::FORMAT_L8: { + detected_channels = Image::DETECTED_L; + } break; + case Image::FORMAT_LA8: { + detected_channels = Image::DETECTED_LA; + } break; + case Image::FORMAT_R8: { + detected_channels = Image::DETECTED_R; + } break; + case Image::FORMAT_RG8: { + detected_channels = Image::DETECTED_RG; + } break; + case Image::FORMAT_RGB8: { + detected_channels = Image::DETECTED_RGB; + } break; + case Image::FORMAT_RGBA8: + case Image::FORMAT_RGBA4444: + case Image::FORMAT_RGBA5551: { + detected_channels = Image::DETECTED_RGBA; + } break; + default: {} + } + } + if (p_source == Image::COMPRESS_SOURCE_SRGB && (detected_channels == Image::DETECTED_R || detected_channels == Image::DETECTED_RG)) { //R and RG do not support SRGB detected_channels = Image::DETECTED_RGB; @@ -147,7 +168,7 @@ static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_f PoolVector<uint8_t>::Read r = img->get_data().read(); - int target_size = Image::get_image_data_size(imgw, imgh, etc_format, p_img->has_mipmaps() ? -1 : 0); + unsigned int target_size = Image::get_image_data_size(imgw, imgh, etc_format, p_img->has_mipmaps()); int mmc = 1 + (p_img->has_mipmaps() ? Image::get_image_required_mipmaps(imgw, imgh, etc_format) : 0); PoolVector<uint8_t> dst_data; @@ -172,7 +193,7 @@ static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_f int wofs = 0; - print_line("begin encoding, format: " + Image::get_format_name(etc_format)); + print_verbose("ETC: Begin encoding, format: " + Image::get_format_name(etc_format)); uint64_t t = OS::get_singleton()->get_ticks_msec(); for (int i = 0; i < mmc; i++) { // convert source image to internal etc2comp format (which is equivalent to Image::FORMAT_RGBAF) @@ -200,7 +221,7 @@ static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_f delete[] src_rgba_f; } - print_line("time encoding: " + rtos(OS::get_singleton()->get_ticks_msec() - t)); + print_verbose("ETC: Time encoding: " + rtos(OS::get_singleton()->get_ticks_msec() - t)); p_img->create(imgw, imgh, p_img->has_mipmaps(), etc_format, dst_data); } @@ -216,8 +237,5 @@ static void _compress_etc2(Image *p_img, float p_lossy_quality, Image::CompressS void _register_etc_compress_func() { Image::_image_compress_etc1_func = _compress_etc1; - //Image::_image_decompress_etc1 = _decompress_etc1; - Image::_image_compress_etc2_func = _compress_etc2; - //Image::_image_decompress_etc2 = _decompress_etc2; } diff --git a/modules/etc/texture_loader_pkm.cpp b/modules/etc/texture_loader_pkm.cpp index ac89259c9b..3041dde876 100644 --- a/modules/etc/texture_loader_pkm.cpp +++ b/modules/etc/texture_loader_pkm.cpp @@ -30,7 +30,7 @@ #include "texture_loader_pkm.h" -#include "os/file_access.h" +#include "core/os/file_access.h" #include <string.h> struct ETC1Header { diff --git a/modules/etc/texture_loader_pkm.h b/modules/etc/texture_loader_pkm.h index 3c6d9180bd..b5a95767c7 100644 --- a/modules/etc/texture_loader_pkm.h +++ b/modules/etc/texture_loader_pkm.h @@ -31,7 +31,7 @@ #ifndef TEXTURE_LOADER_PKM_H #define TEXTURE_LOADER_PKM_H -#include "io/resource_loader.h" +#include "core/io/resource_loader.h" #include "scene/resources/texture.h" class ResourceFormatPKM : public ResourceFormatLoader { diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub index 301f218361..3e2068b8db 100644 --- a/modules/freetype/SCsub +++ b/modules/freetype/SCsub @@ -1,9 +1,11 @@ #!/usr/bin/env python Import('env') +Import('env_modules') + from compat import isbasestring -# Not building in a separate env as scene needs it +env_freetype = env_modules.Clone() # Thirdparty source files if env['builtin_freetype']: @@ -54,26 +56,35 @@ if env['builtin_freetype']: ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - sfnt = thirdparty_dir + 'src/sfnt/sfnt.c' - - if 'platform' in env: - if env['platform'] == 'uwp': - # Include header for UWP to fix build issues - env.Append(CCFLAGS=['/FI', '"modules/freetype/uwpdef.h"']) - elif env['platform'] == 'javascript': - # Forcibly undefine this macro so SIMD is not used in this file, - # since currently unsuported in WASM - sfnt = env.Object(sfnt, CPPFLAGS=['-U__OPTIMIZE__']) + if env['platform'] == 'uwp': + # Include header for UWP to fix build issues + env_freetype.Append(CCFLAGS=['/FI', '"modules/freetype/uwpdef.h"']) + # Globally too, as freetype is used in scene (see bottom) + env.Append(CCFLAGS=['/FI', '"modules/freetype/uwpdef.h"']) + sfnt = thirdparty_dir + 'src/sfnt/sfnt.c' + if env['platform'] == 'javascript': + # Forcibly undefine this macro so SIMD is not used in this file, + # since currently unsupported in WASM + sfnt = env_freetype.Object(sfnt, CPPFLAGS=['-U__OPTIMIZE__']) thirdparty_sources += [sfnt] - env.Append(CPPPATH=[thirdparty_dir, thirdparty_dir + "/include"]) + env_freetype.Append(CPPPATH=[thirdparty_dir + "/include"]) + # Also needed in main env for scene/ + env.Append(CPPPATH=[thirdparty_dir + "/include"]) - # also requires libpng headers + env_freetype.Append(CCFLAGS=['-DFT2_BUILD_LIBRARY', '-DFT_CONFIG_OPTION_USE_PNG']) + if (env['target'] != 'release'): + env_freetype.Append(CCFLAGS=['-DZLIB_DEBUG']) + + # Also requires libpng headers if env['builtin_libpng']: - env.Append(CPPPATH=["#thirdparty/libpng"]) + env_freetype.Append(CPPPATH=["#thirdparty/libpng"]) + + env_thirdparty = env_freetype.Clone() + env_thirdparty.disable_warnings() + lib = env_thirdparty.add_library("freetype_builtin", thirdparty_sources) - lib = env.add_library("freetype_builtin", thirdparty_sources) # Needs to be appended to arrive after libscene in the linker call, # but we don't want it to arrive *after* system libs, so manual hack # LIBS contains first SCons Library objects ("SCons.Node.FS.File object") @@ -86,12 +97,8 @@ if env['builtin_freetype']: break if not inserted: env.Append(LIBS=[lib]) - env.Append(CCFLAGS=['-DFT2_BUILD_LIBRARY']) - if (env['target'] != 'release'): - env.Append(CCFLAGS=['-DZLIB_DEBUG']) # Godot source files -env.add_source_files(env.modules_sources, "*.cpp") -env.Append(CCFLAGS=['-DFREETYPE_ENABLED', '-DFT_CONFIG_OPTION_USE_PNG']) - -Export('env') +env_freetype.add_source_files(env.modules_sources, "*.cpp") +# Used in scene/, needs to be in main env +env.Append(CCFLAGS=['-DFREETYPE_ENABLED']) diff --git a/modules/gdnative/SCsub b/modules/gdnative/SCsub index 116a86b27b..fe2d8c7ce9 100644 --- a/modules/gdnative/SCsub +++ b/modules/gdnative/SCsub @@ -1,284 +1,38 @@ #!/usr/bin/env python Import('env') +Import('env_modules') -gdn_env = env.Clone() -gdn_env.add_source_files(env.modules_sources, "gdnative.cpp") -gdn_env.add_source_files(env.modules_sources, "register_types.cpp") -gdn_env.add_source_files(env.modules_sources, "android/*.cpp") -gdn_env.add_source_files(env.modules_sources, "gdnative/*.cpp") -gdn_env.add_source_files(env.modules_sources, "nativescript/*.cpp") -gdn_env.add_source_files(env.modules_sources, "gdnative_library_singleton_editor.cpp") -gdn_env.add_source_files(env.modules_sources, "gdnative_library_editor_plugin.cpp") +env_gdnative = env_modules.Clone() +env_gdnative.add_source_files(env.modules_sources, "gdnative.cpp") +env_gdnative.add_source_files(env.modules_sources, "register_types.cpp") +env_gdnative.add_source_files(env.modules_sources, "android/*.cpp") +env_gdnative.add_source_files(env.modules_sources, "gdnative/*.cpp") +env_gdnative.add_source_files(env.modules_sources, "nativescript/*.cpp") +env_gdnative.add_source_files(env.modules_sources, "gdnative_library_singleton_editor.cpp") +env_gdnative.add_source_files(env.modules_sources, "gdnative_library_editor_plugin.cpp") -gdn_env.Append(CPPPATH=['#modules/gdnative/include/']) +env_gdnative.Append(CPPPATH=['#modules/gdnative/include/']) + +Export('env_gdnative') SConscript("net/SCsub") SConscript("arvr/SCsub") SConscript("pluginscript/SCsub") -def _spaced(e): - return e if e[-1] == '*' else e + ' ' - -def _build_gdnative_api_struct_header(api): - gdnative_api_init_macro = [ - '\textern const godot_gdnative_core_api_struct *_gdnative_wrapper_api_struct;' - ] - - for ext in api['extensions']: - name = ext['name'] - gdnative_api_init_macro.append( - '\textern const godot_gdnative_ext_{0}_api_struct *_gdnative_wrapper_{0}_api_struct;'.format(name)) - - gdnative_api_init_macro.append('\t_gdnative_wrapper_api_struct = options->api_struct;') - gdnative_api_init_macro.append('\tfor (unsigned int i = 0; i < _gdnative_wrapper_api_struct->num_extensions; i++) { ') - gdnative_api_init_macro.append('\t\tswitch (_gdnative_wrapper_api_struct->extensions[i]->type) {') - - for ext in api['extensions']: - name = ext['name'] - gdnative_api_init_macro.append( - '\t\t\tcase GDNATIVE_EXT_%s:' % ext['type']) - gdnative_api_init_macro.append( - '\t\t\t\t_gdnative_wrapper_{0}_api_struct = (godot_gdnative_ext_{0}_api_struct *)' - ' _gdnative_wrapper_api_struct->extensions[i];'.format(name)) - gdnative_api_init_macro.append('\t\t\t\tbreak;') - gdnative_api_init_macro.append('\t\t}') - gdnative_api_init_macro.append('\t}') - - out = [ - '/* THIS FILE IS GENERATED DO NOT EDIT */', - '#ifndef GODOT_GDNATIVE_API_STRUCT_H', - '#define GODOT_GDNATIVE_API_STRUCT_H', - '', - '#include <gdnative/gdnative.h>', - '#include <android/godot_android.h>', - '#include <arvr/godot_arvr.h>', - '#include <nativescript/godot_nativescript.h>', - '#include <pluginscript/godot_pluginscript.h>', - '', - '#define GDNATIVE_API_INIT(options) do { \\\n' + ' \\\n'.join(gdnative_api_init_macro) + ' \\\n } while (0)', - '', - '#ifdef __cplusplus', - 'extern "C" {', - '#endif', - '', - 'enum GDNATIVE_API_TYPES {', - '\tGDNATIVE_' + api['core']['type'] + ',' - ] - - for ext in api['extensions']: - out += ['\tGDNATIVE_EXT_' + ext['type'] + ','] - - out += ['};', ''] - - - def generate_extension_struct(name, ext, include_version=True): - ret_val = [] - if ext['next']: - ret_val += generate_extension_struct(name, ext['next']) - - ret_val += [ - 'typedef struct godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct {', - '\tunsigned int type;', - '\tgodot_gdnative_api_version version;', - '\tconst godot_gdnative_api_struct *next;' - ] - - for funcdef in ext['api']: - args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) - ret_val.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args)) - - ret_val += ['} godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct;', ''] - - return ret_val - - - for ext in api['extensions']: - name = ext['name'] - out += generate_extension_struct(name, ext, False) - - out += [ - 'typedef struct godot_gdnative_core_api_struct {', - '\tunsigned int type;', - '\tgodot_gdnative_api_version version;', - '\tconst godot_gdnative_api_struct *next;', - '\tunsigned int num_extensions;', - '\tconst godot_gdnative_api_struct **extensions;', - ] - - for funcdef in api['core']['api']: - args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) - out.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args)) - - out += [ - '} godot_gdnative_core_api_struct;', - '', - '#ifdef __cplusplus', - '}', - '#endif', - '', - '#endif // GODOT_GDNATIVE_API_STRUCT_H', - '' - ] - return '\n'.join(out) - -def _build_gdnative_api_struct_source(api): - out = [ - '/* THIS FILE IS GENERATED DO NOT EDIT */', - '', - '#include <gdnative_api_struct.gen.h>', - '' - ] - - def get_extension_struct_name(name, ext, include_version=True): - return 'godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct' - - def get_extension_struct_instance_name(name, ext, include_version=True): - return 'api_extension_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_struct' - - def get_extension_struct_definition(name, ext, include_version=True): - - ret_val = [] - - if ext['next']: - ret_val += get_extension_struct_definition(name, ext['next']) - - ret_val += [ - 'extern const ' + get_extension_struct_name(name, ext, include_version) + ' ' + get_extension_struct_instance_name(name, ext, include_version) + ' = {', - '\tGDNATIVE_EXT_' + ext['type'] + ',', - '\t{' + str(ext['version']['major']) + ', ' + str(ext['version']['minor']) + '},', - '\t' + ('NULL' if not ext['next'] else ('(const godot_gdnative_api_struct *)&' + get_extension_struct_instance_name(name, ext['next']))) + ',' - ] - - for funcdef in ext['api']: - ret_val.append('\t%s,' % funcdef['name']) - - ret_val += ['};\n'] - - return ret_val - for ext in api['extensions']: - name = ext['name'] - out += get_extension_struct_definition(name, ext, False) +from platform_methods import run_in_subprocess +import gdnative_builders - out += ['', 'const godot_gdnative_api_struct *gdnative_extensions_pointers[] = {'] - - for ext in api['extensions']: - name = ext['name'] - out += ['\t(godot_gdnative_api_struct *)&api_extension_' + name + '_struct,'] - - out += ['};\n'] - - out += [ - 'extern const godot_gdnative_core_api_struct api_struct = {', - '\tGDNATIVE_' + api['core']['type'] + ',', - '\t{' + str(api['core']['version']['major']) + ', ' + str(api['core']['version']['minor']) + '},', - '\tNULL,', - '\t' + str(len(api['extensions'])) + ',', - '\tgdnative_extensions_pointers,', - ] - - for funcdef in api['core']['api']: - out.append('\t%s,' % funcdef['name']) - out.append('};\n') - - return '\n'.join(out) - -def build_gdnative_api_struct(target, source, env): - import json - from collections import OrderedDict - - with open(source[0].path, 'r') as fd: - api = json.load(fd) - - header, source = target - with open(header.path, 'w') as fd: - fd.write(_build_gdnative_api_struct_header(api)) - with open(source.path, 'w') as fd: - fd.write(_build_gdnative_api_struct_source(api)) - -_, gensource = gdn_env.CommandNoCache(['include/gdnative_api_struct.gen.h', 'gdnative_api_struct.gen.cpp'], - 'gdnative_api.json', build_gdnative_api_struct) -gdn_env.add_source_files(env.modules_sources, [gensource]) +_, gensource = env_gdnative.CommandNoCache(['include/gdnative_api_struct.gen.h', 'gdnative_api_struct.gen.cpp'], + 'gdnative_api.json', run_in_subprocess(gdnative_builders.build_gdnative_api_struct)) +env_gdnative.add_source_files(env.modules_sources, [gensource]) env.use_ptrcall = True -def _build_gdnative_wrapper_code(api): - out = [ - '/* THIS FILE IS GENERATED DO NOT EDIT */', - '', - '#include <gdnative/gdnative.h>', - '#include <nativescript/godot_nativescript.h>', - '#include <pluginscript/godot_pluginscript.h>', - '#include <arvr/godot_arvr.h>', - '', - '#include <gdnative_api_struct.gen.h>', - '', - '#ifdef __cplusplus', - 'extern "C" {', - '#endif', - '', - 'godot_gdnative_core_api_struct *_gdnative_wrapper_api_struct = 0;', - ] - - for ext in api['extensions']: - name = ext['name'] - out.append('godot_gdnative_ext_' + name + '_api_struct *_gdnative_wrapper_' + name + '_api_struct = 0;') - - out += [''] - - for funcdef in api['core']['api']: - args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) - out.append('%s%s(%s) {' % (_spaced(funcdef['return_type']), funcdef['name'], args)) - - args = ', '.join(['%s' % n for t, n in funcdef['arguments']]) - - return_line = '\treturn ' if funcdef['return_type'] != 'void' else '\t' - return_line += '_gdnative_wrapper_api_struct->' + funcdef['name'] + '(' + args + ');' - - out.append(return_line) - out.append('}') - out.append('') - - for ext in api['extensions']: - name = ext['name'] - for funcdef in ext['api']: - args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) - out.append('%s%s(%s) {' % (_spaced(funcdef['return_type']), funcdef['name'], args)) - - args = ', '.join(['%s' % n for t, n in funcdef['arguments']]) - - return_line = '\treturn ' if funcdef['return_type'] != 'void' else '\t' - return_line += '_gdnative_wrapper_' + name + '_api_struct->' + funcdef['name'] + '(' + args + ');' - - out.append(return_line) - out.append('}') - out.append('') - - out += [ - '#ifdef __cplusplus', - '}', - '#endif' - ] - - return '\n'.join(out) - - -def build_gdnative_wrapper_code(target, source, env): - import json - with open(source[0].path, 'r') as fd: - api = json.load(fd) - - wrapper_file = target[0] - with open(wrapper_file.path, 'w') as fd: - fd.write(_build_gdnative_wrapper_code(api)) - - - if ARGUMENTS.get('gdnative_wrapper', False): -#build wrapper code - gensource, = gdn_env.CommandNoCache('gdnative_wrapper_code.gen.cpp', 'gdnative_api.json', build_gdnative_wrapper_code) + gensource, = env_gdnative.CommandNoCache('gdnative_wrapper_code.gen.cpp', 'gdnative_api.json', run_in_subprocess(gdnative_builders.build_gdnative_wrapper_code)) gd_wrapper_env = env.Clone() gd_wrapper_env.Append(CPPPATH=['#modules/gdnative/include/']) diff --git a/modules/gdnative/arvr/SCsub b/modules/gdnative/arvr/SCsub index ecc5996108..20eaa99592 100644 --- a/modules/gdnative/arvr/SCsub +++ b/modules/gdnative/arvr/SCsub @@ -1,13 +1,6 @@ #!/usr/bin/env python -import os -import methods - Import('env') -Import('env_modules') - -env_arvr_gdnative = env_modules.Clone() - -env_arvr_gdnative.Append(CPPPATH=['#modules/gdnative/include/']) -env_arvr_gdnative.add_source_files(env.modules_sources, '*.cpp') +Import('env_gdnative') +env_gdnative.add_source_files(env.modules_sources, '*.cpp') diff --git a/modules/gdnative/arvr/arvr_interface_gdnative.cpp b/modules/gdnative/arvr/arvr_interface_gdnative.cpp index 385c020a78..b525725107 100644 --- a/modules/gdnative/arvr/arvr_interface_gdnative.cpp +++ b/modules/gdnative/arvr/arvr_interface_gdnative.cpp @@ -125,7 +125,7 @@ bool ARVRInterfaceGDNative::is_stereo() { return stereo; } -bool ARVRInterfaceGDNative::is_initialized() { +bool ARVRInterfaceGDNative::is_initialized() const { bool initialized; ERR_FAIL_COND_V(interface == NULL, false); diff --git a/modules/gdnative/arvr/arvr_interface_gdnative.h b/modules/gdnative/arvr/arvr_interface_gdnative.h index e50be6e196..26d0cdf9f4 100644 --- a/modules/gdnative/arvr/arvr_interface_gdnative.h +++ b/modules/gdnative/arvr/arvr_interface_gdnative.h @@ -59,7 +59,7 @@ public: virtual StringName get_name() const; virtual int get_capabilities() const; - virtual bool is_initialized(); + virtual bool is_initialized() const; virtual bool initialize(); virtual void uninitialize(); diff --git a/modules/gdnative/arvr/config.py b/modules/gdnative/arvr/config.py index 4d1bdfe4d1..53bc827027 100644 --- a/modules/gdnative/arvr/config.py +++ b/modules/gdnative/arvr/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/gdnative/config.py b/modules/gdnative/config.py index c5b37d35b4..701a13d32f 100644 --- a/modules/gdnative/config.py +++ b/modules/gdnative/config.py @@ -9,9 +9,11 @@ def get_doc_classes(): "ARVRInterfaceGDNative", "GDNative", "GDNativeLibrary", + "MultiplayerPeerGDNative", "NativeScript", "PacketPeerGDNative", "PluginScript", + "StreamPeerGDNative", ] def get_doc_path(): diff --git a/modules/gdnative/doc_classes/MultiplayerPeerGDNative.xml b/modules/gdnative/doc_classes/MultiplayerPeerGDNative.xml new file mode 100644 index 0000000000..4433179726 --- /dev/null +++ b/modules/gdnative/doc_classes/MultiplayerPeerGDNative.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="MultiplayerPeerGDNative" inherits="NetworkedMultiplayerPeer" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/gdnative/doc_classes/NativeScript.xml b/modules/gdnative/doc_classes/NativeScript.xml index 1d3053244b..37d5b79e7a 100644 --- a/modules/gdnative/doc_classes/NativeScript.xml +++ b/modules/gdnative/doc_classes/NativeScript.xml @@ -57,6 +57,10 @@ </member> <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"> + </member> + <member name="script_class_name" type="String" setter="set_script_class_name" getter="get_script_class_name"> + </member> </members> <constants> </constants> diff --git a/modules/gdnative/doc_classes/PacketPeerGDNative.xml b/modules/gdnative/doc_classes/PacketPeerGDNative.xml new file mode 100644 index 0000000000..0ae54bc9c7 --- /dev/null +++ b/modules/gdnative/doc_classes/PacketPeerGDNative.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="PacketPeerGDNative" inherits="PacketPeer" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/gdnative/doc_classes/StreamPeerGDNative.xml b/modules/gdnative/doc_classes/StreamPeerGDNative.xml new file mode 100644 index 0000000000..d86cd2c25a --- /dev/null +++ b/modules/gdnative/doc_classes/StreamPeerGDNative.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="StreamPeerGDNative" inherits="StreamPeer" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index e7ebcc73af..f07fdef488 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -30,11 +30,11 @@ #include "gdnative.h" -#include "global_constants.h" -#include "io/file_access_encrypted.h" -#include "os/file_access.h" -#include "os/os.h" -#include "project_settings.h" +#include "core/global_constants.h" +#include "core/io/file_access_encrypted.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "core/project_settings.h" #include "scene/main/scene_tree.h" @@ -243,12 +243,12 @@ void GDNativeLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("set_symbol_prefix", "symbol_prefix"), &GDNativeLibrary::set_symbol_prefix); ClassDB::bind_method(D_METHOD("set_reloadable", "reloadable"), &GDNativeLibrary::set_reloadable); - ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "config_file", PROPERTY_HINT_RESOURCE_TYPE, "ConfigFile"), "set_config_file", "get_config_file"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "config_file", PROPERTY_HINT_RESOURCE_TYPE, "ConfigFile"), "set_config_file", "get_config_file"); - ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "load_once"), "set_load_once", "should_load_once"); - ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "singleton"), "set_singleton", "is_singleton"); - ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "symbol_prefix"), "set_symbol_prefix", "get_symbol_prefix"); - ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "reloadable"), "set_reloadable", "is_reloadable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "load_once"), "set_load_once", "should_load_once"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "singleton"), "set_singleton", "is_singleton"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "symbol_prefix"), "set_symbol_prefix", "get_symbol_prefix"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reloadable"), "set_reloadable", "is_reloadable"); } GDNative::GDNative() { @@ -268,7 +268,7 @@ void GDNative::_bind_methods() { ClassDB::bind_method(D_METHOD("call_native", "calling_type", "procedure_name", "arguments"), &GDNative::call_native); - ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library"); } void GDNative::set_library(Ref<GDNativeLibrary> p_library) { @@ -277,7 +277,7 @@ void GDNative::set_library(Ref<GDNativeLibrary> p_library) { library = p_library; } -Ref<GDNativeLibrary> GDNative::get_library() { +Ref<GDNativeLibrary> GDNative::get_library() const { return library; } @@ -306,6 +306,13 @@ bool GDNative::initialize() { #elif defined(UWP_ENABLED) // On UWP we use a relative path from the app String path = lib_path.replace("res://", ""); +#elif defined(OSX_ENABLED) + // On OSX the exported libraries are located under the Frameworks directory. + // So we need to replace the library path. + String path = ProjectSettings::get_singleton()->globalize_path(lib_path); + if (!FileAccess::exists(path)) { + path = OS::get_singleton()->get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(lib_path.get_file()); + } #else String path = ProjectSettings::get_singleton()->globalize_path(lib_path); #endif @@ -373,7 +380,7 @@ bool GDNative::initialize() { if (library->should_load_once() && !GDNativeLibrary::loaded_libraries->has(lib_path)) { Vector<Ref<GDNative> > gdnatives; gdnatives.resize(1); - gdnatives[0] = Ref<GDNative>(this); + gdnatives.write[0] = Ref<GDNative>(this); GDNativeLibrary::loaded_libraries->insert(lib_path, gdnatives); } @@ -390,7 +397,7 @@ bool GDNative::terminate() { if (library->should_load_once()) { 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 terminte + // there are other GDNative's still using this library, so we actually don't terminate gdnatives->erase(Ref<GDNative>(this)); initialized = false; return true; @@ -428,7 +435,7 @@ bool GDNative::terminate() { return true; } -bool GDNative::is_initialized() { +bool GDNative::is_initialized() const { return initialized; } @@ -442,7 +449,7 @@ Vector<StringName> GDNativeCallRegistry::get_native_call_types() { size_t idx = 0; for (Map<StringName, native_call_cb>::Element *E = native_calls.front(); E; E = E->next(), idx++) { - call_types[idx] = E->key(); + call_types.write[idx] = E->key(); } return call_types; @@ -474,7 +481,7 @@ Variant GDNative::call_native(StringName p_native_call_type, StringName p_proced return res; } -Error GDNative::get_symbol(StringName p_procedure_name, void *&r_handle, bool p_optional) { +Error GDNative::get_symbol(StringName p_procedure_name, void *&r_handle, bool p_optional) const { if (!initialized) { ERR_PRINT("No valid library handle, can't get symbol from GDNative object"); diff --git a/modules/gdnative/gdnative.h b/modules/gdnative/gdnative.h index b17bb94f1c..c5364a72ac 100644 --- a/modules/gdnative/gdnative.h +++ b/modules/gdnative/gdnative.h @@ -31,15 +31,15 @@ #ifndef GDNATIVE_H #define GDNATIVE_H -#include "io/resource_loader.h" -#include "io/resource_saver.h" -#include "os/thread_safe.h" -#include "resource.h" +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#include "core/os/thread_safe.h" +#include "core/resource.h" #include "gdnative/gdnative.h" #include "gdnative_api_struct.gen.h" -#include "io/config_file.h" +#include "core/io/config_file.h" class GDNativeLibraryResourceLoader; class GDNative; @@ -148,16 +148,16 @@ public: static void _bind_methods(); void set_library(Ref<GDNativeLibrary> p_library); - Ref<GDNativeLibrary> get_library(); + Ref<GDNativeLibrary> get_library() const; - bool is_initialized(); + bool is_initialized() const; bool initialize(); bool terminate(); Variant call_native(StringName p_native_call_type, StringName p_procedure_name, Array p_arguments = Array()); - Error get_symbol(StringName p_procedure_name, void *&r_handle, bool p_optional = true); + Error get_symbol(StringName p_procedure_name, void *&r_handle, bool p_optional = true) const; }; class GDNativeLibraryResourceLoader : public ResourceFormatLoader { diff --git a/modules/gdnative/gdnative/array.cpp b/modules/gdnative/gdnative/array.cpp index 1fb0ff0500..a30cc09bf6 100644 --- a/modules/gdnative/gdnative/array.cpp +++ b/modules/gdnative/gdnative/array.cpp @@ -318,6 +318,38 @@ void GDAPI godot_array_destroy(godot_array *p_self) { ((Array *)p_self)->~Array(); } +godot_array GDAPI godot_array_duplicate(const godot_array *p_self, const godot_bool p_deep) { + const Array *self = (const Array *)p_self; + godot_array res; + Array *val = (Array *)&res; + memnew_placement(val, Array); + *val = self->duplicate(p_deep); + return res; +} + +godot_variant GDAPI godot_array_max(const godot_array *p_self) { + const Array *self = (const Array *)p_self; + godot_variant v; + Variant *val = (Variant *)&v; + memnew_placement(val, Variant); + *val = self->max(); + return v; +} + +godot_variant GDAPI godot_array_min(const godot_array *p_self) { + const Array *self = (const Array *)p_self; + godot_variant v; + Variant *val = (Variant *)&v; + memnew_placement(val, Variant); + *val = self->min(); + return v; +} + +void GDAPI godot_array_shuffle(godot_array *p_self) { + Array *self = (Array *)p_self; + self->shuffle(); +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/basis.cpp b/modules/gdnative/gdnative/basis.cpp index 372bdf3fb1..d88499ade1 100644 --- a/modules/gdnative/gdnative/basis.cpp +++ b/modules/gdnative/gdnative/basis.cpp @@ -113,6 +113,40 @@ godot_vector3 GDAPI godot_basis_get_scale(const godot_basis *p_self) { return dest; } +godot_quat GDAPI godot_basis_get_quat(const godot_basis *p_self) { + godot_quat dest; + const Basis *self = (const Basis *)p_self; + *((Quat *)&dest) = self->get_quat(); + return dest; +} + +void GDAPI godot_basis_set_quat(godot_basis *p_self, const godot_quat *p_quat) { + Basis *self = (Basis *)p_self; + const Quat *quat = (const Quat *)p_quat; + self->set_quat(*quat); +} + +void GDAPI godot_basis_set_axis_angle_scale(godot_basis *p_self, const godot_vector3 *p_axis, godot_real p_phi, const godot_vector3 *p_scale) { + Basis *self = (Basis *)p_self; + const Vector3 *axis = (const Vector3 *)p_axis; + const Vector3 *scale = (const Vector3 *)p_scale; + self->set_axis_angle_scale(*axis, p_phi, *scale); +} + +void GDAPI godot_basis_set_euler_scale(godot_basis *p_self, const godot_vector3 *p_euler, const godot_vector3 *p_scale) { + Basis *self = (Basis *)p_self; + const Vector3 *euler = (const Vector3 *)p_euler; + const Vector3 *scale = (const Vector3 *)p_scale; + self->set_euler_scale(*euler, *scale); +} + +void GDAPI godot_basis_set_quat_scale(godot_basis *p_self, const godot_quat *p_quat, const godot_vector3 *p_scale) { + Basis *self = (Basis *)p_self; + const Quat *quat = (const Quat *)p_quat; + const Vector3 *scale = (const Vector3 *)p_scale; + self->set_quat_scale(*quat, *scale); +} + godot_vector3 GDAPI godot_basis_get_euler(const godot_basis *p_self) { godot_vector3 dest; const Basis *self = (const Basis *)p_self; @@ -248,6 +282,15 @@ godot_basis GDAPI godot_basis_operator_multiply_scalar(const godot_basis *p_self return raw_dest; } +godot_basis GDAPI godot_basis_slerp(const godot_basis *p_self, const godot_basis *p_b, const godot_real p_t) { + godot_basis raw_dest; + Basis *dest = (Basis *)&raw_dest; + const Basis *self = (const Basis *)p_self; + const Basis *b = (const Basis *)p_b; + *dest = self->slerp(*b, p_t); + return raw_dest; +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/color.cpp b/modules/gdnative/gdnative/color.cpp index 4089f4458a..79f0c71b5e 100644 --- a/modules/gdnative/gdnative/color.cpp +++ b/modules/gdnative/gdnative/color.cpp @@ -116,6 +116,26 @@ godot_int GDAPI godot_color_to_rgba32(const godot_color *p_self) { return self->to_rgba32(); } +godot_int GDAPI godot_color_to_abgr32(const godot_color *p_self) { + const Color *self = (const Color *)p_self; + return self->to_abgr32(); +} + +godot_int GDAPI godot_color_to_abgr64(const godot_color *p_self) { + const Color *self = (const Color *)p_self; + return self->to_abgr64(); +} + +godot_int GDAPI godot_color_to_argb64(const godot_color *p_self) { + const Color *self = (const Color *)p_self; + return self->to_argb64(); +} + +godot_int GDAPI godot_color_to_rgba64(const godot_color *p_self) { + const Color *self = (const Color *)p_self; + return self->to_rgba64(); +} + godot_int GDAPI godot_color_to_argb32(const godot_color *p_self) { const Color *self = (const Color *)p_self; return self->to_argb32(); @@ -156,6 +176,27 @@ godot_color GDAPI godot_color_blend(const godot_color *p_self, const godot_color return dest; } +godot_color GDAPI godot_color_darkened(const godot_color *p_self, const godot_real p_amount) { + godot_color dest; + const Color *self = (const Color *)p_self; + *((Color *)&dest) = self->darkened(p_amount); + return dest; +} + +godot_color GDAPI godot_color_from_hsv(const godot_color *p_self, const godot_real p_h, const godot_real p_s, const godot_real p_v, const godot_real p_a) { + godot_color dest; + const Color *self = (const Color *)p_self; + *((Color *)&dest) = self->from_hsv(p_h, p_s, p_v, p_a); + return dest; +} + +godot_color GDAPI godot_color_lightened(const godot_color *p_self, const godot_real p_amount) { + godot_color dest; + const Color *self = (const Color *)p_self; + *((Color *)&dest) = self->lightened(p_amount); + return dest; +} + godot_string GDAPI godot_color_to_html(const godot_color *p_self, const godot_bool p_with_alpha) { godot_string dest; const Color *self = (const Color *)p_self; diff --git a/modules/gdnative/gdnative/dictionary.cpp b/modules/gdnative/gdnative/dictionary.cpp index 786e614158..34cc91129e 100644 --- a/modules/gdnative/gdnative/dictionary.cpp +++ b/modules/gdnative/gdnative/dictionary.cpp @@ -155,6 +155,12 @@ godot_string GDAPI godot_dictionary_to_json(const godot_dictionary *p_self) { return raw_dest; } +godot_bool GDAPI godot_dictionary_erase_with_return(godot_dictionary *p_self, const godot_variant *p_key) { + Dictionary *self = (Dictionary *)p_self; + const Variant *key = (const Variant *)p_key; + return self->erase(*key); +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/gdnative.cpp b/modules/gdnative/gdnative/gdnative.cpp index 041990e137..8f10f116e6 100644 --- a/modules/gdnative/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative/gdnative.cpp @@ -30,12 +30,12 @@ #include "gdnative/gdnative.h" -#include "class_db.h" -#include "engine.h" -#include "error_macros.h" -#include "global_constants.h" -#include "os/os.h" -#include "variant.h" +#include "core/class_db.h" +#include "core/engine.h" +#include "core/error_macros.h" +#include "core/global_constants.h" +#include "core/os/os.h" +#include "core/variant.h" #include "modules/gdnative/gdnative.h" @@ -166,6 +166,10 @@ void _gdnative_report_loading_error(const godot_object *p_library, const char *p _err_print_error("gdnative_init", library->get_current_library_path().utf8().ptr(), 0, message.utf8().ptr()); } +bool GDAPI godot_is_instance_valid(const godot_object *p_object) { + return ObjectDB::instance_validate((Object *)p_object); +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/node_path.cpp b/modules/gdnative/gdnative/node_path.cpp index f24facaae8..cf82940e09 100644 --- a/modules/gdnative/gdnative/node_path.cpp +++ b/modules/gdnative/gdnative/node_path.cpp @@ -110,6 +110,15 @@ godot_bool GDAPI godot_node_path_operator_equal(const godot_node_path *p_self, c return *self == *b; } +godot_node_path godot_node_path_get_as_property_path(const godot_node_path *p_self) { + const NodePath *self = (const NodePath *)p_self; + godot_node_path res; + NodePath *val = (NodePath *)&res; + memnew_placement(val, NodePath); + *val = self->get_as_property_path(); + return res; +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/pool_arrays.cpp b/modules/gdnative/gdnative/pool_arrays.cpp index 6688be1a0d..d55d81b5b6 100644 --- a/modules/gdnative/gdnative/pool_arrays.cpp +++ b/modules/gdnative/gdnative/pool_arrays.cpp @@ -30,12 +30,12 @@ #include "gdnative/pool_arrays.h" -#include "array.h" +#include "core/array.h" +#include "core/dvector.h" #include "core/variant.h" -#include "dvector.h" #include "core/color.h" -#include "core/math/math_2d.h" +#include "core/math/vector2.h" #include "core/math/vector3.h" #ifdef __cplusplus diff --git a/modules/gdnative/gdnative/quat.cpp b/modules/gdnative/gdnative/quat.cpp index 56ff7fe3a8..2594759508 100644 --- a/modules/gdnative/gdnative/quat.cpp +++ b/modules/gdnative/gdnative/quat.cpp @@ -49,6 +49,18 @@ void GDAPI godot_quat_new_with_axis_angle(godot_quat *r_dest, const godot_vector *dest = Quat(*axis, p_angle); } +void GDAPI godot_quat_new_with_basis(godot_quat *r_dest, const godot_basis *p_basis) { + const Basis *basis = (const Basis *)p_basis; + Quat *dest = (Quat *)r_dest; + *dest = Quat(*basis); +} + +void GDAPI godot_quat_new_with_euler(godot_quat *r_dest, const godot_vector3 *p_euler) { + const Vector3 *euler = (const Vector3 *)p_euler; + Quat *dest = (Quat *)r_dest; + *dest = Quat(*euler); +} + godot_real GDAPI godot_quat_get_x(const godot_quat *p_self) { const Quat *self = (const Quat *)p_self; return self->x; @@ -213,6 +225,12 @@ godot_quat GDAPI godot_quat_operator_neg(const godot_quat *p_self) { return raw_dest; } +void GDAPI godot_quat_set_axis_angle(godot_quat *p_self, const godot_vector3 *p_axis, const godot_real p_angle) { + Quat *self = (Quat *)p_self; + const Vector3 *axis = (const Vector3 *)p_axis; + self->set_axis_angle(*axis, p_angle); +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/rect2.cpp b/modules/gdnative/gdnative/rect2.cpp index 83c58db520..5cbc2712c3 100644 --- a/modules/gdnative/gdnative/rect2.cpp +++ b/modules/gdnative/gdnative/rect2.cpp @@ -30,7 +30,7 @@ #include "gdnative/rect2.h" -#include "core/math/math_2d.h" +#include "core/math/transform_2d.h" #include "core/variant.h" #ifdef __cplusplus @@ -109,6 +109,27 @@ godot_rect2 GDAPI godot_rect2_grow(const godot_rect2 *p_self, const godot_real p return dest; } +godot_rect2 GDAPI godot_rect2_grow_individual(const godot_rect2 *p_self, const godot_real p_left, const godot_real p_top, const godot_real p_right, const godot_real p_bottom) { + godot_rect2 dest; + const Rect2 *self = (const Rect2 *)p_self; + *((Rect2 *)&dest) = self->grow_individual(p_left, p_top, p_right, p_bottom); + return dest; +} + +godot_rect2 GDAPI godot_rect2_grow_margin(const godot_rect2 *p_self, const godot_int p_margin, const godot_real p_by) { + godot_rect2 dest; + const Rect2 *self = (const Rect2 *)p_self; + *((Rect2 *)&dest) = self->grow_margin((Margin)p_margin, p_by); + return dest; +} + +godot_rect2 GDAPI godot_rect2_abs(const godot_rect2 *p_self) { + godot_rect2 dest; + const Rect2 *self = (const Rect2 *)p_self; + *((Rect2 *)&dest) = self->abs(); + return dest; +} + godot_rect2 GDAPI godot_rect2_expand(const godot_rect2 *p_self, const godot_vector2 *p_to) { godot_rect2 dest; const Rect2 *self = (const Rect2 *)p_self; diff --git a/modules/gdnative/gdnative/string.cpp b/modules/gdnative/gdnative/string.cpp index 7f5dbc12be..0996633b70 100644 --- a/modules/gdnative/gdnative/string.cpp +++ b/modules/gdnative/gdnative/string.cpp @@ -207,7 +207,7 @@ godot_int GDAPI godot_string_findmk(const godot_string *p_self, const godot_arra Array *keys_proxy = (Array *)p_keys; keys.resize(keys_proxy->size()); for (int i = 0; i < keys_proxy->size(); i++) { - keys[i] = (*keys_proxy)[i]; + keys.write[i] = (*keys_proxy)[i]; } return self->findmk(keys); @@ -220,7 +220,7 @@ godot_int GDAPI godot_string_findmk_from(const godot_string *p_self, const godot Array *keys_proxy = (Array *)p_keys; keys.resize(keys_proxy->size()); for (int i = 0; i < keys_proxy->size(); i++) { - keys[i] = (*keys_proxy)[i]; + keys.write[i] = (*keys_proxy)[i]; } return self->findmk(keys, p_from); @@ -233,7 +233,7 @@ godot_int GDAPI godot_string_findmk_from_in_place(const godot_string *p_self, co Array *keys_proxy = (Array *)p_keys; keys.resize(keys_proxy->size()); for (int i = 0; i < keys_proxy->size(); i++) { - keys[i] = (*keys_proxy)[i]; + keys.write[i] = (*keys_proxy)[i]; } return self->findmk(keys, p_from, r_key); @@ -696,7 +696,7 @@ godot_array GDAPI godot_string_split_floats_mk(const godot_string *p_self, const Array *splitter_proxy = (Array *)p_splitters; splitters.resize(splitter_proxy->size()); for (int i = 0; i < splitter_proxy->size(); i++) { - splitters[i] = (*splitter_proxy)[i]; + splitters.write[i] = (*splitter_proxy)[i]; } godot_array result; @@ -719,7 +719,7 @@ godot_array GDAPI godot_string_split_floats_mk_allows_empty(const godot_string * Array *splitter_proxy = (Array *)p_splitters; splitters.resize(splitter_proxy->size()); for (int i = 0; i < splitter_proxy->size(); i++) { - splitters[i] = (*splitter_proxy)[i]; + splitters.write[i] = (*splitter_proxy)[i]; } godot_array result; @@ -774,7 +774,7 @@ godot_array GDAPI godot_string_split_ints_mk(const godot_string *p_self, const g Array *splitter_proxy = (Array *)p_splitters; splitters.resize(splitter_proxy->size()); for (int i = 0; i < splitter_proxy->size(); i++) { - splitters[i] = (*splitter_proxy)[i]; + splitters.write[i] = (*splitter_proxy)[i]; } godot_array result; @@ -797,7 +797,7 @@ godot_array GDAPI godot_string_split_ints_mk_allows_empty(const godot_string *p_ Array *splitter_proxy = (Array *)p_splitters; splitters.resize(splitter_proxy->size()); for (int i = 0; i < splitter_proxy->size(); i++) { - splitters[i] = (*splitter_proxy)[i]; + splitters.write[i] = (*splitter_proxy)[i]; } godot_array result; @@ -1285,6 +1285,64 @@ godot_bool GDAPI godot_string_is_valid_ip_address(const godot_string *p_self) { return self->is_valid_ip_address(); } +godot_string GDAPI godot_string_dedent(const godot_string *p_self) { + const String *self = (const String *)p_self; + godot_string result; + String return_value = self->dedent(); + memnew_placement(&result, String(return_value)); + + return result; +} + +godot_string GDAPI godot_string_trim_prefix(const godot_string *p_self, const godot_string *p_prefix) { + const String *self = (const String *)p_self; + String *prefix = (String *)p_prefix; + godot_string result; + String return_value = self->trim_prefix(*prefix); + memnew_placement(&result, String(return_value)); + + return result; +} + +godot_string GDAPI godot_string_trim_suffix(const godot_string *p_self, const godot_string *p_suffix) { + const String *self = (const String *)p_self; + String *suffix = (String *)p_suffix; + godot_string result; + String return_value = self->trim_suffix(*suffix); + memnew_placement(&result, String(return_value)); + + return result; +} + +godot_string GDAPI godot_string_rstrip(const godot_string *p_self, const godot_string *p_chars) { + const String *self = (const String *)p_self; + String *chars = (String *)p_chars; + godot_string result; + String return_value = self->rstrip(*chars); + memnew_placement(&result, String(return_value)); + + return result; +} + +godot_pool_string_array GDAPI godot_string_rsplit(const godot_string *p_self, const godot_string *p_divisor, + const godot_bool p_allow_empty, const godot_int p_maxsplit) { + const String *self = (const String *)p_self; + String *divisor = (String *)p_divisor; + + godot_pool_string_array result; + memnew_placement(&result, PoolStringArray); + PoolStringArray *proxy = (PoolStringArray *)&result; + PoolStringArray::Write proxy_writer = proxy->write(); + Vector<String> tmp_result = self->rsplit(*divisor, p_allow_empty, p_maxsplit); + proxy->resize(tmp_result.size()); + + for (int i = 0; i < tmp_result.size(); i++) { + proxy_writer[i] = tmp_result[i]; + } + + return result; +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/transform.cpp b/modules/gdnative/gdnative/transform.cpp index 715f2e3c08..ee6140c7d0 100644 --- a/modules/gdnative/gdnative/transform.cpp +++ b/modules/gdnative/gdnative/transform.cpp @@ -56,6 +56,12 @@ void GDAPI godot_transform_new(godot_transform *r_dest, const godot_basis *p_bas *dest = Transform(*basis, *origin); } +void GDAPI godot_transform_new_with_quat(godot_transform *r_dest, const godot_quat *p_quat) { + const Quat *quat = (const Quat *)p_quat; + Transform *dest = (Transform *)r_dest; + *dest = Transform(*quat); +} + godot_basis GDAPI godot_transform_get_basis(const godot_transform *p_self) { godot_basis dest; const Transform *self = (const Transform *)p_self; diff --git a/modules/gdnative/gdnative/transform2d.cpp b/modules/gdnative/gdnative/transform2d.cpp index c69607a18a..fa0e15d9d2 100644 --- a/modules/gdnative/gdnative/transform2d.cpp +++ b/modules/gdnative/gdnative/transform2d.cpp @@ -30,7 +30,7 @@ #include "gdnative/transform2d.h" -#include "core/math/math_2d.h" +#include "core/math/transform_2d.h" #include "core/variant.h" #ifdef __cplusplus diff --git a/modules/gdnative/gdnative/variant.cpp b/modules/gdnative/gdnative/variant.cpp index 423f3312e1..fd6babfc3a 100644 --- a/modules/gdnative/gdnative/variant.cpp +++ b/modules/gdnative/gdnative/variant.cpp @@ -489,6 +489,24 @@ void GDAPI godot_variant_destroy(godot_variant *p_self) { self->~Variant(); } +// GDNative core 1.1 + +godot_string GDAPI godot_variant_get_operator_name(godot_variant_operator p_op) { + Variant::Operator op = (Variant::Operator)p_op; + godot_string raw_dest; + String *dest = (String *)&raw_dest; + memnew_placement(dest, String(Variant::get_operator_name(op))); // operator = is overloaded by String + return raw_dest; +} + +void GDAPI godot_variant_evaluate(godot_variant_operator p_op, const godot_variant *p_a, const godot_variant *p_b, godot_variant *r_ret, godot_bool *r_valid) { + Variant::Operator op = (Variant::Operator)p_op; + const Variant *a = (const Variant *)p_a; + const Variant *b = (const Variant *)p_b; + Variant *ret = (Variant *)r_ret; + Variant::evaluate(op, a, b, *ret, *r_valid); +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/vector2.cpp b/modules/gdnative/gdnative/vector2.cpp index 9e40b42373..c7902e06ee 100644 --- a/modules/gdnative/gdnative/vector2.cpp +++ b/modules/gdnative/gdnative/vector2.cpp @@ -30,7 +30,7 @@ #include "gdnative/vector2.h" -#include "core/math/math_2d.h" +#include "core/math/vector2.h" #include "core/variant.h" #ifdef __cplusplus diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 217fd87c3e..c5a1fa139e 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -5,7 +5,293 @@ "major": 1, "minor": 0 }, - "next": null, + "next": { + "type": "CORE", + "version": { + "major": 1, + "minor": 1 + }, + "next": null, + "api": [ + { + "name": "godot_color_to_abgr32", + "return_type": "godot_int", + "arguments": [ + ["const godot_color *", "p_self"] + ] + }, + { + "name": "godot_color_to_abgr64", + "return_type": "godot_int", + "arguments": [ + ["const godot_color *", "p_self"] + ] + }, + { + "name": "godot_color_to_argb64", + "return_type": "godot_int", + "arguments": [ + ["const godot_color *", "p_self"] + ] + }, + { + "name": "godot_color_to_rgba64", + "return_type": "godot_int", + "arguments": [ + ["const godot_color *", "p_self"] + ] + }, + { + "name": "godot_color_darkened", + "return_type": "godot_color", + "arguments": [ + ["const godot_color *", "p_self"], + ["const godot_real", "p_amount"] + ] + }, + { + "name": "godot_color_from_hsv", + "return_type": "godot_color", + "arguments": [ + ["const godot_color *", "p_self"], + ["const godot_real", "p_h"], + ["const godot_real", "p_s"], + ["const godot_real", "p_v"], + ["const godot_real", "p_a"] + ] + }, + { + "name": "godot_color_lightened", + "return_type": "godot_color", + "arguments": [ + ["const godot_color *", "p_self"], + ["const godot_real", "p_amount"] + ] + }, + { + "name": "godot_array_duplicate", + "return_type": "godot_array", + "arguments": [ + ["const godot_array *", "p_self"], + ["const godot_bool", "p_deep"] + ] + }, + { + "name": "godot_array_max", + "return_type": "godot_variant", + "arguments": [ + ["const godot_array *", "p_self"] + ] + }, + { + "name": "godot_array_min", + "return_type": "godot_variant", + "arguments": [ + ["const godot_array *", "p_self"] + ] + }, + { + "name": "godot_array_shuffle", + "return_type": "void", + "arguments": [ + ["godot_array *", "p_self"] + ] + }, + { + "name": "godot_basis_slerp", + "return_type": "godot_basis", + "arguments": [ + ["const godot_basis *", "p_self"], + ["const godot_basis *", "p_b"], + ["const godot_real", "p_t"] + ] + }, + { + "name": "godot_node_path_get_as_property_path", + "return_type": "godot_node_path", + "arguments": [ + ["const godot_node_path *", "p_self"] + ] + }, + { + "name": "godot_quat_set_axis_angle", + "return_type": "void", + "arguments": [ + ["godot_quat *", "p_self"], + ["const godot_vector3 *", "p_axis"], + ["const godot_real", "p_angle"] + ] + }, + { + "name": "godot_rect2_grow_individual", + "return_type": "godot_rect2", + "arguments": [ + ["const godot_rect2 *", "p_self"], + ["const godot_real", "p_left"], + ["const godot_real", "p_top"], + ["const godot_real", "p_right"], + ["const godot_real", "p_bottom"] + ] + }, + { + "name": "godot_rect2_grow_margin", + "return_type": "godot_rect2", + "arguments": [ + ["const godot_rect2 *", "p_self"], + ["const godot_int", "p_margin"], + ["const godot_real", "p_by"] + ] + }, + { + "name": "godot_rect2_abs", + "return_type": "godot_rect2", + "arguments": [ + ["const godot_rect2 *", "p_self"] + ] + }, + { + "name": "godot_string_dedent", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"] + ] + }, + { + "name": "godot_string_trim_prefix", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_prefix"] + ] + }, + { + "name": "godot_string_trim_suffix", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_suffix"] + ] + }, + { + "name": "godot_string_rstrip", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_chars"] + ] + }, + { + "name": "godot_string_rsplit", + "return_type": "godot_pool_string_array", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_divisor"], + ["const godot_bool", "p_allow_empty"], + ["const godot_int", "p_maxsplit"] + ] + }, + { + "name": "godot_basis_get_quat", + "return_type": "godot_quat", + "arguments": [ + ["const godot_basis *", "p_self"] + ] + }, + { + "name": "godot_basis_set_quat", + "return_type": "void", + "arguments": [ + ["godot_basis *", "p_self"], + ["const godot_quat *", "p_quat"] + ] + }, + { + "name": "godot_basis_set_axis_angle_scale", + "return_type": "void", + "arguments": [ + ["godot_basis *", "p_self"], + ["const godot_vector3 *", "p_axis"], + ["godot_real", "p_phi"], + ["const godot_vector3 *", "p_scale"] + ] + }, + { + "name": "godot_basis_set_euler_scale", + "return_type": "void", + "arguments": [ + ["godot_basis *", "p_self"], + ["const godot_vector3 *", "p_euler"], + ["const godot_vector3 *", "p_scale"] + ] + }, + { + "name": "godot_basis_set_quat_scale", + "return_type": "void", + "arguments": [ + ["godot_basis *", "p_self"], + ["const godot_quat *", "p_quat"], + ["const godot_vector3 *", "p_scale"] + ] + }, + { + "name": "godot_dictionary_erase_with_return", + "return_type": "bool", + "arguments": [ + ["godot_dictionary *", "p_self"], + ["const godot_variant *", "p_key"] + ] + }, + { + "name": "godot_is_instance_valid", + "return_type": "bool", + "arguments": [ + ["const godot_object *", "p_object"] + ] + }, + { + "name": "godot_quat_new_with_basis", + "return_type": "void", + "arguments": [ + ["godot_quat *", "r_dest"], + ["const godot_basis *", "p_basis"] + ] + }, + { + "name": "godot_quat_new_with_euler", + "return_type": "void", + "arguments": [ + ["godot_quat *", "r_dest"], + ["const godot_vector3 *", "p_euler"] + ] + }, + { + "name": "godot_transform_new_with_quat", + "return_type": "void", + "arguments": [ + ["godot_transform *", "r_dest"], + ["const godot_quat *", "p_quat"] + ] + }, + { + "name": "godot_variant_get_operator_name", + "return_type": "godot_string", + "arguments": [ + ["godot_variant_operator", "p_op"] + ] + }, + { + "name": "godot_variant_evaluate", + "return_type": "void", + "arguments": [ + ["godot_variant_operator", "p_op"], + ["const godot_variant *", "p_a"], + ["const godot_variant *", "p_b"], + ["godot_variant *", "r_ret"], + ["godot_bool *", "r_valid"] + ] + } + ] + }, "api": [ { "name": "godot_color_new_rgba", @@ -4484,7 +4770,7 @@ ] }, { - "name": "godot_string_wide_str", + "name": "godot_string_wide_str", "return_type": "const wchar_t *", "arguments": [ ["const godot_string *", "p_self"] @@ -5253,21 +5539,21 @@ "name": "godot_string_ascii", "return_type": "godot_char_string", "arguments": [ - ["const godot_string *", "p_self"] + ["const godot_string *", "p_self"] ] }, { "name": "godot_string_ascii_extended", "return_type": "godot_char_string", "arguments": [ - ["const godot_string *", "p_self"] + ["const godot_string *", "p_self"] ] }, { "name": "godot_string_utf8", "return_type": "godot_char_string", "arguments": [ - ["const godot_string *", "p_self"] + ["const godot_string *", "p_self"] ] }, { @@ -5765,15 +6051,15 @@ "minor": 0 }, "next": { - "type": "NATIVESCRIPT", - "version": { - "major": 1, - "minor": 1 - }, - "next": null, - "api": [ + "type": "NATIVESCRIPT", + "version": { + "major": 1, + "minor": 1 + }, + "next": null, + "api": [ { - "name": "godot_nativescript_set_method_argument_information", + "name": "godot_nativescript_set_method_argument_information", "return_type": "void", "arguments": [ ["void *", "p_gdnative_handle"], @@ -5784,7 +6070,7 @@ ] }, { - "name": "godot_nativescript_set_class_documentation", + "name": "godot_nativescript_set_class_documentation", "return_type": "void", "arguments": [ ["void *", "p_gdnative_handle"], @@ -5793,7 +6079,7 @@ ] }, { - "name": "godot_nativescript_set_method_documentation", + "name": "godot_nativescript_set_method_documentation", "return_type": "void", "arguments": [ ["void *", "p_gdnative_handle"], @@ -5803,7 +6089,7 @@ ] }, { - "name": "godot_nativescript_set_property_documentation", + "name": "godot_nativescript_set_property_documentation", "return_type": "void", "arguments": [ ["void *", "p_gdnative_handle"], @@ -5813,7 +6099,7 @@ ] }, { - "name": "godot_nativescript_set_signal_documentation", + "name": "godot_nativescript_set_signal_documentation", "return_type": "void", "arguments": [ ["void *", "p_gdnative_handle"], @@ -5874,10 +6160,18 @@ "return_type": "void *", "arguments": [ ["int", "p_idx"], - ["godot_object *", "p_object"] + ["godot_object *", "p_object"] + ] + }, + { + "name": "godot_nativescript_profiling_add_data", + "return_type": "void", + "arguments": [ + ["const char *", "p_signature"], + ["uint64_t", "p_line"] ] } - ] + ] }, "api": [ { diff --git a/modules/gdnative/gdnative_builders.py b/modules/gdnative/gdnative_builders.py new file mode 100644 index 0000000000..ff18a3ae69 --- /dev/null +++ b/modules/gdnative/gdnative_builders.py @@ -0,0 +1,310 @@ +"""Functions used to generate source files during build time + +All such functions are invoked in a subprocess on Windows to prevent build flakiness. + +""" +import json +from platform_methods import subprocess_main + + +def _spaced(e): + return e if e[-1] == '*' else e + ' ' + + +def _build_gdnative_api_struct_header(api): + gdnative_api_init_macro = [ + '\textern const godot_gdnative_core_api_struct *_gdnative_wrapper_api_struct;' + ] + + for ext in api['extensions']: + name = ext['name'] + gdnative_api_init_macro.append( + '\textern const godot_gdnative_ext_{0}_api_struct *_gdnative_wrapper_{0}_api_struct;'.format(name)) + + gdnative_api_init_macro.append('\t_gdnative_wrapper_api_struct = options->api_struct;') + gdnative_api_init_macro.append('\tfor (unsigned int i = 0; i < _gdnative_wrapper_api_struct->num_extensions; i++) { ') + gdnative_api_init_macro.append('\t\tswitch (_gdnative_wrapper_api_struct->extensions[i]->type) {') + + for ext in api['extensions']: + name = ext['name'] + gdnative_api_init_macro.append( + '\t\t\tcase GDNATIVE_EXT_%s:' % ext['type']) + gdnative_api_init_macro.append( + '\t\t\t\t_gdnative_wrapper_{0}_api_struct = (godot_gdnative_ext_{0}_api_struct *)' + ' _gdnative_wrapper_api_struct->extensions[i];'.format(name)) + gdnative_api_init_macro.append('\t\t\t\tbreak;') + gdnative_api_init_macro.append('\t\t}') + gdnative_api_init_macro.append('\t}') + + out = [ + '/* THIS FILE IS GENERATED DO NOT EDIT */', + '#ifndef GODOT_GDNATIVE_API_STRUCT_H', + '#define GODOT_GDNATIVE_API_STRUCT_H', + '', + '#include <gdnative/gdnative.h>', + '#include <android/godot_android.h>', + '#include <arvr/godot_arvr.h>', + '#include <nativescript/godot_nativescript.h>', + '#include <pluginscript/godot_pluginscript.h>', + '', + '#define GDNATIVE_API_INIT(options) do { \\\n' + ' \\\n'.join(gdnative_api_init_macro) + ' \\\n } while (0)', + '', + '#ifdef __cplusplus', + 'extern "C" {', + '#endif', + '', + 'enum GDNATIVE_API_TYPES {', + '\tGDNATIVE_' + api['core']['type'] + ',' + ] + + for ext in api['extensions']: + out += ['\tGDNATIVE_EXT_' + ext['type'] + ','] + + out += ['};', ''] + + def generate_extension_struct(name, ext, include_version=True): + ret_val = [] + if ext['next']: + ret_val += generate_extension_struct(name, ext['next']) + + ret_val += [ + 'typedef struct godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct {', + '\tunsigned int type;', + '\tgodot_gdnative_api_version version;', + '\tconst godot_gdnative_api_struct *next;' + ] + + for funcdef in ext['api']: + args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) + ret_val.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args)) + + ret_val += ['} godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct;', ''] + + return ret_val + + + def generate_core_extension_struct(core): + ret_val = [] + if core['next']: + ret_val += generate_core_extension_struct(core['next']) + + ret_val += [ + 'typedef struct godot_gdnative_core_' + ('{0}_{1}'.format(core['version']['major'], core['version']['minor'])) + '_api_struct {', + '\tunsigned int type;', + '\tgodot_gdnative_api_version version;', + '\tconst godot_gdnative_api_struct *next;', + ] + + for funcdef in core['api']: + args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) + ret_val.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args)) + + ret_val += ['} godot_gdnative_core_' + '{0}_{1}'.format(core['version']['major'], core['version']['minor']) + '_api_struct;', ''] + + return ret_val + + + for ext in api['extensions']: + name = ext['name'] + out += generate_extension_struct(name, ext, False) + + if api['core']['next']: + out += generate_core_extension_struct(api['core']['next']) + + out += [ + 'typedef struct godot_gdnative_core_api_struct {', + '\tunsigned int type;', + '\tgodot_gdnative_api_version version;', + '\tconst godot_gdnative_api_struct *next;', + '\tunsigned int num_extensions;', + '\tconst godot_gdnative_api_struct **extensions;', + ] + + for funcdef in api['core']['api']: + args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) + out.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args)) + + out += [ + '} godot_gdnative_core_api_struct;', + '', + '#ifdef __cplusplus', + '}', + '#endif', + '', + '#endif // GODOT_GDNATIVE_API_STRUCT_H', + '' + ] + return '\n'.join(out) + + +def _build_gdnative_api_struct_source(api): + out = [ + '/* THIS FILE IS GENERATED DO NOT EDIT */', + '', + '#include <gdnative_api_struct.gen.h>', + '' + ] + + def get_extension_struct_name(name, ext, include_version=True): + return 'godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct' + + def get_extension_struct_instance_name(name, ext, include_version=True): + return 'api_extension_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_struct' + + def get_extension_struct_definition(name, ext, include_version=True): + + ret_val = [] + + if ext['next']: + ret_val += get_extension_struct_definition(name, ext['next']) + + ret_val += [ + 'extern const ' + get_extension_struct_name(name, ext, include_version) + ' ' + get_extension_struct_instance_name(name, ext, include_version) + ' = {', + '\tGDNATIVE_EXT_' + ext['type'] + ',', + '\t{' + str(ext['version']['major']) + ', ' + str(ext['version']['minor']) + '},', + '\t' + ('NULL' if not ext['next'] else ('(const godot_gdnative_api_struct *)&' + get_extension_struct_instance_name(name, ext['next']))) + ',' + ] + + for funcdef in ext['api']: + ret_val.append('\t%s,' % funcdef['name']) + + ret_val += ['};\n'] + + return ret_val + + + def get_core_struct_definition(core): + ret_val = [] + + if core['next']: + ret_val += get_core_struct_definition(core['next']) + + ret_val += [ + 'extern const godot_gdnative_core_' + ('{0}_{1}_api_struct api_{0}_{1}'.format(core['version']['major'], core['version']['minor'])) + ' = {', + '\tGDNATIVE_' + core['type'] + ',', + '\t{' + str(core['version']['major']) + ', ' + str(core['version']['minor']) + '},', + '\t' + ('NULL' if not core['next'] else ('(const godot_gdnative_api_struct *)& api_{0}_{1}'.format(core['version']['major'], core['version']['minor']))) + ',' + ] + + for funcdef in core['api']: + ret_val.append('\t%s,' % funcdef['name']) + + ret_val += ['};\n'] + + return ret_val + + for ext in api['extensions']: + name = ext['name'] + out += get_extension_struct_definition(name, ext, False) + + out += ['', 'const godot_gdnative_api_struct *gdnative_extensions_pointers[] = {'] + + for ext in api['extensions']: + name = ext['name'] + out += ['\t(godot_gdnative_api_struct *)&api_extension_' + name + '_struct,'] + + out += ['};\n'] + + if api['core']['next']: + out += get_core_struct_definition(api['core']['next']) + + out += [ + 'extern const godot_gdnative_core_api_struct api_struct = {', + '\tGDNATIVE_' + api['core']['type'] + ',', + '\t{' + str(api['core']['version']['major']) + ', ' + str(api['core']['version']['minor']) + '},', + '\tNULL,', + '\t' + str(len(api['extensions'])) + ',', + '\tgdnative_extensions_pointers,', + ] + + for funcdef in api['core']['api']: + out.append('\t%s,' % funcdef['name']) + out.append('};\n') + + return '\n'.join(out) + + +def build_gdnative_api_struct(target, source, env): + + with open(source[0], 'r') as fd: + api = json.load(fd) + + header, source = target + with open(header, 'w') as fd: + fd.write(_build_gdnative_api_struct_header(api)) + with open(source, 'w') as fd: + fd.write(_build_gdnative_api_struct_source(api)) + + +def _build_gdnative_wrapper_code(api): + out = [ + '/* THIS FILE IS GENERATED DO NOT EDIT */', + '', + '#include <gdnative/gdnative.h>', + '#include <nativescript/godot_nativescript.h>', + '#include <pluginscript/godot_pluginscript.h>', + '#include <arvr/godot_arvr.h>', + '', + '#include <gdnative_api_struct.gen.h>', + '', + '#ifdef __cplusplus', + 'extern "C" {', + '#endif', + '', + 'godot_gdnative_core_api_struct *_gdnative_wrapper_api_struct = 0;', + ] + + for ext in api['extensions']: + name = ext['name'] + out.append('godot_gdnative_ext_' + name + '_api_struct *_gdnative_wrapper_' + name + '_api_struct = 0;') + + out += [''] + + for funcdef in api['core']['api']: + args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) + out.append('%s%s(%s) {' % (_spaced(funcdef['return_type']), funcdef['name'], args)) + + args = ', '.join(['%s' % n for t, n in funcdef['arguments']]) + + return_line = '\treturn ' if funcdef['return_type'] != 'void' else '\t' + return_line += '_gdnative_wrapper_api_struct->' + funcdef['name'] + '(' + args + ');' + + out.append(return_line) + out.append('}') + out.append('') + + for ext in api['extensions']: + name = ext['name'] + for funcdef in ext['api']: + args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) + out.append('%s%s(%s) {' % (_spaced(funcdef['return_type']), funcdef['name'], args)) + + args = ', '.join(['%s' % n for t, n in funcdef['arguments']]) + + return_line = '\treturn ' if funcdef['return_type'] != 'void' else '\t' + return_line += '_gdnative_wrapper_' + name + '_api_struct->' + funcdef['name'] + '(' + args + ');' + + out.append(return_line) + out.append('}') + out.append('') + + out += [ + '#ifdef __cplusplus', + '}', + '#endif' + ] + + return '\n'.join(out) + + +def build_gdnative_wrapper_code(target, source, env): + with open(source[0], 'r') as fd: + api = json.load(fd) + + wrapper_file = target[0] + with open(wrapper_file, 'w') as fd: + fd.write(_build_gdnative_wrapper_code(api)) + + +if __name__ == '__main__': + subprocess_main(globals()) diff --git a/modules/gdnative/include/gdnative/array.h b/modules/gdnative/include/gdnative/array.h index 1e66d133b9..876b8f8e8f 100644 --- a/modules/gdnative/include/gdnative/array.h +++ b/modules/gdnative/include/gdnative/array.h @@ -130,6 +130,14 @@ godot_int GDAPI godot_array_bsearch_custom(godot_array *p_self, const godot_vari void GDAPI godot_array_destroy(godot_array *p_self); +godot_array GDAPI godot_array_duplicate(const godot_array *p_self, const godot_bool p_deep); + +godot_variant GDAPI godot_array_max(const godot_array *p_self); + +godot_variant GDAPI godot_array_min(const godot_array *p_self); + +void GDAPI godot_array_shuffle(godot_array *p_self); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/gdnative/basis.h b/modules/gdnative/include/gdnative/basis.h index 53e950b4a2..6128bf3ac3 100644 --- a/modules/gdnative/include/gdnative/basis.h +++ b/modules/gdnative/include/gdnative/basis.h @@ -62,6 +62,7 @@ extern "C" { void GDAPI godot_basis_new_with_rows(godot_basis *r_dest, const godot_vector3 *p_x_axis, const godot_vector3 *p_y_axis, const godot_vector3 *p_z_axis); void GDAPI godot_basis_new_with_axis_and_angle(godot_basis *r_dest, const godot_vector3 *p_axis, const godot_real p_phi); void GDAPI godot_basis_new_with_euler(godot_basis *r_dest, const godot_vector3 *p_euler); +void GDAPI godot_basis_new_with_euler_quat(godot_basis *r_dest, const godot_quat *p_euler); godot_string GDAPI godot_basis_as_string(const godot_basis *p_self); @@ -81,6 +82,16 @@ godot_vector3 GDAPI godot_basis_get_scale(const godot_basis *p_self); godot_vector3 GDAPI godot_basis_get_euler(const godot_basis *p_self); +godot_quat GDAPI godot_basis_get_quat(const godot_basis *p_self); + +void GDAPI godot_basis_set_quat(godot_basis *p_self, const godot_quat *p_quat); + +void GDAPI godot_basis_set_axis_angle_scale(godot_basis *p_self, const godot_vector3 *p_axis, godot_real p_phi, const godot_vector3 *p_scale); + +void GDAPI godot_basis_set_euler_scale(godot_basis *p_self, const godot_vector3 *p_euler, const godot_vector3 *p_scale); + +void GDAPI godot_basis_set_quat_scale(godot_basis *p_self, const godot_quat *p_quat, const godot_vector3 *p_scale); + godot_real GDAPI godot_basis_tdotx(const godot_basis *p_self, const godot_vector3 *p_with); godot_real GDAPI godot_basis_tdoty(const godot_basis *p_self, const godot_vector3 *p_with); @@ -95,8 +106,6 @@ godot_int GDAPI godot_basis_get_orthogonal_index(const godot_basis *p_self); void GDAPI godot_basis_new(godot_basis *r_dest); -void GDAPI godot_basis_new_with_euler_quat(godot_basis *r_dest, const godot_quat *p_euler); - // p_elements is a pointer to an array of 3 (!!) vector3 void GDAPI godot_basis_get_elements(const godot_basis *p_self, godot_vector3 *p_elements); @@ -118,6 +127,8 @@ godot_basis GDAPI godot_basis_operator_multiply_vector(const godot_basis *p_self godot_basis GDAPI godot_basis_operator_multiply_scalar(const godot_basis *p_self, const godot_real p_b); +godot_basis GDAPI godot_basis_slerp(const godot_basis *p_self, const godot_basis *p_b, const godot_real p_t); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/gdnative/color.h b/modules/gdnative/include/gdnative/color.h index 1f0ac8354d..3007dbc6e3 100644 --- a/modules/gdnative/include/gdnative/color.h +++ b/modules/gdnative/include/gdnative/color.h @@ -81,6 +81,14 @@ godot_string GDAPI godot_color_as_string(const godot_color *p_self); godot_int GDAPI godot_color_to_rgba32(const godot_color *p_self); +godot_int GDAPI godot_color_to_abgr32(const godot_color *p_self); + +godot_int GDAPI godot_color_to_abgr64(const godot_color *p_self); + +godot_int GDAPI godot_color_to_argb64(const godot_color *p_self); + +godot_int GDAPI godot_color_to_rgba64(const godot_color *p_self); + godot_int GDAPI godot_color_to_argb32(const godot_color *p_self); godot_real GDAPI godot_color_gray(const godot_color *p_self); @@ -93,6 +101,12 @@ godot_color GDAPI godot_color_linear_interpolate(const godot_color *p_self, cons godot_color GDAPI godot_color_blend(const godot_color *p_self, const godot_color *p_over); +godot_color GDAPI godot_color_darkened(const godot_color *p_self, const godot_real p_amount); + +godot_color GDAPI godot_color_from_hsv(const godot_color *p_self, const godot_real p_h, const godot_real p_s, const godot_real p_v, const godot_real p_a); + +godot_color GDAPI godot_color_lightened(const godot_color *p_self, const godot_real p_amount); + godot_string GDAPI godot_color_to_html(const godot_color *p_self, const godot_bool p_with_alpha); godot_bool GDAPI godot_color_operator_equal(const godot_color *p_self, const godot_color *p_b); diff --git a/modules/gdnative/include/gdnative/dictionary.h b/modules/gdnative/include/gdnative/dictionary.h index a86d60dc72..faace818ee 100644 --- a/modules/gdnative/include/gdnative/dictionary.h +++ b/modules/gdnative/include/gdnative/dictionary.h @@ -94,6 +94,8 @@ godot_bool GDAPI godot_dictionary_operator_equal(const godot_dictionary *p_self, godot_string GDAPI godot_dictionary_to_json(const godot_dictionary *p_self); +godot_bool GDAPI godot_dictionary_erase_with_return(godot_dictionary *p_self, const godot_variant *p_key); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/gdnative/gdnative.h b/modules/gdnative/include/gdnative/gdnative.h index 4cf6e99b06..796ced84f4 100644 --- a/modules/gdnative/include/gdnative/gdnative.h +++ b/modules/gdnative/include/gdnative/gdnative.h @@ -35,7 +35,7 @@ extern "C" { #endif -#ifdef _WIN32 +#if defined(_WIN32) || defined(__ANDROID__) #define GDCALLINGCONV #define GDAPI GDCALLINGCONV #elif defined(__APPLE__) @@ -47,7 +47,7 @@ extern "C" { #define GDCALLINGCONV __attribute__((sysv_abi)) #define GDAPI GDCALLINGCONV #endif -#else +#else // !_WIN32 && !__APPLE__ #define GDCALLINGCONV __attribute__((sysv_abi)) #define GDAPI GDCALLINGCONV #endif @@ -282,6 +282,10 @@ void GDAPI godot_print_error(const char *p_description, const char *p_function, void GDAPI godot_print_warning(const char *p_description, const char *p_function, const char *p_file, int p_line); void GDAPI godot_print(const godot_string *p_message); +// GDNATIVE CORE 1.0.1 + +bool GDAPI godot_is_instance_valid(const godot_object *p_object); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/gdnative/node_path.h b/modules/gdnative/include/gdnative/node_path.h index 2b55e01d13..48fe5b4d3d 100644 --- a/modules/gdnative/include/gdnative/node_path.h +++ b/modules/gdnative/include/gdnative/node_path.h @@ -80,6 +80,8 @@ godot_bool GDAPI godot_node_path_is_empty(const godot_node_path *p_self); godot_bool GDAPI godot_node_path_operator_equal(const godot_node_path *p_self, const godot_node_path *p_b); +godot_node_path godot_node_path_get_as_property_path(const godot_node_path *p_self); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/gdnative/quat.h b/modules/gdnative/include/gdnative/quat.h index 4e86960aaf..634f486e66 100644 --- a/modules/gdnative/include/gdnative/quat.h +++ b/modules/gdnative/include/gdnative/quat.h @@ -60,6 +60,8 @@ extern "C" { void GDAPI godot_quat_new(godot_quat *r_dest, const godot_real p_x, const godot_real p_y, const godot_real p_z, const godot_real p_w); void GDAPI godot_quat_new_with_axis_angle(godot_quat *r_dest, const godot_vector3 *p_axis, const godot_real p_angle); +void GDAPI godot_quat_new_with_basis(godot_quat *r_dest, const godot_basis *p_basis); +void GDAPI godot_quat_new_with_euler(godot_quat *r_dest, const godot_vector3 *p_euler); godot_real GDAPI godot_quat_get_x(const godot_quat *p_self); void GDAPI godot_quat_set_x(godot_quat *p_self, const godot_real val); @@ -107,6 +109,8 @@ godot_bool GDAPI godot_quat_operator_equal(const godot_quat *p_self, const godot godot_quat GDAPI godot_quat_operator_neg(const godot_quat *p_self); +void GDAPI godot_quat_set_axis_angle(godot_quat *p_self, const godot_vector3 *p_axis, const godot_real p_angle); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/gdnative/rect2.h b/modules/gdnative/include/gdnative/rect2.h index 4adcb73e3d..47c15c80bd 100644 --- a/modules/gdnative/include/gdnative/rect2.h +++ b/modules/gdnative/include/gdnative/rect2.h @@ -77,6 +77,12 @@ godot_bool GDAPI godot_rect2_has_point(const godot_rect2 *p_self, const godot_ve godot_rect2 GDAPI godot_rect2_grow(const godot_rect2 *p_self, const godot_real p_by); +godot_rect2 GDAPI godot_rect2_grow_individual(const godot_rect2 *p_self, const godot_real p_left, const godot_real p_top, const godot_real p_right, const godot_real p_bottom); + +godot_rect2 GDAPI godot_rect2_grow_margin(const godot_rect2 *p_self, const godot_int p_margin, const godot_real p_by); + +godot_rect2 GDAPI godot_rect2_abs(const godot_rect2 *p_self); + godot_rect2 GDAPI godot_rect2_expand(const godot_rect2 *p_self, const godot_vector2 *p_to); godot_bool GDAPI godot_rect2_operator_equal(const godot_rect2 *p_self, const godot_rect2 *p_b); diff --git a/modules/gdnative/include/gdnative/string.h b/modules/gdnative/include/gdnative/string.h index 73245160c1..95ae42a9ec 100644 --- a/modules/gdnative/include/gdnative/string.h +++ b/modules/gdnative/include/gdnative/string.h @@ -246,6 +246,12 @@ godot_bool GDAPI godot_string_is_valid_identifier(const godot_string *p_self); godot_bool GDAPI godot_string_is_valid_integer(const godot_string *p_self); godot_bool GDAPI godot_string_is_valid_ip_address(const godot_string *p_self); +godot_string GDAPI godot_string_dedent(const godot_string *p_self); +godot_string GDAPI godot_string_trim_prefix(const godot_string *p_self, const godot_string *p_prefix); +godot_string GDAPI godot_string_trim_suffix(const godot_string *p_self, const godot_string *p_suffix); +godot_string GDAPI godot_string_rstrip(const godot_string *p_self, const godot_string *p_chars); +godot_pool_string_array GDAPI godot_string_rsplit(const godot_string *p_self, const godot_string *p_divisor, const godot_bool p_allow_empty, const godot_int p_maxsplit); + void GDAPI godot_string_destroy(godot_string *p_self); #ifdef __cplusplus diff --git a/modules/gdnative/include/gdnative/transform.h b/modules/gdnative/include/gdnative/transform.h index a646da146a..880f21c88a 100644 --- a/modules/gdnative/include/gdnative/transform.h +++ b/modules/gdnative/include/gdnative/transform.h @@ -62,6 +62,7 @@ extern "C" { void GDAPI godot_transform_new_with_axis_origin(godot_transform *r_dest, const godot_vector3 *p_x_axis, const godot_vector3 *p_y_axis, const godot_vector3 *p_z_axis, const godot_vector3 *p_origin); void GDAPI godot_transform_new(godot_transform *r_dest, const godot_basis *p_basis, const godot_vector3 *p_origin); +void GDAPI godot_transform_new_with_quat(godot_transform *r_dest, const godot_quat *p_quat); godot_basis GDAPI godot_transform_get_basis(const godot_transform *p_self); void GDAPI godot_transform_set_basis(godot_transform *p_self, const godot_basis *p_v); diff --git a/modules/gdnative/include/gdnative/variant.h b/modules/gdnative/include/gdnative/variant.h index 6779dc4092..5e71aa9f11 100644 --- a/modules/gdnative/include/gdnative/variant.h +++ b/modules/gdnative/include/gdnative/variant.h @@ -100,6 +100,45 @@ typedef struct godot_variant_call_error { godot_variant_type expected; } godot_variant_call_error; +typedef enum godot_variant_operator { + // comparison + GODOT_VARIANT_OP_EQUAL, + GODOT_VARIANT_OP_NOT_EQUAL, + GODOT_VARIANT_OP_LESS, + GODOT_VARIANT_OP_LESS_EQUAL, + GODOT_VARIANT_OP_GREATER, + GODOT_VARIANT_OP_GREATER_EQUAL, + + // mathematic + GODOT_VARIANT_OP_ADD, + GODOT_VARIANT_OP_SUBTRACT, + GODOT_VARIANT_OP_MULTIPLY, + GODOT_VARIANT_OP_DIVIDE, + GODOT_VARIANT_OP_NEGATE, + GODOT_VARIANT_OP_POSITIVE, + GODOT_VARIANT_OP_MODULE, + GODOT_VARIANT_OP_STRING_CONCAT, + + // bitwise + GODOT_VARIANT_OP_SHIFT_LEFT, + GODOT_VARIANT_OP_SHIFT_RIGHT, + GODOT_VARIANT_OP_BIT_AND, + GODOT_VARIANT_OP_BIT_OR, + GODOT_VARIANT_OP_BIT_XOR, + GODOT_VARIANT_OP_BIT_NEGATE, + + // logic + GODOT_VARIANT_OP_AND, + GODOT_VARIANT_OP_OR, + GODOT_VARIANT_OP_XOR, + GODOT_VARIANT_OP_NOT, + + // containment + GODOT_VARIANT_OP_IN, + + GODOT_VARIANT_OP_MAX, +} godot_variant_operator; + // reduce extern "C" nesting for VS2013 #ifdef __cplusplus } @@ -204,6 +243,11 @@ godot_bool GDAPI godot_variant_booleanize(const godot_variant *p_self); void GDAPI godot_variant_destroy(godot_variant *p_self); +// GDNative core 1.1 + +godot_string GDAPI godot_variant_get_operator_name(godot_variant_operator p_op); +void GDAPI godot_variant_evaluate(godot_variant_operator p_op, const godot_variant *p_a, const godot_variant *p_b, godot_variant *r_ret, godot_bool *r_valid); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index f28ba352ab..ba044117e2 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -40,12 +40,13 @@ extern "C" { typedef enum { GODOT_METHOD_RPC_MODE_DISABLED, GODOT_METHOD_RPC_MODE_REMOTE, - GODOT_METHOD_RPC_MODE_SYNC, GODOT_METHOD_RPC_MODE_MASTER, - GODOT_METHOD_RPC_MODE_SLAVE, + GODOT_METHOD_RPC_MODE_PUPPET, + GODOT_METHOD_RPC_MODE_SLAVE = GODOT_METHOD_RPC_MODE_PUPPET, GODOT_METHOD_RPC_MODE_REMOTESYNC, + GODOT_METHOD_RPC_MODE_SYNC = GODOT_METHOD_RPC_MODE_REMOTESYNC, GODOT_METHOD_RPC_MODE_MASTERSYNC, - GODOT_METHOD_RPC_MODE_SLAVESYNC, + GODOT_METHOD_RPC_MODE_PUPPETSYNC, } godot_method_rpc_mode; typedef enum { @@ -68,6 +69,7 @@ typedef enum { GODOT_PROPERTY_HINT_GLOBAL_DIR, ///< a directort path must be passed GODOT_PROPERTY_HINT_RESOURCE_TYPE, ///< a resource object type GODOT_PROPERTY_HINT_MULTILINE_TEXT, ///< used for string properties that can contain multiple lines + GODOT_PROPERTY_HINT_PLACEHOLDER_TEXT, ///< used to set a placeholder text for string properties GODOT_PROPERTY_HINT_COLOR_NO_ALPHA, ///< used for ignoring alpha component when editing a color GODOT_PROPERTY_HINT_IMAGE_COMPRESS_LOSSY, GODOT_PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS, @@ -228,6 +230,8 @@ const void GDAPI *godot_nativescript_get_type_tag(const godot_object *p_object); typedef struct { GDCALLINGCONV void *(*alloc_instance_binding_data)(void *, const void *, godot_object *); GDCALLINGCONV void (*free_instance_binding_data)(void *, void *); + GDCALLINGCONV void (*refcount_incremented_instance_binding)(void *, godot_object *); + GDCALLINGCONV bool (*refcount_decremented_instance_binding)(void *, godot_object *); void *data; GDCALLINGCONV void (*free_func)(void *); } godot_instance_binding_functions; @@ -237,6 +241,8 @@ void GDAPI godot_nativescript_unregister_instance_binding_data_functions(int p_i void GDAPI *godot_nativescript_get_instance_binding_data(int p_idx, godot_object *p_object); +void GDAPI godot_nativescript_profiling_add_data(const char *p_signature, uint64_t p_time); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/nativescript/SCsub b/modules/gdnative/nativescript/SCsub index ee3b9c351d..5841ad5531 100644 --- a/modules/gdnative/nativescript/SCsub +++ b/modules/gdnative/nativescript/SCsub @@ -1,12 +1,10 @@ #!/usr/bin/env python Import('env') +Import('env_gdnative') -mod_env = env.Clone() -mod_env.add_source_files(env.modules_sources, "*.cpp") -mod_env.Append(CPPFLAGS=['-DGDAPI_BUILT_IN']) +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"]) - -Export('mod_env') diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp index 4012e821bb..8c6dace847 100644 --- a/modules/gdnative/nativescript/api_generator.cpp +++ b/modules/gdnative/nativescript/api_generator.cpp @@ -35,8 +35,8 @@ #include "core/class_db.h" #include "core/engine.h" #include "core/global_constants.h" +#include "core/os/file_access.h" #include "core/pair.h" -#include "os/file_access.h" // helper stuff @@ -110,7 +110,6 @@ struct ClassAPI { bool is_singleton; bool is_instanciable; // @Unclear - bool is_creatable; bool is_reference; List<MethodAPI> methods; @@ -293,6 +292,7 @@ List<ClassAPI> generate_c_api_classes() { method_api.has_varargs = method_bind && method_bind->is_vararg(); // Method flags + method_api.is_virtual = false; if (method_info.flags) { const uint32_t flags = method_info.flags; method_api.is_editor = flags & METHOD_FLAG_EDITOR; @@ -385,7 +385,6 @@ static List<String> generate_c_api_json(const List<ClassAPI> &p_api) { source.push_back(String("\t\t\"instanciable\": ") + (api.is_instanciable ? "true" : "false") + ",\n"); source.push_back(String("\t\t\"is_reference\": ") + (api.is_reference ? "true" : "false") + ",\n"); // @Unclear - // source.push_back(String("\t\t\"createable\": ") + (api.is_creatable ? "true" : "false") + ",\n"); source.push_back("\t\t\"constants\": {\n"); for (List<ConstantAPI>::Element *e = api.constants.front(); e; e = e->next()) { diff --git a/modules/gdnative/nativescript/godot_nativescript.cpp b/modules/gdnative/nativescript/godot_nativescript.cpp index ace2ecac5c..c39efe126f 100644 --- a/modules/gdnative/nativescript/godot_nativescript.cpp +++ b/modules/gdnative/nativescript/godot_nativescript.cpp @@ -30,12 +30,12 @@ #include "nativescript/godot_nativescript.h" -#include "class_db.h" -#include "error_macros.h" +#include "core/class_db.h" +#include "core/error_macros.h" +#include "core/global_constants.h" +#include "core/project_settings.h" +#include "core/variant.h" #include "gdnative/gdnative.h" -#include "global_constants.h" -#include "project_settings.h" -#include "variant.h" #include "nativescript.h" @@ -365,6 +365,10 @@ void GDAPI *godot_nativescript_get_instance_binding_data(int p_idx, godot_object return NativeScriptLanguage::get_singleton()->get_instance_binding_data(p_idx, (Object *)p_object); } +void GDAPI godot_nativescript_profiling_add_data(const char *p_signature, uint64_t p_time) { + NativeScriptLanguage::get_singleton()->profiling_add_data(StringName(p_signature), p_time); +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index 7bab718b81..bcaf3f346e 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -33,10 +33,10 @@ #include "gdnative/gdnative.h" #include "core/global_constants.h" +#include "core/io/file_access_encrypted.h" +#include "core/os/file_access.h" +#include "core/os/os.h" #include "core/project_settings.h" -#include "io/file_access_encrypted.h" -#include "os/file_access.h" -#include "os/os.h" #include "scene/main/scene_tree.h" #include "scene/resources/scene_format_text.h" @@ -44,7 +44,7 @@ #include <stdlib.h> #ifndef NO_THREADS -#include "os/thread.h" +#include "core/os/thread.h" #endif #if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) @@ -62,13 +62,21 @@ void NativeScript::_bind_methods() { ClassDB::bind_method(D_METHOD("set_library", "library"), &NativeScript::set_library); ClassDB::bind_method(D_METHOD("get_library"), &NativeScript::get_library); + ClassDB::bind_method(D_METHOD("set_script_class_name", "class_name"), &NativeScript::set_script_class_name); + ClassDB::bind_method(D_METHOD("get_script_class_name"), &NativeScript::get_script_class_name); + ClassDB::bind_method(D_METHOD("set_script_class_icon_path", "icon_path"), &NativeScript::set_script_class_icon_path); + ClassDB::bind_method(D_METHOD("get_script_class_icon_path"), &NativeScript::get_script_class_icon_path); + ClassDB::bind_method(D_METHOD("get_class_documentation"), &NativeScript::get_class_documentation); ClassDB::bind_method(D_METHOD("get_method_documentation", "method"), &NativeScript::get_method_documentation); ClassDB::bind_method(D_METHOD("get_signal_documentation", "signal_name"), &NativeScript::get_signal_documentation); ClassDB::bind_method(D_METHOD("get_property_documentation", "path"), &NativeScript::get_property_documentation); - ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "class_name"), "set_class_name", "get_class_name"); - ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "class_name"), "set_class_name", "get_class_name"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library"); + ADD_GROUP("Script Class", "script_class_"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "script_class_name"), "set_script_class_name", "get_script_class_name"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "script_class_icon_path", PROPERTY_HINT_FILE), "set_script_class_icon_path", "get_script_class_icon_path"); ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &NativeScript::_new, MethodInfo(Variant::OBJECT, "new")); } @@ -131,6 +139,22 @@ Ref<GDNativeLibrary> NativeScript::get_library() const { return library; } +void NativeScript::set_script_class_name(String p_type) { + script_class_name = p_type; +} + +String NativeScript::get_script_class_name() const { + return script_class_name; +} + +void NativeScript::set_script_class_icon_path(String p_icon_path) { + script_class_icon_path = p_icon_path; +} + +String NativeScript::get_script_class_icon_path() const { + return script_class_icon_path; +} + bool NativeScript::can_instance() const { NativeScriptDesc *script_data = get_script_desc(); @@ -149,7 +173,10 @@ Ref<Script> NativeScript::get_base_script() const { if (!script_data) return Ref<Script>(); - Ref<NativeScript> ns = Ref<NativeScript>(NSL->create_script()); + NativeScript *script = (NativeScript *)NSL->create_script(); + Ref<NativeScript> ns = Ref<NativeScript>(script); + ERR_FAIL_COND_V(!ns.is_valid(), Ref<Script>()); + ns->set_class_name(script_data->base); ns->set_library(get_library()); return ns; @@ -267,6 +294,10 @@ MethodInfo NativeScript::get_method_info(const StringName &p_method) const { return MethodInfo(); } +bool NativeScript::is_valid() const { + return true; +} + bool NativeScript::is_tool() const { NativeScriptDesc *script_data = get_script_desc(); @@ -553,12 +584,17 @@ bool NativeScriptInstance::set(const StringName &p_name, const Variant &p_value) Variant name = p_name; const Variant *args[2] = { &name, &p_value }; - E->get().method.method((godot_object *)owner, + godot_variant result; + result = E->get().method.method((godot_object *)owner, E->get().method.method_data, userdata, 2, (godot_variant **)args); - return true; + bool handled = *(Variant *)&result; + godot_variant_destroy(&result); + if (handled) { + return true; + } } script_data = script_data->base_data; @@ -593,10 +629,9 @@ bool NativeScriptInstance::get(const StringName &p_name, Variant &r_ret) const { (godot_variant **)args); r_ret = *(Variant *)&result; godot_variant_destroy(&result); - if (r_ret.get_type() == Variant::NIL) { - return false; + if (r_ret.get_type() != Variant::NIL) { + return true; } - return true; } script_data = script_data->base_data; @@ -779,18 +814,16 @@ MultiplayerAPI::RPCMode NativeScriptInstance::get_rpc_mode(const StringName &p_m return MultiplayerAPI::RPC_MODE_DISABLED; case GODOT_METHOD_RPC_MODE_REMOTE: return MultiplayerAPI::RPC_MODE_REMOTE; - case GODOT_METHOD_RPC_MODE_SYNC: - return MultiplayerAPI::RPC_MODE_SYNC; case GODOT_METHOD_RPC_MODE_MASTER: return MultiplayerAPI::RPC_MODE_MASTER; - case GODOT_METHOD_RPC_MODE_SLAVE: - return MultiplayerAPI::RPC_MODE_SLAVE; + case GODOT_METHOD_RPC_MODE_PUPPET: + return MultiplayerAPI::RPC_MODE_PUPPET; case GODOT_METHOD_RPC_MODE_REMOTESYNC: return MultiplayerAPI::RPC_MODE_REMOTESYNC; case GODOT_METHOD_RPC_MODE_MASTERSYNC: return MultiplayerAPI::RPC_MODE_MASTERSYNC; - case GODOT_METHOD_RPC_MODE_SLAVESYNC: - return MultiplayerAPI::RPC_MODE_SLAVESYNC; + case GODOT_METHOD_RPC_MODE_PUPPETSYNC: + return MultiplayerAPI::RPC_MODE_PUPPETSYNC; default: return MultiplayerAPI::RPC_MODE_DISABLED; } @@ -815,12 +848,16 @@ MultiplayerAPI::RPCMode NativeScriptInstance::get_rset_mode(const StringName &p_ return MultiplayerAPI::RPC_MODE_DISABLED; case GODOT_METHOD_RPC_MODE_REMOTE: return MultiplayerAPI::RPC_MODE_REMOTE; - case GODOT_METHOD_RPC_MODE_SYNC: - return MultiplayerAPI::RPC_MODE_SYNC; case GODOT_METHOD_RPC_MODE_MASTER: return MultiplayerAPI::RPC_MODE_MASTER; - case GODOT_METHOD_RPC_MODE_SLAVE: - return MultiplayerAPI::RPC_MODE_SLAVE; + case GODOT_METHOD_RPC_MODE_PUPPET: + return MultiplayerAPI::RPC_MODE_PUPPET; + case GODOT_METHOD_RPC_MODE_REMOTESYNC: + return MultiplayerAPI::RPC_MODE_REMOTESYNC; + case GODOT_METHOD_RPC_MODE_MASTERSYNC: + return MultiplayerAPI::RPC_MODE_MASTERSYNC; + case GODOT_METHOD_RPC_MODE_PUPPETSYNC: + return MultiplayerAPI::RPC_MODE_PUPPETSYNC; default: return MultiplayerAPI::RPC_MODE_DISABLED; } @@ -979,6 +1016,20 @@ NativeScriptLanguage::NativeScriptLanguage() { has_objects_to_register = false; mutex = Mutex::create(); #endif + +#ifdef DEBUG_ENABLED + profiling = false; +#endif + + _init_call_type = "nativescript_init"; + _init_call_name = "nativescript_init"; + _terminate_call_name = "nativescript_terminate"; + _noarg_call_type = "nativescript_no_arg"; + _frame_call_name = "nativescript_frame"; +#ifndef NO_THREADS + _thread_enter_call_name = "nativescript_thread_enter"; + _thread_exit_call_name = "nativescript_thread_exit"; +#endif } NativeScriptLanguage::~NativeScriptLanguage() { @@ -1053,7 +1104,7 @@ Ref<Script> NativeScriptLanguage::get_template(const String &p_class_name, const s->set_class_name(p_class_name); return Ref<NativeScript>(s); } -bool NativeScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { +bool NativeScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { return true; } @@ -1121,17 +1172,105 @@ void NativeScriptLanguage::get_public_constants(List<Pair<String, Variant> > *p_ } void NativeScriptLanguage::profiling_start() { +#ifdef DEBUG_ENABLED +#ifndef NO_THREADS + MutexLock lock(mutex); +#endif + + profile_data.clear(); + profiling = true; +#endif } void NativeScriptLanguage::profiling_stop() { +#ifdef DEBUG_ENABLED +#ifndef NO_THREADS + MutexLock lock(mutex); +#endif + + profiling = false; +#endif } int NativeScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) { +#ifdef DEBUG_ENABLED +#ifndef NO_THREADS + MutexLock lock(mutex); +#endif + int current = 0; + + for (Map<StringName, ProfileData>::Element *d = profile_data.front(); d; d = d->next()) { + if (current >= p_info_max) + break; + + p_info_arr[current].call_count = d->get().call_count; + p_info_arr[current].self_time = d->get().self_time; + p_info_arr[current].total_time = d->get().total_time; + p_info_arr[current].signature = d->get().signature; + current++; + } + + return current; +#else return 0; +#endif } int NativeScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) { +#ifdef DEBUG_ENABLED +#ifndef NO_THREADS + MutexLock lock(mutex); +#endif + int current = 0; + + for (Map<StringName, ProfileData>::Element *d = profile_data.front(); d; d = d->next()) { + if (current >= p_info_max) + break; + + if (d->get().last_frame_call_count) { + p_info_arr[current].call_count = d->get().last_frame_call_count; + p_info_arr[current].self_time = d->get().last_frame_self_time; + p_info_arr[current].total_time = d->get().last_frame_total_time; + p_info_arr[current].signature = d->get().signature; + current++; + } + } + + return current; +#else return 0; +#endif +} + +void NativeScriptLanguage::profiling_add_data(StringName p_signature, uint64_t p_time) { +#ifdef DEBUG_ENABLED +#ifndef NO_THREADS + MutexLock lock(mutex); +#endif + + Map<StringName, ProfileData>::Element *d = profile_data.find(p_signature); + if (d) { + d->get().call_count += 1; + d->get().total_time += p_time; + d->get().frame_call_count += 1; + d->get().frame_total_time += p_time; + } else { + ProfileData data; + + data.signature = p_signature; + data.call_count = 1; + data.self_time = 0; + data.total_time = p_time; + data.frame_call_count = 1; + data.frame_self_time = 0; + data.frame_total_time = p_time; + data.last_frame_call_count = 0; + data.last_frame_self_time = 0; + data.last_frame_total_time = 0; + + profile_data.insert(p_signature, data); + } +#endif } int NativeScriptLanguage::register_binding_functions(godot_instance_binding_functions p_binding_functions) { @@ -1154,8 +1293,8 @@ int NativeScriptLanguage::register_binding_functions(godot_instance_binding_func } // set the functions - binding_functions[idx].first = true; - binding_functions[idx].second = p_binding_functions; + binding_functions.write[idx].first = true; + binding_functions.write[idx].second = p_binding_functions; return idx; } @@ -1170,7 +1309,7 @@ void NativeScriptLanguage::unregister_binding_functions(int p_idx) { binding_functions[p_idx].second.free_instance_binding_data(binding_functions[p_idx].second.data, binding_data[p_idx]); } - binding_functions[p_idx].first = false; + binding_functions.write[p_idx].first = false; if (binding_functions[p_idx].second.free_func) binding_functions[p_idx].second.free_func(binding_functions[p_idx].second.data); @@ -1196,7 +1335,7 @@ void *NativeScriptLanguage::get_instance_binding_data(int p_idx, Object *p_objec binding_data->resize(p_idx + 1); for (int i = old_size; i <= p_idx; i++) { - (*binding_data)[i] = NULL; + (*binding_data).write[i] = NULL; } } @@ -1205,7 +1344,7 @@ void *NativeScriptLanguage::get_instance_binding_data(int p_idx, Object *p_objec const void *global_type_tag = global_type_tags[p_idx].get(p_object->get_class_name()); // no binding data yet, soooooo alloc new one \o/ - (*binding_data)[p_idx] = binding_functions[p_idx].second.alloc_instance_binding_data(binding_functions[p_idx].second.data, global_type_tag, (godot_object *)p_object); + (*binding_data).write[p_idx] = binding_functions[p_idx].second.alloc_instance_binding_data(binding_functions[p_idx].second.data, global_type_tag, (godot_object *)p_object); } return (*binding_data)[p_idx]; @@ -1218,7 +1357,7 @@ void *NativeScriptLanguage::alloc_instance_binding_data(Object *p_object) { binding_data->resize(binding_functions.size()); for (int i = 0; i < binding_functions.size(); i++) { - (*binding_data)[i] = NULL; + (*binding_data).write[i] = NULL; } binding_instances.insert(binding_data); @@ -1247,6 +1386,54 @@ void NativeScriptLanguage::free_instance_binding_data(void *p_data) { delete &binding_data; } +void NativeScriptLanguage::refcount_incremented_instance_binding(Object *p_object) { + + void *data = p_object->get_script_instance_binding(lang_idx); + + if (!data) + return; + + Vector<void *> &binding_data = *(Vector<void *> *)data; + + for (int i = 0; i < binding_data.size(); i++) { + if (!binding_data[i]) + continue; + + if (!binding_functions[i].first) + continue; + + if (binding_functions[i].second.refcount_incremented_instance_binding) { + binding_functions[i].second.refcount_incremented_instance_binding(binding_data[i], p_object); + } + } +} + +bool NativeScriptLanguage::refcount_decremented_instance_binding(Object *p_object) { + + void *data = p_object->get_script_instance_binding(lang_idx); + + if (!data) + return true; + + Vector<void *> &binding_data = *(Vector<void *> *)data; + + bool can_die = true; + + for (int i = 0; i < binding_data.size(); i++) { + if (!binding_data[i]) + continue; + + if (!binding_functions[i].first) + continue; + + if (binding_functions[i].second.refcount_decremented_instance_binding) { + can_die = can_die && binding_functions[i].second.refcount_decremented_instance_binding(binding_data[i], p_object); + } + } + + return can_die; +} + void NativeScriptLanguage::set_global_type_tag(int p_idx, StringName p_class_name, const void *p_type_tag) { if (!global_type_tags.has(p_idx)) { global_type_tags.insert(p_idx, HashMap<StringName, const void *>()); @@ -1374,6 +1561,24 @@ void NativeScriptLanguage::frame() { has_objects_to_register = false; } #endif + +#ifdef DEBUG_ENABLED + { +#ifndef NO_THREADS + MutexLock lock(mutex); +#endif + + for (Map<StringName, ProfileData>::Element *d = profile_data.front(); d; d = d->next()) { + d->get().last_frame_call_count = d->get().frame_call_count; + d->get().last_frame_self_time = d->get().frame_self_time; + d->get().last_frame_total_time = d->get().frame_total_time; + d->get().frame_call_count = 0; + d->get().frame_self_time = 0; + d->get().frame_total_time = 0; + } + } +#endif + call_libraries_cb(_frame_call_name); } @@ -1389,6 +1594,26 @@ void NativeScriptLanguage::thread_exit() { #endif // NO_THREADS +bool NativeScriptLanguage::handles_global_class_type(const String &p_type) const { + return p_type == "NativeScript"; +} + +String NativeScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { + Ref<NativeScript> script = ResourceLoader::load(p_path, "NativeScript"); + if (script.is_valid()) { + if (r_base_type) + *r_base_type = script->get_instance_base_type(); + if (r_icon_path) + *r_icon_path = script->get_script_class_icon_path(); + return script->get_script_class_name(); + } + if (r_base_type) + *r_base_type = String(); + if (r_icon_path) + *r_icon_path = String(); + return String(); +} + void NativeReloadNode::_bind_methods() { ClassDB::bind_method(D_METHOD("_notification"), &NativeReloadNode::_notification); } @@ -1490,8 +1715,7 @@ void NativeReloadNode::_notification(int p_what) { } RES ResourceFormatLoaderNativeScript::load(const String &p_path, const String &p_original_path, Error *r_error) { - ResourceFormatLoaderText rsflt; - return rsflt.load(p_path, p_original_path, r_error); + return ResourceFormatLoaderText::singleton->load(p_path, p_original_path, r_error); } void ResourceFormatLoaderNativeScript::get_recognized_extensions(List<String> *p_extensions) const { diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index be093dde4b..e6f3c06ee5 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -31,21 +31,21 @@ #ifndef NATIVE_SCRIPT_H #define NATIVE_SCRIPT_H +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#include "core/oa_hash_map.h" +#include "core/ordered_hash_map.h" +#include "core/os/thread_safe.h" #include "core/resource.h" #include "core/script_language.h" #include "core/self_list.h" -#include "io/resource_loader.h" -#include "io/resource_saver.h" -#include "oa_hash_map.h" -#include "ordered_hash_map.h" -#include "os/thread_safe.h" #include "scene/main/node.h" #include "modules/gdnative/gdnative.h" #include <nativescript/godot_nativescript.h> #ifndef NO_THREADS -#include "os/mutex.h" +#include "core/os/mutex.h" #endif struct NativeScriptDesc { @@ -70,8 +70,6 @@ struct NativeScriptDesc { String documentation; }; - String documentation; - Map<StringName, Method> methods; OrderedHashMap<StringName, Property> properties; Map<StringName, Signal> signals_; // QtCreator doesn't like the name signals @@ -81,6 +79,8 @@ struct NativeScriptDesc { godot_instance_create_func create_func; godot_instance_destroy_func destroy_func; + String documentation; + const void *type_tag; bool is_tool; @@ -118,6 +118,9 @@ class NativeScript : public Script { String class_name; + String script_class_name; + String script_class_icon_path; + #ifndef NO_THREADS Mutex *owners_lock; #endif @@ -135,6 +138,11 @@ public: void set_library(Ref<GDNativeLibrary> p_library); Ref<GDNativeLibrary> get_library() const; + void set_script_class_name(String p_type); + String get_script_class_name() const; + void set_script_class_icon_path(String p_icon_path); + String get_script_class_icon_path() const; + virtual bool can_instance() const; virtual Ref<Script> get_base_script() const; //for script inheritance @@ -152,6 +160,7 @@ public: virtual MethodInfo get_method_info(const StringName &p_method) const; virtual bool is_tool() const; + virtual bool is_valid() const; virtual ScriptLanguage *get_language() const; @@ -246,6 +255,22 @@ private: Map<int, HashMap<StringName, const void *> > global_type_tags; + struct ProfileData { + StringName signature; + uint64_t call_count; + uint64_t self_time; + uint64_t total_time; + uint64_t frame_call_count; + uint64_t frame_self_time; + uint64_t frame_total_time; + uint64_t last_frame_call_count; + uint64_t last_frame_self_time; + uint64_t last_frame_total_time; + }; + + Map<StringName, ProfileData> profile_data; + bool profiling; + public: // These two maps must only be touched on the main thread Map<String, Map<StringName, NativeScriptDesc> > library_classes; @@ -253,18 +278,14 @@ public: Map<String, Set<NativeScript *> > library_script_users; - const StringName _init_call_type = "nativescript_init"; - const StringName _init_call_name = "nativescript_init"; - - const StringName _terminate_call_name = "nativescript_terminate"; - - const StringName _noarg_call_type = "nativescript_no_arg"; - - const StringName _frame_call_name = "nativescript_frame"; - + StringName _init_call_type; + StringName _init_call_name; + StringName _terminate_call_name; + StringName _noarg_call_type; + StringName _frame_call_name; #ifndef NO_THREADS - const StringName _thread_enter_call_name = "nativescript_thread_enter"; - const StringName _thread_exit_call_name = "nativescript_thread_exit"; + StringName _thread_enter_call_name; + StringName _thread_exit_call_name; #endif NativeScriptLanguage(); @@ -295,7 +316,7 @@ public: virtual void get_comment_delimiters(List<String> *p_delimiters) const; virtual void get_string_delimiters(List<String> *p_delimiters) const; virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const; + virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings = NULL, Set<int> *r_safe_lines = NULL) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; @@ -329,9 +350,16 @@ public: virtual void *alloc_instance_binding_data(Object *p_object); virtual void free_instance_binding_data(void *p_data); + virtual void refcount_incremented_instance_binding(Object *p_object); + virtual bool refcount_decremented_instance_binding(Object *p_object); void set_global_type_tag(int p_idx, StringName p_class_name, const void *p_type_tag); const void *get_global_type_tag(int p_idx, StringName p_class_name) const; + + virtual bool handles_global_class_type(const String &p_type) const; + virtual String get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const; + + void profiling_add_data(StringName p_signature, uint64_t p_time); }; inline NativeScriptDesc *NativeScript::get_script_desc() const { @@ -341,11 +369,14 @@ inline NativeScriptDesc *NativeScript::get_script_desc() const { class NativeReloadNode : public Node { GDCLASS(NativeReloadNode, Node) - bool unloaded = false; + bool unloaded; public: static void _bind_methods(); void _notification(int p_what); + + NativeReloadNode() : + unloaded(false) {} }; class ResourceFormatLoaderNativeScript : public ResourceFormatLoader { diff --git a/modules/gdnative/nativescript/register_types.cpp b/modules/gdnative/nativescript/register_types.cpp index 9a0e764391..4433c0a638 100644 --- a/modules/gdnative/nativescript/register_types.cpp +++ b/modules/gdnative/nativescript/register_types.cpp @@ -30,8 +30,8 @@ #include "register_types.h" -#include "io/resource_loader.h" -#include "io/resource_saver.h" +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" #include "nativescript.h" diff --git a/modules/gdnative/net/SCsub b/modules/gdnative/net/SCsub index 53f9271128..e915703935 100644 --- a/modules/gdnative/net/SCsub +++ b/modules/gdnative/net/SCsub @@ -1,12 +1,7 @@ #!/usr/bin/env python -import os -import methods - Import('env') -Import('env_modules') +Import('env_gdnative') -env_net_gdnative = env_modules.Clone() +env_gdnative.add_source_files(env.modules_sources, '*.cpp') -env_net_gdnative.Append(CPPPATH=['#modules/gdnative/include/']) -env_net_gdnative.add_source_files(env.modules_sources, '*.cpp') diff --git a/modules/gdnative/pluginscript/SCsub b/modules/gdnative/pluginscript/SCsub index 2031a4236b..20eaa99592 100644 --- a/modules/gdnative/pluginscript/SCsub +++ b/modules/gdnative/pluginscript/SCsub @@ -1,9 +1,6 @@ #!/usr/bin/env python Import('env') -Import('env_modules') +Import('env_gdnative') -env_pluginscript = env_modules.Clone() - -env_pluginscript.Append(CPPPATH=['#modules/gdnative/include/']) -env_pluginscript.add_source_files(env.modules_sources, '*.cpp') +env_gdnative.add_source_files(env.modules_sources, '*.cpp') diff --git a/modules/gdnative/pluginscript/pluginscript_language.cpp b/modules/gdnative/pluginscript/pluginscript_language.cpp index 8018178bd5..2b538c4a36 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.cpp +++ b/modules/gdnative/pluginscript/pluginscript_language.cpp @@ -108,7 +108,7 @@ Ref<Script> PluginScriptLanguage::get_template(const String &p_class_name, const return script; } -bool PluginScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { +bool PluginScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { PoolStringArray functions; if (_desc.validate) { bool ret = _desc.validate( diff --git a/modules/gdnative/pluginscript/pluginscript_language.h b/modules/gdnative/pluginscript/pluginscript_language.h index 709345885b..c4df6f3a33 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.h +++ b/modules/gdnative/pluginscript/pluginscript_language.h @@ -74,7 +74,7 @@ public: virtual void get_comment_delimiters(List<String> *p_delimiters) const; virtual void get_string_delimiters(List<String> *p_delimiters) const; virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const; + virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, List<ScriptLanguage::Warning> *r_warnings = NULL, Set<int> *r_safe_lines = NULL) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; diff --git a/modules/gdnative/pluginscript/pluginscript_loader.cpp b/modules/gdnative/pluginscript/pluginscript_loader.cpp index acba297fa0..52d5b2b595 100644 --- a/modules/gdnative/pluginscript/pluginscript_loader.cpp +++ b/modules/gdnative/pluginscript/pluginscript_loader.cpp @@ -29,7 +29,7 @@ /*************************************************************************/ // Godot imports -#include "os/file_access.h" +#include "core/os/file_access.h" // Pythonscript imports #include "pluginscript_language.h" #include "pluginscript_loader.h" diff --git a/modules/gdnative/pluginscript/pluginscript_loader.h b/modules/gdnative/pluginscript/pluginscript_loader.h index 9276ea3ef9..5c17bb932e 100644 --- a/modules/gdnative/pluginscript/pluginscript_loader.h +++ b/modules/gdnative/pluginscript/pluginscript_loader.h @@ -32,9 +32,9 @@ #define PYTHONSCRIPT_PY_LOADER_H // Godot imports +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" #include "core/script_language.h" -#include "io/resource_loader.h" -#include "io/resource_saver.h" class PluginScriptLanguage; diff --git a/modules/gdnative/pluginscript/pluginscript_script.h b/modules/gdnative/pluginscript/pluginscript_script.h index 31c6c4d67f..3ade8ac004 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.h +++ b/modules/gdnative/pluginscript/pluginscript_script.h @@ -102,6 +102,7 @@ public: PropertyInfo get_property_info(const StringName &p_property) const; bool is_tool() const { return _tool; } + bool is_valid() const { return true; } virtual ScriptLanguage *get_language() const; diff --git a/modules/gdnative/pluginscript/register_types.cpp b/modules/gdnative/pluginscript/register_types.cpp index 924abf29df..e677bc9867 100644 --- a/modules/gdnative/pluginscript/register_types.cpp +++ b/modules/gdnative/pluginscript/register_types.cpp @@ -30,11 +30,11 @@ #include "register_types.h" +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#include "core/os/dir_access.h" +#include "core/os/os.h" #include "core/project_settings.h" -#include "io/resource_loader.h" -#include "io/resource_saver.h" -#include "os/dir_access.h" -#include "os/os.h" #include "scene/main/scene_tree.h" #include "pluginscript_language.h" diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index d18297f2f8..62e87c3651 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -29,19 +29,19 @@ /*************************************************************************/ #include "register_types.h" + #include "gdnative/gdnative.h" #include "gdnative.h" -#include "io/resource_loader.h" -#include "io/resource_saver.h" - #include "arvr/register_types.h" #include "nativescript/register_types.h" #include "net/register_types.h" #include "pluginscript/register_types.h" #include "core/engine.h" +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" #include "core/os/os.h" #include "core/project_settings.h" @@ -148,7 +148,7 @@ protected: }; struct LibrarySymbol { - char *name; + const char *name; bool is_required; }; @@ -239,7 +239,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" "extern void add_ios_init_callback(void (*cb)());\n"; String linker_flags = ""; - for (int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { + for (unsigned int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; String code = declare_pattern.replace("$name", full_name); code = code.replace("$weak", expected_symbols[i].is_required ? "" : " __attribute__((weak))"); @@ -255,7 +255,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty additional_code += String("void $prefixinit() {\n").replace("$prefix", lib->get_symbol_prefix()); String register_pattern = " if (&$name) register_dynamic_symbol((char *)\"$name\", (void *)$name);\n"; - for (int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { + for (unsigned int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; additional_code += register_pattern.replace("$name", full_name); } @@ -341,10 +341,10 @@ void register_gdnative_types() { Ref<GDNativeLibrary> lib = ResourceLoader::load(path); - singleton_gdnatives[i].instance(); - singleton_gdnatives[i]->set_library(lib); + singleton_gdnatives.write[i].instance(); + singleton_gdnatives.write[i]->set_library(lib); - if (!singleton_gdnatives[i]->initialize()) { + if (!singleton_gdnatives.write[i]->initialize()) { // Can't initialize. Don't make a native_call then continue; } @@ -374,7 +374,7 @@ void unregister_gdnative_types() { continue; } - singleton_gdnatives[i]->terminate(); + singleton_gdnatives.write[i]->terminate(); } singleton_gdnatives.clear(); diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index 73f09f1659..6904154953 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -9,5 +9,3 @@ env_gdscript.add_source_files(env.modules_sources, "*.cpp") if env['tools']: env_gdscript.add_source_files(env.modules_sources, "./editor/*.cpp") - -Export('env') diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 632970f8c0..4cefdbd7cb 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -8,7 +8,7 @@ [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <tutorials> - <link>http://docs.godotengine.org/en/3.0/getting_started/scripting/gdscript/index.html</link> + <link>https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link> </tutorials> <demos> </demos> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index fedc510f01..e4aee842ba 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -43,10 +43,6 @@ static bool _is_text_char(CharType c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; } -static bool _is_whitespace(CharType c) { - return c == '\t' || c == ' '; -} - static bool _is_char(CharType c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; @@ -75,9 +71,12 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ bool in_keyword = false; bool in_word = false; bool in_function_name = false; + bool in_variable_declaration = false; + bool in_function_args = false; bool in_member_variable = false; bool in_node_path = false; bool is_hex_notation = false; + bool expect_type = false; Color keyword_color; Color color; @@ -119,8 +118,8 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ is_hex_notation = false; } - // check for dot or underscore or 'x' for hex notation in floating point number - if ((str[j] == '.' || str[j] == 'x' || str[j] == '_') && !in_word && prev_is_number && !is_number) { + // check for dot or underscore or 'x' for hex notation in floating point number or 'e' for scientific notation + if ((str[j] == '.' || str[j] == 'x' || str[j] == '_' || str[j] == 'e') && !in_word && prev_is_number && !is_number) { is_number = true; is_symbol = false; is_char = false; @@ -205,6 +204,8 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ if (str[k] == '(') { in_function_name = true; + } else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_VAR)) { + in_variable_declaration = true; } } @@ -220,6 +221,37 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ } if (is_symbol) { + + if (in_function_name) { + in_function_args = true; + } + + if (in_function_args && str[j] == ')') { + in_function_args = false; + } + + if (expect_type && prev_is_char) { + expect_type = false; + } + + if (j > 0 && str[j] == '>' && str[j - 1] == '-') { + expect_type = true; + } + + if (in_variable_declaration || in_function_args) { + int k = j; + // Skip space + while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) { + k++; + } + + if (str[k] == ':') { + // has type hint + expect_type = true; + } + } + + in_variable_declaration = false; in_function_name = false; in_member_variable = false; } @@ -256,6 +288,9 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ } else if (is_number) { next_type = NUMBER; color = number_color; + } else if (expect_type) { + next_type = TYPE; + color = type_color; } else { next_type = IDENTIFIER; } @@ -330,6 +365,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { function_definition_color = EDITOR_GET("text_editor/highlighting/gdscript/function_definition_color"); node_path_color = EDITOR_GET("text_editor/highlighting/gdscript/node_path_color"); + type_color = EDITOR_GET("text_editor/highlighting/base_type_color"); } SyntaxHighlighter *GDScriptSyntaxHighlighter::create() { diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index 0296ab7652..b8cb4a65e9 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -44,7 +44,8 @@ private: FUNCTION, KEYWORD, MEMBER, - IDENTIFIER + IDENTIFIER, + TYPE, }; // colours @@ -56,6 +57,7 @@ private: Color number_color; Color member_color; Color node_path_color; + Color type_color; public: static SyntaxHighlighter *create(); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index f23e7854a5..538249c8e2 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -30,13 +30,13 @@ #include "gdscript.h" -#include "engine.h" +#include "core/engine.h" +#include "core/global_constants.h" +#include "core/io/file_access_encrypted.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "core/project_settings.h" #include "gdscript_compiler.h" -#include "global_constants.h" -#include "io/file_access_encrypted.h" -#include "os/file_access.h" -#include "os/os.h" -#include "project_settings.h" /////////////////////////// @@ -181,7 +181,11 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Variant::CallErro bool GDScript::can_instance() const { - return valid || (!tool && !ScriptServer::is_scripting_enabled()); +#ifdef TOOLS_ENABLED + return valid && (tool || ScriptServer::is_scripting_enabled()); +#else + return valid; +#endif } Ref<Script> GDScript::get_base_script() const { @@ -220,16 +224,14 @@ void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { for (const Map<StringName, GDScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) { + GDScriptFunction *func = E->get(); MethodInfo mi; mi.name = E->key(); - for (int i = 0; i < E->get()->get_argument_count(); i++) { - PropertyInfo arg; - arg.type = Variant::NIL; //variant - arg.name = E->get()->get_argument_name(i); - mi.arguments.push_back(arg); + for (int i = 0; i < func->get_argument_count(); i++) { + mi.arguments.push_back(func->get_argument_type(i)); } - mi.return_val.name = "Variant"; + mi.return_val = func->get_return_type(); p_list->push_back(mi); } } @@ -277,16 +279,14 @@ MethodInfo GDScript::get_method_info(const StringName &p_method) const { if (!E) return MethodInfo(); + GDScriptFunction *func = E->get(); MethodInfo mi; mi.name = E->key(); - for (int i = 0; i < E->get()->get_argument_count(); i++) { - PropertyInfo arg; - arg.type = Variant::NIL; //variant - arg.name = E->get()->get_argument_name(i); - mi.arguments.push_back(arg); + for (int i = 0; i < func->get_argument_count(); i++) { + mi.arguments.push_back(func->get_argument_type(i)); } - mi.return_val.name = "Variant"; + mi.return_val = func->get_return_type(); return mi; } @@ -294,11 +294,6 @@ bool GDScript::get_property_default_value(const StringName &p_property, Variant #ifdef TOOLS_ENABLED - /* - for (const Map<StringName,Variant>::Element *I=member_default_values.front();I;I=I->next()) { - print_line("\t"+String(String(I->key())+":"+String(I->get()))); - } - */ const Map<StringName, Variant>::Element *E = member_default_values_cache.find(p_property); if (E) { r_value = E->get(); @@ -314,27 +309,6 @@ bool GDScript::get_property_default_value(const StringName &p_property, Variant ScriptInstance *GDScript::instance_create(Object *p_this) { - if (!tool && !ScriptServer::is_scripting_enabled()) { - -#ifdef TOOLS_ENABLED - - //instance a fake script for editing the values - //plist.invert(); - - /*print_line("CREATING PLACEHOLDER"); - for(List<PropertyInfo>::Element *E=plist.front();E;E=E->next()) { - print_line(E->get().name); - }*/ - PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(GDScriptLanguage::get_singleton(), Ref<Script>(this), p_this)); - placeholders.insert(si); - //_update_placeholder(si); - _update_exports(); - return si; -#else - return NULL; -#endif - } - GDScript *top = this; while (top->_base) top = top->_base; @@ -353,6 +327,18 @@ ScriptInstance *GDScript::instance_create(Object *p_this) { Variant::CallError unchecked_error; return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this), unchecked_error); } + +PlaceHolderScriptInstance *GDScript::placeholder_instance_create(Object *p_this) { +#ifdef TOOLS_ENABLED + PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(GDScriptLanguage::get_singleton(), Ref<Script>(this), p_this)); + placeholders.insert(si); + _update_exports(); + return si; +#else + return NULL; +#endif +} + bool GDScript::instance_has(const Object *p_this) const { #ifndef NO_THREADS @@ -409,7 +395,6 @@ bool GDScript::_update_exports() { bool changed = false; if (source_changed_cache) { - //print_line("updating source for "+get_path()); source_changed_cache = false; changed = true; @@ -436,34 +421,40 @@ bool GDScript::_update_exports() { base_cache = Ref<GDScript>(); } - if (c->extends_used && String(c->extends_file) != "" && String(c->extends_file) != get_path()) { + if (c->extends_used) { + String path = ""; + if (String(c->extends_file) != "" && String(c->extends_file) != get_path()) { + path = c->extends_file; + if (path.is_rel_path()) { - String path = c->extends_file; - if (path.is_rel_path()) { + String base = get_path(); + if (base == "" || base.is_rel_path()) { - String base = get_path(); - if (base == "" || base.is_rel_path()) { - - ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data()); - } else { - path = base.get_base_dir().plus_file(path); + ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data()); + } else { + path = base.get_base_dir().plus_file(path); + } } - } + } else if (c->extends_class.size() != 0) { + String base = c->extends_class[0]; - if (path != get_path()) { + if (ScriptServer::is_global_class(base)) + path = ScriptServer::get_global_class_path(base); + } - Ref<GDScript> bf = ResourceLoader::load(path); + if (path != "") { + if (path != get_path()) { - if (bf.is_valid()) { + Ref<GDScript> bf = ResourceLoader::load(path); - //print_line("parent is: "+bf->get_path()); - base_cache = bf; - bf->inheriters_cache.insert(get_instance_id()); + if (bf.is_valid()) { - //bf->_update_exports(p_instances,true,false); + base_cache = bf; + bf->inheriters_cache.insert(get_instance_id()); + } + } else { + ERR_PRINT(("Path extending itself in " + path).utf8().get_data()); } - } else { - ERR_PRINT(("Path extending itself in " + path).utf8().get_data()); } } @@ -475,7 +466,6 @@ bool GDScript::_update_exports() { continue; members_cache.push_back(c->variables[i]._export); - //print_line("found "+c->variables[i]._export.name); member_default_values_cache[c->variables[i].identifier] = c->variables[i].default_value; } @@ -484,9 +474,19 @@ bool GDScript::_update_exports() { for (int i = 0; i < c->_signals.size(); i++) { _signals[c->_signals[i].name] = c->_signals[i].arguments; } + } else { + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->set_build_failed(true); + } + return false; } } else { - //print_line("unchanged is "+get_path()); + if (!valid) { + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->set_build_failed(true); + } + return false; + } } if (base_cache.is_valid()) { @@ -495,17 +495,15 @@ bool GDScript::_update_exports() { } } - if (/*changed &&*/ placeholders.size()) { //hm :( + if (placeholders.size()) { //hm :( - //print_line("updating placeholders for "+get_path()); - - //update placeholders if any + // update placeholders if any Map<StringName, Variant> values; List<PropertyInfo> propnames; _update_exports_values(values, propnames); for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { - + E->get()->set_build_failed(false); E->get()->update(propnames, values); } } @@ -525,7 +523,6 @@ void GDScript::update_exports() { Set<ObjectID> copy = inheriters_cache; //might get modified - //print_line("update exports for "+get_path()+" ic: "+itos(copy.size())); for (Set<ObjectID>::Element *E = copy.front(); E; E = E->next()) { Object *id = ObjectDB::get_instance(E->get()); GDScript *s = Object::cast_to<GDScript>(id); @@ -600,6 +597,15 @@ Error GDScript::reload(bool p_keep_state) { return err; } } +#if DEBUG_ENABLED + for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) { + const GDScriptWarning &warning = E->get(); + if (ScriptDebugger::get_singleton()) { + Vector<ScriptLanguage::StackInfo> si; + ScriptDebugger::get_singleton()->send_error("", get_path(), warning.line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si); + } + } +#endif valid = true; @@ -729,22 +735,36 @@ Error GDScript::load_byte_code(const String &p_path) { FileAccess *fa = FileAccess::open(p_path, FileAccess::READ); ERR_FAIL_COND_V(!fa, ERR_CANT_OPEN); + FileAccessEncrypted *fae = memnew(FileAccessEncrypted); ERR_FAIL_COND_V(!fae, ERR_CANT_OPEN); + Vector<uint8_t> key; key.resize(32); for (int i = 0; i < key.size(); i++) { - key[i] = script_encryption_key[i]; + key.write[i] = script_encryption_key[i]; } + Error err = fae->open_and_parse(fa, key, FileAccessEncrypted::MODE_READ); - ERR_FAIL_COND_V(err, err); + + if (err) { + fa->close(); + memdelete(fa); + memdelete(fae); + + ERR_FAIL_COND_V(err, err); + } + bytecode.resize(fae->get_len()); fae->get_buffer(bytecode.ptrw(), bytecode.size()); + fae->close(); memdelete(fae); + } else { bytecode = FileAccess::get_file_as_array(p_path); } + ERR_FAIL_COND_V(bytecode.size() == 0, ERR_PARSE_ERROR); path = p_path; @@ -812,7 +832,6 @@ Error GDScript::load_source_code(const String &p_path) { #ifdef TOOLS_ENABLED source_changed_cache = true; #endif - //print_line("LSC :"+get_path()); path = p_path; return OK; } @@ -848,7 +867,6 @@ bool GDScript::has_script_signal(const StringName &p_signal) const { else if (base_cache.is_valid()) { return base_cache->has_script_signal(p_signal); } - #endif return false; } @@ -941,8 +959,12 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { if (err.error == Variant::CallError::CALL_OK) { return true; //function exists, call was successful } - } else - members[E->get().index] = p_value; + } else { + if (!E->get().data_type.is_type(p_value)) { + return false; // Type mismatch + } + members.write[E->get().index] = p_value; + } return true; } } @@ -1270,7 +1292,7 @@ void GDScriptInstance::reload_members() { if (member_indices_cache.has(E->key())) { Variant value = members[member_indices_cache[E->key()]]; - new_members[E->get().index] = value; + new_members.write[E->get().index] = value; } } @@ -1320,7 +1342,7 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va if (globals.has(p_name)) { //overwrite existing - global_array[globals[p_name]] = p_value; + global_array.write[globals[p_name]] = p_value; return; } globals[p_name] = global_array.size(); @@ -1490,7 +1512,6 @@ int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_ p_info_arr[current].self_time = elem->self()->profile.last_frame_self_time; p_info_arr[current].total_time = elem->self()->profile.last_frame_total_time; p_info_arr[current].signature = elem->self()->profile.signature; - //print_line(String(elem->self()->profile.signature)+": "+itos(elem->self()->profile.last_frame_call_count)); current++; } elem = elem->next(); @@ -1529,7 +1550,7 @@ struct GDScriptDepSort { void GDScriptLanguage::reload_all_scripts() { #ifdef DEBUG_ENABLED - print_line("RELOAD ALL SCRIPTS"); + print_verbose("GDScript: Reloading all scripts"); if (lock) { lock->lock(); } @@ -1539,7 +1560,7 @@ void GDScriptLanguage::reload_all_scripts() { SelfList<GDScript> *elem = script_list.first(); while (elem) { if (elem->self()->get_path().is_resource_file()) { - print_line("FOUND: " + elem->self()->get_path()); + print_verbose("GDScript: Found: " + elem->self()->get_path()); scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } elem = elem->next(); @@ -1555,7 +1576,7 @@ void GDScriptLanguage::reload_all_scripts() { for (List<Ref<GDScript> >::Element *E = scripts.front(); E; E = E->next()) { - print_line("RELOADING: " + E->get()->get_path()); + print_verbose("GDScript: Reloading: " + E->get()->get_path()); E->get()->load_source_code(E->get()->get_path()); E->get()->reload(true); } @@ -1686,7 +1707,6 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so void GDScriptLanguage::frame() { - //print_line("calls: "+itos(calls)); calls = 0; #ifdef DEBUG_ENABLED @@ -1735,10 +1755,13 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "NAN", "self", "true", + "void", // functions + "as", "assert", "breakpoint", "class", + "class_name", "extends", "is", "func", @@ -1768,10 +1791,11 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "remote", "sync", "master", + "puppet", "slave", "remotesync", "mastersync", - "slavesync", + "puppetsync", 0 }; @@ -1788,6 +1812,250 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { } } +bool GDScriptLanguage::handles_global_class_type(const String &p_type) const { + + return p_type == "GDScript"; +} + +String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { + + PoolVector<uint8_t> sourcef; + Error err; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); + if (err) { + return String(); + } + + int len = f->get_len(); + sourcef.resize(len + 1); + PoolVector<uint8_t>::Write w = sourcef.write(); + int r = f->get_buffer(w.ptr(), len); + f->close(); + memdelete(f); + ERR_FAIL_COND_V(r != len, String()); + w[len] = 0; + + String s; + if (s.parse_utf8((const char *)w.ptr())) { + return String(); + } + + GDScriptParser parser; + + parser.parse(s, p_path.get_base_dir(), true, p_path); + + if (parser.get_parse_tree() && parser.get_parse_tree()->type == GDScriptParser::Node::TYPE_CLASS) { + + const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(parser.get_parse_tree()); + if (r_base_type) { + GDScriptParser::DataType base_type; + if (c->base_type.has_type) { + base_type = c->base_type; + while (base_type.has_type && base_type.kind != GDScriptParser::DataType::NATIVE) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = base_type.script_type; + if (gds.is_valid()) { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); + } else { + base_type = GDScriptParser::DataType(); + } + } break; + default: { + base_type = GDScriptParser::DataType(); + } break; + } + } + } + if (base_type.has_type) { + *r_base_type = base_type.native_type; + } else { + // Fallback + if (c->extends_used && c->extends_class.size() == 1) { + *r_base_type = c->extends_class[0]; + } else if (!c->extends_used) { + *r_base_type = "Reference"; + } + } + } + if (r_icon_path) { + if (c->icon_path.empty() || c->icon_path.is_abs_path()) + *r_icon_path = c->icon_path; + else if (c->icon_path.is_rel_path()) + *r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path(); + } + return c->name; + } + + return String(); +} + +#ifdef DEBUG_ENABLED +String GDScriptWarning::get_message() const { + +#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String()); + + switch (code) { + case UNASSIGNED_VARIABLE_OP_ASSIGN: { + CHECK_SYMBOLS(1); + return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value."; + } break; + case UNASSIGNED_VARIABLE: { + CHECK_SYMBOLS(1); + return "The variable '" + symbols[0] + "' was used but never assigned a value."; + } break; + case UNUSED_VARIABLE: { + CHECK_SYMBOLS(1); + return "The local variable '" + symbols[0] + "' is declared but never used in the block."; + } break; + case UNUSED_CLASS_VARIABLE: { + CHECK_SYMBOLS(1); + return "The class variable '" + symbols[0] + "' is declared but never used in the script."; + } break; + case UNUSED_ARGUMENT: { + CHECK_SYMBOLS(2); + return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'."; + } break; + case UNREACHABLE_CODE: { + CHECK_SYMBOLS(1); + return "Unreachable code (statement after return) in function '" + symbols[0] + "()'."; + } break; + case STANDALONE_EXPRESSION: { + return "Standalone expression (the line has no effect)."; + } break; + case VOID_ASSIGNMENT: { + CHECK_SYMBOLS(1); + return "Assignment operation, but the function '" + symbols[0] + "()' returns void."; + } break; + case NARROWING_CONVERSION: { + return "Narrowing coversion (float is converted to int and lose precision)."; + } break; + case FUNCTION_MAY_YIELD: { + CHECK_SYMBOLS(1); + return "Assigned variable is typed but the function '" + symbols[0] + "()' may yield and return a GDScriptFunctionState instead."; + } break; + case VARIABLE_CONFLICTS_FUNCTION: { + CHECK_SYMBOLS(1); + return "Variable declaration of '" + symbols[0] + "' conflicts with a function of the same name."; + } break; + case FUNCTION_CONFLICTS_VARIABLE: { + CHECK_SYMBOLS(1); + return "Function declaration of '" + symbols[0] + "()' conflicts with a variable of the same name."; + } break; + case FUNCTION_CONFLICTS_CONSTANT: { + CHECK_SYMBOLS(1); + return "Function declaration of '" + symbols[0] + "()' conflicts with a constant of the same name."; + } break; + case INCOMPATIBLE_TERNARY: { + return "Values of the ternary conditional are not mutually compatible."; + } break; + case UNUSED_SIGNAL: { + CHECK_SYMBOLS(1); + return "The signal '" + symbols[0] + "' is declared but never emitted."; + } break; + case RETURN_VALUE_DISCARDED: { + CHECK_SYMBOLS(1); + return "The function '" + symbols[0] + "()' returns a value, but this value is never used."; + } break; + case PROPERTY_USED_AS_FUNCTION: { + CHECK_SYMBOLS(2); + return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?"; + } break; + case CONSTANT_USED_AS_FUNCTION: { + CHECK_SYMBOLS(2); + return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?"; + } break; + case FUNCTION_USED_AS_PROPERTY: { + CHECK_SYMBOLS(2); + return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?"; + } break; + case INTEGER_DIVISION: { + return "Integer division, decimal part will be discarded."; + } break; + case UNSAFE_PROPERTY_ACCESS: { + CHECK_SYMBOLS(2); + return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype)."; + } break; + case UNSAFE_METHOD_ACCESS: { + CHECK_SYMBOLS(2); + return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype)."; + } break; + case UNSAFE_CAST: { + CHECK_SYMBOLS(1); + return "The value is cast to '" + symbols[0] + "' but has an unknown type."; + } break; + case UNSAFE_CALL_ARGUMENT: { + CHECK_SYMBOLS(4); + return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided"; + } break; + case DEPRECATED_KEYWORD: { + CHECK_SYMBOLS(2); + return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'."; + } break; + case WARNING_MAX: break; // Can't happen, but silences warning + } + ERR_EXPLAIN("Invalid GDScript warning code: " + get_name_from_code(code)); + ERR_FAIL_V(String()); + +#undef CHECK_SYMBOLS +} + +String GDScriptWarning::get_name() const { + return get_name_from_code(code); +} + +String GDScriptWarning::get_name_from_code(Code p_code) { + ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String()); + + static const char *names[] = { + "UNASSIGNED_VARIABLE", + "UNASSIGNED_VARIABLE_OP_ASSIGN", + "UNUSED_VARIABLE", + "UNUSED_CLASS_VARIABLE", + "UNUSED_ARGUMENT", + "UNREACHABLE_CODE", + "STANDALONE_EXPRESSION", + "VOID_ASSIGNMENT", + "NARROWING_CONVERSION", + "FUNCTION_MAY_YIELD", + "VARIABLE_CONFLICTS_FUNCTION", + "FUNCTION_CONFLICTS_VARIABLE", + "FUNCTION_CONFLICTS_CONSTANT", + "INCOMPATIBLE_TERNARY", + "UNUSED_SIGNAL", + "RETURN_VALUE_DISCARDED", + "PROPERTY_USED_AS_FUNCTION", + "CONSTANT_USED_AS_FUNCTION", + "FUNCTION_USED_AS_PROPERTY", + "INTEGER_DIVISION", + "UNSAFE_PROPERTY_ACCESS", + "UNSAFE_METHOD_ACCESS", + "UNSAFE_CAST", + "UNSAFE_CALL_ARGUMENT", + "DEPRECATED_KEYWORD", + NULL + }; + + return names[(int)p_code]; +} + +GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) { + for (int i = 0; i < WARNING_MAX; i++) { + if (get_name_from_code((Code)i) == p_name) { + return (Code)i; + } + } + + ERR_EXPLAIN("Invalid GDScript warning name: " + p_name); + ERR_FAIL_V(WARNING_MAX); +} + +#endif // DEBUG_ENABLED + GDScriptLanguage::GDScriptLanguage() { calls = 0; @@ -1812,18 +2080,28 @@ GDScriptLanguage::GDScriptLanguage() { _debug_call_stack_pos = 0; int dmcs = GLOBAL_DEF("debug/settings/gdscript/max_call_stack", 1024); + ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/gdscript/max_call_stack", PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater")); //minimum is 1024 + if (ScriptDebugger::get_singleton()) { //debugging enabled! _debug_max_call_stack = dmcs; - if (_debug_max_call_stack < 1024) - _debug_max_call_stack = 1024; _call_stack = memnew_arr(CallLevel, _debug_max_call_stack + 1); } else { _debug_max_call_stack = 0; _call_stack = NULL; } + +#ifdef DEBUG_ENABLED + GLOBAL_DEF("debug/gdscript/warnings/enable", true); + GLOBAL_DEF("debug/gdscript/warnings/treat_warnings_as_errors", false); + GLOBAL_DEF("debug/gdscript/completion/autocomplete_setters_and_getters", false); + for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { + String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); + GLOBAL_DEF("debug/gdscript/warnings/" + warning, !warning.begins_with("unsafe_")); + } +#endif // DEBUG_ENABLED } GDScriptLanguage::~GDScriptLanguage() { @@ -1854,23 +2132,14 @@ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_ori script->set_script_path(p_original_path); // script needs this. script->set_path(p_original_path); Error err = script->load_byte_code(p_path); - - if (err != OK) { - - ERR_FAIL_COND_V(err != OK, RES()); - } + ERR_FAIL_COND_V(err != OK, RES()); } else { Error err = script->load_source_code(p_path); - - if (err != OK) { - - ERR_FAIL_COND_V(err != OK, RES()); - } + ERR_FAIL_COND_V(err != OK, RES()); script->set_script_path(p_original_path); // script needs this. script->set_path(p_original_path); - //script->set_name(p_path.get_file()); script->reload(); } @@ -1879,6 +2148,7 @@ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_ori return scriptres; } + void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("gd"); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index a35b0a10d5..752d660ffb 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -31,10 +31,10 @@ #ifndef GDSCRIPT_H #define GDSCRIPT_H +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#include "core/script_language.h" #include "gdscript_function.h" -#include "io/resource_loader.h" -#include "io/resource_saver.h" -#include "script_language.h" class GDScriptNativeClass : public Reference { @@ -64,6 +64,7 @@ class GDScript : public Script { StringName setter; StringName getter; MultiplayerAPI::RPCMode rpc_mode; + GDScriptDataType data_type; }; friend class GDScriptInstance; @@ -140,13 +141,18 @@ protected: static void _bind_methods(); public: - bool is_valid() const { return valid; } + virtual bool is_valid() const { return valid; } const Map<StringName, Ref<GDScript> > &get_subclasses() const { return subclasses; } const Map<StringName, Variant> &get_constants() const { return constants; } const Set<StringName> &get_members() const { return members; } + const GDScriptDataType &get_member_type(const StringName &p_member) const { + CRASH_COND(!member_indices.has(p_member)); + return member_indices[p_member].data_type; + } const Map<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; } const Ref<GDScriptNativeClass> &get_native() const { return native; } + const String &get_script_class_name() const { return name; } virtual bool has_script_signal(const StringName &p_signal) const; virtual void get_script_signal_list(List<MethodInfo> *r_signals) const; @@ -165,6 +171,7 @@ public: virtual StringName get_instance_base_type() const; // this may not work in all scripts, will return empty if so virtual ScriptInstance *instance_create(Object *p_this); + virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this); virtual bool instance_has(const Object *p_this) const; virtual bool has_source_code() const; @@ -255,6 +262,50 @@ public: ~GDScriptInstance(); }; +#ifdef DEBUG_ENABLED +struct GDScriptWarning { + enum Code { + UNASSIGNED_VARIABLE, // Variable used but never assigned + UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc) + UNUSED_VARIABLE, // Local variable is declared but never used + UNUSED_CLASS_VARIABLE, // Class variable is declared but never used in the file + UNUSED_ARGUMENT, // Function argument is never used + UNREACHABLE_CODE, // Code after a return statement + STANDALONE_EXPRESSION, // Expression not assigned to a variable + VOID_ASSIGNMENT, // Function returns void but it's assigned to a variable + NARROWING_CONVERSION, // Float value into an integer slot, precision is lost + FUNCTION_MAY_YIELD, // Typed assign of function call that yields (it may return a function state) + VARIABLE_CONFLICTS_FUNCTION, // Variable has the same name of a function + FUNCTION_CONFLICTS_VARIABLE, // Function has the same name of a variable + FUNCTION_CONFLICTS_CONSTANT, // Function has the same name of a constant + INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible + UNUSED_SIGNAL, // Signal is defined but never emitted + RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used + PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name + CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name + FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name + INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded + UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes) + UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes) + UNSAFE_CAST, // Cast used in an unknown type + UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument + DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced + WARNING_MAX, + } code; + Vector<String> symbols; + int line; + + String get_name() const; + String get_message() const; + static String get_name_from_code(Code p_code); + static Code get_code_from_name(const String &p_name); + + GDScriptWarning() : + code(WARNING_MAX), + line(-1) {} +}; +#endif // DEBUG_ENABLED + class GDScriptLanguage : public ScriptLanguage { static GDScriptLanguage *singleton; @@ -349,10 +400,10 @@ public: Vector<StackInfo> csi; csi.resize(_debug_call_stack_pos); for (int i = 0; i < _debug_call_stack_pos; i++) { - csi[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0; + csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0; if (_call_stack[i].function) - csi[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name(); - csi[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path(); + csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name(); + csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path(); } return csi; } @@ -391,7 +442,7 @@ public: virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; virtual bool is_using_templates(); virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script); - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const; + virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, List<ScriptLanguage::Warning> *r_warnings = NULL, Set<int> *r_safe_lines = NULL) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; @@ -439,6 +490,11 @@ public: virtual void get_recognized_extensions(List<String> *p_extensions) const; + /* GLOBAL CLASSES */ + + virtual bool handles_global_class_type(const String &p_type) const; + virtual String get_global_class_name(const String &p_path, String *r_base_type = NULL, String *r_icon_path = NULL) const; + GDScriptLanguage(); ~GDScriptLanguage(); }; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 85c36647a1..caa7fbfeca 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -111,23 +111,50 @@ bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptP return true; } -/* -int GDScriptCompiler::_parse_subexpression(CodeGen& codegen,const GDScriptParser::Node *p_expression) { - +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const { + if (!p_datatype.has_type) { + return GDScriptDataType(); + } - int ret = _parse_expression(codegen,p_expression); - if (ret<0) - return ret; + GDScriptDataType result; + result.has_type = true; - if (ret&(GDScriptFunction::ADDR_TYPE_STACK<<GDScriptFunction::ADDR_BITS)) { - codegen.stack_level++; - codegen.check_max_stack_level(); - //stack was used, keep value + switch (p_datatype.kind) { + case GDScriptParser::DataType::BUILTIN: { + result.kind = GDScriptDataType::BUILTIN; + result.builtin_type = p_datatype.builtin_type; + } break; + case GDScriptParser::DataType::NATIVE: { + result.kind = GDScriptDataType::NATIVE; + result.native_type = p_datatype.native_type; + } break; + case GDScriptParser::DataType::SCRIPT: { + result.kind = GDScriptDataType::SCRIPT; + result.script_type = p_datatype.script_type; + result.native_type = result.script_type->get_instance_base_type(); + } break; + case GDScriptParser::DataType::GDSCRIPT: { + result.kind = GDScriptDataType::GDSCRIPT; + result.script_type = p_datatype.script_type; + result.native_type = result.script_type->get_instance_base_type(); + } break; + case GDScriptParser::DataType::CLASS: { + result.kind = GDScriptDataType::GDSCRIPT; + if (!p_datatype.class_type->owner) { + result.script_type = Ref<GDScript>(main_script); + } else { + result.script_type = class_map[p_datatype.class_type->name]; + } + result.native_type = result.script_type->get_instance_base_type(); + } break; + default: { + ERR_PRINT("Parser bug: converting unresolved type."); + result.has_type = false; + } } - return ret; + return result; } -*/ int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level) { @@ -263,21 +290,47 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: owner = owner->_owner; } - /* - handled in constants now - if (codegen.script->subclasses.has(identifier)) { - //same with a subclass, make it a local constant. - int idx = codegen.get_constant_pos(codegen.script->subclasses[identifier]); - return idx|(GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT<<GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) - - }*/ - if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) } + /* TRY GLOBAL CLASSES */ + + if (ScriptServer::is_global_class(identifier)) { + + const GDScriptParser::ClassNode *class_node = codegen.class_node; + while (class_node->owner) { + class_node = class_node->owner; + } + + if (class_node->name == identifier) { + _set_error("Using own name in class file is not allowed (creates a cyclic reference)", p_expression); + return -1; + } + + RES res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); + if (res.is_null()) { + _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); + return -1; + } + + Variant key = res; + int idx; + + if (!codegen.constant_map.has(key)) { + + idx = codegen.constant_map.size(); + codegen.constant_map[key] = idx; + + } else { + idx = codegen.constant_map[key]; + } + + return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) + } + #ifdef TOOLS_ENABLED if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { @@ -335,7 +388,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: int ret = _parse_expression(codegen, an->elements[i], slevel); if (ret < 0) return ret; - if (ret & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } @@ -366,7 +419,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: int ret = _parse_expression(codegen, dn->elements[i].key, slevel); if (ret < 0) return ret; - if (ret & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } @@ -376,7 +429,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: ret = _parse_expression(codegen, dn->elements[i].value, slevel); if (ret < 0) return ret; - if (ret & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } @@ -395,6 +448,83 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return dst_addr; } break; + case GDScriptParser::Node::TYPE_CAST: { + const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); + + int slevel = p_stack_level; + int src_addr = _parse_expression(codegen, cn->source_node, slevel); + if (src_addr < 0) + return src_addr; + if (src_addr & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + switch (cn->cast_type.kind) { + case GDScriptParser::DataType::BUILTIN: { + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_BUILTIN); + codegen.opcodes.push_back(cn->cast_type.builtin_type); + } break; + case GDScriptParser::DataType::NATIVE: { + int class_idx; + if (GDScriptLanguage::get_singleton()->get_global_map().has(cn->cast_type.native_type)) { + + class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cn->cast_type.native_type]; + class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) + } else { + _set_error("Invalid native class type '" + String(cn->cast_type.native_type) + "'.", cn); + return -1; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_NATIVE); // perform operator + codegen.opcodes.push_back(class_idx); // variable type + } break; + case GDScriptParser::DataType::CLASS: { + + Variant script; + int idx = -1; + if (!cn->cast_type.class_type->owner) { + script = codegen.script; + } else { + StringName name = cn->cast_type.class_type->name; + if (class_map[name] == codegen.script->subclasses[name]) { + idx = codegen.get_name_map_pos(name); + idx |= GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS; + } else { + script = class_map[name]; + } + } + + if (idx < 0) { + idx = codegen.get_constant_pos(script); + idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) + } + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator + codegen.opcodes.push_back(idx); // variable type + } break; + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::GDSCRIPT: { + + Variant script = cn->cast_type.script_type; + int idx = codegen.get_constant_pos(script); + idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator + codegen.opcodes.push_back(idx); // variable type + } break; + default: { + _set_error("Parser bug: unresolved data type.", cn); + return -1; + } + } + + codegen.opcodes.push_back(src_addr); // source address + int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode + codegen.alloc_stack(p_stack_level); + return dst_addr; + + } break; case GDScriptParser::Node::TYPE_OPERATOR: { //hell breaks loose @@ -415,7 +545,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: int ret = _parse_expression(codegen, on->arguments[i], slevel); if (ret < 0) return ret; - if (ret & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } @@ -448,7 +578,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: int ret = _parse_expression(codegen, on->arguments[i], slevel); if (ret < 0) return ret; - if (ret & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } @@ -476,7 +606,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: if (ret < 0) return ret; - if (ret & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } @@ -525,7 +655,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: ret = _parse_expression(codegen, on->arguments[i], slevel); if (ret < 0) return ret; - if (ret & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } @@ -551,7 +681,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: int ret = _parse_expression(codegen, on->arguments[i], slevel); if (ret < 0) return ret; - if (ret & (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS)) { + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } @@ -655,8 +785,8 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(codegen.opcodes.size() + 3); - codegen.opcodes[jump_fail_pos] = codegen.opcodes.size(); - codegen.opcodes[jump_fail_pos2] = codegen.opcodes.size(); + codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); + codegen.opcodes.write[jump_fail_pos2] = codegen.opcodes.size(); codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE); codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; @@ -688,8 +818,8 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(codegen.opcodes.size() + 3); - codegen.opcodes[jump_success_pos] = codegen.opcodes.size(); - codegen.opcodes[jump_success_pos2] = codegen.opcodes.size(); + codegen.opcodes.write[jump_success_pos] = codegen.opcodes.size(); + codegen.opcodes.write[jump_success_pos2] = codegen.opcodes.size(); codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE); codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; @@ -720,7 +850,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: int jump_past_pos = codegen.opcodes.size(); codegen.opcodes.push_back(0); - codegen.opcodes[jump_fail_pos] = codegen.opcodes.size(); + codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); res = _parse_expression(codegen, on->arguments[2], p_stack_level); if (res < 0) return res; @@ -729,7 +859,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(res); - codegen.opcodes[jump_past_pos] = codegen.opcodes.size(); + codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size(); return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; @@ -747,14 +877,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: case GDScriptParser::OperatorNode::OP_BIT_INVERT: { if (!_create_unary_operator(codegen, on, Variant::OP_BIT_NEGATE, p_stack_level)) return -1; } break; - case GDScriptParser::OperatorNode::OP_PREINC: { - } break; //? - case GDScriptParser::OperatorNode::OP_PREDEC: { - } break; - case GDScriptParser::OperatorNode::OP_INC: { - } break; - case GDScriptParser::OperatorNode::OP_DEC: { - } break; //binary operators (in precedence order) case GDScriptParser::OperatorNode::OP_IN: { if (!_create_binary_operator(codegen, on, Variant::OP_IN, p_stack_level)) return -1; @@ -828,7 +950,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: if (on->arguments[0]->type == GDScriptParser::Node::TYPE_OPERATOR && (static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX || static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED)) { - // SET (chained) MODE! + // SET (chained) MODE! #ifdef DEBUG_ENABLED if (static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) { const GDScriptParser::OperatorNode *inon = static_cast<GDScriptParser::OperatorNode *>(on->arguments[0]); @@ -887,8 +1009,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return prev_pos; int retval = prev_pos; - //print_line("retval: "+itos(retval)); - if (retval & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { slevel++; codegen.alloc_stack(slevel); @@ -1029,12 +1149,87 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: if (src_address_b < 0) return -1; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + GDScriptParser::DataType assign_type = on->arguments[0]->get_datatype(); + + if (assign_type.has_type && !on->arguments[1]->get_datatype().has_type) { + // Typed assignment + switch (assign_type.kind) { + case GDScriptParser::DataType::BUILTIN: { + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator + codegen.opcodes.push_back(assign_type.builtin_type); // variable type + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 + } break; + case GDScriptParser::DataType::NATIVE: { + int class_idx; + if (GDScriptLanguage::get_singleton()->get_global_map().has(assign_type.native_type)) { + + class_idx = GDScriptLanguage::get_singleton()->get_global_map()[assign_type.native_type]; + class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) + } else { + _set_error("Invalid native class type '" + String(assign_type.native_type) + "'.", on->arguments[0]); + return -1; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator + codegen.opcodes.push_back(class_idx); // variable type + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 + } break; + case GDScriptParser::DataType::CLASS: { + + Variant script; + int idx = -1; + if (!assign_type.class_type->owner) { + script = codegen.script; + } else { + StringName name = assign_type.class_type->name; + if (class_map[name] == codegen.script->subclasses[name]) { + idx = codegen.get_name_map_pos(name); + idx |= GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS; + } else { + script = class_map[name]; + } + } + + if (idx < 0) { + idx = codegen.get_constant_pos(script); + idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) + } + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator + codegen.opcodes.push_back(idx); // variable type + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 + } break; + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::GDSCRIPT: { + + Variant script = assign_type.script_type; + int idx = codegen.get_constant_pos(script); + idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator + codegen.opcodes.push_back(idx); // variable type + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 + } break; + default: { + ERR_PRINT("Compiler bug: unresolved assign."); + + // Shouldn't get here, but fail-safe to a regular assignment + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + } + } + } else { + // Either untyped assignment or already type-checked by the parser + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + } return dst_address_a; //if anything, returns wathever was assigned or correct stack position } - } break; case GDScriptParser::OperatorNode::OP_IS: { @@ -1058,6 +1253,25 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) } break; + case GDScriptParser::OperatorNode::OP_IS_BUILTIN: { + ERR_FAIL_COND_V(on->arguments.size() != 2, false); + ERR_FAIL_COND_V(on->arguments[1]->type != GDScriptParser::Node::TYPE_TYPE, false); + + int slevel = p_stack_level; + + int src_address_a = _parse_expression(codegen, on->arguments[0], slevel); + if (src_address_a < 0) + return -1; + + if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) + slevel++; //uses stack for return, increase stack + + const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(on->arguments[1]); + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_IS_BUILTIN); // perform operator + codegen.opcodes.push_back(src_address_a); // argument 1 + codegen.opcodes.push_back((int)tn->vtype); // argument 2 (unary only takes one parameter) + } break; default: { ERR_EXPLAIN("Bug in bytecode compiler, unexpected operator #" + itos(on->op) + " in parse tree while parsing expression."); @@ -1127,6 +1341,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo int ret = _parse_expression(codegen, op, p_stack_level); if (ret < 0) { + memdelete(id); + memdelete(op); return ERR_PARSE_ERROR; } @@ -1146,6 +1362,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo // compile the condition int ret = _parse_expression(codegen, branch.compiled_pattern, p_stack_level); if (ret < 0) { + memdelete(id); + memdelete(op); return ERR_PARSE_ERROR; } @@ -1158,16 +1376,21 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo Error err = _parse_block(codegen, branch.body, p_stack_level, p_break_addr, continue_addr); if (err) { + memdelete(id); + memdelete(op); return ERR_PARSE_ERROR; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(break_addr); - codegen.opcodes[continue_addr + 1] = codegen.opcodes.size(); + codegen.opcodes.write[continue_addr + 1] = codegen.opcodes.size(); } - codegen.opcodes[break_addr + 1] = codegen.opcodes.size(); + codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size(); + + memdelete(id); + memdelete(op); } break; @@ -1196,16 +1419,16 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); int end_addr = codegen.opcodes.size(); codegen.opcodes.push_back(0); - codegen.opcodes[else_addr] = codegen.opcodes.size(); + codegen.opcodes.write[else_addr] = codegen.opcodes.size(); Error err = _parse_block(codegen, cf->body_else, p_stack_level, p_break_addr, p_continue_addr); if (err) return err; - codegen.opcodes[end_addr] = codegen.opcodes.size(); + codegen.opcodes.write[end_addr] = codegen.opcodes.size(); } else { //end without else - codegen.opcodes[else_addr] = codegen.opcodes.size(); + codegen.opcodes.write[else_addr] = codegen.opcodes.size(); } } break; @@ -1256,7 +1479,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(continue_pos); - codegen.opcodes[break_pos + 1] = codegen.opcodes.size(); + codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size(); codegen.pop_stack_identifiers(); @@ -1282,7 +1505,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(continue_addr); - codegen.opcodes[break_addr + 1] = codegen.opcodes.size(); + codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size(); } break; case GDScriptParser::ControlFlowNode::CF_SWITCH: { @@ -1490,6 +1713,18 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser if (p_func) { gdfunc->_static = p_func->_static; gdfunc->rpc_mode = p_func->rpc_mode; + gdfunc->argument_types.resize(p_func->argument_types.size()); + for (int i = 0; i < p_func->argument_types.size(); i++) { + gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->argument_types[i]); + } + gdfunc->return_type = _gdtype_from_datatype(p_func->return_type); + } else { + gdfunc->_static = false; + gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + gdfunc->return_type = GDScriptDataType(); + gdfunc->return_type.has_type = true; + gdfunc->return_type.kind = GDScriptDataType::BUILTIN; + gdfunc->return_type.builtin_type = Variant::NIL; } #ifdef TOOLS_ENABLED @@ -1499,11 +1734,11 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser if (codegen.constant_map.size()) { gdfunc->_constant_count = codegen.constant_map.size(); gdfunc->constants.resize(codegen.constant_map.size()); - gdfunc->_constants_ptr = &gdfunc->constants[0]; + gdfunc->_constants_ptr = gdfunc->constants.ptrw(); const Variant *K = NULL; while ((K = codegen.constant_map.next(K))) { int idx = codegen.constant_map[*K]; - gdfunc->constants[idx] = *K; + gdfunc->constants.write[idx] = *K; } } else { @@ -1517,7 +1752,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser gdfunc->_global_names_ptr = &gdfunc->global_names[0]; for (Map<StringName, int>::Element *E = codegen.name_map.front(); E; E = E->next()) { - gdfunc->global_names[E->get()] = E->key(); + gdfunc->global_names.write[E->get()] = E->key(); } gdfunc->_global_names_count = gdfunc->global_names.size(); @@ -1532,7 +1767,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser gdfunc->named_globals.resize(codegen.named_globals.size()); gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); for (int i = 0; i < codegen.named_globals.size(); i++) { - gdfunc->named_globals[i] = codegen.named_globals[i]; + gdfunc->named_globals.write[i] = codegen.named_globals[i]; } gdfunc->_named_globals_count = gdfunc->named_globals.size(); } @@ -1618,12 +1853,23 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser return OK; } -Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +Error GDScriptCompiler::_parse_class_level(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - Map<StringName, Ref<GDScript> > old_subclasses; - - if (p_keep_state) { - old_subclasses = p_script->subclasses; + if (p_class->owner && p_class->owner->owner) { + // Owner is not root + StringName owner_name = p_class->owner->name; + if (!parsed_classes.has(owner_name)) { + if (parsing_classes.has(owner_name)) { + _set_error("Cyclic class reference for '" + String(owner_name) + "'.", p_class); + return ERR_PARSE_ERROR; + } + parsing_classes.insert(owner_name); + Error err = _parse_class_level(class_map[owner_name].ptr(), class_map[owner_name]->_owner, p_class->owner, p_keep_state); + if (err) { + return err; + } + parsing_classes.erase(owner_name); + } } p_script->native = Ref<GDScriptNativeClass>(); @@ -1647,236 +1893,100 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons Ref<GDScriptNativeClass> native; - if (p_class->extends_used) { - //do inheritance - String path = p_class->extends_file; - - Ref<GDScript> script; - - if (path != "") { - //path (and optionally subclasses) - - if (path.is_rel_path()) { - - String base; - - if (p_owner) { - GDScript *current_class = p_owner; - while (current_class != NULL) { - base = current_class->get_path(); - if (base == "") - current_class = current_class->_owner; - else - break; - } - } else { - base = p_script->get_path(); - } - - if (base == "" || base.is_rel_path()) { - _set_error("Could not resolve relative path for parent class: " + path, p_class); - return ERR_FILE_NOT_FOUND; - } - path = base.get_base_dir().plus_file(path).simplify_path(); - } - script = ResourceLoader::load(path); - if (script.is_null()) { - _set_error("Could not load base class: " + path, p_class); - return ERR_FILE_NOT_FOUND; - } - if (!script->valid) { - - _set_error("Script not fully loaded (cyclic preload?): " + path, p_class); - return ERR_BUSY; - } - //print_line("EXTENDS PATH: "+path+" script is "+itos(script.is_valid())+" indices is "+itos(script->member_indices.size())+" valid? "+itos(script->valid)); - - if (p_class->extends_class.size()) { - - for (int i = 0; i < p_class->extends_class.size(); i++) { - - String sub = p_class->extends_class[i]; - if (script->subclasses.has(sub)) { - - Ref<Script> subclass = script->subclasses[sub]; //avoid reference from disappearing - script = subclass; - } else { - - _set_error("Could not find subclass: " + sub, p_class); - return ERR_FILE_NOT_FOUND; - } - } - } - - } else { - - ERR_FAIL_COND_V(p_class->extends_class.size() == 0, ERR_BUG); - //look around for the subclasses - - String base = p_class->extends_class[0]; - GDScript *p = p_owner; - Ref<GDScript> base_class; - - while (p) { - - if (p->subclasses.has(base)) { - - base_class = p->subclasses[base]; - break; - } - - if (p->constants.has(base)) { - - base_class = p->constants[base]; - if (base_class.is_null()) { - _set_error("Constant is not a class: " + base, p_class); - return ERR_SCRIPT_FAILED; - } - break; - } - - p = p->_owner; - } - - if (base_class.is_valid()) { - - String ident = base; - - for (int i = 1; i < p_class->extends_class.size(); i++) { - - String subclass = p_class->extends_class[i]; - - ident += ("." + subclass); - - if (base_class->subclasses.has(subclass)) { - - base_class = base_class->subclasses[subclass]; - } else if (base_class->constants.has(subclass)) { - - Ref<GDScript> new_base_class = base_class->constants[subclass]; - if (new_base_class.is_null()) { - _set_error("Constant is not a class: " + ident, p_class); - return ERR_SCRIPT_FAILED; - } - base_class = new_base_class; - } else { - - _set_error("Could not find subclass: " + ident, p_class); - return ERR_FILE_NOT_FOUND; - } - } - - script = base_class; - - } else { - - if (p_class->extends_class.size() > 1) { - - _set_error("Invalid inheritance (unknown class+subclasses)", p_class); - return ERR_FILE_NOT_FOUND; + // Inheritance + switch (p_class->base_type.kind) { + case GDScriptParser::DataType::CLASS: { + StringName base_name = p_class->base_type.class_type->name; + // Make sure dependency is parsed first + if (!parsed_classes.has(base_name)) { + if (parsing_classes.has(base_name)) { + _set_error("Cyclic class reference for '" + String(base_name) + "'.", p_class); + return ERR_PARSE_ERROR; } - //if not found, try engine classes - if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { - - _set_error("Unknown class: '" + base + "'", p_class); - return ERR_FILE_NOT_FOUND; - } - - int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; - native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; - if (!native.is_valid()) { - - _set_error("Global not a class: '" + base + "'", p_class); - - return ERR_FILE_NOT_FOUND; + parsing_classes.insert(base_name); + Error err = _parse_class_level(class_map[base_name].ptr(), class_map[base_name]->_owner, p_class->base_type.class_type, p_keep_state); + if (err) { + return err; } + parsing_classes.erase(base_name); } - } - - if (script.is_valid()) { - - p_script->base = script; + Ref<GDScript> base = class_map[base_name]; + p_script->base = base; p_script->_base = p_script->base.ptr(); - p_script->member_indices = script->member_indices; - - } else if (native.is_valid()) { - + p_script->member_indices = base->member_indices; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> base = p_class->base_type.script_type; + p_script->base = base; + p_script->_base = p_script->base.ptr(); + p_script->member_indices = base->member_indices; + } break; + case GDScriptParser::DataType::NATIVE: { + int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_class->base_type.native_type]; + native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; + ERR_FAIL_COND_V(native.is_null(), ERR_BUG); p_script->native = native; - } else { - - _set_error("Could not determine inheritance", p_class); - return ERR_FILE_NOT_FOUND; - } - - } else { - // without extends, implicitly extend Reference - int native_idx = GDScriptLanguage::get_singleton()->get_global_map()["Reference"]; - native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; - ERR_FAIL_COND_V(native.is_null(), ERR_BUG); - p_script->native = native; + } break; + default: { + _set_error("Parser bug: invalid inheritance.", p_class); + return ERR_BUG; + } break; } - //print_line("Script: "+p_script->get_path()+" indices: "+itos(p_script->member_indices.size())); - for (int i = 0; i < p_class->variables.size(); i++) { StringName name = p_class->variables[i].identifier; - if (p_script->member_indices.has(name)) { - _set_error("Member '" + name + "' already exists (in current or parent class)", p_class); - return ERR_ALREADY_EXISTS; - } - if (_is_class_member_property(p_script, name)) { - _set_error("Member '" + name + "' already exists as a class property.", p_class); - return ERR_ALREADY_EXISTS; - } - if (p_class->variables[i]._export.type != Variant::NIL) { + GDScript::MemberInfo minfo; + minfo.index = p_script->member_indices.size(); + minfo.setter = p_class->variables[i].setter; + minfo.getter = p_class->variables[i].getter; + minfo.rpc_mode = p_class->variables[i].rpc_mode; + minfo.data_type = _gdtype_from_datatype(p_class->variables[i].data_type); + + PropertyInfo prop_info = minfo.data_type; + prop_info.name = name; + PropertyInfo export_info = p_class->variables[i]._export; - p_script->member_info[name] = p_class->variables[i]._export; + if (export_info.type != Variant::NIL) { + + if (!minfo.data_type.has_type) { + prop_info.type = export_info.type; + prop_info.class_name = export_info.class_name; + } + prop_info.hint = export_info.hint; + prop_info.hint_string = export_info.hint_string; + prop_info.usage = export_info.usage; #ifdef TOOLS_ENABLED if (p_class->variables[i].default_value.get_type() != Variant::NIL) { - p_script->member_default_values[name] = p_class->variables[i].default_value; } #endif } else { - - p_script->member_info[name] = PropertyInfo(Variant::NIL, name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); + prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; } - //int new_idx = p_script->member_indices.size(); - GDScript::MemberInfo minfo; - minfo.index = p_script->member_indices.size(); - minfo.setter = p_class->variables[i].setter; - minfo.getter = p_class->variables[i].getter; - minfo.rpc_mode = p_class->variables[i].rpc_mode; - + p_script->member_info[name] = prop_info; p_script->member_indices[name] = minfo; p_script->members.insert(name); #ifdef TOOLS_ENABLED - p_script->member_lines[name] = p_class->variables[i].line; #endif } - for (int i = 0; i < p_class->constant_expressions.size(); i++) { + for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { - StringName name = p_class->constant_expressions[i].identifier; - ERR_CONTINUE(p_class->constant_expressions[i].expression->type != GDScriptParser::Node::TYPE_CONSTANT); + StringName name = E->key(); - if (_is_class_member_property(p_script, name)) { - _set_error("Member '" + name + "' already exists as a class property.", p_class); - return ERR_ALREADY_EXISTS; - } + ERR_CONTINUE(E->get().expression->type != GDScriptParser::Node::TYPE_CONSTANT); - GDScriptParser::ConstantNode *constant = static_cast<GDScriptParser::ConstantNode *>(p_class->constant_expressions[i].expression); + GDScriptParser::ConstantNode *constant = static_cast<GDScriptParser::ConstantNode *>(E->get().expression); p_script->constants.insert(name, constant->value); -//p_script->constants[constant->value].make_const(); #ifdef TOOLS_ENABLED - p_script->member_lines[name] = p_class->constant_expressions[i].expression->line; + p_script->member_lines[name] = E->get().expression->line; #endif } @@ -1909,23 +2019,29 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons p_script->_signals[name] = p_class->_signals[i].arguments; } + + if (p_class->owner) { + parsed_classes.insert(p_class->name); + if (parsing_classes.has(p_class->name)) { + parsing_classes.erase(p_class->name); + } + } + //parse sub-classes for (int i = 0; i < p_class->subclasses.size(); i++) { StringName name = p_class->subclasses[i]->name; - Ref<GDScript> subclass; + Ref<GDScript> subclass = class_map[name]; - if (old_subclasses.has(name)) { - subclass = old_subclasses[name]; - } else { - subclass.instance(); + // Subclass might still be parsing, just skip it + if (!parsed_classes.has(name) && !parsing_classes.has(name)) { + parsing_classes.insert(name); + Error err = _parse_class_level(subclass.ptr(), p_script, p_class->subclasses[i], p_keep_state); + if (err) + return err; } - Error err = _parse_class(subclass.ptr(), p_script, p_class->subclasses[i], p_keep_state); - if (err) - return err; - #ifdef TOOLS_ENABLED p_script->member_lines[name] = p_class->subclasses[i]->line; @@ -1935,6 +2051,10 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons p_script->subclasses.insert(name, subclass); } + return OK; +} + +Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { //parse methods bool has_initializer = false; @@ -1975,50 +2095,10 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons } #ifdef DEBUG_ENABLED - //validate setters/getters if debug is enabled - for (int i = 0; i < p_class->variables.size(); i++) { - - if (p_class->variables[i].setter) { - const Map<StringName, GDScriptFunction *>::Element *E = p_script->get_member_functions().find(p_class->variables[i].setter); - if (!E) { - _set_error("Setter function '" + String(p_class->variables[i].setter) + "' not found in class.", NULL); - err_line = p_class->variables[i].line; - err_column = 0; - return ERR_PARSE_ERROR; - } - - if (E->get()->is_static()) { - - _set_error("Setter function '" + String(p_class->variables[i].setter) + "' is static.", NULL); - err_line = p_class->variables[i].line; - err_column = 0; - return ERR_PARSE_ERROR; - } - } - if (p_class->variables[i].getter) { - const Map<StringName, GDScriptFunction *>::Element *E = p_script->get_member_functions().find(p_class->variables[i].getter); - if (!E) { - _set_error("Getter function '" + String(p_class->variables[i].getter) + "' not found in class.", NULL); - err_line = p_class->variables[i].line; - err_column = 0; - return ERR_PARSE_ERROR; - } - - if (E->get()->is_static()) { - - _set_error("Getter function '" + String(p_class->variables[i].getter) + "' is static.", NULL); - err_line = p_class->variables[i].line; - err_column = 0; - return ERR_PARSE_ERROR; - } - } - } //validate instances if keeping state if (p_keep_state) { - - print_line("RELOAD KEEP " + p_script->path); for (Set<Object *>::Element *E = p_script->instances.front(); E;) { Set<Object *>::Element *N = E->next(); @@ -2065,22 +2145,67 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons } #endif + for (int i = 0; i < p_class->subclasses.size(); i++) { + StringName name = p_class->subclasses[i]->name; + Ref<GDScript> subclass = class_map[name]; + + Error err = _parse_class_blocks(subclass.ptr(), p_class->subclasses[i], p_keep_state); + if (err) { + return err; + } + } + p_script->valid = true; return OK; } +void GDScriptCompiler::_make_scripts(const GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + + Map<StringName, Ref<GDScript> > old_subclasses; + + if (p_keep_state) { + old_subclasses = p_script->subclasses; + } + + for (int i = 0; i < p_class->subclasses.size(); i++) { + StringName name = p_class->subclasses[i]->name; + + Ref<GDScript> subclass; + + if (old_subclasses.has(name)) { + subclass = old_subclasses[name]; + } else { + subclass.instance(); + } + + subclass->_owner = const_cast<GDScript *>(p_script); + class_map.insert(name, subclass); + + _make_scripts(subclass.ptr(), p_class->subclasses[i], p_keep_state); + } +} + Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state) { err_line = -1; err_column = -1; error = ""; parser = p_parser; + main_script = p_script; const GDScriptParser::Node *root = parser->get_parse_tree(); ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, ERR_INVALID_DATA); source = p_script->get_path(); - Error err = _parse_class(p_script, NULL, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); + // Create scripts for subclasses beforehand so they can be referenced + _make_scripts(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); + + Error err = _parse_class_level(p_script, NULL, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); + + if (err) + return err; + + err = _parse_class_blocks(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); if (err) return err; diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 237b0de9e7..55f5e2b48e 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -31,12 +31,17 @@ #ifndef GDSCRIPT_COMPILER_H #define GDSCRIPT_COMPILER_H +#include "core/set.h" #include "gdscript.h" #include "gdscript_parser.h" class GDScriptCompiler { const GDScriptParser *parser; + Map<StringName, Ref<GDScript> > class_map; + Set<StringName> parsed_classes; + Set<StringName> parsing_classes; + GDScript *main_script; struct CodeGen { GDScript *script; @@ -138,11 +143,15 @@ class GDScriptCompiler { bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level); bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false); + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const; + int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level); int _parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false); Error _parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1); Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false); - Error _parse_class(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _parse_class_level(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + void _make_scripts(const GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); int err_line; int err_column; StringName source; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index c0c3bd7b06..068e8d2d92 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -31,43 +31,59 @@ #include "gdscript.h" #include "core/engine.h" -#include "editor/editor_settings.h" +#include "core/global_constants.h" +#include "core/os/file_access.h" #include "gdscript_compiler.h" -#include "global_constants.h" -#include "os/file_access.h" #ifdef TOOLS_ENABLED #include "editor/editor_file_system.h" #include "editor/editor_settings.h" -#include "engine.h" #endif void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("#"); - p_delimiters->push_back("\"\"\" \"\"\""); } void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("\" \""); p_delimiters->push_back("' '"); + p_delimiters->push_back("\"\"\" \"\"\""); } Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const { - String _template = "extends %BASE%\n" "\n" "# Declare member variables here. Examples:\n" - "# var a = 2\n" - "# var b = \"text\"\n" + "# var a%INT_TYPE% = 2\n" + "# var b%STRING_TYPE% = \"text\"\n" "\n" "# Called when the node enters the scene tree for the first time.\n" - "func _ready():\n" + "func _ready()%VOID_RETURN%:\n" "%TS%pass # Replace with function body.\n" "\n" "# Called every frame. 'delta' is the elapsed time since the previous frame.\n" - "#func _process(delta):\n" + "#func _process(delta%FLOAT_TYPE%)%VOID_RETURN%:\n" "#%TS%pass\n"; +#ifdef TOOLS_ENABLED + if (EDITOR_DEF("text_editor/completion/add_type_hints", false)) { + _template = _template.replace("%INT_TYPE%", ": int"); + _template = _template.replace("%STRING_TYPE%", ": String"); + _template = _template.replace("%FLOAT_TYPE%", ": float"); + _template = _template.replace("%VOID_RETURN%", " -> void"); + } else { + _template = _template.replace("%INT_TYPE%", ""); + _template = _template.replace("%STRING_TYPE%", ""); + _template = _template.replace("%FLOAT_TYPE%", ""); + _template = _template.replace("%VOID_RETURN%", ""); + } +#else + _template = _template.replace("%INT_TYPE%", ""); + _template = _template.replace("%STRING_TYPE%", ""); + _template = _template.replace("%FLOAT_TYPE%", ""); + _template = _template.replace("%VOID_RETURN%", ""); +#endif + _template = _template.replace("%BASE%", p_base_class_name); _template = _template.replace("%TS%", _get_indentation()); @@ -91,11 +107,24 @@ void GDScriptLanguage::make_template(const String &p_class_name, const String &p p_script->set_source_code(src); } -bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { +bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { GDScriptParser parser; - Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path); + Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path, false, r_safe_lines); +#ifdef DEBUG_ENABLED + if (r_warnings) { + for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) { + const GDScriptWarning &warn = E->get(); + ScriptLanguage::Warning w; + w.line = warn.line; + w.code = (int)warn.code; + w.string_code = GDScriptWarning::get_name_from_code(warn.code); + w.message = warn.get_message(); + r_warnings->push_back(w); + } + } +#endif if (err) { r_line_error = parser.get_error_line(); r_col_error = parser.get_error_column(); @@ -154,7 +183,6 @@ int GDScriptLanguage::find_function(const String &p_function, const String &p_co if (tokenizer.get_token() == GDScriptTokenizer::TK_NEWLINE) { indent = tokenizer.get_token_line_indent(); } - //print_line("TOKEN: "+String(GDScriptTokenizer::get_token_name(tokenizer.get_token()))); if (indent == 0 && tokenizer.get_token() == GDScriptTokenizer::TK_PR_FUNCTION && tokenizer.get_token(1) == GDScriptTokenizer::TK_IDENTIFIER) { String identifier = tokenizer.get_token_identifier(1); @@ -163,7 +191,6 @@ int GDScriptLanguage::find_function(const String &p_function, const String &p_co } } tokenizer.advance(); - //print_line("NEXT: "+String(GDScriptTokenizer::get_token_name(tokenizer.get_token()))); } return -1; } @@ -415,63 +442,34 @@ void GDScriptLanguage::get_public_constants(List<Pair<String, Variant> > *p_cons String GDScriptLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const { +#ifdef TOOLS_ENABLED + bool th = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints"); +#else + bool th = false; +#endif + String s = "func " + p_name + "("; if (p_args.size()) { for (int i = 0; i < p_args.size(); i++) { if (i > 0) s += ", "; s += p_args[i].get_slice(":", 0); + if (th) { + String type = p_args[i].get_slice(":", 1); + if (!type.empty() && type != "var") { + s += ": " + type; + } + } } } - s += "):\n" + _get_indentation() + "pass # Replace with function body.\n"; + s += String(")") + (th ? " -> void" : "") + ":\n" + _get_indentation() + "pass # Replace with function body.\n"; return s; } -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) +//////// COMPLETION ////////// -struct GDScriptCompletionIdentifier { - - String enumeration; - StringName obj_type; - Ref<GDScript> script; - Variant::Type type; - Variant value; //im case there is a value, also return it - - GDScriptCompletionIdentifier() : - type(Variant::NIL) {} -}; - -static GDScriptCompletionIdentifier _get_type_from_variant(const Variant &p_variant, bool p_allow_gdnative_class = false) { - - GDScriptCompletionIdentifier t; - t.type = p_variant.get_type(); - t.value = p_variant; - if (p_variant.get_type() == Variant::OBJECT) { - Object *obj = p_variant; - if (obj) { - - if (p_allow_gdnative_class && Object::cast_to<GDScriptNativeClass>(obj)) { - t.obj_type = Object::cast_to<GDScriptNativeClass>(obj)->get_name(); - t.value = Variant(); - } else { - - t.obj_type = obj->get_class(); - } - } - } - return t; -} - -static GDScriptCompletionIdentifier _get_type_from_pinfo(const PropertyInfo &p_info) { - - GDScriptCompletionIdentifier t; - t.type = p_info.type; - if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { - t.obj_type = p_info.hint_string; - } - return t; -} +#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) struct GDScriptCompletionContext { @@ -480,1043 +478,1620 @@ struct GDScriptCompletionContext { const GDScriptParser::BlockNode *block; Object *base; String base_path; -}; - -static Ref<Reference> _get_parent_class(GDScriptCompletionContext &context) { - - if (context._class->extends_used) { - //do inheritance - String path = context._class->extends_file; - - Ref<GDScript> script; - Ref<GDScriptNativeClass> native; - - if (path != "") { - //path (and optionally subclasses) - - if (path.is_rel_path()) { + int line; - path = context.base_path.plus_file(path); - } - - if (ScriptCodeCompletionCache::get_singleton()) - script = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path); - else - script = ResourceLoader::load(path); + GDScriptCompletionContext() : + _class(NULL), + function(NULL), + block(NULL), + base(NULL) {} +}; - if (script.is_null()) { - return REF(); - } - if (!script->is_valid()) { +struct GDScriptCompletionIdentifier { + GDScriptParser::DataType type; + String enumeration; + Variant value; + const GDScriptParser::Node *assigned_expression; - return REF(); - } - //print_line("EXTENDS PATH: "+path+" script is "+itos(script.is_valid())+" indices is "+itos(script->member_indices.size())+" valid? "+itos(script->valid)); + GDScriptCompletionIdentifier() : + assigned_expression(NULL) {} +}; - if (context._class->extends_class.size()) { +static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Set<String> &r_list) { - for (int i = 0; i < context._class->extends_class.size(); i++) { + for (int i = 0; i < p_dir->get_file_count(); i++) { + r_list.insert("\"" + p_dir->get_file_path(i) + "\""); + } - String sub = context._class->extends_class[i]; - if (script->get_subclasses().has(sub)) { + for (int i = 0; i < p_dir->get_subdir_count(); i++) { + _get_directory_contents(p_dir->get_subdir(i), r_list); + } +} - script = script->get_subclasses()[sub]; - } else { +static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = true) { - return REF(); - } - } - } + if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + String enum_name = p_info.class_name; + if (enum_name.find(".") == -1) { + return enum_name; + } + return enum_name.get_slice(".", 1); + } - if (script.is_valid()) - return script; + String n = p_info.name; + int idx = n.find(":"); + if (idx != -1) { + return n.substr(idx + 1, n.length()); + } + if (p_info.type == Variant::OBJECT) { + if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { + return p_info.hint_string; } else { + return p_info.class_name.operator String(); + } + } + if (p_info.type == Variant::NIL) { + if (p_isarg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { + return "var"; + } else { + return "void"; + } + } - if (context._class->extends_class.size() == 0) { - ERR_PRINT("BUG"); - return REF(); - } - - String base = context._class->extends_class[0]; - - if (context._class->extends_class.size() > 1) { - - return REF(); - } - //if not found, try engine classes - if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { + return Variant::get_type_name(p_info.type); +} - return REF(); +static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { + GDScriptCompletionIdentifier ci; + ci.value = p_value; + ci.type.is_constant = true; + ci.type.has_type = true; + ci.type.kind = GDScriptParser::DataType::BUILTIN; + ci.type.builtin_type = p_value.get_type(); + + if (ci.type.builtin_type == Variant::OBJECT) { + Object *obj = p_value.operator Object *(); + if (!obj) { + return ci; + } + ci.type.native_type = obj->get_class_name(); + Ref<Script> scr = p_value; + if (scr.is_valid()) { + ci.type.is_meta_type = true; + } else { + ci.type.is_meta_type = false; + scr = obj->get_script(); + } + if (scr.is_valid()) { + ci.type.script_type = scr; + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + ci.type.kind = GDScriptParser::DataType::GDSCRIPT; + } else { + ci.type.kind = GDScriptParser::DataType::SCRIPT; } - - int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; - native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; - return native; + ci.type.native_type = scr->get_instance_base_type(); + } else { + ci.type.kind = GDScriptParser::DataType::NATIVE; } } - return Ref<Reference>(); + return ci; } -static GDScriptCompletionIdentifier _get_native_class(GDScriptCompletionContext &context) { - - GDScriptCompletionIdentifier id; +static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_property) { + GDScriptCompletionIdentifier ci; - REF pc = _get_parent_class(context); - if (!pc.is_valid()) { - return id; + if (p_property.type == Variant::NIL) { + // Variant + return ci; } - Ref<GDScriptNativeClass> nc = pc; - Ref<GDScript> s = pc; - if (s.is_null() && nc.is_null()) { - return id; - } - while (!s.is_null()) { - nc = s->get_native(); - s = s->get_base(); - } - if (nc.is_null()) { - return id; + if (p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + ci.enumeration = p_property.class_name; } - id.type = Variant::OBJECT; - if (context.base) - id.value = context.base; - id.obj_type = nc->get_name(); - return id; + ci.type.has_type = true; + ci.type.builtin_type = p_property.type; + if (p_property.type == Variant::OBJECT) { + ci.type.kind = GDScriptParser::DataType::NATIVE; + ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + } else { + ci.type.kind = GDScriptParser::DataType::BUILTIN; + } + return ci; } -static bool _guess_identifier_type(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type, bool p_for_indexing); - -static bool _guess_expression_type(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, GDScriptCompletionIdentifier &r_type, bool p_for_indexing = false) { - - if (p_node->type == GDScriptParser::Node::TYPE_CONSTANT) { - - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_node); - - r_type = _get_type_from_variant(cn->value); +static GDScriptCompletionIdentifier _type_from_gdtype(const GDScriptDataType &p_gdtype) { + GDScriptCompletionIdentifier ci; + if (!p_gdtype.has_type) { + return ci; + } - return true; - } else if (p_node->type == GDScriptParser::Node::TYPE_DICTIONARY) { + ci.type.has_type = true; + ci.type.builtin_type = p_gdtype.builtin_type; + ci.type.native_type = p_gdtype.native_type; + ci.type.script_type = p_gdtype.script_type; - r_type.type = Variant::DICTIONARY; + switch (p_gdtype.kind) { + case GDScriptDataType::BUILTIN: { + ci.type.kind = GDScriptParser::DataType::BUILTIN; + } break; + case GDScriptDataType::NATIVE: { + ci.type.kind = GDScriptParser::DataType::NATIVE; + } break; + case GDScriptDataType::GDSCRIPT: { + ci.type.kind = GDScriptParser::DataType::GDSCRIPT; + } break; + case GDScriptDataType::SCRIPT: { + ci.type.kind = GDScriptParser::DataType::SCRIPT; + } break; + } + return ci; +} - //what the heck, fill it anyway - const GDScriptParser::DictionaryNode *an = static_cast<const GDScriptParser::DictionaryNode *>(p_node); - Dictionary d; - for (int i = 0; i < an->elements.size(); i++) { - GDScriptCompletionIdentifier k; - if (_guess_expression_type(context, an->elements[i].key, p_line, k) && k.value.get_type() != Variant::NIL) { - GDScriptCompletionIdentifier v; - if (_guess_expression_type(context, an->elements[i].value, p_line, v)) { - d[k.value] = v.value; +static bool _guess_identifier_type(const GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); +static bool _guess_identifier_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); +static bool _guess_method_return_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); + +static bool _guess_expression_type(const GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_expression, GDScriptCompletionIdentifier &r_type) { + bool found = false; + switch (p_expression->type) { + case GDScriptParser::Node::TYPE_CONSTANT: { + const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_expression); + r_type = _type_from_variant(cn->value); + found = true; + } break; + case GDScriptParser::Node::TYPE_SELF: { + if (p_context._class) { + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::CLASS; + r_type.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); + r_type.type.is_constant = true; + r_type.value = p_context.base; + found = true; + } + } break; + case GDScriptParser::Node::TYPE_IDENTIFIER: { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); + found = _guess_identifier_type(p_context, id->name, r_type); + } break; + case GDScriptParser::Node::TYPE_DICTIONARY: { + // Try to recreate the dictionary + const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); + Dictionary d; + bool full = true; + for (int i = 0; i < dn->elements.size(); i++) { + GDScriptCompletionIdentifier key; + if (_guess_expression_type(p_context, dn->elements[i].key, key)) { + GDScriptCompletionIdentifier value; + if (_guess_expression_type(p_context, dn->elements[i].value, value)) { + if (!value.type.is_constant) { + full = false; + break; + } + d[key.value] = value.value; + } else { + full = false; + break; + } + } else { + full = false; + break; } } - } - r_type.value = d; - return true; - } else if (p_node->type == GDScriptParser::Node::TYPE_ARRAY) { - - r_type.type = Variant::ARRAY; - //what the heck, fill it anyway - const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_node); - Array arr; - arr.resize(an->elements.size()); - for (int i = 0; i < an->elements.size(); i++) { - GDScriptCompletionIdentifier ci; - if (_guess_expression_type(context, an->elements[i], p_line, ci)) { - arr[i] = ci.value; + if (full) { + // If not fully constant, setting this value is detrimental to the inference + r_type.value = d; + r_type.type.is_constant = true; } - } - r_type.value = arr; - return true; - - } else if (p_node->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - - MethodInfo mi = GDScriptFunctions::get_info(static_cast<const GDScriptParser::BuiltInFunctionNode *>(p_node)->function); - r_type = _get_type_from_pinfo(mi.return_val); - - return true; - } else if (p_node->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - - return _guess_identifier_type(context, p_line - 1, static_cast<const GDScriptParser::IdentifierNode *>(p_node)->name, r_type, p_for_indexing); - } else if (p_node->type == GDScriptParser::Node::TYPE_SELF) { - //eeh... - - r_type = _get_native_class(context); - return r_type.type != Variant::NIL; - - } else if (p_node->type == GDScriptParser::Node::TYPE_OPERATOR) { - - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node); - if (op->op == GDScriptParser::OperatorNode::OP_CALL) { - - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { - - const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); - r_type.type = tn->vtype; - return true; - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - - const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); - return _guess_expression_type(context, bin, p_line, r_type); - - } else if (op->arguments.size() > 1 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - - StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name; - - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && String(id) == "new") { - - //shortcut - StringName identifier = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name; - - if (ClassDB::class_exists(identifier)) { - r_type.type = Variant::OBJECT; - r_type.value = Variant(); - r_type.obj_type = identifier; - return true; - } + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = Variant::DICTIONARY; + } break; + case GDScriptParser::Node::TYPE_ARRAY: { + // Try to recreate the array + const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); + Array a; + bool full = true; + a.resize(an->elements.size()); + for (int i = 0; i < an->elements.size(); i++) { + GDScriptCompletionIdentifier value; + if (_guess_expression_type(p_context, an->elements[i], value)) { + a[i] = value.value; + } else { + full = false; + break; } - - GDScriptCompletionIdentifier base; - if (!_guess_expression_type(context, op->arguments[0], p_line, base)) - return false; - - if (base.type == Variant::OBJECT) { - - if (id.operator String() == "new" && base.value.get_type() == Variant::OBJECT) { - - Object *obj = base.value; - if (obj && Object::cast_to<GDScriptNativeClass>(obj)) { - GDScriptNativeClass *gdnc = Object::cast_to<GDScriptNativeClass>(obj); - r_type.type = Variant::OBJECT; - r_type.value = Variant(); - r_type.obj_type = gdnc->get_name(); - return true; - } else { - - if (base.obj_type != StringName()) { - - r_type.type = Variant::OBJECT; - r_type.value = Variant(); - r_type.obj_type = base.obj_type; - return true; - } + } + if (full) { + // If not fully constant, setting this value is detrimental to the inference + r_type.value = a; + } + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = Variant::ARRAY; + } break; + case GDScriptParser::Node::TYPE_OPERATOR: { + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_expression); + switch (op->op) { + case GDScriptParser::OperatorNode::OP_CALL: { + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { + const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = tn->vtype; + found = true; + break; + } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { + const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); + MethodInfo mi = GDScriptFunctions::get_info(bin->function); + r_type = _type_from_property(mi.return_val); + found = true; + break; + } else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { + StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name; + + GDScriptCompletionContext c = p_context; + c.line = op->line; + + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(c, op->arguments[0], base)) { + found = false; + break; } - } - - if (ClassDB::has_method(base.obj_type, id)) { - -#ifdef TOOLS_ENABLED - MethodBind *mb = ClassDB::get_method(base.obj_type, id); - PropertyInfo pi = mb->get_return_info(); - - //try calling the function if constant and all args are constant, should not crash.. - Object *baseptr = base.value; - if (mb->is_const() && pi.type == Variant::OBJECT) { + // Try call if constant methods with constant arguments + if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) { + GDScriptParser::DataType native_type = base.type; - bool all_valid = true; - Vector<Variant> args; - for (int i = 2; i < op->arguments.size(); i++) { - GDScriptCompletionIdentifier arg; + while (native_type.kind == GDScriptParser::DataType::CLASS) { + native_type = native_type.class_type->base_type; + } - if (_guess_expression_type(context, op->arguments[i], p_line, arg)) { - if (arg.value.get_type() != Variant::NIL && arg.value.get_type() != Variant::OBJECT) { // calling with object seems dangerous, i don' t know - args.push_back(arg.value); + while (native_type.kind == GDScriptParser::DataType::GDSCRIPT || native_type.kind == GDScriptParser::DataType::SCRIPT) { + if (native_type.script_type.is_valid()) { + Ref<Script> parent = native_type.script_type->get_base_script(); + if (parent.is_valid()) { + native_type.script_type = parent; } else { - all_valid = false; - break; + native_type.kind = GDScriptParser::DataType::NATIVE; + native_type.native_type = native_type.script_type->get_instance_base_type(); + if (!ClassDB::class_exists(native_type.native_type)) { + native_type.native_type = String("_") + native_type.native_type; + if (!ClassDB::class_exists(native_type.native_type)) { + native_type.has_type = false; + } + } } - } else { - all_valid = false; } } - if (all_valid && String(id) == "get_node" && ClassDB::is_parent_class(base.obj_type, "Node") && args.size()) { - - String arg1 = args[0]; - if (arg1.begins_with("/root/")) { - String which = arg1.get_slice("/", 2); - if (which != "") { - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - //print_line("find singleton"); - - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - - String s = E->get().name; - if (!s.begins_with("autoload/")) - continue; - //print_line("found "+s); - String name = s.get_slice("/", 1); - //print_line("name: "+name+", which: "+which); - if (name == which) { - String script = ProjectSettings::get_singleton()->get(s); - - if (!script.begins_with("res://")) { - script = "res://" + script; - } - - if (!script.ends_with(".gd")) { - //not a script, try find the script anyway, - //may have some success - script = script.get_basename() + ".gd"; - } - - if (FileAccess::exists(script)) { - - //print_line("is a script"); - - Ref<Script> scr; - if (ScriptCodeCompletionCache::get_singleton()) - scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script); - else - scr = ResourceLoader::load(script); - - r_type.obj_type = "Node"; - r_type.type = Variant::OBJECT; - r_type.script = scr; - r_type.value = Variant(); - - return true; - } + if (native_type.has_type && native_type.kind == GDScriptParser::DataType::NATIVE) { + MethodBind *mb = ClassDB::get_method(native_type.native_type, id); + if (mb && mb->is_const()) { + bool all_is_const = true; + Vector<Variant> args; + GDScriptCompletionContext c = p_context; + c.line = op->line; + for (int i = 2; all_is_const && i < op->arguments.size(); i++) { + GDScriptCompletionIdentifier arg; + + if (_guess_expression_type(c, op->arguments[i], arg)) { + if (arg.type.has_type && arg.type.is_constant && arg.value.get_type() != Variant::OBJECT) { + args.push_back(arg.value); + } else { + all_is_const = false; } + } else { + all_is_const = false; } } - } - } - - if (baseptr) { - if (all_valid) { - Vector<const Variant *> argptr; - for (int i = 0; i < args.size(); i++) { - argptr.push_back(&args[i]); + Object *baseptr = base.value; + + if (all_is_const && String(id) == "get_node" && ClassDB::is_parent_class(native_type.native_type, "Node") && args.size()) { + + String arg1 = args[0]; + if (arg1.begins_with("/root/")) { + String which = arg1.get_slice("/", 2); + if (which != "") { + // Try singletons first + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) { + r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); + found = true; + } else { + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + String name = s.get_slice("/", 1); + if (name == which) { + String script = ProjectSettings::get_singleton()->get(s); + + if (script.begins_with("*")) { + script = script.right(1); + } + + if (!script.begins_with("res://")) { + script = "res://" + script; + } + + if (!script.ends_with(".gd")) { + //not a script, try find the script anyway, + //may have some success + script = script.get_basename() + ".gd"; + } + + if (FileAccess::exists(script)) { + Ref<Script> scr; + if (ScriptCodeCompletionCache::get_singleton()) { + scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script); + } else { + scr = ResourceLoader::load(script); + } + if (scr.is_valid()) { + r_type.type.has_type = true; + r_type.type.script_type = scr; + r_type.type.is_constant = false; + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + r_type.type.kind = GDScriptParser::DataType::GDSCRIPT; + } else { + r_type.type.kind = GDScriptParser::DataType::SCRIPT; + } + r_type.value = Variant(); + found = true; + } + } + break; + } + } + } + } + } } - Variant::CallError ce; - Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce); - - if (ce.error == Variant::CallError::CALL_OK && ret.get_type() != Variant::NIL) { + if (!found && all_is_const && baseptr) { + Vector<const Variant *> argptr; + for (int i = 0; i < args.size(); i++) { + argptr.push_back(&args[i]); + } - if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != NULL) { + Variant::CallError ce; + Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce); - r_type = _get_type_from_variant(ret); - return true; + if (ce.error == Variant::CallError::CALL_OK && ret.get_type() != Variant::NIL) { + if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != NULL) { + r_type = _type_from_variant(ret); + found = true; + } } } } } } - r_type.type = pi.type; - if (pi.hint == PROPERTY_HINT_RESOURCE_TYPE) { - r_type.obj_type = pi.hint_string; + if (!found) { + found = _guess_method_return_type_from_base(c, base, id, r_type); } - - return true; -#else - return false; -#endif - } else { - return false; } - } else { - //method for some variant.. - Variant::CallError ce; - Variant v = Variant::construct(base.type, NULL, 0, ce); - List<MethodInfo> mi; - v.get_method_list(&mi); - for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) { - - if (!E->get().name.begins_with("_") && E->get().name == id.operator String()) { - - MethodInfo mi = E->get(); - r_type.type = mi.return_val.type; - if (mi.return_val.hint == PROPERTY_HINT_RESOURCE_TYPE) { - r_type.obj_type = mi.return_val.hint_string; - } - return true; - } + } break; + case GDScriptParser::OperatorNode::OP_PARENT_CALL: { + if (!p_context._class || !op->arguments.size() || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { + break; } - } - } - } else if (op->op == GDScriptParser::OperatorNode::OP_INDEX || op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) { - - GDScriptCompletionIdentifier p1; - GDScriptCompletionIdentifier p2; - if (op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) { + StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name; - if (op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - String id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name; - p2.type = Variant::STRING; - p2.value = id; - } + GDScriptCompletionIdentifier base; + base.value = p_context.base; + base.type = p_context._class->base_type; - } else { - if (op->arguments[1]) { - if (!_guess_expression_type(context, op->arguments[1], p_line, p2)) { + GDScriptCompletionContext c = p_context; + c.line = op->line; - return false; + found = _guess_method_return_type_from_base(c, base, id, r_type); + } break; + case GDScriptParser::OperatorNode::OP_INDEX_NAMED: { + if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { + found = false; + break; } - } - } - - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_ARRAY) { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); - const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(op->arguments[0]); - if (p2.value.is_num()) { - int index = p2.value; - if (index < 0 || index >= an->elements.size()) - return false; - return _guess_expression_type(context, an->elements[index], p_line, r_type); - } + GDScriptCompletionContext c = p_context; + c.line = op->line; - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) { - - const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]); - - if (p2.value.get_type() == Variant::NIL) - return false; - - for (int i = 0; i < dn->elements.size(); i++) { - - GDScriptCompletionIdentifier k; - - if (!_guess_expression_type(context, dn->elements[i].key, p_line, k)) { - - return false; + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(c, op->arguments[0], base)) { + found = false; + break; } - if (k.value.get_type() == Variant::NIL) - return false; + if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(id->name))) { + Variant value = base.value.operator Dictionary()[String(id->name)]; + r_type = _type_from_variant(value); + found = true; + break; + } - if (k.value == p2.value) { + const GDScriptParser::DictionaryNode *dn = NULL; + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) { + dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]); + } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) { + dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression); + } - return _guess_expression_type(context, dn->elements[i].value, p_line, r_type); + if (dn) { + for (int i = 0; i < dn->elements.size(); i++) { + GDScriptCompletionIdentifier key; + if (!_guess_expression_type(c, dn->elements[i].key, key)) { + continue; + } + if (key.value == String(id->name)) { + r_type.assigned_expression = dn->elements[i].value; + found = _guess_expression_type(c, dn->elements[i].value, r_type); + break; + } + } } - } - } else { + if (!found) { + found = _guess_identifier_type_from_base(c, base, id->name, r_type); + } + } break; + case GDScriptParser::OperatorNode::OP_INDEX: { + if (op->arguments.size() < 2) { + found = false; + break; + } - if (op->arguments[0]) { - if (!_guess_expression_type(context, op->arguments[0], p_line, p1)) { + GDScriptCompletionContext c = p_context; + c.line = op->line; - return false; + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(c, op->arguments[0], base)) { + found = false; + break; } - } - - if (p1.value.get_type() == Variant::OBJECT) { - //?? - if (p1.obj_type != StringName() && p2.type == Variant::STRING) { + GDScriptCompletionIdentifier index; + if (!_guess_expression_type(c, op->arguments[1], index)) { + found = false; + break; + } - StringName base_type = p1.obj_type; + if (base.value.in(index.value)) { + Variant value = base.value.get(index.value); + r_type = _type_from_variant(value); + found = true; + break; + } - if (p1.obj_type == "GDScriptNativeClass") { - //native enum - Ref<GDScriptNativeClass> gdn = p1.value; - if (gdn.is_valid()) { + // Look if it is a dictionary node + const GDScriptParser::DictionaryNode *dn = NULL; + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) { + dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]); + } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) { + dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression); + } - base_type = gdn->get_name(); + if (dn) { + for (int i = 0; i < dn->elements.size(); i++) { + GDScriptCompletionIdentifier key; + if (!_guess_expression_type(c, dn->elements[i].key, key)) { + continue; } - } - StringName index = p2.value; - bool valid; - Variant::Type t = ClassDB::get_property_type(base_type, index, &valid); - if (t != Variant::NIL && valid) { - r_type.type = t; - if (t == Variant::INT || t == Variant::OBJECT) { -//check for enum! -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) - - StringName getter = ClassDB::get_property_getter(base_type, index); - if (getter != StringName()) { - MethodBind *mb = ClassDB::get_method(base_type, getter); - if (mb) { - PropertyInfo rt = mb->get_return_info(); - if ((rt.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && t == Variant::INT) { - r_type.enumeration = rt.class_name; - } else if (t == Variant::OBJECT) { - - r_type.obj_type = rt.class_name; - } - } - } -#endif + if (key.value == index.value) { + r_type.assigned_expression = dn->elements[i].value; + found = _guess_expression_type(p_context, dn->elements[i].value, r_type); + break; } - - return true; } } - } else if (p1.value.get_type() != Variant::NIL) { - bool valid; - Variant ret = p1.value.get(p2.value, &valid); - if (valid) { - r_type = _get_type_from_variant(ret); - return true; + // Look if it is an array node + if (!found && index.value.is_num()) { + int idx = index.value; + const GDScriptParser::ArrayNode *an = NULL; + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_ARRAY) { + an = static_cast<const GDScriptParser::ArrayNode *>(op->arguments[0]); + } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_ARRAY) { + an = static_cast<const GDScriptParser::ArrayNode *>(base.assigned_expression); + } + + if (an && idx >= 0 && an->elements.size() > idx) { + r_type.assigned_expression = an->elements[idx]; + found = _guess_expression_type(c, an->elements[idx], r_type); + break; + } } - } else { - if (p1.type != Variant::NIL) { - Variant::CallError ce; - Variant base = Variant::construct(p1.type, NULL, 0, ce); - bool valid; - Variant ret = base.get(p2.value, &valid); + // Look for valid indexing in other types + if (!found && (index.value.get_type() == Variant::STRING || index.value.get_type() == Variant::NODE_PATH)) { + StringName id = index.value; + found = _guess_identifier_type_from_base(c, base, id, r_type); + } else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) { + Variant::CallError err; + Variant base_val = Variant::construct(base.type.builtin_type, NULL, 0, err); + bool valid = false; + Variant res = base_val.get(index.value, &valid); if (valid) { - r_type = _get_type_from_variant(ret); - return true; + r_type = _type_from_variant(res); + r_type.value = Variant(); + r_type.type.is_constant = false; + found = true; } } - } - } + } break; + default: { + if (op->arguments.size() < 2) { + found = false; + break; + } - } else { + Variant::Operator vop = Variant::OP_MAX; + switch (op->op) { + case GDScriptParser::OperatorNode::OP_ADD: vop = Variant::OP_ADD; break; + case GDScriptParser::OperatorNode::OP_SUB: vop = Variant::OP_SUBTRACT; break; + case GDScriptParser::OperatorNode::OP_MUL: vop = Variant::OP_MULTIPLY; break; + case GDScriptParser::OperatorNode::OP_DIV: vop = Variant::OP_DIVIDE; break; + case GDScriptParser::OperatorNode::OP_MOD: vop = Variant::OP_MODULE; break; + case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: vop = Variant::OP_SHIFT_LEFT; break; + case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: vop = Variant::OP_SHIFT_RIGHT; break; + case GDScriptParser::OperatorNode::OP_BIT_AND: vop = Variant::OP_BIT_AND; break; + case GDScriptParser::OperatorNode::OP_BIT_OR: vop = Variant::OP_BIT_OR; break; + case GDScriptParser::OperatorNode::OP_BIT_XOR: vop = Variant::OP_BIT_XOR; break; + default: {} + } - Variant::Operator vop = Variant::OP_MAX; - switch (op->op) { - case GDScriptParser::OperatorNode::OP_ADD: vop = Variant::OP_ADD; break; - case GDScriptParser::OperatorNode::OP_SUB: vop = Variant::OP_SUBTRACT; break; - case GDScriptParser::OperatorNode::OP_MUL: vop = Variant::OP_MULTIPLY; break; - case GDScriptParser::OperatorNode::OP_DIV: vop = Variant::OP_DIVIDE; break; - case GDScriptParser::OperatorNode::OP_MOD: vop = Variant::OP_MODULE; break; - case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: vop = Variant::OP_SHIFT_LEFT; break; - case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: vop = Variant::OP_SHIFT_RIGHT; break; - case GDScriptParser::OperatorNode::OP_BIT_AND: vop = Variant::OP_BIT_AND; break; - case GDScriptParser::OperatorNode::OP_BIT_OR: vop = Variant::OP_BIT_OR; break; - case GDScriptParser::OperatorNode::OP_BIT_XOR: vop = Variant::OP_BIT_XOR; break; - default: {} - } - - if (vop == Variant::OP_MAX) - return false; + if (vop == Variant::OP_MAX) { + break; + } - GDScriptCompletionIdentifier p1; - GDScriptCompletionIdentifier p2; + GDScriptCompletionContext context = p_context; + context.line = op->line; - if (op->arguments[0]) { - if (!_guess_expression_type(context, op->arguments[0], p_line, p1)) { + GDScriptCompletionIdentifier p1; + GDScriptCompletionIdentifier p2; - return false; - } - } + if (!_guess_expression_type(context, op->arguments[0], p1)) { + found = false; + break; + } - if (op->arguments.size() > 1) { - if (!_guess_expression_type(context, op->arguments[1], p_line, p2)) { + if (!_guess_expression_type(context, op->arguments[1], p2)) { + found = false; + break; + } - return false; - } - } + Variant::CallError ce; + bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT; + Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type.builtin_type, NULL, 0, ce); + bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT; + Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type.builtin_type, NULL, 0, ce); + // avoid potential invalid ops + if ((vop == Variant::OP_DIVIDE || vop == Variant::OP_MODULE) && v2.get_type() == Variant::INT) { + v2 = 1; + v2_use_value = false; + } + if (vop == Variant::OP_DIVIDE && v2.get_type() == Variant::REAL) { + v2 = 1.0; + v2_use_value = false; + } - Variant::CallError ce; - bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT; - Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type, NULL, 0, ce); - bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT; - Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type, NULL, 0, ce); - // avoid potential invalid ops - if ((vop == Variant::OP_DIVIDE || vop == Variant::OP_MODULE) && v2.get_type() == Variant::INT) { - v2 = 1; - v2_use_value = false; - } - if (vop == Variant::OP_DIVIDE && v2.get_type() == Variant::REAL) { - v2 = 1.0; - v2_use_value = false; + Variant res; + bool valid; + Variant::evaluate(vop, v1, v2, res, valid); + if (!valid) { + found = false; + break; + } + r_type = _type_from_variant(res); + if (!v1_use_value || !v2_use_value) { + r_type.value = Variant(); + r_type.type.is_constant = false; + } + + found = true; + } break; } + } break; + default: {} + } - Variant r; - bool valid; - Variant::evaluate(vop, v1, v2, r, valid); - if (!valid) - return false; - r_type.type = r.get_type(); - if (v1_use_value && v2_use_value) - r_type.value = r; + // It may have found a null, but that's never useful + if (found && r_type.type.has_type && r_type.type.kind == GDScriptParser::DataType::BUILTIN && r_type.type.builtin_type == Variant::NIL) { + found = false; + } - return true; + // Check type hint last. For collections we want chance to get the actual value first + // This way we can detect types from the content of dictionaries and arrays + if (!found && p_expression->get_datatype().has_type) { + r_type.type = p_expression->get_datatype(); + if (!r_type.assigned_expression) { + r_type.assigned_expression = p_expression; } + found = true; } - return false; + return found; } -static bool _guess_identifier_type_in_block(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { +static bool _guess_identifier_type(const GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { - if (context.block->if_condition && context.block->if_condition->type == GDScriptParser::Node::TYPE_OPERATOR && static_cast<const GDScriptParser::OperatorNode *>(context.block->if_condition)->op == GDScriptParser::OperatorNode::OP_IS) { - //is used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. - //super dirty hack, but very useful - //credit: Zylann - //TODO: this could be hacked to detect ANDed conditions too.. - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(context.block->if_condition); - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name == p_identifier) { - //bingo - if (_guess_expression_type(context, op->arguments[1], op->line, r_type)) { - return true; + // Look in blocks first + const GDScriptParser::BlockNode *blk = p_context.block; + int last_assign_line = -1; + const GDScriptParser::Node *last_assigned_expression = NULL; + GDScriptParser::DataType var_type; + while (blk) { + if (blk->variables.has(p_identifier)) { + if (blk->variables[p_identifier]->line > p_context.line) { + return false; } - } - } - GDScriptCompletionIdentifier gdi = _get_native_class(context); - if (gdi.obj_type != StringName()) { - bool valid; - Variant::Type t = ClassDB::get_property_type(gdi.obj_type, p_identifier, &valid); - if (t != Variant::NIL && valid) { - r_type.type = t; - if (t == Variant::INT) { -//check for enum! -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) + var_type = blk->variables[p_identifier]->datatype; - StringName getter = ClassDB::get_property_getter(gdi.obj_type, p_identifier); - if (getter != StringName()) { - MethodBind *mb = ClassDB::get_method(gdi.obj_type, getter); - if (mb) { - PropertyInfo rt = mb->get_return_info(); - if (rt.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - r_type.enumeration = rt.class_name; - } - } + if (!last_assigned_expression && blk->variables[p_identifier]->assign && blk->variables[p_identifier]->assign->type == GDScriptParser::Node::TYPE_OPERATOR) { + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->variables[p_identifier]->assign); + if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN && op->arguments.size() >= 2) { + last_assign_line = op->line; + last_assigned_expression = op->arguments[1]; } -#endif } - return true; } - } - - const GDScriptParser::Node *last_assign = NULL; - int last_assign_line = -1; - - for (int i = 0; i < context.block->statements.size(); i++) { - - if (context.block->statements[i]->line > p_line) - continue; - - if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) { - const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(context.block->statements[i]); + for (const List<GDScriptParser::Node *>::Element *E = blk->statements.front(); E; E = E->next()) { + const GDScriptParser::Node *expr = E->get(); + if (expr->line > p_context.line || expr->type != GDScriptParser::Node::TYPE_OPERATOR) { + continue; + } - if (lv->assign && lv->name == p_identifier) { + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(expr); + if (op->op != GDScriptParser::OperatorNode::OP_ASSIGN || op->line < last_assign_line) { + continue; + } - last_assign = lv->assign; - last_assign_line = context.block->statements[i]->line; + if (op->arguments.size() >= 2 && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]); + if (id->name == p_identifier) { + last_assign_line = op->line; + last_assigned_expression = op->arguments[1]; + } } } - if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_OPERATOR) { - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(context.block->statements[i]); - if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) { - - if (op->arguments.size() && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]); - - if (id->name == p_identifier) { - - last_assign = op->arguments[1]; - last_assign_line = context.block->statements[i]->line; - } + if (blk->if_condition && blk->if_condition->type == GDScriptParser::Node::TYPE_OPERATOR && static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition)->op == GDScriptParser::OperatorNode::OP_IS) { + //is used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. + //super dirty hack, but very useful + //credit: Zylann + //TODO: this could be hacked to detect ANDed conditions too.. + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition); + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name == p_identifier) { + //bingo + GDScriptCompletionContext c = p_context; + c.line = op->line; + c.block = blk; + if (_guess_expression_type(p_context, op->arguments[1], r_type)) { + r_type.type.is_meta_type = false; + return true; } } } - } - - //use the last assignment, (then backwards?) - if (last_assign && last_assign_line != p_line) { - return _guess_expression_type(context, last_assign, last_assign_line, r_type); + blk = blk->parent_block; } - return false; -} - -static bool _guess_identifier_from_assignment_in_function(GDScriptCompletionContext &context, int p_src_line, const StringName &p_identifier, const StringName &p_function, GDScriptCompletionIdentifier &r_type) { - - const GDScriptParser::FunctionNode *func = NULL; - for (int i = 0; i < context._class->functions.size(); i++) { - if (context._class->functions[i]->name == p_function) { - func = context._class->functions[i]; - break; + if (last_assigned_expression && last_assign_line != p_context.line) { + GDScriptCompletionContext c = p_context; + c.line = last_assign_line; + r_type.assigned_expression = last_assigned_expression; + if (_guess_expression_type(c, last_assigned_expression, r_type)) { + return true; } } - if (!func) - return false; + if (var_type.has_type) { + r_type.type = var_type; + return true; + } - for (int i = 0; i < func->body->statements.size(); i++) { + if (p_context.function) { + for (int i = 0; i < p_context.function->arguments.size(); i++) { + if (p_context.function->arguments[i] == p_identifier) { + if (p_context.function->argument_types[i].has_type) { + r_type.type = p_context.function->argument_types[i]; + return true; + } - if (func->body->statements[i]->line == p_src_line) { - break; + int def_from = p_context.function->arguments.size() - p_context.function->default_values.size(); + if (i >= def_from) { + int def_idx = i - def_from; + if (p_context.function->default_values[def_idx]->type == GDScriptParser::Node::TYPE_OPERATOR) { + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_context.function->default_values[def_idx]); + if (op->arguments.size() < 2) { + return false; + } + GDScriptCompletionContext c = p_context; + c.function = NULL; + c.block = NULL; + return _guess_expression_type(c, op->arguments[1], r_type); + } + } + break; + } } - if (func->body->statements[i]->type == GDScriptParser::BlockNode::TYPE_OPERATOR) { - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(func->body->statements[i]); - if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) { - - if (op->arguments.size() && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]); - - if (id->name == p_identifier) { - - return _guess_expression_type(context, op->arguments[1], func->body->statements[i]->line, r_type); + GDScriptParser::DataType base_type = p_context._class->base_type; + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = base_type.script_type; + if (gds.is_valid() && gds->has_method(p_context.function->name)) { + GDScriptFunction *func = gds->get_member_functions()[p_context.function->name]; + if (func) { + for (int i = 0; i < func->get_argument_count(); i++) { + if (func->get_argument_name(i) == p_identifier) { + r_type = _type_from_gdtype(func->get_argument_type(i)); + return true; + } + } + } + Ref<GDScript> base_gds = gds->get_base_script(); + if (base_gds.is_valid()) { + base_type.kind = GDScriptParser::DataType::GDSCRIPT; + base_type.script_type = base_gds; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); + } + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); } - } + } break; + case GDScriptParser::DataType::NATIVE: { + List<MethodInfo> methods; + ClassDB::get_method_list(base_type.native_type, &methods); + ClassDB::get_virtual_methods(base_type.native_type, &methods); + + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name == p_context.function->name) { + MethodInfo &mi = E->get(); + for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { + if (E->get().name == p_identifier) { + r_type = _type_from_property(E->get()); + return true; + } + } + } + } + base_type.has_type = false; + } break; + default: { + base_type.has_type = false; + } break; } } } - return false; -} - -static bool _guess_identifier_type(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type, bool p_for_indexing) { - - //go to block first - - const GDScriptParser::BlockNode *block = context.block; - - while (block) { + // Check current class (including inheritance) + if (p_context._class) { + GDScriptCompletionIdentifier context_base; + context_base.value = p_context.base; + context_base.type.has_type = true; + context_base.type.kind = GDScriptParser::DataType::CLASS; + context_base.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); + context_base.type.is_meta_type = p_context.function && p_context.function->_static; - GDScriptCompletionContext c = context; - c.block = block; - - if (_guess_identifier_type_in_block(c, p_line, p_identifier, r_type)) { + if (_guess_identifier_type_from_base(p_context, context_base, p_identifier, r_type)) { return true; } - - block = block->parent_block; } - //guess from argument if virtual - if (context.function && context.function->name != StringName()) { - - int argindex = -1; - - for (int i = 0; i < context.function->arguments.size(); i++) { + // Check named scripts + if (ScriptServer::is_global_class(p_identifier)) { + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier)); + if (scr.is_valid()) { + r_type = _type_from_variant(scr); + r_type.type.is_meta_type = true; + return true; + } + return false; + } - if (context.function->arguments[i] == p_identifier) { - argindex = i; + for (int i = 0; i < 2; i++) { + StringName target_id; + switch (i) { + case 0: + // Check ClassDB + target_id = p_identifier; + break; + case 1: + // ClassDB again for underscore-prefixed classes + target_id = String("_") + p_identifier; break; + } + + if (ClassDB::class_exists(target_id)) { + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::NATIVE; + r_type.type.native_type = target_id; + if (Engine::get_singleton()->has_singleton(target_id)) { + r_type.type.is_meta_type = false; + r_type.value = Engine::get_singleton()->get_singleton_object(target_id); + } else { + r_type.type.is_meta_type = true; + const Map<StringName, int>::Element *target_elem = GDScriptLanguage::get_singleton()->get_global_map().find(target_id); + // Check because classes like EditorNode are in ClassDB by now, but unknown to GDScript + if (!target_elem) { + return false; + } + int idx = target_elem->get(); + r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx]; } + return true; } + } - if (argindex != -1) { - GDScriptCompletionIdentifier id = _get_native_class(context); - if (id.type == Variant::OBJECT && id.obj_type != StringName()) { - //this kinda sucks but meh + // Check autoload singletons + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) { + r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]); + return true; + } - List<MethodInfo> vmethods; - ClassDB::get_virtual_methods(id.obj_type, &vmethods); - for (List<MethodInfo>::Element *E = vmethods.front(); E; E = E->next()) { + return false; +} - if (E->get().name == context.function->name && argindex < E->get().arguments.size()) { +static bool _guess_identifier_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { + GDScriptParser::DataType base_type = p_base.type; + bool _static = base_type.is_meta_type; + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + if (base_type.class_type->constant_expressions.has(p_identifier)) { + GDScriptParser::ClassNode::Constant c = base_type.class_type->constant_expressions[p_identifier]; + r_type.type = c.type; + if (c.expression->type == GDScriptParser::Node::TYPE_CONSTANT) { + r_type.value = static_cast<const GDScriptParser::ConstantNode *>(c.expression)->value; + } + return true; + } - PropertyInfo arg = E->get().arguments[argindex]; + if (!_static) { + for (int i = 0; i < base_type.class_type->variables.size(); i++) { + GDScriptParser::ClassNode::Member m = base_type.class_type->variables[i]; + if (m.identifier == p_identifier) { + if (m.expression) { + if (p_context.line == m.expression->line) { + // Variable used in the same expression + return false; + } + if (_guess_expression_type(p_context, m.expression, r_type)) { + return true; + } + if (m.expression->get_datatype().has_type) { + r_type.type = m.expression->get_datatype(); + return true; + } + } + if (m.data_type.has_type) { + r_type.type = m.data_type; + return true; + } + return false; + } + } + } + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = base_type.script_type; + if (gds.is_valid()) { + if (gds->get_constants().has(p_identifier)) { + r_type = _type_from_variant(gds->get_constants()[p_identifier]); + return true; + } + if (!_static) { + const Set<StringName>::Element *m = gds->get_members().find(p_identifier); + if (m) { + r_type = _type_from_gdtype(gds->get_member_type(p_identifier)); + return true; + } + } + Ref<GDScript> parent = gds->get_base_script(); + if (parent.is_valid()) { + base_type.script_type = parent; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); + } + } else { + return false; + } + } break; + case GDScriptParser::DataType::SCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + Map<StringName, Variant> constants; + scr->get_constants(&constants); + if (constants.has(p_identifier)) { + r_type = _type_from_variant(constants[p_identifier]); + return true; + } - int scp = String(arg.name).find(":"); - if (scp != -1) { + if (!_static) { + List<PropertyInfo> members; + scr->get_script_property_list(&members); + for (const List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { + const PropertyInfo &prop = E->get(); + if (prop.name == p_identifier) { + r_type = _type_from_property(prop); + return true; + } + } + } + Ref<Script> parent = scr->get_base_script(); + if (parent.is_valid()) { + base_type.script_type = parent; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + return false; + } + } break; + case GDScriptParser::DataType::NATIVE: { + StringName class_name = base_type.native_type; + if (!ClassDB::class_exists(class_name)) { + class_name = String("_") + class_name; + if (!ClassDB::class_exists(class_name)) { + return false; + } + } - r_type.type = Variant::OBJECT; - r_type.obj_type = String(arg.name).substr(scp + 1, String(arg.name).length()); - return true; + // Skip constants since they're all integers. Type does not matter because int has no members + List<PropertyInfo> props; + ClassDB::get_property_list(class_name, &props); + for (const List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + const PropertyInfo &prop = E->get(); + if (prop.name == p_identifier) { + StringName getter = ClassDB::get_property_getter(class_name, p_identifier); + if (getter != StringName()) { + MethodBind *g = ClassDB::get_method(class_name, getter); + if (g) { + r_type = _type_from_property(g->get_return_info()); + return true; + } } else { - - r_type.type = arg.type; - if (arg.hint == PROPERTY_HINT_RESOURCE_TYPE) - r_type.obj_type = arg.hint_string; + r_type = _type_from_property(prop); return true; } + break; } } - } + return false; + } break; + case GDScriptParser::DataType::BUILTIN: { + Variant::CallError err; + Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err); + + if (err.error != Variant::CallError::CALL_OK) { + return false; + } + bool valid = false; + Variant res = tmp.get(p_identifier, &valid); + if (valid) { + r_type = _type_from_variant(res); + r_type.value = Variant(); + r_type.type.is_constant = false; + return true; + } + return false; + } break; + default: { + return false; + } break; } } - //guess type in constant + return false; +} - for (int i = 0; i < context._class->constant_expressions.size(); i++) { +static bool _find_last_return_in_block(const GDScriptCompletionContext &p_context, int &r_last_return_line, const GDScriptParser::Node **r_last_returned_value) { + if (!p_context.block) { + return false; + } - if (context._class->constant_expressions[i].identifier == p_identifier) { + for (int i = 0; i < p_context.block->statements.size(); i++) { + if (p_context.block->statements[i]->line < r_last_return_line) { + continue; + } + if (p_context.block->statements[i]->type != GDScriptParser::Node::TYPE_CONTROL_FLOW) { + continue; + } - ERR_FAIL_COND_V(context._class->constant_expressions[i].expression->type != GDScriptParser::Node::TYPE_CONSTANT, false); - r_type = _get_type_from_variant(static_cast<const GDScriptParser::ConstantNode *>(context._class->constant_expressions[i].expression)->value); - return true; + const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(p_context.block->statements[i]); + if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_RETURN && cf->arguments.size() > 0) { + if (cf->line > r_last_return_line) { + r_last_return_line = cf->line; + *r_last_returned_value = cf->arguments[0]; + } } } - if (!(context.function && context.function->_static)) { + // Recurse into subblocks + for (int i = 0; i < p_context.block->sub_blocks.size(); i++) { + GDScriptCompletionContext c = p_context; + c.block = p_context.block->sub_blocks[i]; + _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); + } - for (int i = 0; i < context._class->variables.size(); i++) { + return false; +} - if (context._class->variables[i].identifier == p_identifier) { +static bool _guess_method_return_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) { + GDScriptParser::DataType base_type = p_base.type; + bool _static = base_type.is_meta_type; - if (context._class->variables[i]._export.type != Variant::NIL) { + if (_static && p_method == "new") { + r_type.type = base_type; + r_type.type.is_meta_type = false; + r_type.type.is_constant = false; + return true; + } - r_type = _get_type_from_pinfo(context._class->variables[i]._export); - return true; - } else if (context._class->variables[i].expression) { - if (p_line <= context._class->variables[i].line) - return false; + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + if (!base_type.class_type) { + base_type.has_type = false; + break; + } - bool rtype = _guess_expression_type(context, context._class->variables[i].expression, context._class->variables[i].line, r_type); - if (rtype && r_type.type != Variant::NIL) - return true; - //return _guess_expression_type(context,context._class->variables[i].expression,context._class->variables[i].line,r_type); + for (int i = 0; i < base_type.class_type->static_functions.size(); i++) { + if (base_type.class_type->static_functions[i]->name == p_method) { + int last_return_line = -1; + const GDScriptParser::Node *last_returned_value = NULL; + GDScriptCompletionContext c = p_context; + c._class = base_type.class_type; + c.function = base_type.class_type->static_functions[i]; + c.block = c.function->body; + + _find_last_return_in_block(c, last_return_line, &last_returned_value); + if (last_returned_value) { + c.line = c.block->end_line; + return _guess_expression_type(c, last_returned_value, r_type); + } + } + } + if (!_static) { + for (int i = 0; i < base_type.class_type->functions.size(); i++) { + if (base_type.class_type->functions[i]->name == p_method) { + int last_return_line = -1; + const GDScriptParser::Node *last_returned_value = NULL; + GDScriptCompletionContext c = p_context; + c._class = base_type.class_type; + c.function = base_type.class_type->functions[i]; + c.block = c.function->body; + + _find_last_return_in_block(c, last_return_line, &last_returned_value); + if (last_returned_value) { + c.line = c.block->end_line; + return _guess_expression_type(c, last_returned_value, r_type); + } + } + } } - //try to guess from assignment in constructor or _ready - if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_ready", r_type)) - return true; - if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_enter_tree", r_type)) - return true; - if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_init", r_type)) + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = base_type.script_type; + if (gds.is_valid()) { + if (gds->get_member_functions().has(p_method)) { + r_type = _type_from_gdtype(gds->get_member_functions()[p_method]->get_return_type()); + return true; + } + Ref<GDScript> base_script = gds->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); + } + } else { + return false; + } + } break; + case GDScriptParser::DataType::SCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + List<MethodInfo> methods; + scr->get_script_method_list(&methods); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + MethodInfo &mi = E->get(); + if (mi.name == p_method) { + r_type = _type_from_property(mi.return_val); + return true; + } + } + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + return false; + } + } break; + case GDScriptParser::DataType::NATIVE: { + StringName native = base_type.native_type; + if (!ClassDB::class_exists(native)) { + native = String("_") + native; + if (!ClassDB::class_exists(native)) { + return false; + } + } + MethodBind *mb = ClassDB::get_method(native, p_method); + if (mb) { + r_type = _type_from_property(mb->get_return_info()); return true; + } + return false; + } break; + case GDScriptParser::DataType::BUILTIN: { + Variant::CallError err; + Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + return false; + } + List<MethodInfo> methods; + tmp.get_method_list(&methods); + + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + MethodInfo &mi = E->get(); + if (mi.name == p_method) { + r_type = _type_from_property(mi.return_val); + return true; + } + } + return false; + } break; + default: { return false; } } } + return false; +} - //autoloads as singletons - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - - String s = E->get().name; - if (!s.begins_with("autoload/")) - continue; - String name = s.get_slice("/", 1); - if (name == String(p_identifier)) { +static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx) { - String path = ProjectSettings::get_singleton()->get(s); - if (path.begins_with("*")) { - String script = path.substr(1, path.length()); + String arghint = _get_visual_datatype(p_info.return_val, false) + " " + p_info.name + "("; - if (!script.ends_with(".gd")) { - //not a script, try find the script anyway, - //may have some success - script = script.get_basename() + ".gd"; - } + int def_args = p_info.arguments.size() - p_info.default_arguments.size(); + int i = 0; + for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E; E = E->next()) { + if (i > 0) { + arghint += ", "; + } else { + arghint += " "; + } - if (FileAccess::exists(script)) { + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + arghint += _get_visual_datatype(E->get(), true) + " " + E->get().name; - //print_line("is a script"); + if (i - def_args >= 0) { + arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string(); + } - Ref<Script> scr; - if (ScriptCodeCompletionCache::get_singleton()) - scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script); - else - scr = ResourceLoader::load(script); + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } - r_type.obj_type = "Node"; - r_type.type = Variant::OBJECT; - r_type.script = scr; - r_type.value = Variant(); + i++; + } - return true; - } - } + if (p_info.flags & METHOD_FLAG_VARARG) { + if (p_info.arguments.size() > 0) { + arghint += ", "; + } else { + arghint += " "; + } + if (p_arg_idx >= p_info.arguments.size()) { + arghint += String::chr(0xFFFF); + } + arghint += "..."; + if (p_arg_idx >= p_info.arguments.size()) { + arghint += String::chr(0xFFFF); } } + if (p_info.arguments.size() > 0 || (p_info.flags & METHOD_FLAG_VARARG)) { + arghint += " "; + } - //global - for (Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { - if (E->key() == p_identifier) { + arghint += ")"; - r_type = _get_type_from_variant(GDScriptLanguage::get_singleton()->get_global_array()[E->get()], !p_for_indexing); - return true; - } - } - return false; + return arghint; } -static void _find_identifiers_in_block(GDScriptCompletionContext &context, int p_line, bool p_only_functions, Set<String> &result) { +static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) { - if (p_only_functions) - return; + String arghint = p_function->return_type.to_string() + " " + p_function->name.operator String() + "("; - for (int i = 0; i < context.block->statements.size(); i++) { + int def_args = p_function->arguments.size() - p_function->default_values.size(); + for (int i = 0; i < p_function->arguments.size(); i++) { + if (i > 0) { + arghint += ", "; + } else { + arghint += " "; + } - GDScriptParser::Node *statement = context.block->statements[i]; - if (statement->line > p_line) - continue; + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + arghint += p_function->argument_types[i].to_string() + " " + p_function->arguments[i].operator String(); + + if (i - def_args >= 0) { + String def_val = "<unknown>"; + if (p_function->default_values[i - def_args] && p_function->default_values[i - def_args]->type == GDScriptParser::Node::TYPE_OPERATOR) { + const GDScriptParser::OperatorNode *assign = static_cast<const GDScriptParser::OperatorNode *>(p_function->default_values[i - def_args]); + + if (assign->arguments.size() >= 2) { + if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) { + const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(assign->arguments[1]); + def_val = cn->value.get_construct_string(); + } else if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->arguments[1]); + def_val = id->name.operator String(); + } + } + } + arghint += " = " + def_val; + } + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + } - GDScriptParser::BlockNode::Type statementType = statement->type; - if (statementType == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) { + if (p_function->arguments.size() > 0) { + arghint += " "; + } + arghint += ")"; - const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(statement); - result.insert(lv->name.operator String()); - } else if (statementType == GDScriptParser::BlockNode::TYPE_CONTROL_FLOW) { + return arghint; +} - const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(statement); - if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_FOR) { +static void _find_enumeration_candidates(const String p_enum_hint, Set<String> &r_result) { - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(cf->arguments[0]); - result.insert(id->name.operator String()); + 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)); } } - } -} - -static void _find_identifiers_in_class(GDScriptCompletionContext &context, bool p_static, bool p_only_functions, Set<String> &result) { + } else { + String class_name = p_enum_hint.get_slice(".", 0); + String enum_name = p_enum_hint.get_slice(".", 1); - if (!p_static && !p_only_functions) { + if (!ClassDB::class_exists(class_name)) { + return; + } - for (int i = 0; i < context._class->variables.size(); i++) { - result.insert(context._class->variables[i].identifier); + List<StringName> enum_constants; + 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); } } - if (!p_only_functions) { +} - for (int i = 0; i < context._class->constant_expressions.size(); i++) { - result.insert(context._class->constant_expressions[i].identifier); +static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Set<String> &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()); } + } + if (p_context.block->parent_block) { + GDScriptCompletionContext c = p_context; + c.block = p_context.block->parent_block; + _find_identifiers_in_block(c, r_result); + } +} + +static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result); - for (int i = 0; i < context._class->subclasses.size(); i++) { - result.insert(context._class->subclasses[i]->name); +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) { + 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); + } } - } - for (int i = 0; i < context._class->static_functions.size(); i++) { - if (context._class->static_functions[i]->arguments.size()) - result.insert(context._class->static_functions[i]->name.operator String() + "("); - else - result.insert(context._class->static_functions[i]->name.operator String() + "()"); - } + 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()); + } + for (int i = 0; i < p_context._class->subclasses.size(); i++) { + r_result.insert(p_context._class->subclasses[i]->name); + } + } - if (!p_static) { + for (int i = 0; i < p_context._class->static_functions.size(); i++) { + if (p_context._class->static_functions[i]->arguments.size()) { + r_result.insert(p_context._class->static_functions[i]->name.operator String() + "("); + } else { + r_result.insert(p_context._class->static_functions[i]->name.operator String() + "()"); + } + } - for (int i = 0; i < context._class->functions.size(); i++) { - if (context._class->functions[i]->arguments.size()) - result.insert(context._class->functions[i]->name.operator String() + "("); - else - result.insert(context._class->functions[i]->name.operator String() + "()"); + if (!p_static) { + for (int i = 0; i < p_context._class->functions.size(); i++) { + if (p_context._class->functions[i]->arguments.size()) { + r_result.insert(p_context._class->functions[i]->name.operator String() + "("); + } else { + r_result.insert(p_context._class->functions[i]->name.operator String() + "()"); + } + } } } - //globals - - Ref<Reference> base = _get_parent_class(context); + // Parents + GDScriptCompletionIdentifier base_type; + base_type.type = p_context._class->base_type; + base_type.type.is_meta_type = p_static; + base_type.value = p_context.base; - while (true) { + GDScriptCompletionContext c = p_context; + c.block = NULL; + c.function = NULL; - Ref<GDScript> script = base; - Ref<GDScriptNativeClass> nc = base; - if (script.is_valid()) { + _find_identifiers_in_base(c, base_type, p_only_functions, r_result); +} - if (!p_static && !p_only_functions) { - for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { - result.insert(E->get().operator String()); +static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &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("); + } + + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + GDScriptCompletionContext c = p_context; + c._class = base_type.class_type; + c.block = NULL; + c.function = NULL; + _find_identifiers_in_class(c, _static, p_only_functions, false, r_result); + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> script = base_type.script_type; + if (script.is_valid()) { + if (!_static && !p_only_functions) { + for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { + r_result.insert(E->get().operator String()); + } + } + 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()); + } + } + for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { + if (!_static || E->get()->is_static()) { + if (E->get()->get_argument_count()) { + r_result.insert(E->key().operator String() + "("); + } else { + r_result.insert(E->key().operator String() + "()"); + } + } + } + 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()); + } + } + base_type = GDScriptParser::DataType(); + if (script->get_base().is_valid()) { + base_type.has_type = true; + base_type.kind = GDScriptParser::DataType::GDSCRIPT; + base_type.script_type = script->get_base(); + } else { + base_type.has_type = script->get_instance_base_type() != StringName(); + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.script_type = script->get_instance_base_type(); + } + } else { + return; } - } + } break; + case GDScriptParser::DataType::SCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + if (!_static && !p_only_functions) { + 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); + } + } + 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()); + } + } - if (!p_only_functions) { - for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) { - result.insert(E->key().operator String()); - } - } + List<MethodInfo> methods; + scr->get_script_method_list(&methods); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().arguments.size()) { + r_result.insert(E->get().name + "("); + } else { + r_result.insert(E->get().name + "()"); + } + } - for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { - if (!p_static || E->get()->is_static()) { - if (E->get()->get_argument_count()) - result.insert(E->key().operator String() + "("); - else - result.insert(E->key().operator String() + "()"); + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + return; } - } - - if (!p_only_functions) { - for (const Map<StringName, Ref<GDScript> >::Element *E = script->get_subclasses().front(); E; E = E->next()) { - result.insert(E->key().operator String()); + } break; + case GDScriptParser::DataType::NATIVE: { + StringName type = base_type.native_type; + if (!ClassDB::class_exists(type)) { + type = String("_") + type; + if (!ClassDB::class_exists(type)) { + return; + } } - } - base = script->get_base(); - if (base.is_null()) - base = script->get_native(); - } else if (nc.is_valid()) { + if (!p_only_functions) { + 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()); + } - StringName type = nc->get_name(); + if (!_static) { + List<PropertyInfo> pinfo; + ClassDB::get_property_list(type, &pinfo); + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { + continue; + } + if (E->get().name.find("/") != -1) { + continue; + } + r_result.insert(E->get().name); + } + } + } - if (!p_only_functions) { + if (!_static) { + List<MethodInfo> methods; + bool is_autocompleting_getters = GLOBAL_GET("debug/gdscript/completion/autocomplete_setters_and_getters").booleanize(); + ClassDB::get_method_list(type, &methods, false, !is_autocompleting_getters); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name.begins_with("_")) { + continue; + } + if (E->get().arguments.size()) { + r_result.insert(E->get().name + "("); + } else { + r_result.insert(E->get().name + "()"); + } + } + } - List<String> constants; - ClassDB::get_integer_constant_list(type, &constants); - for (List<String>::Element *E = constants.front(); E; E = E->next()) { - result.insert(E->get()); + return; + } break; + case GDScriptParser::DataType::BUILTIN: { + Variant::CallError err; + Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + return; } - List<PropertyInfo> pinfo; + if (!p_only_functions) { + List<PropertyInfo> members; + tmp.get_property_list(&members); - ClassDB::get_property_list(type, &pinfo); + for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { + if (String(E->get().name).find("/") == -1) { + r_result.insert(E->get().name); + } + } + } - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) - continue; - if (String(E->get().name).find("/") != -1) - continue; - result.insert(E->get().name); + List<MethodInfo> methods; + tmp.get_method_list(&methods); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().arguments.size()) { + r_result.insert(E->get().name + "("); + } else { + r_result.insert(E->get().name + "()"); + } } - } - List<MethodInfo> methods; - ClassDB::get_method_list(type, &methods, false, true); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name.begins_with("_")) - continue; - if (E->get().arguments.size()) - result.insert(E->get().name + "("); - else - result.insert(E->get().name + "()"); - } - break; - } else - break; + return; + } break; + default: { + return; + } break; + } } } -static void _find_identifiers(GDScriptCompletionContext &context, int p_line, bool p_only_functions, Set<String> &result) { +static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Set<String> &r_result) { - const GDScriptParser::BlockNode *block = context.block; + const GDScriptParser::BlockNode *block = p_context.block; - if (context.function) { + if (p_context.function) { - const GDScriptParser::FunctionNode *f = context.function; + const GDScriptParser::FunctionNode *f = p_context.function; for (int i = 0; i < f->arguments.size(); i++) { - result.insert(f->arguments[i].operator String()); + r_result.insert(f->arguments[i].operator String()); } } - while (block) { - - GDScriptCompletionContext c = context; + if (!p_only_functions && block) { + GDScriptCompletionContext c = p_context; c.block = block; - - _find_identifiers_in_block(c, p_line, p_only_functions, result); - block = block->parent_block; + _find_identifiers_in_block(c, r_result); } - const GDScriptParser::ClassNode *clss = context._class; - - bool _static = context.function && context.function->_static; + const GDScriptParser::ClassNode *clss = p_context._class; + bool _static = !p_context.function || p_context.function->_static; while (clss) { - GDScriptCompletionContext c = context; + GDScriptCompletionContext c = p_context; c._class = clss; c.block = NULL; c.function = NULL; - _find_identifiers_in_class(c, _static, p_only_functions, result); + _find_identifiers_in_class(c, _static, p_only_functions, false, r_result); + _static = true; clss = clss->owner; } for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - - result.insert(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))); + MethodInfo mi = GDScriptFunctions::get_info(GDScriptFunctions::Function(i)); + if (mi.arguments.size() || (mi.flags & METHOD_FLAG_VARARG)) { + r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "("); + } else { + r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "()"); + } } static const char *_type_names[Variant::VARIANT_MAX] = { @@ -1526,667 +2101,372 @@ static void _find_identifiers(GDScriptCompletionContext &context, int p_line, bo }; for (int i = 0; i < Variant::VARIANT_MAX; i++) { - result.insert(_type_names[i]); + r_result.insert(_type_names[i]); } - List<String> reserved_words; - GDScriptLanguage::get_singleton()->get_reserved_words(&reserved_words); + static const char *_keywords[] = { + "and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert", + "breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield", + "const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif", + "else", "for", "pass", "return", "match", "while", "remote", "sync", "master", "puppet", "slave", + "remotesync", "mastersync", "puppetsync", + 0 + }; - for (List<String>::Element *E = reserved_words.front(); E; E = E->next()) { - result.insert(E->get()); + const char **kw = _keywords; + while (*kw) { + r_result.insert(*kw); + kw++; } - //autoload singletons + // Autoload singletons List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) + if (!s.begins_with("autoload/")) { continue; - String name = s.get_slice("/", 1); + } String path = ProjectSettings::get_singleton()->get(s); if (path.begins_with("*")) { - result.insert(name); + r_result.insert(s.get_slice("/", 1)); } } - for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { - result.insert(E->key().operator String()); + // 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()) { + r_result.insert(E->get().operator String()); } -} -static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = true) { - - String n = p_info.name; - int idx = n.find(":"); - if (idx != -1) { - return n.substr(idx + 1, n.length()); - } - - if (p_info.type == Variant::OBJECT && p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) - return p_info.hint_string; - if (p_info.type == Variant::NIL) { - if (p_isarg) - return "var"; - else - return "void"; + // 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()); } - - return Variant::get_type_name(p_info.type); } -static void _make_function_hint(const GDScriptParser::FunctionNode *p_func, int p_argidx, String &arghint) { - - arghint = "func " + p_func->name + "("; - for (int i = 0; i < p_func->arguments.size(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += p_func->arguments[i].operator String(); - int deffrom = p_func->arguments.size() - p_func->default_values.size(); - - if (i >= deffrom) { - int defidx = deffrom - i; - - if (defidx >= 0 && defidx < p_func->default_values.size()) { - - if (p_func->default_values[defidx]->type == GDScriptParser::Node::TYPE_OPERATOR) { - - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[defidx]); - if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) { - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(op->arguments[1]); - arghint += "=" + cn->value.get_construct_string(); +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) { + Variant base = p_base.value; + GDScriptParser::DataType base_type = p_base.type; + bool _static = false; + + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + for (int i = 0; i < base_type.class_type->static_functions.size(); i++) { + if (base_type.class_type->static_functions[i]->name == p_method) { + r_arghint = _make_arguments_hint(base_type.class_type->static_functions[i], p_argidx); + return; + } + } + if (!_static) { + for (int i = 0; i < base_type.class_type->functions.size(); i++) { + if (base_type.class_type->functions[i]->name == p_method) { + r_arghint = _make_arguments_hint(base_type.class_type->functions[i], p_argidx); + return; + } } - } else { } - } - } - - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - if (p_func->arguments.size() > 0) - arghint += " "; - arghint += ")"; -} - -void get_directory_contents(EditorFileSystemDirectory *p_dir, Set<String> &r_list) { - - for (int i = 0; i < p_dir->get_subdir_count(); i++) { - get_directory_contents(p_dir->get_subdir(i), r_list); - } - - for (int i = 0; i < p_dir->get_file_count(); i++) { - r_list.insert("\"" + p_dir->get_file_path(i) + "\""); - } -} - -static void _find_type_arguments(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, const StringName &p_method, const GDScriptCompletionIdentifier &id, int p_argidx, Set<String> &result, bool &r_forced, String &arghint) { - - //print_line("find type arguments?"); - if (id.type == Variant::OBJECT && id.obj_type != StringName()) { - - MethodBind *m = ClassDB::get_method(id.obj_type, p_method); - if (!m) { - //not in static method, see script - - //print_line("not in static: "+String(p_method)); - Ref<GDScript> on_script; - - if (id.value.get_type()) { - Object *obj = id.value; - GDScript *scr = Object::cast_to<GDScript>(obj); - if (scr) { - while (scr) { + 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("\"" + base_type.class_type->_signals[i].name.operator String() + "\""); + } + } - for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) { - if (E->get()->is_static() && p_method == E->get()->get_name()) { - arghint = "static func " + String(p_method) + "("; - for (int i = 0; i < E->get()->get_argument_count(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += "var " + E->get()->get_argument_name(i); - int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count(); - if (i >= deffrom) { - int defidx = deffrom - i; - if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) { - arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string(); - } - } - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - arghint += ")"; - return; //found - } + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = base_type.script_type; + if (gds.is_valid()) { + if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) { + List<MethodInfo> signals; + gds->get_script_signal_list(&signals); + for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { + r_result.insert("\"" + E->get().name + "\""); } - - if (scr->get_base().is_valid()) - scr = scr->get_base().ptr(); - else - scr = NULL; + } + Ref<GDScript> base_script = gds->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); } } else { - if (obj) { - on_script = obj->get_script(); + return; + } + } break; + case GDScriptParser::DataType::NATIVE: { + StringName class_name = base_type.native_type; + if (!ClassDB::class_exists(class_name)) { + class_name = String("_") + class_name; + if (!ClassDB::class_exists(class_name)) { + base_type.has_type = false; + break; } } - } - - //print_line("but it has a script?"); - if (!on_script.is_valid() && id.script.is_valid()) { - //print_line("yes"); - on_script = id.script; - } - - if (on_script.is_valid()) { - - GDScript *scr = on_script.ptr(); - if (scr) { - while (scr) { - - String code = scr->get_source_code(); - //print_line("has source code!"); - - if (code != "") { - //if there is code, parse it. This way is slower but updates in real-time - GDScriptParser p; - //Error parse(const String& p_code, const String& p_base_path="", bool p_just_validate=false,const String& p_self_path="",bool p_for_completion=false); - - Error err = p.parse(scr->get_source_code(), scr->get_path().get_base_dir(), true, "", false); - if (err == OK) { - //print_line("checking the functions..."); - //only if ok, otherwise use what is cached on the script - //GDScriptParser::ClassNode *base = p. - const GDScriptParser::Node *root = p.get_parse_tree(); - ERR_FAIL_COND(root->type != GDScriptParser::Node::TYPE_CLASS); + List<MethodInfo> methods; + ClassDB::get_method_list(class_name, &methods); + ClassDB::get_virtual_methods(class_name, &methods); + int method_args = 0; - const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root); - - const GDScriptParser::FunctionNode *func = NULL; - bool st = false; - - for (int i = 0; i < cl->functions.size(); i++) { - //print_line(String(cl->functions[i]->name)+" vs "+String(p_method)); - if (cl->functions[i]->name == p_method) { - func = cl->functions[i]; - } - } - - for (int i = 0; i < cl->static_functions.size(); i++) { - - //print_line(String(cl->static_functions[i]->name)+" vs "+String(p_method)); - if (cl->static_functions[i]->name == p_method) { - func = cl->static_functions[i]; - st = true; - } - } - - if (func) { - - arghint = "func " + String(p_method) + "("; - if (st) - arghint = "static " + arghint; - for (int i = 0; i < func->arguments.size(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += "var " + String(func->arguments[i]); - int deffrom = func->arguments.size() - func->default_values.size(); - if (i >= deffrom) { - - int defidx = deffrom - i; - - if (defidx >= 0 && defidx < func->default_values.size() && func->default_values[defidx]->type == GDScriptParser::Node::TYPE_OPERATOR) { - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(func->default_values[defidx]); - if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) { - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(op->arguments[1]); - arghint += "=" + cn->value.get_construct_string(); - } - } - } - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - - arghint += " )"; - return; + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name == p_method) { + method_args = E->get().arguments.size(); + if (base.get_type() == Variant::OBJECT) { + Object *obj = base.operator Object *(); + if (obj) { + List<String> options; + obj->get_argument_options(p_method, p_argidx, &options); + for (List<String>::Element *E = options.front(); E; E = E->next()) { + r_result.insert(E->get()); } - } else { - //print_line("failed parsing?"); - code = ""; } } - if (code == "") { - - for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) { - if (p_method == E->get()->get_name()) { - arghint = "func " + String(p_method) + "("; - for (int i = 0; i < E->get()->get_argument_count(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += "var " + E->get()->get_argument_name(i); - int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count(); - if (i >= deffrom) { - int defidx = deffrom - i; - if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) { - arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string(); - } - } - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - arghint += ")"; - return; //found - } + if (p_argidx < method_args) { + PropertyInfo arg_info = E->get().arguments[p_argidx]; + if (arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + _find_enumeration_candidates(arg_info.class_name, r_result); } } - if (scr->get_base().is_valid()) - scr = scr->get_base().ptr(); - else - scr = NULL; + r_arghint = _make_arguments_hint(E->get(), p_argidx); + break; } } - } - - } else { - //regular method -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) - if (p_argidx < m->get_argument_count()) { - PropertyInfo pi = m->get_argument_info(p_argidx); - - if (pi.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - String enumeration = pi.class_name; - if (enumeration.find(".") != -1) { - //class constant - List<StringName> constants; - String cls = enumeration.get_slice(".", 0); - String enm = enumeration.get_slice(".", 1); - - ClassDB::get_enum_constants(cls, enm, &constants); - //constants.sort_custom<StringName::AlphCompare>(); - for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { - String add = cls + "." + E->get(); - result.insert(add); - r_forced = true; - } - } else { - - //global constant - StringName current_enum = enumeration; - - for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { - if (GlobalConstants::get_global_constant_enum(i) == current_enum) { - result.insert(GlobalConstants::get_global_constant_name(i)); - r_forced = true; - } - } - //global + if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) { + List<MethodInfo> signals; + ClassDB::get_signal_list(class_name, &signals); + for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { + r_result.insert("\"" + E->get().name + "\""); } } - } -#endif - if (p_method.operator String() == "connect" || (p_method.operator String() == "emit_signal" && p_argidx == 0)) { - - if (p_argidx == 0) { - List<MethodInfo> sigs; - ClassDB::get_signal_list(id.obj_type, &sigs); - - if (id.script.is_valid()) { - id.script->get_script_signal_list(&sigs); - } else if (id.value.get_type() == Variant::OBJECT) { - Object *obj = id.value; - if (obj && !obj->get_script().is_null()) { - Ref<Script> scr = obj->get_script(); - if (scr.is_valid()) { - scr->get_script_signal_list(&sigs); - } - } - } - for (List<MethodInfo>::Element *E = sigs.front(); E; E = E->next()) { - result.insert("\"" + E->get().name + "\""); - r_forced = true; - } - - } else if (p_argidx == 2) { + if (ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node") && p_argidx == 0) { + // Get autoloads + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); - if (context._class) { - for (int i = 0; i < context._class->functions.size(); i++) { - result.insert("\"" + context._class->functions[i]->name + "\""); - r_forced = true; + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; } + String name = s.get_slice("/", 1); + r_result.insert("\"/root/" + name + "\""); } } - /*if (p_argidx==2) { - - ERR_FAIL_COND(p_node->type!=GDScriptParser::Node::TYPE_OPERATOR); - const GDScriptParser::OperatorNode *op=static_cast<const GDScriptParser::OperatorNode *>(p_node); - if (op->arguments.size()>) - - }*/ - } else { - - if (p_argidx == 0 && (String(p_method) == "get_node" || String(p_method) == "has_node") && ClassDB::is_parent_class(id.obj_type, "Node")) { + if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, "InputEvent") && p_method.operator String().find("action") != -1) { + // Get input actions List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) + if (!s.begins_with("input/")) { continue; - //print_line("found "+s); + } String name = s.get_slice("/", 1); - result.insert("\"/root/" + name + "\""); - r_forced = true; + r_result.insert("\"" + name + "\""); } } - Object *obj = id.value; - if (obj) { - List<String> options; - obj->get_argument_options(p_method, p_argidx, &options); - - for (List<String>::Element *E = options.front(); E; E = E->next()) { - - result.insert(E->get()); - r_forced = true; + base_type.has_type = false; + } break; + case GDScriptParser::DataType::BUILTIN: { + if (base.get_type() == Variant::NIL) { + Variant::CallError err; + base = Variant::construct(base_type.builtin_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + return; } } - } - - arghint = _get_visual_datatype(m->get_return_info(), false) + " " + p_method.operator String() + String("("); - - for (int i = 0; i < m->get_argument_count(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - String n = m->get_argument_info(i).name; - int dp = n.find(":"); - if (dp != -1) - n = n.substr(0, dp); - arghint += _get_visual_datatype(m->get_argument_info(i)) + " " + n; - int deffrom = m->get_argument_count() - m->get_default_argument_count(); - - if (i >= deffrom) { - int defidx = i - deffrom; - if (defidx >= 0 && defidx < m->get_default_argument_count()) { - Variant v = m->get_default_argument(i); - arghint += "=" + v.get_construct_string(); + List<MethodInfo> methods; + base.get_method_list(&methods); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name == p_method) { + r_arghint = _make_arguments_hint(E->get(), p_argidx); + return; } } - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - if (m->get_argument_count() > 0) - arghint += " "; - - arghint += ")"; + base_type.has_type = false; + } break; + default: { + base_type.has_type = false; + } break; } } } -static void _find_call_arguments(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, int p_argidx, Set<String> &result, bool &r_forced, String &arghint) { +static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Set<String> &r_result, bool &r_forced, String &r_arghint) { if (!p_node || p_node->type != GDScriptParser::Node::TYPE_OPERATOR) { - return; } + Variant base; + GDScriptParser::DataType base_type; + StringName function; + bool _static = false; const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node); - if (op->op != GDScriptParser::OperatorNode::OP_CALL) { + GDScriptCompletionIdentifier connect_base; + if (op->op != GDScriptParser::OperatorNode::OP_CALL && op->op != GDScriptParser::OperatorNode::OP_PARENT_CALL) { return; } - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - //complete built-in function - const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); - MethodInfo mi = GDScriptFunctions::get_info(fn->function); + if (!op->arguments.size()) { + return; + } - if (mi.name == "load" && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { - get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), result); - } + if (op->op == GDScriptParser::OperatorNode::OP_CALL) { + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { + // Complete built-in function + const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); + MethodInfo mi = GDScriptFunctions::get_info(fn->function); - arghint = _get_visual_datatype(mi.return_val, false) + " " + GDScriptFunctions::get_func_name(fn->function) + String("("); - for (int i = 0; i < mi.arguments.size(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx || ((mi.flags & METHOD_FLAG_VARARG) && i > p_argidx)) { - arghint += String::chr(0xFFFF); - } - arghint += _get_visual_datatype(mi.arguments[i]) + " " + mi.arguments[i].name; - if (i == p_argidx || ((mi.flags & METHOD_FLAG_VARARG) && i > p_argidx)) { - arghint += String::chr(0xFFFF); + if ((mi.name == "load" || mi.name == "preload") && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { + _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); } - } - if (mi.arguments.size() > 0) - arghint += " "; - arghint += ")"; - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { - //complete constructor - const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); + r_arghint = _make_arguments_hint(mi, p_argidx); + return; - List<MethodInfo> mil; - Variant::get_constructor_list(tn->vtype, &mil); + } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { + // Complete constructor + const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); - for (List<MethodInfo>::Element *E = mil.front(); E; E = E->next()) { + List<MethodInfo> constructors; + Variant::get_constructor_list(tn->vtype, &constructors); - MethodInfo mi = E->get(); - if (mi.arguments.size() == 0) - continue; - if (E->prev()) - arghint += "\n"; - arghint += Variant::get_type_name(tn->vtype) + " " + Variant::get_type_name(tn->vtype) + String("("); - for (int i = 0; i < mi.arguments.size(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += _get_visual_datatype(mi.arguments[i]) + " " + mi.arguments[i].name; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); + int i = 0; + for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) { + if (p_argidx >= E->get().arguments.size()) { + continue; } - } - if (mi.arguments.size() > 0) - arghint += " "; - arghint += ")"; - } - - } else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - //make sure identifier exists... - - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) { - //self, look up - - for (int i = 0; i < context._class->static_functions.size(); i++) { - if (context._class->static_functions[i]->name == id->name) { - _make_function_hint(context._class->static_functions[i], p_argidx, arghint); - return; + if (i > 0) { + r_arghint += "\n"; } + r_arghint += _make_arguments_hint(E->get(), p_argidx); + i++; } + return; + } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) { - if (context.function && !context.function->_static) { - - for (int i = 0; i < context._class->functions.size(); i++) { - if (context._class->functions[i]->name == id->name) { - _make_function_hint(context._class->functions[i], p_argidx, arghint); - return; - } - } + if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { + return; } - Ref<Reference> base = _get_parent_class(context); + base = p_context.base; - while (true) { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); + function = id->name; + base_type.has_type = true; + base_type.kind = GDScriptParser::DataType::CLASS; + base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); + _static = p_context.function && p_context.function->_static; - Ref<GDScript> script = base; - Ref<GDScriptNativeClass> nc = base; - if (script.is_valid()) { - - for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { - - if (E->key() == id->name) { - - if (context.function && context.function->_static && !E->get()->is_static()) - continue; + if (function == "connect" && op->arguments.size() >= 4) { + _guess_expression_type(p_context, op->arguments[3], connect_base); + } - arghint = "func " + id->name.operator String() + String("("); - for (int i = 0; i < E->get()->get_argument_count(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += E->get()->get_argument_name(i); - int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count(); - if (i >= deffrom) { - int defidx = deffrom - i; - if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) { - arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string(); - } - } - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - if (E->get()->get_argument_count() > 0) - arghint += " "; - arghint += ")"; - return; - } - } + } else { + if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { + return; + } + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); + function = id->name; - base = script->get_base(); - if (base.is_null()) - base = script->get_native(); - } else if (nc.is_valid()) { + GDScriptCompletionIdentifier ci; + if (_guess_expression_type(p_context, op->arguments[0], ci)) { + base_type = ci.type; + base = ci.value; + } else { + return; + } + _static = ci.type.is_meta_type; - if (!(context.function && context.function->_static)) { + if (function == "connect" && op->arguments.size() >= 4) { + _guess_expression_type(p_context, op->arguments[3], connect_base); + } + } + } else { + if (!p_context._class || op->arguments.size() < 1 || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { + return; + } + base_type.has_type = true; + base_type.kind = GDScriptParser::DataType::CLASS; + base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); + base_type.is_meta_type = p_context.function && p_context.function->_static; + base = p_context.base; - GDScriptCompletionIdentifier ci; - ci.type = Variant::OBJECT; - ci.obj_type = nc->get_name(); - if (!context._class->owner) - ci.value = context.base; + function = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name; - _find_type_arguments(context, p_node, p_line, id->name, ci, p_argidx, result, r_forced, arghint); - //guess type.. - /* - List<MethodInfo> methods; - ClassDB::get_method_list(type,&methods); - for(List<MethodInfo>::Element *E=methods.front();E;E=E->next()) { - if (E->get().arguments.size()) - result.insert(E->get().name+"("); - else - result.insert(E->get().name+"()"); - }*/ - } - break; - } else - break; - } - } else { - //indexed lookup + if (function == "connect" && op->arguments.size() >= 4) { + _guess_expression_type(p_context, op->arguments[3], connect_base); + } + } - GDScriptCompletionIdentifier ci; - if (_guess_expression_type(context, op->arguments[0], p_line, ci)) { + GDScriptCompletionIdentifier ci; + ci.type = base_type; + ci.value = base; + _find_call_arguments(p_context, ci, function, p_argidx, _static, r_result, r_arghint); - _find_type_arguments(context, p_node, p_line, id->name, ci, p_argidx, result, r_forced, arghint); - return; - } + if (function == "connect" && p_argidx == 2) { + Set<String> 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("\"" + E->get().replace("(", "").replace(")", "") + "\""); } } + + r_forced = r_result.size() > 0; } Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) { - GDScriptParser p; + GDScriptParser parser; - p.parse(p_code, p_base_path, false, "", true); - bool isfunction = false; - Set<String> options; + parser.parse(p_code, p_base_path, false, "", true); r_forced = false; + Set<String> options; GDScriptCompletionContext context; - context._class = p.get_completion_class(); - context.block = p.get_completion_block(); - context.function = p.get_completion_function(); + context._class = parser.get_completion_class(); + context.block = parser.get_completion_block(); + context.function = parser.get_completion_function(); context.base = p_owner; context.base_path = p_base_path; + context.line = parser.get_completion_line(); + bool is_function = false; - switch (p.get_completion_type()) { - + switch (parser.get_completion_type()) { case GDScriptParser::COMPLETION_NONE: { } break; case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { List<StringName> constants; - Variant::get_numeric_constants_for_type(p.get_completion_built_in_constant(), &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()); } - - } break; - case GDScriptParser::COMPLETION_FUNCTION: - isfunction = true; - case GDScriptParser::COMPLETION_IDENTIFIER: { - - _find_identifiers(context, p.get_completion_line(), isfunction, options); } break; case GDScriptParser::COMPLETION_PARENT_FUNCTION: { - + _find_identifiers_in_class(context, !context.function || context.function->_static, true, true, options); + } break; + case GDScriptParser::COMPLETION_FUNCTION: { + is_function = true; + } // fallthrough + case GDScriptParser::COMPLETION_IDENTIFIER: { + _find_identifiers(context, is_function, options); } break; case GDScriptParser::COMPLETION_GET_NODE: { - if (p_owner) { List<String> opts; p_owner->get_argument_options("get_node", 0, &opts); @@ -2204,315 +2484,358 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base } } } + + // Get autoloads + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + String name = s.get_slice("/", 1); + options.insert("\"/root/" + name + "\""); + } } } break; - case GDScriptParser::COMPLETION_METHOD: - isfunction = true; + case GDScriptParser::COMPLETION_METHOD: { + is_function = true; + } // fallthrough case GDScriptParser::COMPLETION_INDEX: { - - const GDScriptParser::Node *node = p.get_completion_node(); - if (node->type != GDScriptParser::Node::TYPE_OPERATOR) + const GDScriptParser::Node *node = parser.get_completion_node(); + if (node->type != GDScriptParser::Node::TYPE_OPERATOR) { break; + } + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(node); + if (op->arguments.size() < 1) { + break; + } - GDScriptCompletionIdentifier t; - if (_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], p.get_completion_line(), t, true)) { - - if (t.type == Variant::OBJECT && t.obj_type == "GDScriptNativeClass") { - //native enum - Ref<GDScriptNativeClass> gdn = t.value; - if (gdn.is_valid()) { - StringName cn = gdn->get_name(); - List<String> cnames; - ClassDB::get_integer_constant_list(cn, &cnames); - for (List<String>::Element *E = cnames.front(); E; E = E->next()) { - options.insert(E->get()); - } - - List<PropertyInfo> pinfo; - ClassDB::get_property_list(cn, &pinfo); - - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) - continue; - if (String(E->get().name).find("/") != -1) - continue; - options.insert(E->get().name); - } - } - } else if (t.type == Variant::OBJECT && t.obj_type != StringName()) { - - Ref<GDScript> on_script; - - if (t.value.get_type()) { - Object *obj = t.value; - - GDScript *scr = Object::cast_to<GDScript>(obj); - if (scr) { - while (scr) { + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(context, op->arguments[0], base)) { + break; + } - if (!isfunction) { - for (const Map<StringName, Variant>::Element *E = scr->get_constants().front(); E; E = E->next()) { - options.insert(E->key()); - } - } - for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) { - if (E->get()->is_static()) - options.insert(E->key()); - } + GDScriptCompletionContext c = context; + c.function = NULL; + c.block = NULL; + c.base = base.value.get_type() == Variant::OBJECT ? base.value.operator Object *() : NULL; + if (base.type.kind == GDScriptParser::DataType::CLASS) { + c._class = base.type.class_type; + } else { + c._class = NULL; + } - if (scr->get_base().is_valid()) - scr = scr->get_base().ptr(); - else - scr = NULL; + _find_identifiers_in_base(c, base, is_function, options); + } break; + case GDScriptParser::COMPLETION_CALL_ARGUMENTS: { + _find_call_arguments(context, parser.get_completion_node(), parser.get_completion_argument_index(), options, r_forced, r_call_hint); + } break; + case GDScriptParser::COMPLETION_VIRTUAL_FUNC: { + GDScriptParser::DataType native_type = context._class->base_type; + while (native_type.has_type && native_type.kind != GDScriptParser::DataType::NATIVE) { + switch (native_type.kind) { + case GDScriptParser::DataType::CLASS: { + native_type = native_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = native_type.script_type; + if (gds.is_valid()) { + Ref<GDScript> base = gds->get_base_script(); + if (base.is_valid()) { + native_type.script_type = base; + } else { + native_type.native_type = gds->get_instance_base_type(); + native_type.kind = GDScriptParser::DataType::NATIVE; } } else { - if (obj) { - on_script = obj->get_script(); - } + native_type.has_type = false; } - } - - if (!on_script.is_valid() && t.script.is_valid()) { - on_script = t.script; - } - - if (on_script.is_valid()) { - - GDScript *scr = on_script.ptr(); - if (scr) { - while (scr) { - - String code = scr->get_source_code(); - - if (code != "") { - //if there is code, parse it. This way is slower but updates in real-time - GDScriptParser p; - - Error err = p.parse(scr->get_source_code(), scr->get_path().get_base_dir(), true, "", false); - - if (err == OK) { - //only if ok, otherwise use what is cached on the script - //GDScriptParser::ClassNode *base = p. - const GDScriptParser::Node *root = p.get_parse_tree(); - ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, ERR_PARSE_ERROR); - - const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root); - - for (int i = 0; i < cl->functions.size(); i++) { - - if (cl->functions[i]->arguments.size()) - options.insert(String(cl->functions[i]->name) + "("); - else - options.insert(String(cl->functions[i]->name) + "()"); - } - - for (int i = 0; i < cl->static_functions.size(); i++) { - - if (cl->static_functions[i]->arguments.size()) - options.insert(String(cl->static_functions[i]->name) + "("); - else - options.insert(String(cl->static_functions[i]->name) + "()"); - } + } break; + default: { + native_type.has_type = false; + } break; + } + } - if (!isfunction) { - for (int i = 0; i < cl->variables.size(); i++) { + if (!native_type.has_type) { + break; + } - options.insert(String(cl->variables[i].identifier)); - } + StringName class_name = native_type.native_type; + if (!ClassDB::class_exists(class_name)) { + class_name = String("_") + class_name; + if (!ClassDB::class_exists(class_name)) { + break; + } + } - for (int i = 0; i < cl->constant_expressions.size(); i++) { + bool use_type_hint = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints").operator bool(); - options.insert(String(cl->constant_expressions[i].identifier)); - } - } + List<MethodInfo> virtual_methods; + ClassDB::get_virtual_methods(class_name, &virtual_methods); + for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) { - } else { - code = ""; //well, then no code - } - } - - if (code == "") { - //use class directly, no code was found - if (!isfunction) { - for (const Map<StringName, Variant>::Element *E = scr->get_constants().front(); E; E = E->next()) { - options.insert(E->key()); - } - } - for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) { - if (E->get()->get_argument_count()) - options.insert(String(E->key()) + "()"); - else - options.insert(String(E->key()) + "("); - } - - for (const Set<StringName>::Element *E = scr->get_members().front(); E; E = E->next()) { - options.insert(E->get()); - } - } + MethodInfo &mi = E->get(); + String method_hint = mi.name; + if (method_hint.find(":") != -1) { + method_hint = method_hint.get_slice(":", 0); + } + method_hint += "("; - if (scr->get_base().is_valid()) - scr = scr->get_base().ptr(); - else - scr = NULL; + if (mi.arguments.size()) { + for (int i = 0; i < mi.arguments.size(); i++) { + if (i > 0) { + method_hint += ", "; + } + String arg = mi.arguments[i].name; + if (arg.find(":") != -1) { + arg = arg.substr(0, arg.find(":")); + } + method_hint += arg; + if (use_type_hint && mi.arguments[i].type != Variant::NIL) { + method_hint += ": "; + if (mi.arguments[i].type == Variant::OBJECT && mi.arguments[i].class_name != StringName()) { + method_hint += mi.arguments[i].class_name.operator String(); + } else { + method_hint += Variant::get_type_name(mi.arguments[i].type); } } } - - if (!isfunction) { - ClassDB::get_integer_constant_list(t.obj_type, r_options); - - List<PropertyInfo> pinfo; - ClassDB::get_property_list(t.obj_type, &pinfo); - - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) - continue; - if (String(E->get().name).find("/") != -1) - continue; - r_options->push_back(E->get().name); - } + } + method_hint += ")"; + if (use_type_hint && (mi.return_val.type != Variant::NIL || !(mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { + method_hint += " -> "; + if (mi.return_val.type == Variant::NIL) { + method_hint += "void"; + } else if (mi.return_val.type == Variant::OBJECT && mi.return_val.class_name != StringName()) { + method_hint += mi.return_val.class_name.operator String(); + } else { + method_hint += Variant::get_type_name(mi.return_val.type); } + } + method_hint += ":"; - List<MethodInfo> mi; - ClassDB::get_method_list(t.obj_type, &mi, false, true); - for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) { - - if (E->get().name.begins_with("_")) - continue; + options.insert(method_hint); + } + } break; + case GDScriptParser::COMPLETION_YIELD: { + const GDScriptParser::Node *node = parser.get_completion_node(); - if (E->get().arguments.size()) - options.insert(E->get().name + "("); - else - options.insert(E->get().name + "()"); - } - } else { + GDScriptCompletionContext c = context; + c.line = node->line; + GDScriptCompletionIdentifier type; + if (!_guess_expression_type(c, node, type)) { + break; + } - //check InputEvent hint - { - if (t.value.get_type() == Variant::NIL) { - Variant::CallError ce; - t.value = Variant::construct(t.type, NULL, 0, ce); + GDScriptParser::DataType base_type = type.type; + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + for (int i = 0; i < base_type.class_type->_signals.size(); i++) { + options.insert("\"" + base_type.class_type->_signals[i].name.operator String() + "\""); } - - if (!isfunction) { - List<PropertyInfo> pl; - t.value.get_property_list(&pl); - for (List<PropertyInfo>::Element *E = pl.front(); E; E = E->next()) { - - if (String(E->get().name).find("/") == -1) - options.insert(E->get().name); + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::GDSCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + List<MethodInfo> signals; + scr->get_script_signal_list(&signals); + for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { + options.insert("\"" + E->get().name + "\""); + } + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + base_type.has_type = false; + } + } break; + case GDScriptParser::DataType::NATIVE: { + base_type.has_type = false; + + StringName class_name = base_type.native_type; + if (!ClassDB::class_exists(class_name)) { + class_name = String("_") + class_name; + if (!ClassDB::class_exists(class_name)) { + break; } } - List<MethodInfo> mi; - t.value.get_method_list(&mi); - for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) { - if (E->get().arguments.size()) - options.insert(E->get().name + "("); - else - options.insert(E->get().name + "()"); + List<MethodInfo> signals; + ClassDB::get_signal_list(class_name, &signals); + for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { + options.insert("\"" + E->get().name + "\""); } + } break; + default: { + base_type.has_type = false; } } } - } break; - case GDScriptParser::COMPLETION_CALL_ARGUMENTS: { - - _find_call_arguments(context, p.get_completion_node(), p.get_completion_line(), p.get_completion_argument_index(), options, r_forced, r_call_hint); + case GDScriptParser::COMPLETION_RESOURCE_PATH: { + if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) { + _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options); + r_forced = true; + } } break; - case GDScriptParser::COMPLETION_VIRTUAL_FUNC: { - - GDScriptCompletionIdentifier cid = _get_native_class(context); - - if (cid.obj_type != StringName()) { - List<MethodInfo> vm; - ClassDB::get_virtual_methods(cid.obj_type, &vm); - for (List<MethodInfo>::Element *E = vm.front(); E; E = E->next()) { + case GDScriptParser::COMPLETION_ASSIGN: { + GDScriptCompletionIdentifier type; + if (!_guess_expression_type(context, parser.get_completion_node(), type)) { + break; + } - MethodInfo &mi = E->get(); - String m = mi.name; - if (m.find(":") != -1) - m = m.substr(0, m.find(":")); - m += "("; - - if (mi.arguments.size()) { - for (int i = 0; i < mi.arguments.size(); i++) { - if (i > 0) - m += ", "; - String n = mi.arguments[i].name; - if (n.find(":") != -1) - n = n.substr(0, n.find(":")); - m += n; + if (!type.enumeration.empty()) { + _find_enumeration_candidates(type.enumeration, options); + r_forced = options.size() > 0; + } + } break; + case GDScriptParser::COMPLETION_TYPE_HINT: { + const GDScriptParser::ClassNode *clss = context._class; + while (clss) { + for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = clss->constant_expressions.front(); E; E = E->next()) { + GDScriptCompletionIdentifier constant; + GDScriptCompletionContext c = context; + c.function = NULL; + c.block = NULL; + 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()); } } - m += "):"; + } + for (int i = 0; i < clss->subclasses.size(); i++) { + if (clss->subclasses[i]->name != StringName()) { + options.insert(clss->subclasses[i]->name.operator String()); + } + } + clss = clss->owner; + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + options.insert(Variant::get_type_name((Variant::Type)i)); + } + } - options.insert(m); + List<StringName> native_classes; + ClassDB::get_class_list(&native_classes); + for (List<StringName>::Element *E = native_classes.front(); E; E = E->next()) { + String class_name = E->get().operator String(); + if (class_name.begins_with("_")) { + class_name = class_name.right(1); } + if (Engine::get_singleton()->has_singleton(class_name)) { + continue; + } + options.insert(class_name); } - } break; - case GDScriptParser::COMPLETION_YIELD: { - const GDScriptParser::Node *node = p.get_completion_node(); + // 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()); + } - GDScriptCompletionIdentifier t; - if (!_guess_expression_type(context, node, p.get_completion_line(), t)) + if (parser.get_completion_identifier_is_function()) { + options.insert("void"); + } + r_forced = true; + } break; + case GDScriptParser::COMPLETION_TYPE_HINT_INDEX: { + GDScriptCompletionIdentifier base; + String index = parser.get_completion_cursor().operator String(); + if (!_guess_identifier_type(context, index.get_slice(".", 0), base)) { break; + } - if (t.type == Variant::OBJECT && t.obj_type != StringName()) { + GDScriptCompletionContext c = context; + c._class = NULL; + c.function = NULL; + c.block = NULL; + bool finding = true; + index = index.right(index.find(".") + 1); + while (index.find(".") != -1) { + String id = index.get_slice(".", 0); - List<MethodInfo> sigs; - ClassDB::get_signal_list(t.obj_type, &sigs); - for (List<MethodInfo>::Element *E = sigs.front(); E; E = E->next()) { - options.insert("\"" + E->get().name + "\""); - r_forced = true; + GDScriptCompletionIdentifier sub_base; + if (!_guess_identifier_type_from_base(c, base, id, sub_base)) { + finding = false; + break; } + index = index.right(index.find(".") + 1); + base = sub_base; } - } break; - case GDScriptParser::COMPLETION_RESOURCE_PATH: { - - if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) { - get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options); - r_forced = true; + if (!finding) { + break; } - } break; - case GDScriptParser::COMPLETION_ASSIGN: { -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) - - GDScriptCompletionIdentifier ci; - if (_guess_expression_type(context, p.get_completion_node(), p.get_completion_line(), ci)) { - - String enumeration = ci.enumeration; - if (enumeration.find(".") != -1) { - //class constant - List<StringName> constants; - String cls = enumeration.get_slice(".", 0); - String enm = enumeration.get_slice(".", 1); - - ClassDB::get_enum_constants(cls, enm, &constants); - //constants.sort_custom<StringName::AlphCompare>(); - for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { - String add = cls + "." + E->get(); - r_options->push_back(add); - r_forced = true; - } - } else { - //global constant - StringName current_enum = enumeration; + GDScriptParser::DataType base_type = base.type; + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + if (base_type.class_type) { + for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = base_type.class_type->constant_expressions.front(); E; E = E->next()) { + GDScriptCompletionIdentifier constant; + GDScriptCompletionContext c = context; + c._class = base_type.class_type; + c.function = NULL; + c.block = NULL; + 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()); + } + } + } + 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()); + } + } - for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { - if (GlobalConstants::get_global_constant_enum(i) == current_enum) { - r_options->push_back(GlobalConstants::get_global_constant_name(i)); - r_forced = true; + base_type = base_type.class_type->base_type; + } else { + base_type.has_type = false; } - } - //global + } break; + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::GDSCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + Map<StringName, Variant> constants; + scr->get_constants(&constants); + 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()); + } + } + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.has_type = false; + } + } else { + base_type.has_type = false; + } + } break; + default: { + base_type.has_type = false; + } break; } } -#endif + r_forced = options.size() > 0; } break; } @@ -2531,6 +2854,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base #endif +//////// END COMPLETION ////////// + String GDScriptLanguage::_get_indentation() const { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -2602,8 +2927,7 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t break; } - //print_line(itos(indent_stack.size())+","+itos(tc)+": "+l); - lines[i] = l; + lines.write[i] = l; } p_code = ""; @@ -2616,6 +2940,185 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t #ifdef TOOLS_ENABLED +static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, const String &p_symbol, bool p_is_function, GDScriptLanguage::LookupResult &r_result) { + GDScriptParser::DataType base_type = p_base; + + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + if (base_type.class_type) { + if (p_is_function) { + for (int i = 0; i < base_type.class_type->functions.size(); i++) { + if (base_type.class_type->functions[i]->name == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = base_type.class_type->functions[i]->line; + return OK; + } + } + for (int i = 0; i < base_type.class_type->static_functions.size(); i++) { + if (base_type.class_type->static_functions[i]->name == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = base_type.class_type->static_functions[i]->line; + return OK; + } + } + } else { + if (base_type.class_type->constant_expressions.has(p_symbol)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = base_type.class_type->constant_expressions[p_symbol].expression->line; + return OK; + } + + for (int i = 0; i < base_type.class_type->variables.size(); i++) { + if (base_type.class_type->variables[i].identifier == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = base_type.class_type->variables[i].line; + return OK; + } + } + } + } + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::GDSCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + int line = scr->get_member_line(p_symbol); + if (line >= 0) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = line; + r_result.script = scr; + return OK; + } + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + base_type.has_type = false; + } + } break; + case GDScriptParser::DataType::NATIVE: { + StringName class_name = base_type.native_type; + if (!ClassDB::class_exists(class_name)) { + class_name = String("_") + class_name; + if (!ClassDB::class_exists(class_name)) { + base_type.has_type = false; + break; + } + } + + if (ClassDB::has_method(class_name, p_symbol, true)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name = base_type.native_type; + r_result.class_member = p_symbol; + return OK; + } + + List<MethodInfo> virtual_methods; + ClassDB::get_virtual_methods(class_name, &virtual_methods, true); + for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) { + if (E->get().name == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name = base_type.native_type; + r_result.class_member = p_symbol; + return OK; + } + } + + StringName enum_name = ClassDB::get_integer_constant_enum(class_name, p_symbol, true); + if (enum_name != StringName()) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM; + r_result.class_name = base_type.native_type; + r_result.class_member = enum_name; + return OK; + } + + List<String> constants; + ClassDB::get_integer_constant_list(class_name, &constants, true); + for (List<String>::Element *E = constants.front(); E; E = E->next()) { + if (E->get() == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; + r_result.class_name = base_type.native_type; + r_result.class_member = p_symbol; + return OK; + } + } + + List<PropertyInfo> properties; + ClassDB::get_property_list(class_name, &properties, true); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + if (E->get().name == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; + r_result.class_name = base_type.native_type; + r_result.class_member = p_symbol; + return OK; + } + } + + StringName parent = ClassDB::get_parent_class(class_name); + if (parent != StringName()) { + if (String(parent).begins_with("_")) { + base_type.native_type = String(parent).right(1); + } else { + base_type.native_type = parent; + } + } else { + base_type.has_type = false; + } + } break; + case GDScriptParser::DataType::BUILTIN: { + base_type.has_type = false; + + if (Variant::has_constant(base_type.builtin_type, p_symbol)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; + r_result.class_name = Variant::get_type_name(base_type.builtin_type); + r_result.class_member = p_symbol; + return OK; + } + + Variant v; + REF v_ref; + if (base_type.builtin_type == Variant::OBJECT) { + v_ref.instance(); + v = v_ref; + } else { + Variant::CallError err; + v = Variant::construct(base_type.builtin_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + break; + } + } + + if (v.has_method(p_symbol)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name = Variant::get_type_name(base_type.builtin_type); + r_result.class_member = p_symbol; + return OK; + } + + bool valid = false; + v.get(p_symbol, &valid); + if (valid) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; + r_result.class_name = Variant::get_type_name(base_type.builtin_type); + r_result.class_member = p_symbol; + return OK; + } + } break; + default: { + base_type.has_type = false; + } break; + } + } + + return ERR_CANT_RESOLVE; +} + Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_base_path, Object *p_owner, LookupResult &r_result) { //before parsing, try the usual stuff @@ -2623,6 +3126,13 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; r_result.class_name = p_symbol; return OK; + } else { + String under_prefix = "_" + p_symbol; + if (ClassDB::class_exists(under_prefix)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; + r_result.class_name = p_symbol; + return OK; + } } for (int i = 0; i < Variant::VARIANT_MAX; i++) { @@ -2650,17 +3160,18 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol return OK; } - GDScriptParser p; - p.parse(p_code, p_base_path, false, "", true); + GDScriptParser parser; + parser.parse(p_code, p_base_path, false, "", true); - if (p.get_completion_type() == GDScriptParser::COMPLETION_NONE) + if (parser.get_completion_type() == GDScriptParser::COMPLETION_NONE) { return ERR_CANT_RESOLVE; + } GDScriptCompletionContext context; - - context._class = p.get_completion_class(); - context.block = p.get_completion_block(); - context.function = p.get_completion_function(); + context._class = parser.get_completion_class(); + context.function = parser.get_completion_function(); + context.block = parser.get_completion_block(); + context.line = parser.get_completion_line(); context.base = p_owner; context.base_path = p_base_path; @@ -2675,171 +3186,68 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } } - bool isfunction = false; + bool is_function = false; - switch (p.get_completion_type()) { - - case GDScriptParser::COMPLETION_GET_NODE: - case GDScriptParser::COMPLETION_NONE: { - } break; + switch (parser.get_completion_type()) { case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; - r_result.class_name = Variant::get_type_name(p.get_completion_built_in_constant()); + r_result.class_name = Variant::get_type_name(parser.get_completion_built_in_constant()); r_result.class_member = p_symbol; return OK; - } break; + case GDScriptParser::COMPLETION_PARENT_FUNCTION: case GDScriptParser::COMPLETION_FUNCTION: { - - if (context._class && context._class->functions.size()) { - for (int i = 0; i < context._class->functions.size(); i++) { - if (context._class->functions[i]->name == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context._class->functions[i]->line; - return OK; - } - } - } - - Ref<GDScript> parent = _get_parent_class(context); - while (parent.is_valid()) { - int line = parent->get_member_line(p_symbol); - if (line >= 0) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = line; - r_result.script = parent; - return OK; - } - - parent = parent->get_base(); - } - - GDScriptCompletionIdentifier identifier = _get_native_class(context); - print_line("identifier: " + String(identifier.obj_type)); - - if (ClassDB::has_method(identifier.obj_type, p_symbol)) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = identifier.obj_type; - r_result.class_member = p_symbol; - return OK; - } - - } break; + is_function = true; + } // fallthrough case GDScriptParser::COMPLETION_IDENTIFIER: { - //check if a function - if (p.get_completion_identifier_is_function()) { - if (context._class && context._class->functions.size()) { - for (int i = 0; i < context._class->functions.size(); i++) { - if (context._class->functions[i]->name == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context._class->functions[i]->line; - return OK; - } - } - } - - Ref<GDScript> parent = _get_parent_class(context); - while (parent.is_valid()) { - int line = parent->get_member_line(p_symbol); - if (line >= 0) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = line; - r_result.script = parent; - return OK; - } - - parent = parent->get_base(); - } - - GDScriptCompletionIdentifier identifier = _get_native_class(context); - - if (ClassDB::has_method(identifier.obj_type, p_symbol)) { + if (!is_function) { + is_function = parser.get_completion_identifier_is_function(); + } - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = identifier.obj_type; - r_result.class_member = p_symbol; - return OK; + GDScriptParser::DataType base_type; + if (context._class) { + if (parser.get_completion_type() != GDScriptParser::COMPLETION_PARENT_FUNCTION) { + base_type.has_type = true; + base_type.kind = GDScriptParser::DataType::CLASS; + base_type.class_type = const_cast<GDScriptParser::ClassNode *>(context._class); + } else { + base_type = context._class->base_type; } } else { + break; + } - GDScriptCompletionIdentifier gdi = _get_native_class(context); - if (gdi.obj_type != StringName()) { - bool valid; - Variant::Type t = ClassDB::get_property_type(gdi.obj_type, p_symbol, &valid); - if (t != Variant::NIL && valid) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; - r_result.class_name = gdi.obj_type; - r_result.class_member = p_symbol; - return OK; - } - } - + if (!is_function && context.block) { + // Lookup local variables const GDScriptParser::BlockNode *block = context.block; - //search in blocks going up (local var?) while (block) { - - for (int i = 0; i < block->statements.size(); i++) { - - if (block->statements[i]->line > p.get_completion_line()) - continue; - - if (block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) { - - const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(block->statements[i]); - - if (lv->assign && lv->name == p_symbol) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = block->statements[i]->line; - return OK; - } - } + if (block->variables.has(p_symbol)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = block->variables[p_symbol]->line; + return OK; } block = block->parent_block; } + } - //guess from function arguments - if (context.function && context.function->name != StringName()) { - - for (int i = 0; i < context.function->arguments.size(); i++) { - - if (context.function->arguments[i] == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context.function->line; - return OK; - } - } - } - - //guess in class constants - - for (int i = 0; i < context._class->constant_expressions.size(); i++) { - - if (context._class->constant_expressions[i].identifier == p_symbol) { + if (context.function && context.function->name != StringName()) { + // Lookup function arguments + for (int i = 0; i < context.function->arguments.size(); i++) { + if (context.function->arguments[i] == p_symbol) { r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context._class->constant_expressions[i].expression->line; + r_result.location = context.function->line; return OK; } } + } - //guess in class variables - if (!(context.function && context.function->_static)) { - - for (int i = 0; i < context._class->variables.size(); i++) { - - if (context._class->variables[i].identifier == p_symbol) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context._class->variables[i].line; - return OK; - } - } - } + if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) { + return OK; + } - //guess in autoloads as singletons + if (!is_function) { + // Guess in autoloads as singletons List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); @@ -2856,8 +3264,8 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol String script = path.substr(1, path.length()); if (!script.ends_with(".gd")) { - //not a script, try find the script anyway, - //may have some success + // Not a script, try find the script anyway, + // may have some success script = script.get_basename() + ".gd"; } @@ -2872,7 +3280,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } } - //global + // Global Map<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map(); if (classes.has(p_symbol)) { Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]]; @@ -2895,7 +3303,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } } else { /* - // Because get_integer_constant_enum and get_integer_constant dont work on @GlobalScope + // Because get_integer_constant_enum and get_integer_constant don't work on @GlobalScope // We cannot determine the exact nature of the identifier here // Otherwise these codes would work StringName enumName = ClassDB::get_integer_constant_enum("@GlobalScope", p_symbol, true); @@ -2918,152 +3326,32 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } } } - - } break; - case GDScriptParser::COMPLETION_PARENT_FUNCTION: { - } break; - case GDScriptParser::COMPLETION_METHOD: - isfunction = true; + case GDScriptParser::COMPLETION_METHOD: { + is_function = true; + } // fallthrough case GDScriptParser::COMPLETION_INDEX: { - - const GDScriptParser::Node *node = p.get_completion_node(); - if (node->type != GDScriptParser::Node::TYPE_OPERATOR) + const GDScriptParser::Node *node = parser.get_completion_node(); + if (node->type != GDScriptParser::Node::TYPE_OPERATOR) { + break; + } + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], base)) { break; - - GDScriptCompletionIdentifier t; - if (_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], p.get_completion_line(), t)) { - - if (t.type == Variant::OBJECT && t.obj_type == "GDScriptNativeClass") { - //native enum - Ref<GDScriptNativeClass> gdn = t.value; - if (gdn.is_valid()) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; - r_result.class_name = gdn->get_name(); - r_result.class_member = p_symbol; - return OK; - } - } else if (t.type == Variant::OBJECT && t.obj_type != StringName()) { - - Ref<GDScript> on_script; - - if (t.value.get_type()) { - Object *obj = t.value; - - if (obj) { - - on_script = obj->get_script(); - - if (on_script.is_valid()) { - int loc = on_script->get_member_line(p_symbol); - if (loc >= 0) { - r_result.script = on_script; - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = loc; - return OK; - } - } - } - } - - if (ClassDB::has_method(t.obj_type, p_symbol)) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = t.obj_type; - r_result.class_member = p_symbol; - return OK; - } - - StringName enumName = ClassDB::get_integer_constant_enum(t.obj_type, p_symbol, true); - if (enumName != StringName()) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM; - r_result.class_name = t.obj_type; - r_result.class_member = enumName; - return OK; - } - - bool success; - ClassDB::get_integer_constant(t.obj_type, p_symbol, &success); - if (success) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; - r_result.class_name = t.obj_type; - r_result.class_member = p_symbol; - return OK; - } - - ClassDB::get_property_type(t.obj_type, p_symbol, &success); - - if (success) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; - r_result.class_name = t.obj_type; - r_result.class_member = p_symbol; - return OK; - } - - } else { - - Variant::CallError ce; - Variant v = Variant::construct(t.type, NULL, 0, ce); - - bool valid; - v.get_numeric_constant_value(t.type, p_symbol, &valid); - if (valid) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; - r_result.class_name = Variant::get_type_name(t.type); - r_result.class_member = p_symbol; - return OK; - } - - //todo check all inputevent types for property - - v.get(p_symbol, &valid); - - if (valid) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; - r_result.class_name = Variant::get_type_name(t.type); - r_result.class_member = p_symbol; - return OK; - } - - if (v.has_method(p_symbol)) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = Variant::get_type_name(t.type); - r_result.class_member = p_symbol; - return OK; - } - } } - } break; - case GDScriptParser::COMPLETION_CALL_ARGUMENTS: { - - return ERR_CANT_RESOLVE; + if (_lookup_symbol_from_base(base.type, p_symbol, is_function, r_result) == OK) { + return OK; + } } break; case GDScriptParser::COMPLETION_VIRTUAL_FUNC: { + GDScriptParser::DataType base_type = context._class->base_type; - GDScriptCompletionIdentifier cid = _get_native_class(context); - - if (cid.obj_type != StringName()) { - List<MethodInfo> vm; - ClassDB::get_virtual_methods(cid.obj_type, &vm); - for (List<MethodInfo>::Element *E = vm.front(); E; E = E->next()) { - - if (p_symbol == E->get().name) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = cid.obj_type; - r_result.class_member = p_symbol; - return OK; - } - } + if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { + return OK; } } break; - case GDScriptParser::COMPLETION_YIELD: { - - return ERR_CANT_RESOLVE; - - } break; + default: {} } return ERR_CANT_RESOLVE; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 10599f0c38..cd6c21a629 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -30,9 +30,9 @@ #include "gdscript_function.h" +#include "core/os/os.h" #include "gdscript.h" #include "gdscript_functions.h" -#include "os/os.h" Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant *p_stack, String &r_error) const { @@ -62,7 +62,7 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta } #endif //member indexing is O(1) - return &p_instance->members[address]; + return &p_instance->members.write[address]; } break; case ADDR_TYPE_CLASS_CONSTANT: { @@ -155,6 +155,7 @@ String GDScriptFunction::_get_call_error(const Variant::CallError &p_err, const return err_text; } +#ifdef DEBUG_ENABLED static String _get_var_type(const Variant *p_type) { String basestr; @@ -164,7 +165,6 @@ static String _get_var_type(const Variant *p_type) { if (!bobj) { basestr = "null instance"; } else { -#ifdef DEBUG_ENABLED if (ObjectDB::instance_validate(bobj)) { if (bobj->get_script_instance()) basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")"; @@ -173,10 +173,6 @@ static String _get_var_type(const Variant *p_type) { } else { basestr = "previously freed instance"; } - -#else - basestr = "Object"; -#endif } } else { @@ -185,12 +181,14 @@ static String _get_var_type(const Variant *p_type) { return basestr; } +#endif #if defined(__GNUC__) #define OPCODES_TABLE \ static const void *switch_table_ops[] = { \ &&OPCODE_OPERATOR, \ &&OPCODE_EXTENDS_TEST, \ + &&OPCODE_IS_BUILTIN, \ &&OPCODE_SET, \ &&OPCODE_GET, \ &&OPCODE_SET_NAMED, \ @@ -200,6 +198,12 @@ static String _get_var_type(const Variant *p_type) { &&OPCODE_ASSIGN, \ &&OPCODE_ASSIGN_TRUE, \ &&OPCODE_ASSIGN_FALSE, \ + &&OPCODE_ASSIGN_TYPED_BUILTIN, \ + &&OPCODE_ASSIGN_TYPED_NATIVE, \ + &&OPCODE_ASSIGN_TYPED_SCRIPT, \ + &&OPCODE_CAST_TO_BUILTIN, \ + &&OPCODE_CAST_TO_NATIVE, \ + &&OPCODE_CAST_TO_SCRIPT, \ &&OPCODE_CONSTRUCT, \ &&OPCODE_CONSTRUCT_ARRAY, \ &&OPCODE_CONSTRUCT_DICTIONARY, \ @@ -271,7 +275,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif uint32_t alloca_size = 0; - GDScript *_class; + GDScript *script; int ip = 0; int line = _initial_line; @@ -282,7 +286,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a line = p_state->line; ip = p_state->ip; alloca_size = p_state->stack.size(); - _class = p_state->_class; + script = p_state->script.ptr(); p_instance = p_state->instance; defarg = p_state->defarg; self = p_state->self; @@ -318,10 +322,28 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (_stack_size) { stack = (Variant *)aptr; - for (int i = 0; i < p_argcount; i++) - memnew_placement(&stack[i], Variant(*p_args[i])); - for (int i = p_argcount; i < _stack_size; i++) + for (int i = 0; i < p_argcount; i++) { + if (!argument_types[i].has_type) { + memnew_placement(&stack[i], Variant(*p_args[i])); + continue; + } + + if (!argument_types[i].is_type(*p_args[i], true)) { + r_err.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_err.argument = i; + r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; + return Variant(); + } + if (argument_types[i].kind == GDScriptDataType::BUILTIN) { + Variant arg = Variant::construct(argument_types[i].builtin_type, &p_args[i], 1, r_err); + memnew_placement(&stack[i], Variant(arg)); + } else { + memnew_placement(&stack[i], Variant(*p_args[i])); + } + } + for (int i = p_argcount; i < _stack_size; i++) { memnew_placement(&stack[i], Variant); + } } else { stack = NULL; } @@ -346,9 +368,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else { self = p_instance->owner; } - _class = p_instance->script.ptr(); + script = p_instance->script.ptr(); } else { - _class = _script; + script = _script; } } @@ -373,7 +395,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #define GET_VARIANT_PTR(m_v, m_code_ofs) \ Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, _class, self, stack, err_text); \ + m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, stack, err_text); \ if (unlikely(!m_v)) \ OPCODE_BREAK; @@ -382,7 +404,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #define CHECK_SPACE(m_space) #define GET_VARIANT_PTR(m_v, m_code_ofs) \ Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, _class, self, stack, err_text); + m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, stack, err_text); #endif @@ -397,8 +419,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a profile.call_count++; profile.frame_call_count++; } -#endif bool exit_ok = false; +#endif #ifdef DEBUG_ENABLED OPCODE_WHILE(ip < _code_size) { @@ -455,56 +477,53 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_VARIANT_PTR(dst, 3); #ifdef DEBUG_ENABLED - if (a->get_type() != Variant::OBJECT || a->operator Object *() == NULL) { - - err_text = "Left operand of 'is' is not an instance of anything."; - OPCODE_BREAK; - } if (b->get_type() != Variant::OBJECT || b->operator Object *() == NULL) { err_text = "Right operand of 'is' is not a class."; OPCODE_BREAK; } #endif - Object *obj_A = *a; - Object *obj_B = *b; - - GDScript *scr_B = Object::cast_to<GDScript>(obj_B); bool extends_ok = false; + if (a->get_type() == Variant::OBJECT && a->operator Object *() != NULL) { + Object *obj_A = *a; + Object *obj_B = *b; - if (scr_B) { - //if B is a script, the only valid condition is that A has an instance which inherits from the script - //in other situation, this shoul return false. + GDScript *scr_B = Object::cast_to<GDScript>(obj_B); - if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) { + if (scr_B) { + //if B is a script, the only valid condition is that A has an instance which inherits from the script + //in other situation, this shoul return false. - GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr()); - //bool found=false; - while (cmp) { + if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) { - if (cmp == scr_B) { - //inherits from script, all ok - extends_ok = true; - break; - } + GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr()); + //bool found=false; + while (cmp) { - cmp = cmp->_base; + if (cmp == scr_B) { + //inherits from script, all ok + extends_ok = true; + break; + } + + cmp = cmp->_base; + } } - } - } else { + } else { - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B); + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B); #ifdef DEBUG_ENABLED - if (!nc) { + if (!nc) { - err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "')."; - OPCODE_BREAK; - } + err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "')."; + OPCODE_BREAK; + } #endif - extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name()); + extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name()); + } } *dst = extends_ok; @@ -512,6 +531,21 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_IS_BUILTIN) { + + CHECK_SPACE(4); + + GET_VARIANT_PTR(value, 1); + Variant::Type var_type = (Variant::Type)_code_ptr[ip + 2]; + GET_VARIANT_PTR(dst, 3); + + GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); + + *dst = value->get_type() == var_type; + ip += 4; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_SET) { CHECK_SPACE(3); @@ -642,8 +676,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_VARIANT_PTR(src, 2); bool valid; +#ifndef DEBUG_ENABLED + ClassDB::set_property(p_instance->owner, *index, *src, &valid); +#else bool ok = ClassDB::set_property(p_instance->owner, *index, *src, &valid); -#ifdef DEBUG_ENABLED if (!ok) { err_text = "Internal error setting property: " + String(*index); OPCODE_BREAK; @@ -663,9 +699,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); const StringName *index = &_global_names_ptr[indexname]; GET_VARIANT_PTR(dst, 2); - bool ok = ClassDB::get_property(p_instance->owner, *index, *dst); -#ifdef DEBUG_ENABLED +#ifndef DEBUG_ENABLED + ClassDB::get_property(p_instance->owner, *index, *dst); +#else + bool ok = ClassDB::get_property(p_instance->owner, *index, *dst); if (!ok) { err_text = "Internal error getting property: " + String(*index); OPCODE_BREAK; @@ -709,6 +747,215 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) { + + CHECK_SPACE(4); + GET_VARIANT_PTR(dst, 2); + GET_VARIANT_PTR(src, 3); + +#ifdef DEBUG_ENABLED + Variant::Type var_type = (Variant::Type)_code_ptr[ip + 1]; + GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); + + if (src->get_type() != var_type) { + if (Variant::can_convert_strict(src->get_type(), var_type)) { + Variant::CallError ce; + *dst = Variant::construct(var_type, const_cast<const Variant **>(&src), 1, ce); + } else { + err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + + "' to a variable of type '" + Variant::get_type_name(var_type) + "'."; + OPCODE_BREAK; + } + } else { +#endif // DEBUG_ENABLED + *dst = *src; +#ifdef DEBUG_ENABLED + } +#endif // DEBUG_ENABLED + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { + + CHECK_SPACE(4); + GET_VARIANT_PTR(dst, 2); + GET_VARIANT_PTR(src, 3); + +#ifdef DEBUG_ENABLED + GET_VARIANT_PTR(type, 1); + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); + GD_ERR_BREAK(!nc); + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + + "' to a variable of type '" + nc->get_name() + "'."; + OPCODE_BREAK; + } + Object *src_obj = src->operator Object *(); + + if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { + err_text = "Trying to assign value of type '" + src_obj->get_class_name() + + "' to a variable of type '" + nc->get_name() + "'."; + OPCODE_BREAK; + } +#endif // DEBUG_ENABLED + *dst = *src; + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) { + + CHECK_SPACE(4); + GET_VARIANT_PTR(dst, 2); + GET_VARIANT_PTR(src, 3); + +#ifdef DEBUG_ENABLED + GET_VARIANT_PTR(type, 1); + Script *base_type = Object::cast_to<Script>(type->operator Object *()); + + GD_ERR_BREAK(!base_type); + + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + + if (src->get_type() != Variant::NIL && src->operator Object *() != NULL) { + + ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); + if (!scr_inst) { + err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() + + "' to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + + Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); + bool valid = false; + + while (src_type) { + if (src_type == base_type) { + valid = true; + break; + } + src_type = src_type->get_base_script().ptr(); + } + + if (!valid) { + err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() + + "' to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + } +#endif // DEBUG_ENABLED + + *dst = *src; + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_BUILTIN) { + + CHECK_SPACE(4); + Variant::Type to_type = (Variant::Type)_code_ptr[ip + 1]; + GET_VARIANT_PTR(src, 2); + GET_VARIANT_PTR(dst, 3); + + GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX); + + Variant::CallError err; + *dst = Variant::construct(to_type, (const Variant **)&src, 1, err); + +#ifdef DEBUG_ENABLED + if (err.error != Variant::CallError::CALL_OK) { + err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'."; + OPCODE_BREAK; + } +#endif + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_NATIVE) { + + CHECK_SPACE(4); + GET_VARIANT_PTR(to_type, 1); + GET_VARIANT_PTR(src, 2); + GET_VARIANT_PTR(dst, 3); + + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *()); + GD_ERR_BREAK(!nc); + +#ifdef DEBUG_ENABLED + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Invalid cast: can't convert a non-object value to an object type."; + OPCODE_BREAK; + } +#endif + Object *src_obj = src->operator Object *(); + + if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { + *dst = Variant(); // invalid cast, assign NULL + } else { + *dst = *src; + } + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_SCRIPT) { + + CHECK_SPACE(4); + GET_VARIANT_PTR(to_type, 1); + GET_VARIANT_PTR(src, 2); + GET_VARIANT_PTR(dst, 3); + + Script *base_type = Object::cast_to<Script>(to_type->operator Object *()); + + GD_ERR_BREAK(!base_type); + +#ifdef DEBUG_ENABLED + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } +#endif + + bool valid = false; + + if (src->get_type() != Variant::NIL && src->operator Object *() != NULL) { + + ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); + + if (scr_inst) { + + Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); + + while (src_type) { + if (src_type == base_type) { + valid = true; + break; + } + src_type = src_type->get_base_script().ptr(); + } + } + } + + if (valid) { + *dst = *src; // Valid cast, copy the source object + } else { + *dst = Variant(); // invalid cast, assign NULL + } + + ip += 4; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_CONSTRUCT) { CHECK_SPACE(2); @@ -1001,16 +1248,15 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a gdfs->state.stack.resize(alloca_size); //copy variant stack for (int i = 0; i < _stack_size; i++) { - memnew_placement(&gdfs->state.stack[sizeof(Variant) * i], Variant(stack[i])); + memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i])); } gdfs->state.stack_size = _stack_size; gdfs->state.self = self; gdfs->state.alloca_size = alloca_size; - gdfs->state._class = _class; + gdfs->state.script = Ref<GDScript>(_script); gdfs->state.ip = ip + ipofs; gdfs->state.line = line; gdfs->state.instance_id = (p_instance && p_instance->get_owner()) ? p_instance->get_owner()->get_instance_id() : 0; - gdfs->state.script_id = _class->get_instance_id(); //gdfs->state.result_pos=ip+ipofs-1; gdfs->state.defarg = defarg; gdfs->state.instance = p_instance; @@ -1033,10 +1279,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_BREAK; } #endif + Object *obj = argobj->operator Object *(); String signal = argname->operator String(); -#ifdef DEBUG_ENABLED +#ifdef DEBUG_ENABLED if (!obj) { err_text = "First argument of yield() is null."; OPCODE_BREAK; @@ -1053,17 +1300,19 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_BREAK; } -#endif Error err = obj->connect(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT); -#ifdef DEBUG_ENABLED if (err != OK) { err_text = "Error connecting to signal: " + signal + " during yield()."; OPCODE_BREAK; } +#else + obj->connect(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT); #endif } +#ifdef DEBUG_ENABLED exit_ok = true; +#endif OPCODE_BREAK; } @@ -1140,7 +1389,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a CHECK_SPACE(2); GET_VARIANT_PTR(r, 1); retvalue = *r; +#ifdef DEBUG_ENABLED exit_ok = true; +#endif OPCODE_BREAK; } @@ -1212,9 +1463,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSERT) { CHECK_SPACE(2); - GET_VARIANT_PTR(test, 1); #ifdef DEBUG_ENABLED + GET_VARIANT_PTR(test, 1); bool result = test->booleanize(); if (!result) { @@ -1269,8 +1520,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_END) { - +#ifdef DEBUG_ENABLED exit_ok = true; +#endif OPCODE_BREAK; } @@ -1293,8 +1545,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a String err_file; if (p_instance) err_file = p_instance->script->path; - else if (_class) - err_file = _class->path; + else if (script) + err_file = script->path; if (err_file == "") err_file = "<built-in>"; String err_func = name; @@ -1362,7 +1614,7 @@ StringName GDScriptFunction::get_global_name(int p_idx) const { int GDScriptFunction::get_default_argument_count() const { - return default_arguments.size(); + return _default_arg_count; } int GDScriptFunction::get_default_argument_addr(int p_idx) const { @@ -1370,6 +1622,15 @@ int GDScriptFunction::get_default_argument_addr(int p_idx) const { return default_arguments[p_idx]; } +GDScriptDataType GDScriptFunction::get_return_type() const { + return return_type; +} + +GDScriptDataType GDScriptFunction::get_argument_type(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, argument_types.size(), GDScriptDataType()); + return argument_types[p_idx]; +} + StringName GDScriptFunction::get_name() const { return name; @@ -1499,17 +1760,14 @@ GDScriptFunction::~GDScriptFunction() { Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { -#ifdef DEBUG_ENABLED if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { +#ifdef DEBUG_ENABLED ERR_EXPLAIN("Resumed after yield, but class instance is gone"); ERR_FAIL_V(Variant()); - } - - if (state.script_id && !ObjectDB::get_instance(state.script_id)) { - ERR_EXPLAIN("Resumed after yield, but script is gone"); - ERR_FAIL_V(Variant()); - } +#else + return Variant(); #endif + } Variant arg; r_error.error = Variant::CallError::CALL_OK; @@ -1579,9 +1837,6 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const { //class instance gone? if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) return false; - //script gone? - if (state.script_id && !ObjectDB::get_instance(state.script_id)) - return false; } return true; @@ -1590,17 +1845,14 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const { Variant GDScriptFunctionState::resume(const Variant &p_arg) { ERR_FAIL_COND_V(!function, Variant()); -#ifdef DEBUG_ENABLED if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { +#ifdef DEBUG_ENABLED ERR_EXPLAIN("Resumed after yield, but class instance is gone"); ERR_FAIL_V(Variant()); - } - - if (state.script_id && !ObjectDB::get_instance(state.script_id)) { - ERR_EXPLAIN("Resumed after yield, but script is gone"); - ERR_FAIL_V(Variant()); - } +#else + return Variant(); #endif + } state.result = p_arg; Variant::CallError err; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 770d5c8733..5509edf24a 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -31,22 +31,112 @@ #ifndef GDSCRIPT_FUNCTION_H #define GDSCRIPT_FUNCTION_H -#include "os/thread.h" -#include "pair.h" -#include "reference.h" -#include "script_language.h" -#include "self_list.h" -#include "string_db.h" -#include "variant.h" +#include "core/os/thread.h" +#include "core/pair.h" +#include "core/reference.h" +#include "core/script_language.h" +#include "core/self_list.h" +#include "core/string_db.h" +#include "core/variant.h" class GDScriptInstance; class GDScript; +struct GDScriptDataType { + bool has_type; + enum { + BUILTIN, + NATIVE, + SCRIPT, + GDSCRIPT + } kind; + Variant::Type builtin_type; + StringName native_type; + Ref<Script> script_type; + + bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const { + if (!has_type) return true; // Can't type check + + switch (kind) { + case BUILTIN: { + Variant::Type var_type = p_variant.get_type(); + bool valid = builtin_type == var_type; + if (!valid && p_allow_implicit_conversion) { + valid = Variant::can_convert_strict(var_type, builtin_type); + } + return valid; + } break; + case NATIVE: { + if (p_variant.get_type() == Variant::NIL) { + return true; + } + if (p_variant.get_type() != Variant::OBJECT) { + return false; + } + Object *obj = p_variant.operator Object *(); + if (obj && !ClassDB::is_parent_class(obj->get_class_name(), native_type)) { + return false; + } + return true; + } break; + case SCRIPT: + case GDSCRIPT: { + if (p_variant.get_type() == Variant::NIL) { + return true; + } + if (p_variant.get_type() != Variant::OBJECT) { + return false; + } + Object *obj = p_variant.operator Object *(); + Ref<Script> base = obj && obj->get_script_instance() ? obj->get_script_instance()->get_script() : NULL; + bool valid = false; + while (base.is_valid()) { + if (base == script_type) { + valid = true; + break; + } + base = base->get_base_script(); + } + return valid; + } break; + } + return false; + } + + operator PropertyInfo() const { + PropertyInfo info; + if (has_type) { + switch (kind) { + case BUILTIN: { + info.type = builtin_type; + } break; + case NATIVE: { + info.type = Variant::OBJECT; + info.class_name = native_type; + } break; + case SCRIPT: + case GDSCRIPT: { + info.type = Variant::OBJECT; + info.class_name = script_type->get_instance_base_type(); + } break; + } + } else { + info.type = Variant::NIL; + info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } + return info; + } + + GDScriptDataType() : + has_type(false) {} +}; + class GDScriptFunction { public: enum Opcode { OPCODE_OPERATOR, OPCODE_EXTENDS_TEST, + OPCODE_IS_BUILTIN, OPCODE_SET, OPCODE_GET, OPCODE_SET_NAMED, @@ -56,6 +146,12 @@ public: OPCODE_ASSIGN, OPCODE_ASSIGN_TRUE, OPCODE_ASSIGN_FALSE, + OPCODE_ASSIGN_TYPED_BUILTIN, + OPCODE_ASSIGN_TYPED_NATIVE, + OPCODE_ASSIGN_TYPED_SCRIPT, + OPCODE_CAST_TO_BUILTIN, + OPCODE_CAST_TO_NATIVE, + OPCODE_CAST_TO_SCRIPT, OPCODE_CONSTRUCT, //only for basic types!! OPCODE_CONSTRUCT_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, @@ -139,6 +235,8 @@ private: #endif Vector<int> default_arguments; Vector<int> code; + Vector<GDScriptDataType> argument_types; + GDScriptDataType return_type; #ifdef TOOLS_ENABLED Vector<StringName> arg_names; @@ -174,15 +272,13 @@ private: public: struct CallState { - ObjectID instance_id; //by debug only - ObjectID script_id; - + ObjectID instance_id; GDScriptInstance *instance; Vector<uint8_t> stack; int stack_size; Variant self; uint32_t alloca_size; - GDScript *_class; + Ref<GDScript> script; int ip; int line; int defarg; @@ -199,6 +295,8 @@ public: int get_max_stack_size() const; int get_default_argument_count() const; int get_default_argument_addr(int p_idx) const; + GDScriptDataType get_return_type() const; + GDScriptDataType get_argument_type(int p_idx) const; GDScript *get_script() const { return _script; } StringName get_source() const { return source; } diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index ce91e7dff3..2485e6f04a 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -30,15 +30,15 @@ #include "gdscript_functions.h" -#include "class_db.h" -#include "func_ref.h" +#include "core/class_db.h" +#include "core/func_ref.h" +#include "core/io/json.h" +#include "core/io/marshalls.h" +#include "core/math/math_funcs.h" +#include "core/os/os.h" +#include "core/reference.h" +#include "core/variant_parser.h" #include "gdscript.h" -#include "io/json.h" -#include "io/marshalls.h" -#include "math_funcs.h" -#include "os/os.h" -#include "reference.h" -#include "variant_parser.h" const char *GDScriptFunctions::get_func_name(Function p_func) { @@ -106,6 +106,8 @@ const char *GDScriptFunctions::get_func_name(Function p_func) { "printerr", "printraw", "print_debug", + "push_error", + "push_warning", "var2str", "str2var", "var2bytes", @@ -642,7 +644,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ str += p_args[i]->operator String(); } - //str+="\n"; print_line(str); r_ret = Variant(); @@ -657,7 +658,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ str += p_args[i]->operator String(); } - //str+="\n"; print_line(str); r_ret = Variant(); @@ -672,7 +672,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ str += p_args[i]->operator String(); } - //str+="\n"; print_line(str); r_ret = Variant(); @@ -686,7 +685,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ str += p_args[i]->operator String(); } - //str+="\n"; print_error(str); r_ret = Variant(); @@ -698,7 +696,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ str += p_args[i]->operator String(); } - //str+="\n"; OS::get_singleton()->print("%s", str.utf8().get_data()); r_ret = Variant(); @@ -712,14 +709,40 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ ScriptLanguage *script = GDScriptLanguage::get_singleton(); if (script->debug_get_stack_level_count() > 0) { - str += "\n\t"; - str += "At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)); // + " in function '" + script->debug_get_stack_level_function(0) + "'"; + str += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()"; } - //str+="\n"; print_line(str); r_ret = Variant(); } break; + case PUSH_ERROR: { + VALIDATE_ARG_COUNT(1); + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + r_ret = Variant(); + break; + } + + String message = *p_args[0]; + ERR_PRINTS(message); + r_ret = Variant(); + } break; + case PUSH_WARNING: { + VALIDATE_ARG_COUNT(1); + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + r_ret = Variant(); + break; + } + + String message = *p_args[0]; + WARN_PRINTS(message); + r_ret = Variant(); + } break; case VAR_TO_STR: { VALIDATE_ARG_COUNT(1); String vars; @@ -735,22 +758,14 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ r_ret = Variant(); return; } + r_ret = *p_args[0]; VariantParser::StreamString ss; ss.s = *p_args[0]; String errs; int line; - Error err = VariantParser::parse(&ss, r_ret, errs, line); - - if (err != OK) { - r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::STRING; - r_ret = "Parse error at line " + itos(line) + ": " + errs; - return; - } - + (void)VariantParser::parse(&ss, r_ret, errs, line); } break; case VAR_TO_BYTES: { VALIDATE_ARG_COUNT(1); @@ -867,7 +882,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ int incr = *p_args[2]; if (incr == 0) { - r_ret = RTR("step argument is zero!"); + r_ret = RTR("Step argument is zero!"); r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; return; } @@ -1098,7 +1113,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ for (Map<StringName, GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { if (d.has(E->key())) { - ins->members[E->get().index] = d[E->key()]; + ins->members.write[E->get().index] = d[E->key()]; } } @@ -1412,7 +1427,7 @@ bool GDScriptFunctions::is_deterministic(Function p_func) { MethodInfo GDScriptFunctions::get_info(Function p_func) { -#ifdef TOOLS_ENABLED +#ifdef DEBUG_ENABLED //using a switch, so the compiler generates a jumptable switch (p_func) { @@ -1464,7 +1479,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case MATH_ATAN2: { - MethodInfo mi("atan2", PropertyInfo(Variant::REAL, "x"), PropertyInfo(Variant::REAL, "y")); + MethodInfo mi("atan2", PropertyInfo(Variant::REAL, "y"), PropertyInfo(Variant::REAL, "x")); mi.return_val.type = Variant::REAL; return mi; } break; @@ -1663,7 +1678,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { MethodInfo mi("weakref", PropertyInfo(Variant::OBJECT, "obj")); mi.return_val.type = Variant::OBJECT; - mi.return_val.name = "WeakRef"; + mi.return_val.class_name = "WeakRef"; return mi; @@ -1672,19 +1687,20 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { MethodInfo mi("funcref", PropertyInfo(Variant::OBJECT, "instance"), PropertyInfo(Variant::STRING, "funcname")); mi.return_val.type = Variant::OBJECT; - mi.return_val.name = "FuncRef"; + mi.return_val.class_name = "FuncRef"; return mi; } break; case TYPE_CONVERT: { - MethodInfo mi("convert", PropertyInfo(Variant::NIL, "what"), PropertyInfo(Variant::INT, "type")); - mi.return_val.type = Variant::OBJECT; + MethodInfo mi("convert", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::INT, "type")); + mi.return_val.type = Variant::NIL; + mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; return mi; } break; case TYPE_OF: { - MethodInfo mi("typeof", PropertyInfo(Variant::NIL, "what")); + MethodInfo mi("typeof", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::INT; return mi; @@ -1759,11 +1775,25 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; + case PUSH_ERROR: { + + MethodInfo mi(Variant::NIL, "push_error", PropertyInfo(Variant::STRING, "message")); + mi.return_val.type = Variant::NIL; + return mi; + + } break; + case PUSH_WARNING: { + + MethodInfo mi(Variant::NIL, "push_warning", PropertyInfo(Variant::STRING, "message")); + mi.return_val.type = Variant::NIL; + return mi; + + } break; case VAR_TO_STR: { - MethodInfo mi("var2str", PropertyInfo(Variant::NIL, "var")); + + MethodInfo mi("var2str", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::STRING; return mi; - } break; case STR_TO_VAR: { @@ -1773,10 +1803,10 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case VAR_TO_BYTES: { - MethodInfo mi("var2bytes", PropertyInfo(Variant::NIL, "var")); + + MethodInfo mi("var2bytes", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::POOL_BYTE_ARRAY; return mi; - } break; case BYTES_TO_VAR: { @@ -1796,7 +1826,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { MethodInfo mi("load", PropertyInfo(Variant::STRING, "path")); mi.return_val.type = Variant::OBJECT; - mi.return_val.name = "Resource"; + mi.return_val.class_name = "Resource"; return mi; } break; case INST2DICT: { @@ -1826,13 +1856,13 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case TO_JSON: { - MethodInfo mi("to_json", PropertyInfo(Variant::NIL, "var")); + MethodInfo mi("to_json", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::STRING; return mi; } break; case HASH: { - MethodInfo mi("hash", PropertyInfo(Variant::NIL, "var")); + MethodInfo mi("hash", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::INT; return mi; } break; @@ -1858,7 +1888,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case GET_STACK: { MethodInfo mi("get_stack"); - mi.return_val.type = Variant::NIL; + mi.return_val.type = Variant::ARRAY; return mi; } break; @@ -1868,7 +1898,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case LEN: { - MethodInfo mi("len", PropertyInfo(Variant::NIL, "var")); + MethodInfo mi("len", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::INT; return mi; } break; diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_functions.h index a29f06e839..33d5f27230 100644 --- a/modules/gdscript/gdscript_functions.h +++ b/modules/gdscript/gdscript_functions.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_FUNCTIONS_H #define GDSCRIPT_FUNCTIONS_H -#include "variant.h" +#include "core/variant.h" class GDScriptFunctions { public: @@ -97,6 +97,8 @@ public: TEXT_PRINTERR, TEXT_PRINTRAW, TEXT_PRINT_DEBUG, + PUSH_ERROR, + PUSH_WARNING, VAR_TO_STR, STR_TO_VAR, VAR_TO_BYTES, @@ -117,7 +119,6 @@ public: LEN, IS_INSTANCE_VALID, FUNC_MAX - }; static const char *get_func_name(Function p_func); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 9650563ee6..6ea0dbcb19 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -30,11 +30,15 @@ #include "gdscript_parser.h" +#include "core/core_string_names.h" +#include "core/engine.h" +#include "core/io/resource_loader.h" +#include "core/os/file_access.h" +#include "core/print_string.h" +#include "core/project_settings.h" +#include "core/reference.h" +#include "core/script_language.h" #include "gdscript.h" -#include "io/resource_loader.h" -#include "os/file_access.h" -#include "print_string.h" -#include "script_language.h" template <class T> T *GDScriptParser::alloc_node() { @@ -52,6 +56,10 @@ T *GDScriptParser::alloc_node() { return t; } +#ifdef DEBUG_ENABLED +static String _find_function_name(const GDScriptParser::OperatorNode *p_call); +#endif // DEBUG_ENABLED + bool GDScriptParser::_end_statement() { if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) { @@ -75,8 +83,11 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { } tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) { + if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { + return false; + } + if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) { // be more python-like int current = tab_level.back()->get(); tab_level.push_back(current); @@ -86,10 +97,11 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { } while (true) { - if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) { return false; //wtf + } else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_EOF) { + return false; } else if (tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) { int indent = tokenizer->get_token_line_indent(); @@ -138,8 +150,9 @@ bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bo } Node *arg = _parse_expression(p_parent, p_static); - if (!arg) + if (!arg) { return false; + } p_args.push_back(arg); @@ -263,6 +276,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s bool need_identifier = true; bool done = false; + int line = tokenizer->get_token_line(); while (!done) { @@ -330,16 +344,19 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_CALL; - + op->line = line; op->arguments.push_back(alloc_node<SelfNode>()); + op->arguments[0]->line = line; IdentifierNode *funcname = alloc_node<IdentifierNode>(); funcname->name = "get_node"; - + funcname->line = line; op->arguments.push_back(funcname); ConstantNode *nodepath = alloc_node<ConstantNode>(); nodepath->value = NodePath(StringName(path)); + nodepath->datatype = _type_from_variant(nodepath->value); + nodepath->line = line; op->arguments.push_back(nodepath); expr = op; @@ -353,6 +370,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = tokenizer->get_token_constant(); + constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_PI) { @@ -360,6 +378,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_PI; + constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_TAU) { @@ -367,6 +386,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_TAU; + constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_INF) { @@ -374,6 +394,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_INF; + constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_NAN) { @@ -381,6 +402,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_NAN; + constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_PRELOAD) { @@ -419,15 +441,13 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } if (subexpr->type == Node::TYPE_IDENTIFIER) { IdentifierNode *in = static_cast<IdentifierNode *>(subexpr); - Vector<ClassNode::Constant> ce = current_class->constant_expressions; // Try to find the constant expression by the identifier - for (int i = 0; i < ce.size(); ++i) { - if (ce[i].identifier == in->name) { - if (ce[i].expression->type == Node::TYPE_CONSTANT) { - cn = static_cast<ConstantNode *>(ce[i].expression); - found_constant = true; - } + if (current_class->constant_expressions.has(in->name)) { + Node *cn_exp = current_class->constant_expressions[in->name].expression; + if (cn_exp->type == Node::TYPE_CONSTANT) { + cn = static_cast<ConstantNode *>(cn_exp); + found_constant = true; } } } @@ -480,15 +500,28 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s _set_error("Expected ')' after 'preload' path"); return NULL; } + + Ref<GDScript> gds = res; + if (gds.is_valid() && !gds->is_valid()) { + _set_error("Could not fully preload the script, possible cyclic reference or compilation error."); + return NULL; + } + tokenizer->advance(); ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = res; + constant->datatype = _type_from_variant(constant->value); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_YIELD) { - //constant defined by tokenizer + if (!current_function) { + _set_error("yield() can only be used inside function blocks."); + return NULL; + } + + current_function->has_yield = true; tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { @@ -582,7 +615,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s _set_error("Built-in type constant or static function expected after '.'"); return NULL; } - if (!Variant::has_numeric_constant(bi_type, identifier)) { + if (!Variant::has_constant(bi_type, identifier)) { if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN && Variant::is_method_const(bi_type, identifier) && @@ -610,14 +643,27 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s expr = op; } else { - - _set_error("Static constant '" + identifier.operator String() + "' not present in built-in type " + Variant::get_type_name(bi_type) + "."); - return NULL; + // Object is a special case + bool valid = false; + if (bi_type == Variant::OBJECT) { + int object_constant = ClassDB::get_integer_constant("Object", identifier, &valid); + if (valid) { + ConstantNode *cn = alloc_node<ConstantNode>(); + cn->value = object_constant; + cn->datatype = _type_from_variant(cn->value); + expr = cn; + } + } + if (!valid) { + _set_error("Static constant '" + identifier.operator String() + "' not present in built-in type " + Variant::get_type_name(bi_type) + "."); + return NULL; + } } } else { ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = Variant::get_numeric_constant_value(bi_type, identifier); + cn->value = Variant::get_constant_value(bi_type, identifier); + cn->datatype = _type_from_variant(cn->value); expr = cn; } @@ -633,7 +679,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) { Variant::Type ct = tokenizer->get_token_type(); - if (p_parsing_constant == false) { + if (!p_parsing_constant) { if (ct == Variant::ARRAY) { if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { ArrayNode *arr = alloc_node<ArrayNode>(); @@ -695,24 +741,65 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s const ClassNode *cln = current_class; bool bfn = false; StringName identifier; + int id_line = tokenizer->get_token_line(); if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) { } - if (p_parsing_constant) { - for (int i = 0; i < cln->constant_expressions.size(); ++i) { - - if (cln->constant_expressions[i].identifier == identifier) { + BlockNode *b = current_block; + while (!bfn && b) { + if (b->variables.has(identifier)) { + IdentifierNode *id = alloc_node<IdentifierNode>(); + id->name = identifier; + id->declared_block = b; + id->line = id_line; + expr = id; + bfn = true; - expr = cln->constant_expressions[i].expression; - bfn = true; - break; +#ifdef DEBUG_ENABLED + LocalVarNode *lv = b->variables[identifier]; + switch (tokenizer->get_token()) { + case GDScriptTokenizer::TK_OP_ASSIGN_ADD: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: + case GDScriptTokenizer::TK_OP_ASSIGN_DIV: + case GDScriptTokenizer::TK_OP_ASSIGN_MOD: + case GDScriptTokenizer::TK_OP_ASSIGN_MUL: + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: + case GDScriptTokenizer::TK_OP_ASSIGN_SUB: { + if (lv->assignments == 0) { + if (!lv->datatype.has_type) { + _set_error("Using assignment with operation on a variable that was never assigned."); + return NULL; + } + _add_warning(GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, -1, identifier.operator String()); + } + } // fallthrough + case GDScriptTokenizer::TK_OP_ASSIGN: { + lv->assignments += 1; + lv->usages--; // Assignment is not really usage + } break; + default: { + lv->usages++; + } } +#endif // DEBUG_ENABLED + break; } + b = b->parent_block; + } - if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { + if (!bfn && p_parsing_constant) { + if (cln->constant_expressions.has(identifier)) { + expr = cln->constant_expressions[identifier].expression; + bfn = true; + } else if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { //check from constants ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = GDScriptLanguage::get_singleton()->get_global_array()[GDScriptLanguage::get_singleton()->get_global_map()[identifier]]; + constant->datatype = _type_from_variant(constant->value); + constant->line = id_line; expr = constant; bfn = true; } @@ -727,8 +814,35 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } if (!bfn) { +#ifdef DEBUG_ENABLED + if (current_function) { + int arg_idx = current_function->arguments.find(identifier); + if (arg_idx != -1) { + switch (tokenizer->get_token()) { + case GDScriptTokenizer::TK_OP_ASSIGN_ADD: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: + case GDScriptTokenizer::TK_OP_ASSIGN_DIV: + case GDScriptTokenizer::TK_OP_ASSIGN_MOD: + case GDScriptTokenizer::TK_OP_ASSIGN_MUL: + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: + case GDScriptTokenizer::TK_OP_ASSIGN_SUB: + case GDScriptTokenizer::TK_OP_ASSIGN: { + // Assignment is not really usage + current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] - 1; + } break; + default: { + current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1; + } + } + } + } +#endif // DEBUG_ENABLED IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = identifier; + id->line = id_line; expr = id; } @@ -764,6 +878,20 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s op->arguments.push_back(subexpr); expr=op;*/ + } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_IS && tokenizer->get_token(1) == GDScriptTokenizer::TK_BUILT_IN_TYPE) { + // 'is' operator with built-in type + OperatorNode *op = alloc_node<OperatorNode>(); + op->op = OperatorNode::OP_IS_BUILTIN; + op->arguments.push_back(expr); + + tokenizer->advance(); + + TypeNode *tn = alloc_node<TypeNode>(); + tn->vtype = tokenizer->get_token_type(); + op->arguments.push_back(tn); + tokenizer->advance(); + + expr = op; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_OPEN) { // array tokenizer->advance(); @@ -902,6 +1030,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //lua style identifier, easier to write ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = tokenizer->get_token_literal(); + cn->datatype = _type_from_variant(cn->value); key = cn; tokenizer->advance(2); expecting = DICT_EXPECT_VALUE; @@ -941,7 +1070,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s expr = dict; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) && tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { + } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR)) { // We check with is_token_literal, as this allows us to use match/sync/etc. as a name // parent call @@ -953,26 +1082,38 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s op->arguments.push_back(self); forbidden for now */ StringName identifier; - if (_get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier)) { - //indexing stuff - } + bool is_completion = _get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier) && for_completion; IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = identifier; op->arguments.push_back(id); - tokenizer->advance(1); - if (!_parse_arguments(op, op->arguments, p_static)) - return NULL; + if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { + if (!is_completion) { + _set_error("Expected '(' for parent function call."); + return NULL; + } + } else { + tokenizer->advance(); + if (!_parse_arguments(op, op->arguments, p_static)) { + return NULL; + } + } expr = op; + } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && expression.size() > 0 && expression[expression.size() - 1].is_op && expression[expression.size() - 1].op == OperatorNode::OP_IS) { + Expression e = expression[expression.size() - 1]; + e.op = OperatorNode::OP_IS_BUILTIN; + expression.write[expression.size() - 1] = e; + + TypeNode *tn = alloc_node<TypeNode>(); + tn->vtype = tokenizer->get_token_type(); + expr = tn; + tokenizer->advance(); } else { //find list [ or find dictionary { - - //print_line("found bug?"); - _set_error("Error parsing expression, misplaced: " + String(tokenizer->get_token_name(tokenizer->get_token()))); return NULL; //nothing } @@ -1087,6 +1228,26 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s break; } + /*****************/ + /* Parse Casting */ + /*****************/ + + bool has_casting = expr->type == Node::TYPE_CAST; + if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_AS) { + if (has_casting) { + _set_error("Unexpected 'as'."); + return NULL; + } + CastNode *cn = alloc_node<CastNode>(); + if (!_parse_type(cn->cast_type)) { + _set_error("Expected type after 'as'."); + return NULL; + } + has_casting = true; + cn->source_node = expr; + expr = cn; + } + /******************/ /* Parse Operator */ /******************/ @@ -1110,7 +1271,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //assign, if allowed is only allowed on the first operator #define _VALIDATE_ASSIGN \ - if (!p_allow_assign) { \ + if (!p_allow_assign || has_casting) { \ _set_error("Unexpected assign."); \ return NULL; \ } \ @@ -1208,6 +1369,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s switch (expression[i].op) { case OperatorNode::OP_IS: + case OperatorNode::OP_IS_BUILTIN: priority = -1; break; //before anything @@ -1325,8 +1487,8 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s op->op = expression[i].op; op->arguments.push_back(expression[i + 1].node); op->line = op_line; //line might have been changed from a \n - expression[i].is_op = false; - expression[i].node = op; + expression.write[i].is_op = false; + expression.write[i].node = op; expression.remove(i + 1); } @@ -1338,11 +1500,11 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s if (next_op >= (expression.size() - 2) || expression[next_op + 2].op != OperatorNode::OP_TERNARY_ELSE) { _set_error("Expected else after ternary if."); - ERR_FAIL_V(NULL); + return NULL; } if (next_op >= (expression.size() - 3)) { _set_error("Expected value after ternary else."); - ERR_FAIL_V(NULL); + return NULL; } OperatorNode *op = alloc_node<OperatorNode>(); @@ -1380,7 +1542,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s op->arguments.push_back(expression[next_op + 3].node); //expression after next goes as when-false //replace all 3 nodes by this operator and make it an expression - expression[next_op - 1].node = op; + expression.write[next_op - 1].node = op; expression.remove(next_op); expression.remove(next_op); expression.remove(next_op); @@ -1416,7 +1578,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s op->arguments.push_back(expression[next_op + 1].node); //next expression goes as right //replace all 3 nodes by this operator and make it an expression - expression[next_op - 1].node = op; + expression.write[next_op - 1].node = op; expression.remove(next_op); expression.remove(next_op); } @@ -1440,7 +1602,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to for (int i = 0; i < an->elements.size(); i++) { - an->elements[i] = _reduce_expression(an->elements[i], p_to_const); + an->elements.write[i] = _reduce_expression(an->elements[i], p_to_const); if (an->elements[i]->type != Node::TYPE_CONSTANT) all_constants = false; } @@ -1450,13 +1612,13 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to ConstantNode *cn = alloc_node<ConstantNode>(); Array arr; - //print_line("mk array "+itos(!p_to_const)); arr.resize(an->elements.size()); for (int i = 0; i < an->elements.size(); i++) { ConstantNode *acn = static_cast<ConstantNode *>(an->elements[i]); arr[i] = acn->value; } cn->value = arr; + cn->datatype = _type_from_variant(cn->value); return cn; } @@ -1470,10 +1632,10 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to for (int i = 0; i < dn->elements.size(); i++) { - dn->elements[i].key = _reduce_expression(dn->elements[i].key, p_to_const); + dn->elements.write[i].key = _reduce_expression(dn->elements[i].key, p_to_const); if (dn->elements[i].key->type != Node::TYPE_CONSTANT) all_constants = false; - dn->elements[i].value = _reduce_expression(dn->elements[i].value, p_to_const); + dn->elements.write[i].value = _reduce_expression(dn->elements[i].value, p_to_const); if (dn->elements[i].value->type != Node::TYPE_CONSTANT) all_constants = false; } @@ -1490,6 +1652,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to dict[key_c->value] = value_c->value; } cn->value = dict; + cn->datatype = _type_from_variant(cn->value); return cn; } @@ -1505,7 +1668,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to for (int i = 0; i < op->arguments.size(); i++) { - op->arguments[i] = _reduce_expression(op->arguments[i], p_to_const); + op->arguments.write[i] = _reduce_expression(op->arguments[i], p_to_const); if (op->arguments[i]->type != Node::TYPE_CONSTANT) { all_constants = false; last_not_constant = i; @@ -1533,7 +1696,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to for (int i = 0; i < ptrs.size(); i++) { ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[i + 1]); - ptrs[i] = &cn->value; + ptrs.write[i] = &cn->value; } vptr = (const Variant **)&ptrs[0]; @@ -1591,6 +1754,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to ConstantNode *cn = alloc_node<ConstantNode>(); 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) { @@ -1620,24 +1784,9 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = v; + cn->datatype = _type_from_variant(v); return cn; - - } /*else if (op->arguments[0]->type==Node::TYPE_CONSTANT && op->arguments[1]->type==Node::TYPE_IDENTIFIER) { - - ConstantNode *ca = static_cast<ConstantNode*>(op->arguments[0]); - IdentifierNode *ib = static_cast<IdentifierNode*>(op->arguments[1]); - - bool valid; - Variant v = ca->value.get_named(ib->name,&valid); - if (!valid) { - _set_error("invalid index '"+String(ib->name)+"' in constant expression"); - return op; - } - - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value=v; - return cn; - }*/ + } return op; @@ -1658,13 +1807,14 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = v; + cn->datatype = _type_from_variant(v); return cn; } return op; } - //validate assignment (don't assign to cosntant expression + //validate assignment (don't assign to constant expression switch (op->op) { case OperatorNode::OP_ASSIGN: @@ -1711,6 +1861,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to } \ ConstantNode *cn = alloc_node<ConstantNode>(); \ cn->value = res; \ + cn->datatype = _type_from_variant(res); \ return cn; #define _REDUCE_BINARY(m_vop) \ @@ -1724,6 +1875,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to } \ ConstantNode *cn = alloc_node<ConstantNode>(); \ cn->value = res; \ + cn->datatype = _type_from_variant(res); \ return cn; switch (op->op) { @@ -1799,6 +1951,13 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to case OperatorNode::OP_BIT_XOR: { _REDUCE_BINARY(Variant::OP_BIT_XOR); } break; + case OperatorNode::OP_TERNARY_IF: { + if (static_cast<ConstantNode *>(op->arguments[0])->value.booleanize()) { + return op->arguments[1]; + } else { + return op->arguments[2]; + } + } break; default: { ERR_FAIL_V(op); } } @@ -1903,8 +2062,25 @@ GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) { // bind case GDScriptTokenizer::TK_PR_VAR: { tokenizer->advance(); + if (!tokenizer->is_token_literal()) { + _set_error("Expected identifier for binding variable name."); + return NULL; + } pattern->pt_type = GDScriptParser::PatternNode::PT_BIND; pattern->bind = tokenizer->get_token_identifier(); + // Check if variable name is already used + BlockNode *bl = current_block; + while (bl) { + if (bl->variables.has(pattern->bind)) { + _set_error("Binding name of '" + pattern->bind.operator String() + "' is already declared in this scope."); + return NULL; + } + bl = bl->parent_block; + } + // Create local variable for proper identifier detection later + LocalVarNode *lv = alloc_node<LocalVarNode>(); + lv->name = pattern->bind; + current_block->variables.insert(lv->name, lv); tokenizer->advance(); } break; // dictionary @@ -2038,18 +2214,34 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran } PatternBranchNode *branch = alloc_node<PatternBranchNode>(); + branch->body = alloc_node<BlockNode>(); + branch->body->parent_block = p_block; + p_block->sub_blocks.push_back(branch->body); + current_block = branch->body; branch->patterns.push_back(_parse_pattern(p_static)); if (!branch->patterns[0]) { return; } + bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND; + bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD; + while (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); branch->patterns.push_back(_parse_pattern(p_static)); if (!branch->patterns[branch->patterns.size() - 1]) { return; } + + PatternNode::PatternType pt = branch->patterns[branch->patterns.size() - 1]->pt_type; + + if (pt == PatternNode::PT_BIND) { + _set_error("Cannot use bindings with multipattern."); + return; + } + + catch_all = catch_all || pt == PatternNode::PT_WILDCARD; } if (!_enter_indent_block()) { @@ -2057,54 +2249,76 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran return; } - branch->body = alloc_node<BlockNode>(); - branch->body->parent_block = p_block; - p_block->sub_blocks.push_back(branch->body); - current_block = branch->body; - _parse_block(branch->body, p_static); current_block = p_block; + if (catch_all && branch->body->has_return) { + p_block->has_return = true; + } + p_branches.push_back(branch); } } void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) { + + const DataType &to_match_type = p_node_to_match->get_datatype(); + switch (p_pattern->pt_type) { case PatternNode::PT_CONSTANT: { - // typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; + DataType pattern_type = _reduce_node_type(p_pattern->constant); + if (error_set) { + return; + } - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); + OperatorNode *type_comp = NULL; - OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>(); - typeof_pattern_value->op = OperatorNode::OP_CALL; - typeof_pattern_value->arguments.push_back(typeof_node); - typeof_pattern_value->arguments.push_back(p_pattern->constant); + // static type check if possible + if (pattern_type.has_type && to_match_type.has_type) { + if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) { + _set_error("Pattern type (" + pattern_type.to_string() + ") is not compatible with the type of the value to match (" + to_match_type.to_string() + ").", + p_pattern->line); + return; + } + } else { + // runtime typecheck + BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); + typeof_node->function = GDScriptFunctions::TYPE_OF; + + OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); + typeof_match_value->op = OperatorNode::OP_CALL; + typeof_match_value->arguments.push_back(typeof_node); + typeof_match_value->arguments.push_back(p_node_to_match); - OperatorNode *type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_pattern_value); + OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>(); + typeof_pattern_value->op = OperatorNode::OP_CALL; + typeof_pattern_value->arguments.push_back(typeof_node); + typeof_pattern_value->arguments.push_back(p_pattern->constant); + + type_comp = alloc_node<OperatorNode>(); + type_comp->op = OperatorNode::OP_EQUAL; + type_comp->arguments.push_back(typeof_match_value); + type_comp->arguments.push_back(typeof_pattern_value); + } - // comare the actual values + // compare the actual values OperatorNode *value_comp = alloc_node<OperatorNode>(); value_comp->op = OperatorNode::OP_EQUAL; value_comp->arguments.push_back(p_pattern->constant); value_comp->arguments.push_back(p_node_to_match); - OperatorNode *comparison = alloc_node<OperatorNode>(); - comparison->op = OperatorNode::OP_AND; - comparison->arguments.push_back(type_comp); - comparison->arguments.push_back(value_comp); + if (type_comp) { + OperatorNode *full_comparison = alloc_node<OperatorNode>(); + full_comparison->op = OperatorNode::OP_AND; + full_comparison->arguments.push_back(type_comp); + full_comparison->arguments.push_back(value_comp); - p_resulting_node = comparison; + p_resulting_node = full_comparison; + } else { + p_resulting_node = value_comp; + } } break; case PatternNode::PT_BIND: { @@ -2129,22 +2343,32 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m // typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length { - // typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; - - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); - - IdentifierNode *typeof_array = alloc_node<IdentifierNode>(); - typeof_array->name = "TYPE_ARRAY"; - - OperatorNode *type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_array); + OperatorNode *type_comp = NULL; + // static type check if possible + if (to_match_type.has_type) { + // must be an array + if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::ARRAY) { + _set_error("Cannot match an array pattern with a non-array expression.", p_pattern->line); + return; + } + } else { + // runtime typecheck + BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); + typeof_node->function = GDScriptFunctions::TYPE_OF; + + OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); + typeof_match_value->op = OperatorNode::OP_CALL; + typeof_match_value->arguments.push_back(typeof_node); + typeof_match_value->arguments.push_back(p_node_to_match); + + IdentifierNode *typeof_array = alloc_node<IdentifierNode>(); + typeof_array->name = "TYPE_ARRAY"; + + type_comp = alloc_node<OperatorNode>(); + type_comp->op = OperatorNode::OP_EQUAL; + type_comp->arguments.push_back(typeof_match_value); + type_comp->arguments.push_back(typeof_array); + } // size ConstantNode *length = alloc_node<ConstantNode>(); @@ -2163,12 +2387,16 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m length_comparison->arguments.push_back(call); length_comparison->arguments.push_back(length); - OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); - type_and_length_comparison->op = OperatorNode::OP_AND; - type_and_length_comparison->arguments.push_back(type_comp); - type_and_length_comparison->arguments.push_back(length_comparison); + if (type_comp) { + OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); + type_and_length_comparison->op = OperatorNode::OP_AND; + type_and_length_comparison->arguments.push_back(type_comp); + type_and_length_comparison->arguments.push_back(length_comparison); - p_resulting_node = type_and_length_comparison; + p_resulting_node = type_and_length_comparison; + } else { + p_resulting_node = length_comparison; + } } for (int i = 0; i < p_pattern->array.size(); i++) { @@ -2208,22 +2436,32 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m // typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length { - // typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; - - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); - - IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>(); - typeof_dictionary->name = "TYPE_DICTIONARY"; - - OperatorNode *type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_dictionary); + OperatorNode *type_comp = NULL; + // static type check if possible + if (to_match_type.has_type) { + // must be an dictionary + if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::DICTIONARY) { + _set_error("Cannot match an dictionary pattern with a non-dictionary expression.", p_pattern->line); + return; + } + } else { + // runtime typecheck + BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); + typeof_node->function = GDScriptFunctions::TYPE_OF; + + OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); + typeof_match_value->op = OperatorNode::OP_CALL; + typeof_match_value->arguments.push_back(typeof_node); + typeof_match_value->arguments.push_back(p_node_to_match); + + IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>(); + typeof_dictionary->name = "TYPE_DICTIONARY"; + + type_comp = alloc_node<OperatorNode>(); + type_comp->op = OperatorNode::OP_EQUAL; + type_comp->arguments.push_back(typeof_match_value); + type_comp->arguments.push_back(typeof_dictionary); + } // size ConstantNode *length = alloc_node<ConstantNode>(); @@ -2242,19 +2480,23 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m length_comparison->arguments.push_back(call); length_comparison->arguments.push_back(length); - OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); - type_and_length_comparison->op = OperatorNode::OP_AND; - type_and_length_comparison->arguments.push_back(type_comp); - type_and_length_comparison->arguments.push_back(length_comparison); + if (type_comp) { + OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); + type_and_length_comparison->op = OperatorNode::OP_AND; + type_and_length_comparison->arguments.push_back(type_comp); + type_and_length_comparison->arguments.push_back(length_comparison); - p_resulting_node = type_and_length_comparison; + p_resulting_node = type_and_length_comparison; + } else { + p_resulting_node = length_comparison; + } } for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) { Node *condition = NULL; - // chech for has, then for pattern + // check for has, then for pattern IdentifierNode *has = alloc_node<IdentifierNode>(); has->name = "has"; @@ -2308,9 +2550,20 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m } } -void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_match_statement) { +void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) { IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = "#match_value"; + id->line = p_match_statement->line; + id->datatype = _reduce_node_type(p_match_statement->val_to_match); + if (id->datatype.has_type) { + _mark_line_as_safe(id->line); + } else { + _mark_line_as_unsafe(id->line); + } + + if (error_set) { + return; + } for (int i = 0; i < p_match_statement->branches.size(); i++) { @@ -2323,11 +2576,16 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_ for (int j = 0; j < branch->patterns.size(); j++) { PatternNode *pattern = branch->patterns[j]; + _mark_line_as_safe(pattern->line); Map<StringName, Node *> bindings; - Node *resulting_node; + Node *resulting_node = NULL; _generate_pattern(pattern, id, resulting_node, bindings); + if (!resulting_node) { + return; + } + if (!binding.empty() && !bindings.empty()) { _set_error("Multipatterns can't contain bindings"); return; @@ -2335,6 +2593,14 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_ binding = bindings; } + // Result is always a boolean + DataType resulting_node_type; + resulting_node_type.has_type = true; + resulting_node_type.is_constant = true; + resulting_node_type.kind = DataType::BUILTIN; + resulting_node_type.builtin_type = Variant::BOOL; + resulting_node->set_datatype(resulting_node_type); + if (compiled_branch.compiled_pattern) { OperatorNode *or_node = alloc_node<OperatorNode>(); or_node->op = OperatorNode::OP_OR; @@ -2350,12 +2616,19 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_ // prepare the body ...hehe for (Map<StringName, Node *>::Element *e = binding.front(); e; e = e->next()) { - LocalVarNode *local_var = alloc_node<LocalVarNode>(); - local_var->name = e->key(); + if (!branch->body->variables.has(e->key())) { + _set_error("Parser bug: missing pattern bind variable.", branch->line); + ERR_FAIL(); + } + + LocalVarNode *local_var = branch->body->variables[e->key()]; local_var->assign = e->value(); + local_var->set_datatype(local_var->assign->get_datatype()); IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = local_var->name; + id->declared_block = branch->body; + id->set_datatype(local_var->assign->get_datatype()); OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_ASSIGN; @@ -2412,8 +2685,23 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { pending_newline = -1; } +#ifdef DEBUG_ENABLED + switch (token) { + case GDScriptTokenizer::TK_EOF: + case GDScriptTokenizer::TK_ERROR: + case GDScriptTokenizer::TK_NEWLINE: + case GDScriptTokenizer::TK_CF_PASS: { + // will check later + } break; + default: { + if (p_block->has_return && !current_function->has_unreachable_code) { + _add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String()); + current_function->has_unreachable_code = true; + } + } break; + } +#endif // DEBUG_ENABLED switch (token) { - case GDScriptTokenizer::TK_EOF: p_block->end_line = tokenizer->get_token_line(); case GDScriptTokenizer::TK_ERROR: { @@ -2443,6 +2731,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { _set_error("Expected ';' or <NewLine>."); return; } + _mark_line_as_safe(tokenizer->get_token_line()); tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) { // Ignore semicolon after 'pass' @@ -2453,6 +2742,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { //variale declaration and (eventual) initialization tokenizer->advance(); + int var_line = tokenizer->get_token_line(); if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected identifier for local variable name."); @@ -2470,24 +2760,34 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { } BlockNode *check_block = p_block; while (check_block) { - for (int i = 0; i < check_block->variables.size(); i++) { - if (n == check_block->variables[i]) { - _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variable_lines[i]) + ")."); - return; - } + if (check_block->variables.has(n)) { + _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variables[n]->line) + ")."); + return; } check_block = check_block->parent_block; } - int var_line = tokenizer->get_token_line(); - //must know when the local variable is declared LocalVarNode *lv = alloc_node<LocalVarNode>(); lv->name = n; + lv->line = var_line; p_block->statements.push_back(lv); Node *assigned = NULL; + if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { + if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { + lv->datatype = DataType(); +#ifdef DEBUG_ENABLED + lv->datatype.infer_type = true; +#endif + tokenizer->advance(); + } else if (!_parse_type(lv->datatype)) { + _set_error("Expected type for variable."); + return; + } + } + if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { tokenizer->advance(); @@ -2499,26 +2799,39 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { return; } - lv->assign = subexpr; + lv->assignments++; assigned = subexpr; } else { ConstantNode *c = alloc_node<ConstantNode>(); - c->value = Variant(); + if (lv->datatype.has_type && lv->datatype.kind == DataType::BUILTIN) { + Variant::CallError err; + c->value = Variant::construct(lv->datatype.builtin_type, NULL, 0, err); + } else { + c->value = Variant(); + } + c->line = var_line; assigned = c; } + lv->assign = assigned; //must be added later, to avoid self-referencing. - p_block->variables.push_back(n); //line? - p_block->variable_lines.push_back(var_line); + p_block->variables.insert(n, lv); IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = n; + id->declared_block = p_block; + id->line = var_line; OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_ASSIGN; op->arguments.push_back(id); op->arguments.push_back(assigned); + op->line = var_line; p_block->statements.push_back(op); + lv->assign_op = op; + lv->assign = assigned; + + lv->assign_op = op; if (!_end_statement()) { _set_error("Expected end of statement (var)"); @@ -2563,6 +2876,9 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { return; p_block->statements.push_back(cf_if); + bool all_have_return = cf_if->body->has_return; + bool have_else = false; + while (true) { while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) @@ -2619,6 +2935,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { if (error_set) return; + all_have_return = all_have_return && cf_else->body->has_return; + } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) { if (tab_level.back()->get() > indent_level) { @@ -2642,12 +2960,19 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { if (error_set) return; + all_have_return = all_have_return && cf_if->body_else->has_return; + have_else = true; + break; //after else, exit } else break; } + cf_if->body->has_return = all_have_return; + // If there's no else block, path out of the if might not have a return + p_block->has_return = all_have_return && have_else; + } break; case GDScriptTokenizer::TK_CF_WHILE: { @@ -2680,6 +3005,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { current_block = p_block; if (error_set) return; + p_block->has_return = cf_while->body->has_return; p_block->statements.push_back(cf_while); } break; case GDScriptTokenizer::TK_CF_FOR: { @@ -2711,6 +3037,9 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { return; } + DataType iter_type; + iter_type.is_constant = true; + if (container->type == Node::TYPE_OPERATOR) { OperatorNode *op = static_cast<OperatorNode *>(container); @@ -2745,6 +3074,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { case 2: cn->value = Vector2(constants[0], constants[1]); break; case 3: cn->value = Vector3(constants[0], constants[1], constants[2]); break; } + cn->datatype = _type_from_variant(cn->value); container = cn; } else { OperatorNode *on = alloc_node<OperatorNode>(); @@ -2766,6 +3096,10 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { container = on; } } + + iter_type.has_type = true; + iter_type.kind = DataType::BUILTIN; + iter_type.builtin_type = Variant::INT; } } @@ -2789,15 +3123,20 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { // this is for checking variable for redefining // inside this _parse_block - cf_for->body->variables.push_back(id->name); - cf_for->body->variable_lines.push_back(id->line); + LocalVarNode *lv = alloc_node<LocalVarNode>(); + lv->name = id->name; + lv->line = id->line; + lv->assignments++; + id->declared_block = cf_for->body; + lv->set_datatype(iter_type); + id->set_datatype(iter_type); + cf_for->body->variables.insert(id->name, lv); _parse_block(cf_for->body, p_static); - cf_for->body->variables.remove(0); - cf_for->body->variable_lines.remove(0); current_block = p_block; if (error_set) return; + p_block->has_return = cf_for->body->has_return; p_block->statements.push_back(cf_for); } break; case GDScriptTokenizer::TK_CF_CONTINUE: { @@ -2827,6 +3166,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { tokenizer->advance(); ControlFlowNode *cf_return = alloc_node<ControlFlowNode>(); cf_return->cf_type = ControlFlowNode::CF_RETURN; + cf_return->line = tokenizer->get_token_line(-1); if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON || tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { //expect end of statement @@ -2850,6 +3190,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { return; } } + p_block->has_return = true; } break; case GDScriptTokenizer::TK_CF_MATCH: { @@ -2882,12 +3223,14 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { _parse_pattern_block(compiled_branches, match_node->branches, p_static); - _transform_match_statment(compiled_branches, match_node); + if (error_set) return; ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>(); match_cf_node->cf_type = ControlFlowNode::CF_MATCH; match_cf_node->match = match_node; + match_cf_node->body = compiled_branches; + p_block->has_return = p_block->has_return || compiled_branches->has_return; p_block->statements.push_back(match_cf_node); _end_statement(); @@ -2938,16 +3281,6 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { } } break; - /* - case GDScriptTokenizer::TK_CF_LOCAL: { - - if (tokenizer->get_token(1)!=GDScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1)!=GDScriptTokenizer::TK_NEWLINE ) { - - _set_error("Expected ';' or <NewLine>."); - } - tokenizer->advance(); - } break; - */ } } } @@ -3087,6 +3420,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { switch (token) { + case GDScriptTokenizer::TK_CURSOR: { + tokenizer->advance(); + } break; case GDScriptTokenizer::TK_EOF: p_class->end_line = tokenizer->get_token_line(); case GDScriptTokenizer::TK_ERROR: { @@ -3103,6 +3439,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } break; case GDScriptTokenizer::TK_PR_EXTENDS: { + _mark_line_as_safe(tokenizer->get_token_line()); _parse_extends(p_class); if (error_set) return; @@ -3112,6 +3449,59 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } break; + case GDScriptTokenizer::TK_PR_CLASS_NAME: { + + if (p_class->owner) { + _set_error("'class_name' is only valid for the main class namespace."); + return; + } + if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) { + + _set_error("'class_name' syntax: 'class_name <UniqueName>'"); + return; + } + + p_class->name = tokenizer->get_token_identifier(1); + + if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) { + _set_error("Unique global class '" + p_class->name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name)); + return; + } + + if (ClassDB::class_exists(p_class->name)) { + _set_error("Class '" + p_class->name + "' shadows a native class."); + return; + } + + tokenizer->advance(2); + + if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { + tokenizer->advance(); + + if ((tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING)) { + Variant constant = tokenizer->get_token_constant(); + String icon_path = constant.operator String(); + + String abs_icon_path = icon_path.is_rel_path() ? self_path.get_base_dir().plus_file(icon_path).simplify_path() : icon_path; + if (!FileAccess::exists(abs_icon_path)) { + _set_error("No class icon found at: " + abs_icon_path); + return; + } + + p_class->icon_path = icon_path; + + tokenizer->advance(); + } else { + _set_error("Optional parameter after 'class_name' must be a string constant file path to an icon."); + return; + } + + } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) { + _set_error("Class icon must be separated by a comma."); + return; + } + + } break; case GDScriptTokenizer::TK_PR_TOOL: { if (p_class->tool) { @@ -3128,7 +3518,6 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { //class inside class :D StringName name; - StringName extends; if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) { @@ -3138,6 +3527,31 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { name = tokenizer->get_token_identifier(1); tokenizer->advance(2); + // Check if name is shadowing something else + if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) { + _set_error("Class '" + String(name) + "' shadows a native class."); + return; + } + if (ScriptServer::is_global_class(name)) { + _set_error("Can't override name of unique global class '" + name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name)); + return; + } + ClassNode *outer_class = p_class; + while (outer_class) { + for (int i = 0; i < outer_class->subclasses.size(); i++) { + if (outer_class->subclasses[i]->name == name) { + _set_error("Another class named '" + String(name) + "' already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ")."); + return; + } + } + if (outer_class->constant_expressions.has(name)) { + _set_error("A constant named '" + String(name) + "' already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ")."); + return; + } + + outer_class = outer_class->owner; + } + ClassNode *newclass = alloc_node<ClassNode>(); newclass->initializer = alloc_node<BlockNode>(); newclass->initializer->parent_class = newclass; @@ -3213,6 +3627,17 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } +#ifdef DEBUG_ENABLED + if (p_class->constant_expressions.has(name)) { + _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name); + } + for (int i = 0; i < p_class->variables.size(); i++) { + if (p_class->variables[i].identifier == name) { + _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_VARIABLE, -1, name); + } + } +#endif // DEBUG_ENABLED + if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { _set_error("Expected '(' after identifier (syntax: 'func <identifier>([arguments]):' )."); @@ -3222,7 +3647,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); Vector<StringName> arguments; + Vector<DataType> argument_types; Vector<Node *> default_values; +#ifdef DEBUG_ENABLED + Vector<int> arguments_usage; +#endif // DEBUG_ENABLED int fnline = tokenizer->get_token_line(); @@ -3249,9 +3678,24 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { StringName argname = tokenizer->get_token_identifier(); arguments.push_back(argname); +#ifdef DEBUG_ENABLED + arguments_usage.push_back(0); +#endif // DEBUG_ENABLED tokenizer->advance(); + DataType argtype; + if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { + if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { + argtype.infer_type = true; + tokenizer->advance(); + } else if (!_parse_type(argtype)) { + _set_error("Expected type for argument."); + return; + } + } + argument_types.push_back(argtype); + if (defaulting && tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) { _set_error("Default parameter expected."); @@ -3269,9 +3713,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { OperatorNode *on = alloc_node<OperatorNode>(); on->op = OperatorNode::OP_ASSIGN; + on->line = fnline; IdentifierNode *in = alloc_node<IdentifierNode>(); in->name = argname; + in->line = fnline; on->arguments.push_back(in); on->arguments.push_back(defval); @@ -3306,8 +3752,26 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { BlockNode *block = alloc_node<BlockNode>(); block->parent_class = p_class; + FunctionNode *function = alloc_node<FunctionNode>(); + function->name = name; + function->arguments = arguments; + function->argument_types = argument_types; + function->default_values = default_values; + function->_static = _static; + function->line = fnline; +#ifdef DEBUG_ENABLED + function->arguments_usage = arguments_usage; +#endif // DEBUG_ENABLED + function->rpc_mode = rpc_mode; + rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + if (name == "_init") { + if (_static) { + _set_error("Constructor cannot be static."); + return; + } + if (p_class->extends_used) { OperatorNode *cparent = alloc_node<OperatorNode>(); @@ -3322,6 +3786,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { _set_error("expected '(' for parent constructor arguments."); + return; } tokenizer->advance(); @@ -3330,7 +3795,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { parenthesis++; while (true) { + current_function = function; Node *arg = _parse_and_reduce_expression(p_class, _static); + current_function = NULL; cparent->arguments.push_back(arg); if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { @@ -3359,21 +3826,22 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } + DataType return_type; + if (tokenizer->get_token() == GDScriptTokenizer::TK_FORWARD_ARROW) { + + if (!_parse_type(return_type, true)) { + _set_error("Expected return type for function."); + return; + } + } + if (!_enter_indent_block(block)) { _set_error("Indented block expected."); return; } - FunctionNode *function = alloc_node<FunctionNode>(); - function->name = name; - function->arguments = arguments; - function->default_values = default_values; - function->_static = _static; - function->line = fnline; - - function->rpc_mode = rpc_mode; - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + function->return_type = return_type; if (_static) p_class->static_functions.push_back(function); @@ -3398,6 +3866,8 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { ClassNode::Signal sig; sig.name = tokenizer->get_token_identifier(); + sig.emissions = 0; + sig.line = tokenizer->get_token_line(); tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { @@ -3489,14 +3959,15 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") { - //current_export.hint=PROPERTY_HINT_ALL_FLAGS; tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { + ERR_EXPLAIN("Exporting bit flags hint requires string constants."); + WARN_DEPRECATED break; } if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - _set_error("Expected ')' or ',' in bit flags hint."); + _set_error("Expected ',' in bit flags hint."); return; } @@ -3861,6 +4332,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; current_export.hint_string = native_class->get_name(); + current_export.class_name = native_class->get_name(); } else { current_export = PropertyInfo(); @@ -3884,6 +4356,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { current_export.type = Variant::INT; current_export.hint = is_flags ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM; + current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; Dictionary enum_values = constant; List<Variant> keys; @@ -3931,10 +4404,10 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); } - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SLAVE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTESYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTERSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SLAVESYNC) { + if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPET && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTESYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTERSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPETSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SLAVE) { current_export = PropertyInfo(); - _set_error("Expected 'var', 'onready', 'remote', 'master', 'slave', 'sync', 'remotesync', 'mastersync', 'slavesync'."); + _set_error("Expected 'var', 'onready', 'remote', 'master', 'puppet', 'sync', 'remotesync', 'mastersync', 'puppetsync'."); return; } @@ -3991,7 +4464,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { rpc_mode = MultiplayerAPI::RPC_MODE_MASTER; continue; } break; - case GDScriptTokenizer::TK_PR_SLAVE: { + case GDScriptTokenizer::TK_PR_SLAVE: +#ifdef DEBUG_ENABLED + _add_warning(GDScriptWarning::DEPRECATED_KEYWORD, tokenizer->get_token_line(), "slave", "puppet"); +#endif + case GDScriptTokenizer::TK_PR_PUPPET: { //may be fallthrough from export, ignore if so tokenizer->advance(); @@ -4008,7 +4485,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } - rpc_mode = MultiplayerAPI::RPC_MODE_SLAVE; + rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET; continue; } break; case GDScriptTokenizer::TK_PR_REMOTESYNC: @@ -4024,7 +4501,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } - rpc_mode = MultiplayerAPI::RPC_MODE_SYNC; + rpc_mode = MultiplayerAPI::RPC_MODE_REMOTESYNC; continue; } break; case GDScriptTokenizer::TK_PR_MASTERSYNC: { @@ -4042,7 +4519,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { rpc_mode = MultiplayerAPI::RPC_MODE_MASTERSYNC; continue; } break; - case GDScriptTokenizer::TK_PR_SLAVESYNC: { + case GDScriptTokenizer::TK_PR_PUPPETSYNC: { //may be fallthrough from export, ignore if so tokenizer->advance(); @@ -4054,7 +4531,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } - rpc_mode = MultiplayerAPI::RPC_MODE_SLAVESYNC; + rpc_mode = MultiplayerAPI::RPC_MODE_PUPPETSYNC; continue; } break; case GDScriptTokenizer::TK_PR_VAR: { @@ -4080,12 +4557,53 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { member.expression = NULL; member._export.name = member.identifier; member.line = tokenizer->get_token_line(); + member.usages = 0; member.rpc_mode = rpc_mode; + if (current_class->constant_expressions.has(member.identifier)) { + _set_error("A constant named '" + String(member.identifier) + "' already exists in this class (at line: " + + itos(current_class->constant_expressions[member.identifier].expression->line) + ")."); + return; + } + + for (int i = 0; i < current_class->variables.size(); i++) { + if (current_class->variables[i].identifier == member.identifier) { + _set_error("Variable '" + String(member.identifier) + "' already exists in this class (at line: " + + itos(current_class->variables[i].line) + ")."); + return; + } + } +#ifdef DEBUG_ENABLED + for (int i = 0; i < current_class->functions.size(); i++) { + if (current_class->functions[i]->name == member.identifier) { + _add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier); + break; + } + } + for (int i = 0; i < current_class->static_functions.size(); i++) { + if (current_class->static_functions[i]->name == member.identifier) { + _add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier); + break; + } + } +#endif // DEBUG_ENABLED tokenizer->advance(); rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { + if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { + member.data_type = DataType(); +#ifdef DEBUG_ENABLED + member.data_type.infer_type = true; +#endif + tokenizer->advance(); + } else if (!_parse_type(member.data_type)) { + _set_error("Expected type for class variable."); + return; + } + } + if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { #ifdef DEBUG_ENABLED @@ -4117,42 +4635,31 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { member.expression = subexpr; - if (autoexport) { - if (1) /*(subexpr->type==Node::TYPE_ARRAY) { - - member._export.type=Variant::ARRAY; + if (autoexport && !member.data_type.has_type) { - } else if (subexpr->type==Node::TYPE_DICTIONARY) { - - member._export.type=Variant::DICTIONARY; - - } else*/ - { - - if (subexpr->type != Node::TYPE_CONSTANT) { + if (subexpr->type != Node::TYPE_CONSTANT) { - _set_error("Type-less export needs a constant expression assigned to infer type."); - return; - } + _set_error("Type-less export needs a constant expression assigned to infer type."); + return; + } - ConstantNode *cn = static_cast<ConstantNode *>(subexpr); - if (cn->value.get_type() == Variant::NIL) { + ConstantNode *cn = static_cast<ConstantNode *>(subexpr); + if (cn->value.get_type() == Variant::NIL) { - _set_error("Can't accept a null constant expression for inferring export type."); + _set_error("Can't accept a null constant expression for inferring export type."); + return; + } + member._export.type = cn->value.get_type(); + member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; + if (cn->value.get_type() == Variant::OBJECT) { + Object *obj = cn->value; + Resource *res = Object::cast_to<Resource>(obj); + if (res == NULL) { + _set_error("Exported constant not a type or resource."); return; } - member._export.type = cn->value.get_type(); - member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; - if (cn->value.get_type() == Variant::OBJECT) { - Object *obj = cn->value; - Resource *res = Object::cast_to<Resource>(obj); - if (res == NULL) { - _set_error("Exported constant not a type or resource."); - return; - } - member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; - member._export.hint_string = res->get_class(); - } + member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; + member._export.hint_string = res->get_class(); } } #ifdef TOOLS_ENABLED @@ -4186,15 +4693,37 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { else p_class->initializer->statements.push_back(op); - } else { + member.initial_assignment = op; - if (autoexport) { + } else { + if (autoexport && !member.data_type.has_type) { _set_error("Type-less export needs a constant expression assigned to infer type."); return; } } + if (autoexport && member.data_type.has_type) { + if (member.data_type.kind == DataType::BUILTIN) { + member._export.type = member.data_type.builtin_type; + } else if (member.data_type.kind == DataType::NATIVE) { + if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) { + member._export.type = Variant::OBJECT; + member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; + member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; + member._export.hint_string = member.data_type.native_type; + member._export.class_name = member.data_type.native_type; + } else { + _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); + return; + } + + } else { + _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); + return; + } + } + if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_SETGET) { tokenizer->advance(); @@ -4231,7 +4760,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } break; case GDScriptTokenizer::TK_PR_CONST: { - //variale declaration and (eventual) initialization + // constant declaration and initialization ClassNode::Constant constant; @@ -4242,9 +4771,38 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } - constant.identifier = tokenizer->get_token_literal(); + StringName const_id = tokenizer->get_token_literal(); + int line = tokenizer->get_token_line(); + + if (current_class->constant_expressions.has(const_id)) { + _set_error("Constant '" + String(const_id) + "' already exists in this class (at line: " + + itos(current_class->constant_expressions[const_id].expression->line) + ")."); + return; + } + + for (int i = 0; i < current_class->variables.size(); i++) { + if (current_class->variables[i].identifier == const_id) { + _set_error("A variable named '" + String(const_id) + "' already exists in this class (at line: " + + itos(current_class->variables[i].line) + ")."); + return; + } + } + tokenizer->advance(); + if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { + if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { + constant.type = DataType(); +#ifdef DEBUG_ENABLED + constant.type.infer_type = true; +#endif + tokenizer->advance(); + } else if (!_parse_type(constant.type)) { + _set_error("Expected type for class constant."); + return; + } + } + if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) { _set_error("Constant expects assignment."); return; @@ -4261,14 +4819,16 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } if (subexpr->type != Node::TYPE_CONSTANT) { - _set_error("Expected constant expression"); + _set_error("Expected constant expression", line); + return; } + subexpr->line = line; constant.expression = subexpr; - p_class->constant_expressions.push_back(constant); + p_class->constant_expressions.insert(const_id, constant); if (!_end_statement()) { - _set_error("Expected end of statement (constant)"); + _set_error("Expected end of statement (constant)", line); return; } @@ -4276,13 +4836,28 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { case GDScriptTokenizer::TK_PR_ENUM: { //multiple constant declarations.. - int last_assign = -1; // Incremented by 1 right before the assingment. + int last_assign = -1; // Incremented by 1 right before the assignment. String enum_name; Dictionary enum_dict; tokenizer->advance(); if (tokenizer->is_token_literal(0, true)) { enum_name = tokenizer->get_token_literal(); + + if (current_class->constant_expressions.has(enum_name)) { + _set_error("A constant named '" + String(enum_name) + "' already exists in this class (at line: " + + itos(current_class->constant_expressions[enum_name].expression->line) + ")."); + return; + } + + for (int i = 0; i < current_class->variables.size(); i++) { + if (current_class->variables[i].identifier == enum_name) { + _set_error("A variable named '" + String(enum_name) + "' already exists in this class (at line: " + + itos(current_class->variables[i].line) + ")."); + return; + } + } + tokenizer->advance(); } if (tokenizer->get_token() != GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) { @@ -4309,12 +4884,12 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } else { // tokenizer->is_token_literal(0, true) - ClassNode::Constant constant; - - constant.identifier = tokenizer->get_token_literal(); + StringName const_id = tokenizer->get_token_literal(); tokenizer->advance(); + ConstantNode *enum_value_expr; + if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { tokenizer->advance(); @@ -4328,23 +4903,23 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (subexpr->type != Node::TYPE_CONSTANT) { _set_error("Expected constant expression"); + return; } - const ConstantNode *subexpr_const = static_cast<const ConstantNode *>(subexpr); + enum_value_expr = static_cast<ConstantNode *>(subexpr); - if (subexpr_const->value.get_type() != Variant::INT) { + if (enum_value_expr->value.get_type() != Variant::INT) { _set_error("Expected an int value for enum"); + return; } - last_assign = subexpr_const->value; - - constant.expression = subexpr; + last_assign = enum_value_expr->value; } else { last_assign = last_assign + 1; - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = last_assign; - constant.expression = cn; + enum_value_expr = alloc_node<ConstantNode>(); + enum_value_expr->value = last_assign; + enum_value_expr->datatype = _type_from_variant(enum_value_expr->value); } if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { @@ -4352,21 +4927,41 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } if (enum_name != "") { - const ConstantNode *cn = static_cast<const ConstantNode *>(constant.expression); - enum_dict[constant.identifier] = cn->value; - } + enum_dict[const_id] = enum_value_expr->value; + } else { + if (current_class->constant_expressions.has(const_id)) { + _set_error("A constant named '" + String(const_id) + "' already exists in this class (at line: " + + itos(current_class->constant_expressions[const_id].expression->line) + ")."); + return; + } - p_class->constant_expressions.push_back(constant); + for (int i = 0; i < current_class->variables.size(); i++) { + if (current_class->variables[i].identifier == const_id) { + _set_error("A variable named '" + String(const_id) + "' already exists in this class (at line: " + + itos(current_class->variables[i].line) + ")."); + return; + } + } + + ClassNode::Constant constant; + constant.type.has_type = true; + constant.type.kind = DataType::BUILTIN; + constant.type.builtin_type = Variant::INT; + constant.expression = enum_value_expr; + p_class->constant_expressions.insert(const_id, constant); + } } } if (enum_name != "") { ClassNode::Constant enum_constant; - enum_constant.identifier = enum_name; ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = enum_dict; + cn->datatype = _type_from_variant(cn->value); + enum_constant.expression = cn; - p_class->constant_expressions.push_back(enum_constant); + enum_constant.type = cn->datatype; + p_class->constant_expressions.insert(enum_name, enum_constant); } if (!_end_statement()) { @@ -4396,6 +4991,3012 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } +void GDScriptParser::_determine_inheritance(ClassNode *p_class) { + + if (p_class->extends_used) { + //do inheritance + String path = p_class->extends_file; + + Ref<GDScript> script; + StringName native; + ClassNode *base_class = NULL; + + if (path != "") { + //path (and optionally subclasses) + + if (path.is_rel_path()) { + + String base = base_path; + + if (base == "" || base.is_rel_path()) { + _set_error("Could not resolve relative path for parent class: " + path, p_class->line); + return; + } + path = base.plus_file(path).simplify_path(); + } + script = ResourceLoader::load(path); + if (script.is_null()) { + _set_error("Could not load base class: " + path, p_class->line); + return; + } + if (!script->is_valid()) { + + _set_error("Script not fully loaded (cyclic preload?): " + path, p_class->line); + return; + } + + if (p_class->extends_class.size()) { + + for (int i = 0; i < p_class->extends_class.size(); i++) { + + String sub = p_class->extends_class[i]; + if (script->get_subclasses().has(sub)) { + + Ref<Script> subclass = script->get_subclasses()[sub]; //avoid reference from disappearing + script = subclass; + } else { + + _set_error("Could not find subclass: " + sub, p_class->line); + return; + } + } + } + + } else { + + if (p_class->extends_class.size() == 0) { + _set_error("Parser bug: undecidable inheritance.", p_class->line); + ERR_FAIL(); + } + //look around for the subclasses + + int extend_iter = 1; + String base = p_class->extends_class[0]; + ClassNode *p = p_class->owner; + Ref<GDScript> base_script; + + if (ScriptServer::is_global_class(base)) { + base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base)); + if (!base_script.is_valid()) { + _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line); + return; + } + p = NULL; + } + + while (p) { + + bool found = false; + + for (int i = 0; i < p->subclasses.size(); i++) { + if (p->subclasses[i]->name == base) { + ClassNode *test = p->subclasses[i]; + while (test) { + if (test == p_class) { + _set_error("Cyclic inheritance.", test->line); + return; + } + if (test->base_type.kind == DataType::CLASS) { + test = test->base_type.class_type; + } else { + break; + } + } + found = true; + if (extend_iter < p_class->extends_class.size()) { + // Keep looking at current classes if possible + base = p_class->extends_class[extend_iter++]; + p = p->subclasses[i]; + } else { + base_class = p->subclasses[i]; + } + break; + } + } + + if (base_class) break; + if (found) continue; + + if (p->constant_expressions.has(base)) { + if (p->constant_expressions[base].expression->type != Node::TYPE_CONSTANT) { + _set_error("Could not resolve constant '" + base + "'.", p_class->line); + return; + } + const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression); + base_script = cn->value; + if (base_script.is_null()) { + _set_error("Constant is not a class: " + base, p_class->line); + return; + } + break; + } + + p = p->owner; + } + + if (base_script.is_valid()) { + + String ident = base; + + for (int i = extend_iter; i < p_class->extends_class.size(); i++) { + + String subclass = p_class->extends_class[i]; + + ident += ("." + subclass); + + if (base_script->get_subclasses().has(subclass)) { + + base_script = base_script->get_subclasses()[subclass]; + } else if (base_script->get_constants().has(subclass)) { + + Ref<GDScript> new_base_class = base_script->get_constants()[subclass]; + if (new_base_class.is_null()) { + _set_error("Constant is not a class: " + ident, p_class->line); + return; + } + base_script = new_base_class; + } else { + + _set_error("Could not find subclass: " + ident, p_class->line); + return; + } + } + + script = base_script; + + } else if (!base_class) { + + if (p_class->extends_class.size() > 1) { + + _set_error("Invalid inheritance (unknown class + subclasses)", p_class->line); + return; + } + //if not found, try engine classes + if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { + + _set_error("Unknown class: '" + base + "'", p_class->line); + return; + } + + native = base; + } + } + + if (base_class) { + p_class->base_type.has_type = true; + p_class->base_type.kind = DataType::CLASS; + p_class->base_type.class_type = base_class; + } else if (script.is_valid()) { + p_class->base_type.has_type = true; + p_class->base_type.kind = DataType::GDSCRIPT; + p_class->base_type.script_type = script; + p_class->base_type.native_type = script->get_instance_base_type(); + } else if (native != StringName()) { + p_class->base_type.has_type = true; + p_class->base_type.kind = DataType::NATIVE; + p_class->base_type.native_type = native; + } else { + _set_error("Could not determine inheritance", p_class->line); + return; + } + + } else { + // without extends, implicitly extend Reference + p_class->base_type.has_type = true; + p_class->base_type.kind = DataType::NATIVE; + 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]); + } +} + +String GDScriptParser::DataType::to_string() const { + if (!has_type) return "var"; + switch (kind) { + case BUILTIN: { + if (builtin_type == Variant::NIL) return "null"; + return Variant::get_type_name(builtin_type); + } break; + case NATIVE: { + if (is_meta_type) { + return "GDScriptNativeClass"; + } + return native_type.operator String(); + } break; + + case GDSCRIPT: { + Ref<GDScript> gds = script_type; + const String &gds_class = gds->get_script_class_name(); + if (!gds_class.empty()) { + return gds_class; + } + } // fallthrough + case SCRIPT: { + if (is_meta_type) { + return script_type->get_class_name().operator String(); + } + String name = script_type->get_name(); + if (name != String()) { + return name; + } + name = script_type->get_path().get_file(); + if (name != String()) { + return name; + } + return native_type.operator String(); + } break; + case CLASS: { + ERR_FAIL_COND_V(!class_type, String()); + if (is_meta_type) { + return "GDScript"; + } + if (class_type->name == StringName()) { + return "self"; + } + return class_type->name.operator String(); + } break; + case UNRESOLVED: { + } break; + } + + return "Unresolved"; +} + +bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) { + tokenizer->advance(); + r_type.has_type = true; + + bool finished = false; + bool can_index = false; + String full_name; + + if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { + completion_cursor = StringName(); + completion_type = COMPLETION_TYPE_HINT; + completion_class = current_class; + completion_function = current_function; + completion_line = tokenizer->get_token_line(); + completion_argument = 0; + completion_block = current_block; + completion_found = true; + completion_ident_is_call = p_can_be_void; + tokenizer->advance(); + } + + switch (tokenizer->get_token()) { + case GDScriptTokenizer::TK_PR_VOID: { + if (!p_can_be_void) { + return false; + } + r_type.kind = DataType::BUILTIN; + r_type.builtin_type = Variant::NIL; + } break; + case GDScriptTokenizer::TK_BUILT_IN_TYPE: { + r_type.builtin_type = tokenizer->get_token_type(); + if (tokenizer->get_token_type() == Variant::OBJECT) { + r_type.kind = DataType::NATIVE; + r_type.native_type = "Object"; + } else { + r_type.kind = DataType::BUILTIN; + } + } break; + case GDScriptTokenizer::TK_IDENTIFIER: { + r_type.native_type = tokenizer->get_token_identifier(); + if (ClassDB::class_exists(r_type.native_type) || ClassDB::class_exists("_" + r_type.native_type.operator String())) { + r_type.kind = DataType::NATIVE; + } else { + r_type.kind = DataType::UNRESOLVED; + can_index = true; + full_name = r_type.native_type; + } + } break; + default: { + return false; + } + } + + tokenizer->advance(); + + if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { + completion_cursor = r_type.native_type; + completion_type = COMPLETION_TYPE_HINT; + completion_class = current_class; + completion_function = current_function; + completion_line = tokenizer->get_token_line(); + completion_argument = 0; + completion_block = current_block; + completion_found = true; + completion_ident_is_call = p_can_be_void; + tokenizer->advance(); + } + + if (can_index) { + while (!finished) { + switch (tokenizer->get_token()) { + case GDScriptTokenizer::TK_PERIOD: { + if (!can_index) { + _set_error("Unexpected '.'."); + return false; + } + can_index = false; + tokenizer->advance(); + } break; + case GDScriptTokenizer::TK_IDENTIFIER: { + if (can_index) { + _set_error("Unexpected identifier."); + return false; + } + + StringName id; + bool has_completion = _get_completable_identifier(COMPLETION_TYPE_HINT_INDEX, id); + if (id == StringName()) { + id = "@temp"; + } + + full_name += "." + id.operator String(); + can_index = true; + if (has_completion) { + completion_cursor = full_name; + } + } break; + default: { + finished = true; + } break; + } + } + + if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) { + _set_error("Expected subclass identifier."); + return false; + } + + r_type.native_type = full_name; + } + + return true; +} + +GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, int p_line) { + if (!p_source.has_type) return p_source; + if (p_source.kind != DataType::UNRESOLVED) return p_source; + + Vector<String> full_name = p_source.native_type.operator String().split(".", false); + int name_part = 0; + + DataType result; + result.has_type = true; + + while (name_part < full_name.size()) { + + bool found = false; + StringName id = full_name[name_part]; + DataType base_type = result; + + ClassNode *p = NULL; + if (name_part == 0) { + if (ScriptServer::is_global_class(id)) { + String script_path = ScriptServer::get_global_class_path(id); + if (script_path == self_path) { + result.kind = DataType::CLASS; + result.class_type = current_class; + } else { + Ref<Script> script = ResourceLoader::load(script_path); + Ref<GDScript> gds = script; + if (gds.is_valid()) { + if (!gds->is_valid()) { + _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line); + return DataType(); + } + result.kind = DataType::GDSCRIPT; + result.script_type = gds; + } else if (script.is_valid()) { + result.kind = DataType::SCRIPT; + result.script_type = script; + } else { + _set_error("Class '" + id + "' was found in global scope but its script could not be loaded.", p_line); + return DataType(); + } + } + name_part++; + continue; + } else { + p = current_class; + } + } else if (base_type.kind == DataType::CLASS) { + p = base_type.class_type; + } + while (p) { + if (p->constant_expressions.has(id)) { + if (p->constant_expressions[id].expression->type != Node::TYPE_CONSTANT) { + _set_error("Parser bug: unresolved constant.", p_line); + ERR_FAIL_V(result); + } + const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[id].expression); + Ref<GDScript> gds = cn->value; + if (gds.is_valid()) { + result.kind = DataType::GDSCRIPT; + result.script_type = gds; + found = true; + } else { + Ref<Script> scr = cn->value; + if (scr.is_valid()) { + result.kind = DataType::SCRIPT; + result.script_type = scr; + found = true; + } + } + break; + } + + // Inner classes + ClassNode *outer_class = p; + while (outer_class) { + for (int i = 0; i < outer_class->subclasses.size(); i++) { + if (outer_class->subclasses[i] == p) { + continue; + } + if (outer_class->subclasses[i]->name == id) { + found = true; + result.kind = DataType::CLASS; + result.class_type = outer_class->subclasses[i]; + break; + } + } + if (found) { + break; + } + outer_class = outer_class->owner; + } + + if (!found && p->base_type.kind == DataType::CLASS) { + p = p->base_type.class_type; + } else { + base_type = p->base_type; + break; + } + } + + // Still look for class constants in parent script + if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) { + Ref<Script> scr = base_type.script_type; + ERR_FAIL_COND_V(scr.is_null(), result); + Map<StringName, Variant> constants; + scr->get_constants(&constants); + + if (constants.has(id)) { + Ref<GDScript> gds = constants[id]; + + if (gds.is_valid()) { + result.kind = DataType::GDSCRIPT; + result.script_type = gds; + found = true; + } else { + Ref<Script> scr = constants[id]; + if (scr.is_valid()) { + result.kind = DataType::SCRIPT; + result.script_type = scr; + found = true; + } + } + } + } + + if (!found && !for_completion) { + String base; + if (name_part == 0) { + base = "self"; + } else { + base = result.to_string(); + } + _set_error("Identifier '" + String(id) + "' is not a valid type (not a script or class), or could not be found on base '" + + base + "'.", + p_line); + return DataType(); + } + + name_part++; + } + + return result; +} + +GDScriptParser::DataType GDScriptParser::_type_from_variant(const Variant &p_value) const { + DataType result; + result.has_type = true; + result.is_constant = true; + result.kind = DataType::BUILTIN; + result.builtin_type = p_value.get_type(); + + if (result.builtin_type == Variant::OBJECT) { + Object *obj = p_value.operator Object *(); + if (!obj) { + return DataType(); + } + result.native_type = obj->get_class_name(); + Ref<Script> scr = p_value; + if (scr.is_valid()) { + result.is_meta_type = true; + } else { + result.is_meta_type = false; + scr = obj->get_script(); + } + if (scr.is_valid()) { + result.script_type = scr; + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = DataType::GDSCRIPT; + } else { + result.kind = DataType::SCRIPT; + } + result.native_type = scr->get_instance_base_type(); + } else { + result.kind = DataType::NATIVE; + } + } + + return result; +} + +GDScriptParser::DataType GDScriptParser::_type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant) const { + DataType ret; + if (p_property.type == Variant::NIL && (p_nil_is_variant || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { + // Variant + return ret; + } + ret.has_type = true; + ret.builtin_type = p_property.type; + if (p_property.type == Variant::OBJECT) { + ret.kind = DataType::NATIVE; + ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + } else { + ret.kind = DataType::BUILTIN; + } + return ret; +} + +GDScriptParser::DataType GDScriptParser::_type_from_gdtype(const GDScriptDataType &p_gdtype) const { + DataType result; + if (!p_gdtype.has_type) { + return result; + } + + result.has_type = true; + result.builtin_type = p_gdtype.builtin_type; + result.native_type = p_gdtype.native_type; + result.script_type = p_gdtype.script_type; + + switch (p_gdtype.kind) { + case GDScriptDataType::BUILTIN: { + result.kind = DataType::BUILTIN; + } break; + case GDScriptDataType::NATIVE: { + result.kind = DataType::NATIVE; + } break; + case GDScriptDataType::GDSCRIPT: { + result.kind = DataType::GDSCRIPT; + } break; + case GDScriptDataType::SCRIPT: { + result.kind = DataType::SCRIPT; + } break; + } + return result; +} + +GDScriptParser::DataType GDScriptParser::_get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const { + if (!p_a.has_type || !p_b.has_type) { + r_valid = true; + return DataType(); + } + + Variant::Type a_type = p_a.kind == DataType::BUILTIN ? p_a.builtin_type : Variant::OBJECT; + Variant::Type b_type = p_b.kind == DataType::BUILTIN ? p_b.builtin_type : Variant::OBJECT; + + Variant a; + REF a_ref; + if (a_type == Variant::OBJECT) { + a_ref.instance(); + a = a_ref; + } else { + Variant::CallError err; + a = Variant::construct(a_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + r_valid = false; + return DataType(); + } + } + Variant b; + REF b_ref; + if (b_type == Variant::OBJECT) { + b_ref.instance(); + b = b_ref; + } else { + Variant::CallError err; + b = Variant::construct(b_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + r_valid = false; + return DataType(); + } + } + + // Avoid division by zero + if (a_type == Variant::INT || a_type == Variant::REAL) { + Variant::evaluate(Variant::OP_ADD, a, 1, a, r_valid); + } + if (b_type == Variant::INT || b_type == Variant::REAL) { + Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid); + } + if (a_type == Variant::STRING && b_type != Variant::ARRAY) { + a = "%s"; // Work around for formatting operator (%) + } + + Variant ret; + Variant::evaluate(p_op, a, b, ret, r_valid); + + if (r_valid) { + return _type_from_variant(ret); + } + + return DataType(); +} + +Variant::Operator GDScriptParser::_get_variant_operation(const OperatorNode::Operator &p_op) const { + switch (p_op) { + case OperatorNode::OP_NEG: { + return Variant::OP_NEGATE; + } break; + case OperatorNode::OP_POS: { + return Variant::OP_POSITIVE; + } break; + case OperatorNode::OP_NOT: { + return Variant::OP_NOT; + } break; + case OperatorNode::OP_BIT_INVERT: { + return Variant::OP_BIT_NEGATE; + } break; + case OperatorNode::OP_IN: { + return Variant::OP_IN; + } break; + case OperatorNode::OP_EQUAL: { + return Variant::OP_EQUAL; + } break; + case OperatorNode::OP_NOT_EQUAL: { + return Variant::OP_NOT_EQUAL; + } break; + case OperatorNode::OP_LESS: { + return Variant::OP_LESS; + } break; + case OperatorNode::OP_LESS_EQUAL: { + return Variant::OP_LESS_EQUAL; + } break; + case OperatorNode::OP_GREATER: { + return Variant::OP_GREATER; + } break; + case OperatorNode::OP_GREATER_EQUAL: { + return Variant::OP_GREATER_EQUAL; + } break; + case OperatorNode::OP_AND: { + return Variant::OP_AND; + } break; + case OperatorNode::OP_OR: { + return Variant::OP_OR; + } break; + case OperatorNode::OP_ASSIGN_ADD: + case OperatorNode::OP_ADD: { + return Variant::OP_ADD; + } break; + case OperatorNode::OP_ASSIGN_SUB: + case OperatorNode::OP_SUB: { + return Variant::OP_SUBTRACT; + } break; + case OperatorNode::OP_ASSIGN_MUL: + case OperatorNode::OP_MUL: { + return Variant::OP_MULTIPLY; + } break; + case OperatorNode::OP_ASSIGN_DIV: + case OperatorNode::OP_DIV: { + return Variant::OP_DIVIDE; + } break; + case OperatorNode::OP_ASSIGN_MOD: + case OperatorNode::OP_MOD: { + return Variant::OP_MODULE; + } break; + case OperatorNode::OP_ASSIGN_BIT_AND: + case OperatorNode::OP_BIT_AND: { + return Variant::OP_BIT_AND; + } break; + case OperatorNode::OP_ASSIGN_BIT_OR: + case OperatorNode::OP_BIT_OR: { + return Variant::OP_BIT_OR; + } break; + case OperatorNode::OP_ASSIGN_BIT_XOR: + case OperatorNode::OP_BIT_XOR: { + return Variant::OP_BIT_XOR; + } break; + case OperatorNode::OP_ASSIGN_SHIFT_LEFT: + case OperatorNode::OP_SHIFT_LEFT: { + return Variant::OP_SHIFT_LEFT; + } + case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: + case OperatorNode::OP_SHIFT_RIGHT: { + return Variant::OP_SHIFT_RIGHT; + } + default: { + return Variant::OP_MAX; + } break; + } +} + +bool GDScriptParser::_is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion) const { + // Ignore for completion + if (!check_types || for_completion) { + return true; + } + // Can't test if not all have type + if (!p_container.has_type || !p_expression.has_type) { + return true; + } + + // Should never get here unresolved + ERR_FAIL_COND_V(p_container.kind == DataType::UNRESOLVED, false); + ERR_FAIL_COND_V(p_expression.kind == DataType::UNRESOLVED, false); + + if (p_container.kind == DataType::BUILTIN && p_expression.kind == DataType::BUILTIN) { + bool valid = p_container.builtin_type == p_expression.builtin_type; + if (p_allow_implicit_conversion) { + valid = valid || Variant::can_convert_strict(p_expression.builtin_type, p_container.builtin_type); + } + return valid; + } + + if (p_container.kind == DataType::BUILTIN && p_container.builtin_type == Variant::OBJECT) { + // Object built-in is a special case, it's compatible with any object and with null + if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) { + return true; + } + if (p_expression.kind == DataType::BUILTIN) { + return false; + } + // If it's not a built-in, must be an object + return true; + } + + if (p_container.kind == DataType::BUILTIN || (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type != Variant::NIL)) { + // Can't mix built-ins with objects + return false; + } + + // From now on everything is objects, check polymorphism + // The container must be the same class or a superclass of the expression + + if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) { + // Null can be assigned to object types + return true; + } + + StringName expr_native; + Ref<Script> expr_script; + ClassNode *expr_class = NULL; + + switch (p_expression.kind) { + case DataType::NATIVE: { + if (p_container.kind != DataType::NATIVE) { + // Non-native type can't be a superclass of a native type + return false; + } + if (p_expression.is_meta_type) { + expr_native = GDScriptNativeClass::get_class_static(); + } else { + expr_native = p_expression.native_type; + } + } break; + case DataType::SCRIPT: + case DataType::GDSCRIPT: { + if (p_container.kind == DataType::CLASS) { + // This cannot be resolved without cyclic dependencies, so just bail out + return false; + } + if (p_expression.is_meta_type) { + expr_native = p_expression.script_type->get_class_name(); + } else { + expr_script = p_expression.script_type; + expr_native = expr_script->get_instance_base_type(); + } + } break; + case DataType::CLASS: { + if (p_expression.is_meta_type) { + expr_native = GDScript::get_class_static(); + } else { + expr_class = p_expression.class_type; + ClassNode *base = expr_class; + while (base->base_type.kind == DataType::CLASS) { + base = base->base_type.class_type; + } + expr_native = base->base_type.native_type; + expr_script = base->base_type.script_type; + } + } break; + case DataType::BUILTIN: // Already handled above + case DataType::UNRESOLVED: // Not allowed, see above + break; + } + + switch (p_container.kind) { + case DataType::NATIVE: { + if (p_container.is_meta_type) { + return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static()); + } else { + return ClassDB::is_parent_class(expr_native, p_container.native_type); + } + } break; + case DataType::SCRIPT: + case DataType::GDSCRIPT: { + if (p_container.is_meta_type) { + return ClassDB::is_parent_class(expr_native, GDScript::get_class_static()); + } + if (expr_class == head && p_container.script_type->get_path() == self_path) { + // Special case: container is self script and expression is self + return true; + } + while (expr_script.is_valid()) { + if (expr_script == p_container.script_type) { + return true; + } + expr_script = expr_script->get_base_script(); + } + return false; + } break; + case DataType::CLASS: { + if (p_container.is_meta_type) { + return ClassDB::is_parent_class(expr_native, GDScript::get_class_static()); + } + if (p_container.class_type == head && expr_script.is_valid() && expr_script->get_path() == self_path) { + // Special case: container is self and expression is self script + return true; + } + while (expr_class) { + if (expr_class == p_container.class_type) { + return true; + } + expr_class = expr_class->base_type.class_type; + } + return false; + } break; + case DataType::BUILTIN: // Already handled above + case DataType::UNRESOLVED: // Not allowed, see above + break; + } + + return false; +} + +GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { + if (p_node->get_datatype().has_type) { + return p_node->get_datatype(); + } + + DataType node_type; + + switch (p_node->type) { + case Node::TYPE_CONSTANT: { + node_type = _type_from_variant(static_cast<ConstantNode *>(p_node)->value); + } break; + case Node::TYPE_TYPE: { + TypeNode *tn = static_cast<TypeNode *>(p_node); + node_type.has_type = true; + node_type.is_meta_type = true; + node_type.kind = DataType::BUILTIN; + node_type.builtin_type = tn->vtype; + } break; + case Node::TYPE_ARRAY: { + node_type.has_type = true; + node_type.kind = DataType::BUILTIN; + node_type.builtin_type = Variant::ARRAY; +#ifdef DEBUG_ENABLED + // Check stuff inside the array + ArrayNode *an = static_cast<ArrayNode *>(p_node); + for (int i = 0; i < an->elements.size(); i++) { + _reduce_node_type(an->elements[i]); + } +#endif // DEBUG_ENABLED + } break; + case Node::TYPE_DICTIONARY: { + node_type.has_type = true; + node_type.kind = DataType::BUILTIN; + node_type.builtin_type = Variant::DICTIONARY; +#ifdef DEBUG_ENABLED + // Check stuff inside the dictionarty + DictionaryNode *dn = static_cast<DictionaryNode *>(p_node); + for (int i = 0; i < dn->elements.size(); i++) { + _reduce_node_type(dn->elements[i].key); + _reduce_node_type(dn->elements[i].value); + } +#endif // DEBUG_ENABLED + } break; + case Node::TYPE_SELF: { + node_type.has_type = true; + node_type.kind = DataType::CLASS; + node_type.class_type = current_class; + } break; + case Node::TYPE_IDENTIFIER: { + IdentifierNode *id = static_cast<IdentifierNode *>(p_node); + if (id->declared_block) { + node_type = id->declared_block->variables[id->name]->get_datatype(); + id->declared_block->variables[id->name]->usages += 1; + } else if (id->name == "#match_value") { + // It's a special id just for the match statetement, ignore + break; + } else if (current_function && current_function->arguments.find(id->name) >= 0) { + int idx = current_function->arguments.find(id->name); + node_type = current_function->argument_types[idx]; + } else { + node_type = _reduce_identifier_type(NULL, id->name, id->line); + } + } break; + case Node::TYPE_CAST: { + CastNode *cn = static_cast<CastNode *>(p_node); + + DataType source_type = _reduce_node_type(cn->source_node); + cn->cast_type = _resolve_type(cn->cast_type, cn->line); + if (source_type.has_type) { + + bool valid = false; + if (check_types) { + if (cn->cast_type.kind == DataType::BUILTIN && source_type.kind == DataType::BUILTIN) { + valid = Variant::can_convert(source_type.builtin_type, cn->cast_type.builtin_type); + } + if (cn->cast_type.kind != DataType::BUILTIN && source_type.kind != DataType::BUILTIN) { + valid = _is_type_compatible(cn->cast_type, source_type) || _is_type_compatible(source_type, cn->cast_type); + } + + if (!valid) { + _set_error("Invalid cast. Cannot convert from '" + source_type.to_string() + + "' to '" + cn->cast_type.to_string() + "'.", + cn->line); + return DataType(); + } + } + } else { +#ifdef DEBUG_ENABLED + _add_warning(GDScriptWarning::UNSAFE_CAST, cn->line, cn->cast_type.to_string()); +#endif // DEBUG_ENABLED + _mark_line_as_unsafe(cn->line); + } + + node_type = cn->cast_type; + + } break; + case Node::TYPE_OPERATOR: { + OperatorNode *op = static_cast<OperatorNode *>(p_node); + + switch (op->op) { + case OperatorNode::OP_CALL: + case OperatorNode::OP_PARENT_CALL: { + node_type = _reduce_function_call_type(op); + } break; + case OperatorNode::OP_YIELD: { + if (op->arguments.size() == 2) { + DataType base_type = _reduce_node_type(op->arguments[0]); + DataType signal_type = _reduce_node_type(op->arguments[1]); + // TODO: Check if signal exists when it's a constant + if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) { + _set_error("First argument of 'yield()' must be an object.", op->line); + return DataType(); + } + if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) { + _set_error("Second argument of 'yield()' must be a string.", op->line); + return DataType(); + } + } + // yield can return anything + node_type.has_type = false; + } break; + case OperatorNode::OP_IS: + case OperatorNode::OP_IS_BUILTIN: { + + if (op->arguments.size() != 2) { + _set_error("Parser bug: binary operation without 2 arguments.", op->line); + ERR_FAIL_V(DataType()); + } + + DataType value_type = _reduce_node_type(op->arguments[0]); + DataType type_type = _reduce_node_type(op->arguments[1]); + + if (check_types && type_type.has_type) { + if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) { + _set_error("Invalid 'is' test: right operand is not a type (not a native type nor a script).", op->line); + return DataType(); + } + type_type.is_meta_type = false; // Test the actual type + if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) { + if (op->op == OperatorNode::OP_IS) { + _set_error("A value of type '" + value_type.to_string() + "' will never be an instance of '" + type_type.to_string() + "'.", op->line); + } else { + _set_error("A value of type '" + value_type.to_string() + "' will never be of type '" + type_type.to_string() + "'.", op->line); + } + return DataType(); + } + } + + node_type.has_type = true; + node_type.is_constant = true; + node_type.is_meta_type = false; + node_type.kind = DataType::BUILTIN; + node_type.builtin_type = Variant::BOOL; + } break; + // Unary operators + case OperatorNode::OP_NEG: + case OperatorNode::OP_POS: + case OperatorNode::OP_NOT: + case OperatorNode::OP_BIT_INVERT: { + + DataType argument_type = _reduce_node_type(op->arguments[0]); + if (!argument_type.has_type) { + break; + } + + Variant::Operator var_op = _get_variant_operation(op->op); + bool valid = false; + node_type = _get_operation_type(var_op, argument_type, argument_type, valid); + + if (check_types && !valid) { + _set_error("Invalid operand type ('" + argument_type.to_string() + + "') to unary operator '" + Variant::get_operator_name(var_op) + "'.", + op->line, op->column); + return DataType(); + } + + } break; + // Binary operators + case OperatorNode::OP_IN: + case OperatorNode::OP_EQUAL: + case OperatorNode::OP_NOT_EQUAL: + case OperatorNode::OP_LESS: + case OperatorNode::OP_LESS_EQUAL: + case OperatorNode::OP_GREATER: + case OperatorNode::OP_GREATER_EQUAL: + case OperatorNode::OP_AND: + case OperatorNode::OP_OR: + case OperatorNode::OP_ADD: + case OperatorNode::OP_SUB: + case OperatorNode::OP_MUL: + case OperatorNode::OP_DIV: + case OperatorNode::OP_MOD: + case OperatorNode::OP_SHIFT_LEFT: + case OperatorNode::OP_SHIFT_RIGHT: + case OperatorNode::OP_BIT_AND: + case OperatorNode::OP_BIT_OR: + case OperatorNode::OP_BIT_XOR: { + + if (op->arguments.size() != 2) { + _set_error("Parser bug: binary operation without 2 arguments.", op->line); + ERR_FAIL_V(DataType()); + } + + DataType argument_a_type = _reduce_node_type(op->arguments[0]); + DataType argument_b_type = _reduce_node_type(op->arguments[1]); + if (!argument_a_type.has_type || !argument_b_type.has_type) { + _mark_line_as_unsafe(op->line); + break; + } + + Variant::Operator var_op = _get_variant_operation(op->op); + bool valid = false; + node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid); + + if (check_types && !valid) { + _set_error("Invalid operand types ('" + argument_a_type.to_string() + "' and '" + + argument_b_type.to_string() + "') to operator '" + Variant::get_operator_name(var_op) + "'.", + op->line, op->column); + return DataType(); + } +#ifdef DEBUG_ENABLED + if (var_op == Variant::OP_DIVIDE && argument_a_type.has_type && argument_a_type.kind == DataType::BUILTIN && argument_a_type.builtin_type == Variant::INT && + argument_b_type.has_type && argument_b_type.kind == DataType::BUILTIN && argument_b_type.builtin_type == Variant::INT) { + _add_warning(GDScriptWarning::INTEGER_DIVISION, op->line); + } +#endif // DEBUG_ENABLED + + } break; + // Ternary operators + case OperatorNode::OP_TERNARY_IF: { + if (op->arguments.size() != 3) { + _set_error("Parser bug: ternary operation without 3 arguments"); + ERR_FAIL_V(DataType()); + } + + DataType true_type = _reduce_node_type(op->arguments[1]); + DataType false_type = _reduce_node_type(op->arguments[2]); + + // If types are equal, then the expression is of the same type + // If they are compatible, return the broader type + if (true_type == false_type || _is_type_compatible(true_type, false_type)) { + node_type = true_type; + } else if (_is_type_compatible(false_type, true_type)) { + node_type = false_type; + } else { +#ifdef DEBUG_ENABLED + _add_warning(GDScriptWarning::INCOMPATIBLE_TERNARY, op->line); +#endif // DEBUG_ENABLED + } + } break; + // Assignment should never happen within an expression + case OperatorNode::OP_ASSIGN: + case OperatorNode::OP_ASSIGN_ADD: + case OperatorNode::OP_ASSIGN_SUB: + case OperatorNode::OP_ASSIGN_MUL: + case OperatorNode::OP_ASSIGN_DIV: + case OperatorNode::OP_ASSIGN_MOD: + case OperatorNode::OP_ASSIGN_SHIFT_LEFT: + case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: + case OperatorNode::OP_ASSIGN_BIT_AND: + case OperatorNode::OP_ASSIGN_BIT_OR: + case OperatorNode::OP_ASSIGN_BIT_XOR: + case OperatorNode::OP_INIT_ASSIGN: { + + _set_error("Assignment inside expression is not allowed (parser bug?).", op->line); + return DataType(); + + } break; + case OperatorNode::OP_INDEX_NAMED: { + if (op->arguments.size() != 2) { + _set_error("Parser bug: named index with invalid arguments.", op->line); + ERR_FAIL_V(DataType()); + } + if (op->arguments[1]->type != Node::TYPE_IDENTIFIER) { + _set_error("Parser bug: named index without identifier argument.", op->line); + ERR_FAIL_V(DataType()); + } + + DataType base_type = _reduce_node_type(op->arguments[0]); + IdentifierNode *member_id = static_cast<IdentifierNode *>(op->arguments[1]); + + if (base_type.has_type) { + if (check_types && base_type.kind == DataType::BUILTIN) { + // Variant type, just test if it's possible + DataType result; + switch (base_type.builtin_type) { + case Variant::NIL: + case Variant::DICTIONARY: { + result.has_type = false; + } break; + default: { + Variant::CallError err; + Variant temp = Variant::construct(base_type.builtin_type, NULL, 0, err); + + bool valid = false; + Variant res = temp.get(member_id->name.operator String(), &valid); + + if (valid) { + result = _type_from_variant(res); + } else if (check_types) { + _set_error("Can't get index '" + String(member_id->name.operator String()) + "' on base '" + + base_type.to_string() + "'.", + op->line); + return DataType(); + } + } break; + } + result.is_constant = false; + node_type = result; + } else { + node_type = _reduce_identifier_type(&base_type, member_id->name, op->line); +#ifdef DEBUG_ENABLED + if (!node_type.has_type) { + _add_warning(GDScriptWarning::UNSAFE_PROPERTY_ACCESS, op->line, member_id->name.operator String(), base_type.to_string()); + } +#endif // DEBUG_ENABLED + } + } else { + _mark_line_as_unsafe(op->line); + } + if (error_set) { + return DataType(); + } + } break; + case OperatorNode::OP_INDEX: { + + if (op->arguments[1]->type == Node::TYPE_CONSTANT) { + ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]); + if (cn->value.get_type() == Variant::STRING) { + // Treat this as named indexing + + IdentifierNode *id = alloc_node<IdentifierNode>(); + id->name = cn->value.operator StringName(); + + op->op = OperatorNode::OP_INDEX_NAMED; + op->arguments.write[1] = id; + + return _reduce_node_type(op); + } + } + + DataType base_type = _reduce_node_type(op->arguments[0]); + DataType index_type = _reduce_node_type(op->arguments[1]); + + if (!base_type.has_type) { + _mark_line_as_unsafe(op->line); + break; + } + + if (check_types && index_type.has_type) { + if (base_type.kind == DataType::BUILTIN) { + // Check if indexing is valid + bool error = index_type.kind != DataType::BUILTIN; + if (!error) { + switch (base_type.builtin_type) { + // Expect int or real as index + case Variant::POOL_BYTE_ARRAY: + case Variant::POOL_COLOR_ARRAY: + case Variant::POOL_INT_ARRAY: + case Variant::POOL_REAL_ARRAY: + case Variant::POOL_STRING_ARRAY: + case Variant::POOL_VECTOR2_ARRAY: + case Variant::POOL_VECTOR3_ARRAY: + case Variant::ARRAY: + case Variant::STRING: { + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL; + } break; + // Expect String only + case Variant::RECT2: + case Variant::PLANE: + case Variant::QUAT: + case Variant::AABB: + case Variant::OBJECT: { + error = index_type.builtin_type != Variant::STRING; + } break; + // Expect String or number + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::TRANSFORM2D: + case Variant::BASIS: + case Variant::TRANSFORM: { + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL && + index_type.builtin_type != Variant::STRING; + } break; + // Expect String or int + case Variant::COLOR: { + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; + } break; + default: {} + } + } + if (error) { + _set_error("Invalid index type (" + index_type.to_string() + ") for base '" + base_type.to_string() + "'.", + op->line); + return DataType(); + } + + if (op->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) { + ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]); + // Index is a constant, just try it if possible + switch (base_type.builtin_type) { + // Arrays/string have variable indexing, can't test directly + case Variant::STRING: + case Variant::ARRAY: + case Variant::DICTIONARY: + case Variant::POOL_BYTE_ARRAY: + case Variant::POOL_COLOR_ARRAY: + case Variant::POOL_INT_ARRAY: + case Variant::POOL_REAL_ARRAY: + case Variant::POOL_STRING_ARRAY: + case Variant::POOL_VECTOR2_ARRAY: + case Variant::POOL_VECTOR3_ARRAY: { + break; + } + default: { + Variant::CallError err; + Variant temp = Variant::construct(base_type.builtin_type, NULL, 0, err); + + bool valid = false; + Variant res = temp.get(cn->value, &valid); + + if (valid) { + node_type = _type_from_variant(res); + node_type.is_constant = false; + } else if (check_types) { + _set_error("Can't get index '" + String(cn->value) + "' on base '" + + base_type.to_string() + "'.", + op->line); + return DataType(); + } + } break; + } + } else { + _mark_line_as_unsafe(op->line); + } + } else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) { + _set_error("Only strings can be used as index in the base type '" + base_type.to_string() + "'.", op->line); + return DataType(); + } + } + if (check_types && !node_type.has_type) { + // Can infer indexing type for some variant types + DataType result; + result.has_type = true; + result.kind = DataType::BUILTIN; + switch (base_type.builtin_type) { + // Can't index at all + case Variant::NIL: + case Variant::BOOL: + case Variant::INT: + case Variant::REAL: + case Variant::NODE_PATH: + case Variant::_RID: { + _set_error("Can't index on a value of type '" + base_type.to_string() + "'.", op->line); + return DataType(); + } break; + // Return int + case Variant::POOL_BYTE_ARRAY: + case Variant::POOL_INT_ARRAY: { + result.builtin_type = Variant::INT; + } break; + // Return real + case Variant::POOL_REAL_ARRAY: + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::QUAT: { + result.builtin_type = Variant::REAL; + } break; + // Return color + case Variant::POOL_COLOR_ARRAY: { + result.builtin_type = Variant::COLOR; + } break; + // Return string + case Variant::POOL_STRING_ARRAY: + case Variant::STRING: { + result.builtin_type = Variant::STRING; + } break; + // Return Vector2 + case Variant::POOL_VECTOR2_ARRAY: + case Variant::TRANSFORM2D: + case Variant::RECT2: { + result.builtin_type = Variant::VECTOR2; + } break; + // Return Vector3 + case Variant::POOL_VECTOR3_ARRAY: + case Variant::AABB: + case Variant::BASIS: { + result.builtin_type = Variant::VECTOR3; + } break; + // Depends on the index + case Variant::TRANSFORM: + case Variant::PLANE: + case Variant::COLOR: + default: { + result.has_type = false; + } break; + } + node_type = result; + } + } break; + default: { + _set_error("Parser bug: unhandled operation.", op->line); + ERR_FAIL_V(DataType()); + } + } + } break; + default: {} + } + + p_node->set_datatype(_resolve_type(node_type, p_node->line)); + return node_type; +} + +bool GDScriptParser::_get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const { + + r_static = false; + r_default_arg_count = 0; + + DataType original_type = p_base_type; + ClassNode *base = NULL; + FunctionNode *callee = NULL; + + if (p_base_type.kind == DataType::CLASS) { + base = p_base_type.class_type; + } + + // Look up the current file (parse tree) + while (!callee && base) { + for (int i = 0; i < base->static_functions.size(); i++) { + FunctionNode *func = base->static_functions[i]; + if (p_function == func->name) { + r_static = true; + callee = func; + break; + } + } + if (!callee && !p_base_type.is_meta_type) { + for (int i = 0; i < base->functions.size(); i++) { + FunctionNode *func = base->functions[i]; + if (p_function == func->name) { + callee = func; + break; + } + } + } + p_base_type = base->base_type; + if (p_base_type.kind == DataType::CLASS) { + base = p_base_type.class_type; + } else { + break; + } + } + + if (callee) { + r_return_type = callee->get_datatype(); + for (int i = 0; i < callee->argument_types.size(); i++) { + r_arg_types.push_back(callee->argument_types[i]); + } + r_default_arg_count = callee->default_values.size(); + return true; + } + + // Nothing in current file, check parent script + Ref<GDScript> base_gdscript; + Ref<Script> base_script; + StringName native; + if (p_base_type.kind == DataType::GDSCRIPT) { + base_gdscript = p_base_type.script_type; + if (base_gdscript.is_null() || !base_gdscript->is_valid()) { + // GDScript wasn't properly compÃled, don't bother trying + return false; + } + } else if (p_base_type.kind == DataType::SCRIPT) { + base_script = p_base_type.script_type; + } else if (p_base_type.kind == DataType::NATIVE) { + native = p_base_type.native_type; + } + + while (base_gdscript.is_valid()) { + native = base_gdscript->get_instance_base_type(); + + Map<StringName, GDScriptFunction *> funcs = base_gdscript->get_member_functions(); + + if (funcs.has(p_function)) { + GDScriptFunction *f = funcs[p_function]; + r_static = f->is_static(); + r_default_arg_count = f->get_default_argument_count(); + r_return_type = _type_from_gdtype(f->get_return_type()); + for (int i = 0; i < f->get_argument_count(); i++) { + r_arg_types.push_back(_type_from_gdtype(f->get_argument_type(i))); + } + return true; + } + + base_gdscript = base_gdscript->get_base_script(); + } + + while (base_script.is_valid()) { + native = base_script->get_instance_base_type(); + MethodInfo mi = base_script->get_method_info(p_function); + + if (!(mi == MethodInfo())) { + r_return_type = _type_from_property(mi.return_val, false); + r_default_arg_count = mi.default_arguments.size(); + for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { + r_arg_types.push_back(_type_from_property(E->get())); + } + return true; + } + base_script = base_script->get_base_script(); + } + + if (native == StringName()) { + // Empty native class, might happen in some Script implementations + // Just ignore it + return false; + } + +#ifdef DEBUG_METHODS_ENABLED + + // Only native remains + if (!ClassDB::class_exists(native)) { + native = "_" + native.operator String(); + } + if (!ClassDB::class_exists(native)) { + if (!check_types) return false; + ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found."); + ERR_FAIL_V(false); + } + + MethodBind *method = ClassDB::get_method(native, p_function); + + if (!method) { + // Try virtual methods + List<MethodInfo> virtuals; + ClassDB::get_virtual_methods(native, &virtuals); + + for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) { + const MethodInfo &mi = E->get(); + if (mi.name == p_function) { + r_default_arg_count = mi.default_arguments.size(); + for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) { + r_arg_types.push_back(_type_from_property(pi->get())); + } + r_return_type = _type_from_property(mi.return_val, false); + r_vararg = mi.flags & METHOD_FLAG_VARARG; + return true; + } + } + + // If the base is a script, it might be trying to access members of the Script class itself + if (original_type.is_meta_type && !(p_function == "new") && (original_type.kind == DataType::SCRIPT || original_type.kind == DataType::GDSCRIPT)) { + method = ClassDB::get_method(original_type.script_type->get_class_name(), p_function); + + if (method) { + r_static = true; + } else { + // Try virtual methods of the script type + virtuals.clear(); + ClassDB::get_virtual_methods(original_type.script_type->get_class_name(), &virtuals); + for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) { + const MethodInfo &mi = E->get(); + if (mi.name == p_function) { + r_default_arg_count = mi.default_arguments.size(); + for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) { + r_arg_types.push_back(_type_from_property(pi->get())); + } + r_return_type = _type_from_property(mi.return_val, false); + r_static = true; + r_vararg = mi.flags & METHOD_FLAG_VARARG; + return true; + } + } + return false; + } + } else { + return false; + } + } + + r_default_arg_count = method->get_default_argument_count(); + r_return_type = _type_from_property(method->get_return_info(), false); + r_vararg = method->is_vararg(); + + for (int i = 0; i < method->get_argument_count(); i++) { + r_arg_types.push_back(_type_from_property(method->get_argument_info(i))); + } + return true; +#else + return false; +#endif +} + +GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const OperatorNode *p_call) { + if (p_call->arguments.size() < 1) { + _set_error("Parser bug: function call without enough arguments.", p_call->line); + ERR_FAIL_V(DataType()); + } + + DataType return_type; + List<DataType> arg_types; + int default_args_count = 0; + int arg_count = p_call->arguments.size(); + String callee_name; + bool is_vararg = false; + + switch (p_call->arguments[0]->type) { + case GDScriptParser::Node::TYPE_TYPE: { + // Built-in constructor, special case + TypeNode *tn = static_cast<TypeNode *>(p_call->arguments[0]); + + Vector<DataType> par_types; + par_types.resize(p_call->arguments.size() - 1); + for (int i = 1; i < p_call->arguments.size(); i++) { + par_types.write[i - 1] = _reduce_node_type(p_call->arguments[i]); + } + + if (error_set) return DataType(); + + bool match = false; + List<MethodInfo> constructors; + Variant::get_constructor_list(tn->vtype, &constructors); + PropertyInfo return_type; + + for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) { + MethodInfo &mi = E->get(); + + if (p_call->arguments.size() - 1 < mi.arguments.size() - mi.default_arguments.size()) { + continue; + } + if (p_call->arguments.size() - 1 > mi.arguments.size()) { + continue; + } + + bool types_match = true; + for (int i = 0; i < par_types.size(); i++) { + DataType arg_type; + if (mi.arguments[i].type != Variant::NIL) { + arg_type.has_type = true; + arg_type.kind = mi.arguments[i].type == Variant::OBJECT ? DataType::NATIVE : DataType::BUILTIN; + arg_type.builtin_type = mi.arguments[i].type; + arg_type.native_type = mi.arguments[i].class_name; + } + + if (!_is_type_compatible(arg_type, par_types[i], true)) { + types_match = false; + break; + } else { +#ifdef DEBUG_ENABLED + if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_types[i].kind == DataType::BUILTIN && par_types[i].builtin_type == Variant::REAL) { + _add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, Variant::get_type_name(tn->vtype)); + } + if (par_types[i].may_yield && p_call->arguments[i + 1]->type == Node::TYPE_OPERATOR) { + _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i + 1]))); + } +#endif // DEBUG_ENABLED + } + } + + if (types_match) { + match = true; + return_type = mi.return_val; + break; + } + } + + if (match) { + return _type_from_property(return_type, false); + } else if (check_types) { + String err = "No constructor of '"; + err += Variant::get_type_name(tn->vtype); + err += "' matches the signature '"; + err += Variant::get_type_name(tn->vtype) + "("; + for (int i = 0; i < par_types.size(); i++) { + if (i > 0) err += ", "; + err += par_types[i].to_string(); + } + err += ")'."; + _set_error(err, p_call->line, p_call->column); + return DataType(); + } + return DataType(); + } break; + case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: { + BuiltInFunctionNode *func = static_cast<BuiltInFunctionNode *>(p_call->arguments[0]); + MethodInfo mi = GDScriptFunctions::get_info(func->function); + + return_type = _type_from_property(mi.return_val, false); + +#ifdef DEBUG_ENABLED + // Check all arguments beforehand to solve warnings + for (int i = 1; i < p_call->arguments.size(); i++) { + _reduce_node_type(p_call->arguments[i]); + } +#endif // DEBUG_ENABLED + + // Check arguments + + is_vararg = mi.flags & METHOD_FLAG_VARARG; + + default_args_count = mi.default_arguments.size(); + callee_name = mi.name; + arg_count -= 1; + + // Check each argument type + for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { + arg_types.push_back(_type_from_property(E->get())); + } + } break; + default: { + if (p_call->op == OperatorNode::OP_CALL && p_call->arguments.size() < 2) { + _set_error("Parser bug: self method call without enough arguments.", p_call->line); + ERR_FAIL_V(DataType()); + } + + int arg_id = p_call->op == OperatorNode::OP_CALL ? 1 : 0; + + if (p_call->arguments[arg_id]->type != Node::TYPE_IDENTIFIER) { + _set_error("Parser bug: invalid function call argument.", p_call->line); + ERR_FAIL_V(DataType()); + } + +#ifdef DEBUG_ENABLED + // Check all arguments beforehand to solve warnings + for (int i = arg_id + 1; i < p_call->arguments.size(); i++) { + _reduce_node_type(p_call->arguments[i]); + } +#endif // DEBUG_ENABLED + + IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]); + callee_name = func_id->name; + arg_count -= 1 + arg_id; + + DataType base_type; + if (p_call->op == OperatorNode::OP_PARENT_CALL) { + base_type = current_class->base_type; + } else { + base_type = _reduce_node_type(p_call->arguments[0]); + } + + if (!base_type.has_type || (base_type.kind == DataType::BUILTIN && base_type.builtin_type == Variant::NIL)) { + _mark_line_as_unsafe(p_call->line); + return DataType(); + } + + if (base_type.kind == DataType::BUILTIN) { + Variant::CallError err; + Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err); + + if (check_types) { + if (!tmp.has_method(callee_name)) { + _set_error("Method '" + callee_name + "' is not declared on base '" + base_type.to_string() + "'.", p_call->line); + return DataType(); + } + + default_args_count = Variant::get_method_default_arguments(base_type.builtin_type, callee_name).size(); + const Vector<Variant::Type> &var_arg_types = Variant::get_method_argument_types(base_type.builtin_type, callee_name); + + for (int i = 0; i < var_arg_types.size(); i++) { + DataType argtype; + if (var_arg_types[i] != Variant::NIL) { + argtype.has_type = true; + argtype.kind = DataType::BUILTIN; + argtype.builtin_type = var_arg_types[i]; + } + arg_types.push_back(argtype); + } + } + + bool rets = false; + return_type.has_type = true; + return_type.kind = DataType::BUILTIN; + return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name, &rets); + // If the method returns, but it might return any type, (Variant::NIL), pretend we don't know the type. + // At least make sure we know that it returns + if (rets && return_type.builtin_type == Variant::NIL) { + return_type.has_type = false; + } + break; + } + + DataType original_type = base_type; + bool is_initializer = callee_name == "new"; + bool is_static = false; + bool valid = false; + + if (is_initializer && original_type.is_meta_type) { + // Try to check it as initializer + base_type = original_type; + callee_name = "_init"; + base_type.is_meta_type = false; + + valid = _get_function_signature(base_type, callee_name, return_type, arg_types, + default_args_count, is_static, is_vararg); + + if (valid) { + return_type = original_type; + return_type.is_meta_type = false; + } + } + + if (!valid) { + base_type = original_type; + return_type = DataType(); + valid = _get_function_signature(base_type, callee_name, return_type, arg_types, + default_args_count, is_static, is_vararg); + } + + if (!valid) { +#ifdef DEBUG_ENABLED + if (p_call->arguments[0]->type == Node::TYPE_SELF) { + _set_error("Method '" + callee_name + "' is not declared in the current class.", p_call->line); + return DataType(); + } + DataType tmp_type; + valid = _get_member_type(original_type, func_id->name, tmp_type); + if (valid) { + if (tmp_type.is_constant) { + _add_warning(GDScriptWarning::CONSTANT_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string()); + } else { + _add_warning(GDScriptWarning::PROPERTY_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string()); + } + } + _add_warning(GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->line, callee_name, original_type.to_string()); + _mark_line_as_unsafe(p_call->line); +#endif // DEBUG_ENABLED + return DataType(); + } + +#ifdef DEBUG_ENABLED + if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) { + if (current_function && current_function->_static && p_call->arguments[0]->type == Node::TYPE_SELF) { + _set_error("Can't call non-static function from a static function.", p_call->line); + return DataType(); + } + } + + if (check_types && !is_static && !is_initializer && base_type.is_meta_type) { + _set_error("Non-static function '" + String(callee_name) + "' can only be called from an instance.", p_call->line); + return DataType(); + } + + // Check signal emission for warnings + if (callee_name == "emit_signal" && p_call->op == OperatorNode::OP_CALL && p_call->arguments[0]->type == Node::TYPE_SELF && p_call->arguments.size() >= 3 && p_call->arguments[2]->type == Node::TYPE_CONSTANT) { + ConstantNode *sig = static_cast<ConstantNode *>(p_call->arguments[2]); + String emitted = sig->value.get_type() == Variant::STRING ? sig->value.operator String() : ""; + for (int i = 0; i < current_class->_signals.size(); i++) { + if (current_class->_signals[i].name == emitted) { + current_class->_signals.write[i].emissions += 1; + break; + } + } + } +#endif // DEBUG_ENABLED + } break; + } + + if (!check_types) { + return return_type; + } + + if (arg_count < arg_types.size() - default_args_count) { + _set_error("Too few arguments for '" + callee_name + "()' call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line); + return return_type; + } + if (!is_vararg && arg_count > arg_types.size()) { + _set_error("Too many arguments for '" + callee_name + "()' call. Expected at most " + itos(arg_types.size()) + ".", p_call->line); + return return_type; + } + + int arg_diff = p_call->arguments.size() - arg_count; + for (int i = arg_diff; i < p_call->arguments.size(); i++) { + DataType par_type = _reduce_node_type(p_call->arguments[i]); + + if ((i - arg_diff) >= arg_types.size()) { + continue; + } + + DataType arg_type = arg_types[i - arg_diff]; + + if (!par_type.has_type) { + _mark_line_as_unsafe(p_call->line); +#ifdef DEBUG_ENABLED + if (par_type.may_yield && p_call->arguments[i]->type == Node::TYPE_OPERATOR) { + _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i]))); + } +#endif // DEBUG_ENABLED + } else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) { + // Supertypes are acceptable for dynamic compliance + if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) { + _set_error("At '" + callee_name + "()' call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" + + par_type.to_string() + ") doesn't match the function argument's type (" + + arg_types[i - arg_diff].to_string() + ").", + p_call->line); + return DataType(); + } else { + _mark_line_as_unsafe(p_call->line); + } + } else { +#ifdef DEBUG_ENABLED + if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_type.kind == DataType::BUILTIN && par_type.builtin_type == Variant::REAL) { + _add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, callee_name); + } +#endif // DEBUG_ENABLED + } + } + + return return_type; +} + +bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const { + DataType base_type = p_base_type; + + // Check classes in current file + ClassNode *base = NULL; + if (base_type.kind == DataType::CLASS) { + base = base_type.class_type; + } + + while (base) { + if (base->constant_expressions.has(p_member)) { + r_member_type = base->constant_expressions[p_member].expression->get_datatype(); + return true; + } + + if (!base_type.is_meta_type) { + for (int i = 0; i < base->variables.size(); i++) { + ClassNode::Member m = base->variables[i]; + if (m.identifier == p_member) { + r_member_type = m.data_type; + return true; + } + } + } else { + for (int i = 0; i < base->subclasses.size(); i++) { + ClassNode *c = base->subclasses[i]; + if (c->name == p_member) { + DataType class_type; + class_type.has_type = true; + class_type.is_constant = true; + class_type.is_meta_type = true; + class_type.kind = DataType::CLASS; + class_type.class_type = c; + r_member_type = class_type; + return true; + } + } + } + + base_type = base->base_type; + if (base_type.kind == DataType::CLASS) { + base = base_type.class_type; + } else { + break; + } + } + + Ref<GDScript> gds; + if (base_type.kind == DataType::GDSCRIPT) { + gds = base_type.script_type; + if (gds.is_null() || !gds->is_valid()) { + // GDScript wasn't properly compÃled, don't bother trying + return false; + } + } + + Ref<Script> scr; + if (base_type.kind == DataType::SCRIPT) { + scr = base_type.script_type; + } + + StringName native; + if (base_type.kind == DataType::NATIVE) { + native = base_type.native_type; + } + + // Check GDScripts + while (gds.is_valid()) { + if (gds->get_constants().has(p_member)) { + Variant c = gds->get_constants()[p_member]; + r_member_type = _type_from_variant(c); + return true; + } + + if (!base_type.is_meta_type) { + if (gds->get_members().has(p_member)) { + r_member_type = _type_from_gdtype(gds->get_member_type(p_member)); + return true; + } + } + + native = gds->get_instance_base_type(); + if (gds->get_base_script().is_valid()) { + gds = gds->get_base_script(); + scr = gds->get_base_script(); + bool is_meta = base_type.is_meta_type; + base_type = _type_from_variant(scr.operator Variant()); + base_type.is_meta_type = is_meta; + } else { + break; + } + } + + // Check other script types + while (scr.is_valid()) { + Map<StringName, Variant> constants; + scr->get_constants(&constants); + if (constants.has(p_member)) { + r_member_type = _type_from_variant(constants[p_member]); + return true; + } + + List<PropertyInfo> properties; + scr->get_script_property_list(&properties); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + if (E->get().name == p_member) { + r_member_type = _type_from_property(E->get()); + return true; + } + } + + base_type = _type_from_variant(scr.operator Variant()); + native = scr->get_instance_base_type(); + scr = scr->get_base_script(); + } + + if (native == StringName()) { + // Empty native class, might happen in some Script implementations + // Just ignore it + return false; + } + + // Check ClassDB + if (!ClassDB::class_exists(native)) { + native = "_" + native.operator String(); + } + if (!ClassDB::class_exists(native)) { + if (!check_types) return false; + ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found."); + ERR_FAIL_V(false); + } + + bool valid = false; + ClassDB::get_integer_constant(native, p_member, &valid); + if (valid) { + DataType ct; + ct.has_type = true; + ct.is_constant = true; + ct.kind = DataType::BUILTIN; + ct.builtin_type = Variant::INT; + r_member_type = ct; + return true; + } + + if (!base_type.is_meta_type) { + List<PropertyInfo> properties; + ClassDB::get_property_list(native, &properties); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + if (E->get().name == p_member) { + // Check if a getter exists + StringName getter_name = ClassDB::get_property_getter(native, p_member); + if (getter_name != StringName()) { + // Use the getter return type +#ifdef DEBUG_METHODS_ENABLED + MethodBind *getter_method = ClassDB::get_method(native, getter_name); + if (getter_method) { + r_member_type = _type_from_property(getter_method->get_return_info()); + } else { + r_member_type = DataType(); + } +#else + r_member_type = DataType(); +#endif + } else { + r_member_type = _type_from_property(E->get()); + } + return true; + } + } + } + + // If the base is a script, it might be trying to access members of the Script class itself + if (p_base_type.is_meta_type && (p_base_type.kind == DataType::SCRIPT || p_base_type.kind == DataType::GDSCRIPT)) { + native = p_base_type.script_type->get_class_name(); + ClassDB::get_integer_constant(native, p_member, &valid); + if (valid) { + DataType ct; + ct.has_type = true; + ct.is_constant = true; + ct.kind = DataType::BUILTIN; + ct.builtin_type = Variant::INT; + r_member_type = ct; + return true; + } + + List<PropertyInfo> properties; + ClassDB::get_property_list(native, &properties); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + if (E->get().name == p_member) { + // Check if a getter exists + StringName getter_name = ClassDB::get_property_getter(native, p_member); + if (getter_name != StringName()) { + // Use the getter return type +#ifdef DEBUG_METHODS_ENABLED + MethodBind *getter_method = ClassDB::get_method(native, getter_name); + if (getter_method) { + r_member_type = _type_from_property(getter_method->get_return_info()); + } else { + r_member_type = DataType(); + } +#else + r_member_type = DataType(); +#endif + } else { + r_member_type = _type_from_property(E->get()); + } + return true; + } + } + } + + return false; +} + +GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line) { + + if (p_base_type && !p_base_type->has_type) { + return DataType(); + } + + DataType base_type; + + // Check classes in current file + ClassNode *base = NULL; + if (!p_base_type) { + base = current_class; + base_type.has_type = true; + base_type.is_constant = true; + base_type.kind = DataType::CLASS; + base_type.class_type = base; + } else { + base_type = DataType(*p_base_type); + if (base_type.kind == DataType::CLASS) { + base = base_type.class_type; + } + } + + DataType member_type; + + for (int i = 0; i < current_class->variables.size(); i++) { + if (current_class->variables[i].identifier == p_identifier) { + member_type = current_class->variables[i].data_type; + current_class->variables.write[i].usages += 1; + return member_type; + } + } + + if (_get_member_type(base_type, p_identifier, member_type)) { + return member_type; + } + + if (!p_base_type) { + // Possibly this is a global, check before failing + + if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) { + DataType result; + result.has_type = true; + result.is_constant = true; + result.is_meta_type = true; + if (Engine::get_singleton()->has_singleton(p_identifier) || Engine::get_singleton()->has_singleton("_" + p_identifier.operator String())) { + result.is_meta_type = false; + } + result.kind = DataType::NATIVE; + result.native_type = p_identifier; + return result; + } + + ClassNode *outer_class = current_class; + while (outer_class) { + if (outer_class->name == p_identifier) { + DataType result; + result.has_type = true; + result.is_constant = true; + result.is_meta_type = true; + result.kind = DataType::CLASS; + result.class_type = outer_class; + return result; + } + if (outer_class->constant_expressions.has(p_identifier)) { + return outer_class->constant_expressions[p_identifier].type; + } + for (int i = 0; i < outer_class->subclasses.size(); i++) { + if (outer_class->subclasses[i] == current_class) { + continue; + } + if (outer_class->subclasses[i]->name == p_identifier) { + DataType result; + result.has_type = true; + result.is_constant = true; + result.is_meta_type = true; + result.kind = DataType::CLASS; + result.class_type = outer_class->subclasses[i]; + return result; + } + } + outer_class = outer_class->owner; + } + + if (ScriptServer::is_global_class(p_identifier)) { + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier)); + if (scr.is_valid()) { + DataType result; + result.has_type = true; + result.script_type = scr; + result.is_meta_type = true; + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + if (!gds->is_valid()) { + _set_error("Class '" + p_identifier + "' could not be fully loaded (script error or cyclic inheritance)."); + return DataType(); + } + result.kind = DataType::GDSCRIPT; + } else { + result.kind = DataType::SCRIPT; + } + return result; + } + _set_error("Class '" + p_identifier + "' was found in global scope but its script could not be loaded."); + return DataType(); + } + + if (GDScriptLanguage::get_singleton()->get_global_map().has(p_identifier)) { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier]; + Variant g = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + return _type_from_variant(g); + } + + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) { + Variant g = GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]; + return _type_from_variant(g); + } + + // Non-tool singletons aren't loaded, check project settings + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + String name = s.get_slice("/", 1); + if (name == p_identifier) { + String script = ProjectSettings::get_singleton()->get(s); + if (script.begins_with("*")) { + script = script.right(1); + } + if (!script.begins_with("res://")) { + script = "res://" + script; + } + Ref<Script> singleton = ResourceLoader::load(script); + if (singleton.is_valid()) { + DataType result; + result.has_type = true; + result.script_type = singleton; + + Ref<GDScript> gds = singleton; + if (gds.is_valid()) { + if (!gds->is_valid()) { + _set_error("Couldn't fully load singleton script '" + p_identifier + "' (possible cyclic reference or parse error).", p_line); + return DataType(); + } + result.kind = DataType::GDSCRIPT; + } else { + result.kind = DataType::SCRIPT; + } + } + } + } + + // This means looking in the current class, which type is always known + _set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line); + } + +#ifdef DEBUG_ENABLED + { + DataType tmp_type; + List<DataType> arg_types; + int argcount; + bool _static; + bool vararg; + if (_get_function_signature(base_type, p_identifier, tmp_type, arg_types, argcount, _static, vararg)) { + _add_warning(GDScriptWarning::FUNCTION_USED_AS_PROPERTY, p_line, p_identifier.operator String(), base_type.to_string()); + } + } +#endif // DEBUG_ENABLED + + _mark_line_as_unsafe(p_line); + return DataType(); +} + +void GDScriptParser::_check_class_level_types(ClassNode *p_class) { + + _mark_line_as_safe(p_class->line); + + // Constants + for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { + ClassNode::Constant &c = E->get(); + _mark_line_as_safe(c.expression->line); + DataType cont = _resolve_type(c.type, c.expression->line); + DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line); + + if (!_is_type_compatible(cont, expr)) { + _set_error("Constant value type (" + expr.to_string() + ") is not compatible with declared type (" + cont.to_string() + ").", + c.expression->line); + return; + } + + expr.is_constant = true; + c.type = expr; + c.expression->set_datatype(expr); + + DataType tmp; + if (_get_member_type(p_class->base_type, E->key(), tmp)) { + _set_error("Member '" + String(E->key()) + "' already exists in parent class.", c.expression->line); + return; + } + } + + // Function declarations + for (int i = 0; i < p_class->static_functions.size(); i++) { + _check_function_types(p_class->static_functions[i]); + if (error_set) return; + } + + for (int i = 0; i < p_class->functions.size(); i++) { + _check_function_types(p_class->functions[i]); + if (error_set) return; + } + + // Class variables + for (int i = 0; i < p_class->variables.size(); i++) { + ClassNode::Member &v = p_class->variables.write[i]; + + DataType tmp; + if (_get_member_type(p_class->base_type, v.identifier, tmp)) { + _set_error("Member '" + String(v.identifier) + "' already exists in parent class.", v.line); + return; + } + + _mark_line_as_safe(v.line); + v.data_type = _resolve_type(v.data_type, v.line); + + if (v.expression) { + DataType expr_type = _reduce_node_type(v.expression); + + if (!_is_type_compatible(v.data_type, expr_type)) { + // Try supertype test + if (_is_type_compatible(expr_type, v.data_type)) { + _mark_line_as_unsafe(v.line); + } else { + // Try with implicit conversion + if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) { + _set_error("Assigned expression type (" + expr_type.to_string() + ") doesn't match the variable's type (" + + v.data_type.to_string() + ").", + v.line); + return; + } + + // Replace assignment with implict conversion + BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); + convert->line = v.line; + convert->function = GDScriptFunctions::TYPE_CONVERT; + + ConstantNode *tgt_type = alloc_node<ConstantNode>(); + tgt_type->line = v.line; + tgt_type->value = (int)v.data_type.builtin_type; + + OperatorNode *convert_call = alloc_node<OperatorNode>(); + convert_call->line = v.line; + convert_call->op = OperatorNode::OP_CALL; + convert_call->arguments.push_back(convert); + convert_call->arguments.push_back(v.expression); + convert_call->arguments.push_back(tgt_type); + + v.expression = convert_call; + v.initial_assignment->arguments.write[1] = convert_call; + } + } + + if (v.data_type.infer_type) { + if (!expr_type.has_type) { + _set_error("Assigned value does not have a set type, variable type cannot be inferred.", v.line); + return; + } + v.data_type = expr_type; + v.data_type.is_constant = false; + } + } else if (v.data_type.has_type && v.data_type.kind == DataType::BUILTIN) { + // Create default value based on the type + IdentifierNode *id = alloc_node<IdentifierNode>(); + id->line = v.line; + id->name = v.identifier; + + ConstantNode *init = alloc_node<ConstantNode>(); + init->line = v.line; + Variant::CallError err; + init->value = Variant::construct(v.data_type.builtin_type, NULL, 0, err); + + OperatorNode *op = alloc_node<OperatorNode>(); + op->line = v.line; + op->op = OperatorNode::OP_INIT_ASSIGN; + op->arguments.push_back(id); + op->arguments.push_back(init); + + p_class->initializer->statements.push_front(op); + v.initial_assignment = op; +#ifdef DEBUG_ENABLED + NewLineNode *nl = alloc_node<NewLineNode>(); + nl->line = v.line - 1; + p_class->initializer->statements.push_front(nl); +#endif + } + + // Check export hint + if (v.data_type.has_type && v._export.type != Variant::NIL) { + DataType export_type = _type_from_property(v._export); + if (!_is_type_compatible(v.data_type, export_type, true)) { + _set_error("Export hint type (" + export_type.to_string() + ") doesn't match the variable's type (" + + v.data_type.to_string() + ").", + v.line); + return; + } + } + + // Setter and getter + if (v.setter == StringName() && v.getter == StringName()) continue; + + bool found_getter = false; + bool found_setter = false; + for (int j = 0; j < p_class->functions.size(); j++) { + if (v.setter == p_class->functions[j]->name) { + found_setter = true; + FunctionNode *setter = p_class->functions[j]; + + if (setter->arguments.size() != 1) { + _set_error("Setter function needs to receive exactly 1 argument. See '" + setter->name + + "()' definition at line " + itos(setter->line) + ".", + v.line); + return; + } + if (!_is_type_compatible(v.data_type, setter->argument_types[0])) { + _set_error("Setter argument type (" + setter->argument_types[0].to_string() + + ") doesn't match the variable's type (" + v.data_type.to_string() + "). See '" + + setter->name + "()' definition at line " + itos(setter->line) + ".", + v.line); + return; + } + continue; + } + if (v.getter == p_class->functions[j]->name) { + found_getter = true; + FunctionNode *getter = p_class->functions[j]; + + if (getter->arguments.size() != 0) { + _set_error("Getter function can't receive arguments. See '" + getter->name + + "()' definition at line " + itos(getter->line) + ".", + v.line); + return; + } + if (!_is_type_compatible(v.data_type, getter->get_datatype())) { + _set_error("Getter return type (" + getter->get_datatype().to_string() + + ") doesn't match the variable's type (" + v.data_type.to_string() + + "). See '" + getter->name + "()' definition at line " + itos(getter->line) + ".", + v.line); + return; + } + } + if (found_getter && found_setter) break; + } + + if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) continue; + + // Check for static functions + for (int j = 0; j < p_class->static_functions.size(); j++) { + if (v.setter == p_class->static_functions[j]->name) { + FunctionNode *setter = p_class->static_functions[j]; + _set_error("Setter can't be a static function. See '" + setter->name + "()' definition at line " + itos(setter->line) + ".", v.line); + return; + } + if (v.getter == p_class->static_functions[j]->name) { + FunctionNode *getter = p_class->static_functions[j]; + _set_error("Getter can't be a static function. See '" + getter->name + "()' definition at line " + itos(getter->line) + ".", v.line); + return; + } + } + + if (!found_setter && v.setter != StringName()) { + _set_error("Setter function is not defined.", v.line); + return; + } + + if (!found_getter && v.getter != StringName()) { + _set_error("Getter function is not defined.", v.line); + return; + } + } + + // Inner classes + for (int i = 0; i < p_class->subclasses.size(); i++) { + current_class = p_class->subclasses[i]; + _check_class_level_types(current_class); + if (error_set) return; + current_class = p_class; + } +} + +void GDScriptParser::_check_function_types(FunctionNode *p_function) { + + p_function->return_type = _resolve_type(p_function->return_type, p_function->line); + + // Arguments + int defaults_ofs = p_function->arguments.size() - p_function->default_values.size(); + for (int i = 0; i < p_function->arguments.size(); i++) { + if (i < defaults_ofs) { + p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line); + } else { + if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) { + _set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column); + return; + } + + OperatorNode *op = static_cast<OperatorNode *>(p_function->default_values[i - defaults_ofs]); + + if (op->op != OperatorNode::OP_ASSIGN || op->arguments.size() != 2) { + _set_error("Parser bug: invalid argument default value operation.", p_function->line); + return; + } + + DataType def_type = _reduce_node_type(op->arguments[1]); + + if (p_function->argument_types[i].infer_type) { + def_type.is_constant = false; + p_function->argument_types.write[i] = def_type; + } else { + p_function->return_type = _resolve_type(p_function->return_type, p_function->line); + + if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) { + String arg_name = p_function->arguments[i]; + _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" + + arg_name + "' (" + p_function->arguments[i] + ")", + p_function->line); + } + } + } +#ifdef DEBUG_ENABLED + if (p_function->arguments_usage[i] == 0 && !p_function->arguments[i].operator String().begins_with("_")) { + _add_warning(GDScriptWarning::UNUSED_ARGUMENT, p_function->line, p_function->name, p_function->arguments[i].operator String()); + } +#endif // DEBUG_ENABLED + } + + if (!(p_function->name == "_init")) { + // Signature for the initializer may vary +#ifdef DEBUG_ENABLED + DataType return_type; + List<DataType> arg_types; + int default_arg_count = 0; + bool _static = false; + bool vararg = false; + + DataType base_type = current_class->base_type; + if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) { + bool valid = _static == p_function->_static; + valid = valid && return_type == p_function->return_type; + int argsize_diff = p_function->arguments.size() - arg_types.size(); + valid = valid && argsize_diff >= 0; + valid = valid && p_function->default_values.size() >= default_arg_count + argsize_diff; + int i = 0; + for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) { + valid = valid && E->get() == p_function->argument_types[i++]; + } + + if (!valid) { + String parent_signature = return_type.has_type ? return_type.to_string() : "Variant"; + if (parent_signature == "null") { + parent_signature = "void"; + } + parent_signature += " " + p_function->name + "("; + if (arg_types.size()) { + int i = 0; + for (List<DataType>::Element *E = arg_types.front(); E; E = E->next()) { + if (E != arg_types.front()) { + parent_signature += ", "; + } + String arg = E->get().to_string(); + if (arg == "null" || arg == "var") { + arg = "Variant"; + } + parent_signature += arg; + if (i == arg_types.size() - default_arg_count) { + parent_signature += "=default"; + } + + i++; + } + } + parent_signature += ")"; + _set_error("Function signature doesn't match the parent. Parent signature is: '" + parent_signature + "'.", p_function->line); + return; + } + } +#endif // DEBUG_ENABLED + } else { + if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { + _set_error("Constructor cannot return a value.", p_function->line); + return; + } + } + + if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { + if (!p_function->body->has_return) { + _set_error("Non-void function must return a value in all possible paths.", p_function->line); + return; + } + } + + if (p_function->has_yield) { + // yield() will make the function return a GDScriptFunctionState, so the type is ambiguous + p_function->return_type.has_type = false; + p_function->return_type.may_yield = true; + } +} + +void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) { + + // Function blocks + for (int i = 0; i < p_class->static_functions.size(); i++) { + current_function = p_class->static_functions[i]; + current_block = current_function->body; + _mark_line_as_safe(current_function->line); + _check_block_types(current_block); + current_block = NULL; + current_function = NULL; + if (error_set) return; + } + + for (int i = 0; i < p_class->functions.size(); i++) { + current_function = p_class->functions[i]; + current_block = current_function->body; + _mark_line_as_safe(current_function->line); + _check_block_types(current_block); + current_block = NULL; + current_function = NULL; + if (error_set) return; + } + +#ifdef DEBUG_ENABLED + // Warnings + for (int i = 0; i < p_class->variables.size(); i++) { + if (p_class->variables[i].usages == 0) { + _add_warning(GDScriptWarning::UNUSED_CLASS_VARIABLE, p_class->variables[i].line, p_class->variables[i].identifier); + } + } + for (int i = 0; i < p_class->_signals.size(); i++) { + if (p_class->_signals[i].emissions == 0) { + _add_warning(GDScriptWarning::UNUSED_SIGNAL, p_class->_signals[i].line, p_class->_signals[i].name); + } + } +#endif // DEBUG_ENABLED + + // Inner classes + for (int i = 0; i < p_class->subclasses.size(); i++) { + current_class = p_class->subclasses[i]; + _check_class_blocks_types(current_class); + if (error_set) return; + current_class = p_class; + } +} + +#ifdef DEBUG_ENABLED +static String _find_function_name(const GDScriptParser::OperatorNode *p_call) { + switch (p_call->arguments[0]->type) { + case GDScriptParser::Node::TYPE_TYPE: { + return Variant::get_type_name(static_cast<GDScriptParser::TypeNode *>(p_call->arguments[0])->vtype); + } break; + case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: { + return GDScriptFunctions::get_func_name(static_cast<GDScriptParser::BuiltInFunctionNode *>(p_call->arguments[0])->function); + } break; + default: { + int id_index = p_call->op == GDScriptParser::OperatorNode::OP_PARENT_CALL ? 0 : 1; + if (p_call->arguments.size() > id_index && p_call->arguments[id_index]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { + return static_cast<GDScriptParser::IdentifierNode *>(p_call->arguments[id_index])->name; + } + } break; + } + return String(); +} +#endif // DEBUG_ENABLED + +void GDScriptParser::_check_block_types(BlockNode *p_block) { + + Node *last_var_assign = NULL; + + // Check each statement + for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) { + Node *statement = E->get(); + switch (statement->type) { + case Node::TYPE_NEWLINE: + case Node::TYPE_BREAKPOINT: + case Node::TYPE_ASSERT: { + // Nothing to do + } break; + case Node::TYPE_LOCAL_VAR: { + LocalVarNode *lv = static_cast<LocalVarNode *>(statement); + lv->datatype = _resolve_type(lv->datatype, lv->line); + _mark_line_as_safe(lv->line); + + last_var_assign = lv->assign; + if (lv->assign) { + DataType assign_type = _reduce_node_type(lv->assign); +#ifdef DEBUG_ENABLED + if (assign_type.has_type && assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) { + if (lv->assign->type == Node::TYPE_OPERATOR) { + OperatorNode *call = static_cast<OperatorNode *>(lv->assign); + if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) { + _add_warning(GDScriptWarning::VOID_ASSIGNMENT, lv->line, _find_function_name(call)); + } + } + } + if (lv->datatype.has_type && assign_type.may_yield && lv->assign->type == Node::TYPE_OPERATOR) { + _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, lv->line, _find_function_name(static_cast<OperatorNode *>(lv->assign))); + } +#endif // DEBUG_ENABLED + + if (!_is_type_compatible(lv->datatype, assign_type)) { + // Try supertype test + if (_is_type_compatible(assign_type, lv->datatype)) { + _mark_line_as_unsafe(lv->line); + } else { + // Try implict conversion + if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) { + _set_error("Assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" + + lv->datatype.to_string() + ").", + lv->line); + return; + } + // Replace assignment with implict conversion + BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); + convert->line = lv->line; + convert->function = GDScriptFunctions::TYPE_CONVERT; + + ConstantNode *tgt_type = alloc_node<ConstantNode>(); + tgt_type->line = lv->line; + tgt_type->value = (int)lv->datatype.builtin_type; + + OperatorNode *convert_call = alloc_node<OperatorNode>(); + convert_call->line = lv->line; + convert_call->op = OperatorNode::OP_CALL; + convert_call->arguments.push_back(convert); + convert_call->arguments.push_back(lv->assign); + convert_call->arguments.push_back(tgt_type); + + lv->assign = convert_call; + lv->assign_op->arguments.write[1] = convert_call; +#ifdef DEBUG_ENABLED + if (lv->datatype.builtin_type == Variant::INT && assign_type.builtin_type == Variant::REAL) { + _add_warning(GDScriptWarning::NARROWING_CONVERSION, lv->line); + } +#endif // DEBUG_ENABLED + } + } + if (lv->datatype.infer_type) { + if (!assign_type.has_type) { + _set_error("Assigned value does not have a set type, variable type cannot be inferred.", lv->line); + return; + } + lv->datatype = assign_type; + lv->datatype.is_constant = false; + } + if (lv->datatype.has_type && !assign_type.has_type) { + _mark_line_as_unsafe(lv->line); + } + } + } break; + case Node::TYPE_OPERATOR: { + OperatorNode *op = static_cast<OperatorNode *>(statement); + + switch (op->op) { + case OperatorNode::OP_ASSIGN: + case OperatorNode::OP_ASSIGN_ADD: + case OperatorNode::OP_ASSIGN_SUB: + case OperatorNode::OP_ASSIGN_MUL: + case OperatorNode::OP_ASSIGN_DIV: + case OperatorNode::OP_ASSIGN_MOD: + case OperatorNode::OP_ASSIGN_SHIFT_LEFT: + case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: + case OperatorNode::OP_ASSIGN_BIT_AND: + case OperatorNode::OP_ASSIGN_BIT_OR: + case OperatorNode::OP_ASSIGN_BIT_XOR: { + if (op->arguments.size() < 2) { + _set_error("Parser bug: operation without enough arguments.", op->line, op->column); + return; + } + + if (op->arguments[1] == last_var_assign) { + // Assignment was already checked + break; + } + + _mark_line_as_safe(op->line); + + DataType lh_type = _reduce_node_type(op->arguments[0]); + + if (error_set) { + return; + } + + if (!lh_type.has_type) { + if (op->arguments[0]->type == Node::TYPE_OPERATOR) { + _mark_line_as_unsafe(op->line); + } + } else if (lh_type.is_constant) { + _set_error("Cannot assign a new value to a constant.", op->line); + return; + } + + DataType rh_type; + if (op->op != OperatorNode::OP_ASSIGN) { + // Validate operation + DataType arg_type = _reduce_node_type(op->arguments[1]); + if (!arg_type.has_type) { + _mark_line_as_unsafe(op->line); + break; + } + + Variant::Operator oper = _get_variant_operation(op->op); + bool valid = false; + rh_type = _get_operation_type(oper, lh_type, arg_type, valid); + + if (!valid) { + _set_error("Invalid operand types ('" + lh_type.to_string() + "' and '" + arg_type.to_string() + + "') to assignment operator '" + Variant::get_operator_name(oper) + "'.", + op->line); + return; + } + } else { + rh_type = _reduce_node_type(op->arguments[1]); + } +#ifdef DEBUG_ENABLED + if (rh_type.has_type && rh_type.kind == DataType::BUILTIN && rh_type.builtin_type == Variant::NIL) { + if (op->arguments[1]->type == Node::TYPE_OPERATOR) { + OperatorNode *call = static_cast<OperatorNode *>(op->arguments[1]); + if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) { + _add_warning(GDScriptWarning::VOID_ASSIGNMENT, op->line, _find_function_name(call)); + } + } + } + if (lh_type.has_type && rh_type.may_yield && op->arguments[1]->type == Node::TYPE_OPERATOR) { + _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, op->line, _find_function_name(static_cast<OperatorNode *>(op->arguments[1]))); + } +#endif // DEBUG_ENABLED + + if (!_is_type_compatible(lh_type, rh_type)) { + // Try supertype test + if (_is_type_compatible(rh_type, lh_type)) { + _mark_line_as_unsafe(op->line); + } else { + // Try implict conversion + if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) { + _set_error("Assigned value type (" + rh_type.to_string() + ") doesn't match the variable's type (" + + lh_type.to_string() + ").", + op->line); + return; + } + // Replace assignment with implict conversion + BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); + convert->line = op->line; + convert->function = GDScriptFunctions::TYPE_CONVERT; + + ConstantNode *tgt_type = alloc_node<ConstantNode>(); + tgt_type->line = op->line; + tgt_type->value = (int)lh_type.builtin_type; + + OperatorNode *convert_call = alloc_node<OperatorNode>(); + convert_call->line = op->line; + convert_call->op = OperatorNode::OP_CALL; + convert_call->arguments.push_back(convert); + convert_call->arguments.push_back(op->arguments[1]); + convert_call->arguments.push_back(tgt_type); + + op->arguments.write[1] = convert_call; +#ifdef DEBUG_ENABLED + if (lh_type.builtin_type == Variant::INT && rh_type.builtin_type == Variant::REAL) { + _add_warning(GDScriptWarning::NARROWING_CONVERSION, op->line); + } +#endif // DEBUG_ENABLED + } + } + if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) { + _mark_line_as_unsafe(op->line); + } + } break; + case OperatorNode::OP_CALL: + case OperatorNode::OP_PARENT_CALL: { + _mark_line_as_safe(op->line); + DataType func_type = _reduce_function_call_type(op); +#ifdef DEBUG_ENABLED + if (func_type.has_type && (func_type.kind != DataType::BUILTIN || func_type.builtin_type != Variant::NIL)) { + // Figure out function name for warning + String func_name = _find_function_name(op); + if (func_name.empty()) { + func_name = "<undetected name>"; + } + _add_warning(GDScriptWarning::RETURN_VALUE_DISCARDED, op->line, func_name); + } +#endif // DEBUG_ENABLED + if (error_set) return; + } break; + case OperatorNode::OP_YIELD: { + _mark_line_as_safe(op->line); + _reduce_node_type(op); + } break; + default: { + _mark_line_as_safe(op->line); + _reduce_node_type(op); // Test for safety anyway +#ifdef DEBUG_ENABLED + _add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line); +#endif // DEBUG_ENABLED + } + } + } break; + case Node::TYPE_CONTROL_FLOW: { + ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement); + _mark_line_as_safe(cf->line); + + switch (cf->cf_type) { + case ControlFlowNode::CF_RETURN: { + DataType function_type = current_function->get_datatype(); + + DataType ret_type; + if (cf->arguments.size() > 0) { + ret_type = _reduce_node_type(cf->arguments[0]); + if (error_set) { + return; + } + } + + if (!function_type.has_type) break; + + if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) { + // Return void, should not have arguments + if (cf->arguments.size() > 0) { + _set_error("Void function cannot return a value.", cf->line, cf->column); + return; + } + } else { + // Return something, cannot be empty + if (cf->arguments.size() == 0) { + _set_error("Non-void function must return a value.", cf->line, cf->column); + return; + } + + if (!_is_type_compatible(function_type, ret_type)) { + _set_error("Returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" + + function_type.to_string() + ").", + cf->line, cf->column); + return; + } + } + } break; + case ControlFlowNode::CF_MATCH: { + MatchNode *match_node = cf->match; + _transform_match_statment(match_node); + } break; + default: { + if (cf->body_else) { + _mark_line_as_safe(cf->body_else->line); + } + for (int i = 0; i < cf->arguments.size(); i++) { + _reduce_node_type(cf->arguments[i]); + } + } break; + } + } break; + case Node::TYPE_CONSTANT: { + ConstantNode *cn = static_cast<ConstantNode *>(statement); + // Strings are fine since they can be multiline comments + if (cn->value.get_type() == Variant::STRING) { + break; + } + } // falthrough + default: { + _mark_line_as_safe(statement->line); + _reduce_node_type(statement); // Test for safety anyway +#ifdef DEBUG_ENABLED + _add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line); +#endif // DEBUG_ENABLED + } + } + } + + // Parse sub blocks + for (int i = 0; i < p_block->sub_blocks.size(); i++) { + current_block = p_block->sub_blocks[i]; + _check_block_types(current_block); + current_block = p_block; + if (error_set) return; + } + +#ifdef DEBUG_ENABLED + // Warnings check + for (Map<StringName, LocalVarNode *>::Element *E = p_block->variables.front(); E; E = E->next()) { + LocalVarNode *lv = E->get(); + if (!lv->name.operator String().begins_with("_")) { + if (lv->usages == 0) { + _add_warning(GDScriptWarning::UNUSED_VARIABLE, lv->line, lv->name); + } else if (lv->assignments == 0) { + _add_warning(GDScriptWarning::UNASSIGNED_VARIABLE, lv->line, lv->name); + } + } + } +#endif // DEBUG_ENABLED +} + void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) { if (error_set) @@ -4407,6 +8008,56 @@ void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) error_set = true; } +#ifdef DEBUG_ENABLED +void GDScriptParser::_add_warning(int p_code, int p_line, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) { + Vector<String> symbols; + if (!p_symbol1.empty()) { + symbols.push_back(p_symbol1); + } + if (!p_symbol2.empty()) { + symbols.push_back(p_symbol2); + } + if (!p_symbol3.empty()) { + symbols.push_back(p_symbol3); + } + if (!p_symbol4.empty()) { + symbols.push_back(p_symbol4); + } + _add_warning(p_code, p_line, symbols); +} + +void GDScriptParser::_add_warning(int p_code, int p_line, const Vector<String> &p_symbols) { + if (tokenizer->is_ignoring_warnings() || !GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) { + return; + } + String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower(); + if (tokenizer->get_warning_global_skips().has(warn_name)) { + return; + } + if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) { + return; + } + + GDScriptWarning warn; + warn.code = (GDScriptWarning::Code)p_code; + warn.symbols = p_symbols; + warn.line = p_line == -1 ? tokenizer->get_token_line() : p_line; + + List<GDScriptWarning>::Element *before = NULL; + for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { + if (E->get().line > warn.line) { + break; + } + before = E; + } + if (before) { + warnings.insert_after(before, warn); + } else { + warnings.push_front(warn); + } +} +#endif // DEBUG_ENABLED + String GDScriptParser::get_error() const { return error; @@ -4440,10 +8091,70 @@ Error GDScriptParser::_parse(const String &p_base_path) { _set_error("Parse Error: " + tokenizer->get_token_error()); } + if (error_set && !for_completion) { + return ERR_PARSE_ERROR; + } + + _determine_inheritance(main_class); + + if (error_set) { + return ERR_PARSE_ERROR; + } + + current_class = main_class; + current_function = NULL; + current_block = NULL; +#ifdef DEBUG_ENABLED + if (for_completion) check_types = false; +#else + check_types = false; +#endif + + // Resolve all class-level stuff before getting into function blocks + _check_class_level_types(main_class); + if (error_set) { + return ERR_PARSE_ERROR; + } + + // Resolve the function blocks + _check_class_blocks_types(main_class); + if (error_set) { return ERR_PARSE_ERROR; } + +#ifdef DEBUG_ENABLED + // Resolve warning ignores + Vector<Pair<int, String> > warning_skips = tokenizer->get_warning_skips(); + bool warning_is_error = GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors").booleanize(); + for (List<GDScriptWarning>::Element *E = warnings.front(); E;) { + GDScriptWarning &w = E->get(); + int skip_index = -1; + for (int i = 0; i < warning_skips.size(); i++) { + if (warning_skips[i].first >= w.line) { + break; + } + skip_index = i; + } + List<GDScriptWarning>::Element *next = E->next(); + bool erase = false; + if (skip_index != -1) { + if (warning_skips[skip_index].second == GDScriptWarning::get_name_from_code(w.code).to_lower()) { + erase = true; + } + warning_skips.remove(skip_index); + } + if (erase) { + warnings.erase(E); + } else if (warning_is_error) { + _set_error(w.get_message() + " (warning treated as error)", w.line); + return ERR_PARSE_ERROR; + } + E = next; + } +#endif // DEBUG_ENABLED + return OK; } @@ -4461,7 +8172,7 @@ Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const St return ret; } -Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion) { +Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines) { clear(); @@ -4471,6 +8182,9 @@ Error GDScriptParser::parse(const String &p_code, const String &p_base_path, boo validating = p_just_validate; for_completion = p_for_completion; +#ifdef DEBUG_ENABLED + safe_lines = r_safe_lines; +#endif // DEBUG_ENABLED tokenizer = tt; Error ret = _parse(p_base_path); memdelete(tt); @@ -4523,7 +8237,11 @@ void GDScriptParser::clear() { pending_newline = -1; parenthesis = 0; current_export.type = Variant::NIL; + check_types = true; error = ""; +#ifdef DEBUG_ENABLED + safe_lines = NULL; +#endif // DEBUG_ENABLED } GDScriptParser::CompletionType GDScriptParser::get_completion_type() { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index b88a59537c..8121fb7f85 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -31,14 +31,79 @@ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H +#include "core/map.h" +#include "core/object.h" +#include "core/script_language.h" #include "gdscript_functions.h" #include "gdscript_tokenizer.h" -#include "map.h" -#include "object.h" -#include "script_language.h" + +struct GDScriptDataType; +struct GDScriptWarning; class GDScriptParser { public: + struct ClassNode; + + struct DataType { + enum { + BUILTIN, + NATIVE, + SCRIPT, + GDSCRIPT, + CLASS, + UNRESOLVED + } kind; + + bool has_type; + bool is_constant; + bool is_meta_type; // Whether the value can be used as a type + bool infer_type; + bool may_yield; // For function calls + + Variant::Type builtin_type; + StringName native_type; + Ref<Script> script_type; + ClassNode *class_type; + + String to_string() const; + + bool operator==(const DataType &other) const { + if (!has_type || !other.has_type) { + return true; // Can be considered equal for parsing purpose + } + if (kind != other.kind) { + return false; + } + switch (kind) { + case BUILTIN: { + return builtin_type == other.builtin_type; + } break; + case NATIVE: { + return native_type == other.native_type; + } break; + case GDSCRIPT: + case SCRIPT: { + return script_type == other.script_type; + } break; + case CLASS: { + return class_type == other.class_type; + } break; + case UNRESOLVED: { + } break; + } + return false; + } + + DataType() : + has_type(false), + is_constant(false), + is_meta_type(false), + infer_type(false), + may_yield(false), + builtin_type(Variant::NIL), + class_type(NULL) {} + }; + struct Node { enum Type { @@ -55,6 +120,7 @@ public: TYPE_OPERATOR, TYPE_CONTROL_FLOW, TYPE_LOCAL_VAR, + TYPE_CAST, TYPE_ASSERT, TYPE_BREAKPOINT, TYPE_NEWLINE, @@ -65,11 +131,17 @@ public: int column; Type type; + virtual DataType get_datatype() const { return DataType(); } + virtual void set_datatype(const DataType &p_datatype) {} + virtual ~Node() {} }; struct FunctionNode; struct BlockNode; + struct ConstantNode; + struct LocalVarNode; + struct OperatorNode; struct ClassNode : public Node { @@ -78,6 +150,8 @@ public: bool extends_used; StringName extends_file; Vector<StringName> extends_class; + DataType base_type; + String icon_path; struct Member { PropertyInfo _export; @@ -85,25 +159,30 @@ public: Variant default_value; #endif StringName identifier; + DataType data_type; StringName setter; StringName getter; int line; Node *expression; + OperatorNode *initial_assignment; MultiplayerAPI::RPCMode rpc_mode; + int usages; }; struct Constant { - StringName identifier; Node *expression; + DataType type; }; struct Signal { StringName name; Vector<StringName> arguments; + int emissions; + int line; }; Vector<ClassNode *> subclasses; Vector<Member> variables; - Vector<Constant> constant_expressions; + Map<StringName, Constant> constant_expressions; Vector<FunctionNode *> functions; Vector<FunctionNode *> static_functions; Vector<Signal> _signals; @@ -126,15 +205,27 @@ public: bool _static; MultiplayerAPI::RPCMode rpc_mode; + bool has_yield; + bool has_unreachable_code; StringName name; + DataType return_type; Vector<StringName> arguments; + Vector<DataType> argument_types; Vector<Node *> default_values; BlockNode *body; +#ifdef DEBUG_ENABLED + Vector<int> arguments_usage; +#endif // DEBUG_ENABLED + + virtual DataType get_datatype() const { return return_type; } + virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; } FunctionNode() { type = TYPE_FUNCTION; _static = false; rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + has_yield = false; + has_unreachable_code = false; } }; @@ -142,10 +233,9 @@ public: ClassNode *parent_class; BlockNode *parent_block; - Map<StringName, int> locals; List<Node *> statements; - Vector<StringName> variables; - Vector<int> variable_lines; + Map<StringName, LocalVarNode *> variables; + bool has_return; Node *if_condition; //tiny hack to improve code completion on if () blocks @@ -158,6 +248,7 @@ public: end_line = -1; parent_block = NULL; parent_class = NULL; + has_return = false; } }; @@ -174,28 +265,55 @@ public: struct IdentifierNode : public Node { StringName name; - IdentifierNode() { type = TYPE_IDENTIFIER; } + BlockNode *declared_block; // Simplify lookup by checking if it is declared locally + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } + IdentifierNode() { + type = TYPE_IDENTIFIER; + declared_block = NULL; + } }; struct LocalVarNode : public Node { StringName name; Node *assign; + OperatorNode *assign_op; + int assignments; + int usages; + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } LocalVarNode() { type = TYPE_LOCAL_VAR; assign = NULL; + assign_op = NULL; + assignments = 0; + usages = 0; } }; struct ConstantNode : public Node { Variant value; + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } ConstantNode() { type = TYPE_CONSTANT; } }; struct ArrayNode : public Node { Vector<Node *> elements; - ArrayNode() { type = TYPE_ARRAY; } + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } + ArrayNode() { + type = TYPE_ARRAY; + datatype.has_type = true; + datatype.kind = DataType::BUILTIN; + datatype.builtin_type = Variant::ARRAY; + } }; struct DictionaryNode : public Node { @@ -207,7 +325,15 @@ public: }; Vector<Pair> elements; - DictionaryNode() { type = TYPE_DICTIONARY; } + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } + DictionaryNode() { + type = TYPE_DICTIONARY; + datatype.has_type = true; + datatype.kind = DataType::BUILTIN; + datatype.builtin_type = Variant::DICTIONARY; + } }; struct SelfNode : public Node { @@ -221,6 +347,7 @@ public: OP_PARENT_CALL, OP_YIELD, OP_IS, + OP_IS_BUILTIN, //indexing operator OP_INDEX, OP_INDEX_NAMED, @@ -229,10 +356,6 @@ public: OP_POS, OP_NOT, OP_BIT_INVERT, - OP_PREINC, - OP_PREDEC, - OP_INC, - OP_DEC, //binary operators (in precedence order) OP_IN, OP_EQUAL, @@ -273,6 +396,9 @@ public: Operator op; Vector<Node *> arguments; + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } OperatorNode() { type = TYPE_OPERATOR; } }; @@ -340,6 +466,15 @@ public: } }; + struct CastNode : public Node { + Node *source_node; + DataType cast_type; + DataType return_type; + virtual DataType get_datatype() const { return return_type; } + virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; } + CastNode() { type = TYPE_CAST; } + }; + struct AssertNode : public Node { Node *condition; AssertNode() { type = TYPE_ASSERT; } @@ -362,76 +497,6 @@ public: }; }; - /* - struct OperatorNode : public Node { - - DataType return_cache; - Operator op; - Vector<Node*> arguments; - virtual DataType get_datatype() const { return return_cache; } - - OperatorNode() { type=TYPE_OPERATOR; return_cache=TYPE_VOID; } - }; - - struct VariableNode : public Node { - - DataType datatype_cache; - StringName name; - virtual DataType get_datatype() const { return datatype_cache; } - - VariableNode() { type=TYPE_VARIABLE; datatype_cache=TYPE_VOID; } - }; - - struct ConstantNode : public Node { - - DataType datatype; - Variant value; - virtual DataType get_datatype() const { return datatype; } - - ConstantNode() { type=TYPE_CONSTANT; } - }; - - struct BlockNode : public Node { - - Map<StringName,DataType> variables; - List<Node*> statements; - BlockNode() { type=TYPE_BLOCK; } - }; - - struct ControlFlowNode : public Node { - - FlowOperation flow_op; - Vector<Node*> statements; - ControlFlowNode() { type=TYPE_CONTROL_FLOW; flow_op=FLOW_OP_IF;} - }; - - struct MemberNode : public Node { - - DataType datatype; - StringName name; - Node* owner; - virtual DataType get_datatype() const { return datatype; } - MemberNode() { type=TYPE_MEMBER; } - }; - - - struct ProgramNode : public Node { - - struct Function { - StringName name; - FunctionNode*function; - }; - - Map<StringName,DataType> builtin_variables; - Map<StringName,DataType> preexisting_variables; - - Vector<Function> functions; - BlockNode *body; - - ProgramNode() { type=TYPE_PROGRAM; } - }; -*/ - enum CompletionType { COMPLETION_NONE, COMPLETION_BUILT_IN_TYPE_CONSTANT, @@ -446,6 +511,8 @@ public: COMPLETION_VIRTUAL_FUNC, COMPLETION_YIELD, COMPLETION_ASSIGN, + COMPLETION_TYPE_HINT, + COMPLETION_TYPE_HINT_INDEX, }; private: @@ -463,6 +530,14 @@ private: String error; int error_line; int error_column; + bool check_types; +#ifdef DEBUG_ENABLED + Set<int> *safe_lines; +#endif // DEBUG_ENABLED + +#ifdef DEBUG_ENABLED + List<GDScriptWarning> warnings; +#endif // DEBUG_ENABLED int pending_newline; @@ -480,7 +555,6 @@ private: CompletionType completion_type; StringName completion_cursor; - bool completion_static; Variant::Type completion_built_in_constant; Node *completion_node; ClassNode *completion_class; @@ -496,6 +570,10 @@ private: MultiplayerAPI::RPCMode rpc_mode; void _set_error(const String &p_error, int p_line = -1, int p_column = -1); +#ifdef DEBUG_ENABLED + void _add_warning(int p_code, int p_line = -1, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String()); + void _add_warning(int p_code, int p_line, const Vector<String> &p_symbols); +#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); @@ -507,7 +585,7 @@ private: PatternNode *_parse_pattern(bool p_static); void _parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static); - void _transform_match_statment(BlockNode *p_block, MatchNode *p_match_statement); + void _transform_match_statment(MatchNode *p_match_statement); void _generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings); void _parse_block(BlockNode *p_block, bool p_static); @@ -515,13 +593,46 @@ private: void _parse_class(ClassNode *p_class); bool _end_statement(); + void _determine_inheritance(ClassNode *p_class); + 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; + DataType _type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant = true) const; + DataType _type_from_gdtype(const GDScriptDataType &p_gdtype) const; + DataType _get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const; + Variant::Operator _get_variant_operation(const OperatorNode::Operator &p_op) const; + bool _get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const; + bool _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const; + bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const; + + DataType _reduce_node_type(Node *p_node); + DataType _reduce_function_call_type(const OperatorNode *p_call); + DataType _reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line); + void _check_class_level_types(ClassNode *p_class); + void _check_class_blocks_types(ClassNode *p_class); + void _check_function_types(FunctionNode *p_function); + void _check_block_types(BlockNode *p_block); + _FORCE_INLINE_ void _mark_line_as_safe(int p_line) const { +#ifdef DEBUG_ENABLED + if (safe_lines) safe_lines->insert(p_line); +#endif // DEBUG_ENABLED + } + _FORCE_INLINE_ void _mark_line_as_unsafe(int p_line) const { +#ifdef DEBUG_ENABLED + if (safe_lines) safe_lines->erase(p_line); +#endif // DEBUG_ENABLED + } + Error _parse(const String &p_base_path); public: String get_error() const; int get_error_line() const; int get_error_column() const; - Error parse(const String &p_code, const String &p_base_path = "", bool p_just_validate = false, const String &p_self_path = "", bool p_for_completion = false); +#ifdef DEBUG_ENABLED + const List<GDScriptWarning> &get_warnings() const { return warnings; } +#endif // DEBUG_ENABLED + Error parse(const String &p_code, const String &p_base_path = "", bool p_just_validate = false, const String &p_self_path = "", bool p_for_completion = false, Set<int> *r_safe_lines = NULL); Error parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path = "", const String &p_self_path = ""); bool is_tool_script() const; diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 3c8e1ddbe4..c37142b3c1 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -30,10 +30,10 @@ #include "gdscript_tokenizer.h" +#include "core/io/marshalls.h" +#include "core/map.h" +#include "core/print_string.h" #include "gdscript_functions.h" -#include "io/marshalls.h" -#include "map.h" -#include "print_string.h" const char *GDScriptTokenizer::token_names[TK_MAX] = { "Empty", @@ -91,6 +91,7 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = { "match", "func", "class", + "class_name", "extends", "is", "onready", @@ -100,6 +101,8 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = { "setget", "const", "var", + "as", + "void", "enum", "preload", "assert", @@ -109,10 +112,11 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = { "rpc", "sync", "master", + "puppet", "slave", "remotesync", "mastersync", - "slavesync", + "puppetsync", "'['", "']'", "'{'", @@ -125,6 +129,7 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = { "'?'", "':'", "'$'", + "'->'", "'\\n'", "PI", "TAU", @@ -187,6 +192,7 @@ static const _kws _keyword_list[] = { //func { GDScriptTokenizer::TK_PR_FUNCTION, "func" }, { GDScriptTokenizer::TK_PR_CLASS, "class" }, + { GDScriptTokenizer::TK_PR_CLASS_NAME, "class_name" }, { GDScriptTokenizer::TK_PR_EXTENDS, "extends" }, { GDScriptTokenizer::TK_PR_IS, "is" }, { GDScriptTokenizer::TK_PR_ONREADY, "onready" }, @@ -195,6 +201,8 @@ static const _kws _keyword_list[] = { { GDScriptTokenizer::TK_PR_EXPORT, "export" }, { GDScriptTokenizer::TK_PR_SETGET, "setget" }, { GDScriptTokenizer::TK_PR_VAR, "var" }, + { GDScriptTokenizer::TK_PR_AS, "as" }, + { GDScriptTokenizer::TK_PR_VOID, "void" }, { GDScriptTokenizer::TK_PR_PRELOAD, "preload" }, { GDScriptTokenizer::TK_PR_ASSERT, "assert" }, { GDScriptTokenizer::TK_PR_YIELD, "yield" }, @@ -203,10 +211,11 @@ static const _kws _keyword_list[] = { { GDScriptTokenizer::TK_PR_REMOTE, "remote" }, { GDScriptTokenizer::TK_PR_MASTER, "master" }, { GDScriptTokenizer::TK_PR_SLAVE, "slave" }, + { GDScriptTokenizer::TK_PR_PUPPET, "puppet" }, { GDScriptTokenizer::TK_PR_SYNC, "sync" }, { GDScriptTokenizer::TK_PR_REMOTESYNC, "remotesync" }, { GDScriptTokenizer::TK_PR_MASTERSYNC, "mastersync" }, - { GDScriptTokenizer::TK_PR_SLAVESYNC, "slavesync" }, + { GDScriptTokenizer::TK_PR_PUPPETSYNC, "puppetsync" }, { GDScriptTokenizer::TK_PR_CONST, "const" }, { GDScriptTokenizer::TK_PR_ENUM, "enum" }, //controlflow @@ -251,11 +260,11 @@ bool GDScriptTokenizer::is_token_literal(int p_offset, bool variable_safe) const case TK_PR_SIGNAL: case TK_PR_REMOTE: case TK_PR_MASTER: - case TK_PR_SLAVE: + case TK_PR_PUPPET: case TK_PR_SYNC: case TK_PR_REMOTESYNC: case TK_PR_MASTERSYNC: - case TK_PR_SLAVESYNC: + case TK_PR_PUPPETSYNC: return true; // Literal for non-variables only: @@ -519,8 +528,13 @@ void GDScriptTokenizerText::_advance() { return; } case '#': { // line comment skip - +#ifdef DEBUG_ENABLED + String comment; +#endif // DEBUG_ENABLED while (GETCHAR(0) != '\n') { +#ifdef DEBUG_ENABLED + comment += GETCHAR(0); +#endif // DEBUG_ENABLED code_pos++; if (GETCHAR(0) == 0) { //end of file //_make_error("Unterminated Comment"); @@ -528,6 +542,17 @@ void GDScriptTokenizerText::_advance() { return; } } +#ifdef DEBUG_ENABLED + if (comment.begins_with("#warning-ignore:")) { + String code = comment.get_slice(":", 1); + warning_skips.push_back(Pair<int, String>(line, code.strip_edges().to_lower())); + } else if (comment.begins_with("#warning-ignore-all:")) { + String code = comment.get_slice(":", 1); + warning_global_skips.insert(code.strip_edges().to_lower()); + } else if (comment.strip_edges() == "#warnings-disable") { + ignore_warnings = true; + } +#endif // DEBUG_ENABLED INCPOS(1); column = 1; line++; @@ -705,11 +730,9 @@ void GDScriptTokenizerText::_advance() { if (GETCHAR(1) == '=') { _make_token(TK_OP_ASSIGN_SUB); INCPOS(1); - /* - } else if (GETCHAR(1)=='-') { - _make_token(TK_OP_MINUS_MINUS); + } else if (GETCHAR(1) == '>') { + _make_token(TK_FORWARD_ARROW); INCPOS(1); - */ } else { _make_token(TK_OP_SUB); } @@ -916,7 +939,6 @@ void GDScriptTokenizerText::_advance() { _make_constant(val); } else if (period_found || exponent_found) { double val = str.to_double(); - //print_line("*%*%*%*% to convert: "+str+" result: "+rtos(val)); _make_constant(val); } else { int64_t val = str.to_int64(); @@ -1040,6 +1062,9 @@ void GDScriptTokenizerText::set_code(const String &p_code) { column = 1; //the same holds for columns tk_rb_pos = 0; error_flag = false; +#ifdef DEBUG_ENABLED + ignore_warnings = false; +#endif // DEBUG_ENABLED last_error = ""; for (int i = 0; i < MAX_LOOKAHEAD + 1; i++) _advance(); @@ -1135,9 +1160,9 @@ void GDScriptTokenizerText::advance(int p_amount) { _advance(); } - ////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////// -#define BYTECODE_VERSION 12 +#define BYTECODE_VERSION 13 Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) { @@ -1167,15 +1192,15 @@ Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) Vector<uint8_t> cs; cs.resize(len); for (int j = 0; j < len; j++) { - cs[j] = b[j] ^ 0xb6; + cs.write[j] = b[j] ^ 0xb6; } - cs[cs.size() - 1] = 0; + cs.write[cs.size() - 1] = 0; String s; s.parse_utf8((const char *)cs.ptr()); b += len; total_len -= len + 4; - identifiers[i] = s; + identifiers.write[i] = s; } constants.resize(constant_count); @@ -1188,7 +1213,7 @@ Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) return err; b += len; total_len -= len; - constants[i] = v; + constants.write[i] = v; } ERR_FAIL_COND_V(line_count * 8 > total_len, ERR_INVALID_DATA); @@ -1213,10 +1238,10 @@ Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) if ((*b) & TOKEN_BYTE_MASK) { //little endian always ERR_FAIL_COND_V(total_len < 4, ERR_INVALID_DATA); - tokens[i] = decode_uint32(b) & ~TOKEN_BYTE_MASK; + tokens.write[i] = decode_uint32(b) & ~TOKEN_BYTE_MASK; b += 4; } else { - tokens[i] = *b; + tokens.write[i] = *b; b += 1; total_len--; } @@ -1315,15 +1340,15 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code) //save header buf.resize(24); - buf[0] = 'G'; - buf[1] = 'D'; - buf[2] = 'S'; - buf[3] = 'C'; - encode_uint32(BYTECODE_VERSION, &buf[4]); - encode_uint32(identifier_map.size(), &buf[8]); - encode_uint32(constant_map.size(), &buf[12]); - encode_uint32(line_map.size(), &buf[16]); - encode_uint32(token_array.size(), &buf[20]); + buf.write[0] = 'G'; + buf.write[1] = 'D'; + buf.write[2] = 'S'; + buf.write[3] = 'C'; + encode_uint32(BYTECODE_VERSION, &buf.write[4]); + encode_uint32(identifier_map.size(), &buf.write[8]); + encode_uint32(constant_map.size(), &buf.write[12]); + encode_uint32(line_map.size(), &buf.write[16]); + encode_uint32(token_array.size(), &buf.write[20]); //save identifiers @@ -1355,7 +1380,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code) ERR_FAIL_COND_V(err != OK, Vector<uint8_t>()); int pos = buf.size(); buf.resize(pos + len); - encode_variant(E->get(), &buf[pos], len); + encode_variant(E->get(), &buf.write[pos], len); } for (Map<int, uint32_t>::Element *E = rev_line_map.front(); E; E = E->next()) { diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index c4f1f9fd94..cc894fb101 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -31,11 +31,12 @@ #ifndef GDSCRIPT_TOKENIZER_H #define GDSCRIPT_TOKENIZER_H +#include "core/pair.h" +#include "core/string_db.h" +#include "core/ustring.h" +#include "core/variant.h" +#include "core/vmap.h" #include "gdscript_functions.h" -#include "string_db.h" -#include "ustring.h" -#include "variant.h" -#include "vmap.h" class GDScriptTokenizer { public: @@ -96,6 +97,7 @@ public: TK_CF_MATCH, TK_PR_FUNCTION, TK_PR_CLASS, + TK_PR_CLASS_NAME, TK_PR_EXTENDS, TK_PR_IS, TK_PR_ONREADY, @@ -105,6 +107,8 @@ public: TK_PR_SETGET, TK_PR_CONST, TK_PR_VAR, + TK_PR_AS, + TK_PR_VOID, TK_PR_ENUM, TK_PR_PRELOAD, TK_PR_ASSERT, @@ -115,9 +119,10 @@ public: TK_PR_SYNC, TK_PR_MASTER, TK_PR_SLAVE, + TK_PR_PUPPET, TK_PR_REMOTESYNC, TK_PR_MASTERSYNC, - TK_PR_SLAVESYNC, + TK_PR_PUPPETSYNC, TK_BRACKET_OPEN, TK_BRACKET_CLOSE, TK_CURLY_BRACKET_OPEN, @@ -130,6 +135,7 @@ public: TK_QUESTION_MARK, TK_COLON, TK_DOLLAR, + TK_FORWARD_ARROW, TK_NEWLINE, TK_CONST_PI, TK_CONST_TAU, @@ -167,6 +173,11 @@ public: virtual int get_token_line_indent(int p_offset = 0) const = 0; virtual String get_token_error(int p_offset = 0) const = 0; virtual void advance(int p_amount = 1) = 0; +#ifdef DEBUG_ENABLED + virtual const Vector<Pair<int, String> > &get_warning_skips() const = 0; + virtual const Set<String> &get_warning_global_skips() const = 0; + virtual const bool is_ignoring_warnings() const = 0; +#endif // DEBUG_ENABLED virtual ~GDScriptTokenizer(){}; }; @@ -186,6 +197,7 @@ class GDScriptTokenizerText : public GDScriptTokenizer { union { Variant::Type vtype; //for type types GDScriptFunctions::Function func; //function for built in functions + int warning_code; //for warning skip }; int line, col; TokenData() { @@ -213,6 +225,11 @@ class GDScriptTokenizerText : public GDScriptTokenizer { int tk_rb_pos; String last_error; bool error_flag; +#ifdef DEBUG_ENABLED + Vector<Pair<int, String> > warning_skips; + Set<String> warning_global_skips; + bool ignore_warnings; +#endif // DEBUG_ENABLED void _advance(); @@ -228,6 +245,11 @@ public: virtual const Variant &get_token_constant(int p_offset = 0) const; virtual String get_token_error(int p_offset = 0) const; virtual void advance(int p_amount = 1); +#ifdef DEBUG_ENABLED + virtual const Vector<Pair<int, String> > &get_warning_skips() const { return warning_skips; } + virtual const Set<String> &get_warning_global_skips() const { return warning_global_skips; } + virtual const bool is_ignoring_warnings() const { return ignore_warnings; } +#endif // DEBUG_ENABLED }; class GDScriptTokenizerBuffer : public GDScriptTokenizer { @@ -261,6 +283,17 @@ public: virtual const Variant &get_token_constant(int p_offset = 0) const; virtual String get_token_error(int p_offset = 0) const; virtual void advance(int p_amount = 1); +#ifdef DEBUG_ENABLED + virtual const Vector<Pair<int, String> > &get_warning_skips() const { + static Vector<Pair<int, String> > v; + return v; + } + virtual const Set<String> &get_warning_global_skips() const { + static Set<String> s; + return s; + } + virtual const bool is_ignoring_warnings() const { return true; } +#endif // DEBUG_ENABLED GDScriptTokenizerBuffer(); }; diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 422223370b..26dcbdcf89 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -30,12 +30,12 @@ #include "register_types.h" +#include "core/io/file_access_encrypted.h" +#include "core/io/resource_loader.h" +#include "core/os/file_access.h" #include "editor/gdscript_highlighter.h" #include "gdscript.h" #include "gdscript_tokenizer.h" -#include "io/file_access_encrypted.h" -#include "io/resource_loader.h" -#include "os/file_access.h" GDScriptLanguage *script_language_gd = NULL; ResourceFormatLoaderGDScript *resource_loader_gd = NULL; diff --git a/modules/gridmap/SCsub b/modules/gridmap/SCsub index 2ffe15cd33..62b8a0ff93 100644 --- a/modules/gridmap/SCsub +++ b/modules/gridmap/SCsub @@ -6,5 +6,3 @@ Import('env_modules') env_gridmap = env_modules.Clone() env_gridmap.add_source_files(env.modules_sources, "*.cpp") - -Export('env') diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index d5f9563600..9b9088dd82 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -10,7 +10,7 @@ A GridMap is split into a sparse collection of octants for efficient rendering and physics processing. Every octant has the same dimensions and can contain several cells. </description> <tutorials> - <link>http://docs.godotengine.org/en/3.0/tutorials/3d/using_gridmaps.html</link> + <link>https://docs.godotengine.org/en/latest/tutorials/3d/using_gridmaps.html</link> </tutorials> <demos> </demos> @@ -65,7 +65,7 @@ <argument index="2" name="z" type="int"> </argument> <description> - The orientation of the cell at the grid-based X, Y and Z coordinates. -1 is retuned if the cell is empty. + The orientation of the cell at the grid-based X, Y and Z coordinates. -1 is returned if the cell is empty. </description> </method> <method name="get_collision_layer_bit" qualifiers="const"> @@ -212,9 +212,12 @@ </member> <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask"> </member> - <member name="theme" type="MeshLibrary" setter="set_theme" getter="get_theme"> + <member name="mesh_library" type="MeshLibrary" setter="set_mesh_library" getter="get_mesh_library"> The assigned [MeshLibrary]. </member> + <member name="theme" type="MeshLibrary" setter="set_theme" getter="get_theme"> + Deprecated, use [member mesh_library] instead. + </member> </members> <constants> <constant name="INVALID_CELL_ITEM" value="-1"> diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 234a59e516..274a2f0249 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -29,37 +29,19 @@ /*************************************************************************/ #include "grid_map.h" -#include "message_queue.h" -#include "scene/3d/light.h" -#include "scene/resources/surface_tool.h" -#include "servers/visual_server.h" -#include "io/marshalls.h" -#include "os/os.h" +#include "core/io/marshalls.h" +#include "core/message_queue.h" +#include "scene/3d/light.h" #include "scene/resources/mesh_library.h" +#include "scene/resources/surface_tool.h" #include "scene/scene_string_names.h" +#include "servers/visual_server.h" bool GridMap::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; - /* } else if (name=="cells") { - PoolVector<int> cells = p_value; - int amount=cells.size(); - PoolVector<int>::Read r = cells.read(); - ERR_FAIL_COND_V(amount&1,false); // not even - cell_map.clear(); - for(int i=0;i<amount/3;i++) { - - - IndexKey ik; - ik.key=decode_uint64(&r[i*3]); - Cell cell; - cell.cell=uint32_t(r[i*+1]); - cell_map[ik]=cell; - - } - _recreate_octant_data();*/ if (name == "data") { Dictionary d = p_value; @@ -80,7 +62,9 @@ bool GridMap::_set(const StringName &p_name, const Variant &p_value) { cell_map[ik] = cell; } } + _recreate_octant_data(); + } else if (name == "baked_meshes") { clear_baked_meshes(); @@ -103,8 +87,9 @@ bool GridMap::_set(const StringName &p_name, const Variant &p_value) { _recreate_octant_data(); - } else + } else { return false; + } return true; } @@ -208,21 +193,39 @@ bool GridMap::get_collision_layer_bit(int p_bit) const { return get_collision_layer() & (1 << p_bit); } +#ifndef DISABLE_DEPRECATED void GridMap::set_theme(const Ref<MeshLibrary> &p_theme) { - if (!theme.is_null()) - theme->unregister_owner(this); - theme = p_theme; - if (!theme.is_null()) - theme->register_owner(this); + ERR_EXPLAIN("GridMap.theme/set_theme() is deprecated and will be removed in a future version. Use GridMap.mesh_library/set_mesh_library() instead."); + WARN_DEPRECATED - _recreate_octant_data(); - _change_notify("theme"); + set_mesh_library(p_theme); } Ref<MeshLibrary> GridMap::get_theme() const { - return theme; + ERR_EXPLAIN("GridMap.theme/get_theme() is deprecated and will be removed in a future version. Use GridMap.mesh_library/get_mesh_library() instead."); + WARN_DEPRECATED + + return get_mesh_library(); +} +#endif // DISABLE_DEPRECATED + +void GridMap::set_mesh_library(const Ref<MeshLibrary> &p_mesh_library) { + + if (!mesh_library.is_null()) + mesh_library->unregister_owner(this); + mesh_library = p_mesh_library; + if (!mesh_library.is_null()) + mesh_library->register_owner(this); + + _recreate_octant_data(); + _change_notify("mesh_library"); +} + +Ref<MeshLibrary> GridMap::get_mesh_library() const { + + return mesh_library; } void GridMap::set_cell_size(const Vector3 &p_size) { @@ -469,11 +472,9 @@ bool GridMap::_octant_update(const OctantKey &p_key) { ERR_CONTINUE(!cell_map.has(E->get())); const Cell &c = cell_map[E->get()]; - if (!theme.is_valid() || !theme->has_item(c.item)) + if (!mesh_library.is_valid() || !mesh_library->has_item(c.item)) continue; - //print_line("OCTANT, CELLS: "+itos(ii.cells.size())); - Vector3 cellpos = Vector3(E->get().x, E->get().y, E->get().z); Vector3 ofs = _get_offset(); @@ -488,7 +489,7 @@ bool GridMap::_octant_update(const OctantKey &p_key) { xform.set_origin(cellpos * cell_size + ofs); xform.basis.scale(Vector3(cell_scale, cell_scale, cell_scale)); if (baked_meshes.size() == 0) { - if (theme->get_item_mesh(c.item).is_valid()) { + if (mesh_library->get_item_mesh(c.item).is_valid()) { if (!multimesh_items.has(c.item)) { multimesh_items[c.item] = List<Pair<Transform, IndexKey> >(); } @@ -500,7 +501,7 @@ bool GridMap::_octant_update(const OctantKey &p_key) { } } - Vector<MeshLibrary::ShapeData> shapes = theme->get_item_shapes(c.item); + Vector<MeshLibrary::ShapeData> shapes = mesh_library->get_item_shapes(c.item); // add the item's shape at given xform to octant's static_body for (int i = 0; i < shapes.size(); i++) { // add the item's shape @@ -508,14 +509,12 @@ bool GridMap::_octant_update(const OctantKey &p_key) { continue; PhysicsServer::get_singleton()->body_add_shape(g.static_body, shapes[i].shape->get_rid(), xform * shapes[i].local_transform); if (g.collision_debug.is_valid()) { - shapes[i].shape->add_vertices_to_array(col_debug, xform * shapes[i].local_transform); + shapes.write[i].shape->add_vertices_to_array(col_debug, xform * shapes[i].local_transform); } - - //print_line("PHIS x: "+xform); } // add the item's navmesh at given xform to GridMap's Navigation ancestor - Ref<NavigationMesh> navmesh = theme->get_item_navmesh(c.item); + Ref<NavigationMesh> navmesh = mesh_library->get_item_navmesh(c.item); if (navmesh.is_valid()) { Octant::NavMesh nm; nm.xform = xform; @@ -537,7 +536,7 @@ bool GridMap::_octant_update(const OctantKey &p_key) { RID mm = VS::get_singleton()->multimesh_create(); VS::get_singleton()->multimesh_allocate(mm, E->get().size(), VS::MULTIMESH_TRANSFORM_3D, VS::MULTIMESH_COLOR_NONE); - VS::get_singleton()->multimesh_set_mesh(mm, theme->get_item_mesh(E->key())->get_rid()); + VS::get_singleton()->multimesh_set_mesh(mm, mesh_library->get_item_mesh(E->key())->get_rid()); int idx = 0; for (List<Pair<Transform, IndexKey> >::Element *F = E->get().front(); F; F = F->next()) { @@ -600,7 +599,6 @@ void GridMap::_octant_enter_world(const OctantKey &p_key) { Octant &g = *octant_map[p_key]; PhysicsServer::get_singleton()->body_set_state(g.static_body, PhysicsServer::BODY_STATE_TRANSFORM, get_global_transform()); PhysicsServer::get_singleton()->body_set_space(g.static_body, get_world()->get_space()); - //print_line("BODYPOS: "+get_global_transform()); if (g.collision_debug_instance.is_valid()) { VS::get_singleton()->instance_set_scenario(g.collision_debug_instance, get_world()->get_scenario()); @@ -612,11 +610,11 @@ void GridMap::_octant_enter_world(const OctantKey &p_key) { VS::get_singleton()->instance_set_transform(g.multimesh_instances[i].instance, get_global_transform()); } - if (navigation && theme.is_valid()) { + if (navigation && mesh_library.is_valid()) { for (Map<IndexKey, Octant::NavMesh>::Element *F = g.navmesh_ids.front(); F; F = F->next()) { if (cell_map.has(F->key()) && F->get().id < 0) { - Ref<NavigationMesh> nm = theme->get_item_navmesh(cell_map[F->key()].item); + Ref<NavigationMesh> nm = mesh_library->get_item_navmesh(cell_map[F->key()].item); if (nm.is_valid()) { F->get().id = navigation->navmesh_add(nm, F->get().xform, this); } @@ -758,7 +756,7 @@ void GridMap::_update_visibility() { for (Map<OctantKey, Octant *>::Element *e = octant_map.front(); e; e = e->next()) { Octant *octant = e->value(); for (int i = 0; i < octant->multimesh_instances.size(); i++) { - Octant::MultimeshInstance &mi = octant->multimesh_instances[i]; + const Octant::MultimeshInstance &mi = octant->multimesh_instances[i]; VS::get_singleton()->instance_set_visible(mi.instance, is_visible()); } } @@ -846,8 +844,13 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &GridMap::set_collision_layer_bit); ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &GridMap::get_collision_layer_bit); +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_theme", "theme"), &GridMap::set_theme); ClassDB::bind_method(D_METHOD("get_theme"), &GridMap::get_theme); +#endif // DISABLE_DEPRECATED + + ClassDB::bind_method(D_METHOD("set_mesh_library", "mesh_library"), &GridMap::set_mesh_library); + ClassDB::bind_method(D_METHOD("get_mesh_library"), &GridMap::get_mesh_library); ClassDB::bind_method(D_METHOD("set_cell_size", "size"), &GridMap::set_cell_size); ClassDB::bind_method(D_METHOD("get_cell_size"), &GridMap::get_cell_size); @@ -865,7 +868,6 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("world_to_map", "pos"), &GridMap::world_to_map); ClassDB::bind_method(D_METHOD("map_to_world", "x", "y", "z"), &GridMap::map_to_world); - //ClassDB::bind_method(D_METHOD("_recreate_octants"),&GridMap::_recreate_octants); ClassDB::bind_method(D_METHOD("_update_octants_callback"), &GridMap::_update_octants_callback); ClassDB::bind_method(D_METHOD("resource_changed", "resource"), &GridMap::resource_changed); @@ -889,7 +891,11 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_baked_meshes"), &GridMap::clear_baked_meshes); ClassDB::bind_method(D_METHOD("make_baked_meshes", "gen_lightmap_uv", "lightmap_uv_texel_size"), &GridMap::make_baked_meshes, DEFVAL(false), DEFVAL(0.1)); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "MeshLibrary"), "set_theme", "get_theme"); +#ifndef DISABLE_DEPRECATED + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "MeshLibrary", 0), "set_theme", "get_theme"); +#endif // DISABLE_DEPRECATED + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh_library", PROPERTY_HINT_RESOURCE_TYPE, "MeshLibrary"), "set_mesh_library", "get_mesh_library"); ADD_GROUP("Cell", "cell_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "cell_size"), "set_cell_size", "get_cell_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_octant_size", PROPERTY_HINT_RANGE, "1,1024,1"), "set_octant_size", "get_octant_size"); @@ -952,7 +958,7 @@ Array GridMap::get_used_cells() const { Array GridMap::get_meshes() { - if (theme.is_null()) + if (mesh_library.is_null()) return Array(); Vector3 ofs = _get_offset(); @@ -961,9 +967,9 @@ Array GridMap::get_meshes() { for (Map<IndexKey, Cell>::Element *E = cell_map.front(); E; E = E->next()) { int id = E->get().item; - if (!theme->has_item(id)) + if (!mesh_library->has_item(id)) continue; - Ref<Mesh> mesh = theme->get_item_mesh(id); + Ref<Mesh> mesh = mesh_library->get_item_mesh(id); if (mesh.is_null()) continue; @@ -1004,7 +1010,7 @@ void GridMap::clear_baked_meshes() { void GridMap::make_baked_meshes(bool p_gen_lightmap_uv, float p_lightmap_uv_texel_size) { - if (!theme.is_valid()) + if (!mesh_library.is_valid()) return; //generate @@ -1015,10 +1021,10 @@ void GridMap::make_baked_meshes(bool p_gen_lightmap_uv, float p_lightmap_uv_texe IndexKey key = E->key(); int item = E->get().item; - if (!theme->has_item(item)) + if (!mesh_library->has_item(item)) continue; - Ref<Mesh> mesh = theme->get_item_mesh(item); + Ref<Mesh> mesh = mesh_library->get_item_mesh(item); if (!mesh.is_valid()) continue; @@ -1060,11 +1066,8 @@ void GridMap::make_baked_meshes(bool p_gen_lightmap_uv, float p_lightmap_uv_texe } } - int ofs = 0; - for (Map<OctantKey, Map<Ref<Material>, Ref<SurfaceTool> > >::Element *E = surface_map.front(); E; E = E->next()) { - print_line("generating mesh " + itos(ofs++) + "/" + itos(surface_map.size())); Ref<ArrayMesh> mesh; mesh.instance(); for (Map<Ref<Material>, Ref<SurfaceTool> >::Element *F = E->get().front(); F; F = F->next()) { @@ -1137,8 +1140,8 @@ GridMap::GridMap() { GridMap::~GridMap() { - if (!theme.is_null()) - theme->unregister_owner(this); + if (!mesh_library.is_null()) + mesh_library->unregister_owner(this); clear(); } diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index ed36751fc8..3d8be5c9c7 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -157,7 +157,7 @@ class GridMap : public Spatial { Vector3::Axis clip_axis; - Ref<MeshLibrary> theme; + Ref<MeshLibrary> mesh_library; Map<OctantKey, Octant *> octant_map; Map<IndexKey, Cell> cell_map; @@ -227,8 +227,13 @@ public: void set_collision_mask_bit(int p_bit, bool p_value); bool get_collision_mask_bit(int p_bit) const; +#ifndef DISABLE_DEPRECATED void set_theme(const Ref<MeshLibrary> &p_theme); Ref<MeshLibrary> get_theme() const; +#endif // DISABLE_DEPRECATED + + void set_mesh_library(const Ref<MeshLibrary> &p_mesh_library); + Ref<MeshLibrary> get_mesh_library() const; void set_cell_size(const Vector3 &p_size); Vector3 get_cell_size() const; diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index fc5972c810..126b49832a 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -29,21 +29,21 @@ /*************************************************************************/ #include "grid_map_editor_plugin.h" +#include "core/os/input.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/plugins/spatial_editor_plugin.h" -#include "os/input.h" #include "scene/3d/camera.h" -#include "geometry.h" -#include "os/keyboard.h" +#include "core/math/geometry.h" +#include "core/os/keyboard.h" void GridMapEditor::_node_removed(Node *p_node) { if (p_node == node) { node = NULL; hide(); - theme_pallete->hide(); + mesh_library_palette->hide(); } } @@ -320,12 +320,12 @@ bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, boo if (!spatial_editor) return false; - if (selected_pallete < 0 && input_action != INPUT_COPY && input_action != INPUT_SELECT && input_action != INPUT_DUPLICATE) + if (selected_palette < 0 && input_action != INPUT_COPY && input_action != INPUT_SELECT && input_action != INPUT_DUPLICATE) return false; - Ref<MeshLibrary> theme = node->get_theme(); - if (theme.is_null()) + Ref<MeshLibrary> mesh_library = node->get_mesh_library(); + if (mesh_library.is_null()) return false; - if (input_action != INPUT_COPY && input_action != INPUT_SELECT && input_action != INPUT_DUPLICATE && !theme->has_item(selected_pallete)) + if (input_action != INPUT_COPY && input_action != INPUT_SELECT && input_action != INPUT_DUPLICATE && !mesh_library->has_item(selected_palette)) return false; Camera *camera = p_camera; @@ -407,9 +407,9 @@ bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, boo int item = node->get_cell_item(cell[0], cell[1], cell[2]); if (item >= 0) { - selected_pallete = item; - theme_pallete->set_current(item); - update_pallete(); + selected_palette = item; + mesh_library_palette->set_current(item); + update_palette(); _update_cursor_instance(); } return true; @@ -417,12 +417,12 @@ bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, boo if (input_action == INPUT_PAINT) { SetItem si; si.pos = Vector3(cell[0], cell[1], cell[2]); - si.new_value = selected_pallete; + si.new_value = selected_palette; si.new_orientation = cursor_rot; si.old_value = node->get_cell_item(cell[0], cell[1], cell[2]); si.old_orientation = node->get_cell_item_orientation(cell[0], cell[1], cell[2]); set_items.push_back(si); - node->set_cell_item(cell[0], cell[1], cell[2], selected_pallete, cursor_rot); + node->set_cell_item(cell[0], cell[1], cell[2], selected_palette, cursor_rot); return true; } else if (input_action == INPUT_ERASE) { SetItem si; @@ -474,7 +474,7 @@ void GridMapEditor::_fill_selection() { for (int k = selection.begin.z; k <= selection.end.z; k++) { - undo_redo->add_do_method(node, "set_cell_item", i, j, k, selected_pallete, cursor_rot); + undo_redo->add_do_method(node, "set_cell_item", i, j, k, selected_palette, cursor_rot); undo_redo->add_undo_method(node, "set_cell_item", i, j, k, node->get_cell_item(i, j, k), node->get_cell_item_orientation(i, j, k)); } } @@ -597,29 +597,31 @@ bool GridMapEditor::forward_spatial_input_event(Camera *p_camera, const Ref<Inpu if (mb->get_button_index() == BUTTON_LEFT) { if (input_action == INPUT_DUPLICATE) { - //paste _duplicate_paste(); input_action = INPUT_NONE; _update_duplicate_indicator(); } else if (mb->get_shift()) { input_action = INPUT_SELECT; - } else if (mb->get_command()) + } else if (mb->get_command()) { input_action = INPUT_COPY; - else { + } else { input_action = INPUT_PAINT; set_items.clear(); } - } else if (mb->get_button_index() == BUTTON_RIGHT) + } else if (mb->get_button_index() == BUTTON_RIGHT) { if (input_action == INPUT_DUPLICATE) { - input_action = INPUT_NONE; _update_duplicate_indicator(); } else if (mb->get_shift()) { input_action = INPUT_ERASE; set_items.clear(); - } else + } else { return false; + } + } else { + return false; + } return do_input_action(p_camera, Point2(mb->get_position().x, mb->get_position().y), true); } else { @@ -645,7 +647,7 @@ bool GridMapEditor::forward_spatial_input_event(Camera *p_camera, const Ref<Inpu } set_items.clear(); input_action = INPUT_NONE; - return true; + return set_items.size() > 0; } if (mb->get_button_index() == BUTTON_LEFT && input_action != INPUT_NONE) { @@ -712,42 +714,42 @@ void GridMapEditor::_set_display_mode(int p_mode) { display_mode = p_mode; - update_pallete(); + update_palette(); } -void GridMapEditor::update_pallete() { - int selected = theme_pallete->get_current(); +void GridMapEditor::update_palette() { + int selected = mesh_library_palette->get_current(); - theme_pallete->clear(); + mesh_library_palette->clear(); if (display_mode == DISPLAY_THUMBNAIL) { - theme_pallete->set_max_columns(0); - theme_pallete->set_icon_mode(ItemList::ICON_MODE_TOP); + mesh_library_palette->set_max_columns(0); + mesh_library_palette->set_icon_mode(ItemList::ICON_MODE_TOP); } else if (display_mode == DISPLAY_LIST) { - theme_pallete->set_max_columns(1); - theme_pallete->set_icon_mode(ItemList::ICON_MODE_LEFT); + mesh_library_palette->set_max_columns(1); + mesh_library_palette->set_icon_mode(ItemList::ICON_MODE_LEFT); } float min_size = EDITOR_DEF("editors/grid_map/preview_size", 64); - theme_pallete->set_fixed_icon_size(Size2(min_size, min_size)); - theme_pallete->set_fixed_column_width(min_size * 3 / 2); - theme_pallete->set_max_text_lines(2); + mesh_library_palette->set_fixed_icon_size(Size2(min_size, min_size)); + mesh_library_palette->set_fixed_column_width(min_size * 3 / 2); + mesh_library_palette->set_max_text_lines(2); - Ref<MeshLibrary> theme = node->get_theme(); + Ref<MeshLibrary> mesh_library = node->get_mesh_library(); - if (theme.is_null()) { - last_theme = NULL; + if (mesh_library.is_null()) { + last_mesh_library = NULL; return; } Vector<int> ids; - ids = theme->get_item_list(); + ids = mesh_library->get_item_list(); List<_CGMEItemSort> il; for (int i = 0; i < ids.size(); i++) { _CGMEItemSort is; is.id = ids[i]; - is.name = theme->get_item_name(ids[i]); + is.name = mesh_library->get_item_name(ids[i]); il.push_back(is); } il.sort(); @@ -757,28 +759,28 @@ void GridMapEditor::update_pallete() { for (List<_CGMEItemSort>::Element *E = il.front(); E; E = E->next()) { int id = E->get().id; - theme_pallete->add_item(""); + mesh_library_palette->add_item(""); - String name = theme->get_item_name(id); - Ref<Texture> preview = theme->get_item_preview(id); + String name = mesh_library->get_item_name(id); + Ref<Texture> preview = mesh_library->get_item_preview(id); if (!preview.is_null()) { - theme_pallete->set_item_icon(item, preview); - theme_pallete->set_item_tooltip(item, name); + mesh_library_palette->set_item_icon(item, preview); + mesh_library_palette->set_item_tooltip(item, name); } if (name != "") { - theme_pallete->set_item_text(item, name); + mesh_library_palette->set_item_text(item, name); } - theme_pallete->set_item_metadata(item, id); + mesh_library_palette->set_item_metadata(item, id); item++; } if (selected != -1) { - theme_pallete->select(selected); + mesh_library_palette->select(selected); } - last_theme = theme.operator->(); + last_mesh_library = mesh_library.operator->(); } void GridMapEditor::edit(GridMap *p_gridmap) { @@ -800,12 +802,14 @@ void GridMapEditor::edit(GridMap *p_gridmap) { VisualServer::get_singleton()->instance_set_visible(grid_instance[i], false); } - VisualServer::get_singleton()->instance_set_visible(cursor_instance, false); + if (cursor_instance.is_valid()) { + VisualServer::get_singleton()->instance_set_visible(cursor_instance, false); + } return; } - update_pallete(); + update_palette(); set_process(true); @@ -914,7 +918,7 @@ void GridMapEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - theme_pallete->connect("item_selected", this, "_item_selected_cbk"); + mesh_library_palette->connect("item_selected", this, "_item_selected_cbk"); for (int i = 0; i < 3; i++) { grid[i] = VS::get_singleton()->mesh_create(); @@ -959,9 +963,9 @@ void GridMapEditor::_notification(int p_what) { } grid_xform = xf; } - Ref<MeshLibrary> cgmt = node->get_theme(); - if (cgmt.operator->() != last_theme) - update_pallete(); + Ref<MeshLibrary> cgmt = node->get_mesh_library(); + if (cgmt.operator->() != last_mesh_library) + update_palette(); if (lock_view) { @@ -994,10 +998,10 @@ void GridMapEditor::_update_cursor_instance() { VisualServer::get_singleton()->free(cursor_instance); cursor_instance = RID(); - if (selected_pallete >= 0) { + if (selected_palette >= 0) { - if (node && !node->get_theme().is_null()) { - Ref<Mesh> mesh = node->get_theme()->get_item_mesh(selected_pallete); + if (node && !node->get_mesh_library().is_null()) { + Ref<Mesh> mesh = node->get_mesh_library()->get_item_mesh(selected_palette); if (!mesh.is_null() && mesh->get_rid().is_valid()) { cursor_instance = VisualServer::get_singleton()->instance_create2(mesh->get_rid(), get_tree()->get_root()->get_world()->get_scenario()); @@ -1008,7 +1012,7 @@ void GridMapEditor::_update_cursor_instance() { } void GridMapEditor::_item_selected_cbk(int idx) { - selected_pallete = theme_pallete->get_item_metadata(idx); + selected_palette = mesh_library_palette->get_item_metadata(idx); _update_cursor_instance(); } @@ -1146,9 +1150,9 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { display_mode = DISPLAY_THUMBNAIL; - theme_pallete = memnew(ItemList); - add_child(theme_pallete); - theme_pallete->set_v_size_flags(SIZE_EXPAND_FILL); + mesh_library_palette = memnew(ItemList); + add_child(mesh_library_palette); + mesh_library_palette->set_v_size_flags(SIZE_EXPAND_FILL); edit_axis = Vector3::AXIS_Y; edit_floor[0] = -1; @@ -1156,7 +1160,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { edit_floor[2] = -1; cursor_visible = false; - selected_pallete = -1; + selected_palette = -1; lock_view = false; cursor_rot = 0; last_mouseover = Vector3(-1, -1, -1); @@ -1315,9 +1319,24 @@ GridMapEditor::~GridMapEditor() { VisualServer::get_singleton()->free(duplicate_instance); } +void GridMapEditorPlugin::_notification(int p_what) { + + if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { + + switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) { + case 0: { // Left. + SpatialEditor::get_singleton()->get_palette_split()->move_child(grid_map_editor, 0); + } break; + case 1: { // Right. + SpatialEditor::get_singleton()->get_palette_split()->move_child(grid_map_editor, 1); + } break; + } + } +} + void GridMapEditorPlugin::edit(Object *p_object) { - gridmap_editor->edit(Object::cast_to<GridMap>(p_object)); + grid_map_editor->edit(Object::cast_to<GridMap>(p_object)); } bool GridMapEditorPlugin::handles(Object *p_object) const { @@ -1328,29 +1347,35 @@ bool GridMapEditorPlugin::handles(Object *p_object) const { void GridMapEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - gridmap_editor->show(); - gridmap_editor->spatial_editor_hb->show(); - gridmap_editor->set_process(true); + grid_map_editor->show(); + grid_map_editor->spatial_editor_hb->show(); + grid_map_editor->set_process(true); } else { - gridmap_editor->spatial_editor_hb->hide(); - gridmap_editor->hide(); - gridmap_editor->edit(NULL); - gridmap_editor->set_process(false); + grid_map_editor->spatial_editor_hb->hide(); + grid_map_editor->hide(); + grid_map_editor->edit(NULL); + grid_map_editor->set_process(false); } } GridMapEditorPlugin::GridMapEditorPlugin(EditorNode *p_node) { editor = p_node; - gridmap_editor = memnew(GridMapEditor(editor)); - SpatialEditor::get_singleton()->get_palette_split()->add_child(gridmap_editor); - // TODO: make this configurable, so the user can choose were to put this, it makes more sense - // on the right, but some people might find it strange. - SpatialEditor::get_singleton()->get_palette_split()->move_child(gridmap_editor, 1); + EDITOR_DEF("editors/grid_map/editor_side", 1); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/grid_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right")); - gridmap_editor->hide(); + grid_map_editor = memnew(GridMapEditor(editor)); + switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) { + case 0: { // Left. + add_control_to_container(CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, grid_map_editor); + } break; + case 1: { // Right. + add_control_to_container(CONTAINER_SPATIAL_EDITOR_SIDE_RIGHT, grid_map_editor); + } break; + } + grid_map_editor->hide(); } GridMapEditorPlugin::~GridMapEditorPlugin() { diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h index 7c5feda125..663274f46e 100644 --- a/modules/gridmap/grid_map_editor_plugin.h +++ b/modules/gridmap/grid_map_editor_plugin.h @@ -97,7 +97,7 @@ class GridMapEditor : public VBoxContainer { List<SetItem> set_items; GridMap *node; - MeshLibrary *last_theme; + MeshLibrary *last_mesh_library; ClipMode clip_mode; bool lock_view; @@ -141,7 +141,7 @@ class GridMapEditor : public VBoxContainer { Vector3 last_mouseover; int display_mode; - int selected_pallete; + int selected_palette; int cursor_rot; enum Menu { @@ -185,9 +185,9 @@ class GridMapEditor : public VBoxContainer { void update_grid(); void _configure(); void _menu_option(int); - void update_pallete(); + void update_palette(); void _set_display_mode(int p_mode); - ItemList *theme_pallete; + ItemList *mesh_library_palette; void _item_selected_cbk(int idx); void _update_cursor_transform(); void _update_cursor_instance(); @@ -207,7 +207,6 @@ class GridMapEditor : public VBoxContainer { bool do_input_action(Camera *p_camera, const Point2 &p_point, bool p_click); friend class GridMapEditorPlugin; - Panel *theme_panel; protected: void _notification(int p_what); @@ -227,11 +226,14 @@ class GridMapEditorPlugin : public EditorPlugin { GDCLASS(GridMapEditorPlugin, EditorPlugin); - GridMapEditor *gridmap_editor; + GridMapEditor *grid_map_editor; EditorNode *editor; +protected: + void _notification(int p_what); + public: - virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event) { return gridmap_editor->forward_spatial_input_event(p_camera, p_event); } + virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event) { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); } virtual String get_name() const { return "GridMap"; } bool has_main_screen() const { return false; } virtual void edit(Object *p_object); diff --git a/modules/gridmap/register_types.cpp b/modules/gridmap/register_types.cpp index a3ceea10af..030286d651 100644 --- a/modules/gridmap/register_types.cpp +++ b/modules/gridmap/register_types.cpp @@ -30,7 +30,7 @@ #include "register_types.h" #ifndef _3D_DISABLED -#include "class_db.h" +#include "core/class_db.h" #include "grid_map.h" #include "grid_map_editor_plugin.h" #endif diff --git a/modules/hdr/image_loader_hdr.cpp b/modules/hdr/image_loader_hdr.cpp index d592c19b97..fc6779ce86 100644 --- a/modules/hdr/image_loader_hdr.cpp +++ b/modules/hdr/image_loader_hdr.cpp @@ -30,8 +30,8 @@ #include "image_loader_hdr.h" -#include "os/os.h" -#include "print_string.h" +#include "core/os/os.h" +#include "core/print_string.h" #include "thirdparty/tinyexr/tinyexr.h" diff --git a/modules/hdr/image_loader_hdr.h b/modules/hdr/image_loader_hdr.h index 3cce483745..7175b38cc8 100644 --- a/modules/hdr/image_loader_hdr.h +++ b/modules/hdr/image_loader_hdr.h @@ -31,7 +31,7 @@ #ifndef IMAGE_LOADER_HDR_H #define IMAGE_LOADER_HDR_H -#include "io/image_loader.h" +#include "core/io/image_loader.h" /** @author Juan Linietsky <reduzio@gmail.com> diff --git a/modules/jpg/SCsub b/modules/jpg/SCsub index e72dc6a1ca..d5f87905eb 100644 --- a/modules/jpg/SCsub +++ b/modules/jpg/SCsub @@ -13,8 +13,11 @@ thirdparty_sources = [ ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env_jpg.add_source_files(env.modules_sources, thirdparty_sources) env_jpg.Append(CPPPATH=[thirdparty_dir]) +env_thirdparty = env_jpg.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Godot's own source files env_jpg.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp index 0168be3a26..24d40111cf 100644 --- a/modules/jpg/image_loader_jpegd.cpp +++ b/modules/jpg/image_loader_jpegd.cpp @@ -30,8 +30,8 @@ #include "image_loader_jpegd.h" -#include "os/os.h" -#include "print_string.h" +#include "core/os/os.h" +#include "core/print_string.h" #include <jpgd.h> #include <string.h> @@ -48,9 +48,9 @@ Error jpeg_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p const int image_width = decoder.get_width(); const int image_height = decoder.get_height(); - int comps = decoder.get_num_components(); - if (comps == 3) - comps = 4; //weird + const int comps = decoder.get_num_components(); + if (comps != 1 && comps != 3) + return ERR_FILE_CORRUPT; if (decoder.begin_decoding() != jpgd::JPGD_SUCCESS) return ERR_FILE_CORRUPT; @@ -73,7 +73,19 @@ Error jpeg_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p } jpgd::uint8 *pDst = pImage_data + y * dst_bpl; - memcpy(pDst, pScan_line, dst_bpl); + + if (comps == 1) { + memcpy(pDst, pScan_line, dst_bpl); + } else { + // For images with more than 1 channel pScan_line will always point to a buffer + // containing 32-bit RGBA pixels. Alpha is always 255 and we ignore it. + for (int x = 0; x < image_width; x++) { + pDst[0] = pScan_line[x * 4 + 0]; + pDst[1] = pScan_line[x * 4 + 1]; + pDst[2] = pScan_line[x * 4 + 2]; + pDst += 3; + } + } } //all good @@ -82,7 +94,7 @@ Error jpeg_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p if (comps == 1) fmt = Image::FORMAT_L8; else - fmt = Image::FORMAT_RGBA8; + fmt = Image::FORMAT_RGB8; dw = PoolVector<uint8_t>::Write(); p_image->create(image_width, image_height, 0, fmt, data); @@ -121,9 +133,7 @@ static Ref<Image> _jpegd_mem_loader_func(const uint8_t *p_png, int p_size) { Ref<Image> img; img.instance(); Error err = jpeg_load_image_from_buffer(img.ptr(), p_png, p_size); - if (err) - ERR_PRINT("Couldn't initialize ImageLoaderJPG with the given resource."); - + ERR_FAIL_COND_V(err, Ref<Image>()); return img; } diff --git a/modules/jpg/image_loader_jpegd.h b/modules/jpg/image_loader_jpegd.h index 3e3ac5217f..b5f0637c9b 100644 --- a/modules/jpg/image_loader_jpegd.h +++ b/modules/jpg/image_loader_jpegd.h @@ -31,7 +31,7 @@ #ifndef IMAGE_LOADER_JPG_H #define IMAGE_LOADER_JPG_H -#include "io/image_loader.h" +#include "core/io/image_loader.h" /** @author Juan Linietsky <reduzio@gmail.com> diff --git a/modules/mbedtls/SCsub b/modules/mbedtls/SCsub index 40540a857f..0c6c703e16 100755 --- a/modules/mbedtls/SCsub +++ b/modules/mbedtls/SCsub @@ -20,6 +20,8 @@ if env['builtin_mbedtls']: "camellia.c", "ccm.c", "certs.c", + "chacha20.c", + "chachapoly.c", "cipher.c", "cipher_wrap.c", "cmac.c", @@ -37,6 +39,7 @@ if env['builtin_mbedtls']: "error.c", "gcm.c", "havege.c", + "hkdf.c", "hmac_drbg.c", "md2.c", "md4.c", @@ -45,6 +48,7 @@ if env['builtin_mbedtls']: "md_wrap.c", "memory_buffer_alloc.c", "net_sockets.c", + "nist_kw.c", "oid.c", "padlock.c", "pem.c", @@ -57,6 +61,7 @@ if env['builtin_mbedtls']: "pkwrite.c", "platform.c", "platform_util.c", + "poly1305.c", "ripemd160.c", "rsa.c", "rsa_internal.c", @@ -86,8 +91,12 @@ if env['builtin_mbedtls']: thirdparty_dir = "#thirdparty/mbedtls/library/" thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_mbed_tls.add_source_files(env.modules_sources, thirdparty_sources) + env_mbed_tls.Prepend(CPPPATH=["#thirdparty/mbedtls/include/"]) + env_thirdparty = env_mbed_tls.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Module sources env_mbed_tls.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/mbedtls/stream_peer_mbed_tls.cpp b/modules/mbedtls/stream_peer_mbed_tls.cpp index a63e53ec1f..5c81f32e9e 100755 --- a/modules/mbedtls/stream_peer_mbed_tls.cpp +++ b/modules/mbedtls/stream_peer_mbed_tls.cpp @@ -30,6 +30,11 @@ #include "stream_peer_mbed_tls.h" +#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) { @@ -81,6 +86,40 @@ int StreamPeerMbedTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) { return got; } +void StreamPeerMbedTLS::_cleanup() { + + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + + base = Ref<StreamPeer>(); + status = STATUS_DISCONNECTED; +} + +Error StreamPeerMbedTLS::_do_handshake() { + int ret = 0; + while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + // An error occurred. + ERR_PRINTS("TLS handshake error: " + itos(ret)); + _print_error(ret); + disconnect_from_stream(); + status = STATUS_ERROR; + return FAILED; + } + + // Handshake is still in progress. + if (!blocking_handshake) { + // Will retry via poll later + return OK; + } + } + + status = STATUS_CONNECTED; + return OK; +} + Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs, const String &p_for_hostname) { base = p_base; @@ -95,6 +134,7 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); if (ret != 0) { ERR_PRINTS(" failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret)); + _cleanup(); return FAILED; } @@ -112,29 +152,24 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida mbedtls_ssl_set_bio(&ssl, this, bio_send, bio_recv, NULL); - while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - ERR_PRINTS("TLS handshake error: " + itos(ret)); - _print_error(ret); - status = STATUS_ERROR_HOSTNAME_MISMATCH; - return FAILED; - } - } + status = STATUS_HANDSHAKING; - connected = true; - status = STATUS_CONNECTED; + if ((ret = _do_handshake()) != OK) { + status = STATUS_ERROR_HOSTNAME_MISMATCH; + return FAILED; + } return OK; } Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base) { - return ERR_UNAVAILABLE; + return OK; } Error StreamPeerMbedTLS::put_data(const uint8_t *p_data, int p_bytes) { - ERR_FAIL_COND_V(!connected, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED); Error err; int sent = 0; @@ -155,7 +190,7 @@ Error StreamPeerMbedTLS::put_data(const uint8_t *p_data, int p_bytes) { Error StreamPeerMbedTLS::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) { - ERR_FAIL_COND_V(!connected, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED); r_sent = 0; @@ -164,7 +199,12 @@ Error StreamPeerMbedTLS::put_partial_data(const uint8_t *p_data, int p_bytes, in int ret = mbedtls_ssl_write(&ssl, p_data, p_bytes); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { - ret = 0; // non blocking io + // Non blocking IO + ret = 0; + } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + // Clean close + disconnect_from_stream(); + return ERR_FILE_EOF; } else if (ret <= 0) { _print_error(ret); disconnect_from_stream(); @@ -177,7 +217,7 @@ Error StreamPeerMbedTLS::put_partial_data(const uint8_t *p_data, int p_bytes, in Error StreamPeerMbedTLS::get_data(uint8_t *p_buffer, int p_bytes) { - ERR_FAIL_COND_V(!connected, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED); Error err; @@ -199,13 +239,17 @@ Error StreamPeerMbedTLS::get_data(uint8_t *p_buffer, int p_bytes) { Error StreamPeerMbedTLS::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) { - ERR_FAIL_COND_V(!connected, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED); r_received = 0; int ret = mbedtls_ssl_read(&ssl, p_buffer, p_bytes); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { ret = 0; // non blocking io + } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + // Clean close + disconnect_from_stream(); + return ERR_FILE_EOF; } else if (ret <= 0) { _print_error(ret); disconnect_from_stream(); @@ -218,27 +262,43 @@ Error StreamPeerMbedTLS::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r void StreamPeerMbedTLS::poll() { - ERR_FAIL_COND(!connected); + ERR_FAIL_COND(status != STATUS_CONNECTED && status != STATUS_HANDSHAKING); ERR_FAIL_COND(!base.is_valid()); + if (status == STATUS_HANDSHAKING) { + _do_handshake(); + return; + } + int ret = mbedtls_ssl_read(&ssl, NULL, 0); - if (ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + // Nothing to read/write (non blocking IO) + } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + // Clean close (disconnect) + disconnect_from_stream(); + return; + } else if (ret < 0) { _print_error(ret); disconnect_from_stream(); return; } + + Ref<StreamPeerTCP> tcp = base; + if (tcp.is_valid() && tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + disconnect_from_stream(); + return; + } } int StreamPeerMbedTLS::get_available_bytes() const { - ERR_FAIL_COND_V(!connected, 0); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, 0); return mbedtls_ssl_get_bytes_avail(&ssl); } StreamPeerMbedTLS::StreamPeerMbedTLS() { - connected = false; status = STATUS_DISCONNECTED; } @@ -248,17 +308,16 @@ StreamPeerMbedTLS::~StreamPeerMbedTLS() { void StreamPeerMbedTLS::disconnect_from_stream() { - if (!connected) + if (status != STATUS_CONNECTED && status != STATUS_HANDSHAKING) return; - mbedtls_ssl_free(&ssl); - mbedtls_ssl_config_free(&conf); - mbedtls_ctr_drbg_free(&ctr_drbg); - mbedtls_entropy_free(&entropy); + Ref<StreamPeerTCP> tcp = base; + if (tcp.is_valid() && tcp->get_status() == StreamPeerTCP::STATUS_CONNECTED) { + // We are still connected on the socket, try to send close notity. + mbedtls_ssl_close_notify(&ssl); + } - base = Ref<StreamPeer>(); - connected = false; - status = STATUS_DISCONNECTED; + _cleanup(); } StreamPeerMbedTLS::Status StreamPeerMbedTLS::get_status() const { @@ -293,15 +352,13 @@ void StreamPeerMbedTLS::initialize_ssl() { mbedtls_debug_set_threshold(1); #endif - PoolByteArray cert_array = StreamPeerSSL::get_project_cert_array(); - - if (cert_array.size() > 0) - _load_certs(cert_array); - available = true; } void StreamPeerMbedTLS::finalize_ssl() { + available = false; + _create = NULL; + load_certs_func = NULL; mbedtls_x509_crt_free(&cacert); } diff --git a/modules/mbedtls/stream_peer_mbed_tls.h b/modules/mbedtls/stream_peer_mbed_tls.h index 2b96a194a1..abf87b79cc 100755 --- a/modules/mbedtls/stream_peer_mbed_tls.h +++ b/modules/mbedtls/stream_peer_mbed_tls.h @@ -31,14 +31,14 @@ #ifndef STREAM_PEER_OPEN_SSL_H #define STREAM_PEER_OPEN_SSL_H -#include "io/stream_peer_ssl.h" +#include "core/io/stream_peer_ssl.h" -#include "mbedtls/config.h" -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/debug.h" -#include "mbedtls/entropy.h" -#include "mbedtls/net.h" -#include "mbedtls/ssl.h" +#include <mbedtls/config.h> +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/debug.h> +#include <mbedtls/entropy.h> +#include <mbedtls/net.h> +#include <mbedtls/ssl.h> #include <stdio.h> #include <stdlib.h> @@ -48,8 +48,6 @@ private: Status status; String hostname; - bool connected; - Ref<StreamPeer> base; static StreamPeerSSL *_create_func(); @@ -57,9 +55,11 @@ private: static int bio_recv(void *ctx, unsigned char *buf, size_t len); static int bio_send(void *ctx, const unsigned char *buf, size_t len); + void _cleanup(); protected: static mbedtls_x509_crt cacert; + mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_ssl_context ssl; @@ -67,6 +67,8 @@ protected: static void _bind_methods(); + Error _do_handshake(); + public: virtual void poll(); virtual Error accept_stream(Ref<StreamPeer> p_base); diff --git a/modules/mobile_vr/SCsub b/modules/mobile_vr/SCsub index b4e2edcca1..4bd184f025 100644 --- a/modules/mobile_vr/SCsub +++ b/modules/mobile_vr/SCsub @@ -1,13 +1,8 @@ #!/usr/bin/env python -import os -import methods - Import('env') Import('env_modules') env_mobile_vr = env_modules.Clone() env_mobile_vr.add_source_files(env.modules_sources, '*.cpp') - -SConscript("shaders/SCsub") diff --git a/modules/mobile_vr/config.py b/modules/mobile_vr/config.py index 4912457e2b..e85fa631dd 100644 --- a/modules/mobile_vr/config.py +++ b/modules/mobile_vr/config.py @@ -1,6 +1,5 @@ def can_build(env, platform): - # should probably change this to only be true on iOS and Android - return False + return True def configure(env): pass diff --git a/modules/mobile_vr/mobile_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp index 6b1c7eb279..87e4ddd900 100644 --- a/modules/mobile_vr/mobile_interface.cpp +++ b/modules/mobile_vr/mobile_vr_interface.cpp @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "mobile_interface.h" +#include "mobile_vr_interface.h" #include "core/os/input.h" #include "core/os/os.h" #include "servers/visual/visual_server_global.h" @@ -279,7 +279,7 @@ bool MobileVRInterface::is_stereo() { return true; }; -bool MobileVRInterface::is_initialized() { +bool MobileVRInterface::is_initialized() const { return (initialized); }; @@ -304,7 +304,7 @@ bool MobileVRInterface::initialize() { arvr_server->set_primary_interface(this); last_ticks = OS::get_singleton()->get_ticks_usec(); - ; + initialized = true; }; @@ -397,40 +397,26 @@ void MobileVRInterface::commit_for_eye(ARVRInterface::Eyes p_eye, RID p_render_t // Because we are rendering to our device we must use our main viewport! ERR_FAIL_COND(p_screen_rect == Rect2()); - float offset_x = 0.0; - float aspect_ratio = 0.5 * p_screen_rect.size.x / p_screen_rect.size.y; + Rect2 dest = p_screen_rect; Vector2 eye_center; + // we output half a screen + dest.size.x *= 0.5; + if (p_eye == ARVRInterface::EYE_LEFT) { - offset_x = -1.0; eye_center.x = ((-intraocular_dist / 2.0) + (display_width / 4.0)) / (display_width / 2.0); } else if (p_eye == ARVRInterface::EYE_RIGHT) { + dest.position.x = dest.size.x; eye_center.x = ((intraocular_dist / 2.0) - (display_width / 4.0)) / (display_width / 2.0); } + // we don't offset the eye center vertically (yet) + eye_center.y = 0.0; // unset our render target so we are outputting to our main screen by making RasterizerStorageGLES3::system_fbo our current FBO VSG::rasterizer->set_current_render_target(RID()); - // now output to screen - // VSG::rasterizer->blit_render_target_to_screen(p_render_target, screen_rect, 0); - - // get our render target - RID eye_texture = VSG::storage->render_target_get_texture(p_render_target); - uint32_t texid = VS::get_singleton()->texture_get_texid(eye_texture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texid); - - lens_shader.bind(); - lens_shader.set_uniform(LensDistortedShaderGLES3::OFFSET_X, offset_x); - lens_shader.set_uniform(LensDistortedShaderGLES3::K1, k1); - lens_shader.set_uniform(LensDistortedShaderGLES3::K2, k2); - lens_shader.set_uniform(LensDistortedShaderGLES3::EYE_CENTER, eye_center); - lens_shader.set_uniform(LensDistortedShaderGLES3::UPSCALE, oversample); - lens_shader.set_uniform(LensDistortedShaderGLES3::ASPECT_RATIO, aspect_ratio); - - glBindVertexArray(half_screen_array); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glBindVertexArray(0); + // and output + VSG::rasterizer->output_lens_distorted_to_screen(p_render_target, dest, k1, k2, eye_center, oversample); }; void MobileVRInterface::process() { @@ -453,42 +439,6 @@ MobileVRInterface::MobileVRInterface() { k1 = 0.215; k2 = 0.215; last_ticks = 0; - - // create our shader stuff - lens_shader.init(); - - { - glGenBuffers(1, &half_screen_quad); - glBindBuffer(GL_ARRAY_BUFFER, half_screen_quad); - { - /* clang-format off */ - const float qv[16] = { - 0, -1, - -1, -1, - 0, 1, - -1, 1, - 1, 1, - 1, 1, - 1, -1, - 1, -1, - }; - /* clang-format on */ - - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 16, qv, GL_STATIC_DRAW); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind - - glGenVertexArrays(1, &half_screen_array); - glBindVertexArray(half_screen_array); - glBindBuffer(GL_ARRAY_BUFFER, half_screen_quad); - glVertexAttribPointer(VS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, 0); - glEnableVertexAttribArray(0); - glVertexAttribPointer(VS::ARRAY_TEX_UV, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, ((uint8_t *)NULL) + 8); - glEnableVertexAttribArray(4); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind - } }; MobileVRInterface::~MobileVRInterface() { diff --git a/modules/mobile_vr/mobile_interface.h b/modules/mobile_vr/mobile_vr_interface.h index bb84281b46..05b6331f94 100644 --- a/modules/mobile_vr/mobile_interface.h +++ b/modules/mobile_vr/mobile_vr_interface.h @@ -34,8 +34,6 @@ #include "servers/arvr/arvr_interface.h" #include "servers/arvr/arvr_positional_tracker.h" -#include "shaders/lens_distorted.glsl.gen.h" - /** @author Bastiaan Olij <mux213@gmail.com> @@ -58,10 +56,6 @@ private: float eye_height; uint64_t last_ticks; - LensDistortedShaderGLES3 lens_shader; - GLuint half_screen_quad; - GLuint half_screen_array; - real_t intraocular_dist; real_t display_width; real_t display_to_lens; @@ -134,7 +128,7 @@ public: virtual StringName get_name() const; virtual int get_capabilities() const; - virtual bool is_initialized(); + virtual bool is_initialized() const; virtual bool initialize(); virtual void uninitialize(); @@ -150,4 +144,4 @@ public: ~MobileVRInterface(); }; -#endif // MOBILE_VR_INTERFACE_H +#endif // !MOBILE_VR_INTERFACE_H diff --git a/modules/mobile_vr/register_types.cpp b/modules/mobile_vr/register_types.cpp index 0655727a4a..edddaa87e1 100644 --- a/modules/mobile_vr/register_types.cpp +++ b/modules/mobile_vr/register_types.cpp @@ -30,7 +30,7 @@ #include "register_types.h" -#include "mobile_interface.h" +#include "mobile_vr_interface.h" void register_mobile_vr_types() { ClassDB::register_class<MobileVRInterface>(); diff --git a/modules/mobile_vr/shaders/SCsub b/modules/mobile_vr/shaders/SCsub deleted file mode 100644 index cf53c9ebe0..0000000000 --- a/modules/mobile_vr/shaders/SCsub +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python - -Import('env') - -if 'GLES3_GLSL' in env['BUILDERS']: - env.GLES3_GLSL('lens_distorted.glsl'); - diff --git a/modules/mobile_vr/shaders/lens_distorted.glsl b/modules/mobile_vr/shaders/lens_distorted.glsl deleted file mode 100644 index 5a2975d737..0000000000 --- a/modules/mobile_vr/shaders/lens_distorted.glsl +++ /dev/null @@ -1,59 +0,0 @@ -[vertex] - -layout(location=0) in highp vec4 vertex_attrib; -layout(location=4) in vec2 uv_in; - -uniform float offset_x; - -out vec2 uv_interp; - -void main() { - - uv_interp = uv_in; - gl_Position = vec4(vertex_attrib.x + offset_x, vertex_attrib.y, 0.0, 1.0); -} - -[fragment] - -uniform sampler2D source; //texunit:0 - -uniform vec2 eye_center; -uniform float k1; -uniform float k2; -uniform float upscale; -uniform float aspect_ratio; - -in vec2 uv_interp; - -layout(location = 0) out vec4 frag_color; - -void main() { - vec2 coords = uv_interp; - vec2 offset = coords - eye_center; - - // take aspect ratio into account - offset.y /= aspect_ratio; - - // distort - vec2 offset_sq = offset * offset; - float radius_sq = offset_sq.x + offset_sq.y; - float radius_s4 = radius_sq * radius_sq; - float distortion_scale = 1.0 + (k1 * radius_sq) + (k2 * radius_s4); - offset *= distortion_scale; - - // reapply aspect ratio - offset.y *= aspect_ratio; - - // add our eye center back in - coords = offset + eye_center; - coords /= upscale; - - // and check our color - if (coords.x < -1.0 || coords.y < -1.0 || coords.x > 1.0 || coords.y > 1.0) { - frag_color = vec4(0.0, 0.0, 0.0, 1.0); - } else { - coords = (coords + vec2(1.0)) / vec2(2.0); - frag_color = textureLod(source, coords, 0.0); - } -} - diff --git a/modules/mono/SCsub b/modules/mono/SCsub index c69a3c9ba6..e1f5e2ef28 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -7,44 +7,48 @@ env_mono = env_modules.Clone() # TODO move functions to their own modules -def make_cs_files_header(src, dst): +def make_cs_files_header(src, dst, version_dst): from compat import byte_to_str with open(dst, 'w') as header: - header.write('/* This is an automatically generated file; DO NOT EDIT! OK THX */\n') - header.write('#ifndef _CS_FILES_DATA_H\n') - header.write('#define _CS_FILES_DATA_H\n\n') - header.write('#include "map.h"\n') - header.write('#include "ustring.h"\n') + header.write('/* THIS FILE IS GENERATED DO NOT EDIT */\n') + header.write('#ifndef CS_COMPRESSED_H\n') + header.write('#define CS_COMPRESSED_H\n\n') + header.write('#ifdef TOOLS_ENABLED\n\n') + header.write('#include "core/map.h"\n') + header.write('#include "core/ustring.h"\n') inserted_files = '' import os - for file in os.listdir(src): - if file.endswith('.cs'): - with open(os.path.join(src, file), 'rb') as f: + latest_mtime = 0 + cs_file_count = 0 + for root, _, files in os.walk(src): + files = [f for f in files if f.endswith('.cs')] + for file in files: + cs_file_count += 1 + filepath = os.path.join(root, file) + filepath_src_rel = os.path.relpath(filepath, src) + mtime = os.path.getmtime(filepath) + latest_mtime = mtime if mtime > latest_mtime else latest_mtime + with open(filepath, 'rb') as f: buf = f.read() decomp_size = len(buf) import zlib buf = zlib.compress(buf) - name = os.path.splitext(file)[0] - header.write('\nstatic const int _cs_' + name + '_compressed_size = ' + str(len(buf)) + ';\n') + 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 unsigned char _cs_' + name + '_compressed[] = { ') for i, buf_idx in enumerate(range(len(buf))): if i > 0: header.write(', ') header.write(byte_to_str(buf[buf_idx])) - inserted_files += '\tr_files.insert("' + file + '", ' \ + inserted_files += '\tr_files.insert("' + filepath_src_rel.replace('\\', '\\\\') + '", ' \ 'CompressedFile(_cs_' + name + '_compressed_size, ' \ '_cs_' + name + '_uncompressed_size, ' \ '_cs_' + name + '_compressed));\n' header.write(' };\n') - version_file = os.path.join(src, 'VERSION.txt') - with open(version_file, 'r') as content_file: - try: - glue_version = int(content_file.read()) # make sure the format is valid - header.write('\n#define CS_GLUE_VERSION UINT32_C(' + str(glue_version) + ')\n') - except ValueError: - raise ValueError('Invalid C# glue version in: ' + version_file) header.write('\nstruct CompressedFile\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' @@ -52,16 +56,28 @@ def make_cs_files_header(src, dst): '\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' ) - header.write('#endif // _CS_FILES_DATA_H') + header.write('\n#endif // TOOLS_ENABLED\n') + header.write('\n#endif // CS_COMPRESSED_H\n') + + glue_version = int(latest_mtime) # The latest modified time will do for now + + with open(version_dst, 'w') as version_header: + version_header.write('/* THIS FILE IS GENERATED DO NOT EDIT */\n') + version_header.write('#ifndef CS_GLUE_VERSION_H\n') + version_header.write('#define CS_GLUE_VERSION_H\n\n') + version_header.write('#define CS_GLUE_VERSION UINT32_C(' + str(glue_version) + ')\n') + version_header.write('\n#endif // CS_GLUE_VERSION_H\n') env_mono.add_source_files(env.modules_sources, '*.cpp') +env_mono.add_source_files(env.modules_sources, 'glue/*.cpp') env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp') env_mono.add_source_files(env.modules_sources, 'utils/*.cpp') if env['tools']: env_mono.add_source_files(env.modules_sources, 'editor/*.cpp') - make_cs_files_header('glue/cs_files', 'glue/cs_compressed.gen.h') + # NOTE: It is safe to generate this file here, since this is still executed serially + make_cs_files_header('glue/Managed/Files', 'glue/cs_compressed.gen.h', 'glue/cs_glue_version.gen.h') vars = Variables() vars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True)) @@ -70,12 +86,7 @@ vars.Update(env_mono) # Glue sources if env_mono['mono_glue']: - env_mono.add_source_files(env.modules_sources, 'glue/*.cpp') -else: - env_mono.Append(CPPDEFINES=['MONO_GLUE_DISABLED']) - -if ARGUMENTS.get('yolo_copy', False): - env_mono.Append(CPPDEFINES=['YOLO_COPY']) + env_mono.Append(CPPDEFINES=['MONO_GLUE_ENABLED']) # Configure TLS checks @@ -91,6 +102,87 @@ env_mono = conf.Finish() import os +def find_nuget_unix(): + import os + + if 'NUGET_PATH' in os.environ: + hint_path = os.environ['NUGET_PATH'] + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + hint_path = os.path.join(hint_path, 'nuget') + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + + import os.path + import sys + + hint_dirs = ['/opt/novell/mono/bin'] + if sys.platform == 'darwin': + hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs + + for hint_dir in hint_dirs: + hint_path = os.path.join(hint_dir, 'nuget') + if os.path.isfile(hint_path): + return hint_path + elif os.path.isfile(hint_path + '.exe'): + return hint_path + '.exe' + + for hint_dir in os.environ['PATH'].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, 'nuget') + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK): + return hint_path + '.exe' + + return None + + +def find_nuget_windows(): + import os + + if 'NUGET_PATH' in os.environ: + hint_path = os.environ['NUGET_PATH'] + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + hint_path = os.path.join(hint_path, 'nuget.exe') + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + + import mono_reg_utils as monoreg + + mono_root = '' + bits = env['bits'] + + if bits == '32': + if os.getenv('MONO32_PREFIX'): + mono_root = os.getenv('MONO32_PREFIX') + else: + mono_root = monoreg.find_mono_root_dir(bits) + else: + if os.getenv('MONO64_PREFIX'): + mono_root = os.getenv('MONO64_PREFIX') + else: + mono_root = monoreg.find_mono_root_dir(bits) + + if mono_root: + mono_bin_dir = os.path.join(mono_root, 'bin') + nuget_mono = os.path.join(mono_bin_dir, 'nuget.bat') + + if os.path.isfile(nuget_mono): + return nuget_mono + + # Standalone NuGet + + for hint_dir in os.environ['PATH'].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, 'nuget.exe') + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + + return None + + def find_msbuild_unix(filename): import os.path import sys @@ -120,6 +212,7 @@ def find_msbuild_unix(filename): def find_msbuild_windows(): import mono_reg_utils as monoreg + mono_root = '' bits = env['bits'] if bits == '32': @@ -164,14 +257,17 @@ def mono_build_solution(source, target, env): import mono_reg_utils as monoreg from shutil import copyfile - framework_path = '' + sln_path = os.path.abspath(str(source[0])) + target_path = os.path.abspath(str(target[0])) + framework_path = '' msbuild_env = os.environ.copy() # Needed when running from Developer Command Prompt for VS if 'PLATFORM' in msbuild_env: del msbuild_env['PLATFORM'] + # Find MSBuild if os.name == 'nt': msbuild_info = find_msbuild_windows() if msbuild_info is None: @@ -201,11 +297,27 @@ def mono_build_solution(source, target, env): print('MSBuild path: ' + msbuild_path) + # Find NuGet + nuget_path = find_nuget_windows() 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, - os.path.abspath(str(source[0])), + sln_path, '/p:Configuration=' + build_config, ] @@ -215,30 +327,31 @@ def mono_build_solution(source, target, env): try: subprocess.check_call(msbuild_args, env=msbuild_env) except subprocess.CalledProcessError: - raise RuntimeError('GodotSharpTools build failed') + raise RuntimeError('GodotSharpTools: Build failed') - src_dir = os.path.abspath(os.path.join(str(source[0]), os.pardir, 'bin', build_config)) - dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) + # 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) - asm_file = 'GodotSharpTools.dll' - copyfile(os.path.join(src_dir, asm_file), os.path.join(dst_dir, asm_file)) -output_dir = Dir('#bin').abspath -assemblies_output_dir = Dir(env['mono_assemblies_output_dir']).abspath + # Dependencies + copyfile(os.path.join(src_dir, "DotNet.Glob.dll"), os.path.join(dst_dir, "DotNet.Glob.dll")) -mono_sln_builder = Builder(action=mono_build_solution) -env_mono.Append(BUILDERS={'MonoBuildSolution': mono_sln_builder}) -env_mono.MonoBuildSolution( - os.path.join(assemblies_output_dir, 'GodotSharpTools.dll'), - 'editor/GodotSharpTools/GodotSharpTools.sln' -) - -if os.path.normpath(output_dir) != os.path.normpath(assemblies_output_dir): - rel_assemblies_output_dir = os.path.relpath(assemblies_output_dir, output_dir) - env_mono.Append(CPPDEFINES={'GD_MONO_EDITOR_ASSEMBLIES_DIR': rel_assemblies_output_dir}) +if env['tools']: + output_dir = Dir('#bin').abspath + editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools') + + 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' + ) diff --git a/modules/mono/config.py b/modules/mono/config.py index 9a000a2a72..189699cca8 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -5,7 +5,7 @@ import sys import subprocess from distutils.version import LooseVersion -from SCons.Script import BoolVariable, Dir, Environment, File, PathVariable, SCons, Variables +from SCons.Script import BoolVariable, Dir, Environment, File, SCons, Variables monoreg = imp.load_source('mono_reg_utils', 'modules/mono/mono_reg_utils.py') @@ -55,34 +55,26 @@ def copy_file(src_dir, dst_dir, name): copyfile(src_path, dst_path) -def custom_path_is_dir_create(key, val, env): - """Validator to check if Path is a directory, creating it if it does not exist. - Similar to PathIsDirCreate, except it uses SCons.Script.Dir() and - SCons.Script.File() in order to support the '#' top level directory token. - """ - # Dir constructor will throw an error if the path points to a file - fsDir = Dir(val) - if not fsDir.exists: - os.makedirs(fsDir.abspath) - - def configure(env): env.use_ptrcall = True env.add_module_version_string('mono') envvars = Variables() envvars.Add(BoolVariable('mono_static', 'Statically link mono', False)) - envvars.Add(PathVariable('mono_assemblies_output_dir', 'Path to the assemblies output directory', '#bin', custom_path_is_dir_create)) + envvars.Add(BoolVariable('copy_mono_root', 'Make a copy of the mono installation directory to bundle with the editor', False)) envvars.Update(env) bits = env['bits'] + tools_enabled = env['tools'] mono_static = env['mono_static'] - assemblies_output_dir = Dir(env['mono_assemblies_output_dir']).abspath + copy_mono_root = env['copy_mono_root'] mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0'] if env['platform'] == 'windows': + mono_root = '' + if bits == '32': if os.getenv('MONO32_PREFIX'): mono_root = os.getenv('MONO32_PREFIX') @@ -97,6 +89,8 @@ def configure(env): if not mono_root: raise RuntimeError('Mono installation directory not found') + print('Found Mono root directory: ' + mono_root) + mono_version = mono_root_try_find_mono_version(mono_root) configure_for_mono_version(env, mono_version) @@ -147,8 +141,6 @@ def configure(env): raise RuntimeError('Could not find mono shared library in: ' + mono_bin_path) copy_file(mono_bin_path, 'bin', mono_dll_name + '.dll') - - copy_file(os.path.join(mono_lib_path, 'mono', '4.5'), assemblies_output_dir, 'mscorlib.dll') else: sharedlib_ext = '.dylib' if sys.platform == 'darwin' else '.so' @@ -162,6 +154,14 @@ def configure(env): if os.getenv('MONO64_PREFIX'): mono_root = os.getenv('MONO64_PREFIX') + if not mono_root and sys.platform == 'darwin': + # Try with some known directories under OSX + hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current', '/usr/local/var/homebrew/linked/mono'] + for hint_dir in hint_dirs: + if os.path.isdir(hint_dir): + mono_root = hint_dir + break + # We can't use pkg-config to link mono statically, # but we can still use it to find the mono root directory if not mono_root and mono_static: @@ -170,6 +170,8 @@ def configure(env): raise RuntimeError('Building with mono_static=yes, but failed to find the mono prefix with pkg-config. Specify one manually') 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_version) @@ -190,16 +192,14 @@ def configure(env): if sys.platform == 'darwin': env.Append(LINKFLAGS=['-Wl,-force_load,' + mono_lib_file]) - elif sys.platform == 'linux' or sys.platform == 'linux2': - env.Append(LINKFLAGS=['-Wl,-whole-archive', mono_lib_file, '-Wl,-no-whole-archive']) else: - raise RuntimeError('mono-static: Not supported on this platform') + env.Append(LINKFLAGS=['-Wl,-whole-archive', mono_lib_file, '-Wl,-no-whole-archive']) else: env.Append(LIBS=[mono_lib]) if sys.platform == 'darwin': env.Append(LIBS=['iconv', 'pthread']) - elif sys.platform == 'linux' or sys.platform == 'linux2': + else: env.Append(LIBS=['m', 'rt', 'dl', 'pthread']) if not mono_static: @@ -209,11 +209,12 @@ def configure(env): raise RuntimeError('Could not find mono shared library in: ' + mono_lib_path) copy_file(mono_lib_path, 'bin', 'lib' + mono_so_name + sharedlib_ext) - - copy_file(os.path.join(mono_lib_path, 'mono', '4.5'), assemblies_output_dir, 'mscorlib.dll') else: assert not mono_static + # 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_version) @@ -221,7 +222,6 @@ def configure(env): mono_lib_path = '' mono_so_name = '' - mono_prefix = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip() tmpenv = Environment() tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH')) @@ -238,16 +238,159 @@ def configure(env): raise RuntimeError('Could not find mono shared library in: ' + str(tmpenv['LIBPATH'])) copy_file(mono_lib_path, 'bin', 'lib' + mono_so_name + sharedlib_ext) - copy_file(os.path.join(mono_prefix, 'lib', 'mono', '4.5'), assemblies_output_dir, 'mscorlib.dll') env.Append(LINKFLAGS='-rdynamic') + if not tools_enabled: + if not mono_root: + mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip() + + make_template_dir(env, mono_root) + + if copy_mono_root: + if not mono_root: + mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip() + + if tools_enabled: + copy_mono_root_files(env, mono_root) + else: + print("Ignoring option: 'copy_mono_root'. Only available for builds with 'tools' enabled.") + + +def make_template_dir(env, mono_root): + from shutil import rmtree + + platform = env['platform'] + target = env['target'] + + template_dir_name = '' + + if platform in ['windows', 'osx', 'x11']: + template_dir_name = 'data.mono.%s.%s.%s' % (platform, env['bits'], target) + else: + assert False + + output_dir = Dir('#bin').abspath + template_dir = os.path.join(output_dir, template_dir_name) + + template_mono_root_dir = os.path.join(template_dir, 'Mono') + + if os.path.isdir(template_mono_root_dir): + rmtree(template_mono_root_dir) # Clean first + + # Copy etc/mono/ + + template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono') + copy_mono_etc_dir(mono_root, template_mono_config_dir, env['platform']) + + # Copy the required shared libraries + + copy_mono_shared_libs(mono_root, template_mono_root_dir, env['platform']) + + +def copy_mono_root_files(env, mono_root): + from glob import glob + from shutil import copy + from shutil import rmtree + + if not mono_root: + raise RuntimeError('Mono installation directory not found') + + output_dir = Dir('#bin').abspath + editor_mono_root_dir = os.path.join(output_dir, 'GodotSharp', 'Mono') + + if os.path.isdir(editor_mono_root_dir): + rmtree(editor_mono_root_dir) # Clean first + + # Copy etc/mono/ + + editor_mono_config_dir = os.path.join(editor_mono_root_dir, 'etc', 'mono') + copy_mono_etc_dir(mono_root, editor_mono_config_dir, env['platform']) + + # Copy the required shared libraries + + copy_mono_shared_libs(mono_root, editor_mono_root_dir, env['platform']) + + # Copy framework assemblies + + mono_framework_dir = os.path.join(mono_root, 'lib', 'mono', '4.5') + mono_framework_facades_dir = os.path.join(mono_framework_dir, 'Facades') + + editor_mono_framework_dir = os.path.join(editor_mono_root_dir, 'lib', 'mono', '4.5') + editor_mono_framework_facades_dir = os.path.join(editor_mono_framework_dir, 'Facades') + + if not os.path.isdir(editor_mono_framework_dir): + os.makedirs(editor_mono_framework_dir) + if not os.path.isdir(editor_mono_framework_facades_dir): + os.makedirs(editor_mono_framework_facades_dir) + + for assembly in glob(os.path.join(mono_framework_dir, '*.dll')): + copy(assembly, editor_mono_framework_dir) + for assembly in glob(os.path.join(mono_framework_facades_dir, '*.dll')): + copy(assembly, editor_mono_framework_facades_dir) + + +def copy_mono_etc_dir(mono_root, target_mono_config_dir, platform): + from distutils.dir_util import copy_tree + from glob import glob + from shutil import copy + + if not os.path.isdir(target_mono_config_dir): + os.makedirs(target_mono_config_dir) + + mono_etc_dir = os.path.join(mono_root, 'etc', 'mono') + if not os.path.isdir(mono_etc_dir): + mono_etc_dir = '' + etc_hint_dirs = [] + if platform != 'windows': + etc_hint_dirs += ['/etc/mono', '/usr/local/etc/mono'] + if 'MONO_CFG_DIR' in os.environ: + etc_hint_dirs += [os.path.join(os.environ['MONO_CFG_DIR'], 'mono')] + for etc_hint_dir in etc_hint_dirs: + if os.path.isdir(etc_hint_dir): + mono_etc_dir = etc_hint_dir + break + if not mono_etc_dir: + raise RuntimeError('Mono installation etc directory not found') + + copy_tree(os.path.join(mono_etc_dir, '2.0'), os.path.join(target_mono_config_dir, '2.0')) + copy_tree(os.path.join(mono_etc_dir, '4.0'), os.path.join(target_mono_config_dir, '4.0')) + copy_tree(os.path.join(mono_etc_dir, '4.5'), os.path.join(target_mono_config_dir, '4.5')) + copy_tree(os.path.join(mono_etc_dir, 'mconfig'), os.path.join(target_mono_config_dir, 'mconfig')) + + for file in glob(os.path.join(mono_etc_dir, '*')): + if os.path.isfile(file): + copy(file, target_mono_config_dir) + + +def copy_mono_shared_libs(mono_root, target_mono_root_dir, platform): + from shutil import copy + + if platform == 'windows': + target_mono_bin_dir = os.path.join(target_mono_root_dir, 'bin') + + if not os.path.isdir(target_mono_bin_dir): + os.makedirs(target_mono_bin_dir) + + copy(os.path.join(mono_root, 'bin', 'MonoPosixHelper.dll'), os.path.join(target_mono_bin_dir, 'MonoPosixHelper.dll')) + else: + target_mono_lib_dir = os.path.join(target_mono_root_dir, 'lib') + + if not os.path.isdir(target_mono_lib_dir): + os.makedirs(target_mono_lib_dir) + + if platform == 'osx': + copy(os.path.join(mono_root, 'lib', 'libMonoPosixHelper.dylib'), os.path.join(target_mono_lib_dir, 'libMonoPosixHelper.dylib')) + elif platform == 'x11': + copy(os.path.join(mono_root, 'lib', 'libmono-btls-shared.so'), os.path.join(target_mono_lib_dir, 'libmono-btls-shared.so')) + copy(os.path.join(mono_root, 'lib', 'libMonoPosixHelper.so'), os.path.join(target_mono_lib_dir, 'libMonoPosixHelper.so')) + def configure_for_mono_version(env, mono_version): if mono_version is None: raise RuntimeError('Mono JIT compiler version not found') - print('Mono JIT compiler version: ' + str(mono_version)) - if mono_version >= LooseVersion("5.12.0"): + print('Found Mono JIT compiler version: ' + str(mono_version)) + if mono_version >= LooseVersion('5.12.0'): env.Append(CPPFLAGS=['-DHAS_PENDING_EXCEPTIONS']) @@ -263,11 +406,13 @@ def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext): 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(line) + version = LooseVersion(decode_utf8(line)) if greater_version is None or version > greater_version: greater_version = version except ValueError: @@ -278,7 +423,14 @@ def pkgconfig_try_find_mono_version(): def mono_root_try_find_mono_version(mono_root): from compat import decode_utf8 - output = subprocess.check_output([os.path.join(mono_root, 'bin', 'mono'), '--version']) + 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())]) diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 62a6b96bb5..943d95bfc9 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -32,17 +32,17 @@ #include <mono/metadata/threads.h> -#include "os/file_access.h" -#include "os/os.h" -#include "os/thread.h" -#include "project_settings.h" +#include "core/io/json.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "core/os/thread.h" +#include "core/project_settings.h" #ifdef TOOLS_ENABLED #include "editor/bindings_generator.h" #include "editor/csharp_project.h" #include "editor/editor_node.h" #include "editor/godotsharp_editor.h" -#include "utils/string_utils.h" #endif #include "godotsharp_dirs.h" @@ -50,6 +50,8 @@ #include "mono_gd/gd_mono_marshal.h" #include "signal_awaiter_utils.h" #include "utils/macros.h" +#include "utils/mutex_utils.h" +#include "utils/string_utils.h" #include "utils/thread_local.h" #define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var) @@ -107,7 +109,7 @@ void CSharpLanguage::init() { gdmono = memnew(GDMono); gdmono->initialize(); -#ifdef MONO_GLUE_DISABLED +#ifndef MONO_GLUE_ENABLED WARN_PRINT("This binary is built with `mono_glue=no` and cannot be used for scripting"); #endif @@ -121,7 +123,7 @@ void CSharpLanguage::init() { #ifdef TOOLS_ENABLED EditorNode::add_init_callback(&gdsharp_editor_init_callback); - GLOBAL_DEF("mono/export/include_scripts_content", true); + GLOBAL_DEF("mono/export/include_scripts_content", false); #endif } @@ -138,7 +140,7 @@ void CSharpLanguage::finish() { #endif // Release gchandle bindings before finalizing mono runtime - gchandle_bindings.clear(); + script_bindings.clear(); if (gdmono) { memdelete(gdmono); @@ -370,70 +372,82 @@ bool CSharpLanguage::supports_builtin_mode() const { return false; } +#ifdef TOOLS_ENABLED static String variant_type_to_managed_name(const String &p_var_type_name) { if (p_var_type_name.empty()) return "object"; if (!ClassDB::class_exists(p_var_type_name)) { - Variant::Type var_types[] = { - Variant::BOOL, - Variant::INT, - Variant::REAL, - Variant::STRING, - Variant::VECTOR2, - Variant::RECT2, - Variant::VECTOR3, - Variant::TRANSFORM2D, - Variant::PLANE, - Variant::QUAT, - Variant::AABB, - Variant::BASIS, - Variant::TRANSFORM, - Variant::COLOR, - Variant::NODE_PATH, - Variant::_RID - }; - - for (int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) { - if (p_var_type_name == Variant::get_type_name(var_types[i])) - return p_var_type_name; - } + return p_var_type_name; + } - if (p_var_type_name == "String") - return "string"; // I prefer this one >:[ + if (p_var_type_name == Variant::get_type_name(Variant::OBJECT)) + return "Godot.Object"; - // TODO these will be rewritten later into custom containers + if (p_var_type_name == Variant::get_type_name(Variant::REAL)) { +#ifdef REAL_T_IS_DOUBLE + return "double"; +#else + return "float"; +#endif + } - if (p_var_type_name == "Array") - return "object[]"; + if (p_var_type_name == Variant::get_type_name(Variant::STRING)) + return "string"; // I prefer this one >:[ - if (p_var_type_name == "Dictionary") - return "Dictionary<object, object>"; + if (p_var_type_name == Variant::get_type_name(Variant::DICTIONARY)) + return "Collections.Dictionary"; - if (p_var_type_name == "PoolByteArray") - return "byte[]"; - if (p_var_type_name == "PoolIntArray") - return "int[]"; - if (p_var_type_name == "PoolRealArray") - return "float[]"; - if (p_var_type_name == "PoolStringArray") - return "string[]"; - if (p_var_type_name == "PoolVector2Array") - return "Vector2[]"; - if (p_var_type_name == "PoolVector3Array") - return "Vector3[]"; - if (p_var_type_name == "PoolColorArray") - return "Color[]"; + if (p_var_type_name == Variant::get_type_name(Variant::ARRAY)) + return "Collections.Array"; - return "object"; + if (p_var_type_name == Variant::get_type_name(Variant::POOL_BYTE_ARRAY)) + return "byte[]"; + if (p_var_type_name == Variant::get_type_name(Variant::POOL_INT_ARRAY)) + return "int[]"; + if (p_var_type_name == Variant::get_type_name(Variant::POOL_REAL_ARRAY)) { +#ifdef REAL_T_IS_DOUBLE + return "double[]"; +#else + return "float[]"; +#endif + } + if (p_var_type_name == Variant::get_type_name(Variant::POOL_STRING_ARRAY)) + return "string[]"; + if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR2_ARRAY)) + return "Vector2[]"; + if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR3_ARRAY)) + return "Vector3[]"; + if (p_var_type_name == Variant::get_type_name(Variant::POOL_COLOR_ARRAY)) + return "Color[]"; + + Variant::Type var_types[] = { + Variant::BOOL, + Variant::INT, + Variant::VECTOR2, + Variant::RECT2, + Variant::VECTOR3, + Variant::TRANSFORM2D, + Variant::PLANE, + Variant::QUAT, + Variant::AABB, + Variant::BASIS, + Variant::TRANSFORM, + Variant::COLOR, + Variant::NODE_PATH, + Variant::_RID + }; + + for (int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) { + if (p_var_type_name == Variant::get_type_name(var_types[i])) + return p_var_type_name; } - return p_var_type_name; + return "object"; } -String CSharpLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const { -#ifdef TOOLS_ENABLED +String CSharpLanguage::make_function(const String &, const String &p_name, const PoolStringArray &p_args) const { // FIXME // - Due to Godot's API limitation this just appends the function to the end of the file // - Use fully qualified name if there is ambiguity @@ -449,10 +463,12 @@ String CSharpLanguage::make_function(const String &p_class, const String &p_name s += ")\n{\n // Replace with function body.\n}\n"; return s; +} #else +String CSharpLanguage::make_function(const String &, const String &, const PoolStringArray &) const { return String(); -#endif } +#endif String CSharpLanguage::_get_indentation() const { #ifdef TOOLS_ENABLED @@ -504,8 +520,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec MonoException *exc = NULL; - GDMonoUtils::StackTrace_GetFrames st_get_frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames); - MonoArray *frames = st_get_frames(p_stack_trace, (MonoObject **)&exc); + MonoArray *frames = invoke_method_thunk(CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames), p_stack_trace, (MonoObject **)&exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); @@ -523,13 +538,13 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec si.resize(frame_count); for (int i = 0; i < frame_count; i++) { - StackInfo &sif = si[i]; + StackInfo &sif = si.write[i]; MonoObject *frame = mono_array_get(frames, MonoObject *, i); MonoString *file_name; int file_line_num; MonoString *method_decl; - get_sf_info(frame, &file_name, &file_line_num, &method_decl, (MonoObject **)&exc); + invoke_method_thunk(get_sf_info, frame, &file_name, &file_line_num, &method_decl, (MonoObject **)&exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); @@ -551,22 +566,20 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec void CSharpLanguage::frame() { - const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle; - - if (task_scheduler_handle.is_valid()) { - MonoObject *task_scheduler = task_scheduler_handle->get_target(); + if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) { + const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle; - if (task_scheduler) { - GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate); + if (task_scheduler_handle.is_valid()) { + MonoObject *task_scheduler = task_scheduler_handle->get_target(); - ERR_FAIL_NULL(thunk); + if (task_scheduler) { + MonoException *exc = NULL; + invoke_method_thunk(CACHED_METHOD_THUNK(GodotTaskScheduler, Activate), task_scheduler, (MonoObject **)&exc); - MonoException *exc = NULL; - thunk(task_scheduler, (MonoObject **)&exc); - - if (exc) { - GDMonoUtils::debug_unhandled_exception(exc); - _UNREACHABLE_(); + if (exc) { + GDMonoUtils::debug_unhandled_exception(exc); + _UNREACHABLE_(); + } } } } @@ -596,24 +609,20 @@ void CSharpLanguage::reload_all_scripts() { #ifdef DEBUG_ENABLED -#ifndef NO_THREADS - lock->lock(); -#endif - List<Ref<CSharpScript> > scripts; - SelfList<CSharpScript> *elem = script_list.first(); - while (elem) { - if (elem->self()->get_path().is_resource_file()) { - scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to gdscript to avoid being erased by accident + { + SCOPED_MUTEX_LOCK(script_instances_mutex); + + SelfList<CSharpScript> *elem = script_list.first(); + while (elem) { + if (elem->self()->get_path().is_resource_file()) { + scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to gdscript to avoid being erased by accident + } + elem = elem->next(); } - elem = elem->next(); } -#ifndef NO_THREADS - lock->unlock(); -#endif - //as scripts are going to be reloaded, must proceed without locking here scripts.sort_custom<CSharpScriptDepSort>(); //update in inheritance dependency order @@ -622,6 +631,7 @@ void CSharpLanguage::reload_all_scripts() { E->get()->load_source_code(E->get()->get_path()); E->get()->reload(true); } + #endif } @@ -631,155 +641,236 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft #ifdef TOOLS_ENABLED MonoReloadNode::get_singleton()->restart_reload_timer(); - reload_assemblies_if_needed(p_soft_reload); + if (is_assembly_reloading_needed()) { + reload_assemblies(p_soft_reload); + } #endif } #ifdef TOOLS_ENABLED -void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { +bool CSharpLanguage::is_assembly_reloading_needed() { - if (gdmono->is_runtime_initialized()) { + if (!gdmono->is_runtime_initialized()) + return false; - GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); + GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; - } + String name = ProjectSettings::get_singleton()->get("application/config/name"); + if (name.empty()) { + name = "UnnamedProject"; + } - if (proj_assembly) { - String proj_asm_path = proj_assembly->get_path(); + name += ".dll"; - 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); - if (!FileAccess::exists(proj_asm_path)) - return; // No assembly to load - } + if (proj_assembly) { + String proj_asm_path = proj_assembly->get_path(); - if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) - return; // Already up to date - } else { - if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name))) - return; // No assembly to load + 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); + if (!FileAccess::exists(proj_asm_path)) + return false; // No assembly to load } + + 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))) + return false; // No assembly to load } -#ifndef NO_THREADS - lock->lock(); -#endif + 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 + + return true; +} + +void CSharpLanguage::reload_assemblies(bool p_soft_reload) { + + if (!gdmono->is_runtime_initialized()) + return; + + // There is no soft reloading with Mono. It's always hard reloading. List<Ref<CSharpScript> > scripts; - SelfList<CSharpScript> *elem = script_list.first(); - while (elem) { - if (elem->self()->get_path().is_resource_file()) { + { + SCOPED_MUTEX_LOCK(script_instances_mutex); - scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to CSharpScript to avoid being erased by accident + 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())); + } } - elem = elem->next(); } -#ifndef NO_THREADS - lock->unlock(); + List<Ref<CSharpScript> > to_reload; + + // 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); + + // 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 *E = script->instances.front(); E; E = E->next()) { + script->pending_reload_instances.insert(E->get()->get_instance_id()); + } + +#ifdef TOOLS_ENABLED + for (Set<PlaceHolderScriptInstance *>::Element *E = script->placeholders.front(); E; E = E->next()) { + script->pending_reload_instances.insert(E->get()->get_owner()->get_instance_id()); + } #endif - //when someone asks you why dynamically typed languages are easier to write.... + // FIXME: What about references? Need to keep them alive if only managed code references them. - Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload; + // Save state and remove script from instances + Map<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state; - //as scripts are going to be reloaded, must proceed without locking here + while (script->instances.front()) { + Object *obj = script->instances.front()->get(); + // Save instance info + CSharpScript::StateBackup state; - scripts.sort_custom<CSharpScriptDepSort>(); //update in inheritance dependency order + ERR_CONTINUE(!obj->get_script_instance()); - for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { + // TODO: Proper state backup (Not only variants, serialize managed state of scripts) + obj->get_script_instance()->get_property_state(state.properties); - to_reload.insert(E->get(), Map<ObjectID, List<Pair<StringName, Variant> > >()); + Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle; + if (gchandle.is_valid()) + gchandle->release(); - if (!p_soft_reload) { + owners_map[obj->get_instance_id()] = state; + obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload) + } - //save state and remove script from instances - Map<ObjectID, List<Pair<StringName, Variant> > > &map = to_reload[E->get()]; + script->_clear(); + } - while (E->get()->instances.front()) { - Object *obj = E->get()->instances.front()->get(); - //save instance info - List<Pair<StringName, Variant> > state; - if (obj->get_script_instance()) { + // Do domain reload + if (gdmono->reload_scripts_domain() != OK) { + // Failed to reload the scripts domain + // Make sure to add the scripts back to their owners before returning + for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) { + Ref<CSharpScript> scr = E->get(); - obj->get_script_instance()->get_property_state(state); + for (const Map<ObjectID, CSharpScript::StateBackup>::Element *F = scr->pending_reload_state.front(); F; F = F->next()) { + Object *obj = ObjectDB::get_instance(F->key()); - Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle; - if (gchandle.is_valid()) - gchandle->release(); + if (!obj) + continue; - map[obj->get_instance_id()] = state; - obj->set_script(RefPtr()); - } - } + ObjectID obj_id = obj->get_instance_id(); - //same thing for placeholders - while (E->get()->placeholders.size()) { + // Use a placeholder for now to avoid losing the state when saving a scene - Object *obj = E->get()->placeholders.front()->get()->get_owner(); - //save instance info - List<Pair<StringName, Variant> > state; - if (obj->get_script_instance()) { - obj->get_script_instance()->get_property_state(state); - map[obj->get_instance_id()] = state; - obj->set_script(RefPtr()); + obj->set_script(scr.get_ref_ptr()); + + PlaceHolderScriptInstance *placeholder = scr->placeholder_instance_create(obj); + obj->set_script_instance(placeholder); + + // Even though build didn't fail, this tells the placeholder to keep properties and + // it allows using property_set_fallback for restoring the state without a valid script. + placeholder->set_build_failed(true); + + // Restore Variant properties state, it will be kept by the placeholder until the next script reloading + for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) { + placeholder->property_set_fallback(G->get().first, G->get().second, NULL); } - } - for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get()->pending_reload_state.front(); F; F = F->next()) { - map[F->key()] = F->get(); //pending to reload, use this one instead + scr->pending_reload_state.erase(obj_id); } - - E->get()->_clear(); } - } - - if (gdmono->reload_scripts_domain() != OK) return; + } - for (Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > >::Element *E = to_reload.front(); E; E = E->next()) { + for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) { - Ref<CSharpScript> scr = E->key(); + Ref<CSharpScript> scr = E->get(); scr->exports_invalidated = true; scr->signals_invalidated = true; scr->reload(p_soft_reload); scr->update_exports(); - //restore state if saved - for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get().front(); F; F = F->next()) { + { +#ifdef DEBUG_ENABLED + for (Set<ObjectID>::Element *F = scr->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); + continue; + } - Object *obj = ObjectDB::get_instance(F->key()); - if (!obj) - continue; + ScriptInstance *si = obj->get_script_instance(); - if (!p_soft_reload) { - //clear it just in case (may be a pending reload state) - obj->set_script(RefPtr()); - } - obj->set_script(scr.get_ref_ptr()); - if (!obj->get_script_instance()) { - //failed, save reload state for next time if not saved - if (!scr->pending_reload_state.has(obj->get_instance_id())) { - scr->pending_reload_state[obj->get_instance_id()] = F->get(); +#ifdef TOOLS_ENABLED + if (si) { + // If the script instance is not null, then it must be a placeholder. + // Non-placeholder script instances are removed in godot_icall_Object_Disposed. + CRASH_COND(!si->is_placeholder()); + + if (scr->is_tool() || ScriptServer::is_scripting_enabled()) { + // Replace placeholder with a script instance + + CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id]; + + // Backup placeholder script instance state before replacing it with a script instance + obj->get_script_instance()->get_property_state(state_backup.properties); + + ScriptInstance *script_instance = scr->instance_create(obj); + + if (script_instance) { + scr->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; + } +#else + CRASH_COND(si != NULL); +#endif + // Re-create script instance + + obj->set_script(scr.get_ref_ptr()); // will create the script instance as well + + // TODO: Restore serialized state + + 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); } - continue; - } - for (List<Pair<StringName, Variant> >::Element *G = F->get().front(); G; G = G->next()) { - obj->get_script_instance()->set(G->get().first, G->get().second); + scr->pending_reload_state.erase(obj_id); } +#endif - scr->pending_reload_state.erase(obj->get_instance_id()); //as it reloaded, remove pending state + scr->pending_reload_instances.clear(); } - - //if instance states were saved, set them! } + // FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative. if (Engine::get_singleton()->is_editor_hint()) { EditorNode::get_singleton()->get_inspector()->update_tree(); NodeDock::singleton->update_lists(); @@ -787,6 +878,49 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { } #endif +void CSharpLanguage::project_assembly_loaded() { + + scripts_metadata.clear(); + + String scripts_metadata_filename = "scripts_metadata."; + +#ifdef TOOLS_ENABLED + scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player"; +#else +#ifdef DEBUG_ENABLED + scripts_metadata_filename += "debug"; +#else + scripts_metadata_filename += "release"; +#endif +#endif + + String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename); + + if (FileAccess::exists(scripts_metadata_path)) { + String old_json; + + Error ferr = read_all_file_utf8(scripts_metadata_path, old_json); + ERR_FAIL_COND(ferr != OK); + + Variant old_dict_var; + String err_str; + int err_line; + Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line); + if (json_err != OK) { + ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")"); + return; + } + + scripts_metadata = old_dict_var.operator Dictionary(); + + print_verbose("Successfully loaded scripts metadata"); + } else { + if (!Engine::get_singleton()->is_editor_hint()) { + ERR_PRINT("Missing scripts metadata file"); + } + } +} + void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("cs"); @@ -857,6 +991,35 @@ void CSharpLanguage::set_language_index(int p_idx) { lang_idx = p_idx; } +void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) { + + if (!p_gchandle->is_released()) { // Do not lock unnecessarily + SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex); + p_gchandle->release(); + } +} + +void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle) { + + uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_expected_obj); // We might lock after this, so pin it + + if (!p_gchandle->is_released()) { // Do not lock unnecessarily + SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex); + + MonoObject *target = p_gchandle->get_target(); + + // We release the gchandle if it points to the MonoObject* we expect (otherwise it was + // already released and could have been replaced) or if we can't get its target MonoObject* + // (which doesn't necessarily mean it was released, and we want it released in order to + // avoid locking other threads unnecessarily). + if (target == p_expected_obj || target == NULL) { + p_gchandle->release(); + } + } + + MonoGCHandle::free_handle(pinned_gchandle); +} + CSharpLanguage::CSharpLanguage() { ERR_FAIL_COND(singleton); @@ -867,11 +1030,13 @@ CSharpLanguage::CSharpLanguage() { gdmono = NULL; #ifdef NO_THREADS - lock = NULL; - gchandle_bind_lock = NULL; + script_instances_mutex = NULL; + script_gchandle_release_mutex = NULL; + language_bind_mutex = NULL; #else - lock = Mutex::create(); - script_bind_lock = Mutex::create(); + script_instances_mutex = Mutex::create(); + script_gchandle_release_mutex = Mutex::create(); + language_bind_mutex = Mutex::create(); #endif lang_idx = -1; @@ -881,14 +1046,19 @@ CSharpLanguage::~CSharpLanguage() { finish(); - if (lock) { - memdelete(lock); - lock = NULL; + if (script_instances_mutex) { + memdelete(script_instances_mutex); + script_instances_mutex = NULL; + } + + if (language_bind_mutex) { + memdelete(language_bind_mutex); + language_bind_mutex = NULL; } - if (script_bind_lock) { - memdelete(script_bind_lock); - script_bind_lock = NULL; + if (script_gchandle_release_mutex) { + memdelete(script_gchandle_release_mutex); + script_gchandle_release_mutex = NULL; } singleton = NULL; @@ -919,6 +1089,19 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) { ERR_FAIL_NULL_V(mono_object, NULL); + CSharpScriptBinding script_binding; + + script_binding.type_name = type_name; + script_binding.wrapper_class = type_class; // cache + script_binding.gchandle = MonoGCHandle::create_strong(mono_object); + + void *data; + + { + SCOPED_MUTEX_LOCK(language_bind_mutex); + data = (void *)script_bindings.insert(p_object, script_binding); + } + // Tie managed to unmanaged Reference *ref = Object::cast_to<Reference>(p_object); @@ -926,23 +1109,11 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) { // 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: _GodotSharp::_dispose_object(Object *p_object) + // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) ref->reference(); } - Ref<MonoGCHandle> gchandle = MonoGCHandle::create_strong(mono_object); - -#ifndef NO_THREADS - script_bind_lock->lock(); -#endif - - void *data = (void *)gchandle_bindings.insert(p_object, gchandle); - -#ifndef NO_THREADS - script_bind_lock->unlock(); -#endif - return data; } @@ -950,7 +1121,7 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) { if (GDMono::get_singleton() == NULL) { #ifdef DEBUG_ENABLED - CRASH_COND(!gchandle_bindings.empty()); + CRASH_COND(!script_bindings.empty()); #endif // Mono runtime finalized, all the gchandle bindings were already released return; @@ -959,23 +1130,82 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) { if (finalizing) return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there -#ifndef NO_THREADS - script_bind_lock->lock(); + { + SCOPED_MUTEX_LOCK(language_bind_mutex); + + Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data; + + // Set the native instance field to IntPtr.Zero, if not yet garbage collected + MonoObject *mono_object = data->value().gchandle->get_target(); + if (mono_object) { + CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL); + } + + script_bindings.erase(data); + } +} + +void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { + + Reference *ref_owner = Object::cast_to<Reference>(p_object); + +#ifdef DEBUG_ENABLED + CRASH_COND(!ref_owner); #endif - Map<Object *, Ref<MonoGCHandle> >::Element *data = (Map<Object *, Ref<MonoGCHandle> >::Element *)p_data; + void *data = p_object->get_script_instance_binding(get_language_index()); + if (!data) + return; + Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle; - // Set the native instance field to IntPtr.Zero, if not yet garbage collected - MonoObject *mono_object = data->value()->get_target(); - if (mono_object) { - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL); + 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, + // so the owner must hold the managed side alive again to avoid it from being GCed. + + MonoObject *target = gchandle->get_target(); + if (!target) + return; // Called after the managed side was collected, so nothing to do here + + // Release the current weak handle and replace it with a strong handle. + uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(target); + gchandle->release(); + gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE); } +} - gchandle_bindings.erase(data); +bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { -#ifndef NO_THREADS - script_bind_lock->unlock(); + Reference *ref_owner = Object::cast_to<Reference>(p_object); + +#ifdef DEBUG_ENABLED + CRASH_COND(!ref_owner); #endif + + int refcount = ref_owner->reference_get_count(); + + void *data = p_object->get_script_instance_binding(get_language_index()); + if (!data) + return refcount == 0; + Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle; + + if (refcount == 1 && !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. + + MonoObject *target = gchandle->get_target(); + if (!target) + return refcount == 0; // Called after the managed side was collected, so nothing to do here + + // Release the current strong handle and replace it with a weak handle. + uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(target); + gchandle->release(); + gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE); + + return false; + } + + return refcount == 0; } CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle) { @@ -998,9 +1228,8 @@ CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpS } MonoObject *CSharpInstance::get_mono_object() const { -#ifdef DEBUG_ENABLED - CRASH_COND(gchandle.is_null()); -#endif + + ERR_FAIL_COND_V(gchandle.is_null(), NULL); return gchandle->get_target(); } @@ -1024,7 +1253,7 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { GDMonoProperty *property = script->script_class->get_property(p_name); if (property) { - property->set_value(mono_object, GDMonoMarshal::variant_to_mono_object(p_value)); + property->set_value(mono_object, GDMonoMarshal::variant_to_mono_object(p_value, property->get_type())); return true; } @@ -1044,7 +1273,7 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { MonoObject *ret = method->invoke(mono_object, args); - if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret) == true) + if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret)) return true; break; @@ -1228,10 +1457,12 @@ void CSharpInstance::call_multilevel_reversed(const StringName &p_method, const call_multilevel(p_method, p_args, p_argcount); } -void CSharpInstance::_reference_owner_unsafe() { +bool CSharpInstance::_reference_owner_unsafe() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); + CRASH_COND(owner == NULL); + CRASH_COND(unsafe_referenced); // already referenced #endif // Unsafe refcount increment. The managed instance also counts as a reference. @@ -1240,66 +1471,144 @@ void CSharpInstance::_reference_owner_unsafe() { // See: _unreference_owner_unsafe() // May not me referenced yet, so we must use init_ref() instead of reference() - Object::cast_to<Reference>(owner)->init_ref(); + bool success = Object::cast_to<Reference>(owner)->init_ref(); + unsafe_referenced = success; + return success; } -void CSharpInstance::_unreference_owner_unsafe() { +bool CSharpInstance::_unreference_owner_unsafe() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); + CRASH_COND(owner == NULL); #endif + if (!unsafe_referenced) + return false; // Already unreferenced + + unsafe_referenced = false; + // Called from CSharpInstance::mono_object_disposed() or ~CSharpInstance() // Unsafe refcount decrement. The managed instance also counts as a reference. // See: _reference_owner_unsafe() - if (Object::cast_to<Reference>(owner)->unreference()) { + bool die = static_cast<Reference *>(owner)->unreference(); + + if (die) { memdelete(owner); owner = NULL; } + + return die; } -void CSharpInstance::mono_object_disposed() { +MonoObject *CSharpInstance::_internal_new_managed() { +#ifdef DEBUG_ENABLED + CRASH_COND(!gchandle.is_valid()); +#endif + + CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); + + ERR_FAIL_NULL_V(owner, NULL); + ERR_FAIL_COND_V(script.is_null(), NULL); if (base_ref) - _unreference_owner_unsafe(); + _reference_owner_unsafe(); + + MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script->script_class->get_mono_ptr()); + + if (!mono_object) { + script = Ref<CSharpScript>(); + owner->set_script_instance(NULL); + ERR_EXPLAIN("Failed to allocate memory for the object"); + ERR_FAIL_V(NULL); + } + + CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner); + + // Construct + GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0); + ctor->invoke_raw(mono_object, NULL); + + // Tie managed to unmanaged + gchandle = MonoGCHandle::create_strong(mono_object); + + return mono_object; +} + +void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { + +#ifdef DEBUG_ENABLED + CRASH_COND(base_ref); + CRASH_COND(gchandle.is_null()); +#endif + CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); +} + +void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_owner_deleted) { + +#ifdef DEBUG_ENABLED + CRASH_COND(!base_ref); + CRASH_COND(gchandle.is_null()); +#endif + if (_unreference_owner_unsafe()) { + r_owner_deleted = true; + } else { + r_owner_deleted = false; + CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); + if (p_is_finalizer && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { + // If the native instance is still alive, then it was + // referenced from another thread before the finalizer could + // unreference it and delete it, so we want to keep it. + // GC.ReRegisterForFinalize(this) is not safe because the objects + // referenced by this could have already been collected. + // Instead we will create a new managed instance here. + _internal_new_managed(); + } + } } void CSharpInstance::refcount_incremented() { +#ifdef DEBUG_ENABLED CRASH_COND(!base_ref); + CRASH_COND(owner == NULL); +#endif Reference *ref_owner = Object::cast_to<Reference>(owner); - if (ref_owner->reference_get_count() > 1) { // The managed side also holds a reference, hence 1 instead of 0 + 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, // so the owner must hold the managed side alive again to avoid it from being GCed. // Release the current weak handle and replace it with a strong handle. - uint32_t strong_gchandle = MonoGCHandle::make_strong_handle(gchandle->get_target()); + uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(gchandle->get_target()); gchandle->release(); - gchandle->set_handle(strong_gchandle); + gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE); } } bool CSharpInstance::refcount_decremented() { +#ifdef DEBUG_ENABLED CRASH_COND(!base_ref); + CRASH_COND(owner == NULL); +#endif Reference *ref_owner = Object::cast_to<Reference>(owner); int refcount = ref_owner->reference_get_count(); - if (refcount == 1) { // The managed side also holds a reference, hence 1 instead of 0 + if (refcount == 1 && !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. // Release the current strong handle and replace it with a weak handle. - uint32_t weak_gchandle = MonoGCHandle::make_weak_handle(gchandle->get_target()); + uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(gchandle->get_target()); gchandle->release(); - gchandle->set_handle(weak_gchandle); + gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE); return false; } @@ -1313,18 +1622,20 @@ MultiplayerAPI::RPCMode CSharpInstance::_member_get_rpc_mode(GDMonoClassMember * if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) return MultiplayerAPI::RPC_MODE_REMOTE; - if (p_member->has_attribute(CACHED_CLASS(SyncAttribute))) - return MultiplayerAPI::RPC_MODE_SYNC; if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) return MultiplayerAPI::RPC_MODE_MASTER; + if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) + return MultiplayerAPI::RPC_MODE_PUPPET; if (p_member->has_attribute(CACHED_CLASS(SlaveAttribute))) - return MultiplayerAPI::RPC_MODE_SLAVE; + return MultiplayerAPI::RPC_MODE_PUPPET; if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute))) return MultiplayerAPI::RPC_MODE_REMOTESYNC; + if (p_member->has_attribute(CACHED_CLASS(SyncAttribute))) + return MultiplayerAPI::RPC_MODE_REMOTESYNC; if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute))) return MultiplayerAPI::RPC_MODE_MASTERSYNC; - if (p_member->has_attribute(CACHED_CLASS(SlaveSyncAttribute))) - return MultiplayerAPI::RPC_MODE_SLAVESYNC; + if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute))) + return MultiplayerAPI::RPC_MODE_PUPPETSYNC; return MultiplayerAPI::RPC_MODE_DISABLED; } @@ -1368,25 +1679,64 @@ MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab void CSharpInstance::notification(int p_notification) { - MonoObject *mono_object = get_mono_object(); - if (p_notification == Object::NOTIFICATION_PREDELETE) { - if (mono_object != NULL) { // otherwise it was collected, and the finalizer already called NOTIFICATION_PREDELETE - call_notification_no_check(mono_object, p_notification); - // Set the native instance field to IntPtr.Zero - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL); + // When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose(). + // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed + // to be sent at least once, which happens right before the call to the destructor. + + predelete_notified = true; + + if (base_ref) { + // It's not safe to proceed if the owner derives Reference and the refcount reached 0. + // At this point, Dispose() was already called (manually or from the finalizer) so + // that's not a problem. The refcount wouldn't have reached 0 otherwise, since the + // managed side references it and Dispose() needs to be called to release it. + // However, this means C# Reference scripts can't receive NOTIFICATION_PREDELETE, but + // this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784 + return; } + + _call_notification(p_notification); + + MonoObject *mono_object = get_mono_object(); + ERR_FAIL_NULL(mono_object); + + MonoException *exc = NULL; + GDMonoUtils::dispose(mono_object, &exc); + + if (exc) { + GDMonoUtils::set_pending_exception(exc); + } + return; } - call_notification_no_check(mono_object, p_notification); + _call_notification(p_notification); } -void CSharpInstance::call_notification_no_check(MonoObject *p_mono_object, int p_notification) { - Variant value = p_notification; - const Variant *args[1] = { &value }; +void CSharpInstance::_call_notification(int p_notification) { + + MonoObject *mono_object = get_mono_object(); + ERR_FAIL_NULL(mono_object); + + // Custom version of _call_multilevel, optimized for _notification + + uint32_t arg = p_notification; + void *args[1] = { &arg }; + StringName method_name = CACHED_STRING_NAME(_notification); + + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + GDMonoMethod *method = top->get_method(method_name, 1); + + if (method) { + method->invoke_raw(mono_object, args); + return; + } - _call_multilevel(p_mono_object, CACHED_STRING_NAME(_notification), args, 1); + top = top->get_parent_class(); + } } Ref<Script> CSharpInstance::get_script() const { @@ -1399,30 +1749,48 @@ ScriptLanguage *CSharpInstance::get_language() { return CSharpLanguage::get_singleton(); } -CSharpInstance::CSharpInstance() { - - owner = NULL; - base_ref = false; - ref_dying = false; +CSharpInstance::CSharpInstance() : + owner(NULL), + base_ref(false), + ref_dying(false), + unsafe_referenced(false), + predelete_notified(false), + destructing_script_instance(false) { } CSharpInstance::~CSharpInstance() { if (gchandle.is_valid()) { + if (!predelete_notified && !ref_dying) { + // This destructor is not called from the owners destructor. + // This could be being called from the owner's set_script_instance method, + // meaning this script is being replaced with another one. If this is the case, + // we must call Dispose here, because Dispose calls owner->set_script_instance(NULL) + // and that would mess up with the new script instance if called later. + + MonoObject *mono_object = gchandle->get_target(); + + if (mono_object) { + MonoException *exc = NULL; + destructing_script_instance = true; + GDMonoUtils::dispose(mono_object, &exc); + destructing_script_instance = false; + + if (exc) { + GDMonoUtils::set_pending_exception(exc); + } + } + } + gchandle->release(); // Make sure it's released } - if (base_ref && !ref_dying) { // it may be called from the owner's destructor -#ifdef DEBUG_ENABLED - CRASH_COND(!owner); // dunno, just in case -#endif + if (base_ref && !ref_dying && owner) { // it may be called from the owner's destructor _unreference_owner_unsafe(); } if (script.is_valid() && owner) { -#ifndef NO_THREADS - CSharpLanguage::singleton->lock->lock(); -#endif + SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); #ifdef DEBUG_ENABLED // CSharpInstance must not be created unless it's going to be added to the list for sure @@ -1432,10 +1800,6 @@ CSharpInstance::~CSharpInstance() { #else script->instances.erase(owner); #endif - -#ifndef NO_THREADS - CSharpLanguage::singleton->lock->unlock(); -#endif } } @@ -1466,8 +1830,12 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List bool CSharpScript::_update_exports() { #ifdef TOOLS_ENABLED - if (!valid) + if (!valid) { + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->set_build_failed(true); + } return false; + } bool changed = false; @@ -1480,26 +1848,27 @@ bool CSharpScript::_update_exports() { exported_members_cache.clear(); exported_members_defval_cache.clear(); - // We are creating a temporary new instance of the class here to get the default value - // TODO Workaround. Should be replaced with IL opcodes analysis + // 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()); - if (tmp_object) { - CACHED_FIELD(GodotObject, ptr)->set_value_raw(tmp_object, tmp_object); // FIXME WTF is this workaround + if (!tmp_object) { + ERR_PRINT("Failed to create temporary MonoObject"); + return false; + } - GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - MonoException *exc = NULL; - ctor->invoke(tmp_object, NULL, &exc); + uint32_t tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed) - if (exc) { - ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(exc); - tmp_object = NULL; - ERR_FAIL_V(false); - } - } else { - ERR_PRINT("Failed to create temporary MonoObject"); + GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); + MonoException *ctor_exc = NULL; + ctor->invoke(tmp_object, NULL, &ctor_exc); + + if (ctor_exc) { + MonoGCHandle::free_handle(tmp_pinned_gchandle); + tmp_object = NULL; + + ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); + GDMonoUtils::debug_print_unhandled_exception(ctor_exc); return false; } @@ -1560,6 +1929,19 @@ bool CSharpScript::_update_exports() { top = top->get_parent_class(); } + + // Dispose the temporary managed instance + + MonoException *exc = NULL; + GDMonoUtils::dispose(tmp_object, &exc); + + if (exc) { + ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); + GDMonoUtils::debug_print_unhandled_exception(exc); + } + + MonoGCHandle::free_handle(tmp_pinned_gchandle); + tmp_object = NULL; } if (placeholders.size()) { @@ -1569,6 +1951,7 @@ bool CSharpScript::_update_exports() { _update_exports_values(values, propnames); for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->set_build_failed(false); E->get()->update(propnames, values); } } @@ -1609,7 +1992,7 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> ¶ms) { if (p_delegate->has_attribute(CACHED_CLASS(SignalAttribute))) { - MonoType *raw_type = GDMonoClass::get_raw_type(p_delegate); + MonoType *raw_type = p_delegate->get_mono_type(); if (mono_type_get_type(raw_type) == MONO_TYPE_CLASS) { // Arguments are accessibles as arguments of .Invoke method @@ -1643,6 +2026,10 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve } #ifdef TOOLS_ENABLED +/** + * 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(GDMonoClass *p_class, GDMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported) { StringName name = p_member->get_name(); @@ -1668,49 +2055,100 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, GDMonoClassMember *p Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type); - if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) { - if (p_member->get_member_type() == GDMonoClassMember::MEMBER_TYPE_PROPERTY) { - GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); - if (!property->has_getter() || !property->has_setter()) { - ERR_PRINTS("Cannot export property because it does not provide a getter or a setter: " + p_class->get_full_name() + "." + name.operator String()); - return false; - } + if (!p_member->has_attribute(CACHED_CLASS(ExportAttribute))) { + r_prop_info = PropertyInfo(variant_type, name.operator String(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); + r_exported = false; + return true; + } + + if (p_member->get_member_type() == GDMonoClassMember::MEMBER_TYPE_PROPERTY) { + GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); + if (!property->has_getter() || !property->has_setter()) { + ERR_PRINTS("Cannot export property because it does not provide a getter or a setter: " + p_class->get_full_name() + "." + name.operator String()); + return false; } + } - MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); + MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); - PropertyHint hint; - String hint_string; + PropertyHint hint = PROPERTY_HINT_NONE; + String hint_string; - if (variant_type == Variant::NIL) { - ERR_PRINTS("Unknown type of exported member: " + p_class->get_full_name() + "." + name.operator String()); - return false; - } else if (variant_type == Variant::INT && type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(type.type_class->get_mono_ptr())) { - variant_type = Variant::INT; - hint = PROPERTY_HINT_ENUM; + if (variant_type == Variant::NIL) { + ERR_PRINTS("Unknown type of exported member: " + p_class->get_full_name() + "." + name.operator String()); + return false; + } else if (variant_type == Variant::INT && type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(type.type_class->get_mono_ptr())) { + variant_type = Variant::INT; + hint = PROPERTY_HINT_ENUM; + + Vector<MonoClassField *> fields = type.type_class->get_enum_fields(); - Vector<MonoClassField *> fields = type.type_class->get_enum_fields(); + MonoType *enum_basetype = mono_class_enum_basetype(type.type_class->get_mono_ptr()); - for (int i = 0; i < fields.size(); i++) { - if (i > 0) - hint_string += ","; - hint_string += mono_field_get_name(fields[i]); + String name_only_hint_string; + + // True: enum Foo { Bar, Baz, Quux } + // True: enum Foo { Bar = 0, Baz = 1, Quux = 2 } + // False: enum Foo { Bar = 0, Baz = 7, Quux = 5 } + bool uses_default_values = true; + + for (int i = 0; i < fields.size(); i++) { + MonoClassField *field = fields[i]; + + if (i > 0) { + hint_string += ","; + name_only_hint_string += ","; } - } else if (variant_type == Variant::OBJECT && CACHED_CLASS(GodotReference)->is_assignable_from(type.type_class)) { - hint = PROPERTY_HINT_RESOURCE_TYPE; - hint_string = NATIVE_GDMONOCLASS_NAME(type.type_class); - } else { - hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); - hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); + + String enum_field_name = mono_field_get_name(field); + hint_string += enum_field_name; + name_only_hint_string += enum_field_name; + + // TODO: + // Instead of using mono_field_get_value_object, we can do this without boxing. Check the + // internal mono functions: ves_icall_System_Enum_GetEnumValuesAndNames and the get_enum_field. + + MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, NULL); + + if (val_obj == NULL) { + ERR_PRINTS("Failed to get '" + enum_field_name + "' constant enum value of exported member: " + + p_class->get_full_name() + "." + name.operator String()); + return false; + } + + bool r_error; + uint64_t val = GDMonoUtils::unbox_enum_value(val_obj, enum_basetype, r_error); + if (r_error) { + ERR_PRINTS("Failed to unbox '" + enum_field_name + "' constant enum value of exported member: " + + p_class->get_full_name() + "." + name.operator String()); + return false; + } + + if (val != i) { + uses_default_values = false; + } + + hint_string += ":"; + hint_string += String::num_uint64(val); } - r_prop_info = PropertyInfo(variant_type, name.operator String(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE); - r_exported = true; + if (uses_default_values) { + // If we use the format NAME:VAL, that's what the editor displays. + // That's annoying if the user is not using custom values for the enum constants. + // This may not be needed in the future if the editor is changed to not display values. + hint_string = name_only_hint_string; + } + } else if (variant_type == Variant::OBJECT && CACHED_CLASS(GodotReference)->is_assignable_from(type.type_class)) { + hint = PROPERTY_HINT_RESOURCE_TYPE; + hint_string = NATIVE_GDMONOCLASS_NAME(type.type_class); } else { - r_prop_info = PropertyInfo(variant_type, name.operator String(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); - r_exported = false; + hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); + hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); } + r_prop_info = PropertyInfo(variant_type, name.operator String(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE); + r_exported = true; + return true; } #endif @@ -1865,7 +2303,28 @@ bool CSharpScript::can_instance() const { } #endif - return valid || (!tool && !ScriptServer::is_scripting_enabled()); +#ifdef TOOLS_ENABLED + bool extra_cond = tool || ScriptServer::is_scripting_enabled(); +#else + bool extra_cond = true; +#endif + + // FIXME Need to think this through better. + // For tool scripts, this will never fire if the class is not found. That's because we + // don't know if it's a tool script if we can't find the class to access the attributes. + if (extra_cond && !script_class) { + if (GDMono::get_singleton()->get_project_assembly() == NULL) { + // The project assembly is not loaded + ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path()); + ERR_FAIL_V(NULL); + } else { + // The project assembly is loaded, but the class could not found + ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path()); + ERR_FAIL_V(NULL); + } + } + + return valid && extra_cond; } StringName CSharpScript::get_instance_base_type() const { @@ -1901,15 +2360,13 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg ERR_FAIL_V(NULL); } -#ifndef NO_THREADS - CSharpLanguage::singleton->lock->lock(); -#endif - - instances.insert(instance->owner); + // Tie managed to unmanaged + instance->gchandle = MonoGCHandle::create_strong(mono_object); -#ifndef NO_THREADS - CSharpLanguage::singleton->lock->unlock(); -#endif + { + SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + instances.insert(instance->owner); + } CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, instance->owner); @@ -1917,9 +2374,6 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); ctor->invoke(mono_object, p_args); - // Tie managed to unmanaged - instance->gchandle = MonoGCHandle::create_strong(mono_object); - /* STEP 3, PARTY */ //@TODO make thread safe @@ -1963,30 +2417,9 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::Call ScriptInstance *CSharpScript::instance_create(Object *p_this) { - ERR_FAIL_COND_V(!valid, NULL); - - if (!tool && !ScriptServer::is_scripting_enabled()) { -#ifdef TOOLS_ENABLED - PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(CSharpLanguage::get_singleton(), Ref<Script>(this), p_this)); - placeholders.insert(si); - _update_exports(); - return si; -#else - return NULL; +#ifdef DEBUG_ENABLED + CRASH_COND(!valid); #endif - } - - if (!script_class) { - if (GDMono::get_singleton()->get_project_assembly() == NULL) { - // The project assembly is not loaded - ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path()); - ERR_FAIL_V(NULL); - } - - // The project assembly is loaded, but the class could not found - ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path()); - ERR_FAIL_V(NULL); - } if (native) { String native_name = native->get_name(); @@ -2003,19 +2436,22 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this), unchecked_error); } -bool CSharpScript::instance_has(const Object *p_this) const { +PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_this) { -#ifndef NO_THREADS - CSharpLanguage::singleton->lock->lock(); +#ifdef TOOLS_ENABLED + PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(CSharpLanguage::get_singleton(), Ref<Script>(this), p_this)); + placeholders.insert(si); + _update_exports(); + return si; +#else + return NULL; #endif +} - bool ret = instances.has((Object *)p_this); - -#ifndef NO_THREADS - CSharpLanguage::singleton->lock->unlock(); -#endif +bool CSharpScript::instance_has(const Object *p_this) const { - return ret; + SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + return instances.has((Object *)p_this); } bool CSharpScript::has_source_code() const { @@ -2038,37 +2474,81 @@ void CSharpScript::set_source_code(const String &p_code) { #endif } +void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { + + if (!script_class) + return; + + // TODO: Filter out things unsuitable for explicit calls, like constructors. + const Vector<GDMonoMethod *> &methods = script_class->get_all_methods(); + for (int i = 0; i < methods.size(); ++i) { + p_list->push_back(methods[i]->get_method_info()); + } +} + bool CSharpScript::has_method(const StringName &p_method) const { + if (!script_class) + return false; + return script_class->has_fetched_method_unknown_params(p_method); } -Error CSharpScript::reload(bool p_keep_state) { +MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { -#ifndef NO_THREADS - CSharpLanguage::singleton->lock->lock(); -#endif + if (!script_class) + return MethodInfo(); - bool has_instances = instances.size(); + GDMonoClass *top = script_class; -#ifndef NO_THREADS - CSharpLanguage::singleton->lock->unlock(); -#endif + while (top && top != native) { + GDMonoMethod *params = top->get_fetched_method_unknown_params(p_method); + if (params) { + return params->get_method_info(); + } + + top = top->get_parent_class(); + } + + return MethodInfo(); +} + +Error CSharpScript::reload(bool p_keep_state) { + + bool has_instances; + { + SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + has_instances = instances.size(); + } ERR_FAIL_COND_V(!p_keep_state && has_instances, ERR_ALREADY_IN_USE); GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly(); if (project_assembly) { - script_class = project_assembly->get_object_derived_class(name); + const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path()); + if (script_metadata_var) { + Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"]; + const Variant *namespace_ = script_metadata.getptr("namespace"); + const Variant *class_name = script_metadata.getptr("class_name"); + ERR_FAIL_NULL_V(namespace_, ERR_BUG); + ERR_FAIL_NULL_V(class_name, ERR_BUG); + GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String()); + if (klass) { + bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(klass); + ERR_FAIL_COND_V(!obj_type, ERR_BUG); + script_class = klass; + } + } else { + // Missing script metadata. Fallback to legacy method + script_class = project_assembly->get_object_derived_class(name); + } valid = script_class != NULL; if (script_class) { #ifdef DEBUG_ENABLED - OS::get_singleton()->print(String("Found class " + script_class->get_namespace() + "." + - script_class->get_name() + " for script " + get_path() + "\n") - .utf8()); + print_verbose("Found class " + script_class->get_namespace() + "." + script_class->get_name() + " for script " + get_path()); #endif tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute)); @@ -2190,29 +2670,14 @@ int CSharpScript::get_member_line(const StringName &p_member) const { Error CSharpScript::load_source_code(const String &p_path) { - PoolVector<uint8_t> sourcef; - Error err; - FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); - ERR_FAIL_COND_V(err != OK, err); - - int len = f->get_len(); - sourcef.resize(len + 1); - PoolVector<uint8_t>::Write w = sourcef.write(); - int r = f->get_buffer(w.ptr(), len); - f->close(); - memdelete(f); - ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN); - w[len] = 0; - - String s; - if (s.parse_utf8((const char *)w.ptr())) { - - ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); - ERR_FAIL_V(ERR_INVALID_DATA); + Error ferr = read_all_file_utf8(p_path, source); + if (ferr != OK) { + if (ferr == ERR_INVALID_DATA) { + ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); + } + ERR_FAIL_V(ferr); } - source = s; - #ifdef TOOLS_ENABLED source_changed_cache = true; #endif @@ -2240,35 +2705,19 @@ CSharpScript::CSharpScript() : _resource_path_changed(); #ifdef DEBUG_ENABLED - -#ifndef NO_THREADS - CSharpLanguage::get_singleton()->lock->lock(); -#endif - - CSharpLanguage::get_singleton()->script_list.add(&script_list); - -#ifndef NO_THREADS - CSharpLanguage::get_singleton()->lock->unlock(); + { + SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + CSharpLanguage::get_singleton()->script_list.add(&this->script_list); + } #endif - -#endif // DEBUG_ENABLED } CSharpScript::~CSharpScript() { #ifdef DEBUG_ENABLED - -#ifndef NO_THREADS - CSharpLanguage::get_singleton()->lock->lock(); -#endif - - CSharpLanguage::get_singleton()->script_list.remove(&script_list); - -#ifndef NO_THREADS - CSharpLanguage::get_singleton()->lock->unlock(); + SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + CSharpLanguage::get_singleton()->script_list.remove(&this->script_list); #endif - -#endif // DEBUG_ENABLED } /*************** RESOURCE ***************/ @@ -2362,7 +2811,8 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r "Compile", ProjectSettings::get_singleton()->globalize_path(p_path)); } else { - ERR_PRINTS("Cannot add " + p_path + " to the C# project because it could not be created."); + ERR_PRINTS("Failed to create C# project"); + ERR_PRINTS("Cannot add " + p_path + " to the C# project"); } } #endif diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index df597ba776..08466bae58 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -31,10 +31,10 @@ #ifndef CSHARP_SCRIPT_H #define CSHARP_SCRIPT_H -#include "io/resource_loader.h" -#include "io/resource_saver.h" -#include "script_language.h" -#include "self_list.h" +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#include "core/script_language.h" +#include "core/self_list.h" #include "mono_gc_handle.h" #include "mono_gd/gd_mono.h" @@ -48,6 +48,8 @@ class CSharpLanguage; #ifdef NO_SAFE_CAST template <typename TScriptInstance, typename TScriptLanguage> TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { + if (!p_inst) + return NULL; return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : NULL; } #else @@ -65,7 +67,7 @@ class CSharpScript : public Script { friend class CSharpInstance; friend class CSharpLanguage; - friend class CSharpScriptDepSort; + friend struct CSharpScriptDepSort; bool tool; bool valid; @@ -80,6 +82,21 @@ class CSharpScript : public Script { Set<Object *> instances; +#ifdef DEBUG_ENABLED + Set<ObjectID> pending_reload_instances; +#endif + + struct StateBackup { + // TODO + // Replace with buffer containing the serialized state of managed scripts. + // Keep variant state backup to use only with script instance placeholders. + List<Pair<StringName, Variant> > properties; + }; + +#ifdef TOOLS_ENABLED + Map<ObjectID, CSharpScript::StateBackup> pending_reload_state; +#endif + String source; StringName name; @@ -103,10 +120,6 @@ class CSharpScript : public Script { virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); #endif -#ifdef DEBUG_ENABLED - Map<ObjectID, List<Pair<StringName, Variant> > > pending_reload_state; -#endif - Map<StringName, PropertyInfo> member_info; void _clear(); @@ -139,6 +152,7 @@ public: virtual bool can_instance() const; virtual StringName get_instance_base_type() const; virtual ScriptInstance *instance_create(Object *p_this); + virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this); virtual bool instance_has(const Object *p_this) const; virtual bool has_source_code() const; @@ -155,12 +169,14 @@ public: virtual void update_exports(); virtual bool is_tool() const { return tool; } + virtual bool is_valid() const { return valid; } + virtual Ref<Script> get_base_script() const; virtual ScriptLanguage *get_language() const; - /* TODO */ virtual void get_script_method_list(List<MethodInfo> *p_list) const {} + virtual void get_script_method_list(List<MethodInfo> *p_list) const; bool has_method(const StringName &p_method) const; - /* TODO */ MethodInfo get_method_info(const StringName &p_method) const { return MethodInfo(); } + MethodInfo get_method_info(const StringName &p_method) const; virtual int get_member_line(const StringName &p_member) const; @@ -176,14 +192,21 @@ class CSharpInstance : public ScriptInstance { friend class CSharpScript; friend class CSharpLanguage; + Object *owner; - Ref<CSharpScript> script; - Ref<MonoGCHandle> gchandle; bool base_ref; bool ref_dying; + bool unsafe_referenced; + bool predelete_notified; + bool destructing_script_instance; + + Ref<CSharpScript> script; + Ref<MonoGCHandle> gchandle; - void _reference_owner_unsafe(); - void _unreference_owner_unsafe(); + bool _reference_owner_unsafe(); + bool _unreference_owner_unsafe(); + + MonoObject *_internal_new_managed(); // Do not use unless you know what you are doing friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *); @@ -196,6 +219,8 @@ class CSharpInstance : public ScriptInstance { public: MonoObject *get_mono_object() const; + _FORCE_INLINE_ bool is_destructing_script_instance() { return destructing_script_instance; } + virtual bool set(const StringName &p_name, const Variant &p_value); virtual bool get(const StringName &p_name, Variant &r_ret) const; virtual void get_property_list(List<PropertyInfo> *p_properties) const; @@ -207,7 +232,8 @@ public: virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount); virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount); - void mono_object_disposed(); + void mono_object_disposed(MonoObject *p_obj); + void mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_owner_deleted); virtual void refcount_incremented(); virtual bool refcount_decremented(); @@ -216,7 +242,7 @@ public: virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; virtual void notification(int p_notification); - void call_notification_no_check(MonoObject *p_mono_object, int p_notification); + void _call_notification(int p_notification); virtual Ref<Script> get_script() const; @@ -226,6 +252,12 @@ public: ~CSharpInstance(); }; +struct CSharpScriptBinding { + StringName type_name; + GDMonoClass *wrapper_class; + Ref<MonoGCHandle> gchandle; +}; + class CSharpLanguage : public ScriptLanguage { friend class CSharpScript; @@ -238,12 +270,11 @@ class CSharpLanguage : public ScriptLanguage { GDMono *gdmono; SelfList<CSharpScript>::List script_list; - Mutex *lock; - Mutex *script_bind_lock; + Mutex *script_instances_mutex; + Mutex *script_gchandle_release_mutex; + Mutex *language_bind_mutex; - Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload; - - Map<Object *, Ref<MonoGCHandle> > gchandle_bindings; + Map<Object *, CSharpScriptBinding> script_bindings; struct StringNameCache { @@ -259,6 +290,8 @@ class CSharpLanguage : public ScriptLanguage { int lang_idx; + Dictionary scripts_metadata; + public: StringNameCache string_names; @@ -269,13 +302,21 @@ public: _FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; } + static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle); + static void release_script_gchandle(MonoObject *p_pinned_expected_obj, Ref<MonoGCHandle> &p_gchandle); + bool debug_break(const String &p_error, bool p_allow_continue = true); bool debug_break_parse(const String &p_file, int p_line, const String &p_error); #ifdef TOOLS_ENABLED - void reload_assemblies_if_needed(bool p_soft_reload); + bool is_assembly_reloading_needed(); + void reload_assemblies(bool p_soft_reload); #endif + void project_assembly_loaded(); + + _FORCE_INLINE_ const Dictionary &get_scripts_metadata() { return scripts_metadata; } + virtual String get_name() const; /* LANGUAGE FUNCTIONS */ @@ -292,7 +333,7 @@ public: virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; virtual bool is_using_templates(); virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script); - /* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { return true; } + /* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings = NULL, Set<int> *r_safe_lines = NULL) const { return true; } virtual String validate_path(const String &p_path) const; virtual Script *create_script() const; virtual bool has_named_classes() const; @@ -345,6 +386,8 @@ public: // Don't use these. I'm watching you virtual void *alloc_instance_binding_data(Object *p_object); virtual void free_instance_binding_data(void *p_data); + virtual void refcount_incremented_instance_binding(Object *p_object); + virtual bool refcount_decremented_instance_binding(Object *p_object); #ifdef DEBUG_ENABLED Vector<StackInfo> stack_trace_get_info(MonoObject *p_stack_trace); diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml index 985c66464b..921c1ca825 100644 --- a/modules/mono/doc_classes/GodotSharp.xml +++ b/modules/mono/doc_classes/GodotSharp.xml @@ -23,18 +23,44 @@ Detaches the current thread from the mono runtime. </description> </method> - <method name="is_domain_loaded"> + <method name="get_domain_id"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_scripts_domain_id"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="is_domain_finalizing_for_unload"> + <return type="bool"> + </return> + <argument index="0" name="domain_id" type="int"> + </argument> + <description> + Returns whether the domain is being finalized. + </description> + </method> + <method name="is_runtime_initialized"> + <return type="bool"> + </return> + <description> + </description> + </method> + <method name="is_runtime_shutting_down"> <return type="bool"> </return> <description> - Returns whether the scripts domain is loaded. </description> </method> - <method name="is_finalizing_domain"> + <method name="is_scripts_domain_loaded"> <return type="bool"> </return> <description> - Returns whether the scripts domain is being finalized. + Returns whether the scripts domain is loaded. </description> </method> </methods> diff --git a/modules/mono/editor/GodotSharpTools/.gitignore b/modules/mono/editor/GodotSharpTools/.gitignore new file mode 100644 index 0000000000..296ad48834 --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/.gitignore @@ -0,0 +1,2 @@ +# nuget packages +packages
\ No newline at end of file diff --git a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs b/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs new file mode 100644 index 0000000000..5fd708d539 --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.IO; +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 ? ".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.Contains("OSX"); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + extern static string GetTemplatesDir(); + + [MethodImpl(MethodImplOptions.InternalCall)] + extern static string GetDataDirName(); + } +} diff --git a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs b/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs index 303be3b732..fba4a8f65c 100644 --- a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs +++ b/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs @@ -2,13 +2,23 @@ using System; using System.IO; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace GodotSharpTools.Editor { public class MonoDevelopInstance { - private Process process; - private string solutionFile; + public enum EditorId + { + MonoDevelop = 0, + VisualStudioForMac = 1 + } + + readonly string solutionFile; + readonly EditorId editorId; + + Process process; public void Execute(string[] files) { @@ -16,6 +26,35 @@ namespace GodotSharpTools.Editor List<string> args = new List<string>(); + string command; + + if (Utils.OS.IsOSX()) + { + string bundleId = codeEditorBundleIds[editorId]; + + if (IsApplicationBundleInstalled(bundleId)) + { + command = "open"; + + args.Add("-b"); + args.Add(bundleId); + + // The 'open' process must wait until the application finishes + if (newWindow) + args.Add("--wait-apps"); + + args.Add("--args"); + } + else + { + command = codeEditorPaths[editorId]; + } + } + else + { + command = codeEditorPaths[editorId]; + } + args.Add("--ipc-tcp"); if (newWindow) @@ -33,25 +72,73 @@ namespace GodotSharpTools.Editor if (newWindow) { - ProcessStartInfo startInfo = new ProcessStartInfo(MonoDevelopFile, string.Join(" ", args)); - process = Process.Start(startInfo); + process = Process.Start(new ProcessStartInfo() + { + FileName = command, + Arguments = string.Join(" ", args), + UseShellExecute = false + }); } else { - Process.Start(MonoDevelopFile, string.Join(" ", args)); + Process.Start(new ProcessStartInfo() + { + FileName = command, + Arguments = string.Join(" ", args), + UseShellExecute = false + }); } } - public MonoDevelopInstance(string solutionFile) + public MonoDevelopInstance(string solutionFile, EditorId editorId) { + if (editorId == EditorId.VisualStudioForMac && !Utils.OS.IsOSX()) + throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform"); + this.solutionFile = solutionFile; + this.editorId = editorId; } - private static string MonoDevelopFile + [MethodImpl(MethodImplOptions.InternalCall)] + private extern static bool IsApplicationBundleInstalled(string bundleId); + + static readonly IReadOnlyDictionary<EditorId, string> codeEditorPaths; + static readonly IReadOnlyDictionary<EditorId, string> codeEditorBundleIds; + + static MonoDevelopInstance() { - get + if (Utils.OS.IsOSX()) + { + codeEditorPaths = new Dictionary<EditorId, string> + { + // Rely on PATH + { EditorId.MonoDevelop, "monodevelop" }, + { EditorId.VisualStudioForMac, "VisualStudio" } + }; + codeEditorBundleIds = new Dictionary<EditorId, string> + { + // TODO EditorId.MonoDevelop + { EditorId.VisualStudioForMac, "com.microsoft.visual-studio" } + }; + } + else if (Utils.OS.IsWindows()) + { + codeEditorPaths = 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" } + }; + } + else if (Utils.OS.IsUnix()) { - return "monodevelop"; + codeEditorPaths = new Dictionary<EditorId, string> + { + // Rely on PATH + { EditorId.MonoDevelop, "monodevelop" } + }; } } } diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj b/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj index 1c8714e31d..f9e9f41977 100644 --- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj +++ b/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj @@ -31,6 +31,9 @@ <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> + </Reference> </ItemGroup> <ItemGroup> <Compile Include="StringExtensions.cs" /> @@ -40,6 +43,11 @@ <Compile Include="Project\ProjectGenerator.cs" /> <Compile Include="Project\ProjectUtils.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Utils\OS.cs" /> + <Compile Include="Editor\GodotSharpExport.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> </Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.userprefs b/modules/mono/editor/GodotSharpTools/GodotSharpTools.userprefs deleted file mode 100644 index 0cbafdc20d..0000000000 --- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.userprefs +++ /dev/null @@ -1,14 +0,0 @@ -<Properties StartupItem="GodotSharpTools.csproj"> - <MonoDevelop.Ide.Workspace ActiveConfiguration="Debug" /> - <MonoDevelop.Ide.Workbench ActiveDocument="Build/BuildSystem.cs"> - <Files> - <File FileName="Build/ProjectExtensions.cs" Line="1" Column="1" /> - <File FileName="Build/ProjectGenerator.cs" Line="1" Column="1" /> - <File FileName="Build/BuildSystem.cs" Line="37" Column="14" /> - </Files> - </MonoDevelop.Ide.Workbench> - <MonoDevelop.Ide.DebuggingService.Breakpoints> - <BreakpointStore /> - </MonoDevelop.Ide.DebuggingService.Breakpoints> - <MonoDevelop.Ide.DebuggingService.PinnedWatches /> -</Properties>
\ No newline at end of file diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs index f00ec5a2ad..647d9ac81d 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs @@ -1,4 +1,5 @@ using System; +using DotNet.Globbing; using Microsoft.Build.Construction; namespace GodotSharpTools.Project @@ -7,7 +8,10 @@ namespace GodotSharpTools.Project { public static bool HasItem(this ProjectRootElement root, string itemType, string include) { - string includeNormalized = include.NormalizePath(); + GlobOptions globOptions = new GlobOptions(); + globOptions.Evaluation.CaseInsensitive = false; + + string normalizedInclude = include.NormalizePath(); foreach (var itemGroup in root.ItemGroups) { @@ -16,10 +20,14 @@ namespace GodotSharpTools.Project foreach (var item in itemGroup.Items) { - if (item.ItemType == itemType) + if (item.ItemType != itemType) + continue; + + var glob = Glob.Parse(item.Include.NormalizePath(), globOptions); + + if (glob.IsMatch(normalizedInclude)) { - if (item.Include.NormalizePath() == includeNormalized) - return true; + return true; } } } diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs index 1d863e6f61..2ce7837a27 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs @@ -6,18 +6,24 @@ namespace GodotSharpTools.Project { 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}"; + public static string GenCoreApiProject(string dir, string[] compileItems) { - string path = Path.Combine(dir, CoreApiProject + ".csproj"); + string path = Path.Combine(dir, CoreApiProjectName + ".csproj"); ProjectPropertyGroupElement mainGroup; - var root = CreateLibraryProject(CoreApiProject, out mainGroup); + var root = CreateLibraryProject(CoreApiProjectName, out mainGroup); mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml")); mainGroup.SetProperty("RootNamespace", "Godot"); + mainGroup.SetProperty("ProjectGuid", CoreApiProjectGuid); - GenAssemblyInfoFile(root, dir, CoreApiProject, - new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProject + "\")]" }, + GenAssemblyInfoFile(root, dir, CoreApiProjectName, + new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]" }, new string[] { "System.Runtime.CompilerServices" }); foreach (var item in compileItems) @@ -27,33 +33,33 @@ namespace GodotSharpTools.Project root.Save(path); - return root.GetGuid().ToString().ToUpper(); + return CoreApiProjectGuid; } - public static string GenEditorApiProject(string dir, string coreApiHintPath, string[] compileItems) + public static string GenEditorApiProject(string dir, string coreApiProjPath, string[] compileItems) { - string path = Path.Combine(dir, EditorApiProject + ".csproj"); + string path = Path.Combine(dir, EditorApiProjectName + ".csproj"); ProjectPropertyGroupElement mainGroup; - var root = CreateLibraryProject(EditorApiProject, out mainGroup); + var root = CreateLibraryProject(EditorApiProjectName, out mainGroup); mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml")); mainGroup.SetProperty("RootNamespace", "Godot"); + mainGroup.SetProperty("ProjectGuid", EditorApiProjectGuid); - GenAssemblyInfoFile(root, dir, EditorApiProject); + GenAssemblyInfoFile(root, dir, EditorApiProjectName); foreach (var item in compileItems) { root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\")); } - var coreApiRef = root.AddItem("Reference", CoreApiProject); - coreApiRef.AddMetadata("HintPath", coreApiHintPath); + var coreApiRef = root.AddItem("ProjectReference", coreApiProjPath.Replace("/", "\\")); coreApiRef.AddMetadata("Private", "False"); root.Save(path); - return root.GetGuid().ToString().ToUpper(); + return EditorApiProjectGuid; } public static string GenGameProject(string dir, string name, string[] compileItems) @@ -77,13 +83,13 @@ namespace GodotSharpTools.Project toolsGroup.AddProperty("WarningLevel", "4"); toolsGroup.AddProperty("ConsolePause", "false"); - var coreApiRef = root.AddItem("Reference", CoreApiProject); - coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProject + ".dll")); + var coreApiRef = root.AddItem("Reference", CoreApiProjectName); + coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProjectName + ".dll")); coreApiRef.AddMetadata("Private", "False"); - var editorApiRef = root.AddItem("Reference", EditorApiProject); + var editorApiRef = root.AddItem("Reference", EditorApiProjectName); editorApiRef.Condition = " '$(Configuration)' == 'Tools' "; - editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProject + ".dll")); + editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProjectName + ".dll")); editorApiRef.AddMetadata("Private", "False"); GenAssemblyInfoFile(root, dir, name); @@ -182,9 +188,6 @@ namespace GodotSharpTools.Project } } - public const string CoreApiProject = "GodotSharp"; - public const string EditorApiProject = "GodotSharpEditor"; - private const string assemblyInfoTemplate = @"using System.Reflection;{0} diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs index 6889ea715f..a13f4fd6ef 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs @@ -1,5 +1,6 @@ -using System; +using System.Collections.Generic; using System.IO; +using DotNet.Globbing; using Microsoft.Build.Construction; namespace GodotSharpTools.Project @@ -10,8 +11,61 @@ namespace GodotSharpTools.Project { var dir = Directory.GetParent(projectPath).FullName; var root = ProjectRootElement.Open(projectPath); - if (root.AddItemChecked(itemType, include.RelativeToPath(dir).Replace("/", "\\"))) + 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/StringExtensions.cs b/modules/mono/editor/GodotSharpTools/StringExtensions.cs index b66c86f8ce..b0436d2f18 100644 --- a/modules/mono/editor/GodotSharpTools/StringExtensions.cs +++ b/modules/mono/editor/GodotSharpTools/StringExtensions.cs @@ -36,7 +36,9 @@ namespace GodotSharpTools public static bool IsAbsolutePath(this string path) { - return path.StartsWith("/") || path.StartsWith("\\") || path.StartsWith(driveRoot); + return path.StartsWith("/", StringComparison.Ordinal) || + path.StartsWith("\\", StringComparison.Ordinal) || + path.StartsWith(driveRoot, StringComparison.Ordinal); } public static string CsvEscape(this string value, char delimiter = ',') diff --git a/modules/mono/editor/GodotSharpTools/Utils/OS.cs b/modules/mono/editor/GodotSharpTools/Utils/OS.cs new file mode 100644 index 0000000000..148e954e77 --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/Utils/OS.cs @@ -0,0 +1,62 @@ +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/GodotSharpTools/packages.config b/modules/mono/editor/GodotSharpTools/packages.config new file mode 100644 index 0000000000..2c7cb0bd4b --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/packages.config @@ -0,0 +1,4 @@ +<?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/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 4c598d4f37..166b3e1324 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -32,23 +32,23 @@ #ifdef DEBUG_METHODS_ENABLED -#include "engine.h" -#include "global_constants.h" -#include "io/compression.h" -#include "os/dir_access.h" -#include "os/file_access.h" -#include "os/os.h" -#include "ucaps.h" +#include "core/engine.h" +#include "core/global_constants.h" +#include "core/io/compression.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "core/ucaps.h" #include "../glue/cs_compressed.gen.h" +#include "../glue/cs_glue_version.gen.h" #include "../godotsharp_defs.h" #include "../mono_gd/gd_mono_marshal.h" #include "../utils/path_utils.h" #include "../utils/string_utils.h" #include "csharp_project.h" -#include "net_solution.h" -#define CS_INDENT " " +#define CS_INDENT " " // 4 whitespaces #define INDENT1 CS_INDENT #define INDENT2 INDENT1 INDENT1 @@ -68,23 +68,18 @@ #define CLOSE_BLOCK_L3 INDENT3 CLOSE_BLOCK #define CLOSE_BLOCK_L4 INDENT4 CLOSE_BLOCK -#define LOCAL_RET "ret" - #define CS_FIELD_MEMORYOWN "memoryOwn" #define CS_PARAM_METHODBIND "method" #define CS_PARAM_INSTANCE "ptr" #define CS_SMETHOD_GETINSTANCE "GetPtr" -#define CS_FIELD_SINGLETON "instance" -#define CS_PROP_SINGLETON "Instance" -#define CS_CLASS_SIGNALAWAITER "SignalAwaiter" #define CS_METHOD_CALL "Call" #define GLUE_HEADER_FILE "glue_header.h" #define ICALL_PREFIX "godot_icall_" #define SINGLETON_ICALL_SUFFIX "_get_singleton" -#define ICALL_GET_METHODBIND ICALL_PREFIX "ClassDB_get_method" -#define ICALL_CONNECT_SIGNAL_AWAITER ICALL_PREFIX "Object_connect_signal_awaiter" -#define ICALL_OBJECT_DTOR ICALL_PREFIX "Object_Dtor" +#define ICALL_GET_METHODBIND ICALL_PREFIX "Object_ClassDB_get_method" + +#define C_LOCAL_RET "ret" #define C_LOCAL_PTRCALL_ARGS "call_args" #define C_MACRO_OBJECT_CONSTRUCT "GODOTSHARP_INSTANCE_OBJECT" @@ -100,10 +95,8 @@ #define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL "::mono_string_from_godot" #define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type #define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array" -#define C_METHOD_MANAGED_TO_DICT C_NS_MONOMARSHAL "::mono_object_to_Dictionary" -#define C_METHOD_MANAGED_FROM_DICT C_NS_MONOMARSHAL "::Dictionary_to_mono_object" -#define BINDINGS_GENERATOR_VERSION UINT32_C(2) +#define BINDINGS_GENERATOR_VERSION UINT32_C(5) const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN = "\t%0 %1_in = %1;\n"; @@ -179,65 +172,74 @@ static String snake_to_camel_case(const String &p_identifier, bool p_input_is_up return ret; } -String BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) { +int BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) { CRASH_COND(p_ienum.constants.empty()); - const List<ConstantInterface>::Element *front = p_ienum.constants.front(); - int candidate_len = front->get().name.length(); + const ConstantInterface &front_iconstant = p_ienum.constants.front()->get(); + Vector<String> front_parts = front_iconstant.name.split("_", /* p_allow_empty: */ true); + int candidate_len = front_parts.size() - 1; - for (const List<ConstantInterface>::Element *E = front->next(); E; E = E->next()) { - int j = 0; - for (j = 0; j < candidate_len && j < E->get().name.length(); j++) { - if (front->get().name[j] != E->get().name[j]) - break; + if (candidate_len == 0) + return 0; + + for (const List<ConstantInterface>::Element *E = p_ienum.constants.front()->next(); E; E = E->next()) { + const ConstantInterface &iconstant = E->get(); + + Vector<String> parts = iconstant.name.split("_", /* p_allow_empty: */ true); + + int i; + for (i = 0; i < candidate_len && i < parts.size(); i++) { + if (front_parts[i] != parts[i]) { + // HARDCODED: Some Flag enums have the prefix 'FLAG_' for everything except 'FLAGS_DEFAULT' (same for 'METHOD_FLAG_' and'METHOD_FLAGS_DEFAULT'). + bool hardcoded_exc = (i == candidate_len - 1 && ((front_parts[i] == "FLAGS" && parts[i] == "FLAG") || (front_parts[i] == "FLAG" && parts[i] == "FLAGS"))); + if (!hardcoded_exc) + break; + } } - candidate_len = j; + candidate_len = i; + + if (candidate_len == 0) + return 0; } - return front->get().name.substr(0, candidate_len); + return candidate_len; } -void BindingsGenerator::_generate_header_icalls() { +void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumInterface &p_ienum, int p_prefix_length) { - core_custom_icalls.clear(); + if (p_prefix_length > 0) { + for (List<ConstantInterface>::Element *E = p_ienum.constants.front(); E; E = E->next()) { + int curr_prefix_length = p_prefix_length; + + ConstantInterface &curr_const = E->get(); + + String constant_name = curr_const.name; - core_custom_icalls.push_back(InternalCall(ICALL_GET_METHODBIND, "IntPtr", "string type, string method")); - core_custom_icalls.push_back(InternalCall(ICALL_OBJECT_DTOR, "void", "object obj, IntPtr ptr")); - - core_custom_icalls.push_back(InternalCall(ICALL_CONNECT_SIGNAL_AWAITER, "Error", - "IntPtr source, string signal, IntPtr target, " CS_CLASS_SIGNALAWAITER " awaiter")); - - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "NodePath_Ctor", "IntPtr", "string path")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "NodePath_Dtor", "void", "IntPtr ptr")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "NodePath_operator_String", "string", "IntPtr ptr")); - - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "RID_Ctor", "IntPtr", "IntPtr from")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "RID_Dtor", "void", "IntPtr ptr")); - - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_md5_buffer", "byte[]", "string str")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_md5_text", "string", "string str")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_rfind", "int", "string str, string what, int from")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_rfindn", "int", "string str, string what, int from")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_sha256_buffer", "byte[]", "string str")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_sha256_text", "string", "string str")); - - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_bytes2var", "object", "byte[] bytes")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_convert", "object", "object what, int type")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_hash", "int", "object var")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_instance_from_id", "Object", "int instance_id")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_print", "void", "object[] what")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_printerr", "void", "object[] what")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_printraw", "void", "object[] what")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_prints", "void", "object[] what")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_printt", "void", "object[] what")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_seed", "void", "int seed")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_str", "string", "object[] what")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_str2var", "object", "string str")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_type_exists", "bool", "string type")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_var2bytes", "byte[]", "object what")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_var2str", "string", "object var")); - core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_weakref", "WeakRef", "IntPtr obj")); + Vector<String> parts = constant_name.split("_", /* p_allow_empty: */ true); + + if (parts.size() <= curr_prefix_length) + continue; + + if (parts[curr_prefix_length][0] >= '0' && parts[curr_prefix_length][0] <= '9') { + // The name of enum constants may begin with a numeric digit when strip from the enum prefix, + // so we make the prefix for this constant one word shorter in those cases. + for (curr_prefix_length = curr_prefix_length - 1; curr_prefix_length > 0; curr_prefix_length--) { + if (parts[curr_prefix_length][0] < '0' || parts[curr_prefix_length][0] > '9') + break; + } + } + + constant_name = ""; + for (int i = curr_prefix_length; i < parts.size(); i++) { + if (i > curr_prefix_length) + constant_name += "_"; + constant_name += parts[i]; + } + + curr_const.proxy_name = snake_to_pascal_case(constant_name, true); + } + } } void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { @@ -250,13 +252,8 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { const TypeInterface *return_type = _get_type_or_placeholder(imethod.return_type); - String im_sig; - String im_unique_sig; - - if (p_itype.is_object_type) { - im_sig += "IntPtr " CS_PARAM_METHODBIND ", "; - im_unique_sig += imethod.return_type.cname.operator String() + ",IntPtr,IntPtr"; - } + String im_sig = "IntPtr " CS_PARAM_METHODBIND ", "; + String im_unique_sig = imethod.return_type.cname.operator String() + ",IntPtr,IntPtr"; im_sig += "IntPtr " CS_PARAM_INSTANCE; @@ -270,37 +267,28 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { im_sig += " arg"; im_sig += itos(i + 1); - if (p_itype.is_object_type) { - im_unique_sig += ","; - im_unique_sig += get_unique_sig(*arg_type); - } + im_unique_sig += ","; + im_unique_sig += get_unique_sig(*arg_type); i++; } + // godot_icall_{argc}_{icallcount} String icall_method = ICALL_PREFIX; - - if (p_itype.is_object_type) { - icall_method += itos(imethod.arguments.size()) + "_" + itos(method_icalls.size()); // godot_icall_{argc}_{icallcount} - } else { - icall_method += p_itype.name + "_" + imethod.name; // godot_icall_{Type}_{method} - } + icall_method += itos(imethod.arguments.size()); + icall_method += "_"; + icall_method += itos(method_icalls.size()); InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, return_type->im_type_out, im_sig, im_unique_sig); - if (p_itype.is_object_type) { - List<InternalCall>::Element *match = method_icalls.find(im_icall); + List<InternalCall>::Element *match = method_icalls.find(im_icall); - if (match) { - if (p_itype.api_type != ClassDB::API_EDITOR) - match->get().editor_only = false; - method_icalls_map.insert(&E->get(), &match->get()); - } else { - List<InternalCall>::Element *added = method_icalls.push_back(im_icall); - method_icalls_map.insert(&E->get(), &added->get()); - } + if (match) { + if (p_itype.api_type != ClassDB::API_EDITOR) + match->get().editor_only = false; + method_icalls_map.insert(&E->get(), &match->get()); } else { - List<InternalCall>::Element *added = builtin_method_icalls.push_back(im_icall); + List<InternalCall>::Element *added = method_icalls.push_back(im_icall); method_icalls_map.insert(&E->get(), &added->get()); } } @@ -334,7 +322,7 @@ void BindingsGenerator::_generate_global_constants(List<String> &p_output) { } p_output.push_back(MEMBER_BEGIN "public const int "); - p_output.push_back(iconstant.name); + p_output.push_back(iconstant.proxy_name); p_output.push_back(" = "); p_output.push_back(itos(iconstant.value)); p_output.push_back(";"); @@ -396,25 +384,8 @@ void BindingsGenerator::_generate_global_constants(List<String> &p_output) { p_output.push_back(INDENT2 "/// </summary>\n"); } - String constant_name = iconstant.name; - - if (!ienum.prefix.empty() && constant_name.begins_with(ienum.prefix)) { - constant_name = constant_name.substr(ienum.prefix.length(), constant_name.length()); - } - - if (constant_name[0] >= '0' && constant_name[0] <= '9') { - // The name of enum constants may begin with a numeric digit when strip from the enum prefix, - // so we make the prefix one word shorter in those cases. - int i = 0; - for (i = ienum.prefix.length() - 1; i >= 0; i--) { - if (ienum.prefix[i] >= 'A' && ienum.prefix[i] <= 'Z') - break; - } - constant_name = ienum.prefix.substr(i, ienum.prefix.length()) + constant_name; - } - p_output.push_back(INDENT2); - p_output.push_back(constant_name); + p_output.push_back(iconstant.proxy_name); p_output.push_back(" = "); p_output.push_back(itos(iconstant.value)); p_output.push_back(E != ienum.constants.back() ? ",\n" : "\n"); @@ -429,32 +400,29 @@ void BindingsGenerator::_generate_global_constants(List<String> &p_output) { p_output.push_back(CLOSE_BLOCK); // end of namespace } -Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bool p_verbose_output) { +Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output) { verbose_output = p_verbose_output; + String proj_dir = p_solution_dir.plus_file(CORE_API_ASSEMBLY_NAME); + 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(proj_dir)) { + Error err = da->make_dir_recursive(proj_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - da->change_dir(p_output_dir); + da->change_dir(proj_dir); da->make_dir("Core"); da->make_dir("ObjectType"); - String core_dir = path_join(p_output_dir, "Core"); - String obj_type_dir = path_join(p_output_dir, "ObjectType"); + String core_dir = path_join(proj_dir, "Core"); + String obj_type_dir = path_join(proj_dir, "ObjectType"); Vector<String> compile_items; - NETSolution solution(API_ASSEMBLY_NAME); - - if (!solution.set_path(p_output_dir)) - return ERR_FILE_NOT_FOUND; - // Generate source file for global scope constants and enums { List<String> constants_source; @@ -485,20 +453,6 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo compile_items.push_back(output_file); } -#define GENERATE_BUILTIN_TYPE(m_name) \ - { \ - String output_file = path_join(core_dir, #m_name ".cs"); \ - Error err = _generate_cs_type(builtin_types[#m_name], output_file); \ - if (err != OK) \ - return err; \ - compile_items.push_back(output_file); \ - } - - GENERATE_BUILTIN_TYPE(NodePath); - GENERATE_BUILTIN_TYPE(RID); - -#undef GENERATE_BUILTIN_TYPE - // Generate sources from compressed files Map<String, CompressedFile> compressed_files; @@ -514,6 +468,15 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo data.resize(file_data.uncompressed_size); Compression::decompress(data.ptrw(), file_data.uncompressed_size, file_data.data, file_data.compressed_size, Compression::MODE_DEFLATE); + String output_dir = output_file.get_base_dir(); + + if (!DirAccess::exists(output_dir)) { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); + Error err = da->make_dir_recursive(ProjectSettings::get_singleton()->globalize_path(output_dir)); + ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); + } + FileAccessRef file = FileAccess::open(output_file, FileAccess::WRITE); ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); file->store_buffer(data.ptr(), data.size()); @@ -526,34 +489,30 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo cs_icalls_content.push_back("using System;\n" "using System.Runtime.CompilerServices;\n" - "using System.Collections.Generic;\n" "\n"); cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK); - cs_icalls_content.push_back(INDENT2 "internal static ulong godot_api_hash = "); + cs_icalls_content.push_back(MEMBER_BEGIN "internal static ulong godot_api_hash = "); cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n"); - cs_icalls_content.push_back(INDENT2 "internal static uint bindings_version = "); + cs_icalls_content.push_back(MEMBER_BEGIN "internal static uint bindings_version = "); cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); - cs_icalls_content.push_back(INDENT2 "internal static uint cs_glue_version = "); + cs_icalls_content.push_back(MEMBER_BEGIN "internal static uint cs_glue_version = "); cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n"); - cs_icalls_content.push_back("\n"); -#define ADD_INTERNAL_CALL(m_icall) \ - if (!m_icall.editor_only) { \ - cs_icalls_content.push_back(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ - cs_icalls_content.push_back(INDENT2 "internal extern static "); \ - cs_icalls_content.push_back(m_icall.im_type_out + " "); \ - cs_icalls_content.push_back(m_icall.name + "("); \ - cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \ +#define ADD_INTERNAL_CALL(m_icall) \ + if (!m_icall.editor_only) { \ + cs_icalls_content.push_back(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ + cs_icalls_content.push_back(INDENT2 "internal extern static "); \ + cs_icalls_content.push_back(m_icall.im_type_out + " "); \ + cs_icalls_content.push_back(m_icall.name + "("); \ + cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \ } for (const List<InternalCall>::Element *E = core_custom_icalls.front(); E; E = E->next()) ADD_INTERNAL_CALL(E->get()); for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next()) ADD_INTERNAL_CALL(E->get()); - for (const List<InternalCall>::Element *E = builtin_method_icalls.front(); E; E = E->next()) - ADD_INTERNAL_CALL(E->get()); #undef ADD_INTERNAL_CALL @@ -567,15 +526,15 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo compile_items.push_back(internal_methods_file); - String guid = CSharpProject::generate_core_api_project(p_output_dir, compile_items); + String guid = CSharpProject::generate_core_api_project(proj_dir, compile_items); - solution.add_new_project(API_ASSEMBLY_NAME, guid); + 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"); - Error sln_error = solution.save(); - if (sln_error != OK) { - ERR_PRINT("Could not to save .NET solution."); - return sln_error; - } + r_solution.add_new_project(CORE_API_ASSEMBLY_NAME, proj_info); if (verbose_output) OS::get_singleton()->print("The solution and C# project for the Core API was generated successfully\n"); @@ -583,32 +542,29 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo return OK; } -Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir, const String &p_core_dll_path, bool p_verbose_output) { +Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output) { verbose_output = p_verbose_output; + String proj_dir = p_solution_dir.plus_file(EDITOR_API_ASSEMBLY_NAME); + 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(proj_dir)) { + Error err = da->make_dir_recursive(proj_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - da->change_dir(p_output_dir); + da->change_dir(proj_dir); da->make_dir("Core"); da->make_dir("ObjectType"); - String core_dir = path_join(p_output_dir, "Core"); - String obj_type_dir = path_join(p_output_dir, "ObjectType"); + String core_dir = path_join(proj_dir, "Core"); + String obj_type_dir = path_join(proj_dir, "ObjectType"); Vector<String> compile_items; - NETSolution solution(EDITOR_API_ASSEMBLY_NAME); - - if (!solution.set_path(p_output_dir)) - return ERR_FILE_NOT_FOUND; - for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) { const TypeInterface &itype = E.get(); @@ -631,7 +587,6 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir, cs_icalls_content.push_back("using System;\n" "using System.Runtime.CompilerServices;\n" - "using System.Collections.Generic;\n" "\n"); cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK); @@ -653,8 +608,6 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir, cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \ } - // No need to add builtin_method_icalls. Builtin types are core only - for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next()) ADD_INTERNAL_CALL(E->get()); for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next()) @@ -672,41 +625,96 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir, compile_items.push_back(internal_methods_file); - String guid = CSharpProject::generate_editor_api_project(p_output_dir, p_core_dll_path, compile_items); + 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); + + if (verbose_output) + OS::get_singleton()->print("The solution and C# project for the Editor API was generated successfully\n"); + + return OK; +} + +Error BindingsGenerator::generate_cs_api(const String &p_output_dir, bool p_verbose_output) { + + 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); + ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); + } + + DotNetSolution solution(API_SOLUTION_NAME); + + if (!solution.set_path(p_output_dir)) + return ERR_FILE_NOT_FOUND; + + Error proj_err; + + proj_err = generate_cs_core_project(p_output_dir, solution, p_verbose_output); + if (proj_err != OK) { + ERR_PRINT("Generation of the Core API C# project failed"); + return proj_err; + } - solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, guid); + proj_err = generate_cs_editor_project(p_output_dir, solution, p_verbose_output); + 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("Could not to save .NET solution."); + ERR_PRINT("Failed to save API solution"); return sln_error; } - if (verbose_output) - OS::get_singleton()->print("The solution and C# project for the Editor API was generated successfully\n"); - return OK; } -// TODO: there are constants that hide inherited members. must explicitly use `new` to avoid warnings -// e.g.: warning CS0108: 'SpriteBase3D.FLAG_MAX' hides inherited member 'GeometryInstance.FLAG_MAX'. Use the new keyword if hiding was intended. +// FIXME: There are some members that hide other inherited members. +// - In the case of both members being the same kind, the new one must be declared +// explicitly as `new` to avoid the warning (and we must print a message about it). +// - In the case of both members being of a different kind, then the new one must +// be renamed to avoid the name collision (and we must print a warning about it). +// - Csc warning e.g.: +// ObjectType/LineEdit.cs(140,38): warning CS0108: 'LineEdit.FocusMode' hides inherited member 'Control.FocusMode'. Use the new keyword if hiding was intended. Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const String &p_output_file) { + CRASH_COND(!itype.is_object_type); + bool is_derived_type = itype.base_name != StringName(); + if (!is_derived_type) { + // Some Godot.Object assertions + CRASH_COND(itype.cname != name_cache.type_Object); + CRASH_COND(!itype.is_instantiable); + CRASH_COND(itype.api_type != ClassDB::API_CORE); + CRASH_COND(itype.is_reference); + CRASH_COND(itype.is_singleton); + } + List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls; if (verbose_output) OS::get_singleton()->print(String("Generating " + itype.proxy_name + ".cs...\n").utf8()); - String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); + String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); // Used only for derived types List<String> output; output.push_back("using System;\n"); // IntPtr + output.push_back("using System.Diagnostics;\n"); // DebuggerBrowsable - if (itype.requires_collections) - output.push_back("using System.Collections.Generic;\n"); // Dictionary + output.push_back("\n#pragma warning disable CS1591 // Disable warning: " + "'Missing XML comment for publicly visible type or member'\n"); output.push_back("\nnamespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); @@ -730,21 +738,24 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } output.push_back(INDENT1 "public "); - bool is_abstract = itype.is_object_type && !ClassDB::can_instance(itype.name) && ClassDB::is_class_enabled(itype.name); // can_instance returns true if there's a constructor and the class is not 'disabled' - output.push_back(itype.is_singleton ? "static class " : (is_abstract ? "abstract class " : "class ")); + if (itype.is_singleton) { + output.push_back("static partial class "); + } else { + output.push_back(itype.is_instantiable ? "partial class " : "abstract partial class "); + } output.push_back(itype.proxy_name); if (itype.is_singleton) { output.push_back("\n"); - } else if (!is_derived_type || !itype.is_object_type /* assuming only object types inherit */) { - output.push_back(" : IDisposable\n"); - } else if (obj_types.has(itype.base_name)) { - output.push_back(" : "); - output.push_back(obj_types[itype.base_name].proxy_name); - output.push_back("\n"); - } else { - ERR_PRINTS("Base type '" + itype.base_name.operator String() + "' does not exist, for class " + itype.name); - return ERR_INVALID_DATA; + } else if (is_derived_type) { + if (obj_types.has(itype.base_name)) { + output.push_back(" : "); + output.push_back(obj_types[itype.base_name].proxy_name); + output.push_back("\n"); + } else { + ERR_PRINTS("Base type '" + itype.base_name.operator String() + "' does not exist, for class " + itype.name); + return ERR_INVALID_DATA; + } } output.push_back(INDENT1 "{"); @@ -774,7 +785,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } output.push_back(MEMBER_BEGIN "public const int "); - output.push_back(iconstant.name); + output.push_back(iconstant.proxy_name); output.push_back(" = "); output.push_back(itos(iconstant.value)); output.push_back(";"); @@ -814,25 +825,8 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.push_back(INDENT3 "/// </summary>\n"); } - String constant_name = iconstant.name; - - if (!ienum.prefix.empty() && constant_name.begins_with(ienum.prefix)) { - constant_name = constant_name.substr(ienum.prefix.length(), constant_name.length()); - } - - if (constant_name[0] >= '0' && constant_name[0] <= '9') { - // The name of enum constants may begin with a numeric digit when strip from the enum prefix, - // so we make the prefix one word shorter in those cases. - int i = 0; - for (i = ienum.prefix.length() - 1; i >= 0; i--) { - if (ienum.prefix[i] >= 'A' && ienum.prefix[i] <= 'Z') - break; - } - constant_name = ienum.prefix.substr(i, ienum.prefix.length()) + constant_name; - } - output.push_back(INDENT3); - output.push_back(constant_name); + output.push_back(iconstant.proxy_name); output.push_back(" = "); output.push_back(itos(iconstant.value)); output.push_back(E != ienum.constants.back() ? ",\n" : "\n"); @@ -841,9 +835,6 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.push_back(INDENT2 CLOSE_BLOCK); } - if (itype.enums.size()) - output.push_back("\n"); - // Add properties for (const List<PropertyInterface>::Element *E = itype.properties.front(); E; E = E->next()) { @@ -855,43 +846,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str ERR_FAIL_V(prop_err); } } - - if (class_doc->properties.size()) - output.push_back("\n"); } - if (!itype.is_object_type) { - output.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \"" + itype.name + "\";\n"); - output.push_back(MEMBER_BEGIN "private bool disposed = false;\n"); - output.push_back(MEMBER_BEGIN "internal IntPtr " BINDINGS_PTR_FIELD ";\n"); - - output.push_back(MEMBER_BEGIN "internal static IntPtr " CS_SMETHOD_GETINSTANCE "("); - output.push_back(itype.proxy_name); - output.push_back(" instance)\n" OPEN_BLOCK_L2 "return instance == null ? IntPtr.Zero : instance." BINDINGS_PTR_FIELD ";\n" CLOSE_BLOCK_L2); - - // Add Destructor - output.push_back(MEMBER_BEGIN "~"); - output.push_back(itype.proxy_name); - output.push_back("()\n" OPEN_BLOCK_L2 "Dispose(false);\n" CLOSE_BLOCK_L2); - - // Add the Dispose from IDisposable - output.push_back(MEMBER_BEGIN "public void Dispose()\n" OPEN_BLOCK_L2 "Dispose(true);\n" INDENT3 "GC.SuppressFinalize(this);\n" CLOSE_BLOCK_L2); - - // Add the virtual Dispose - output.push_back(MEMBER_BEGIN "protected virtual void Dispose(bool disposing)\n" OPEN_BLOCK_L2 - "if (disposed) return;\n" INDENT3 - "if (" BINDINGS_PTR_FIELD " != IntPtr.Zero)\n" OPEN_BLOCK_L3 "NativeCalls.godot_icall_"); - output.push_back(itype.proxy_name); - output.push_back("_Dtor(" BINDINGS_PTR_FIELD ");\n" INDENT5 BINDINGS_PTR_FIELD " = IntPtr.Zero;\n" CLOSE_BLOCK_L3 INDENT3 - "GC.SuppressFinalize(this);\n" INDENT3 "disposed = true;\n" CLOSE_BLOCK_L2); - - output.push_back(MEMBER_BEGIN "internal "); - output.push_back(itype.proxy_name); - output.push_back("(IntPtr " BINDINGS_PTR_FIELD ")\n" OPEN_BLOCK_L2 "this." BINDINGS_PTR_FIELD " = " BINDINGS_PTR_FIELD ";\n" CLOSE_BLOCK_L2); - - output.push_back(MEMBER_BEGIN "public IntPtr NativeInstance\n" OPEN_BLOCK_L2 - "get { return " BINDINGS_PTR_FIELD "; }\n" CLOSE_BLOCK_L2); - } else if (itype.is_singleton) { + if (itype.is_singleton) { // Add the type name and the singleton pointer as static fields output.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); @@ -903,21 +860,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.push_back("." ICALL_PREFIX); output.push_back(itype.name); output.push_back(SINGLETON_ICALL_SUFFIX "();\n"); - } else { + } else if (is_derived_type) { // Add member fields output.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); output.push_back(itype.name); output.push_back("\";\n"); - // Only the base class stores the pointer to the native object - // This pointer is expected to be and must be of type Object* - if (!is_derived_type) { - output.push_back(MEMBER_BEGIN "private bool disposed = false;\n"); - output.push_back(INDENT2 "internal IntPtr " BINDINGS_PTR_FIELD ";\n"); - output.push_back(INDENT2 "internal bool " CS_FIELD_MEMORYOWN ";\n"); - } - // Add default constructor if (itype.is_instantiable) { output.push_back(MEMBER_BEGIN "public "); @@ -942,67 +891,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Add.. em.. trick constructor. Sort of. output.push_back(MEMBER_BEGIN "internal "); output.push_back(itype.proxy_name); - if (is_derived_type) { - output.push_back("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n"); - } else { - output.push_back("(bool " CS_FIELD_MEMORYOWN ")\n" OPEN_BLOCK_L2 - "this." CS_FIELD_MEMORYOWN " = " CS_FIELD_MEMORYOWN ";\n" CLOSE_BLOCK_L2); - } - - // Add methods - - if (!is_derived_type) { - output.push_back(MEMBER_BEGIN "public IntPtr NativeInstance\n" OPEN_BLOCK_L2 - "get { return " BINDINGS_PTR_FIELD "; }\n" CLOSE_BLOCK_L2); - - output.push_back(MEMBER_BEGIN "internal static IntPtr " CS_SMETHOD_GETINSTANCE "(Object instance)\n" OPEN_BLOCK_L2 - "return instance == null ? IntPtr.Zero : instance." BINDINGS_PTR_FIELD ";\n" CLOSE_BLOCK_L2); - } - - if (!is_derived_type) { - // Add destructor - output.push_back(MEMBER_BEGIN "~"); - output.push_back(itype.proxy_name); - output.push_back("()\n" OPEN_BLOCK_L2 "Dispose(false);\n" CLOSE_BLOCK_L2); - - // Add the Dispose from IDisposable - output.push_back(MEMBER_BEGIN "public void Dispose()\n" OPEN_BLOCK_L2 "Dispose(true);\n" INDENT3 "GC.SuppressFinalize(this);\n" CLOSE_BLOCK_L2); - - // Add the virtual Dispose - output.push_back(MEMBER_BEGIN "protected virtual void Dispose(bool disposing)\n" OPEN_BLOCK_L2 - "if (disposed) return;\n" INDENT3 - "if (" BINDINGS_PTR_FIELD " != IntPtr.Zero)\n" OPEN_BLOCK_L3 - "if (" CS_FIELD_MEMORYOWN ")\n" OPEN_BLOCK_L4 CS_FIELD_MEMORYOWN - " = false;\n" INDENT5 BINDINGS_CLASS_NATIVECALLS "." ICALL_OBJECT_DTOR - "(this, " BINDINGS_PTR_FIELD ");\n" CLOSE_BLOCK_L4 CLOSE_BLOCK_L3 INDENT3 - "this." BINDINGS_PTR_FIELD " = IntPtr.Zero;\n" INDENT3 - "GC.SuppressFinalize(this);\n" INDENT3 "disposed = true;\n" CLOSE_BLOCK_L2); - - Map<StringName, TypeInterface>::Element *array_itype = builtin_types.find(name_cache.type_Array); - - if (!array_itype) { - ERR_PRINT("BUG: Array type interface not found!"); - return ERR_BUG; - } - - OrderedHashMap<StringName, TypeInterface>::Element object_itype = obj_types.find("Object"); - - if (!object_itype) { - ERR_PRINT("BUG: Object type interface not found!"); - return ERR_BUG; - } - - output.push_back(MEMBER_BEGIN "public " CS_CLASS_SIGNALAWAITER " ToSignal("); - output.push_back(object_itype.get().cs_type); - output.push_back(" source, string signal)\n" OPEN_BLOCK_L2 - "return new " CS_CLASS_SIGNALAWAITER "(source, signal, this);\n" CLOSE_BLOCK_L2); - } + output.push_back("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n"); } - Map<StringName, String>::Element *extra_member = extra_members.find(itype.cname); - if (extra_member) - output.push_back(extra_member->get()); - int method_bind_count = 0; for (const List<MethodInterface>::Element *E = itype.methods.front(); E; E = E->next()) { const MethodInterface &imethod = E->get(); @@ -1020,14 +911,17 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str custom_icalls.push_back(singleton_icall); } - if (itype.is_instantiable) { + if (is_derived_type && itype.is_instantiable) { InternalCall ctor_icall = InternalCall(itype.api_type, ctor_method, "IntPtr", itype.proxy_name + " obj"); if (!find_icall_by_name(ctor_icall.name, custom_icalls)) custom_icalls.push_back(ctor_icall); } - output.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + output.push_back(INDENT1 CLOSE_BLOCK /* class */ + CLOSE_BLOCK /* namespace */); + + output.push_back("\n#pragma warning restore CS1591\n"); return _save_file(p_output_file, output); } @@ -1165,9 +1059,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf String arguments_sig; String cs_in_statements; - String icall_params; - if (p_itype.is_object_type) - icall_params += method_bind_field + ", "; + String icall_params = method_bind_field + ", "; icall_params += sformat(p_itype.cs_in, "this"); List<String> default_args_doc; @@ -1244,9 +1136,9 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Generate method { - if (p_itype.is_object_type && !p_imethod.is_virtual && !p_imethod.requires_object_call) { - p_output.push_back(MEMBER_BEGIN "private static IntPtr "); - p_output.push_back(method_bind_field + " = " BINDINGS_CLASS_NATIVECALLS "." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); + if (!p_imethod.is_virtual && !p_imethod.requires_object_call) { + p_output.push_back(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static IntPtr "); + p_output.push_back(method_bind_field + " = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); p_output.push_back(p_imethod.name); p_output.push_back("\");\n"); } @@ -1338,7 +1230,6 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf } else if (return_type->cs_out.empty()) { p_output.push_back("return " + im_call + ";\n"); } else { - p_output.push_back(INDENT3); p_output.push_back(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out)); p_output.push_back("\n"); } @@ -1360,19 +1251,31 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { List<String> output; - output.push_back("#include \"" GLUE_HEADER_FILE "\"\n" - "\n"); + output.push_back("/* THIS FILE IS GENERATED DO NOT EDIT */\n"); + output.push_back("#include \"" GLUE_HEADER_FILE "\"\n"); + output.push_back("\n#ifdef MONO_GLUE_ENABLED\n"); generated_icall_funcs.clear(); for (OrderedHashMap<StringName, TypeInterface>::Element type_elem = obj_types.front(); type_elem; type_elem = type_elem.next()) { const TypeInterface &itype = type_elem.get(); + bool is_derived_type = itype.base_name != StringName(); + + if (!is_derived_type) { + // Some Object assertions + CRASH_COND(itype.cname != name_cache.type_Object); + CRASH_COND(!itype.is_instantiable); + CRASH_COND(itype.api_type != ClassDB::API_CORE); + CRASH_COND(itype.is_reference); + CRASH_COND(itype.is_singleton); + } + List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls; OS::get_singleton()->print(String("Generating " + itype.name + "...\n").utf8()); - String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); + String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); // Used only for derived types for (const List<MethodInterface>::Element *E = itype.methods.front(); E; E = E->next()) { const MethodInterface &imethod = E->get(); @@ -1397,7 +1300,7 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { output.push_back("\");\n" CLOSE_BLOCK "\n"); } - if (itype.is_instantiable) { + if (is_derived_type && itype.is_instantiable) { InternalCall ctor_icall = InternalCall(itype.api_type, ctor_method, "IntPtr", itype.proxy_name + " obj"); if (!find_icall_by_name(ctor_icall.name, custom_icalls)) @@ -1414,23 +1317,21 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { } } - output.push_back("namespace GodotSharpBindings\n" OPEN_BLOCK); + output.push_back("namespace GodotSharpBindings\n" OPEN_BLOCK "\n"); output.push_back("uint64_t get_core_api_hash() { return "); - output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "; }\n"); + output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "U; }\n"); output.push_back("#ifdef TOOLS_ENABLED\n" "uint64_t get_editor_api_hash() { return "); - output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + - "; }\n#endif // TOOLS_ENABLED\n"); + output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + "U; }\n"); + output.push_back("#endif // TOOLS_ENABLED\n"); output.push_back("uint32_t get_bindings_version() { return "); output.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n"); - output.push_back("uint32_t get_cs_glue_version() { return "); - output.push_back(String::num_uint64(CS_GLUE_VERSION) + "; }\n"); - output.push_back("void register_generated_icalls() " OPEN_BLOCK); - output.push_back("\tgodot_register_header_icalls();"); + output.push_back("\nvoid register_generated_icalls() " OPEN_BLOCK); + output.push_back("\tgodot_register_glue_header_icalls();\n"); #define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ { \ @@ -1493,12 +1394,11 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { output.push_back("#endif\n"); } - for (const List<InternalCall>::Element *E = builtin_method_icalls.front(); E; E = E->next()) - ADD_INTERNAL_CALL_REGISTRATION(E->get()); - #undef ADD_INTERNAL_CALL_REGISTRATION - output.push_back(CLOSE_BLOCK "}\n"); + output.push_back(CLOSE_BLOCK "\n} // namespace GodotSharpBindings\n"); + + output.push_back("\n#endif // MONO_GLUE_ENABLED\n"); Error save_err = _save_file(path_join(p_output_dir, "mono_glue.gen.cpp"), output); if (save_err != OK) @@ -1513,10 +1413,6 @@ uint32_t BindingsGenerator::get_version() { return BINDINGS_GENERATOR_VERSION; } -uint32_t BindingsGenerator::get_cs_glue_version() { - return CS_GLUE_VERSION; -} - Error BindingsGenerator::_save_file(const String &p_path, const List<String> &p_content) { FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE); @@ -1579,9 +1475,6 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte i++; } - if (!p_itype.is_object_type) - return OK; // no auto-generated icall functions for builtin types - const Map<const MethodInterface *, const InternalCall *>::Element *match = method_icalls_map.find(&p_imethod); ERR_FAIL_NULL_V(match, ERR_BUG); @@ -1616,7 +1509,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte } p_output.push_back("\t" + ptrcall_return_type); - p_output.push_back(" " LOCAL_RET); + p_output.push_back(" " C_LOCAL_RET); p_output.push_back(initialization + ";\n"); p_output.push_back("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE); p_output.push_back(fail_ret); @@ -1653,7 +1546,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte "\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n" "\t\t" C_LOCAL_PTRCALL_ARGS ".set("); p_output.push_back(real_argc_str); - p_output.push_back(" + i, &varargs[i]);\n\t" CLOSE_BLOCK); + p_output.push_back(" + i, &varargs.write[i]);\n\t" CLOSE_BLOCK); } else { p_output.push_back(c_in_statements); p_output.push_back("\tconst void* " C_LOCAL_PTRCALL_ARGS "["); @@ -1666,7 +1559,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte p_output.push_back("\tVariant::CallError vcall_error;\n\t"); if (!ret_void) - p_output.push_back(LOCAL_RET " = "); + p_output.push_back(C_LOCAL_RET " = "); p_output.push_back(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", "); p_output.push_back(p_imethod.arguments.size() ? "(const Variant**)" C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL"); @@ -1674,14 +1567,14 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte } else { p_output.push_back("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", "); p_output.push_back(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, "); - p_output.push_back(!ret_void ? "&" LOCAL_RET ");\n" : "NULL);\n"); + p_output.push_back(!ret_void ? "&" C_LOCAL_RET ");\n" : "NULL);\n"); } if (!ret_void) { if (return_type->c_out.empty()) - p_output.push_back("\treturn " LOCAL_RET ";\n"); + p_output.push_back("\treturn " C_LOCAL_RET ";\n"); else - p_output.push_back(sformat(return_type->c_out, return_type->c_type_out, LOCAL_RET, return_type->name)); + p_output.push_back(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name)); } p_output.push_back(CLOSE_BLOCK "\n"); @@ -1740,9 +1633,6 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placehol return &placeholder_types.insert(placeholder.cname, placeholder)->get(); } -static void _create_constant_interface_from(const StringName &p_constant, const DocData::ClassDoc &p_classdoc) { -} - void BindingsGenerator::_populate_object_type_interfaces() { obj_types.clear(); @@ -1768,6 +1658,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { continue; } + if (!ClassDB::is_class_enabled(type_cname)) { + if (verbose_output) + WARN_PRINTS("Ignoring type " + type_cname.operator String() + " because it's not enabled"); + class_list.pop_front(); + continue; + } + ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(type_cname); TypeInterface itype = TypeInterface::create_object_type(type_cname, api_type); @@ -1870,9 +1767,6 @@ void BindingsGenerator::_populate_object_type_interfaces() { if (virtual_method_list.find(method_info)) { // A virtual method without the virtual flag. This is a special case. - // This type of method can only be found in Object derived types. - ERR_FAIL_COND(!itype.is_object_type); - // There is no method bind, so let's fallback to Godot's object.Call(string, params) imethod.requires_object_call = true; @@ -1909,9 +1803,6 @@ void BindingsGenerator::_populate_object_type_interfaces() { imethod.return_type.cname = Variant::get_type_name(return_info.type); } - if (!itype.requires_collections && imethod.return_type.cname == name_cache.type_Dictionary) - itype.requires_collections = true; - for (int i = 0; i < argc; i++) { PropertyInfo arginfo = method_info.arguments[i]; @@ -1933,9 +1824,6 @@ void BindingsGenerator::_populate_object_type_interfaces() { iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name)); - if (!itype.requires_collections && iarg.type.cname == name_cache.type_Dictionary) - itype.requires_collections = true; - if (m && m->has_default_argument(i)) { _default_argument_from_variant(m->get_default_argument(i), iarg); } @@ -2006,11 +1894,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { EnumInterface ienum(enum_proxy_cname); const List<StringName> &constants = enum_map.get(*k); for (const List<StringName>::Element *E = constants.front(); E; E = E->next()) { - int *value = class_info->constant_map.getptr(E->get()); + const StringName &constant_cname = E->get(); + String constant_name = constant_cname.operator String(); + int *value = class_info->constant_map.getptr(constant_cname); ERR_FAIL_NULL(value); - constant_list.erase(E->get().operator String()); + constant_list.erase(constant_name); - ConstantInterface iconstant(snake_to_pascal_case(E->get(), true), *value); + ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value); iconstant.const_doc = NULL; for (int i = 0; i < itype.class_doc->constants.size(); i++) { @@ -2025,7 +1915,9 @@ void BindingsGenerator::_populate_object_type_interfaces() { ienum.constants.push_back(iconstant); } - ienum.prefix = _determine_enum_prefix(ienum); + int prefix_length = _determine_enum_prefix(ienum); + + _apply_prefix_to_enum_constants(ienum, prefix_length); itype.enums.push_back(ienum); @@ -2039,10 +1931,11 @@ void BindingsGenerator::_populate_object_type_interfaces() { } for (const List<String>::Element *E = constant_list.front(); E; E = E->next()) { - int *value = class_info->constant_map.getptr(E->get()); + const String &constant_name = E->get(); + int *value = class_info->constant_map.getptr(StringName(E->get())); ERR_FAIL_NULL(value); - ConstantInterface iconstant(snake_to_pascal_case(E->get(), true), *value); + ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value); iconstant.const_doc = NULL; for (int i = 0; i < itype.class_doc->constants.size(); i++) { @@ -2153,18 +2046,18 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { TypeInterface itype; -#define INSERT_STRUCT_TYPE(m_type, m_type_in) \ - { \ - itype = TypeInterface::create_value_type(String(#m_type)); \ - itype.c_in = "\tMARSHALLED_IN(" #m_type ", %1, %1_in);\n"; \ - itype.c_out = "\tMARSHALLED_OUT(" #m_type ", %1, ret_out)\n" \ - "\treturn mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(%2), ret_out);\n"; \ - itype.c_arg_in = "&%s_in"; \ - itype.c_type_in = m_type_in; \ - itype.cs_in = "ref %s"; \ - itype.cs_out = "return (%1)%0;"; \ - itype.im_type_out = "object"; \ - builtin_types.insert(itype.cname, itype); \ +#define INSERT_STRUCT_TYPE(m_type, m_type_in) \ + { \ + itype = TypeInterface::create_value_type(String(#m_type)); \ + itype.c_in = "\t%0 %1_in = MARSHALLED_IN(" #m_type ", %1);\n"; \ + itype.c_out = "\treturn MARSHALLED_OUT(" #m_type ", %1);\n"; \ + itype.c_arg_in = "&%s_in"; \ + itype.c_type_in = "GDMonoMarshal::M_" #m_type "*"; \ + itype.c_type_out = "GDMonoMarshal::M_" #m_type; \ + itype.cs_in = "ref %s"; \ + itype.cs_out = "return (%1)%0;"; \ + itype.im_type_out = itype.cs_type; \ + builtin_types.insert(itype.cname, itype); \ } INSERT_STRUCT_TYPE(Vector2, "real_t*") @@ -2182,26 +2075,31 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { // bool itype = TypeInterface::create_value_type(String("bool")); - itype.c_arg_in = "&%s"; - // /* MonoBoolean <---> bool - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; - itype.c_type = "bool"; - // */ - itype.c_type_in = "MonoBoolean"; - itype.c_type_out = itype.c_type_in; + + { + // MonoBoolean <---> bool + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; + itype.c_out = "\treturn (%0)%1;\n"; + itype.c_type = "bool"; + itype.c_type_in = "MonoBoolean"; + itype.c_type_out = itype.c_type_in; + itype.c_arg_in = "&%s_in"; + } itype.im_type_in = itype.name; itype.im_type_out = itype.name; builtin_types.insert(itype.cname, itype); // int + // C interface is the same as that of enums. Remember to apply any + // changes done here to TypeInterface::postsetup_enum_type as well itype = TypeInterface::create_value_type(String("int")); itype.c_arg_in = "&%s_in"; - // /* ptrcall only supports int64_t and uint64_t - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; - itype.c_type = "int64_t"; - // */ + { + // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'. + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; + itype.c_out = "\treturn (%0)%1;\n"; + itype.c_type = "int64_t"; + } itype.c_type_in = "int32_t"; itype.c_type_out = itype.c_type_in; itype.im_type_in = itype.name; @@ -2210,21 +2108,22 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { // real_t itype = TypeInterface(); + itype.name = "float"; // The name is always "float" in Variant, even with REAL_T_IS_DOUBLE. + itype.cname = itype.name; #ifdef REAL_T_IS_DOUBLE - itype.name = "double"; + itype.proxy_name = "double"; #else - itype.name = "float"; + itype.proxy_name = "float"; #endif - itype.cname = itype.name; - itype.proxy_name = itype.name; - itype.c_arg_in = "&%s_in"; - //* ptrcall only supports double - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; - itype.c_type = "double"; - //*/ - itype.c_type_in = "real_t"; - itype.c_type_out = "real_t"; + { + // The expected type for parameters and return value in ptrcall is 'double'. + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; + itype.c_out = "\treturn (%0)%1;\n"; + itype.c_type = "double"; + itype.c_type_in = "real_t"; + itype.c_type_out = "real_t"; + itype.c_arg_in = "&%s_in"; + } itype.cs_type = itype.proxy_name; itype.im_type_in = itype.proxy_name; itype.im_type_out = itype.proxy_name; @@ -2260,13 +2159,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.cs_out = "return new %1(%0);"; itype.im_type_in = "IntPtr"; itype.im_type_out = "IntPtr"; - _populate_builtin_type(itype, Variant::NODE_PATH); - extra_members.insert(itype.cname, MEMBER_BEGIN "public NodePath() : this(string.Empty) {}\n" MEMBER_BEGIN "public NodePath(string path)\n" OPEN_BLOCK_L2 - "this." BINDINGS_PTR_FIELD " = NativeCalls.godot_icall_NodePath_Ctor(path);\n" CLOSE_BLOCK_L2 - MEMBER_BEGIN "public static implicit operator NodePath(string from)\n" OPEN_BLOCK_L2 "return new NodePath(from);\n" CLOSE_BLOCK_L2 - MEMBER_BEGIN "public static implicit operator string(NodePath from)\n" OPEN_BLOCK_L2 - "return NativeCalls." ICALL_PREFIX "NodePath_operator_String(NodePath." CS_SMETHOD_GETINSTANCE "(from));\n" CLOSE_BLOCK_L2 - MEMBER_BEGIN "public override string ToString()\n" OPEN_BLOCK_L2 "return (string)this;\n" CLOSE_BLOCK_L2); builtin_types.insert(itype.cname, itype); // RID @@ -2283,9 +2175,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.cs_out = "return new %1(%0);"; itype.im_type_in = "IntPtr"; itype.im_type_out = "IntPtr"; - _populate_builtin_type(itype, Variant::_RID); - extra_members.insert(itype.cname, MEMBER_BEGIN "internal RID()\n" OPEN_BLOCK_L2 - "this." BINDINGS_PTR_FIELD " = IntPtr.Zero;\n" CLOSE_BLOCK_L2); builtin_types.insert(itype.cname, itype); // Variant @@ -2337,7 +2226,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { #define INSERT_ARRAY(m_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_proxy_t) - INSERT_ARRAY(Array, object); INSERT_ARRAY(PoolIntArray, int); INSERT_ARRAY_FULL(PoolByteArray, PoolByteArray, byte); @@ -2355,20 +2243,36 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { #undef INSERT_ARRAY + // Array + itype = TypeInterface(); + itype.name = "Array"; + itype.cname = itype.name; + itype.proxy_name = itype.name; + itype.c_out = "\treturn memnew(Array(%1));\n"; + itype.c_type = itype.name; + itype.c_type_in = itype.c_type + "*"; + itype.c_type_out = itype.c_type + "*"; + itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name; + itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()"; + itype.cs_out = "return new " + itype.cs_type + "(%0);"; + itype.im_type_in = "IntPtr"; + itype.im_type_out = "IntPtr"; + builtin_types.insert(itype.cname, itype); + // Dictionary itype = TypeInterface(); itype.name = "Dictionary"; itype.cname = itype.name; - itype.proxy_name = "Dictionary<object, object>"; - itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_DICT "(%1);\n"; - itype.c_out = "\treturn " C_METHOD_MANAGED_FROM_DICT "(%1);\n"; - itype.c_arg_in = "&%s_in"; + itype.proxy_name = itype.name; + itype.c_out = "\treturn memnew(Dictionary(%1));\n"; itype.c_type = itype.name; - itype.c_type_in = "MonoObject*"; - itype.c_type_out = "MonoObject*"; - itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.c_type_in = itype.c_type + "*"; + itype.c_type_out = itype.c_type + "*"; + itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name; + itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()"; + itype.cs_out = "return new " + itype.cs_type + "(%0);"; + itype.im_type_in = "IntPtr"; + itype.im_type_out = "IntPtr"; builtin_types.insert(itype.cname, itype); // void (fictitious type to represent the return type of methods that do not return anything) @@ -2385,66 +2289,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { builtin_types.insert(itype.cname, itype); } -void BindingsGenerator::_populate_builtin_type(TypeInterface &r_itype, Variant::Type vtype) { - - Variant::CallError cerror; - Variant v = Variant::construct(vtype, NULL, 0, cerror); - - List<MethodInfo> method_list; - v.get_method_list(&method_list); - method_list.sort(); - - for (List<MethodInfo>::Element *E = method_list.front(); E; E = E->next()) { - MethodInfo &mi = E->get(); - MethodInterface imethod; - - imethod.name = mi.name; - imethod.cname = imethod.name; - imethod.proxy_name = escape_csharp_keyword(snake_to_pascal_case(mi.name)); - - for (int i = 0; i < mi.arguments.size(); i++) { - ArgumentInterface iarg; - PropertyInfo pi = mi.arguments[i]; - - iarg.name = pi.name; - - if (pi.type == Variant::NIL) - iarg.type.cname = name_cache.type_Variant; - else - iarg.type.cname = Variant::get_type_name(pi.type); - - if (!r_itype.requires_collections && iarg.type.cname == name_cache.type_Dictionary) - r_itype.requires_collections = true; - - if ((mi.default_arguments.size() - mi.arguments.size() + i) >= 0) - _default_argument_from_variant(Variant::construct(pi.type, NULL, 0, cerror), iarg); - - imethod.add_argument(iarg); - } - - if (mi.return_val.type == Variant::NIL) { - if (mi.return_val.name != "") - imethod.return_type.cname = name_cache.type_Variant; - } else { - imethod.return_type.cname = Variant::get_type_name(mi.return_val.type); - } - - if (!r_itype.requires_collections && imethod.return_type.cname == name_cache.type_Dictionary) - r_itype.requires_collections = true; - - if (r_itype.class_doc) { - for (int i = 0; i < r_itype.class_doc->methods.size(); i++) { - if (r_itype.class_doc->methods[i].name == imethod.name) { - imethod.method_doc = &r_itype.class_doc->methods[i]; - break; - } - } - } - - r_itype.methods.push_back(imethod); - } -} - void BindingsGenerator::_populate_global_constants() { int global_constants_count = GlobalConstants::get_global_constant_count(); @@ -2462,8 +2306,8 @@ void BindingsGenerator::_populate_global_constants() { String constant_name = GlobalConstants::get_global_constant_name(i); const DocData::ConstantDoc *const_doc = NULL; - for (int i = 0; i < global_scope_doc.constants.size(); i++) { - const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[i]; + for (int j = 0; j < global_scope_doc.constants.size(); j++) { + const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[j]; if (curr_const_doc.name == constant_name) { const_doc = &curr_const_doc; @@ -2474,7 +2318,7 @@ void BindingsGenerator::_populate_global_constants() { int constant_value = GlobalConstants::get_global_constant_value(i); StringName enum_name = GlobalConstants::get_global_constant_enum(i); - ConstantInterface iconstant(snake_to_pascal_case(constant_name, true), constant_value); + ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), constant_value); iconstant.const_doc = const_doc; if (enum_name != StringName()) { @@ -2502,16 +2346,18 @@ void BindingsGenerator::_populate_global_constants() { TypeInterface::postsetup_enum_type(enum_itype); enum_types.insert(enum_itype.cname, enum_itype); - ienum.prefix = _determine_enum_prefix(ienum); + int prefix_length = _determine_enum_prefix(ienum); - // HARDCODED + // HARDCODED: The Error enum have the prefix 'ERR_' for everything except 'OK' and 'FAILED'. if (ienum.cname == name_cache.enum_Error) { - if (!ienum.prefix.empty()) { // Just in case it ever changes + if (prefix_length > 0) { // Just in case it ever changes ERR_PRINTS("Prefix for enum 'Error' is not empty"); } - ienum.prefix = "Err"; + prefix_length = 1; // 'ERR_' } + + _apply_prefix_to_enum_constants(ienum, prefix_length); } } @@ -2542,25 +2388,22 @@ void BindingsGenerator::initialize() { _populate_global_constants(); - // Populate internal calls (after populating type interfaces and global constants) + // Generate internal calls (after populating type interfaces and global constants) - _generate_header_icalls(); + core_custom_icalls.clear(); + editor_custom_icalls.clear(); for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) _generate_method_icalls(E.get()); - - _generate_method_icalls(builtin_types["NodePath"]); - _generate_method_icalls(builtin_types["RID"]); } void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) { - const int NUM_OPTIONS = 3; + const int NUM_OPTIONS = 2; int options_left = NUM_OPTIONS; String mono_glue_option = "--generate-mono-glue"; - String cs_core_api_option = "--generate-cs-core-api"; - String cs_editor_api_option = "--generate-cs-editor-api"; + String cs_api_option = "--generate-cs-api"; verbose_output = true; @@ -2574,42 +2417,24 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) if (path_elem) { if (get_singleton()->generate_glue(path_elem->get()) != OK) - ERR_PRINT("Mono glue generation failed"); + ERR_PRINTS(mono_glue_option + ": Failed to generate mono glue"); elem = elem->next(); } else { - ERR_PRINTS("--generate-mono-glue: No output directory specified"); + ERR_PRINTS(mono_glue_option + ": No output directory specified"); } --options_left; - } else if (elem->get() == cs_core_api_option) { + } else if (elem->get() == cs_api_option) { const List<String>::Element *path_elem = elem->next(); if (path_elem) { - if (get_singleton()->generate_cs_core_project(path_elem->get()) != OK) - ERR_PRINT("Generation of solution and C# project for the Core API failed"); + if (get_singleton()->generate_cs_api(path_elem->get()) != OK) + ERR_PRINTS(cs_api_option + ": Failed to generate the C# API"); elem = elem->next(); } else { - ERR_PRINTS(cs_core_api_option + ": No output directory specified"); - } - - --options_left; - - } else if (elem->get() == cs_editor_api_option) { - - const List<String>::Element *path_elem = elem->next(); - - if (path_elem) { - if (path_elem->next()) { - if (get_singleton()->generate_cs_editor_project(path_elem->get(), path_elem->next()->get()) != OK) - ERR_PRINT("Generation of solution and C# project for the Editor API failed"); - elem = path_elem->next(); - } else { - ERR_PRINTS(cs_editor_api_option + ": No hint path for the Core API dll specified"); - } - } else { - ERR_PRINTS(cs_editor_api_option + ": No output directory specified"); + ERR_PRINTS(cs_api_option + ": No output directory specified"); } --options_left; @@ -2621,7 +2446,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) verbose_output = false; if (options_left != NUM_OPTIONS) - exit(0); + ::exit(0); } #endif diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 5b33a0e53f..91c474c4f0 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -31,32 +31,34 @@ #ifndef BINDINGS_GENERATOR_H #define BINDINGS_GENERATOR_H -#include "class_db.h" +#include "core/class_db.h" +#include "dotnet_solution.h" #include "editor/doc/doc_data.h" #include "editor/editor_help.h" #ifdef DEBUG_METHODS_ENABLED -#include "ustring.h" +#include "core/ustring.h" class BindingsGenerator { struct ConstantInterface { String name; + String proxy_name; int value; const DocData::ConstantDoc *const_doc; ConstantInterface() {} - ConstantInterface(const String &p_name, int p_value) { + ConstantInterface(const String &p_name, const String &p_proxy_name, int p_value) { name = p_name; + proxy_name = p_proxy_name; value = p_value; } }; struct EnumInterface { StringName cname; - String prefix; List<ConstantInterface> constants; _FORCE_INLINE_ bool operator==(const EnumInterface &p_ienum) const { @@ -192,7 +194,7 @@ class BindingsGenerator { /** * Used only by Object-derived types. - * Determines if this type is not virtual (incomplete). + * Determines if this type is not abstract (incomplete). * e.g.: CanvasItem cannot be instantiated. */ bool is_instantiable; @@ -204,12 +206,6 @@ class BindingsGenerator { */ bool memory_own; - /** - * Determines if the file must have a using directive for System.Collections.Generic - * e.g.: When the generated class makes use of Dictionary - */ - bool requires_collections; - // !! The comments of the following fields make reference to other fields via square brackets, e.g.: [field_name] // !! When renaming those fields, make sure to rename their references in the comments @@ -229,7 +225,7 @@ class BindingsGenerator { String c_in; /** - * Determines the name of the variable that will be passed as argument to a ptrcall. + * Determines the expression that will be passed as argument to ptrcall. * By default the value equals the name of the parameter, * this varies for types that require special manipulation via [c_in]. * Formatting elements: @@ -295,7 +291,7 @@ class BindingsGenerator { /** * Type used for method signatures, both for parameters and the return type. - * Same as [proxy_name] except for variable arguments (VarArg). + * Same as [proxy_name] except for variable arguments (VarArg) and collections (which include the namespace). */ String cs_type; @@ -339,8 +335,6 @@ class BindingsGenerator { itype.proxy_name = itype.name; itype.c_type = itype.name; - itype.c_type_in = "void*"; - itype.c_type_out = "MonoObject*"; itype.cs_type = itype.proxy_name; itype.im_type_in = "ref " + itype.proxy_name; itype.im_type_out = itype.proxy_name; @@ -391,10 +385,19 @@ class BindingsGenerator { } static void postsetup_enum_type(TypeInterface &r_enum_itype) { - r_enum_itype.c_arg_in = "&%s"; - r_enum_itype.c_type = "int"; - r_enum_itype.c_type_in = "int"; - r_enum_itype.c_type_out = "int"; + // C interface is the same as that of 'int'. Remember to apply any + // changes done here to the 'int' type interface as well + + r_enum_itype.c_arg_in = "&%s_in"; + { + // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'. + r_enum_itype.c_in = "\t%0 %1_in = (%0)%1;\n"; + r_enum_itype.c_out = "\treturn (%0)%1;\n"; + r_enum_itype.c_type = "int64_t"; + } + r_enum_itype.c_type_in = "int32_t"; + r_enum_itype.c_type_out = r_enum_itype.c_type_in; + r_enum_itype.cs_type = r_enum_itype.proxy_name; r_enum_itype.cs_in = "(int)%s"; r_enum_itype.cs_out = "return (%1)%0;"; @@ -414,7 +417,6 @@ class BindingsGenerator { is_instantiable = false; memory_own = false; - requires_collections = false; c_arg_in = "%s"; @@ -463,10 +465,7 @@ class BindingsGenerator { List<EnumInterface> global_enums; List<ConstantInterface> global_constants; - Map<StringName, String> extra_members; - List<InternalCall> method_icalls; - List<InternalCall> builtin_method_icalls; Map<const MethodInterface *, const InternalCall *> method_icalls_map; List<const InternalCall *> generated_icall_funcs; @@ -523,16 +522,15 @@ class BindingsGenerator { return p_type.name; } - String _determine_enum_prefix(const EnumInterface &p_ienum); + int _determine_enum_prefix(const EnumInterface &p_ienum); + void _apply_prefix_to_enum_constants(EnumInterface &p_ienum, int p_prefix_length); - void _generate_header_icalls(); void _generate_method_icalls(const TypeInterface &p_itype); const TypeInterface *_get_type_or_null(const TypeReference &p_typeref); const TypeInterface *_get_type_or_placeholder(const TypeReference &p_typeref); void _default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg); - void _populate_builtin_type(TypeInterface &r_itype, Variant::Type vtype); void _populate_object_type_interfaces(); void _populate_builtin_type_interfaces(); @@ -559,12 +557,12 @@ class BindingsGenerator { static BindingsGenerator *singleton; public: - Error generate_cs_core_project(const String &p_output_dir, bool p_verbose_output = true); - Error generate_cs_editor_project(const String &p_output_dir, const String &p_core_dll_path, bool p_verbose_output = true); + Error generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output = true); + Error generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output = true); + Error generate_cs_api(const String &p_output_dir, bool p_verbose_output = true); Error generate_glue(const String &p_output_dir); static uint32_t get_version(); - static uint32_t get_cs_glue_version(); void initialize(); diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index bc95607743..416108603b 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -30,11 +30,17 @@ #include "csharp_project.h" -#include "os/os.h" -#include "project_settings.h" +#include "core/io/json.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "core/project_settings.h" +#include "../csharp_script.h" #include "../mono_gd/gd_mono_class.h" #include "../mono_gd/gd_mono_marshal.h" +#include "../utils/string_utils.h" +#include "script_class_parser.h" namespace CSharpProject { @@ -51,28 +57,28 @@ String generate_core_api_project(const String &p_dir, const Vector<String> &p_fi MonoObject *ret = klass->get_method("GenCoreApiProject", 2)->invoke(NULL, args, &exc); if (exc) { - GDMonoUtils::debug_unhandled_exception(exc); + GDMonoUtils::debug_print_unhandled_exception(exc); ERR_FAIL_V(String()); } return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); } -String generate_editor_api_project(const String &p_dir, const String &p_core_dll_path, const Vector<String> &p_files) { +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_dll_path = p_core_dll_path; + Variant core_proj_path = p_core_proj_path; Variant compile_items = p_files; - const Variant *args[3] = { &dir, &core_dll_path, &compile_items }; + 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_unhandled_exception(exc); + GDMonoUtils::debug_print_unhandled_exception(exc); ERR_FAIL_V(String()); } @@ -93,7 +99,7 @@ String generate_game_project(const String &p_dir, const String &p_name, const Ve MonoObject *ret = klass->get_method("GenGameProject", 3)->invoke(NULL, args, &exc); if (exc) { - GDMonoUtils::debug_unhandled_exception(exc); + GDMonoUtils::debug_print_unhandled_exception(exc); ERR_FAIL_V(String()); } @@ -102,6 +108,9 @@ String generate_game_project(const String &p_dir, const String &p_name, const Ve void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) { + if (!GLOBAL_DEF("mono/project/auto_update_project", true)) + return; + _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils"); @@ -114,8 +123,122 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str klass->get_method("AddItemToProjectChecked", 3)->invoke(NULL, args, &exc); if (exc) { - GDMonoUtils::debug_unhandled_exception(exc); + GDMonoUtils::debug_print_unhandled_exception(exc); ERR_FAIL(); } } + +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(); + 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); + + 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 381dd17e02..aeeeff50f0 100644 --- a/modules/mono/editor/csharp_project.h +++ b/modules/mono/editor/csharp_project.h @@ -31,7 +31,7 @@ #ifndef CSHARP_PROJECT_H #define CSHARP_PROJECT_H -#include "ustring.h" +#include "core/ustring.h" namespace CSharpProject { @@ -40,6 +40,9 @@ String generate_editor_api_project(const String &p_dir, const String &p_core_dll String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files = Vector<String>()); 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/net_solution.cpp b/modules/mono/editor/dotnet_solution.cpp index dab96e44e9..ab92e2e378 100644 --- a/modules/mono/editor/net_solution.cpp +++ b/modules/mono/editor/dotnet_solution.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* net_solution.cpp */ +/* dotnet_solution.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,10 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "net_solution.h" +#include "dotnet_solution.h" -#include "os/dir_access.h" -#include "os/file_access.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" #include "../utils/path_utils.h" #include "../utils/string_utils.h" @@ -52,33 +52,32 @@ #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 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 NETSolution::add_new_project(const String &p_name, const String &p_guid, const Vector<String> &p_extra_configs) { - if (projects.has(p_name)) - WARN_PRINT("Overriding existing project."); - - ProjectInfo procinfo; - procinfo.guid = p_guid; +void DotNetSolution::add_new_project(const String &p_name, const ProjectInfo &p_project_info) { + projects[p_name] = p_project_info; +} - procinfo.configs.push_back("Debug"); - procinfo.configs.push_back("Release"); +bool DotNetSolution::has_project(const String &p_name) const { + return projects.find(p_name) != NULL; +} - for (int i = 0; i < p_extra_configs.size(); i++) { - procinfo.configs.push_back(p_extra_configs[i]); - } +const DotNetSolution::ProjectInfo &DotNetSolution::get_project_info(const String &p_name) const { + return projects[p_name]; +} - projects[p_name] = procinfo; +bool DotNetSolution::remove_project(const String &p_name) { + return projects.erase(p_name); } -Error NETSolution::save() { +Error DotNetSolution::save() { bool dir_exists = DirAccess::exists(path); ERR_EXPLAIN("The directory does not exist."); - ERR_FAIL_COND_V(!dir_exists, ERR_FILE_BAD_PATH); + ERR_FAIL_COND_V(!dir_exists, ERR_FILE_NOT_FOUND); String projs_decl; String sln_platform_cfg; @@ -86,34 +85,40 @@ Error NETSolution::save() { for (Map<String, ProjectInfo>::Element *E = projects.front(); E; E = E->next()) { const String &name = E->key(); - const ProjectInfo &procinfo = E->value(); + const ProjectInfo &proj_info = E->value(); - projs_decl += sformat(PROJECT_DECLARATION, name, name + ".csproj", procinfo.guid); + bool is_front = E == projects.front(); - for (int i = 0; i < procinfo.configs.size(); i++) { - const String &config = procinfo.configs[i]; + if (!is_front) + projs_decl += "\n"; - if (i != 0) { + 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, procinfo.guid, 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); - FileAccessRef file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE); - ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); + 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 NETSolution::set_path(const String &p_existing_path) { +bool DotNetSolution::set_path(const String &p_existing_path) { if (p_existing_path.is_abs_path()) { path = p_existing_path; } else { @@ -126,6 +131,10 @@ bool NETSolution::set_path(const String &p_existing_path) { return true; } -NETSolution::NETSolution(const String &p_name) { +String DotNetSolution::get_path() { + return path; +} + +DotNetSolution::DotNetSolution(const String &p_name) { name = p_name; } diff --git a/modules/mono/editor/net_solution.h b/modules/mono/editor/dotnet_solution.h index 293e86917a..629605ad18 100644 --- a/modules/mono/editor/net_solution.h +++ b/modules/mono/editor/dotnet_solution.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* net_solution.h */ +/* dotnet_solution.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -31,26 +31,31 @@ #ifndef NET_SOLUTION_H #define NET_SOLUTION_H -#include "map.h" -#include "ustring.h" +#include "core/map.h" +#include "core/ustring.h" -struct NETSolution { +struct DotNetSolution { String name; - void add_new_project(const String &p_name, const String &p_guid, const Vector<String> &p_extra_configs = Vector<String>()); + struct ProjectInfo { + String guid; + String relpath; // Must be relative to the solution directory + Vector<String> configs; + }; + + 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); Error save(); bool set_path(const String &p_existing_path); + String get_path(); - NETSolution(const String &p_name); + DotNetSolution(const String &p_name); private: - struct ProjectInfo { - String guid; - Vector<String> configs; - }; - String path; Map<String, ProjectInfo> projects; }; diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp index b3b259e851..0f12fc5243 100644 --- a/modules/mono/editor/godotsharp_builds.cpp +++ b/modules/mono/editor/godotsharp_builds.cpp @@ -30,13 +30,16 @@ #include "godotsharp_builds.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)" @@ -50,6 +53,16 @@ void godot_icall_BuildInstance_ExitCallback(MonoString *p_solution, MonoString * 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); @@ -61,15 +74,9 @@ String _find_build_engine_on_unix(const String &p_name) { if (ret_fallback.length()) return ret_fallback; - const char *locations[] = { -#ifdef OSX_ENABLED - "/Library/Frameworks/Mono.framework/Versions/Current/bin/", - "/usr/local/var/homebrew/linked/mono/bin/", -#endif - "/opt/novell/mono/bin/" - }; + static Vector<const char *> locations = _get_msbuild_hint_dirs(); - for (int i = 0; i < sizeof(locations) / sizeof(const char *); i++) { + for (int i = 0; i < locations.size(); i++) { String hint_path = locations[i] + p_name; if (FileAccess::exists(hint_path)) { @@ -88,7 +95,12 @@ MonoString *godot_icall_BuildInstance_get_MSBuildPath() { #if defined(WINDOWS_ENABLED) switch (build_tool) { case GodotSharpBuilds::MSBUILD_VS: { - static String msbuild_tools_path = MonoRegUtils::find_msbuild_tools_path(); + 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.length()) { if (!msbuild_tools_path.ends_with("\\")) @@ -97,8 +109,7 @@ MonoString *godot_icall_BuildInstance_get_MSBuildPath() { return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe"); } - if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->print("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Trying with '" PROP_NAME_MSBUILD_MONO "'...\n"); + print_verbose("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Trying with '" PROP_NAME_MSBUILD_MONO "'..."); } // FALL THROUGH case GodotSharpBuilds::MSBUILD_MONO: { String msbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("msbuild.bat"); @@ -123,15 +134,25 @@ MonoString *godot_icall_BuildInstance_get_MSBuildPath() { CRASH_NOW(); } #elif defined(UNIX_ENABLED) - static String msbuild_path = _find_build_engine_on_unix("msbuild"); - static String xbuild_path = _find_build_engine_on_unix("xbuild"); + 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()) { WARN_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()) { WARN_PRINT("Cannot find binary for '" PROP_NAME_MSBUILD_MONO "'"); return NULL; @@ -187,7 +208,11 @@ MonoBoolean godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows() { #endif } -void GodotSharpBuilds::_register_internal_calls() { +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); @@ -202,18 +227,24 @@ void GodotSharpBuilds::show_build_error_dialog(const String &p_message) { MonoBottomPanel::get_singleton()->show_build_tab(); } -bool GodotSharpBuilds::build_api_sln(const String &p_name, const String &p_api_sln_dir, const String &p_config) { +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(p_name + ".sln"); - String api_assembly_dir = p_api_sln_dir.plus_file("bin").plus_file(p_config); - String api_assembly_file = api_assembly_dir.plus_file(p_name + ".dll"); + String api_sln_file = p_api_sln_dir.plus_file(API_SOLUTION_NAME ".sln"); - if (!FileAccess::exists(api_assembly_file)) { + 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 " + p_name + " solution."); + show_build_error_dialog("Failed to build " API_SOLUTION_NAME " solution."); return false; } } @@ -223,6 +254,18 @@ bool GodotSharpBuilds::build_api_sln(const String &p_name, const String &p_api_s 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); @@ -230,7 +273,7 @@ bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String & 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)) { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + 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) @@ -242,8 +285,6 @@ bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String & Error err = da->copy(assembly_src, assembly_dst); - memdelete(da); - if (err != OK) { show_build_error_dialog("Failed to copy " + assembly_file); return false; @@ -262,82 +303,56 @@ String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) { GDMono::get_singleton()->get_api_editor_hash(); return String::num_uint64(api_hash) + "_" + String::num_uint64(BindingsGenerator::get_version()) + - "_" + String::num_uint64(BindingsGenerator::get_cs_glue_version()); + "_" + String::num_uint64(CS_GLUE_VERSION); } -bool GodotSharpBuilds::make_api_sln(APIAssembly::Type p_api_type) { +bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) { - String api_name = p_api_type == APIAssembly::API_CORE ? API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; - String api_build_config = "Release"; + String api_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; - EditorProgress pr("mono_build_release_" + api_name, "Building " + api_name + " solution...", 4); + String editor_prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir(); + String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - pr.step("Generating " + api_name + " solution"); + 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 core_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir() - .plus_file(_api_folder_name(APIAssembly::API_CORE)) - .plus_file(API_ASSEMBLY_NAME); - String editor_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir() - .plus_file(_api_folder_name(APIAssembly::API_EDITOR)) - .plus_file(EDITOR_API_ASSEMBLY_NAME); + String api_build_config = "Release"; - String api_sln_dir = p_api_type == APIAssembly::API_CORE ? core_api_sln_dir : editor_api_sln_dir; - String api_sln_file = api_sln_dir.plus_file(api_name + ".sln"); + EditorProgress pr("mono_build_release_" API_SOLUTION_NAME, "Building " API_SOLUTION_NAME " solution...", 3); - if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) { - String core_api_assembly; + pr.step("Generating " API_SOLUTION_NAME " solution", 0); - if (p_api_type == APIAssembly::API_EDITOR) { - core_api_assembly = core_api_sln_dir.plus_file("bin") - .plus_file(api_build_config) - .plus_file(API_ASSEMBLY_NAME ".dll"); - } + String api_sln_dir = GodotSharpDirs::get_mono_solutions_dir() + .plus_file(_api_folder_name(APIAssembly::API_CORE)); -#ifndef DEBUG_METHODS_ENABLED -#error "How am I supposed to generate the bindings?" -#endif + 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 *gen = BindingsGenerator::get_singleton(); bool gen_verbose = OS::get_singleton()->is_stdout_verbose(); - Error err = p_api_type == APIAssembly::API_CORE ? - gen->generate_cs_core_project(api_sln_dir, gen_verbose) : - gen->generate_cs_editor_project(api_sln_dir, core_api_assembly, gen_verbose); - + Error err = gen->generate_cs_api(api_sln_dir, gen_verbose); if (err != OK) { - show_build_error_dialog("Failed to generate " + api_name + " solution. Error: " + itos(err)); + show_build_error_dialog("Failed to generate " API_SOLUTION_NAME " solution. Error: " + itos(err)); return false; } } - pr.step("Building " + api_name + " solution"); + pr.step("Building " API_SOLUTION_NAME " solution", 1); - if (!GodotSharpBuilds::build_api_sln(api_name, api_sln_dir, api_build_config)) + if (!GodotSharpBuilds::build_api_sln(api_sln_dir, api_build_config)) return false; - pr.step("Copying " + api_name + " assembly"); - - String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - - // Create assemblies directory if needed - if (!DirAccess::exists(res_assemblies_dir)) { - DirAccess *da = DirAccess::create_for_path(res_assemblies_dir); - Error err = da->make_dir_recursive(res_assemblies_dir); - memdelete(da); - - if (err != OK) { - show_build_error_dialog("Failed to create assemblies directory. Error: " + itos(err)); - 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("bin").plus_file(api_build_config); + 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; - pr.step("Done"); - return true; } @@ -346,15 +361,14 @@ bool GodotSharpBuilds::build_project_blocking(const String &p_config) { if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) return true; // No solution to build - if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_CORE)) + if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) return false; - if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_EDITOR)) + if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) return false; - EditorProgress pr("mono_project_debug_build", "Building project solution...", 2); - - pr.step("Building project solution"); + 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); if (!GodotSharpBuilds::get_singleton()->build(build_info)) { @@ -362,13 +376,25 @@ bool GodotSharpBuilds::build_project_blocking(const String &p_config) { return false; } - pr.step("Done"); - return true; } bool GodotSharpBuilds::editor_build_callback() { + 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); + } + return build_project_blocking("Tools"); } @@ -554,8 +580,9 @@ void GodotSharpBuilds::BuildProcess::start(bool p_blocking) { exited = true; exit_code = klass->get_field("exitCode")->get_int_value(mono_object); - if (exit_code != 0 && OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->print(String("MSBuild finished with exit code " + itos(exit_code) + "\n").utf8()); + if (exit_code != 0) { + print_verbose("MSBuild finished with exit code " + itos(exit_code)); + } build_tab->on_build_exit(exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR); } else { diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h index 4afc284d45..7f38b0aa49 100644 --- a/modules/mono/editor/godotsharp_builds.h +++ b/modules/mono/editor/godotsharp_builds.h @@ -61,9 +61,6 @@ private: static GodotSharpBuilds *singleton; - friend class GDMono; - static void _register_internal_calls(); - public: enum BuildTool { MSBUILD_MONO, @@ -75,6 +72,8 @@ public: _FORCE_INLINE_ static GodotSharpBuilds *get_singleton() { return singleton; } + static void register_internal_calls(); + static void show_build_error_dialog(const String &p_message); void build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code); @@ -85,10 +84,10 @@ public: 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_name, const String &p_api_sln_dir, const String &p_config); + 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_sln(APIAssembly::Type p_api_type); + static bool make_api_assembly(APIAssembly::Type p_api_type); static bool build_project_blocking(const String &p_config); diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp index 998da8bda3..cce86efbf5 100644 --- a/modules/mono/editor/godotsharp_editor.cpp +++ b/modules/mono/editor/godotsharp_editor.cpp @@ -38,11 +38,16 @@ #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" -#include "net_solution.h" + +#ifdef OSX_ENABLED +#include "../utils/osx_utils.h" +#endif #ifdef WINDOWS_ENABLED #include "../utils/mono_reg_utils.h" @@ -66,17 +71,21 @@ bool GodotSharpEditor::_create_project_solution() { if (guid.length()) { - NETSolution solution(name); + DotNetSolution solution(name); if (!solution.set_path(path)) { show_error_dialog(TTR("Failed to create solution.")); return false; } - Vector<String> extra_configs; - extra_configs.push_back("Tools"); + 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, guid, extra_configs); + solution.add_new_project(name, proj_info); Error sln_error = solution.save(); @@ -85,10 +94,10 @@ bool GodotSharpEditor::_create_project_solution() { return false; } - if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_CORE)) + if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) return false; - if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_EDITOR)) + if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) return false; pr.step(TTR("Done")); @@ -103,6 +112,33 @@ bool GodotSharpEditor::_create_project_solution() { return true; } +void GodotSharpEditor::_make_api_solutions_if_needed() { + // I'm sick entirely of ProgressDialog + static bool recursion_guard = false; + if (!recursion_guard) { + recursion_guard = true; + _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)); @@ -164,11 +200,38 @@ void GodotSharpEditor::_notification(int p_notification) { void GodotSharpEditor::_bind_methods() { 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); @@ -181,8 +244,36 @@ Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int ExternalEditor editor = ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))); switch (editor) { - case EDITOR_CODE: { + 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 + vscode_path = path_which("code"); + } + 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()); @@ -194,18 +285,47 @@ Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int args.push_back(script_path); } - static String program = path_which("code"); +#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); - Error err = OS::get_singleton()->execute(program.length() ? program : "code", args, false); + String command = vscode_path; +#endif + + Error err = OS::get_singleton()->execute(command, args, false); if (err != OK) { - ERR_PRINT("GodotSharp: Could not execute external editor"); + 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: { - if (!monodevel_instance) - monodevel_instance = memnew(MonoDevelopInstance(GodotSharpDirs::get_project_sln_path())); +#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()); @@ -213,7 +333,7 @@ Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int script_path += ";" + itos(p_line + 1) + ";" + itos(p_col); } - monodevel_instance->execute(script_path); + (*instance)->execute(script_path); } break; default: return ERR_UNAVAILABLE; @@ -231,7 +351,10 @@ GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { singleton = this; - monodevel_instance = NULL; + monodevelop_instance = NULL; +#ifdef OSX_ENABLED + visualstudio_mac_instance = NULL; +#endif editor = p_editor; @@ -278,13 +401,11 @@ GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { about_label->set_autowrap(true); String about_text = String("C# support in Godot Engine is a brand new feature and a work in progress.\n") + - "It is at the alpha stage and thus not suitable for use in production.\n\n" + - "As of Godot 3.0, C# support is not feature-complete and may crash in some situations. " + - "Bugs and usability issues will be addressed gradually over 3.0.x and 3.x releases, " + + "It is currently in an alpha stage and is not suitable for use in production.\n\n" + + "As of Godot 3.1, C# support is not feature-complete and may crash in some situations. " + + "Bugs and usability issues will be addressed gradually over future 3.x releases, " + "including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + - "The main missing feature is the ability to export games using C# assemblies - you will therefore be able to develop and run games in the editor, " + - "but not to share them as standalone binaries yet. This feature is of course high on the priority list and should be available as soon as possible.\n\n" + - "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, Mono version, IDE, etc.:\n\n" + + "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, Mono 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); @@ -301,7 +422,10 @@ GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { 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)) { + if (FileAccess::exists(sln_path) && FileAccess::exists(csproj_path)) { + // We can't use EditorProgress here. It calls Main::iterarion() and the main loop is not initialized yet. + call_deferred("_make_api_solutions_if_needed"); + } else { bottom_panel_btn->hide(); menu_popup->add_item(TTR("Create C# solution"), MENU_CREATE_SLN); } @@ -316,7 +440,18 @@ GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { // External editor settings EditorSettings *ed_settings = EditorSettings::get_singleton(); EDITOR_DEF("mono/editor/external_editor", EDITOR_NONE); - ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/editor/external_editor", PROPERTY_HINT_ENUM, "None,MonoDevelop,Visual Studio Code")); + + String settings_hint_str = "None"; + +#ifdef WINDOWS_ENABLED + settings_hint_str += ",MonoDevelop,Visual Studio Code"; +#elif OSX_ENABLED + settings_hint_str += ",Visual Studio,MonoDevelop,Visual Studio Code"; +#elif 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; @@ -330,9 +465,9 @@ GodotSharpEditor::~GodotSharpEditor() { memdelete(godotsharp_builds); - if (monodevel_instance) { - memdelete(monodevel_instance); - monodevel_instance = NULL; + if (monodevelop_instance) { + memdelete(monodevelop_instance); + monodevelop_instance = NULL; } } @@ -340,7 +475,9 @@ MonoReloadNode *MonoReloadNode::singleton = NULL; void MonoReloadNode::_reload_timer_timeout() { - CSharpLanguage::get_singleton()->reload_assemblies_if_needed(false); + if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { + CSharpLanguage::get_singleton()->reload_assemblies(false); + } } void MonoReloadNode::restart_reload_timer() { @@ -358,7 +495,9 @@ void MonoReloadNode::_notification(int p_what) { switch (p_what) { case MainLoop::NOTIFICATION_WM_FOCUS_IN: { restart_reload_timer(); - CSharpLanguage::get_singleton()->reload_assemblies_if_needed(true); + if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { + CSharpLanguage::get_singleton()->reload_assemblies(false); + } } break; default: { } break; diff --git a/modules/mono/editor/godotsharp_editor.h b/modules/mono/editor/godotsharp_editor.h index 66da814c8b..9fb0e40132 100644 --- a/modules/mono/editor/godotsharp_editor.h +++ b/modules/mono/editor/godotsharp_editor.h @@ -50,9 +50,14 @@ class GodotSharpEditor : public Node { GodotSharpBuilds *godotsharp_builds; - MonoDevelopInstance *monodevel_instance; + 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(); @@ -74,12 +79,24 @@ public: enum ExternalEditor { EDITOR_NONE, +#ifdef WINDOWS_ENABLED + //EDITOR_VISUALSTUDIO, // TODO EDITOR_MONODEVELOP, - EDITOR_CODE, + EDITOR_VSCODE +#elif OSX_ENABLED + EDITOR_VISUALSTUDIO_MAC, + EDITOR_MONODEVELOP, + EDITOR_VSCODE +#elif 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); diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index cd09e6516a..34c710320a 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -30,12 +30,38 @@ #include "godotsharp_export.h" +#include "core/version.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" -void GodotSharpExport::_export_file(const String &p_path, const String &p_type, const Set<String> &p_features) { +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; @@ -56,62 +82,87 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug // 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(GDMono::get_singleton()->get_tools_domain()); + ERR_FAIL_NULL(TOOLS_DOMAIN); + ERR_FAIL_NULL(GDMono::get_singleton()->get_editor_tools_assembly()); String build_config = p_debug ? "Debug" : "Release"; - ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config)); + 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); - // Add API assemblies + ERR_FAIL_COND(!_add_file(scripts_metadata_path, scripts_metadata_path)); - String core_api_dll_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(API_ASSEMBLY_NAME ".dll"); - ERR_FAIL_COND(!_add_assembly(core_api_dll_path, core_api_dll_path)); + ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config)); - String editor_api_dll_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - ERR_FAIL_COND(!_add_assembly(editor_api_dll_path, editor_api_dll_path)); + // Add dependency assemblies - // Add project assembly + 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_path = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config).plus_file(project_dll_name + ".dll"); - String project_dll_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(project_dll_name + ".dll"); - ERR_FAIL_COND(!_add_assembly(project_dll_src_path, project_dll_dst_path)); + 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); - // Add dependencies + { + MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain"); + ERR_FAIL_NULL(export_domain); + _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); - MonoDomain *prev_domain = mono_domain_get(); - MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain"); + _GDMONO_SCOPE_DOMAIN_(export_domain); - ERR_FAIL_COND(!export_domain); - ERR_FAIL_COND(!mono_domain_set(export_domain, false)); + GDMonoAssembly *scripts_assembly = NULL; + bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name, + project_dll_src_path, &scripts_assembly, /* refonly: */ true); - Map<String, String> dependencies; - dependencies.insert("mscorlib", GDMono::get_singleton()->get_corlib_assembly()->get_path()); + ERR_EXPLAIN("Cannot load refonly assembly: " + project_dll_name); + ERR_FAIL_COND(!load_success); - GDMonoAssembly *scripts_assembly = GDMonoAssembly::load_from(project_dll_name, project_dll_src_path, /* refonly: */ true); + Vector<String> search_dirs; + GDMonoAssembly::fill_search_dirs(search_dirs); + 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)); + } - ERR_EXPLAIN("Cannot load refonly assembly: " + project_dll_name); - ERR_FAIL_COND(!scripts_assembly); + // Mono specific export template extras (data dir) - Error depend_error = _get_assembly_dependencies(scripts_assembly, dependencies); + 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); - GDMono::get_singleton()->finalize_and_unload_domain(export_domain); - mono_domain_set(prev_domain, false); + 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_set(features, MonoString *, i, boxed); + i++; + } - ERR_FAIL_COND(depend_error != OK); + 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); - 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_assembly(depend_src_path, depend_dst_path)); + if (exc) { + GDMonoUtils::debug_print_unhandled_exception(exc); + ERR_FAIL(); } } -bool GodotSharpExport::_add_assembly(const String &p_src_path, const String &p_dst_path) { +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); @@ -120,12 +171,12 @@ bool GodotSharpExport::_add_assembly(const String &p_src_path, const String &p_d data.resize(f->get_len()); f->get_buffer(data.ptrw(), data.size()); - add_file(p_dst_path, data, false); + add_file(p_dst_path, data, p_remap); return true; } -Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, Map<String, String> &r_dependencies) { +Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies) { MonoImage *image = p_assembly->get_image(); @@ -134,18 +185,48 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, M mono_assembly_get_assemblyref(image, i, ref_aname); String ref_name = mono_assembly_name_get_name(ref_aname); - if (ref_name == "mscorlib" || r_dependencies.find(ref_name)) + if (r_dependencies.find(ref_name)) continue; GDMonoAssembly *ref_assembly = NULL; - if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true)) { - ERR_EXPLAIN("Cannot load refonly assembly: " + ref_name); + String path; + bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe"); + + for (int i = 0; i < p_search_dirs.size(); i++) { + const String &search_dir = p_search_dirs[i]; + + if (has_extension) { + path = search_dir.plus_file(ref_name); + if (FileAccess::exists(path)) { + GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true); + if (ref_assembly != NULL) + break; + } + } else { + path = search_dir.plus_file(ref_name + ".dll"); + if (FileAccess::exists(path)) { + GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true); + if (ref_assembly != NULL) + break; + } + + path = search_dir.plus_file(ref_name + ".exe"); + if (FileAccess::exists(path)) { + GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true); + if (ref_assembly != NULL) + break; + } + } + } + + if (!ref_assembly) { + ERR_EXPLAIN("Cannot load assembly (refonly): " + ref_name); ERR_FAIL_V(ERR_CANT_RESOLVE); } r_dependencies.insert(ref_name, ref_assembly->get_path()); - Error err = _get_assembly_dependencies(ref_assembly, r_dependencies); + Error err = _get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); if (err != OK) return err; } diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h index b38db9660c..10d4375567 100644 --- a/modules/mono/editor/godotsharp_export.h +++ b/modules/mono/editor/godotsharp_export.h @@ -41,15 +41,17 @@ class GodotSharpExport : public EditorExportPlugin { MonoAssemblyName *aname_prealloc; - bool _add_assembly(const String &p_src_path, const String &p_dst_path); + bool _add_file(const String &p_src_path, const String &p_dst_path, bool p_remap = false); - Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, Map<String, String> &r_dependencies); + 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); public: + static void register_internal_calls(); + GodotSharpExport(); ~GodotSharpExport(); }; diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp index 9317550d28..e89d21d92d 100644 --- a/modules/mono/editor/mono_bottom_panel.cpp +++ b/modules/mono/editor/mono_bottom_panel.cpp @@ -31,6 +31,8 @@ #include "mono_bottom_panel.h" #include "../csharp_script.h" +#include "../godotsharp_dirs.h" +#include "csharp_project.h" #include "godotsharp_editor.h" MonoBottomPanel *MonoBottomPanel::singleton = NULL; @@ -53,9 +55,9 @@ void MonoBottomPanel::_update_build_tabs_list() { build_tabs_list->add_item(item_name, tab->get_icon_texture()); - String item_tooltip = String("Solution: ") + tab->build_info.solution; - item_tooltip += String("\nConfiguration: ") + tab->build_info.configuration; - item_tooltip += String("\nStatus: "); + 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"; @@ -63,7 +65,7 @@ void MonoBottomPanel::_update_build_tabs_list() { item_tooltip += "Running"; } - if (!tab->build_exited || !tab->build_result == MonoBuildTab::RESULT_SUCCESS) { + if (!tab->build_exited || tab->build_result == MonoBuildTab::RESULT_ERROR) { item_tooltip += "\nErrors: " + itos(tab->error_count); } @@ -148,10 +150,18 @@ void MonoBottomPanel::_errors_toggled(bool p_pressed) { void MonoBottomPanel::_build_project_pressed() { - GodotSharpBuilds::get_singleton()->build_project_blocking("Tools"); + String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor"); + Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path); + ERR_FAIL_COND(metadata_err != OK); - MonoReloadNode::get_singleton()->restart_reload_timer(); - CSharpLanguage::get_singleton()->reload_assemblies_if_needed(true); + bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools"); + + if (build_success) { + 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() { @@ -475,14 +485,14 @@ void MonoBuildTab::_bind_methods() { } MonoBuildTab::MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir) : - build_info(p_build_info), - logs_dir(p_logs_dir), build_exited(false), issues_list(memnew(ItemList)), error_count(0), warning_count(0), errors_visible(true), - warnings_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/monodevelop_instance.cpp b/modules/mono/editor/monodevelop_instance.cpp index 9f05711fd6..1d858d80bf 100644 --- a/modules/mono/editor/monodevelop_instance.cpp +++ b/modules/mono/editor/monodevelop_instance.cpp @@ -47,7 +47,7 @@ void MonoDevelopInstance::execute(const Vector<String> &p_files) { execute_method->invoke(gc_handle->get_target(), args, &exc); if (exc) { - GDMonoUtils::debug_unhandled_exception(exc); + GDMonoUtils::debug_print_unhandled_exception(exc); ERR_FAIL(); } } @@ -59,7 +59,7 @@ void MonoDevelopInstance::execute(const String &p_file) { execute(files); } -MonoDevelopInstance::MonoDevelopInstance(const String &p_solution) { +MonoDevelopInstance::MonoDevelopInstance(const String &p_solution, EditorId p_editor_id) { _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) @@ -67,15 +67,16 @@ MonoDevelopInstance::MonoDevelopInstance(const String &p_solution) { MonoObject *obj = mono_object_new(TOOLS_DOMAIN, klass->get_mono_ptr()); - GDMonoMethod *ctor = klass->get_method(".ctor", 1); + GDMonoMethod *ctor = klass->get_method(".ctor", 2); MonoException *exc = NULL; Variant solution = p_solution; - const Variant *args[1] = { &solution }; + Variant editor_id = p_editor_id; + const Variant *args[2] = { &solution, &editor_id }; ctor->invoke(obj, args, &exc); if (exc) { - GDMonoUtils::debug_unhandled_exception(exc); + GDMonoUtils::debug_print_unhandled_exception(exc); ERR_FAIL(); } diff --git a/modules/mono/editor/monodevelop_instance.h b/modules/mono/editor/monodevelop_instance.h index 552c10a61d..29de4a4735 100644 --- a/modules/mono/editor/monodevelop_instance.h +++ b/modules/mono/editor/monodevelop_instance.h @@ -31,7 +31,7 @@ #ifndef MONODEVELOP_INSTANCE_H #define MONODEVELOP_INSTANCE_H -#include "reference.h" +#include "core/reference.h" #include "../mono_gc_handle.h" #include "../mono_gd/gd_mono_method.h" @@ -42,10 +42,15 @@ class MonoDevelopInstance { 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); + MonoDevelopInstance(const String &p_solution, EditorId p_editor_id); }; #endif // MONODEVELOP_INSTANCE_H diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp new file mode 100644 index 0000000000..9042bff74a --- /dev/null +++ b/modules/mono/editor/script_class_parser.cpp @@ -0,0 +1,635 @@ +#include "script_class_parser.h" + +#include "core/map.h" +#include "core/os/os.h" + +#include "../utils/string_utils.h" + +const char *ScriptClassParser::token_names[ScriptClassParser::TK_MAX] = { + "[", + "]", + "{", + "}", + ".", + ":", + ",", + "Symbol", + "Identifier", + "String", + "Number", + "<", + ">", + "EOF", + "Error" +}; + +String ScriptClassParser::get_token_name(ScriptClassParser::Token p_token) { + + ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>"); + return token_names[p_token]; +} + +ScriptClassParser::Token ScriptClassParser::get_token() { + + while (true) { + switch (code[idx]) { + case '\n': { + line++; + idx++; + break; + }; + case 0: { + return TK_EOF; + } break; + case '{': { + idx++; + return TK_CURLY_BRACKET_OPEN; + }; + case '}': { + idx++; + return TK_CURLY_BRACKET_CLOSE; + }; + case '[': { + idx++; + return TK_BRACKET_OPEN; + }; + case ']': { + idx++; + return TK_BRACKET_CLOSE; + }; + case '<': { + idx++; + return TK_OP_LESS; + }; + case '>': { + idx++; + return TK_OP_GREATER; + }; + case ':': { + idx++; + return TK_COLON; + }; + case ',': { + idx++; + return TK_COMMA; + }; + case '.': { + idx++; + return TK_PERIOD; + }; + case '#': { + //compiler directive + while (code[idx] != '\n' && code[idx] != 0) { + idx++; + } + continue; + } break; + case '/': { + switch (code[idx + 1]) { + case '*': { // block comment + idx += 2; + while (true) { + if (code[idx] == 0) { + error_str = "Unterminated comment"; + error = true; + return TK_ERROR; + } else if (code[idx] == '*' && code[idx + 1] == '/') { + idx += 2; + break; + } else if (code[idx] == '\n') { + line++; + } + + idx++; + } + + } break; + case '/': { // line comment skip + while (code[idx] != '\n' && code[idx] != 0) { + idx++; + } + + } break; + default: { + value = "/"; + idx++; + return TK_SYMBOL; + } + } + + continue; // a comment + } break; + case '\'': + case '"': { + bool verbatim = idx != 0 && code[idx - 1] == '@'; + + CharType begin_str = code[idx]; + idx++; + String tk_string = String(); + while (true) { + if (code[idx] == 0) { + error_str = "Unterminated String"; + error = true; + return TK_ERROR; + } else if (code[idx] == begin_str) { + if (verbatim && code[idx + 1] == '"') { // `""` is verbatim string's `\"` + idx += 2; // skip next `"` as well + continue; + } + + idx += 1; + break; + } else if (code[idx] == '\\' && !verbatim) { + //escaped characters... + idx++; + CharType next = code[idx]; + if (next == 0) { + error_str = "Unterminated String"; + error = true; + return TK_ERROR; + } + CharType res = 0; + + switch (next) { + case 'b': res = 8; break; + case 't': res = 9; break; + case 'n': res = 10; break; + case 'f': res = 12; break; + case 'r': + res = 13; + break; + case '\"': res = '\"'; break; + case '\\': + res = '\\'; + break; + default: { + res = next; + } break; + } + + tk_string += res; + + } else { + if (code[idx] == '\n') + line++; + tk_string += code[idx]; + } + idx++; + } + + value = tk_string; + + return TK_STRING; + } break; + default: { + if (code[idx] <= 32) { + idx++; + break; + } + + if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 63) || (code[idx] >= 91 && code[idx] <= 94) || code[idx] == 96 || (code[idx] >= 123 && code[idx] <= 127)) { + value = String::chr(code[idx]); + idx++; + return TK_SYMBOL; + } + + if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) { + //a number + const CharType *rptr; + double number = String::to_double(&code[idx], &rptr); + idx += (rptr - &code[idx]); + value = number; + return TK_NUMBER; + + } else if ((code[idx] == '@' && code[idx + 1] != '"') || code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) { + String id; + + id += code[idx]; + idx++; + + while (code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || (code[idx] >= '0' && code[idx] <= '9') || code[idx] > 127) { + id += code[idx]; + idx++; + } + + value = id; + return TK_IDENTIFIER; + } else if (code[idx] == '@' && code[idx + 1] == '"') { + // begin of verbatim string + idx++; + } else { + error_str = "Unexpected character."; + error = true; + return TK_ERROR; + } + } + } + } +} + +Error ScriptClassParser::_skip_generic_type_params() { + + Token tk; + + while (true) { + tk = get_token(); + + if (tk == TK_IDENTIFIER) { + tk = get_token(); + + if (tk == TK_PERIOD) { + while (true) { + tk = get_token(); + + if (tk != TK_IDENTIFIER) { + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + tk = get_token(); + + if (tk != TK_PERIOD) + break; + } + } + + if (tk == TK_OP_LESS) { + Error err = _skip_generic_type_params(); + if (err) + return err; + continue; + } else if (tk == TK_OP_GREATER) { + return OK; + } else if (tk != TK_COMMA) { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + } else if (tk == TK_OP_LESS) { + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found " + get_token_name(TK_OP_LESS); + error = true; + return ERR_PARSE_ERROR; + } else if (tk == TK_OP_GREATER) { + return OK; + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + } +} + +Error ScriptClassParser::_parse_type_full_name(String &r_full_name) { + + Token tk = get_token(); + + if (tk != TK_IDENTIFIER) { + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + r_full_name += String(value); + + if (code[idx] != '.') // We only want to take the next token if it's a period + return OK; + + tk = get_token(); + + CRASH_COND(tk != TK_PERIOD); // Assertion + + r_full_name += "."; + + return _parse_type_full_name(r_full_name); +} + +Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) { + + String name; + + Error err = _parse_type_full_name(name); + if (err) + return err; + + Token tk = get_token(); + + bool generic = false; + if (tk == TK_OP_LESS) { + Error err = _skip_generic_type_params(); + if (err) + return err; + // We don't add it to the base list if it's generic + generic = true; + tk = get_token(); + } + + if (tk == TK_COMMA) { + Error err = _parse_class_base(r_base); + if (err) + return err; + } else if (tk == TK_IDENTIFIER && String(value) == "where") { + Error err = _parse_type_constraints(); + if (err) { + return err; + } + + // An open curly bracket was parsed by _parse_type_constraints, so we can exit + } else if (tk == TK_CURLY_BRACKET_OPEN) { + // we are finished when we hit the open curly bracket + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + if (!generic) { + r_base.push_back(name); + } + + return OK; +} + +Error ScriptClassParser::_parse_type_constraints() { + Token tk = get_token(); + if (tk != TK_IDENTIFIER) { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + tk = get_token(); + if (tk != TK_COLON) { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + while (true) { + tk = get_token(); + if (tk == TK_IDENTIFIER) { + if (String(value) == "where") { + return _parse_type_constraints(); + } + + tk = get_token(); + if (tk == TK_PERIOD) { + while (true) { + tk = get_token(); + + if (tk != TK_IDENTIFIER) { + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + tk = get_token(); + + if (tk != TK_PERIOD) + break; + } + } + } + + if (tk == TK_COMMA) { + continue; + } else if (tk == TK_IDENTIFIER && String(value) == "where") { + return _parse_type_constraints(); + } else if (tk == TK_SYMBOL && String(value) == "(") { + tk = get_token(); + if (tk != TK_SYMBOL || String(value) != ")") { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + } else if (tk == TK_OP_LESS) { + Error err = _skip_generic_type_params(); + if (err) + return err; + } else if (tk == TK_CURLY_BRACKET_OPEN) { + return OK; + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + } +} + +Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) { + + Token tk = get_token(); + + if (tk == TK_IDENTIFIER) { + r_name += String(value); + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + tk = get_token(); + + if (tk == TK_PERIOD) { + r_name += "."; + return _parse_namespace_name(r_name, r_curly_stack); + } else if (tk == TK_CURLY_BRACKET_OPEN) { + r_curly_stack++; + return OK; + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } +} + +Error ScriptClassParser::parse(const String &p_code) { + + code = p_code; + idx = 0; + line = 0; + error_str = String(); + error = false; + value = Variant(); + classes.clear(); + + Token tk = get_token(); + + Map<int, NameDecl> name_stack; + int curly_stack = 0; + int type_curly_stack = 0; + + while (!error && tk != TK_EOF) { + if (tk == TK_IDENTIFIER && String(value) == "class") { + tk = get_token(); + + if (tk == TK_IDENTIFIER) { + String name = value; + int at_level = type_curly_stack; + + ClassDecl class_decl; + + for (Map<int, NameDecl>::Element *E = name_stack.front(); E; E = E->next()) { + const NameDecl &name_decl = E->value(); + + if (name_decl.type == NameDecl::NAMESPACE_DECL) { + if (E != name_stack.front()) + class_decl.namespace_ += "."; + class_decl.namespace_ += name_decl.name; + } else { + class_decl.name += name_decl.name + "."; + } + } + + class_decl.name += name; + class_decl.nested = type_curly_stack > 0; + + bool generic = false; + + while (true) { + tk = get_token(); + + if (tk == TK_COLON) { + Error err = _parse_class_base(class_decl.base); + if (err) + return err; + + curly_stack++; + type_curly_stack++; + + break; + } else if (tk == TK_CURLY_BRACKET_OPEN) { + curly_stack++; + type_curly_stack++; + break; + } else if (tk == TK_OP_LESS && !generic) { + generic = true; + + Error err = _skip_generic_type_params(); + if (err) + return err; + } else if (tk == TK_IDENTIFIER && String(value) == "where") { + Error err = _parse_type_constraints(); + if (err) { + return err; + } + + // An open curly bracket was parsed by _parse_type_constraints, so we can exit + curly_stack++; + type_curly_stack++; + break; + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + } + + NameDecl name_decl; + name_decl.name = name; + name_decl.type = NameDecl::CLASS_DECL; + name_stack[at_level] = name_decl; + + if (!generic) { // no generics, thanks + classes.push_back(class_decl); + } else if (OS::get_singleton()->is_stdout_verbose()) { + String full_name = class_decl.namespace_; + if (full_name.length()) + full_name += "."; + full_name += class_decl.name; + OS::get_singleton()->print(String("Ignoring generic class declaration: " + class_decl.name).utf8()); + } + } + } else if (tk == TK_IDENTIFIER && String(value) == "struct") { + String name; + int at_level = type_curly_stack; + while (true) { + tk = get_token(); + if (tk == TK_IDENTIFIER && name.empty()) { + name = String(value); + } else if (tk == TK_CURLY_BRACKET_OPEN) { + if (name.empty()) { + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + " after keyword `struct`, found " + get_token_name(TK_CURLY_BRACKET_OPEN); + error = true; + return ERR_PARSE_ERROR; + } + + curly_stack++; + type_curly_stack++; + break; + } else if (tk == TK_EOF) { + error_str = "Expected " + get_token_name(TK_CURLY_BRACKET_OPEN) + " after struct decl, found " + get_token_name(TK_EOF); + error = true; + return ERR_PARSE_ERROR; + } + } + + NameDecl name_decl; + name_decl.name = name; + name_decl.type = NameDecl::STRUCT_DECL; + name_stack[at_level] = name_decl; + } else if (tk == TK_IDENTIFIER && String(value) == "namespace") { + if (type_curly_stack > 0) { + error_str = "Found namespace nested inside type."; + error = true; + return ERR_PARSE_ERROR; + } + + String name; + int at_level = curly_stack; + + Error err = _parse_namespace_name(name, curly_stack); + if (err) + return err; + + NameDecl name_decl; + name_decl.name = name; + name_decl.type = NameDecl::NAMESPACE_DECL; + name_stack[at_level] = name_decl; + } else if (tk == TK_CURLY_BRACKET_OPEN) { + curly_stack++; + } else if (tk == TK_CURLY_BRACKET_CLOSE) { + curly_stack--; + if (name_stack.has(curly_stack)) { + if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL) + type_curly_stack--; + name_stack.erase(curly_stack); + } + } + + tk = get_token(); + } + + if (!error && tk == TK_EOF && curly_stack > 0) { + error_str = "Reached EOF with missing close curly brackets."; + error = true; + } + + if (error) + return ERR_PARSE_ERROR; + + return OK; +} + +Error ScriptClassParser::parse_file(const String &p_filepath) { + + String source; + + Error ferr = read_all_file_utf8(p_filepath, source); + if (ferr != OK) { + if (ferr == ERR_INVALID_DATA) { + ERR_EXPLAIN("File '" + p_filepath + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); + } + ERR_FAIL_V(ferr); + } + + return parse(source); +} + +String ScriptClassParser::get_error() { + return error_str; +} + +Vector<ScriptClassParser::ClassDecl> ScriptClassParser::get_classes() { + return classes; +} diff --git a/modules/mono/editor/script_class_parser.h b/modules/mono/editor/script_class_parser.h new file mode 100644 index 0000000000..184adebaf2 --- /dev/null +++ b/modules/mono/editor/script_class_parser.h @@ -0,0 +1,80 @@ +#ifndef SCRIPT_CLASS_PARSER_H +#define SCRIPT_CLASS_PARSER_H + +#include "core/ustring.h" +#include "core/variant.h" +#include "core/vector.h" + +class ScriptClassParser { + +public: + struct NameDecl { + enum Type { + NAMESPACE_DECL, + CLASS_DECL, + STRUCT_DECL + }; + + String name; + Type type; + }; + + struct ClassDecl { + String name; + String namespace_; + Vector<String> base; + bool nested; + bool has_script_attr; + }; + +private: + String code; + int idx; + int line; + String error_str; + bool error; + Variant value; + + Vector<ClassDecl> classes; + + enum Token { + TK_BRACKET_OPEN, + TK_BRACKET_CLOSE, + TK_CURLY_BRACKET_OPEN, + TK_CURLY_BRACKET_CLOSE, + TK_PERIOD, + TK_COLON, + TK_COMMA, + TK_SYMBOL, + TK_IDENTIFIER, + TK_STRING, + TK_NUMBER, + TK_OP_LESS, + TK_OP_GREATER, + TK_EOF, + TK_ERROR, + TK_MAX + }; + + static const char *token_names[TK_MAX]; + static String get_token_name(Token p_token); + + Token get_token(); + + Error _skip_generic_type_params(); + + Error _parse_type_full_name(String &r_full_name); + Error _parse_class_base(Vector<String> &r_base); + Error _parse_type_constraints(); + Error _parse_namespace_name(String &r_name, int &r_curly_stack); + +public: + Error parse(const String &p_code); + Error parse_file(const String &p_filepath); + + String get_error(); + + Vector<ClassDecl> get_classes(); +}; + +#endif // SCRIPT_CLASS_PARSER_H diff --git a/modules/mono/glue/cs_files/AABB.cs b/modules/mono/glue/Managed/Files/AABB.cs index 39f2d2ed51..33b2b46712 100644 --- a/modules/mono/glue/cs_files/AABB.cs +++ b/modules/mono/glue/Managed/Files/AABB.cs @@ -15,39 +15,33 @@ namespace Godot { public struct AABB : IEquatable<AABB> { - private Vector3 position; - private Vector3 size; + private Vector3 _position; + private Vector3 _size; public Vector3 Position { - get - { - return position; - } + get { return _position; } + set { _position = value; } } public Vector3 Size { - get - { - return size; - } + get { return _size; } + set { _size = value; } } public Vector3 End { - get - { - return position + size; - } + get { return _position + _size; } + set { _size = value - _position; } } public bool Encloses(AABB with) { - Vector3 src_min = position; - Vector3 src_max = position + size; - Vector3 dst_min = with.position; - Vector3 dst_max = with.position + with.size; + Vector3 src_min = _position; + Vector3 src_max = _position + _size; + Vector3 dst_min = with._position; + Vector3 dst_max = with._position + with._size; return src_min.x <= dst_min.x && src_max.x > dst_max.x && @@ -57,31 +51,31 @@ namespace Godot src_max.z > dst_max.z; } - public AABB Expand(Vector3 to_point) + public AABB Expand(Vector3 point) { - Vector3 begin = position; - Vector3 end = position + size; - - if (to_point.x < begin.x) - begin.x = to_point.x; - if (to_point.y < begin.y) - begin.y = to_point.y; - if (to_point.z < begin.z) - begin.z = to_point.z; - - if (to_point.x > end.x) - end.x = to_point.x; - if (to_point.y > end.y) - end.y = to_point.y; - if (to_point.z > end.z) - end.z = to_point.z; + Vector3 begin = _position; + Vector3 end = _position + _size; + + if (point.x < begin.x) + begin.x = point.x; + if (point.y < begin.y) + begin.y = point.y; + if (point.z < begin.z) + begin.z = point.z; + + if (point.x > end.x) + end.x = point.x; + if (point.y > end.y) + end.y = point.y; + if (point.z > end.z) + end.z = point.z; return new AABB(begin, end - begin); } public real_t GetArea() { - return size.x * size.y * size.z; + return _size.x * _size.y * _size.z; } public Vector3 GetEndpoint(int idx) @@ -89,21 +83,21 @@ namespace Godot switch (idx) { case 0: - return new Vector3(position.x, position.y, position.z); + return new Vector3(_position.x, _position.y, _position.z); case 1: - return new Vector3(position.x, position.y, position.z + size.z); + return new Vector3(_position.x, _position.y, _position.z + _size.z); case 2: - return new Vector3(position.x, position.y + size.y, position.z); + return new Vector3(_position.x, _position.y + _size.y, _position.z); case 3: - return new Vector3(position.x, position.y + size.y, position.z + size.z); + return new Vector3(_position.x, _position.y + _size.y, _position.z + _size.z); case 4: - return new Vector3(position.x + size.x, position.y, position.z); + return new Vector3(_position.x + _size.x, _position.y, _position.z); case 5: - return new Vector3(position.x + size.x, position.y, position.z + size.z); + return new Vector3(_position.x + _size.x, _position.y, _position.z + _size.z); case 6: - return new Vector3(position.x + size.x, position.y + size.y, position.z); + return new Vector3(_position.x + _size.x, _position.y + _size.y, _position.z); case 7: - return new Vector3(position.x + size.x, position.y + size.y, position.z + size.z); + return new Vector3(_position.x + _size.x, _position.y + _size.y, _position.z + _size.z); default: throw new ArgumentOutOfRangeException(nameof(idx), String.Format("Index is {0}, but a value from 0 to 7 is expected.", idx)); } @@ -112,15 +106,15 @@ namespace Godot public Vector3 GetLongestAxis() { var axis = new Vector3(1f, 0f, 0f); - real_t max_size = size.x; + real_t max_size = _size.x; - if (size.y > max_size) + if (_size.y > max_size) { axis = new Vector3(0f, 1f, 0f); - max_size = size.y; + max_size = _size.y; } - if (size.z > max_size) + if (_size.z > max_size) { axis = new Vector3(0f, 0f, 1f); } @@ -131,15 +125,15 @@ namespace Godot public Vector3.Axis GetLongestAxisIndex() { var axis = Vector3.Axis.X; - real_t max_size = size.x; + real_t max_size = _size.x; - if (size.y > max_size) + if (_size.y > max_size) { axis = Vector3.Axis.Y; - max_size = size.y; + max_size = _size.y; } - if (size.z > max_size) + if (_size.z > max_size) { axis = Vector3.Axis.Z; } @@ -149,13 +143,13 @@ namespace Godot public real_t GetLongestAxisSize() { - real_t max_size = size.x; + real_t max_size = _size.x; - if (size.y > max_size) - max_size = size.y; + if (_size.y > max_size) + max_size = _size.y; - if (size.z > max_size) - max_size = size.z; + if (_size.z > max_size) + max_size = _size.z; return max_size; } @@ -163,15 +157,15 @@ namespace Godot public Vector3 GetShortestAxis() { var axis = new Vector3(1f, 0f, 0f); - real_t max_size = size.x; + real_t max_size = _size.x; - if (size.y < max_size) + if (_size.y < max_size) { axis = new Vector3(0f, 1f, 0f); - max_size = size.y; + max_size = _size.y; } - if (size.z < max_size) + if (_size.z < max_size) { axis = new Vector3(0f, 0f, 1f); } @@ -182,15 +176,15 @@ namespace Godot public Vector3.Axis GetShortestAxisIndex() { var axis = Vector3.Axis.X; - real_t max_size = size.x; + real_t max_size = _size.x; - if (size.y < max_size) + if (_size.y < max_size) { axis = Vector3.Axis.Y; - max_size = size.y; + max_size = _size.y; } - if (size.z < max_size) + if (_size.z < max_size) { axis = Vector3.Axis.Z; } @@ -200,21 +194,21 @@ namespace Godot public real_t GetShortestAxisSize() { - real_t max_size = size.x; + real_t max_size = _size.x; - if (size.y < max_size) - max_size = size.y; + if (_size.y < max_size) + max_size = _size.y; - if (size.z < max_size) - max_size = size.z; + if (_size.z < max_size) + max_size = _size.z; return max_size; } public Vector3 GetSupport(Vector3 dir) { - Vector3 half_extents = size * 0.5f; - Vector3 ofs = position + half_extents; + Vector3 half_extents = _size * 0.5f; + Vector3 ofs = _position + half_extents; return ofs + new Vector3( dir.x > 0f ? -half_extents.x : half_extents.x, @@ -226,39 +220,39 @@ namespace Godot { var res = this; - res.position.x -= by; - res.position.y -= by; - res.position.z -= by; - res.size.x += 2.0f * by; - res.size.y += 2.0f * by; - res.size.z += 2.0f * by; + res._position.x -= by; + res._position.y -= by; + res._position.z -= by; + res._size.x += 2.0f * by; + res._size.y += 2.0f * by; + res._size.z += 2.0f * by; return res; } public bool HasNoArea() { - return size.x <= 0f || size.y <= 0f || size.z <= 0f; + return _size.x <= 0f || _size.y <= 0f || _size.z <= 0f; } public bool HasNoSurface() { - return size.x <= 0f && size.y <= 0f && size.z <= 0f; + return _size.x <= 0f && _size.y <= 0f && _size.z <= 0f; } public bool HasPoint(Vector3 point) { - if (point.x < position.x) + if (point.x < _position.x) return false; - if (point.y < position.y) + if (point.y < _position.y) return false; - if (point.z < position.z) + if (point.z < _position.z) return false; - if (point.x > position.x + size.x) + if (point.x > _position.x + _size.x) return false; - if (point.y > position.y + size.y) + if (point.y > _position.y + _size.y) return false; - if (point.z > position.z + size.z) + if (point.z > _position.z + _size.z) return false; return true; @@ -266,10 +260,10 @@ namespace Godot public AABB Intersection(AABB with) { - Vector3 src_min = position; - Vector3 src_max = position + size; - Vector3 dst_min = with.position; - Vector3 dst_max = with.position + with.size; + Vector3 src_min = _position; + Vector3 src_max = _position + _size; + Vector3 dst_min = with._position; + Vector3 dst_max = with._position + with._size; Vector3 min, max; @@ -302,17 +296,17 @@ namespace Godot public bool Intersects(AABB with) { - if (position.x >= with.position.x + with.size.x) + if (_position.x >= with._position.x + with._size.x) return false; - if (position.x + size.x <= with.position.x) + if (_position.x + _size.x <= with._position.x) return false; - if (position.y >= with.position.y + with.size.y) + if (_position.y >= with._position.y + with._size.y) return false; - if (position.y + size.y <= with.position.y) + if (_position.y + _size.y <= with._position.y) return false; - if (position.z >= with.position.z + with.size.z) + if (_position.z >= with._position.z + with._size.z) return false; - if (position.z + size.z <= with.position.z) + if (_position.z + _size.z <= with._position.z) return false; return true; @@ -322,14 +316,14 @@ namespace Godot { Vector3[] points = { - new Vector3(position.x, position.y, position.z), - new Vector3(position.x, position.y, position.z + size.z), - new Vector3(position.x, position.y + size.y, position.z), - new Vector3(position.x, position.y + size.y, position.z + size.z), - new Vector3(position.x + size.x, position.y, position.z), - new Vector3(position.x + size.x, position.y, position.z + size.z), - new Vector3(position.x + size.x, position.y + size.y, position.z), - new Vector3(position.x + size.x, position.y + size.y, position.z + size.z) + new Vector3(_position.x, _position.y, _position.z), + new Vector3(_position.x, _position.y, _position.z + _size.z), + new Vector3(_position.x, _position.y + _size.y, _position.z), + new Vector3(_position.x, _position.y + _size.y, _position.z + _size.z), + new Vector3(_position.x + _size.x, _position.y, _position.z), + new Vector3(_position.x + _size.x, _position.y, _position.z + _size.z), + new Vector3(_position.x + _size.x, _position.y + _size.y, _position.z), + new Vector3(_position.x + _size.x, _position.y + _size.y, _position.z + _size.z) }; bool over = false; @@ -353,29 +347,29 @@ namespace Godot for (int i = 0; i < 3; i++) { - real_t seg_from = from[i]; - real_t seg_to = to[i]; - real_t box_begin = position[i]; - real_t box_end = box_begin + size[i]; + real_t segFrom = from[i]; + real_t segTo = to[i]; + real_t boxBegin = _position[i]; + real_t boxEnd = boxBegin + _size[i]; real_t cmin, cmax; - if (seg_from < seg_to) + if (segFrom < segTo) { - if (seg_from > box_end || seg_to < box_begin) + if (segFrom > boxEnd || segTo < boxBegin) return false; - real_t length = seg_to - seg_from; - cmin = seg_from < box_begin ? (box_begin - seg_from) / length : 0f; - cmax = seg_to > box_end ? (box_end - seg_from) / length : 1f; + real_t length = segTo - segFrom; + cmin = segFrom < boxBegin ? (boxBegin - segFrom) / length : 0f; + cmax = segTo > boxEnd ? (boxEnd - segFrom) / length : 1f; } else { - if (seg_to > box_end || seg_from < box_begin) + if (segTo > boxEnd || segFrom < boxBegin) return false; - real_t length = seg_to - seg_from; - cmin = seg_from > box_end ? (box_end - seg_from) / length : 0f; - cmax = seg_to < box_begin ? (box_begin - seg_from) / length : 1f; + real_t length = segTo - segFrom; + cmin = segFrom > boxEnd ? (boxEnd - segFrom) / length : 0f; + cmax = segTo < boxBegin ? (boxBegin - segFrom) / length : 1f; } if (cmin > min) @@ -394,31 +388,31 @@ namespace Godot public AABB Merge(AABB with) { - Vector3 beg_1 = position; - Vector3 beg_2 = with.position; - var end_1 = new Vector3(size.x, size.y, size.z) + beg_1; - var end_2 = new Vector3(with.size.x, with.size.y, with.size.z) + beg_2; + Vector3 beg1 = _position; + Vector3 beg2 = with._position; + var end1 = new Vector3(_size.x, _size.y, _size.z) + beg1; + var end2 = new Vector3(with._size.x, with._size.y, with._size.z) + beg2; var min = new Vector3( - beg_1.x < beg_2.x ? beg_1.x : beg_2.x, - beg_1.y < beg_2.y ? beg_1.y : beg_2.y, - beg_1.z < beg_2.z ? beg_1.z : beg_2.z + beg1.x < beg2.x ? beg1.x : beg2.x, + beg1.y < beg2.y ? beg1.y : beg2.y, + beg1.z < beg2.z ? beg1.z : beg2.z ); var max = new Vector3( - end_1.x > end_2.x ? end_1.x : end_2.x, - end_1.y > end_2.y ? end_1.y : end_2.y, - end_1.z > end_2.z ? end_1.z : end_2.z + end1.x > end2.x ? end1.x : end2.x, + end1.y > end2.y ? end1.y : end2.y, + end1.z > end2.z ? end1.z : end2.z ); return new AABB(min, max - min); } - - // Constructors + + // Constructors public AABB(Vector3 position, Vector3 size) { - this.position = position; - this.size = size; + _position = position; + _size = size; } public static bool operator ==(AABB left, AABB right) @@ -443,20 +437,20 @@ namespace Godot public bool Equals(AABB other) { - return position == other.position && size == other.size; + return _position == other._position && _size == other._size; } public override int GetHashCode() { - return position.GetHashCode() ^ size.GetHashCode(); + return _position.GetHashCode() ^ _size.GetHashCode(); } public override string ToString() { return String.Format("{0} - {1}", new object[] { - position.ToString(), - size.ToString() + _position.ToString(), + _size.ToString() }); } @@ -464,8 +458,8 @@ namespace Godot { return String.Format("{0} - {1}", new object[] { - position.ToString(format), - size.ToString(format) + _position.ToString(format), + _size.ToString(format) }); } } diff --git a/modules/mono/glue/Managed/Files/Array.cs b/modules/mono/glue/Managed/Files/Array.cs new file mode 100644 index 0000000000..d5a35d7ae0 --- /dev/null +++ b/modules/mono/glue/Managed/Files/Array.cs @@ -0,0 +1,349 @@ +using System; +using System.Collections.Generic; +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Godot.Collections +{ + class ArraySafeHandle : SafeHandle + { + public ArraySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) + { + this.handle = handle; + } + + public override bool IsInvalid + { + get + { + return handle == IntPtr.Zero; + } + } + + protected override bool ReleaseHandle() + { + Array.godot_icall_Array_Dtor(handle); + return true; + } + } + + public class Array : IList<object>, ICollection<object>, IEnumerable<object>, IDisposable + { + ArraySafeHandle safeHandle; + bool disposed = false; + + public Array() + { + safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor()); + } + + internal Array(ArraySafeHandle handle) + { + safeHandle = handle; + } + + internal Array(IntPtr handle) + { + safeHandle = new ArraySafeHandle(handle); + } + + internal IntPtr GetPtr() + { + return safeHandle.DangerousGetHandle(); + } + + public void Dispose() + { + if (disposed) + return; + + if (safeHandle != null) + { + safeHandle.Dispose(); + safeHandle = null; + } + + disposed = true; + } + + public object this[int index] + { + get + { + return godot_icall_Array_At(GetPtr(), index); + } + set + { + godot_icall_Array_SetAt(GetPtr(), index, value); + } + } + + public int Count + { + get + { + return godot_icall_Array_Count(GetPtr()); + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public void Add(object item) + { + godot_icall_Array_Add(GetPtr(), item); + } + + public void Clear() + { + godot_icall_Array_Clear(GetPtr()); + } + + public bool Contains(object item) + { + return godot_icall_Array_Contains(GetPtr(), item); + } + + public void CopyTo(object[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array), "Value cannot be null."); + + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension."); + + // Internal call may throw ArgumentException + godot_icall_Array_CopyTo(GetPtr(), array, arrayIndex); + } + + public IEnumerator<object> GetEnumerator() + { + int count = Count; + + for (int i = 0; i < count; i++) + { + yield return this[i]; + } + } + + public int IndexOf(object item) + { + return godot_icall_Array_IndexOf(GetPtr(), item); + } + + public void Insert(int index, object item) + { + godot_icall_Array_Insert(GetPtr(), index, item); + } + + public bool Remove(object item) + { + return godot_icall_Array_Remove(GetPtr(), item); + } + + public void RemoveAt(int index) + { + godot_icall_Array_RemoveAt(GetPtr(), index); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Array_Ctor(); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_Dtor(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static object godot_icall_Array_At(IntPtr ptr, int index); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static object godot_icall_Array_At_Generic(IntPtr ptr, int index, int elemTypeEncoding, IntPtr elemTypeClass); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_SetAt(IntPtr ptr, int index, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_Array_Count(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_Add(IntPtr ptr, object item); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_Clear(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Array_Contains(IntPtr ptr, object item); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_CopyTo(IntPtr ptr, object[] array, int arrayIndex); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_Array_IndexOf(IntPtr ptr, object item); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_Insert(IntPtr ptr, int index, object item); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Array_Remove(IntPtr ptr, object item); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_RemoveAt(IntPtr ptr, int index); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_Generic_GetElementTypeInfo(Type elemType, out int elemTypeEncoding, out IntPtr elemTypeClass); + } + + public class Array<T> : IList<T>, ICollection<T>, IEnumerable<T> + { + Array objectArray; + + internal static int elemTypeEncoding; + internal static IntPtr elemTypeClass; + + static Array() + { + Array.godot_icall_Array_Generic_GetElementTypeInfo(typeof(T), out elemTypeEncoding, out elemTypeClass); + } + + public Array() + { + objectArray = new Array(); + } + + public Array(Array array) + { + objectArray = array; + } + + internal Array(IntPtr handle) + { + objectArray = new Array(handle); + } + + internal Array(ArraySafeHandle handle) + { + objectArray = new Array(handle); + } + + public static explicit operator Array(Array<T> from) + { + return from.objectArray; + } + + public T this[int index] + { + get + { + return (T)Array.godot_icall_Array_At_Generic(GetPtr(), index, elemTypeEncoding, elemTypeClass); + } + set + { + objectArray[index] = value; + } + } + + public int Count + { + get + { + return objectArray.Count; + } + } + + public bool IsReadOnly + { + get + { + return objectArray.IsReadOnly; + } + } + + public void Add(T item) + { + objectArray.Add(item); + } + + public void Clear() + { + objectArray.Clear(); + } + + public bool Contains(T item) + { + return objectArray.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array), "Value cannot be null."); + + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension."); + + // TODO This may be quite slow because every element access is an internal call. + // It could be moved entirely to an internal call if we find out how to do the cast there. + + int count = objectArray.Count; + + if (array.Length < (arrayIndex + count)) + throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + + for (int i = 0; i < count; i++) + { + array[arrayIndex] = (T)this[i]; + arrayIndex++; + } + } + + public IEnumerator<T> GetEnumerator() + { + int count = objectArray.Count; + + for (int i = 0; i < count; i++) + { + yield return (T)this[i]; + } + } + + public int IndexOf(T item) + { + return objectArray.IndexOf(item); + } + + public void Insert(int index, T item) + { + objectArray.Insert(index, item); + } + + public bool Remove(T item) + { + return objectArray.Remove(item); + } + + public void RemoveAt(int index) + { + objectArray.RemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + internal IntPtr GetPtr() + { + return objectArray.GetPtr(); + } + } +} diff --git a/modules/mono/glue/Managed/Files/Attributes/ExportAttribute.cs b/modules/mono/glue/Managed/Files/Attributes/ExportAttribute.cs new file mode 100644 index 0000000000..6adf044886 --- /dev/null +++ b/modules/mono/glue/Managed/Files/Attributes/ExportAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class ExportAttribute : Attribute + { + private PropertyHint hint; + private string hintString; + + public ExportAttribute(PropertyHint hint = PropertyHint.None, string hintString = "") + { + this.hint = hint; + this.hintString = hintString; + } + } +} diff --git a/modules/mono/glue/cs_files/GodotMethodAttribute.cs b/modules/mono/glue/Managed/Files/Attributes/GodotMethodAttribute.cs index 55848769d5..55848769d5 100644 --- a/modules/mono/glue/cs_files/GodotMethodAttribute.cs +++ b/modules/mono/glue/Managed/Files/Attributes/GodotMethodAttribute.cs diff --git a/modules/mono/glue/cs_files/RPCAttributes.cs b/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs index 6bf9560bfa..2398e10135 100644 --- a/modules/mono/glue/cs_files/RPCAttributes.cs +++ b/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs @@ -12,6 +12,9 @@ namespace Godot public class MasterAttribute : Attribute {} [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + public class PuppetAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] public class SlaveAttribute : Attribute {} [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] @@ -21,5 +24,5 @@ namespace Godot public class MasterSyncAttribute : Attribute {} [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] - public class SlaveSyncAttribute : Attribute {} + public class PuppetSyncAttribute : Attribute {} } diff --git a/modules/mono/glue/cs_files/SignalAttribute.cs b/modules/mono/glue/Managed/Files/Attributes/SignalAttribute.cs index 3957387be9..3957387be9 100644 --- a/modules/mono/glue/cs_files/SignalAttribute.cs +++ b/modules/mono/glue/Managed/Files/Attributes/SignalAttribute.cs diff --git a/modules/mono/glue/Managed/Files/Attributes/ToolAttribute.cs b/modules/mono/glue/Managed/Files/Attributes/ToolAttribute.cs new file mode 100644 index 0000000000..d0437409af --- /dev/null +++ b/modules/mono/glue/Managed/Files/Attributes/ToolAttribute.cs @@ -0,0 +1,7 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Class)] + public class ToolAttribute : Attribute {} +} diff --git a/modules/mono/glue/cs_files/Basis.cs b/modules/mono/glue/Managed/Files/Basis.cs index aa49a5e04f..b318d96bb9 100644 --- a/modules/mono/glue/cs_files/Basis.cs +++ b/modules/mono/glue/Managed/Files/Basis.cs @@ -13,9 +13,9 @@ namespace Godot { private static readonly Basis identity = new Basis ( - new Vector3(1f, 0f, 0f), - new Vector3(0f, 1f, 0f), - new Vector3(0f, 0f, 1f) + 1f, 0f, 0f, + 0f, 1f, 0f, + 0f, 0f, 1f ); private static readonly Basis[] orthoBases = { @@ -159,12 +159,44 @@ namespace Godot { return new Basis ( - new Vector3(xAxis.x, yAxis.x, zAxis.x), - new Vector3(xAxis.y, yAxis.y, zAxis.y), - new Vector3(xAxis.z, yAxis.z, zAxis.z) + xAxis.x, yAxis.x, zAxis.x, + xAxis.y, yAxis.y, zAxis.y, + xAxis.z, yAxis.z, zAxis.z ); } + internal Quat RotationQuat() + { + Basis orthonormalizedBasis = Orthonormalized(); + real_t det = orthonormalizedBasis.Determinant(); + if (det < 0) + { + // Ensure that the determinant is 1, such that result is a proper rotation matrix which can be represented by Euler angles. + orthonormalizedBasis = orthonormalizedBasis.Scaled(Vector3.NegOne); + } + + return orthonormalizedBasis.Quat(); + } + + internal void SetQuantScale(Quat quat, Vector3 scale) + { + SetDiagonal(scale); + Rotate(quat); + } + + private void Rotate(Quat quat) + { + this *= new Basis(quat); + } + + private void SetDiagonal(Vector3 diagonal) + { + _x = new Vector3(diagonal.x, 0, 0); + _y = new Vector3(0, diagonal.y, 0); + _z = new Vector3(0, 0, diagonal.z); + + } + public real_t Determinant() { return this[0, 0] * (this[1, 1] * this[2, 2] - this[2, 1] * this[1, 2]) - @@ -343,17 +375,17 @@ namespace Godot { var tr = this; - real_t temp = this[0, 1]; - this[0, 1] = this[1, 0]; - this[1, 0] = temp; + real_t temp = tr[0, 1]; + tr[0, 1] = tr[1, 0]; + tr[1, 0] = temp; - temp = this[0, 2]; - this[0, 2] = this[2, 0]; - this[2, 0] = temp; + temp = tr[0, 2]; + tr[0, 2] = tr[2, 0]; + tr[2, 0] = temp; - temp = this[1, 2]; - this[1, 2] = this[2, 1]; - this[2, 1] = temp; + temp = tr[1, 2]; + tr[1, 2] = tr[2, 1]; + tr[2, 1] = temp; return tr; } @@ -378,55 +410,61 @@ namespace Godot ); } - public Quat Quat() { - real_t trace = _x[0] + _y[1] + _z[2]; - - if (trace > 0.0f) { - real_t s = Mathf.Sqrt(trace + 1.0f) * 2f; - real_t inv_s = 1f / s; - return new Quat( - (_z[1] - _y[2]) * inv_s, - (_x[2] - _z[0]) * inv_s, - (_y[0] - _x[1]) * inv_s, - s * 0.25f - ); - } - - if (_x[0] > _y[1] && _x[0] > _z[2]) { - real_t s = Mathf.Sqrt(_x[0] - _y[1] - _z[2] + 1.0f) * 2f; - real_t inv_s = 1f / s; - return new Quat( - s * 0.25f, - (_x[1] + _y[0]) * inv_s, - (_x[2] + _z[0]) * inv_s, - (_z[1] - _y[2]) * inv_s - ); - } - - if (_y[1] > _z[2]) { - real_t s = Mathf.Sqrt(-_x[0] + _y[1] - _z[2] + 1.0f) * 2f; - real_t inv_s = 1f / s; - return new Quat( - (_x[1] + _y[0]) * inv_s, - s * 0.25f, - (_y[2] + _z[1]) * inv_s, - (_x[2] - _z[0]) * inv_s - ); - } else { - real_t s = Mathf.Sqrt(-_x[0] - _y[1] + _z[2] + 1.0f) * 2f; - real_t inv_s = 1f / s; - return new Quat( - (_x[2] + _z[0]) * inv_s, - (_y[2] + _z[1]) * inv_s, - s * 0.25f, - (_y[0] - _x[1]) * inv_s - ); - } - } + public Quat Quat() + { + real_t trace = _x[0] + _y[1] + _z[2]; + + if (trace > 0.0f) + { + real_t s = Mathf.Sqrt(trace + 1.0f) * 2f; + real_t inv_s = 1f / s; + return new Quat( + (_z[1] - _y[2]) * inv_s, + (_x[2] - _z[0]) * inv_s, + (_y[0] - _x[1]) * inv_s, + s * 0.25f + ); + } + + if (_x[0] > _y[1] && _x[0] > _z[2]) + { + real_t s = Mathf.Sqrt(_x[0] - _y[1] - _z[2] + 1.0f) * 2f; + real_t inv_s = 1f / s; + return new Quat( + s * 0.25f, + (_x[1] + _y[0]) * inv_s, + (_x[2] + _z[0]) * inv_s, + (_z[1] - _y[2]) * inv_s + ); + } + + if (_y[1] > _z[2]) + { + real_t s = Mathf.Sqrt(-_x[0] + _y[1] - _z[2] + 1.0f) * 2f; + real_t inv_s = 1f / s; + return new Quat( + (_x[1] + _y[0]) * inv_s, + s * 0.25f, + (_y[2] + _z[1]) * inv_s, + (_x[2] - _z[0]) * inv_s + ); + } + else + { + real_t s = Mathf.Sqrt(-_x[0] - _y[1] + _z[2] + 1.0f) * 2f; + real_t inv_s = 1f / s; + return new Quat( + (_x[2] + _z[0]) * inv_s, + (_y[2] + _z[1]) * inv_s, + s * 0.25f, + (_y[0] - _x[1]) * inv_s + ); + } + } public Basis(Quat quat) { - real_t s = 2.0f / quat.LengthSquared(); + real_t s = 2.0f / quat.LengthSquared; real_t xs = quat.x * s; real_t ys = quat.y * s; @@ -470,8 +508,8 @@ namespace Godot { var axis_sq = new Vector3(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z); - real_t cosine = Mathf.Cos( phi); - real_t sine = Mathf.Sin( phi); + real_t cosine = Mathf.Cos(phi); + real_t sine = Mathf.Sin(phi); _x = new Vector3 ( @@ -497,12 +535,17 @@ namespace Godot public Basis(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis) { - _x = xAxis; - _y = yAxis; - _z = zAxis; + _x = new Vector3(xAxis.x, yAxis.x, zAxis.x); + _y = new Vector3(xAxis.y, yAxis.y, zAxis.y); + _z = new Vector3(xAxis.z, yAxis.z, zAxis.z); + // Same as: + // SetAxis(0, xAxis); + // SetAxis(1, yAxis); + // SetAxis(2, zAxis); + // We need to assign the struct fields so we can't do that... } - public Basis(real_t xx, real_t xy, real_t xz, real_t yx, real_t yy, real_t yz, real_t zx, real_t zy, real_t zz) + internal Basis(real_t xx, real_t xy, real_t xz, real_t yx, real_t yy, real_t yz, real_t zx, real_t zy, real_t zz) { _x = new Vector3(xx, xy, xz); _y = new Vector3(yx, yy, yz); diff --git a/modules/mono/glue/cs_files/Color.cs b/modules/mono/glue/Managed/Files/Color.cs index e0d6d27840..fc5bb010a9 100644 --- a/modules/mono/glue/cs_files/Color.cs +++ b/modules/mono/glue/Managed/Files/Color.cs @@ -258,11 +258,6 @@ namespace Godot return res; } - public float Gray() - { - return (r + g + b) / 3.0f; - } - public Color Inverted() { return new Color( @@ -293,28 +288,80 @@ namespace Godot return res; } - public int ToRgba32() + public int ToAbgr32() { - int c = (byte)(r * 255); + int c = (byte)Math.Round(a * 255); c <<= 8; - c |= (byte)(g * 255); + c |= (byte)Math.Round(b * 255); c <<= 8; - c |= (byte)(b * 255); + c |= (byte)Math.Round(g * 255); c <<= 8; - c |= (byte)(a * 255); + c |= (byte)Math.Round(r * 255); + + return c; + } + + public long ToAbgr64() + { + long c = (ushort)Math.Round(a * 65535); + c <<= 16; + c |= (ushort)Math.Round(b * 65535); + c <<= 16; + c |= (ushort)Math.Round(g * 65535); + c <<= 16; + c |= (ushort)Math.Round(r * 65535); return c; } public int ToArgb32() { - int c = (byte)(a * 255); + int c = (byte)Math.Round(a * 255); + c <<= 8; + c |= (byte)Math.Round(r * 255); + c <<= 8; + c |= (byte)Math.Round(g * 255); + c <<= 8; + c |= (byte)Math.Round(b * 255); + + return c; + } + + public long ToArgb64() + { + long c = (ushort)Math.Round(a * 65535); + c <<= 16; + c |= (ushort)Math.Round(r * 65535); + c <<= 16; + c |= (ushort)Math.Round(g * 65535); + c <<= 16; + c |= (ushort)Math.Round(b * 65535); + + return c; + } + + public int ToRgba32() + { + int c = (byte)Math.Round(r * 255); c <<= 8; - c |= (byte)(r * 255); + c |= (byte)Math.Round(g * 255); c <<= 8; - c |= (byte)(g * 255); + c |= (byte)Math.Round(b * 255); c <<= 8; - c |= (byte)(b * 255); + c |= (byte)Math.Round(a * 255); + + return c; + } + + public long ToRgba64() + { + long c = (ushort)Math.Round(r * 65535); + c <<= 16; + c |= (ushort)Math.Round(g * 65535); + c <<= 16; + c |= (ushort)Math.Round(b * 65535); + c <<= 16; + c |= (ushort)Math.Round(a * 65535); return c; } @@ -323,17 +370,17 @@ namespace Godot { var txt = string.Empty; - txt += _to_hex(r); - txt += _to_hex(g); - txt += _to_hex(b); + txt += ToHex32(r); + txt += ToHex32(g); + txt += ToHex32(b); if (include_alpha) - txt = _to_hex(a) + txt; + txt = ToHex32(a) + txt; return txt; } - - // Constructors + + // Constructors public Color(float r, float g, float b, float a = 1.0f) { this.r = r; @@ -353,7 +400,18 @@ namespace Godot r = (rgba & 0xFF) / 255.0f; } - private static int _parse_col(string str, int ofs) + public Color(long rgba) + { + a = (rgba & 0xFFFF) / 65535.0f; + rgba >>= 16; + b = (rgba & 0xFFFF) / 65535.0f; + rgba >>= 16; + g = (rgba & 0xFFFF) / 65535.0f; + rgba >>= 16; + r = (rgba & 0xFFFF) / 65535.0f; + } + + private static int ParseCol8(string str, int ofs) { int ig = 0; @@ -390,9 +448,9 @@ namespace Godot return ig; } - private String _to_hex(float val) + private String ToHex32(float val) { - var v = (int) Mathf.Clamp(val * 255.0f, 0, 255); + int v = Mathf.RoundToInt(Mathf.Clamp(val * 255, 0, 255)); var ret = string.Empty; @@ -432,17 +490,17 @@ namespace Godot if (alpha) { - if (_parse_col(color, 0) < 0) + if (ParseCol8(color, 0) < 0) return false; } int from = alpha ? 2 : 0; - if (_parse_col(color, from + 0) < 0) + if (ParseCol8(color, from + 0) < 0) return false; - if (_parse_col(color, from + 2) < 0) + if (ParseCol8(color, from + 2) < 0) return false; - if (_parse_col(color, from + 4) < 0) + if (ParseCol8(color, from + 4) < 0) return false; return true; @@ -484,7 +542,7 @@ namespace Godot if (alpha) { - a = _parse_col(rgba, 0) / 255f; + a = ParseCol8(rgba, 0) / 255f; if (a < 0) throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba); @@ -496,17 +554,17 @@ namespace Godot int from = alpha ? 2 : 0; - r = _parse_col(rgba, from + 0) / 255f; + r = ParseCol8(rgba, from + 0) / 255f; if (r < 0) throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba); - g = _parse_col(rgba, from + 2) / 255f; + g = ParseCol8(rgba, from + 2) / 255f; if (g < 0) throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba); - b = _parse_col(rgba, from + 4) / 255f; + b = ParseCol8(rgba, from + 4) / 255f; if (b < 0) throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba); diff --git a/modules/mono/glue/cs_files/DebuggingUtils.cs b/modules/mono/glue/Managed/Files/DebuggingUtils.cs index b27816084e..b27816084e 100644 --- a/modules/mono/glue/cs_files/DebuggingUtils.cs +++ b/modules/mono/glue/Managed/Files/DebuggingUtils.cs diff --git a/modules/mono/glue/Managed/Files/Dictionary.cs b/modules/mono/glue/Managed/Files/Dictionary.cs new file mode 100644 index 0000000000..7695f03cd6 --- /dev/null +++ b/modules/mono/glue/Managed/Files/Dictionary.cs @@ -0,0 +1,417 @@ +using System; +using System.Collections.Generic; +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Godot.Collections +{ + class DictionarySafeHandle : SafeHandle + { + public DictionarySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) + { + this.handle = handle; + } + + public override bool IsInvalid + { + get + { + return handle == IntPtr.Zero; + } + } + + protected override bool ReleaseHandle() + { + Dictionary.godot_icall_Dictionary_Dtor(handle); + return true; + } + } + + public class Dictionary : + IDictionary<object, object>, + ICollection<KeyValuePair<object, object>>, + IEnumerable<KeyValuePair<object, object>>, + IDisposable + { + DictionarySafeHandle safeHandle; + bool disposed = false; + + public Dictionary() + { + safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor()); + } + + internal Dictionary(DictionarySafeHandle handle) + { + safeHandle = handle; + } + + internal Dictionary(IntPtr handle) + { + safeHandle = new DictionarySafeHandle(handle); + } + + internal IntPtr GetPtr() + { + return safeHandle.DangerousGetHandle(); + } + + public void Dispose() + { + if (disposed) + return; + + if (safeHandle != null) + { + safeHandle.Dispose(); + safeHandle = null; + } + + disposed = true; + } + + public object this[object key] + { + get + { + return godot_icall_Dictionary_GetValue(GetPtr(), key); + } + set + { + godot_icall_Dictionary_SetValue(GetPtr(), key, value); + } + } + + public ICollection<object> Keys + { + get + { + IntPtr handle = godot_icall_Dictionary_Keys(GetPtr()); + return new Array(new ArraySafeHandle(handle)); + } + } + + public ICollection<object> Values + { + get + { + IntPtr handle = godot_icall_Dictionary_Values(GetPtr()); + return new Array(new ArraySafeHandle(handle)); + } + } + + public int Count + { + get + { + return godot_icall_Dictionary_Count(GetPtr()); + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public void Add(object key, object value) + { + godot_icall_Dictionary_Add(GetPtr(), key, value); + } + + public void Add(KeyValuePair<object, object> item) + { + Add(item.Key, item.Value); + } + + public void Clear() + { + godot_icall_Dictionary_Clear(GetPtr()); + } + + public bool Contains(KeyValuePair<object, object> item) + { + return godot_icall_Dictionary_Contains(GetPtr(), item.Key, item.Value); + } + + public bool ContainsKey(object key) + { + return godot_icall_Dictionary_ContainsKey(GetPtr(), key); + } + + public void CopyTo(KeyValuePair<object, object>[] array, int arrayIndex) + { + // TODO 3 internal calls, can reduce to 1 + Array keys = (Array)Keys; + Array values = (Array)Values; + int count = Count; + + for (int i = 0; i < count; i++) + { + // TODO 2 internal calls, can reduce to 1 + array[arrayIndex] = new KeyValuePair<object, object>(keys[i], values[i]); + arrayIndex++; + } + } + + public IEnumerator<KeyValuePair<object, object>> GetEnumerator() + { + // TODO 3 internal calls, can reduce to 1 + Array keys = (Array)Keys; + Array values = (Array)Values; + int count = Count; + + for (int i = 0; i < count; i++) + { + // TODO 2 internal calls, can reduce to 1 + yield return new KeyValuePair<object, object>(keys[i], values[i]); + } + } + + public bool Remove(object key) + { + return godot_icall_Dictionary_RemoveKey(GetPtr(), key); + } + + public bool Remove(KeyValuePair<object, object> item) + { + return godot_icall_Dictionary_Remove(GetPtr(), item.Key, item.Value); + } + + public bool TryGetValue(object key, out object value) + { + object retValue; + bool found = godot_icall_Dictionary_TryGetValue(GetPtr(), key, out retValue); + value = found ? retValue : default(object); + return found; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Dictionary_Ctor(); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Dictionary_Dtor(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static object godot_icall_Dictionary_GetValue(IntPtr ptr, object key); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static object godot_icall_Dictionary_GetValue_Generic(IntPtr ptr, object key, int valTypeEncoding, IntPtr valTypeClass); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Dictionary_SetValue(IntPtr ptr, object key, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Dictionary_Keys(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Dictionary_Values(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_Dictionary_Count(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Dictionary_Add(IntPtr ptr, object key, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Dictionary_Clear(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_Contains(IntPtr ptr, object key, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_Remove(IntPtr ptr, object key, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_TryGetValue(IntPtr ptr, object key, out object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_TryGetValue_Generic(IntPtr ptr, object key, out object value, int valTypeEncoding, IntPtr valTypeClass); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Dictionary_Generic_GetValueTypeInfo(Type valueType, out int valTypeEncoding, out IntPtr valTypeClass); + } + + public class Dictionary<TKey, TValue> : + IDictionary<TKey, TValue>, + ICollection<KeyValuePair<TKey, TValue>>, + IEnumerable<KeyValuePair<TKey, TValue>> + { + Dictionary objectDict; + + internal static int valTypeEncoding; + internal static IntPtr valTypeClass; + + static Dictionary() + { + Dictionary.godot_icall_Dictionary_Generic_GetValueTypeInfo(typeof(TValue), out valTypeEncoding, out valTypeClass); + } + + public Dictionary() + { + objectDict = new Dictionary(); + } + + public Dictionary(Dictionary dictionary) + { + objectDict = dictionary; + } + + internal Dictionary(IntPtr handle) + { + objectDict = new Dictionary(handle); + } + + internal Dictionary(DictionarySafeHandle handle) + { + objectDict = new Dictionary(handle); + } + + public static explicit operator Dictionary(Dictionary<TKey, TValue> from) + { + return from.objectDict; + } + + public TValue this[TKey key] + { + get + { + return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(objectDict.GetPtr(), key, valTypeEncoding, valTypeClass); + } + set + { + objectDict[key] = value; + } + } + + public ICollection<TKey> Keys + { + get + { + IntPtr handle = Dictionary.godot_icall_Dictionary_Keys(objectDict.GetPtr()); + return new Array<TKey>(new ArraySafeHandle(handle)); + } + } + + public ICollection<TValue> Values + { + get + { + IntPtr handle = Dictionary.godot_icall_Dictionary_Values(objectDict.GetPtr()); + return new Array<TValue>(new ArraySafeHandle(handle)); + } + } + + public int Count + { + get + { + return objectDict.Count; + } + } + + public bool IsReadOnly + { + get + { + return objectDict.IsReadOnly; + } + } + + public void Add(TKey key, TValue value) + { + objectDict.Add(key, value); + } + + public void Add(KeyValuePair<TKey, TValue> item) + { + objectDict.Add(item.Key, item.Value); + } + + public void Clear() + { + objectDict.Clear(); + } + + public bool Contains(KeyValuePair<TKey, TValue> item) + { + return objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value)); + } + + public bool ContainsKey(TKey key) + { + return objectDict.ContainsKey(key); + } + + public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) + { + // TODO 3 internal calls, can reduce to 1 + Array<TKey> keys = (Array<TKey>)Keys; + Array<TValue> values = (Array<TValue>)Values; + int count = Count; + + for (int i = 0; i < count; i++) + { + // TODO 2 internal calls, can reduce to 1 + array[arrayIndex] = new KeyValuePair<TKey, TValue>(keys[i], values[i]); + arrayIndex++; + } + } + + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + // TODO 3 internal calls, can reduce to 1 + Array<TKey> keys = (Array<TKey>)Keys; + Array<TValue> values = (Array<TValue>)Values; + int count = Count; + + for (int i = 0; i < count; i++) + { + // TODO 2 internal calls, can reduce to 1 + yield return new KeyValuePair<TKey, TValue>(keys[i], values[i]); + } + } + + public bool Remove(TKey key) + { + return objectDict.Remove(key); + } + + public bool Remove(KeyValuePair<TKey, TValue> item) + { + return objectDict.Remove(new KeyValuePair<object, object>(item.Key, item.Value)); + } + + public bool TryGetValue(TKey key, out TValue value) + { + object retValue; + bool found = Dictionary.godot_icall_Dictionary_TryGetValue_Generic(GetPtr(), key, out retValue, valTypeEncoding, valTypeClass); + value = found ? (TValue)retValue : default(TValue); + return found; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + internal IntPtr GetPtr() + { + return objectDict.GetPtr(); + } + } +} diff --git a/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs b/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs new file mode 100644 index 0000000000..366d89b1c2 --- /dev/null +++ b/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs @@ -0,0 +1,45 @@ +namespace Godot +{ + public partial class Node + { + public T GetNode<T>(NodePath path) where T : class + { + return (T)(object)GetNode(path); + } + + public T GetNodeOrNull<T>(NodePath path) where T : class + { + return GetNode(path) as T; + } + + public T GetChild<T>(int idx) where T : class + { + return (T)(object)GetChild(idx); + } + + public T GetChildOrNull<T>(int idx) where T : class + { + return GetChild(idx) as T; + } + + public T GetOwner<T>() where T : class + { + return (T)(object)GetOwner(); + } + + public T GetOwnerOrNull<T>() where T : class + { + return GetOwner() as T; + } + + public T GetParent<T>() where T : class + { + return (T)(object)GetParent(); + } + + public T GetParentOrNull<T>() where T : class + { + return GetParent() as T; + } + } +} diff --git a/modules/mono/glue/Managed/Files/Extensions/ObjectExtensions.cs b/modules/mono/glue/Managed/Files/Extensions/ObjectExtensions.cs new file mode 100644 index 0000000000..9ef0959750 --- /dev/null +++ b/modules/mono/glue/Managed/Files/Extensions/ObjectExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Godot +{ + public partial class Object + { + public static bool IsInstanceValid(Object instance) + { + return instance != null && instance.NativeInstance != IntPtr.Zero; + } + + public static WeakRef WeakRef(Object obj) + { + return godot_icall_Object_weakref(Object.GetPtr(obj)); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static WeakRef godot_icall_Object_weakref(IntPtr obj); + } +} diff --git a/modules/mono/glue/Managed/Files/Extensions/ResourceLoaderExtensions.cs b/modules/mono/glue/Managed/Files/Extensions/ResourceLoaderExtensions.cs new file mode 100644 index 0000000000..684d160b57 --- /dev/null +++ b/modules/mono/glue/Managed/Files/Extensions/ResourceLoaderExtensions.cs @@ -0,0 +1,10 @@ +namespace Godot +{ + public static partial class ResourceLoader + { + public static T Load<T>(string path) where T : class + { + return (T)(object)Load(path); + } + } +} diff --git a/modules/mono/glue/Managed/Files/GD.cs b/modules/mono/glue/Managed/Files/GD.cs new file mode 100644 index 0000000000..75a35a9eea --- /dev/null +++ b/modules/mono/glue/Managed/Files/GD.cs @@ -0,0 +1,258 @@ +using System; +using System.Runtime.CompilerServices; +#if REAL_T_IS_DOUBLE +using real_t = System.Double; +#else +using real_t = System.Single; +#endif + +// TODO: Add comments describing what this class does. It is not obvious. + +namespace Godot +{ + public static partial class GD + { + public static object Bytes2Var(byte[] bytes) + { + return godot_icall_GD_bytes2var(bytes); + } + + public static object Convert(object what, int type) + { + return godot_icall_GD_convert(what, type); + } + + public static real_t Db2Linear(real_t db) + { + return (real_t)Math.Exp(db * 0.11512925464970228420089957273422); + } + + public static real_t DecTime(real_t value, real_t amount, real_t step) + { + real_t sgn = Mathf.Sign(value); + real_t val = Mathf.Abs(value); + val -= amount * step; + if (val < 0) + val = 0; + return val * sgn; + } + + public static FuncRef FuncRef(Object instance, string funcname) + { + var ret = new FuncRef(); + ret.SetInstance(instance); + ret.SetFunction(funcname); + return ret; + } + + public static int Hash(object var) + { + return godot_icall_GD_hash(var); + } + + public static Object InstanceFromId(int instanceId) + { + return godot_icall_GD_instance_from_id(instanceId); + } + + public static real_t Linear2Db(real_t linear) + { + return (real_t)(Math.Log(linear) * 8.6858896380650365530225783783321); + } + + public static Resource Load(string path) + { + return ResourceLoader.Load(path); + } + + public static T Load<T>(string path) where T : class + { + return ResourceLoader.Load<T>(path); + } + + public static void PushError(string message) + { + godot_icall_GD_pusherror(message); + } + + public static void PushWarning(string message) + { + godot_icall_GD_pushwarning(message); + } + + public static void Print(params object[] what) + { + godot_icall_GD_print(what); + } + + public static void PrintStack() + { + Print(System.Environment.StackTrace); + } + + public static void PrintErr(params object[] what) + { + godot_icall_GD_printerr(what); + } + + public static void PrintRaw(params object[] what) + { + godot_icall_GD_printraw(what); + } + + public static void PrintS(params object[] what) + { + godot_icall_GD_prints(what); + } + + public static void PrintT(params object[] what) + { + godot_icall_GD_printt(what); + } + + public static int[] Range(int length) + { + var ret = new int[length]; + + for (int i = 0; i < length; i++) + { + ret[i] = i; + } + + return ret; + } + + public static int[] Range(int from, int to) + { + if (to < from) + return new int[0]; + + var ret = new int[to - from]; + + for (int i = from; i < to; i++) + { + ret[i - from] = i; + } + + return ret; + } + + public static int[] Range(int from, int to, int increment) + { + if (to < from && increment > 0) + return new int[0]; + if (to > from && increment < 0) + return new int[0]; + + // Calculate count + int count; + + if (increment > 0) + count = (to - from - 1) / increment + 1; + else + count = (from - to - 1) / -increment + 1; + + var ret = new int[count]; + + if (increment > 0) + { + int idx = 0; + for (int i = from; i < to; i += increment) + { + ret[idx++] = i; + } + } + else + { + int idx = 0; + for (int i = from; i > to; i += increment) + { + ret[idx++] = i; + } + } + + return ret; + } + + public static void Seed(int seed) + { + godot_icall_GD_seed(seed); + } + + public static string Str(params object[] what) + { + return godot_icall_GD_str(what); + } + + public static object Str2Var(string str) + { + return godot_icall_GD_str2var(str); + } + + public static bool TypeExists(string type) + { + return godot_icall_GD_type_exists(type); + } + + public static byte[] Var2Bytes(object var) + { + return godot_icall_GD_var2bytes(var); + } + + public static string Var2Str(object var) + { + return godot_icall_GD_var2str(var); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static object godot_icall_GD_bytes2var(byte[] bytes); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static object godot_icall_GD_convert(object what, int type); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_GD_hash(object var); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static Object godot_icall_GD_instance_from_id(int instance_id); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_GD_print(object[] what); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_GD_printerr(object[] what); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_GD_printraw(object[] what); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_GD_prints(object[] what); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_GD_printt(object[] what); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_GD_seed(int seed); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_GD_str(object[] what); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static object godot_icall_GD_str2var(string str); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_GD_type_exists(string type); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static byte[] godot_icall_GD_var2bytes(object what); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_GD_var2str(object var); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_GD_pusherror(string type); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_GD_pushwarning(string type); + } +} diff --git a/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs b/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs new file mode 100644 index 0000000000..e727781d63 --- /dev/null +++ b/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs @@ -0,0 +1,25 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Godot +{ + public class GodotSynchronizationContext : SynchronizationContext + { + private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); + + public override void Post(SendOrPostCallback d, object state) + { + queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); + } + + public void ExecutePendingContinuations() + { + KeyValuePair<SendOrPostCallback, object> workItem; + while (queue.TryTake(out workItem)) + { + workItem.Key(workItem.Value); + } + } + } +} diff --git a/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs b/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs new file mode 100644 index 0000000000..9a40fef5a9 --- /dev/null +++ b/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Godot +{ + public class GodotTaskScheduler : TaskScheduler + { + private GodotSynchronizationContext Context { get; set; } + private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); + + public GodotTaskScheduler() + { + Context = new GodotSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(Context); + } + + protected sealed override void QueueTask(Task task) + { + lock (_tasks) + { + _tasks.AddLast(task); + } + } + + protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + if (SynchronizationContext.Current != Context) + { + return false; + } + + if (taskWasPreviouslyQueued) + { + TryDequeue(task); + } + + return TryExecuteTask(task); + } + + protected sealed override bool TryDequeue(Task task) + { + lock (_tasks) + { + return _tasks.Remove(task); + } + } + + protected sealed override IEnumerable<Task> GetScheduledTasks() + { + lock (_tasks) + { + return _tasks.ToArray(); + } + } + + public void Activate() + { + ExecuteQueuedTasks(); + Context.ExecutePendingContinuations(); + } + + private void ExecuteQueuedTasks() + { + while (true) + { + Task task; + + lock (_tasks) + { + if (_tasks.Any()) + { + task = _tasks.First.Value; + _tasks.RemoveFirst(); + } + else + { + break; + } + } + + if (task != null) + { + if (!TryExecuteTask(task)) + { + throw new InvalidOperationException(); + } + } + } + } + } +} diff --git a/modules/mono/glue/cs_files/IAwaitable.cs b/modules/mono/glue/Managed/Files/Interfaces/IAwaitable.cs index 0397957d00..0397957d00 100644 --- a/modules/mono/glue/cs_files/IAwaitable.cs +++ b/modules/mono/glue/Managed/Files/Interfaces/IAwaitable.cs diff --git a/modules/mono/glue/Managed/Files/Interfaces/IAwaiter.cs b/modules/mono/glue/Managed/Files/Interfaces/IAwaiter.cs new file mode 100644 index 0000000000..d3be9d781c --- /dev/null +++ b/modules/mono/glue/Managed/Files/Interfaces/IAwaiter.cs @@ -0,0 +1,18 @@ +using System.Runtime.CompilerServices; + +namespace Godot +{ + public interface IAwaiter : INotifyCompletion + { + bool IsCompleted { get; } + + void GetResult(); + } + + public interface IAwaiter<out TResult> : INotifyCompletion + { + bool IsCompleted { get; } + + TResult GetResult(); + } +} diff --git a/modules/mono/glue/Managed/Files/MarshalUtils.cs b/modules/mono/glue/Managed/Files/MarshalUtils.cs new file mode 100644 index 0000000000..f7699a15bf --- /dev/null +++ b/modules/mono/glue/Managed/Files/MarshalUtils.cs @@ -0,0 +1,18 @@ +using System; +using Godot.Collections; + +namespace Godot +{ + static class MarshalUtils + { + static bool IsArrayGenericType(Type type) + { + return type.GetGenericTypeDefinition() == typeof(Array<>); + } + + static bool IsDictionaryGenericType(Type type) + { + return type.GetGenericTypeDefinition() == typeof(Dictionary<, >); + } + } +} diff --git a/modules/mono/glue/cs_files/Mathf.cs b/modules/mono/glue/Managed/Files/Mathf.cs index a89dfe5f27..dcab3c1ffc 100644 --- a/modules/mono/glue/cs_files/Mathf.cs +++ b/modules/mono/glue/Managed/Files/Mathf.cs @@ -206,7 +206,7 @@ namespace Godot public static real_t PosMod(real_t a, real_t b) { real_t c = a % b; - if ((c < 0 && b > 0) || (c > 0 && b < 0)) + if ((c < 0 && b > 0) || (c > 0 && b < 0)) { c += b; } @@ -219,7 +219,7 @@ namespace Godot public static int PosMod(int a, int b) { int c = a % b; - if ((c < 0 && b > 0) || (c > 0 && b < 0)) + if ((c < 0 && b > 0) || (c > 0 && b < 0)) { c += b; } diff --git a/modules/mono/glue/cs_files/MathfEx.cs b/modules/mono/glue/Managed/Files/MathfEx.cs index 739b7fb568..2ef02cc288 100644 --- a/modules/mono/glue/cs_files/MathfEx.cs +++ b/modules/mono/glue/Managed/Files/MathfEx.cs @@ -29,7 +29,7 @@ namespace Godot public static int FloorToInt(real_t s) { return (int)Math.Floor(s); - } + } public static int RoundToInt(real_t s) { diff --git a/modules/mono/glue/Managed/Files/NodePath.cs b/modules/mono/glue/Managed/Files/NodePath.cs new file mode 100644 index 0000000000..2c89bec87f --- /dev/null +++ b/modules/mono/glue/Managed/Files/NodePath.cs @@ -0,0 +1,147 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Godot +{ + public partial class NodePath : IDisposable + { + private bool disposed = false; + + internal IntPtr ptr; + + internal static IntPtr GetPtr(NodePath instance) + { + return instance == null ? IntPtr.Zero : instance.ptr; + } + + ~NodePath() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposed) + return; + + if (ptr != IntPtr.Zero) + { + godot_icall_NodePath_Dtor(ptr); + ptr = IntPtr.Zero; + } + + disposed = true; + } + + internal NodePath(IntPtr ptr) + { + this.ptr = ptr; + } + + public IntPtr NativeInstance + { + get { return ptr; } + } + + public NodePath() : this(string.Empty) {} + + public NodePath(string path) + { + this.ptr = godot_icall_NodePath_Ctor(path); + } + + public static implicit operator NodePath(string from) + { + return new NodePath(from); + } + + public static implicit operator string(NodePath from) + { + return godot_icall_NodePath_operator_String(NodePath.GetPtr(from)); + } + + public override string ToString() + { + return (string)this; + } + + public NodePath GetAsPropertyPath() + { + return new NodePath(godot_icall_NodePath_get_as_property_path(NodePath.GetPtr(this))); + } + + public string GetConcatenatedSubnames() + { + return godot_icall_NodePath_get_concatenated_subnames(NodePath.GetPtr(this)); + } + + public string GetName(int idx) + { + return godot_icall_NodePath_get_name(NodePath.GetPtr(this), idx); + } + + public int GetNameCount() + { + return godot_icall_NodePath_get_name_count(NodePath.GetPtr(this)); + } + + public string GetSubname(int idx) + { + return godot_icall_NodePath_get_subname(NodePath.GetPtr(this), idx); + } + + public int GetSubnameCount() + { + return godot_icall_NodePath_get_subname_count(NodePath.GetPtr(this)); + } + + public bool IsAbsolute() + { + return godot_icall_NodePath_is_absolute(NodePath.GetPtr(this)); + } + + public bool IsEmpty() + { + return godot_icall_NodePath_is_empty(NodePath.GetPtr(this)); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_NodePath_Ctor(string path); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_NodePath_Dtor(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_NodePath_operator_String(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_NodePath_get_as_property_path(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_NodePath_get_concatenated_subnames(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_NodePath_get_name(IntPtr ptr, int arg1); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_NodePath_get_name_count(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_NodePath_get_subname(IntPtr ptr, int arg1); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_NodePath_get_subname_count(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_NodePath_is_absolute(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_NodePath_is_empty(IntPtr ptr); + } +} diff --git a/modules/mono/glue/Managed/Files/Object.base.cs b/modules/mono/glue/Managed/Files/Object.base.cs new file mode 100644 index 0000000000..30490a715f --- /dev/null +++ b/modules/mono/glue/Managed/Files/Object.base.cs @@ -0,0 +1,88 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Godot +{ + public partial class Object : IDisposable + { + private bool disposed = false; + + private const string nativeName = "Object"; + + internal IntPtr ptr; + internal bool memoryOwn; + + public Object() : this(false) + { + if (ptr == IntPtr.Zero) + ptr = godot_icall_Object_Ctor(this); + } + + internal Object(bool memoryOwn) + { + this.memoryOwn = memoryOwn; + } + + public IntPtr NativeInstance + { + get { return ptr; } + } + + internal static IntPtr GetPtr(Object instance) + { + return instance == null ? IntPtr.Zero : instance.ptr; + } + + ~Object() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposed) + return; + + if (ptr != IntPtr.Zero) + { + if (memoryOwn) + { + memoryOwn = false; + godot_icall_Reference_Disposed(this, ptr, !disposing); + } + else + { + godot_icall_Object_Disposed(this, ptr); + } + + this.ptr = IntPtr.Zero; + } + + disposed = true; + } + + public SignalAwaiter ToSignal(Object source, string signal) + { + return new SignalAwaiter(source, signal, this); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Object_Ctor(Object obj); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Object_Disposed(Object obj, IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Reference_Disposed(Object obj, IntPtr ptr, bool isFinalizer); + + // Used by the generated API + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Object_ClassDB_get_method(string type, string method); + } +} diff --git a/modules/mono/glue/cs_files/Plane.cs b/modules/mono/glue/Managed/Files/Plane.cs index 1020f06bf5..f11cd490a9 100644 --- a/modules/mono/glue/cs_files/Plane.cs +++ b/modules/mono/glue/Managed/Files/Plane.cs @@ -144,8 +144,17 @@ namespace Godot { return point - _normal * DistanceTo(point); } - - // Constructors + + // Constants + private static readonly Plane _planeYZ = new Plane(1, 0, 0, 0); + private static readonly Plane _planeXZ = new Plane(0, 1, 0, 0); + private static readonly Plane _planeXY = new Plane(0, 0, 1, 0); + + public static Plane PlaneYZ { get { return _planeYZ; } } + public static Plane PlaneXZ { get { return _planeXZ; } } + public static Plane PlaneXY { get { return _planeXY; } } + + // Constructors public Plane(real_t a, real_t b, real_t c, real_t d) { _normal = new Vector3(a, b, c); diff --git a/modules/mono/glue/cs_files/Quat.cs b/modules/mono/glue/Managed/Files/Quat.cs index c69c55d997..fd1ac01083 100644 --- a/modules/mono/glue/cs_files/Quat.cs +++ b/modules/mono/glue/Managed/Files/Quat.cs @@ -11,18 +11,11 @@ namespace Godot [StructLayout(LayoutKind.Sequential)] public struct Quat : IEquatable<Quat> { - private static readonly Quat identity = new Quat(0f, 0f, 0f, 1f); - public real_t x; public real_t y; public real_t z; public real_t w; - public static Quat Identity - { - get { return identity; } - } - public real_t this[int index] { get @@ -63,6 +56,16 @@ namespace Godot } } + public real_t Length + { + get { return Mathf.Sqrt(LengthSquared); } + } + + public real_t LengthSquared + { + get { return Dot(this); } + } + public Quat CubicSlerp(Quat b, Quat preA, Quat postB, real_t t) { real_t t2 = (1.0f - t) * t * 2f; @@ -76,24 +79,20 @@ namespace Godot return x * b.x + y * b.y + z * b.z + w * b.w; } - public Quat Inverse() - { - return new Quat(-x, -y, -z, w); - } - - public real_t Length() + public Vector3 GetEuler() { - return Mathf.Sqrt(LengthSquared()); + var basis = new Basis(this); + return basis.GetEuler(); } - public real_t LengthSquared() + public Quat Inverse() { - return Dot(this); + return new Quat(-x, -y, -z, w); } public Quat Normalized() { - return this / Length(); + return this / Length; } public void Set(real_t x, real_t y, real_t z, real_t w) @@ -103,12 +102,20 @@ namespace Godot this.z = z; this.w = w; } + public void Set(Quat q) { - x = q.x; - y = q.y; - z = q.z; - w = q.w; + this = q; + } + + public void SetAxisAngle(Vector3 axis, real_t angle) + { + this = new Quat(axis, angle); + } + + public void SetEuler(Vector3 eulerYXZ) + { + this = new Quat(eulerYXZ); } public Quat Slerp(Quat b, real_t t) @@ -192,22 +199,56 @@ namespace Godot return new Vector3(q.x, q.y, q.z); } - // Constructors + // Static Readonly Properties + public static Quat Identity { get; } = new Quat(0f, 0f, 0f, 1f); + + // Constructors public Quat(real_t x, real_t y, real_t z, real_t w) { this.x = x; this.y = y; this.z = z; this.w = w; - } + } + + public bool IsNormalized() + { + return Mathf.Abs(LengthSquared - 1) <= Mathf.Epsilon; + } + public Quat(Quat q) - { - x = q.x; - y = q.y; - z = q.z; - w = q.w; + { + this = q; + } + + public Quat(Basis basis) + { + this = basis.Quat(); } - + + public Quat(Vector3 eulerYXZ) + { + real_t half_a1 = eulerYXZ.y * (real_t)0.5; + real_t half_a2 = eulerYXZ.x * (real_t)0.5; + real_t half_a3 = eulerYXZ.z * (real_t)0.5; + + // R = Y(a1).X(a2).Z(a3) convention for Euler angles. + // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6) + // a3 is the angle of the first rotation, following the notation in this reference. + + real_t cos_a1 = Mathf.Cos(half_a1); + real_t sin_a1 = Mathf.Sin(half_a1); + real_t cos_a2 = Mathf.Cos(half_a2); + real_t sin_a2 = Mathf.Sin(half_a2); + real_t cos_a3 = Mathf.Cos(half_a3); + real_t sin_a3 = Mathf.Sin(half_a3); + + x = sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3; + y = sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3; + z = -sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3; + w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3; + } + public Quat(Vector3 axis, real_t angle) { real_t d = axis.Length(); diff --git a/modules/mono/glue/Managed/Files/RID.cs b/modules/mono/glue/Managed/Files/RID.cs new file mode 100644 index 0000000000..b862b8cac0 --- /dev/null +++ b/modules/mono/glue/Managed/Files/RID.cs @@ -0,0 +1,76 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Godot +{ + public partial class RID : IDisposable + { + private bool disposed = false; + + internal IntPtr ptr; + + internal static IntPtr GetPtr(RID instance) + { + return instance == null ? IntPtr.Zero : instance.ptr; + } + + ~RID() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposed) + return; + + if (ptr != IntPtr.Zero) + { + godot_icall_RID_Dtor(ptr); + ptr = IntPtr.Zero; + } + + disposed = true; + } + + internal RID(IntPtr ptr) + { + this.ptr = ptr; + } + + public IntPtr NativeInstance + { + get { return ptr; } + } + + internal RID() + { + this.ptr = IntPtr.Zero; + } + + public RID(Object from) + { + this.ptr = godot_icall_RID_Ctor(Object.GetPtr(from)); + } + + public int GetId() + { + return godot_icall_RID_get_id(RID.GetPtr(this)); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_RID_Ctor(IntPtr from); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_RID_Dtor(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_RID_get_id(IntPtr ptr); + } +} diff --git a/modules/mono/glue/cs_files/Rect2.cs b/modules/mono/glue/Managed/Files/Rect2.cs index 6f16656573..888f300347 100644 --- a/modules/mono/glue/cs_files/Rect2.cs +++ b/modules/mono/glue/Managed/Files/Rect2.cs @@ -11,24 +11,25 @@ namespace Godot [StructLayout(LayoutKind.Sequential)] public struct Rect2 : IEquatable<Rect2> { - private Vector2 position; - private Vector2 size; + private Vector2 _position; + private Vector2 _size; public Vector2 Position { - get { return position; } - set { position = value; } + get { return _position; } + set { _position = value; } } public Vector2 Size { - get { return size; } - set { size = value; } + get { return _size; } + set { _size = value; } } public Vector2 End { - get { return position + size; } + get { return _position + _size; } + set { _size = value - _position; } } public real_t Area @@ -36,6 +37,13 @@ namespace Godot get { return GetArea(); } } + public Rect2 Abs() + { + Vector2 end = End; + Vector2 topLeft = new Vector2(Mathf.Min(_position.x, end.x), Mathf.Min(_position.y, end.y)); + return new Rect2(topLeft, _size.Abs()); + } + public Rect2 Clip(Rect2 b) { var newRect = b; @@ -43,31 +51,31 @@ namespace Godot if (!Intersects(newRect)) return new Rect2(); - newRect.position.x = Mathf.Max(b.position.x, position.x); - newRect.position.y = Mathf.Max(b.position.y, position.y); + newRect._position.x = Mathf.Max(b._position.x, _position.x); + newRect._position.y = Mathf.Max(b._position.y, _position.y); - Vector2 bEnd = b.position + b.size; - Vector2 end = position + size; + Vector2 bEnd = b._position + b._size; + Vector2 end = _position + _size; - newRect.size.x = Mathf.Min(bEnd.x, end.x) - newRect.position.x; - newRect.size.y = Mathf.Min(bEnd.y, end.y) - newRect.position.y; + newRect._size.x = Mathf.Min(bEnd.x, end.x) - newRect._position.x; + newRect._size.y = Mathf.Min(bEnd.y, end.y) - newRect._position.y; return newRect; } public bool Encloses(Rect2 b) { - return b.position.x >= position.x && b.position.y >= position.y && - b.position.x + b.size.x < position.x + size.x && - b.position.y + b.size.y < position.y + size.y; + return b._position.x >= _position.x && b._position.y >= _position.y && + b._position.x + b._size.x < _position.x + _size.x && + b._position.y + b._size.y < _position.y + _size.y; } public Rect2 Expand(Vector2 to) { var expanded = this; - Vector2 begin = expanded.position; - Vector2 end = expanded.position + expanded.size; + Vector2 begin = expanded._position; + Vector2 end = expanded._position + expanded._size; if (to.x < begin.x) begin.x = to.x; @@ -79,25 +87,25 @@ namespace Godot if (to.y > end.y) end.y = to.y; - expanded.position = begin; - expanded.size = end - begin; + expanded._position = begin; + expanded._size = end - begin; return expanded; } public real_t GetArea() { - return size.x * size.y; + return _size.x * _size.y; } public Rect2 Grow(real_t by) { var g = this; - g.position.x -= by; - g.position.y -= by; - g.size.x += by * 2; - g.size.y += by * 2; + g._position.x -= by; + g._position.y -= by; + g._size.x += by * 2; + g._size.y += by * 2; return g; } @@ -106,10 +114,10 @@ namespace Godot { var g = this; - g.position.x -= left; - g.position.y -= top; - g.size.x += left + right; - g.size.y += top + bottom; + g._position.x -= left; + g._position.y -= top; + g._size.x += left + right; + g._size.y += top + bottom; return g; } @@ -128,19 +136,19 @@ namespace Godot public bool HasNoArea() { - return size.x <= 0 || size.y <= 0; + return _size.x <= 0 || _size.y <= 0; } public bool HasPoint(Vector2 point) { - if (point.x < position.x) + if (point.x < _position.x) return false; - if (point.y < position.y) + if (point.y < _position.y) return false; - if (point.x >= position.x + size.x) + if (point.x >= _position.x + _size.x) return false; - if (point.y >= position.y + size.y) + if (point.y >= _position.y + _size.y) return false; return true; @@ -148,13 +156,13 @@ namespace Godot public bool Intersects(Rect2 b) { - if (position.x > b.position.x + b.size.x) + if (_position.x > b._position.x + b._size.x) return false; - if (position.x + size.x < b.position.x) + if (_position.x + _size.x < b._position.x) return false; - if (position.y > b.position.y + b.size.y) + if (_position.y > b._position.y + b._size.y) return false; - if (position.y + size.y < b.position.y) + if (_position.y + _size.y < b._position.y) return false; return true; @@ -164,37 +172,37 @@ namespace Godot { Rect2 newRect; - newRect.position.x = Mathf.Min(b.position.x, position.x); - newRect.position.y = Mathf.Min(b.position.y, position.y); + newRect._position.x = Mathf.Min(b._position.x, _position.x); + newRect._position.y = Mathf.Min(b._position.y, _position.y); - newRect.size.x = Mathf.Max(b.position.x + b.size.x, position.x + size.x); - newRect.size.y = Mathf.Max(b.position.y + b.size.y, position.y + size.y); + newRect._size.x = Mathf.Max(b._position.x + b._size.x, _position.x + _size.x); + newRect._size.y = Mathf.Max(b._position.y + b._size.y, _position.y + _size.y); - newRect.size = newRect.size - newRect.position; // Make relative again + newRect._size = newRect._size - newRect._position; // Make relative again return newRect; } - - // Constructors + + // Constructors public Rect2(Vector2 position, Vector2 size) { - this.position = position; - this.size = size; + _position = position; + _size = size; } public Rect2(Vector2 position, real_t width, real_t height) { - this.position = position; - size = new Vector2(width, height); + _position = position; + _size = new Vector2(width, height); } public Rect2(real_t x, real_t y, Vector2 size) { - position = new Vector2(x, y); - this.size = size; + _position = new Vector2(x, y); + _size = size; } public Rect2(real_t x, real_t y, real_t width, real_t height) { - position = new Vector2(x, y); - size = new Vector2(width, height); + _position = new Vector2(x, y); + _size = new Vector2(width, height); } public static bool operator ==(Rect2 left, Rect2 right) @@ -219,20 +227,20 @@ namespace Godot public bool Equals(Rect2 other) { - return position.Equals(other.position) && size.Equals(other.size); + return _position.Equals(other._position) && _size.Equals(other._size); } public override int GetHashCode() { - return position.GetHashCode() ^ size.GetHashCode(); + return _position.GetHashCode() ^ _size.GetHashCode(); } public override string ToString() { return String.Format("({0}, {1})", new object[] { - position.ToString(), - size.ToString() + _position.ToString(), + _size.ToString() }); } @@ -240,8 +248,8 @@ namespace Godot { return String.Format("({0}, {1})", new object[] { - position.ToString(format), - size.ToString(format) + _position.ToString(format), + _size.ToString(format) }); } } diff --git a/modules/mono/glue/cs_files/SignalAwaiter.cs b/modules/mono/glue/Managed/Files/SignalAwaiter.cs index c06f6b05c9..9483b6ffb4 100644 --- a/modules/mono/glue/cs_files/SignalAwaiter.cs +++ b/modules/mono/glue/Managed/Files/SignalAwaiter.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace Godot { @@ -10,12 +11,12 @@ namespace Godot public SignalAwaiter(Object source, string signal, Object target) { - NativeCalls.godot_icall_Object_connect_signal_awaiter( - Object.GetPtr(source), - signal, Object.GetPtr(target), this - ); + godot_icall_SignalAwaiter_connect(Object.GetPtr(source), signal, Object.GetPtr(target), this); } + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static Error godot_icall_SignalAwaiter_connect(IntPtr source, string signal, IntPtr target, SignalAwaiter awaiter); + public bool IsCompleted { get diff --git a/modules/mono/glue/cs_files/StringExtensions.cs b/modules/mono/glue/Managed/Files/StringExtensions.cs index eaeed7b37b..c194facd0b 100644 --- a/modules/mono/glue/cs_files/StringExtensions.cs +++ b/modules/mono/glue/Managed/Files/StringExtensions.cs @@ -1,8 +1,7 @@ -//using System; - using System; using System.Collections.Generic; using System.Globalization; +using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Text.RegularExpressions; @@ -28,7 +27,7 @@ namespace Godot return slices; } - private static string GetSlicec(this string instance, char splitter, int slice) + private static string GetSliceCharacter(this string instance, char splitter, int slice) { if (!instance.Empty() && slice >= 0) { @@ -38,12 +37,18 @@ namespace Godot while (true) { - if (instance[i] == 0 || instance[i] == splitter) + bool end = instance.Length <= i; + + if (end || instance[i] == splitter) { if (slice == count) { return instance.Substring(prev, i - prev); } + else if (end) + { + return string.Empty; + } count++; prev = i + 1; @@ -59,7 +64,7 @@ namespace Godot // <summary> // If the string is a path to a file, return the path to the file without the extension. // </summary> - public static string Basename(this string instance) + public static string BaseName(this string instance) { int index = instance.LastIndexOf('.'); @@ -146,7 +151,7 @@ namespace Godot for (int i = 0; i < aux.GetSliceCount(" "); i++) { - string slice = aux.GetSlicec(' ', i); + string slice = aux.GetSliceCharacter(' ', i); if (slice.Length > 0) { slice = char.ToUpper(slice[0]) + slice.Substring(1); @@ -164,30 +169,59 @@ namespace Godot // </summary> public static int CasecmpTo(this string instance, string to) { + return instance.CompareTo(to, true); + } + + // <summary> + // Perform a comparison to another string, return -1 if less, 0 if equal and +1 if greater. + // </summary> + public static int CompareTo(this string instance, string to, bool caseSensitive = true) + { if (instance.Empty()) return to.Empty() ? 0 : -1; if (to.Empty()) return 1; - int instance_idx = 0; - int to_idx = 0; + int instanceIndex = 0; + int toIndex = 0; - while (true) + if (caseSensitive) // Outside while loop to avoid checking multiple times, despite some code duplication. { - if (to[to_idx] == 0 && instance[instance_idx] == 0) - return 0; // We're equal - if (instance[instance_idx] == 0) - return -1; // If this is empty, and the other one is not, then we're less... I think? - if (to[to_idx] == 0) - return 1; // Otherwise the other one is smaller... - if (instance[instance_idx] < to[to_idx]) // More than - return -1; - if (instance[instance_idx] > to[to_idx]) // Less than - return 1; - - instance_idx++; - to_idx++; + while (true) + { + if (to[toIndex] == 0 && instance[instanceIndex] == 0) + return 0; // We're equal + if (instance[instanceIndex] == 0) + return -1; // If this is empty, and the other one is not, then we're less... I think? + if (to[toIndex] == 0) + return 1; // Otherwise the other one is smaller... + if (instance[instanceIndex] < to[toIndex]) // More than + return -1; + if (instance[instanceIndex] > to[toIndex]) // Less than + return 1; + + instanceIndex++; + toIndex++; + } + } else + { + while (true) + { + if (to[toIndex] == 0 && instance[instanceIndex] == 0) + return 0; // We're equal + if (instance[instanceIndex] == 0) + return -1; // If this is empty, and the other one is not, then we're less... I think? + if (to[toIndex] == 0) + return 1; // Otherwise the other one is smaller.. + if (char.ToUpper(instance[instanceIndex]) < char.ToUpper(to[toIndex])) // More than + return -1; + if (char.ToUpper(instance[instanceIndex]) > char.ToUpper(to[toIndex])) // Less than + return 1; + + instanceIndex++; + toIndex++; + } } } @@ -363,7 +397,7 @@ namespace Godot // <summary> // Check whether this string is a subsequence of the given string. // </summary> - public static bool IsSubsequenceOf(this string instance, string text, bool case_insensitive) + public static bool IsSubsequenceOf(this string instance, string text, bool caseSensitive = true) { int len = instance.Length; @@ -373,50 +407,42 @@ namespace Godot if (len > text.Length) return false; - int src = 0; - int tgt = 0; + int source = 0; + int target = 0; - while (instance[src] != 0 && text[tgt] != 0) + while (instance[source] != 0 && text[target] != 0) { bool match; - if (case_insensitive) + if (!caseSensitive) { - char srcc = char.ToLower(instance[src]); - char tgtc = char.ToLower(text[tgt]); - match = srcc == tgtc; + char sourcec = char.ToLower(instance[source]); + char targetc = char.ToLower(text[target]); + match = sourcec == targetc; } else { - match = instance[src] == text[tgt]; + match = instance[source] == text[target]; } if (match) { - src++; - if (instance[src] == 0) + source++; + if (instance[source] == 0) return true; } - tgt++; + target++; } return false; } // <summary> - // Check whether this string is a subsequence of the given string, considering case. - // </summary> - public static bool IsSubsequenceOf(this string instance, string text) - { - return instance.IsSubsequenceOf(text, false); - } - - // <summary> - // Check whether this string is a subsequence of the given string, without considering case. + // Check whether this string is a subsequence of the given string, ignoring case differences. // </summary> public static bool IsSubsequenceOfI(this string instance, string text) { - return instance.IsSubsequenceOf(text, true); + return instance.IsSubsequenceOf(text, false); } // <summary> @@ -454,12 +480,12 @@ namespace Godot return false; // Don't start with number plz } - bool valid_char = instance[i] >= '0' && - instance[i] <= '9' || instance[i] >= 'a' && - instance[i] <= 'z' || instance[i] >= 'A' && + bool validChar = instance[i] >= '0' && + instance[i] <= '9' || instance[i] >= 'a' && + instance[i] <= 'z' || instance[i] >= 'A' && instance[i] <= 'Z' || instance[i] == '_'; - if (!valid_char) + if (!validChar) return false; } @@ -478,8 +504,9 @@ namespace Godot // <summary> // Check whether the string contains a valid IP address. // </summary> - public static bool IsValidIpAddress(this string instance) + public static bool IsValidIPAddress(this string instance) { + // TODO: Support IPv6 addresses string[] ip = instance.Split("."); if (ip.Length != 4) @@ -502,7 +529,7 @@ namespace Godot // <summary> // Return a copy of the string with special characters escaped using the JSON standard. // </summary> - public static string JsonEscape(this string instance) + public static string JSONEscape(this string instance) { var sb = new StringBuilder(string.Copy(instance)); @@ -565,15 +592,15 @@ namespace Godot // <summary> // Do a simple case sensitive expression match, using ? and * wildcards (see [method expr_match]). // </summary> - public static bool Match(this string instance, string expr) + public static bool Match(this string instance, string expr, bool caseSensitive = true) { - return instance.ExprMatch(expr, true); + return instance.ExprMatch(expr, caseSensitive); } // <summary> // Do a simple case insensitive expression match, using ? and * wildcards (see [method expr_match]). // </summary> - public static bool Matchn(this string instance, string expr) + public static bool MatchN(this string instance, string expr) { return instance.ExprMatch(expr, false); } @@ -581,49 +608,31 @@ namespace Godot // <summary> // Return the MD5 hash of the string as an array of bytes. // </summary> - public static byte[] Md5Buffer(this string instance) + public static byte[] MD5Buffer(this string instance) { - return NativeCalls.godot_icall_String_md5_buffer(instance); + return godot_icall_String_md5_buffer(instance); } + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static byte[] godot_icall_String_md5_buffer(string str); + // <summary> // Return the MD5 hash of the string as a string. // </summary> - public static string Md5Text(this string instance) + public static string MD5Text(this string instance) { - return NativeCalls.godot_icall_String_md5_text(instance); + return godot_icall_String_md5_text(instance); } + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_String_md5_text(string str); + // <summary> // Perform a case-insensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. // </summary> public static int NocasecmpTo(this string instance, string to) { - if (instance.Empty()) - return to.Empty() ? 0 : -1; - - if (to.Empty()) - return 1; - - int instance_idx = 0; - int to_idx = 0; - - while (true) - { - if (to[to_idx] == 0 && instance[instance_idx] == 0) - return 0; // We're equal - if (instance[instance_idx] == 0) - return -1; // If this is empty, and the other one is not, then we're less... I think? - if (to[to_idx] == 0) - return 1; // Otherwise the other one is smaller.. - if (char.ToUpper(instance[instance_idx]) < char.ToUpper(to[to_idx])) // More than - return -1; - if (char.ToUpper(instance[instance_idx]) > char.ToUpper(to[to_idx])) // Less than - return 1; - - instance_idx++; - to_idx++; - } + return instance.CompareTo(to, false); } // <summary> @@ -740,7 +749,7 @@ namespace Godot // <summary> // Replace occurrences of a substring for different ones inside the string, but search case-insensitive. // </summary> - public static string Replacen(this string instance, string what, string forwhat) + public static string ReplaceN(this string instance, string what, string forwhat) { return Regex.Replace(instance, what, forwhat, RegexOptions.IgnoreCase); } @@ -748,19 +757,25 @@ namespace Godot // <summary> // Perform a search for a substring, but start from the end of the string instead of the beginning. // </summary> - public static int Rfind(this string instance, string what, int from = -1) + public static int RFind(this string instance, string what, int from = -1) { - return NativeCalls.godot_icall_String_rfind(instance, what, from); + return godot_icall_String_rfind(instance, what, from); } + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_String_rfind(string str, string what, int from); + // <summary> // Perform a search for a substring, but start from the end of the string instead of the beginning. Also search case-insensitive. // </summary> - public static int Rfindn(this string instance, string what, int from = -1) + public static int RFindN(this string instance, string what, int from = -1) { - return NativeCalls.godot_icall_String_rfindn(instance, what, from); + return godot_icall_String_rfindn(instance, what, from); } + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_String_rfindn(string str, string what, int from); + // <summary> // Return the right side of the string from a given position. // </summary> @@ -775,19 +790,25 @@ namespace Godot return instance.Substring(pos, instance.Length - pos); } - public static byte[] Sha256Buffer(this string instance) + public static byte[] SHA256Buffer(this string instance) { - return NativeCalls.godot_icall_String_sha256_buffer(instance); + return godot_icall_String_sha256_buffer(instance); } + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static byte[] godot_icall_String_sha256_buffer(string str); + // <summary> // Return the SHA-256 hash of the string as a string. // </summary> - public static string Sha256Text(this string instance) + public static string SHA256Text(this string instance) { - return NativeCalls.godot_icall_String_sha256_text(instance); + return godot_icall_String_sha256_text(instance); } + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_String_sha256_text(string str); + // <summary> // Return the similarity index of the text compared to this string. 1 means totally similar and 0 means totally dissimilar. // </summary> @@ -804,20 +825,20 @@ namespace Godot return 0.0f; } - string[] srcBigrams = instance.Bigrams(); - string[] tgtBigrams = text.Bigrams(); + string[] sourceBigrams = instance.Bigrams(); + string[] targetBigrams = text.Bigrams(); - int src_size = srcBigrams.Length; - int tgt_size = tgtBigrams.Length; + int sourceSize = sourceBigrams.Length; + int targetSize = targetBigrams.Length; - float sum = src_size + tgt_size; + float sum = sourceSize + targetSize; float inter = 0; - for (int i = 0; i < src_size; i++) + for (int i = 0; i < sourceSize; i++) { - for (int j = 0; j < tgt_size; j++) + for (int j = 0; j < targetSize; j++) { - if (srcBigrams[i] == tgtBigrams[j]) + if (sourceBigrams[i] == targetBigrams[j]) { inter++; break; @@ -831,7 +852,7 @@ namespace Godot // <summary> // Split the string by a divisor string, return an array of the substrings. Example "One,Two,Three" will return ["One","Two","Three"] if split by ",". // </summary> - public static string[] Split(this string instance, string divisor, bool allow_empty = true) + public static string[] Split(this string instance, string divisor, bool allowEmpty = true) { return instance.Split(new[] { divisor }, StringSplitOptions.RemoveEmptyEntries); } @@ -839,7 +860,7 @@ namespace Godot // <summary> // Split the string in floats by using a divisor string, return an array of the substrings. Example "1,2.5,3" will return [1,2.5,3] if split by ",". // </summary> - public static float[] SplitFloats(this string instance, string divisor, bool allow_empty = true) + public static float[] SplitFloats(this string instance, string divisor, bool allowEmpty = true) { var ret = new List<float>(); int from = 0; @@ -850,7 +871,7 @@ namespace Godot int end = instance.Find(divisor, from); if (end < 0) end = len; - if (allow_empty || end > from) + if (allowEmpty || end > from) ret.Add(float.Parse(instance.Substring(from))); if (end == len) break; @@ -861,7 +882,7 @@ namespace Godot return ret.ToArray(); } - private static readonly char[] non_printable = { + private static readonly char[] _nonPrintable = { (char)00, (char)01, (char)02, (char)03, (char)04, (char)05, (char)06, (char)07, (char)08, (char)09, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, @@ -878,11 +899,11 @@ namespace Godot if (left) { if (right) - return instance.Trim(non_printable); - return instance.TrimStart(non_printable); + return instance.Trim(_nonPrintable); + return instance.TrimStart(_nonPrintable); } - return instance.TrimEnd(non_printable); + return instance.TrimEnd(_nonPrintable); } // <summary> @@ -936,7 +957,7 @@ namespace Godot // <summary> // Convert the String (which is an array of characters) to PoolByteArray (which is an array of bytes). The conversion is a bit slower than to_ascii(), but supports all UTF-8 characters. Therefore, you should prefer this function over to_ascii(). // </summary> - public static byte[] ToUtf8(this string instance) + public static byte[] ToUTF8(this string instance) { return Encoding.UTF8.GetBytes(instance); } @@ -944,7 +965,7 @@ namespace Godot // <summary> // Return a copy of the string with special characters escaped using the XML standard. // </summary> - public static string XmlEscape(this string instance) + public static string XMLEscape(this string instance) { return SecurityElement.Escape(instance); } @@ -952,7 +973,7 @@ namespace Godot // <summary> // Return a copy of the string with escaped characters replaced by their meanings according to the XML standard. // </summary> - public static string XmlUnescape(this string instance) + public static string XMLUnescape(this string instance) { return SecurityElement.FromString(instance).Text; } diff --git a/modules/mono/glue/cs_files/Transform.cs b/modules/mono/glue/Managed/Files/Transform.cs index d1b247a552..fa85855edd 100644 --- a/modules/mono/glue/cs_files/Transform.cs +++ b/modules/mono/glue/Managed/Files/Transform.cs @@ -20,6 +20,25 @@ namespace Godot return new Transform(basisInv, basisInv.Xform(-origin)); } + public Transform InterpolateWith(Transform transform, real_t c) + { + /* not sure if very "efficient" but good enough? */ + + Vector3 sourceScale = basis.Scale; + Quat sourceRotation = basis.RotationQuat(); + Vector3 sourceLocation = origin; + + Vector3 destinationScale = transform.basis.Scale; + Quat destinationRotation = transform.basis.RotationQuat(); + Vector3 destinationLocation = transform.origin; + + var interpolated = new Transform(); + interpolated.basis.SetQuantScale(sourceRotation.Slerp(destinationRotation, c).Normalized(), sourceScale.LinearInterpolate(destinationScale, c)); + interpolated.origin = sourceLocation.LinearInterpolate(destinationLocation, c); + + return interpolated; + } + public Transform Inverse() { Basis basisTr = basis.Transposed(); @@ -102,8 +121,19 @@ namespace Godot basis[0, 2] * vInv.x + basis[1, 2] * vInv.y + basis[2, 2] * vInv.z ); } - - // Constructors + + // Constants + private static readonly Transform _identity = new Transform(Basis.Identity, Vector3.Zero); + private static readonly Transform _flipX = new Transform(new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1), Vector3.Zero); + private static readonly Transform _flipY = new Transform(new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1), Vector3.Zero); + private static readonly Transform _flipZ = new Transform(new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1), Vector3.Zero); + + public static Transform Identity { get { return _identity; } } + public static Transform FlipX { get { return _flipX; } } + public static Transform FlipY { get { return _flipY; } } + public static Transform FlipZ { get { return _flipZ; } } + + // Constructors public Transform(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis, Vector3 origin) { basis = Basis.CreateFromAxes(xAxis, yAxis, zAxis); diff --git a/modules/mono/glue/cs_files/Transform2D.cs b/modules/mono/glue/Managed/Files/Transform2D.cs index ff5259178b..c9e5b560b2 100644 --- a/modules/mono/glue/cs_files/Transform2D.cs +++ b/modules/mono/glue/Managed/Files/Transform2D.cs @@ -11,22 +11,10 @@ namespace Godot [StructLayout(LayoutKind.Sequential)] public struct Transform2D : IEquatable<Transform2D> { - private static readonly Transform2D identity = new Transform2D - ( - new Vector2(1f, 0f), - new Vector2(0f, 1f), - new Vector2(0f, 0f) - ); - public Vector2 x; public Vector2 y; public Vector2 o; - public static Transform2D Identity - { - get { return identity; } - } - public Vector2 Origin { get { return o; } @@ -264,15 +252,24 @@ namespace Godot Vector2 vInv = v - o; return new Vector2(x.Dot(vInv), y.Dot(vInv)); } - - // Constructors + + // Constants + private static readonly Transform2D _identity = new Transform2D(new Vector2(1f, 0f), new Vector2(0f, 1f), Vector2.Zero); + private static readonly Transform2D _flipX = new Transform2D(new Vector2(-1f, 0f), new Vector2(0f, 1f), Vector2.Zero); + private static readonly Transform2D _flipY = new Transform2D(new Vector2(1f, 0f), new Vector2(0f, -1f), Vector2.Zero); + + public static Transform2D Identity { get { return _identity; } } + public static Transform2D FlipX { get { return _flipX; } } + public static Transform2D FlipY { get { return _flipY; } } + + // Constructors public Transform2D(Vector2 xAxis, Vector2 yAxis, Vector2 origin) { x = xAxis; y = yAxis; o = origin; } - + public Transform2D(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy) { x = new Vector2(xx, xy); diff --git a/modules/mono/glue/cs_files/Vector2.cs b/modules/mono/glue/Managed/Files/Vector2.cs index c274364895..ce41886bfc 100644 --- a/modules/mono/glue/cs_files/Vector2.cs +++ b/modules/mono/glue/Managed/Files/Vector2.cs @@ -184,6 +184,11 @@ namespace Godot return result; } + public Vector2 Project(Vector2 onNormal) + { + return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); + } + public Vector2 Reflect(Vector2 n) { return 2.0f * n * Dot(n) - this; @@ -210,7 +215,7 @@ namespace Godot x = v.x; y = v.y; } - + public Vector2 Slerp(Vector2 b, real_t t) { real_t theta = AngleTo(b); @@ -231,24 +236,27 @@ namespace Godot { return new Vector2(y, -x); } - - private static readonly Vector2 zero = new Vector2 (0, 0); - private static readonly Vector2 one = new Vector2 (1, 1); - private static readonly Vector2 negOne = new Vector2 (-1, -1); - - private static readonly Vector2 up = new Vector2 (0, 1); - private static readonly Vector2 down = new Vector2 (0, -1); - private static readonly Vector2 right = new Vector2 (1, 0); - private static readonly Vector2 left = new Vector2 (-1, 0); - - public static Vector2 Zero { get { return zero; } } - public static Vector2 One { get { return one; } } - public static Vector2 NegOne { get { return negOne; } } - - public static Vector2 Up { get { return up; } } - public static Vector2 Down { get { return down; } } - public static Vector2 Right { get { return right; } } - public static Vector2 Left { get { return left; } } + + // Constants + private static readonly Vector2 _zero = new Vector2(0, 0); + private static readonly Vector2 _one = new Vector2(1, 1); + private static readonly Vector2 _negOne = new Vector2(-1, -1); + private static readonly Vector2 _inf = new Vector2(Mathf.Inf, Mathf.Inf); + + private static readonly Vector2 _up = new Vector2(0, -1); + private static readonly Vector2 _down = new Vector2(0, 1); + private static readonly Vector2 _right = new Vector2(1, 0); + private static readonly Vector2 _left = new Vector2(-1, 0); + + public static Vector2 Zero { get { return _zero; } } + public static Vector2 NegOne { get { return _negOne; } } + public static Vector2 One { get { return _one; } } + public static Vector2 Inf { get { return _inf; } } + + public static Vector2 Up { get { return _up; } } + public static Vector2 Down { get { return _down; } } + public static Vector2 Right { get { return _right; } } + public static Vector2 Left { get { return _left; } } // Constructors public Vector2(real_t x, real_t y) diff --git a/modules/mono/glue/cs_files/Vector3.cs b/modules/mono/glue/Managed/Files/Vector3.cs index 085a4f0043..f6ff27989d 100644 --- a/modules/mono/glue/cs_files/Vector3.cs +++ b/modules/mono/glue/Managed/Files/Vector3.cs @@ -204,12 +204,17 @@ namespace Godot public Basis Outer(Vector3 b) { return new Basis( - new Vector3(x * b.x, x * b.y, x * b.z), - new Vector3(y * b.x, y * b.y, y * b.z), - new Vector3(z * b.x, z * b.y, z * b.z) + x * b.x, x * b.y, x * b.z, + y * b.x, y * b.y, y * b.z, + z * b.x, z * b.y, z * b.z ); } + public Vector3 Project(Vector3 onNormal) + { + return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); + } + public Vector3 Reflect(Vector3 n) { #if DEBUG @@ -271,28 +276,31 @@ namespace Godot 0f, 0f, z ); } - - private static readonly Vector3 zero = new Vector3 (0, 0, 0); - private static readonly Vector3 one = new Vector3 (1, 1, 1); - private static readonly Vector3 negOne = new Vector3 (-1, -1, -1); - - private static readonly Vector3 up = new Vector3 (0, 1, 0); - private static readonly Vector3 down = new Vector3 (0, -1, 0); - private static readonly Vector3 right = new Vector3 (1, 0, 0); - private static readonly Vector3 left = new Vector3 (-1, 0, 0); - private static readonly Vector3 forward = new Vector3 (0, 0, -1); - private static readonly Vector3 back = new Vector3 (0, 0, 1); - - public static Vector3 Zero { get { return zero; } } - public static Vector3 One { get { return one; } } - public static Vector3 NegOne { get { return negOne; } } - - public static Vector3 Up { get { return up; } } - public static Vector3 Down { get { return down; } } - public static Vector3 Right { get { return right; } } - public static Vector3 Left { get { return left; } } - public static Vector3 Forward { get { return forward; } } - public static Vector3 Back { get { return back; } } + + // Constants + private static readonly Vector3 _zero = new Vector3(0, 0, 0); + private static readonly Vector3 _one = new Vector3(1, 1, 1); + private static readonly Vector3 _negOne = new Vector3(-1, -1, -1); + private static readonly Vector3 _inf = new Vector3(Mathf.Inf, Mathf.Inf, Mathf.Inf); + + private static readonly Vector3 _up = new Vector3(0, 1, 0); + private static readonly Vector3 _down = new Vector3(0, -1, 0); + private static readonly Vector3 _right = new Vector3(1, 0, 0); + private static readonly Vector3 _left = new Vector3(-1, 0, 0); + private static readonly Vector3 _forward = new Vector3(0, 0, -1); + private static readonly Vector3 _back = new Vector3(0, 0, 1); + + public static Vector3 Zero { get { return _zero; } } + public static Vector3 One { get { return _one; } } + public static Vector3 NegOne { get { return _negOne; } } + public static Vector3 Inf { get { return _inf; } } + + public static Vector3 Up { get { return _up; } } + public static Vector3 Down { get { return _down; } } + public static Vector3 Right { get { return _right; } } + public static Vector3 Left { get { return _left; } } + public static Vector3 Forward { get { return _forward; } } + public static Vector3 Back { get { return _back; } } // Constructors public Vector3(real_t x, real_t y, real_t z) diff --git a/modules/mono/glue/Managed/IgnoredFiles/Enums.cs b/modules/mono/glue/Managed/IgnoredFiles/Enums.cs new file mode 100644 index 0000000000..05f1abcf93 --- /dev/null +++ b/modules/mono/glue/Managed/IgnoredFiles/Enums.cs @@ -0,0 +1,21 @@ + +namespace Godot +{ + public enum Margin + { + Left = 0, + Top = 1, + Right = 2, + Bottom = 3 + } + + public enum Error + { + Ok = 0 + } + + public enum PropertyHint + { + None = 0 + } +} diff --git a/modules/mono/glue/Managed/IgnoredFiles/FuncRef.cs b/modules/mono/glue/Managed/IgnoredFiles/FuncRef.cs new file mode 100644 index 0000000000..83504fe49f --- /dev/null +++ b/modules/mono/glue/Managed/IgnoredFiles/FuncRef.cs @@ -0,0 +1,17 @@ +using System; + +namespace Godot +{ + public partial class FuncRef + { + public void SetInstance(Object instance) + { + throw new NotImplementedException(); + } + + public void SetFunction(string name) + { + throw new NotImplementedException(); + } + } +} diff --git a/modules/mono/glue/Managed/IgnoredFiles/Node.cs b/modules/mono/glue/Managed/IgnoredFiles/Node.cs new file mode 100644 index 0000000000..99ba0f827a --- /dev/null +++ b/modules/mono/glue/Managed/IgnoredFiles/Node.cs @@ -0,0 +1,28 @@ + +using System; + +namespace Godot +{ + public partial class Node + { + public Node GetChild(int idx) + { + throw new NotImplementedException(); + } + + public Node GetNode(NodePath path) + { + throw new NotImplementedException(); + } + + public Node GetOwner() + { + throw new NotImplementedException(); + } + + public Node GetParent() + { + throw new NotImplementedException(); + } + } +} diff --git a/modules/mono/glue/Managed/IgnoredFiles/Resource.cs b/modules/mono/glue/Managed/IgnoredFiles/Resource.cs new file mode 100644 index 0000000000..cc0a5555b1 --- /dev/null +++ b/modules/mono/glue/Managed/IgnoredFiles/Resource.cs @@ -0,0 +1,7 @@ +namespace Godot +{ + public partial class Resource + { + + } +} diff --git a/modules/mono/glue/Managed/IgnoredFiles/ResourceLoader.cs b/modules/mono/glue/Managed/IgnoredFiles/ResourceLoader.cs new file mode 100644 index 0000000000..6461d35146 --- /dev/null +++ b/modules/mono/glue/Managed/IgnoredFiles/ResourceLoader.cs @@ -0,0 +1,12 @@ +using System; + +namespace Godot +{ + public partial class ResourceLoader + { + public static Resource Load(string path, string typeHint = "", bool pNoCache = false) + { + throw new NotImplementedException(); + } + } +} diff --git a/modules/mono/glue/Managed/IgnoredFiles/WeakRef.cs b/modules/mono/glue/Managed/IgnoredFiles/WeakRef.cs new file mode 100644 index 0000000000..1498b7836b --- /dev/null +++ b/modules/mono/glue/Managed/IgnoredFiles/WeakRef.cs @@ -0,0 +1,7 @@ +namespace Godot +{ + public partial class WeakRef + { + + } +} diff --git a/modules/mono/glue/Managed/Managed.csproj b/modules/mono/glue/Managed/Managed.csproj new file mode 100644 index 0000000000..1f82dde5e7 --- /dev/null +++ b/modules/mono/glue/Managed/Managed.csproj @@ -0,0 +1,40 @@ +<?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)' == '' ">x86</Platform> + <ProjectGuid>{DAA3DEF8-5112-407C-A5E5-6C608CF5F955}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>Managed</RootNamespace> + <AssemblyName>Managed</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ExternalConsole>true</ExternalConsole> + <PlatformTarget>x86</PlatformTarget> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ExternalConsole>true</ExternalConsole> + <PlatformTarget>x86</PlatformTarget> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Files\**\*.cs" /> + <Compile Include="IgnoredFiles\**\*.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> +</Project> diff --git a/modules/mono/glue/Managed/Managed.sln b/modules/mono/glue/Managed/Managed.sln new file mode 100644 index 0000000000..61ddde0fb7 --- /dev/null +++ b/modules/mono/glue/Managed/Managed.sln @@ -0,0 +1,17 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managed", "Managed.csproj", "{DAA3DEF8-5112-407C-A5E5-6C608CF5F955}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DAA3DEF8-5112-407C-A5E5-6C608CF5F955}.Debug|x86.ActiveCfg = Debug|x86 + {DAA3DEF8-5112-407C-A5E5-6C608CF5F955}.Debug|x86.Build.0 = Debug|x86 + {DAA3DEF8-5112-407C-A5E5-6C608CF5F955}.Release|x86.ActiveCfg = Release|x86 + {DAA3DEF8-5112-407C-A5E5-6C608CF5F955}.Release|x86.Build.0 = Release|x86 + EndGlobalSection +EndGlobal diff --git a/modules/mono/glue/Managed/Properties/AssemblyInfo.cs b/modules/mono/glue/Managed/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..77b3774e81 --- /dev/null +++ b/modules/mono/glue/Managed/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("Managed")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[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/glue/Managed/README.md b/modules/mono/glue/Managed/README.md new file mode 100644 index 0000000000..65e63cae37 --- /dev/null +++ b/modules/mono/glue/Managed/README.md @@ -0,0 +1,5 @@ +The directory `Files` contains C# files from the core assembly project that are not part of the generated API. Any file with the `.cs` extension in this directory will be added to the core assembly project. + +A dummy solution and project is provided to get tooling help while editing these files, like code completion and name refactoring. + +The directory `IgnoredFiles` contains C# files that are needed to build the dummy project but must not be added to the core assembly project. They contain placeholders for the declarations that are part of the generated API. diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp new file mode 100644 index 0000000000..58916c5283 --- /dev/null +++ b/modules/mono/glue/base_object_glue.cpp @@ -0,0 +1,158 @@ +/*************************************************************************/ +/* base_object_glue.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 "base_object_glue.h" + +#ifdef MONO_GLUE_ENABLED + +#include "core/reference.h" +#include "core/string_db.h" + +#include "../csharp_script.h" +#include "../mono_gd/gd_mono_internals.h" +#include "../mono_gd/gd_mono_utils.h" +#include "../signal_awaiter_utils.h" + +Object *godot_icall_Object_Ctor(MonoObject *p_obj) { + Object *instance = memnew(Object); + GDMonoInternals::tie_managed_to_unmanaged(p_obj, instance); + return instance; +} + +void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_ptr == NULL); +#endif + + if (p_ptr->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance()); + if (cs_instance) { + if (!cs_instance->is_destructing_script_instance()) { + cs_instance->mono_object_disposed(p_obj); + p_ptr->set_script_instance(NULL); + } + return; + } + } + + void *data = p_ptr->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); + + if (data) { + Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle; + if (gchandle.is_valid()) { + CSharpLanguage::release_script_gchandle(p_obj, gchandle); + } + } +} + +void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, bool p_is_finalizer) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_ptr == NULL); + // This is only called with Reference derived classes + CRASH_COND(!Object::cast_to<Reference>(p_ptr)); +#endif + + Reference *ref = static_cast<Reference *>(p_ptr); + + if (ref->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(ref->get_script_instance()); + if (cs_instance) { + if (!cs_instance->is_destructing_script_instance()) { + bool r_owner_deleted; + cs_instance->mono_object_disposed_baseref(p_obj, p_is_finalizer, r_owner_deleted); + if (!r_owner_deleted && !p_is_finalizer) { + // If the native instance is still alive and Dispose() was called + // (instead of the finalizer), then we remove the script instance. + ref->set_script_instance(NULL); + } + } + return; + } + } + + // Unsafe refcount decrement. The managed instance also counts as a reference. + // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) + if (ref->unreference()) { + memdelete(ref); + } else { + void *data = ref->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); + + if (data) { + Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle; + if (gchandle.is_valid()) { + CSharpLanguage::release_script_gchandle(p_obj, gchandle); + } + } + } +} + +MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method) { + StringName type(GDMonoMarshal::mono_string_to_godot(p_type)); + StringName method(GDMonoMarshal::mono_string_to_godot(p_method)); + return ClassDB::get_method(type, method); +} + +MonoObject *godot_icall_Object_weakref(Object *p_obj) { + if (!p_obj) + return NULL; + + Ref<WeakRef> wref; + Reference *ref = Object::cast_to<Reference>(p_obj); + + if (ref) { + REF r = ref; + if (!r.is_valid()) + return NULL; + + wref.instance(); + wref->set_ref(r); + } else { + wref.instance(); + wref->set_obj(p_obj); + } + + return GDMonoUtils::create_managed_for_godot_object(CACHED_CLASS(WeakRef), Reference::get_class_static(), Object::cast_to<Object>(wref.ptr())); +} + +Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal, Object *p_target, MonoObject *p_awaiter) { + String signal = GDMonoMarshal::mono_string_to_godot(p_signal); + return SignalAwaiterUtils::connect_signal_awaiter(p_source, signal, p_target, p_awaiter); +} + +void godot_register_object_icalls() { + mono_add_internal_call("Godot.Object::godot_icall_Object_Ctor", (void *)godot_icall_Object_Ctor); + mono_add_internal_call("Godot.Object::godot_icall_Object_Disposed", (void *)godot_icall_Object_Disposed); + mono_add_internal_call("Godot.Object::godot_icall_Reference_Disposed", (void *)godot_icall_Reference_Disposed); + mono_add_internal_call("Godot.Object::godot_icall_Object_ClassDB_get_method", (void *)godot_icall_Object_ClassDB_get_method); + mono_add_internal_call("Godot.Object::godot_icall_Object_weakref", (void *)godot_icall_Object_weakref); + mono_add_internal_call("Godot.SignalAwaiter::godot_icall_SignalAwaiter_connect", (void *)godot_icall_SignalAwaiter_connect); +} + +#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/base_object_glue.h b/modules/mono/glue/base_object_glue.h new file mode 100644 index 0000000000..2d4d66ebb8 --- /dev/null +++ b/modules/mono/glue/base_object_glue.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* base_object_glue.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef BASE_OBJECT_GLUE_H +#define BASE_OBJECT_GLUE_H + +#ifdef MONO_GLUE_ENABLED + +#include "core/class_db.h" +#include "core/object.h" + +#include "../mono_gd/gd_mono_marshal.h" + +Object *godot_icall_Object_Ctor(MonoObject *p_obj); + +void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr); + +void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, bool p_is_finalizer); + +MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method); + +MonoObject *godot_icall_Object_weakref(Object *p_obj); + +Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal, Object *p_target, MonoObject *p_awaiter); + +// Register internal calls + +void godot_register_object_icalls(); + +#endif // MONO_GLUE_ENABLED + +#endif // BASE_OBJECT_GLUE_H diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp new file mode 100644 index 0000000000..059e2ff6de --- /dev/null +++ b/modules/mono/glue/collections_glue.cpp @@ -0,0 +1,298 @@ +/*************************************************************************/ +/* collections_glue.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 "collections_glue.h" + +#ifdef MONO_GLUE_ENABLED + +#include <mono/metadata/exception.h> + +#include "../mono_gd/gd_mono_class.h" +#include "../mono_gd/gd_mono_utils.h" + +Array *godot_icall_Array_Ctor() { + return memnew(Array); +} + +void godot_icall_Array_Dtor(Array *ptr) { + memdelete(ptr); +} + +MonoObject *godot_icall_Array_At(Array *ptr, int index) { + if (index < 0 || index > ptr->size()) { + GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); + return NULL; + } + return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index)); +} + +MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_encoding, GDMonoClass *type_class) { + if (index < 0 || index > ptr->size()) { + GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); + return NULL; + } + return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index), ManagedType(type_encoding, type_class)); +} + +void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) { + if (index < 0 || index > ptr->size()) { + GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); + return; + } + ptr->operator[](index) = GDMonoMarshal::mono_object_to_variant(value); +} + +int godot_icall_Array_Count(Array *ptr) { + return ptr->size(); +} + +void godot_icall_Array_Add(Array *ptr, MonoObject *item) { + ptr->append(GDMonoMarshal::mono_object_to_variant(item)); +} + +void godot_icall_Array_Clear(Array *ptr) { + ptr->clear(); +} + +bool godot_icall_Array_Contains(Array *ptr, MonoObject *item) { + return ptr->find(GDMonoMarshal::mono_object_to_variant(item)) != -1; +} + +void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) { + int count = ptr->size(); + + if (mono_array_length(array) < (array_index + count)) { + MonoException *exc = mono_get_exception_argument("", "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + GDMonoUtils::set_pending_exception(exc); + return; + } + + for (int i = 0; i < count; i++) { + MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(ptr->operator[](i)); + mono_array_setref(array, array_index, boxed); + array_index++; + } +} + +int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) { + return ptr->find(GDMonoMarshal::mono_object_to_variant(item)); +} + +void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item) { + if (index < 0 || index > ptr->size()) { + GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); + return; + } + ptr->insert(index, GDMonoMarshal::mono_object_to_variant(item)); +} + +bool godot_icall_Array_Remove(Array *ptr, MonoObject *item) { + int idx = ptr->find(GDMonoMarshal::mono_object_to_variant(item)); + if (idx >= 0) { + ptr->remove(idx); + return true; + } + return false; +} + +void godot_icall_Array_RemoveAt(Array *ptr, int index) { + if (index < 0 || index > ptr->size()) { + GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); + return; + } + ptr->remove(index); +} + +void godot_icall_Array_Generic_GetElementTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class) { + MonoType *elem_type = mono_reflection_type_get_type(refltype); + + *type_encoding = mono_type_get_type(elem_type); + MonoClass *type_class_raw = mono_class_from_mono_type(elem_type); + *type_class = GDMono::get_singleton()->get_class(type_class_raw); +} + +Dictionary *godot_icall_Dictionary_Ctor() { + return memnew(Dictionary); +} + +void godot_icall_Dictionary_Dtor(Dictionary *ptr) { + memdelete(ptr); +} + +MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key) { + Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); + if (ret == NULL) { + MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr()); +#ifdef DEBUG_ENABLED + CRASH_COND(!exc); +#endif + GDMonoUtils::runtime_object_init(exc); + GDMonoUtils::set_pending_exception((MonoException *)exc); + return NULL; + } + return GDMonoMarshal::variant_to_mono_object(ret); +} + +MonoObject *godot_icall_Dictionary_GetValue_Generic(Dictionary *ptr, MonoObject *key, uint32_t type_encoding, GDMonoClass *type_class) { + Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); + if (ret == NULL) { + MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr()); +#ifdef DEBUG_ENABLED + CRASH_COND(!exc); +#endif + GDMonoUtils::runtime_object_init(exc); + GDMonoUtils::set_pending_exception((MonoException *)exc); + return NULL; + } + return GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class)); +} + +void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value) { + ptr->operator[](GDMonoMarshal::mono_object_to_variant(key)) = GDMonoMarshal::mono_object_to_variant(value); +} + +Array *godot_icall_Dictionary_Keys(Dictionary *ptr) { + return memnew(Array(ptr->keys())); +} + +Array *godot_icall_Dictionary_Values(Dictionary *ptr) { + return memnew(Array(ptr->values())); +} + +int godot_icall_Dictionary_Count(Dictionary *ptr) { + return ptr->size(); +} + +void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value) { + Variant varKey = GDMonoMarshal::mono_object_to_variant(key); + Variant *ret = ptr->getptr(varKey); + if (ret != NULL) { + GDMonoUtils::set_pending_exception(mono_get_exception_argument("key", "An element with the same key already exists")); + return; + } + ptr->operator[](varKey) = GDMonoMarshal::mono_object_to_variant(value); +} + +void godot_icall_Dictionary_Clear(Dictionary *ptr) { + ptr->clear(); +} + +bool godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value) { + // no dupes + Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); + return ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value); +} + +bool godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key) { + return ptr->has(GDMonoMarshal::mono_object_to_variant(key)); +} + +bool godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key) { + return ptr->erase(GDMonoMarshal::mono_object_to_variant(key)); +} + +bool godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value) { + Variant varKey = GDMonoMarshal::mono_object_to_variant(key); + + // no dupes + Variant *ret = ptr->getptr(varKey); + if (ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value)) { + ptr->erase(varKey); + return true; + } + + return false; +} + +bool godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value) { + Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); + if (ret == NULL) { + *value = NULL; + return false; + } + *value = GDMonoMarshal::variant_to_mono_object(ret); + return true; +} + +bool godot_icall_Dictionary_TryGetValue_Generic(Dictionary *ptr, MonoObject *key, MonoObject **value, uint32_t type_encoding, GDMonoClass *type_class) { + Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); + if (ret == NULL) { + *value = NULL; + return false; + } + *value = GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class)); + return true; +} + +void godot_icall_Dictionary_Generic_GetValueTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class) { + MonoType *value_type = mono_reflection_type_get_type(refltype); + + *type_encoding = mono_type_get_type(value_type); + MonoClass *type_class_raw = mono_class_from_mono_type(value_type); + *type_class = GDMono::get_singleton()->get_class(type_class_raw); +} + +void godot_register_collections_icalls() { + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor", (void *)godot_icall_Array_Ctor); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Dtor", (void *)godot_icall_Array_Dtor); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At", (void *)godot_icall_Array_At); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At_Generic", (void *)godot_icall_Array_At_Generic); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_SetAt", (void *)godot_icall_Array_SetAt); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Count", (void *)godot_icall_Array_Count); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Add", (void *)godot_icall_Array_Add); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", (void *)godot_icall_Array_Clear); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", (void *)godot_icall_Array_Contains); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", (void *)godot_icall_Array_CopyTo); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_IndexOf", (void *)godot_icall_Array_IndexOf); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Insert", (void *)godot_icall_Array_Insert); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Remove", (void *)godot_icall_Array_Remove); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_RemoveAt", (void *)godot_icall_Array_RemoveAt); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Generic_GetElementTypeInfo", (void *)godot_icall_Array_Generic_GetElementTypeInfo); + + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Ctor", (void *)godot_icall_Dictionary_Ctor); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Dtor", (void *)godot_icall_Dictionary_Dtor); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue", (void *)godot_icall_Dictionary_GetValue); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue_Generic", (void *)godot_icall_Dictionary_GetValue_Generic); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_SetValue", (void *)godot_icall_Dictionary_SetValue); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Keys", (void *)godot_icall_Dictionary_Keys); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Values", (void *)godot_icall_Dictionary_Values); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Count", (void *)godot_icall_Dictionary_Count); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Add", (void *)godot_icall_Dictionary_Add); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Clear", (void *)godot_icall_Dictionary_Clear); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Contains", (void *)godot_icall_Dictionary_Contains); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ContainsKey", (void *)godot_icall_Dictionary_ContainsKey); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_RemoveKey", (void *)godot_icall_Dictionary_RemoveKey); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Remove", (void *)godot_icall_Dictionary_Remove); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue", (void *)godot_icall_Dictionary_TryGetValue); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue_Generic", (void *)godot_icall_Dictionary_TryGetValue_Generic); + mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Generic_GetValueTypeInfo", (void *)godot_icall_Dictionary_Generic_GetValueTypeInfo); +} + +#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/collections_glue.h b/modules/mono/glue/collections_glue.h new file mode 100644 index 0000000000..b9b1338510 --- /dev/null +++ b/modules/mono/glue/collections_glue.h @@ -0,0 +1,114 @@ +/*************************************************************************/ +/* collections_glue.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef COLLECTIONS_GLUE_H +#define COLLECTIONS_GLUE_H + +#ifdef MONO_GLUE_ENABLED + +#include "core/array.h" + +#include "../mono_gd/gd_mono_marshal.h" + +// Array + +Array *godot_icall_Array_Ctor(); + +void godot_icall_Array_Dtor(Array *ptr); + +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); + +void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value); + +int godot_icall_Array_Count(Array *ptr); + +void godot_icall_Array_Add(Array *ptr, MonoObject *item); + +void godot_icall_Array_Clear(Array *ptr); + +bool godot_icall_Array_Contains(Array *ptr, MonoObject *item); + +void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index); + +int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item); + +void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item); + +bool godot_icall_Array_Remove(Array *ptr, MonoObject *item); + +void godot_icall_Array_RemoveAt(Array *ptr, int index); + +void godot_icall_Array_Generic_GetElementTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class); + +// Dictionary + +Dictionary *godot_icall_Dictionary_Ctor(); + +void godot_icall_Dictionary_Dtor(Dictionary *ptr); + +MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key); + +MonoObject *godot_icall_Dictionary_GetValue_Generic(Dictionary *ptr, MonoObject *key, uint32_t type_encoding, GDMonoClass *type_class); + +void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value); + +Array *godot_icall_Dictionary_Keys(Dictionary *ptr); + +Array *godot_icall_Dictionary_Values(Dictionary *ptr); + +int godot_icall_Dictionary_Count(Dictionary *ptr); + +void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value); + +void godot_icall_Dictionary_Clear(Dictionary *ptr); + +bool godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value); + +bool godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key); + +bool godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key); + +bool godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value); + +bool godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value); + +bool godot_icall_Dictionary_TryGetValue_Generic(Dictionary *ptr, MonoObject *key, MonoObject **value, uint32_t type_encoding, GDMonoClass *type_class); + +void godot_icall_Dictionary_Generic_GetValueTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class); + +// Register internal calls + +void godot_register_collections_icalls(); + +#endif // MONO_GLUE_ENABLED + +#endif // COLLECTIONS_GLUE_H diff --git a/modules/mono/glue/cs_files/ExportAttribute.cs b/modules/mono/glue/cs_files/ExportAttribute.cs deleted file mode 100644 index e6f569e1bb..0000000000 --- a/modules/mono/glue/cs_files/ExportAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Godot -{ - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public class ExportAttribute : Attribute - { - private PropertyHint hint; - private string hintString; - - public ExportAttribute(PropertyHint hint = PropertyHint.None, string hintString = "") - { - this.hint = hint; - this.hintString = hintString; - } - } -} diff --git a/modules/mono/glue/cs_files/GD.cs b/modules/mono/glue/cs_files/GD.cs deleted file mode 100644 index ec1534cb9a..0000000000 --- a/modules/mono/glue/cs_files/GD.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; - -// TODO: Add comments describing what this class does. It is not obvious. - -namespace Godot -{ - public static partial class GD - { - public static object Bytes2Var(byte[] bytes) - { - return NativeCalls.godot_icall_Godot_bytes2var(bytes); - } - - public static object Convert(object what, int type) - { - return NativeCalls.godot_icall_Godot_convert(what, type); - } - - public static float Db2Linear(float db) - { - return (float)Math.Exp(db * 0.11512925464970228420089957273422); - } - - public static float Dectime(float value, float amount, float step) - { - float sgn = value < 0 ? -1.0f : 1.0f; - float val = Mathf.Abs(value); - val -= amount * step; - if (val < 0.0f) - val = 0.0f; - return val * sgn; - } - - public static FuncRef Funcref(Object instance, string funcname) - { - var ret = new FuncRef(); - ret.SetInstance(instance); - ret.SetFunction(funcname); - return ret; - } - - public static int Hash(object var) - { - return NativeCalls.godot_icall_Godot_hash(var); - } - - public static Object InstanceFromId(int instanceId) - { - return NativeCalls.godot_icall_Godot_instance_from_id(instanceId); - } - - public static double Linear2Db(double linear) - { - return Math.Log(linear) * 8.6858896380650365530225783783321; - } - - public static Resource Load(string path) - { - return ResourceLoader.Load(path); - } - - public static void Print(params object[] what) - { - NativeCalls.godot_icall_Godot_print(what); - } - - public static void PrintStack() - { - Print(System.Environment.StackTrace); - } - - public static void Printerr(params object[] what) - { - NativeCalls.godot_icall_Godot_printerr(what); - } - - public static void Printraw(params object[] what) - { - NativeCalls.godot_icall_Godot_printraw(what); - } - - public static void Prints(params object[] what) - { - NativeCalls.godot_icall_Godot_prints(what); - } - - public static void Printt(params object[] what) - { - NativeCalls.godot_icall_Godot_printt(what); - } - - public static int[] Range(int length) - { - var ret = new int[length]; - - for (int i = 0; i < length; i++) - { - ret[i] = i; - } - - return ret; - } - - public static int[] Range(int from, int to) - { - if (to < from) - return new int[0]; - - var ret = new int[to - from]; - - for (int i = from; i < to; i++) - { - ret[i - from] = i; - } - - return ret; - } - - public static int[] Range(int from, int to, int increment) - { - if (to < from && increment > 0) - return new int[0]; - if (to > from && increment < 0) - return new int[0]; - - // Calculate count - int count; - - if (increment > 0) - count = (to - from - 1) / increment + 1; - else - count = (from - to - 1) / -increment + 1; - - var ret = new int[count]; - - if (increment > 0) - { - int idx = 0; - for (int i = from; i < to; i += increment) - { - ret[idx++] = i; - } - } - else - { - int idx = 0; - for (int i = from; i > to; i += increment) - { - ret[idx++] = i; - } - } - - return ret; - } - - public static void Seed(int seed) - { - NativeCalls.godot_icall_Godot_seed(seed); - } - - public static string Str(params object[] what) - { - return NativeCalls.godot_icall_Godot_str(what); - } - - public static object Str2Var(string str) - { - return NativeCalls.godot_icall_Godot_str2var(str); - } - - public static bool TypeExists(string type) - { - return NativeCalls.godot_icall_Godot_type_exists(type); - } - - public static byte[] Var2Bytes(object var) - { - return NativeCalls.godot_icall_Godot_var2bytes(var); - } - - public static string Var2Str(object var) - { - return NativeCalls.godot_icall_Godot_var2str(var); - } - - public static WeakRef Weakref(Object obj) - { - return NativeCalls.godot_icall_Godot_weakref(Object.GetPtr(obj)); - } - } -} diff --git a/modules/mono/glue/cs_files/GodotSynchronizationContext.cs b/modules/mono/glue/cs_files/GodotSynchronizationContext.cs deleted file mode 100644 index da3c7bac83..0000000000 --- a/modules/mono/glue/cs_files/GodotSynchronizationContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; - -namespace Godot -{ - public class GodotSynchronizationContext : SynchronizationContext - { - private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); - - public override void Post(SendOrPostCallback d, object state) - { - queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); - } - - public void ExecutePendingContinuations() - { - KeyValuePair<SendOrPostCallback, object> workItem; - while (queue.TryTake(out workItem)) - { - workItem.Key(workItem.Value); - } - } - } -} diff --git a/modules/mono/glue/cs_files/GodotTaskScheduler.cs b/modules/mono/glue/cs_files/GodotTaskScheduler.cs deleted file mode 100644 index 3d23ec10f1..0000000000 --- a/modules/mono/glue/cs_files/GodotTaskScheduler.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Godot -{ - public class GodotTaskScheduler : TaskScheduler - { - private GodotSynchronizationContext Context { get; set; } - private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); - - public GodotTaskScheduler() - { - Context = new GodotSynchronizationContext(); - SynchronizationContext.SetSynchronizationContext(Context); - } - - protected sealed override void QueueTask(Task task) - { - lock (_tasks) - { - _tasks.AddLast(task); - } - } - - protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) - { - if (SynchronizationContext.Current != Context) - { - return false; - } - - if (taskWasPreviouslyQueued) - { - TryDequeue(task); - } - - return TryExecuteTask(task); - } - - protected sealed override bool TryDequeue(Task task) - { - lock (_tasks) - { - return _tasks.Remove(task); - } - } - - protected sealed override IEnumerable<Task> GetScheduledTasks() - { - lock (_tasks) - { - return _tasks.ToArray(); - } - } - - public void Activate() - { - ExecuteQueuedTasks(); - Context.ExecutePendingContinuations(); - } - - private void ExecuteQueuedTasks() - { - while (true) - { - Task task; - - lock (_tasks) - { - if (_tasks.Any()) - { - task = _tasks.First.Value; - _tasks.RemoveFirst(); - } - else - { - break; - } - } - - if (task != null) - { - if (!TryExecuteTask(task)) - { - throw new InvalidOperationException(); - } - } - } - } - } -} diff --git a/modules/mono/glue/cs_files/IAwaiter.cs b/modules/mono/glue/cs_files/IAwaiter.cs deleted file mode 100644 index b5aa1a5389..0000000000 --- a/modules/mono/glue/cs_files/IAwaiter.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Godot -{ - public interface IAwaiter : INotifyCompletion - { - bool IsCompleted { get; } - - void GetResult(); - } - - public interface IAwaiter<out TResult> : INotifyCompletion - { - bool IsCompleted { get; } - - TResult GetResult(); - } -} diff --git a/modules/mono/glue/cs_files/MarshalUtils.cs b/modules/mono/glue/cs_files/MarshalUtils.cs deleted file mode 100644 index ff4477cc6c..0000000000 --- a/modules/mono/glue/cs_files/MarshalUtils.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Godot -{ - internal static class MarshalUtils - { - private static Dictionary<object, object> ArraysToDictionary(object[] keys, object[] values) - { - var ret = new Dictionary<object, object>(); - - for (int i = 0; i < keys.Length; i++) - { - ret.Add(keys[i], values[i]); - } - - return ret; - } - - private static void DictionaryToArrays(Dictionary<object, object> from, out object[] keysTo, out object[] valuesTo) - { - var keys = from.Keys; - keysTo = new object[keys.Count]; - keys.CopyTo(keysTo, 0); - - var values = from.Values; - valuesTo = new object[values.Count]; - values.CopyTo(valuesTo, 0); - } - - private static Type GetDictionaryType() - { - return typeof(Dictionary<object, object>); - } - } -} diff --git a/modules/mono/glue/cs_files/ToolAttribute.cs b/modules/mono/glue/cs_files/ToolAttribute.cs deleted file mode 100644 index d8601b5b32..0000000000 --- a/modules/mono/glue/cs_files/ToolAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace Godot -{ - [AttributeUsage(AttributeTargets.Class)] - public class ToolAttribute : Attribute {} -} diff --git a/modules/mono/glue/cs_files/VERSION.txt b/modules/mono/glue/cs_files/VERSION.txt deleted file mode 100755 index b8626c4cff..0000000000 --- a/modules/mono/glue/cs_files/VERSION.txt +++ /dev/null @@ -1 +0,0 @@ -4 diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp new file mode 100644 index 0000000000..9f5bcecdd9 --- /dev/null +++ b/modules/mono/glue/gd_glue.cpp @@ -0,0 +1,212 @@ +/*************************************************************************/ +/* gd_glue.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 "gd_glue.h" + +#ifdef MONO_GLUE_ENABLED + +#include "core/array.h" +#include "core/io/marshalls.h" +#include "core/os/os.h" +#include "core/ustring.h" +#include "core/variant.h" +#include "core/variant_parser.h" + +#include "../mono_gd/gd_mono_utils.h" + +MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes) { + Variant ret; + PoolByteArray varr = GDMonoMarshal::mono_array_to_PoolByteArray(p_bytes); + PoolByteArray::Read r = varr.read(); + Error err = decode_variant(ret, r.ptr(), varr.size(), NULL); + if (err != OK) { + ret = RTR("Not enough bytes for decoding bytes, or invalid format."); + } + return GDMonoMarshal::variant_to_mono_object(ret); +} + +MonoObject *godot_icall_GD_convert(MonoObject *p_what, int p_type) { + Variant what = GDMonoMarshal::mono_object_to_variant(p_what); + const Variant *args[1] = { &what }; + Variant::CallError ce; + Variant ret = Variant::construct(Variant::Type(p_type), args, 1, ce); + ERR_FAIL_COND_V(ce.error != Variant::CallError::CALL_OK, NULL); + return GDMonoMarshal::variant_to_mono_object(ret); +} + +int godot_icall_GD_hash(MonoObject *p_var) { + return GDMonoMarshal::mono_object_to_variant(p_var).hash(); +} + +MonoObject *godot_icall_GD_instance_from_id(int p_instance_id) { + return GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(p_instance_id)); +} + +void godot_icall_GD_print(MonoArray *p_what) { + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + String str; + for (int i = 0; i < what.size(); i++) + str += what[i].operator String(); + print_line(str); +} + +void godot_icall_GD_printerr(MonoArray *p_what) { + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + String str; + for (int i = 0; i < what.size(); i++) + str += what[i].operator String(); + OS::get_singleton()->printerr("%s\n", str.utf8().get_data()); +} + +void godot_icall_GD_printraw(MonoArray *p_what) { + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + String str; + for (int i = 0; i < what.size(); i++) + str += what[i].operator String(); + OS::get_singleton()->print("%s", str.utf8().get_data()); +} + +void godot_icall_GD_prints(MonoArray *p_what) { + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + String str; + for (int i = 0; i < what.size(); i++) { + if (i) + str += " "; + str += what[i].operator String(); + } + print_line(str); +} + +void godot_icall_GD_printt(MonoArray *p_what) { + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + String str; + for (int i = 0; i < what.size(); i++) { + if (i) + str += "\t"; + str += what[i].operator String(); + } + print_line(str); +} + +void godot_icall_GD_seed(int p_seed) { + Math::seed(p_seed); +} + +MonoString *godot_icall_GD_str(MonoArray *p_what) { + String str; + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + + for (int i = 0; i < what.size(); i++) { + String os = what[i].operator String(); + + if (i == 0) + str = os; + else + str += os; + } + + return GDMonoMarshal::mono_string_from_godot(str); +} + +MonoObject *godot_icall_GD_str2var(MonoString *p_str) { + Variant ret; + + VariantParser::StreamString ss; + ss.s = GDMonoMarshal::mono_string_to_godot(p_str); + + String errs; + int line; + Error err = VariantParser::parse(&ss, ret, errs, line); + if (err != OK) { + String err_str = "Parse error at line " + itos(line) + ": " + errs; + ERR_PRINTS(err_str); + ret = err_str; + } + + return GDMonoMarshal::variant_to_mono_object(ret); +} + +bool godot_icall_GD_type_exists(MonoString *p_type) { + return ClassDB::class_exists(GDMonoMarshal::mono_string_to_godot(p_type)); +} + +void godot_icall_GD_pusherror(MonoString *p_str) { + ERR_PRINTS(GDMonoMarshal::mono_string_to_godot(p_str)); +} + +void godot_icall_GD_pushwarning(MonoString *p_str) { + WARN_PRINTS(GDMonoMarshal::mono_string_to_godot(p_str)); +} + +MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var) { + Variant var = GDMonoMarshal::mono_object_to_variant(p_var); + + PoolByteArray barr; + int len; + Error err = encode_variant(var, NULL, len); + ERR_EXPLAIN("Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); + ERR_FAIL_COND_V(err != OK, NULL); + + barr.resize(len); + { + PoolByteArray::Write w = barr.write(); + encode_variant(var, w.ptr(), len); + } + + return GDMonoMarshal::PoolByteArray_to_mono_array(barr); +} + +MonoString *godot_icall_GD_var2str(MonoObject *p_var) { + String vars; + VariantWriter::write_to_string(GDMonoMarshal::mono_object_to_variant(p_var), vars); + return GDMonoMarshal::mono_string_from_godot(vars); +} + +void godot_register_gd_icalls() { + mono_add_internal_call("Godot.GD::godot_icall_GD_bytes2var", (void *)godot_icall_GD_bytes2var); + mono_add_internal_call("Godot.GD::godot_icall_GD_convert", (void *)godot_icall_GD_convert); + mono_add_internal_call("Godot.GD::godot_icall_GD_hash", (void *)godot_icall_GD_hash); + mono_add_internal_call("Godot.GD::godot_icall_GD_instance_from_id", (void *)godot_icall_GD_instance_from_id); + mono_add_internal_call("Godot.GD::godot_icall_GD_pusherror", (void *)godot_icall_GD_pusherror); + mono_add_internal_call("Godot.GD::godot_icall_GD_pushwarning", (void *)godot_icall_GD_pushwarning); + mono_add_internal_call("Godot.GD::godot_icall_GD_print", (void *)godot_icall_GD_print); + mono_add_internal_call("Godot.GD::godot_icall_GD_printerr", (void *)godot_icall_GD_printerr); + mono_add_internal_call("Godot.GD::godot_icall_GD_printraw", (void *)godot_icall_GD_printraw); + mono_add_internal_call("Godot.GD::godot_icall_GD_prints", (void *)godot_icall_GD_prints); + mono_add_internal_call("Godot.GD::godot_icall_GD_printt", (void *)godot_icall_GD_printt); + mono_add_internal_call("Godot.GD::godot_icall_GD_seed", (void *)godot_icall_GD_seed); + mono_add_internal_call("Godot.GD::godot_icall_GD_str", (void *)godot_icall_GD_str); + mono_add_internal_call("Godot.GD::godot_icall_GD_str2var", (void *)godot_icall_GD_str2var); + mono_add_internal_call("Godot.GD::godot_icall_GD_type_exists", (void *)godot_icall_GD_type_exists); + mono_add_internal_call("Godot.GD::godot_icall_GD_var2bytes", (void *)godot_icall_GD_var2bytes); + mono_add_internal_call("Godot.GD::godot_icall_GD_var2str", (void *)godot_icall_GD_var2str); +} + +#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/gd_glue.h b/modules/mono/glue/gd_glue.h new file mode 100644 index 0000000000..6f846f221d --- /dev/null +++ b/modules/mono/glue/gd_glue.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* gd_glue.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef GD_GLUE_H +#define GD_GLUE_H + +#ifdef MONO_GLUE_ENABLED + +#include "../mono_gd/gd_mono_marshal.h" + +MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes); + +MonoObject *godot_icall_GD_convert(MonoObject *p_what, int p_type); + +int godot_icall_GD_hash(MonoObject *p_var); + +MonoObject *godot_icall_GD_instance_from_id(int p_instance_id); + +void godot_icall_GD_print(MonoArray *p_what); + +void godot_icall_GD_printerr(MonoArray *p_what); + +void godot_icall_GD_printraw(MonoArray *p_what); + +void godot_icall_GD_prints(MonoArray *p_what); + +void godot_icall_GD_printt(MonoArray *p_what); + +void godot_icall_GD_seed(int p_seed); + +MonoString *godot_icall_GD_str(MonoArray *p_what); + +MonoObject *godot_icall_GD_str2var(MonoString *p_str); + +bool godot_icall_GD_type_exists(MonoString *p_type); + +MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var); + +MonoString *godot_icall_GD_var2str(MonoObject *p_var); + +// Register internal calls + +void godot_register_gd_icalls(); + +#endif // MONO_GLUE_ENABLED + +#endif // GD_GLUE_H diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h index cedc8e9992..69c5c6dcdb 100644 --- a/modules/mono/glue/glue_header.h +++ b/modules/mono/glue/glue_header.h @@ -28,26 +28,44 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "builtin_types_glue.h" +#ifdef MONO_GLUE_ENABLED + +#include "base_object_glue.h" +#include "collections_glue.h" +#include "gd_glue.h" +#include "nodepath_glue.h" +#include "rid_glue.h" +#include "string_glue.h" + +/** + * Registers internal calls that were not generated. This function is called + * from the generated GodotSharpBindings::register_generated_icalls() function. + */ +void godot_register_glue_header_icalls() { + godot_register_collections_icalls(); + godot_register_gd_icalls(); + godot_register_nodepath_icalls(); + godot_register_object_icalls(); + godot_register_rid_icalls(); + godot_register_string_icalls(); +} + +// Used by the generated glue + +#include "core/array.h" +#include "core/class_db.h" +#include "core/dictionary.h" +#include "core/engine.h" +#include "core/method_bind.h" +#include "core/node_path.h" +#include "core/object.h" +#include "core/reference.h" +#include "core/typedefs.h" +#include "core/ustring.h" -#include "../csharp_script.h" #include "../mono_gd/gd_mono_class.h" #include "../mono_gd/gd_mono_internals.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../signal_awaiter_utils.h" - -#include "bind/core_bind.h" -#include "class_db.h" -#include "engine.h" -#include "io/marshalls.h" -#include "object.h" -#include "os/os.h" -#include "reference.h" -#include "variant_parser.h" - -#ifdef TOOLS_ENABLED -#include "editor/editor_node.h" -#endif +#include "../mono_gd/gd_mono_utils.h" #define GODOTSHARP_INSTANCE_OBJECT(m_instance, m_type) \ static ClassDB::ClassInfo *ci = NULL; \ @@ -56,256 +74,4 @@ } \ Object *m_instance = ci->creation_func(); -void godot_icall_Object_Dtor(MonoObject *obj, Object *ptr) { -#ifdef DEBUG_ENABLED - CRASH_COND(ptr == NULL); -#endif - _GodotSharp::get_singleton()->queue_dispose(obj, ptr); -} - -// -- ClassDB -- - -MethodBind *godot_icall_ClassDB_get_method(MonoString *p_type, MonoString *p_method) { - StringName type(GDMonoMarshal::mono_string_to_godot(p_type)); - StringName method(GDMonoMarshal::mono_string_to_godot(p_method)); - return ClassDB::get_method(type, method); -} - -// -- SignalAwaiter -- - -Error godot_icall_Object_connect_signal_awaiter(Object *p_source, MonoString *p_signal, Object *p_target, MonoObject *p_awaiter) { - String signal = GDMonoMarshal::mono_string_to_godot(p_signal); - return SignalAwaiterUtils::connect_signal_awaiter(p_source, signal, p_target, p_awaiter); -} - -// -- NodePath -- - -NodePath *godot_icall_NodePath_Ctor(MonoString *p_path) { - return memnew(NodePath(GDMonoMarshal::mono_string_to_godot(p_path))); -} - -void godot_icall_NodePath_Dtor(NodePath *p_ptr) { - ERR_FAIL_NULL(p_ptr); - _GodotSharp::get_singleton()->queue_dispose(p_ptr); -} - -MonoString *godot_icall_NodePath_operator_String(NodePath *p_np) { - return GDMonoMarshal::mono_string_from_godot(p_np->operator String()); -} - -// -- RID -- - -RID *godot_icall_RID_Ctor(Object *p_from) { - Resource *res_from = Object::cast_to<Resource>(p_from); - - if (res_from) - return memnew(RID(res_from->get_rid())); - - return memnew(RID); -} - -void godot_icall_RID_Dtor(RID *p_ptr) { - ERR_FAIL_NULL(p_ptr); - _GodotSharp::get_singleton()->queue_dispose(p_ptr); -} - -// -- String -- - -MonoArray *godot_icall_String_md5_buffer(MonoString *p_str) { - Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_buffer(); - // TODO Check possible Array/Vector<uint8_t> problem? - return GDMonoMarshal::Array_to_mono_array(Variant(ret)); -} - -MonoString *godot_icall_String_md5_text(MonoString *p_str) { - String ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_text(); - return GDMonoMarshal::mono_string_from_godot(ret); -} - -int godot_icall_String_rfind(MonoString *p_str, MonoString *p_what, int p_from) { - String what = GDMonoMarshal::mono_string_to_godot(p_what); - return GDMonoMarshal::mono_string_to_godot(p_str).rfind(what, p_from); -} - -int godot_icall_String_rfindn(MonoString *p_str, MonoString *p_what, int p_from) { - String what = GDMonoMarshal::mono_string_to_godot(p_what); - return GDMonoMarshal::mono_string_to_godot(p_str).rfindn(what, p_from); -} - -MonoArray *godot_icall_String_sha256_buffer(MonoString *p_str) { - Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).sha256_buffer(); - return GDMonoMarshal::Array_to_mono_array(Variant(ret)); -} - -MonoString *godot_icall_String_sha256_text(MonoString *p_str) { - String ret = GDMonoMarshal::mono_string_to_godot(p_str).sha256_text(); - return GDMonoMarshal::mono_string_from_godot(ret); -} - -// -- Global Scope -- - -MonoObject *godot_icall_Godot_bytes2var(MonoArray *p_bytes) { - Variant ret; - PoolByteArray varr = GDMonoMarshal::mono_array_to_PoolByteArray(p_bytes); - PoolByteArray::Read r = varr.read(); - Error err = decode_variant(ret, r.ptr(), varr.size(), NULL); - if (err != OK) { - ret = RTR("Not enough bytes for decoding bytes, or invalid format."); - } - return GDMonoMarshal::variant_to_mono_object(ret); -} - -MonoObject *godot_icall_Godot_convert(MonoObject *p_what, int p_type) { - Variant what = GDMonoMarshal::mono_object_to_variant(p_what); - const Variant *args[1] = { &what }; - Variant::CallError ce; - Variant ret = Variant::construct(Variant::Type(p_type), args, 1, ce); - ERR_FAIL_COND_V(ce.error != Variant::CallError::CALL_OK, NULL); - return GDMonoMarshal::variant_to_mono_object(ret); -} - -int godot_icall_Godot_hash(MonoObject *p_var) { - return GDMonoMarshal::mono_object_to_variant(p_var).hash(); -} - -MonoObject *godot_icall_Godot_instance_from_id(int p_instance_id) { - return GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(p_instance_id)); -} - -void godot_icall_Godot_print(MonoArray *p_what) { - Array what = GDMonoMarshal::mono_array_to_Array(p_what); - String str; - for (int i = 0; i < what.size(); i++) - str += what[i].operator String(); - print_line(str); -} - -void godot_icall_Godot_printerr(MonoArray *p_what) { - Array what = GDMonoMarshal::mono_array_to_Array(p_what); - String str; - for (int i = 0; i < what.size(); i++) - str += what[i].operator String(); - OS::get_singleton()->printerr("%s\n", str.utf8().get_data()); -} - -void godot_icall_Godot_printraw(MonoArray *p_what) { - Array what = GDMonoMarshal::mono_array_to_Array(p_what); - String str; - for (int i = 0; i < what.size(); i++) - str += what[i].operator String(); - OS::get_singleton()->print("%s", str.utf8().get_data()); -} - -void godot_icall_Godot_prints(MonoArray *p_what) { - Array what = GDMonoMarshal::mono_array_to_Array(p_what); - String str; - for (int i = 0; i < what.size(); i++) { - if (i) - str += " "; - str += what[i].operator String(); - } - print_line(str); -} - -void godot_icall_Godot_printt(MonoArray *p_what) { - Array what = GDMonoMarshal::mono_array_to_Array(p_what); - String str; - for (int i = 0; i < what.size(); i++) { - if (i) - str += "\t"; - str += what[i].operator String(); - } - print_line(str); -} - -void godot_icall_Godot_seed(int p_seed) { - Math::seed(p_seed); -} - -MonoString *godot_icall_Godot_str(MonoArray *p_what) { - String str; - Array what = GDMonoMarshal::mono_array_to_Array(p_what); - - for (int i = 0; i < what.size(); i++) { - String os = what[i].operator String(); - - if (i == 0) - str = os; - else - str += os; - } - - return GDMonoMarshal::mono_string_from_godot(str); -} - -MonoObject *godot_icall_Godot_str2var(MonoString *p_str) { - Variant ret; - - VariantParser::StreamString ss; - ss.s = GDMonoMarshal::mono_string_to_godot(p_str); - - String errs; - int line; - Error err = VariantParser::parse(&ss, ret, errs, line); - if (err != OK) { - String err_str = "Parse error at line " + itos(line) + ": " + errs; - ERR_PRINTS(err_str); - ret = err_str; - } - - return GDMonoMarshal::variant_to_mono_object(ret); -} - -bool godot_icall_Godot_type_exists(MonoString *p_type) { - return ClassDB::class_exists(GDMonoMarshal::mono_string_to_godot(p_type)); -} - -MonoArray *godot_icall_Godot_var2bytes(MonoObject *p_var) { - Variant var = GDMonoMarshal::mono_object_to_variant(p_var); - - PoolByteArray barr; - int len; - Error err = encode_variant(var, NULL, len); - ERR_EXPLAIN("Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); - ERR_FAIL_COND_V(err != OK, NULL); - - barr.resize(len); - { - PoolByteArray::Write w = barr.write(); - encode_variant(var, w.ptr(), len); - } - - return GDMonoMarshal::PoolByteArray_to_mono_array(barr); -} - -MonoString *godot_icall_Godot_var2str(MonoObject *p_var) { - String vars; - VariantWriter::write_to_string(GDMonoMarshal::mono_object_to_variant(p_var), vars); - return GDMonoMarshal::mono_string_from_godot(vars); -} - -MonoObject *godot_icall_Godot_weakref(Object *p_obj) { - if (!p_obj) - return NULL; - - Ref<WeakRef> wref; - Reference *ref = Object::cast_to<Reference>(p_obj); - - if (ref) { - REF r = ref; - if (!r.is_valid()) - return NULL; - - wref.instance(); - wref->set_ref(r); - } else { - wref.instance(); - wref->set_obj(p_obj); - } - - return GDMonoUtils::create_managed_for_godot_object(CACHED_CLASS(WeakRef), Reference::get_class_static(), Object::cast_to<Object>(wref.ptr())); -} - -void godot_register_header_icalls() { - godot_register_builtin_type_icalls(); -} +#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/builtin_types_glue.h b/modules/mono/glue/nodepath_glue.cpp index ef9f152682..422d73104d 100644 --- a/modules/mono/glue/builtin_types_glue.h +++ b/modules/mono/glue/nodepath_glue.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* builtin_types_glue.h */ +/* nodepath_glue.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,15 +28,24 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef BUILTIN_TYPES_GLUE_H -#define BUILTIN_TYPES_GLUE_H +#include "nodepath_glue.h" -#include "core/node_path.h" -#include "core/rid.h" +#ifdef MONO_GLUE_ENABLED -#include <mono/metadata/object.h> +#include "core/ustring.h" -#include "../mono_gd/gd_mono_marshal.h" +NodePath *godot_icall_NodePath_Ctor(MonoString *p_path) { + return memnew(NodePath(GDMonoMarshal::mono_string_to_godot(p_path))); +} + +void godot_icall_NodePath_Dtor(NodePath *p_ptr) { + ERR_FAIL_NULL(p_ptr); + memdelete(p_ptr); +} + +MonoString *godot_icall_NodePath_operator_String(NodePath *p_np) { + return GDMonoMarshal::mono_string_from_godot(p_np->operator String()); +} MonoBoolean godot_icall_NodePath_is_absolute(NodePath *p_ptr) { return (MonoBoolean)p_ptr->is_absolute(); @@ -70,20 +79,18 @@ MonoBoolean godot_icall_NodePath_is_empty(NodePath *p_ptr) { return (MonoBoolean)p_ptr->is_empty(); } -uint32_t godot_icall_RID_get_id(RID *p_ptr) { - return p_ptr->get_id(); -} - -void godot_register_builtin_type_icalls() { - mono_add_internal_call("Godot.NativeCalls::godot_icall_NodePath_get_as_property_path", (void *)godot_icall_NodePath_get_as_property_path); - mono_add_internal_call("Godot.NativeCalls::godot_icall_NodePath_get_concatenated_subnames", (void *)godot_icall_NodePath_get_concatenated_subnames); - mono_add_internal_call("Godot.NativeCalls::godot_icall_NodePath_get_name", (void *)godot_icall_NodePath_get_name); - mono_add_internal_call("Godot.NativeCalls::godot_icall_NodePath_get_name_count", (void *)godot_icall_NodePath_get_name_count); - mono_add_internal_call("Godot.NativeCalls::godot_icall_NodePath_get_subname", (void *)godot_icall_NodePath_get_subname); - mono_add_internal_call("Godot.NativeCalls::godot_icall_NodePath_get_subname_count", (void *)godot_icall_NodePath_get_subname_count); - mono_add_internal_call("Godot.NativeCalls::godot_icall_NodePath_is_absolute", (void *)godot_icall_NodePath_is_absolute); - mono_add_internal_call("Godot.NativeCalls::godot_icall_NodePath_is_empty", (void *)godot_icall_NodePath_is_empty); - mono_add_internal_call("Godot.NativeCalls::godot_icall_RID_get_id", (void *)godot_icall_RID_get_id); +void godot_register_nodepath_icalls() { + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_Ctor", (void *)godot_icall_NodePath_Ctor); + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_Dtor", (void *)godot_icall_NodePath_Dtor); + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_operator_String", (void *)godot_icall_NodePath_operator_String); + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_as_property_path", (void *)godot_icall_NodePath_get_as_property_path); + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_subnames", (void *)godot_icall_NodePath_get_concatenated_subnames); + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name", (void *)godot_icall_NodePath_get_name); + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name_count", (void *)godot_icall_NodePath_get_name_count); + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname", (void *)godot_icall_NodePath_get_subname); + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname_count", (void *)godot_icall_NodePath_get_subname_count); + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_is_absolute", (void *)godot_icall_NodePath_is_absolute); + mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_is_empty", (void *)godot_icall_NodePath_is_empty); } -#endif // BUILTIN_TYPES_GLUE_H +#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/nodepath_glue.h b/modules/mono/glue/nodepath_glue.h new file mode 100644 index 0000000000..92579399a6 --- /dev/null +++ b/modules/mono/glue/nodepath_glue.h @@ -0,0 +1,68 @@ +/*************************************************************************/ +/* nodepath_glue.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef NODEPATH_GLUE_H +#define NODEPATH_GLUE_H + +#ifdef MONO_GLUE_ENABLED + +#include "core/node_path.h" + +#include "../mono_gd/gd_mono_marshal.h" + +NodePath *godot_icall_NodePath_Ctor(MonoString *p_path); + +void godot_icall_NodePath_Dtor(NodePath *p_ptr); + +MonoString *godot_icall_NodePath_operator_String(NodePath *p_np); + +MonoBoolean godot_icall_NodePath_is_absolute(NodePath *p_ptr); + +uint32_t godot_icall_NodePath_get_name_count(NodePath *p_ptr); + +MonoString *godot_icall_NodePath_get_name(NodePath *p_ptr, uint32_t p_idx); + +uint32_t godot_icall_NodePath_get_subname_count(NodePath *p_ptr); + +MonoString *godot_icall_NodePath_get_subname(NodePath *p_ptr, uint32_t p_idx); + +MonoString *godot_icall_NodePath_get_concatenated_subnames(NodePath *p_ptr); + +NodePath *godot_icall_NodePath_get_as_property_path(NodePath *p_ptr); + +MonoBoolean godot_icall_NodePath_is_empty(NodePath *p_ptr); + +// Register internal calls + +void godot_register_nodepath_icalls(); + +#endif // MONO_GLUE_ENABLED + +#endif // NODEPATH_GLUE_H diff --git a/modules/mono/glue/rid_glue.cpp b/modules/mono/glue/rid_glue.cpp new file mode 100644 index 0000000000..6c002b5b9d --- /dev/null +++ b/modules/mono/glue/rid_glue.cpp @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* rid_glue.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 "rid_glue.h" + +#ifdef MONO_GLUE_ENABLED + +#include "core/resource.h" + +RID *godot_icall_RID_Ctor(Object *p_from) { + Resource *res_from = Object::cast_to<Resource>(p_from); + + if (res_from) + return memnew(RID(res_from->get_rid())); + + return memnew(RID); +} + +void godot_icall_RID_Dtor(RID *p_ptr) { + ERR_FAIL_NULL(p_ptr); + memdelete(p_ptr); +} + +uint32_t godot_icall_RID_get_id(RID *p_ptr) { + return p_ptr->get_id(); +} + +void godot_register_rid_icalls() { + mono_add_internal_call("Godot.RID::godot_icall_RID_Ctor", (void *)godot_icall_RID_Ctor); + mono_add_internal_call("Godot.RID::godot_icall_RID_Dtor", (void *)godot_icall_RID_Dtor); + mono_add_internal_call("Godot.RID::godot_icall_RID_get_id", (void *)godot_icall_RID_get_id); +} + +#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/rid_glue.h b/modules/mono/glue/rid_glue.h new file mode 100644 index 0000000000..c725a9b5de --- /dev/null +++ b/modules/mono/glue/rid_glue.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* rid_glue.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef RID_GLUE_H +#define RID_GLUE_H + +#ifdef MONO_GLUE_ENABLED + +#include "core/object.h" +#include "core/rid.h" + +#include "../mono_gd/gd_mono_marshal.h" + +RID *godot_icall_RID_Ctor(Object *p_from); + +void godot_icall_RID_Dtor(RID *p_ptr); + +uint32_t godot_icall_RID_get_id(RID *p_ptr); + +// Register internal calls + +void godot_register_rid_icalls(); + +#endif // MONO_GLUE_ENABLED + +#endif // RID_GLUE_H diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp new file mode 100644 index 0000000000..e1a2f1affd --- /dev/null +++ b/modules/mono/glue/string_glue.cpp @@ -0,0 +1,79 @@ +/*************************************************************************/ +/* string_glue.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 "string_glue.h" + +#ifdef MONO_GLUE_ENABLED + +#include "core/ustring.h" +#include "core/variant.h" +#include "core/vector.h" + +MonoArray *godot_icall_String_md5_buffer(MonoString *p_str) { + Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_buffer(); + // TODO Check possible Array/Vector<uint8_t> problem? + return GDMonoMarshal::Array_to_mono_array(Variant(ret)); +} + +MonoString *godot_icall_String_md5_text(MonoString *p_str) { + String ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_text(); + return GDMonoMarshal::mono_string_from_godot(ret); +} + +int godot_icall_String_rfind(MonoString *p_str, MonoString *p_what, int p_from) { + String what = GDMonoMarshal::mono_string_to_godot(p_what); + return GDMonoMarshal::mono_string_to_godot(p_str).rfind(what, p_from); +} + +int godot_icall_String_rfindn(MonoString *p_str, MonoString *p_what, int p_from) { + String what = GDMonoMarshal::mono_string_to_godot(p_what); + return GDMonoMarshal::mono_string_to_godot(p_str).rfindn(what, p_from); +} + +MonoArray *godot_icall_String_sha256_buffer(MonoString *p_str) { + Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).sha256_buffer(); + return GDMonoMarshal::Array_to_mono_array(Variant(ret)); +} + +MonoString *godot_icall_String_sha256_text(MonoString *p_str) { + String ret = GDMonoMarshal::mono_string_to_godot(p_str).sha256_text(); + return GDMonoMarshal::mono_string_from_godot(ret); +} + +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); +} + +#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/string_glue.h b/modules/mono/glue/string_glue.h new file mode 100644 index 0000000000..8b1553e28b --- /dev/null +++ b/modules/mono/glue/string_glue.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* string_glue.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef STRING_GLUE_H +#define STRING_GLUE_H + +#ifdef MONO_GLUE_ENABLED + +#include "../mono_gd/gd_mono_marshal.h" + +MonoArray *godot_icall_String_md5_buffer(MonoString *p_str); + +MonoString *godot_icall_String_md5_text(MonoString *p_str); + +int godot_icall_String_rfind(MonoString *p_str, MonoString *p_what, int p_from); + +int godot_icall_String_rfindn(MonoString *p_str, MonoString *p_what, int p_from); + +MonoArray *godot_icall_String_sha256_buffer(MonoString *p_str); + +MonoString *godot_icall_String_sha256_text(MonoString *p_str); + +// Register internal calls + +void godot_register_string_icalls(); + +#endif // MONO_GLUE_ENABLED + +#endif // STRING_GLUE_H diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h index f604464e8f..5a6a2d1742 100644 --- a/modules/mono/godotsharp_defs.h +++ b/modules/mono/godotsharp_defs.h @@ -32,10 +32,12 @@ #define GODOTSHARP_DEFS_H #define BINDINGS_NAMESPACE "Godot" +#define BINDINGS_NAMESPACE_COLLECTIONS BINDINGS_NAMESPACE ".Collections" #define BINDINGS_GLOBAL_SCOPE_CLASS "GD" #define BINDINGS_PTR_FIELD "ptr" #define BINDINGS_NATIVE_NAME_FIELD "nativeName" -#define API_ASSEMBLY_NAME "GodotSharp" +#define API_SOLUTION_NAME "GodotSharp" +#define CORE_API_ASSEMBLY_NAME "GodotSharp" #define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor" #define EDITOR_TOOLS_ASSEMBLY_NAME "GodotSharpTools" diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 92c5cdc5c1..d3fb2cb640 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -30,13 +30,13 @@ #include "godotsharp_dirs.h" -#include "os/os.h" +#include "core/os/dir_access.h" +#include "core/os/os.h" +#include "core/project_settings.h" #ifdef TOOLS_ENABLED +#include "core/version.h" #include "editor/editor_settings.h" -#include "os/dir_access.h" -#include "project_settings.h" -#include "version.h" #endif namespace GodotSharpDirs { @@ -95,10 +95,18 @@ public: #ifdef TOOLS_ENABLED String mono_solutions_dir; String build_logs_dir; + String sln_filepath; String csproj_filepath; + + String data_mono_bin_dir; + String data_editor_tools_dir; + String data_editor_prebuilt_api_dir; #endif + String data_mono_etc_dir; + String data_mono_lib_dir; + private: _GodotSharpDirs() { res_data_dir = "res://.mono"; @@ -123,10 +131,60 @@ private: name = "UnnamedProject"; } - String base_path = String("res://") + name; + 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"); +#endif + + String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir(); + +#ifdef TOOLS_ENABLED + + String data_dir_root = exe_dir.plus_file("GodotSharp"); + data_editor_tools_dir = data_dir_root.plus_file("Tools"); + data_editor_prebuilt_api_dir = data_dir_root.plus_file("Api"); + + String data_mono_root_dir = data_dir_root.plus_file("Mono"); + data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); + data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); + data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); + +#ifdef OSX_ENABLED + if (!DirAccess::exists(data_editor_tools_dir)) { + data_editor_tools_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Tools"); + } + + if (!DirAccess::exists(data_editor_prebuilt_api_dir)) { + data_editor_prebuilt_api_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Api"); + } + + if (!DirAccess::exists(data_mono_root_dir)) { + data_mono_bin_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Mono/bin"); + data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc"); + data_mono_lib_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Mono/lib"); + } +#endif + +#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); + 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"); + data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); + +#ifdef OSX_ENABLED + if (!DirAccess::exists(data_mono_root_dir)) { + data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc"); + data_mono_lib_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Mono/lib"); + } +#endif - sln_filepath = ProjectSettings::get_singleton()->globalize_path(base_path + ".sln"); - csproj_filepath = ProjectSettings::get_singleton()->globalize_path(base_path + ".csproj"); #endif } @@ -192,5 +250,26 @@ String get_project_sln_path() { String get_project_csproj_path() { return _GodotSharpDirs::get_singleton().csproj_filepath; } + +String get_data_mono_bin_dir() { + return _GodotSharpDirs::get_singleton().data_mono_bin_dir; +} + +String get_data_editor_tools_dir() { + return _GodotSharpDirs::get_singleton().data_editor_tools_dir; +} + +String get_data_editor_prebuilt_api_dir() { + return _GodotSharpDirs::get_singleton().data_editor_prebuilt_api_dir; +} #endif + +String get_data_mono_etc_dir() { + return _GodotSharpDirs::get_singleton().data_mono_etc_dir; +} + +String get_data_mono_lib_dir() { + return _GodotSharpDirs::get_singleton().data_mono_lib_dir; +} + } // namespace GodotSharpDirs diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h index e87b5a4150..35b564be30 100644 --- a/modules/mono/godotsharp_dirs.h +++ b/modules/mono/godotsharp_dirs.h @@ -31,7 +31,7 @@ #ifndef GODOTSHARP_DIRS_H #define GODOTSHARP_DIRS_H -#include "ustring.h" +#include "core/ustring.h" namespace GodotSharpDirs { @@ -49,11 +49,18 @@ String get_mono_logs_dir(); #ifdef TOOLS_ENABLED String get_mono_solutions_dir(); String get_build_logs_dir(); -String get_custom_project_settings_dir(); -#endif String get_project_sln_path(); String get_project_csproj_path(); + +String get_data_mono_bin_dir(); +String get_data_editor_tools_dir(); +String get_data_editor_prebuilt_api_dir(); +#endif + +String get_data_mono_etc_dir(); +String get_data_mono_lib_dir(); + } // namespace GodotSharpDirs #endif // GODOTSHARP_DIRS_H diff --git a/modules/mono/mono_gc_handle.cpp b/modules/mono/mono_gc_handle.cpp index 12109045e0..f695bddfeb 100644 --- a/modules/mono/mono_gc_handle.cpp +++ b/modules/mono/mono_gc_handle.cpp @@ -32,24 +32,34 @@ #include "mono_gd/gd_mono.h" -uint32_t MonoGCHandle::make_strong_handle(MonoObject *p_object) { +uint32_t MonoGCHandle::new_strong_handle(MonoObject *p_object) { return mono_gchandle_new(p_object, /* pinned: */ false); } -uint32_t MonoGCHandle::make_weak_handle(MonoObject *p_object) { +uint32_t MonoGCHandle::new_strong_handle_pinned(MonoObject *p_object) { + + return mono_gchandle_new(p_object, /* pinned: */ true); +} + +uint32_t MonoGCHandle::new_weak_handle(MonoObject *p_object) { return mono_gchandle_new_weakref(p_object, /* track_resurrection: */ false); } +void MonoGCHandle::free_handle(uint32_t p_gchandle) { + + mono_gchandle_free(p_gchandle); +} + Ref<MonoGCHandle> MonoGCHandle::create_strong(MonoObject *p_object) { - return memnew(MonoGCHandle(make_strong_handle(p_object))); + return memnew(MonoGCHandle(new_strong_handle(p_object), STRONG_HANDLE)); } Ref<MonoGCHandle> MonoGCHandle::create_weak(MonoObject *p_object) { - return memnew(MonoGCHandle(make_weak_handle(p_object))); + return memnew(MonoGCHandle(new_weak_handle(p_object), WEAK_HANDLE)); } void MonoGCHandle::release() { @@ -59,14 +69,15 @@ void MonoGCHandle::release() { #endif if (!released && GDMono::get_singleton()->is_runtime_initialized()) { - mono_gchandle_free(handle); + free_handle(handle); released = true; } } -MonoGCHandle::MonoGCHandle(uint32_t p_handle) { +MonoGCHandle::MonoGCHandle(uint32_t p_handle, HandleType p_handle_type) { released = false; + weak = p_handle_type == WEAK_HANDLE; handle = p_handle; } diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h index 9cb3ef0fbb..e5aa04e2b8 100644 --- a/modules/mono/mono_gc_handle.h +++ b/modules/mono/mono_gc_handle.h @@ -33,31 +33,43 @@ #include <mono/jit/jit.h> -#include "reference.h" +#include "core/reference.h" class MonoGCHandle : public Reference { GDCLASS(MonoGCHandle, Reference) bool released; + bool weak; uint32_t handle; public: - static uint32_t make_strong_handle(MonoObject *p_object); - static uint32_t make_weak_handle(MonoObject *p_object); + enum HandleType { + STRONG_HANDLE, + WEAK_HANDLE + }; + + static uint32_t new_strong_handle(MonoObject *p_object); + static uint32_t new_strong_handle_pinned(MonoObject *p_object); + static uint32_t new_weak_handle(MonoObject *p_object); + static void free_handle(uint32_t p_gchandle); static Ref<MonoGCHandle> create_strong(MonoObject *p_object); static Ref<MonoGCHandle> create_weak(MonoObject *p_object); + _FORCE_INLINE_ bool is_released() { return released; } + _FORCE_INLINE_ bool is_weak() { return weak; } + _FORCE_INLINE_ MonoObject *get_target() const { return released ? NULL : mono_gchandle_get_target(handle); } - _FORCE_INLINE_ void set_handle(uint32_t p_handle) { - handle = p_handle; + _FORCE_INLINE_ void set_handle(uint32_t p_handle, HandleType p_handle_type) { released = false; + weak = p_handle_type == WEAK_HANDLE; + handle = p_handle; } void release(); - MonoGCHandle(uint32_t p_handle); + MonoGCHandle(uint32_t p_handle, HandleType p_handle_type); ~MonoGCHandle(); }; diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index bc84f43b4f..a80155bd89 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -35,13 +35,14 @@ #include <mono/metadata/mono-debug.h> #include <mono/metadata/mono-gc.h> -#include "os/dir_access.h" -#include "os/file_access.h" -#include "os/os.h" -#include "os/thread.h" -#include "project_settings.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "core/os/thread.h" +#include "core/project_settings.h" #include "../csharp_script.h" +#include "../glue/cs_glue_version.gen.h" #include "../godotsharp_dirs.h" #include "../utils/path_utils.h" #include "gd_mono_class.h" @@ -77,12 +78,12 @@ void setup_runtime_main_args() { Vector<char *> main_args; main_args.resize(cmdline_args.size() + 1); - main_args[0] = execpath.ptrw(); + main_args.write[0] = execpath.ptrw(); int i = 1; for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) { CharString &stored = cmdline_args_utf8.push_back(E->get().utf8())->get(); - main_args[i] = stored.ptrw(); + main_args.write[i] = stored.ptrw(); i++; } @@ -148,7 +149,7 @@ void GDMono::initialize() { ERR_FAIL_NULL(Engine::get_singleton()); - OS::get_singleton()->print("Mono: Initializing module...\n"); + print_verbose("Mono: Initializing module..."); #ifdef DEBUG_METHODS_ENABLED _initialize_and_check_api_hashes(); @@ -161,26 +162,62 @@ void GDMono::initialize() { mono_trace_set_printerr_handler(gdmono_MonoPrintCallback); #endif + String assembly_rootdir; + String config_dir; + +#ifdef TOOLS_ENABLED #ifdef WINDOWS_ENABLED mono_reg_info = MonoRegUtils::find_mono(); - CharString assembly_dir; - CharString config_dir; - if (mono_reg_info.assembly_dir.length() && DirAccess::exists(mono_reg_info.assembly_dir)) { - assembly_dir = mono_reg_info.assembly_dir.utf8(); + assembly_rootdir = mono_reg_info.assembly_dir; } if (mono_reg_info.config_dir.length() && DirAccess::exists(mono_reg_info.config_dir)) { - config_dir = mono_reg_info.config_dir.utf8(); + config_dir = mono_reg_info.config_dir; + } +#elif OSX_ENABLED + const char *c_assembly_rootdir = mono_assembly_getrootdir(); + const char *c_config_dir = mono_get_config_dir(); + + if (!c_assembly_rootdir || !c_config_dir || !DirAccess::exists(c_assembly_rootdir) || !DirAccess::exists(c_config_dir)) { + Vector<const char *> locations; + locations.push_back("/Library/Frameworks/Mono.framework/Versions/Current/"); + 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"); + + if (FileAccess::exists(hint_mscorlib_path) && DirAccess::exists(hint_config_dir)) { + assembly_rootdir = hint_assembly_rootdir; + config_dir = hint_config_dir; + break; + } + } } +#endif +#endif // TOOLS_ENABLED + + String bundled_assembly_rootdir = GodotSharpDirs::get_data_mono_lib_dir(); + String bundled_config_dir = GodotSharpDirs::get_data_mono_etc_dir(); - mono_set_dirs(assembly_dir.length() ? assembly_dir.get_data() : NULL, - config_dir.length() ? config_dir.get_data() : NULL); +#ifdef TOOLS_ENABLED + if (DirAccess::exists(bundled_assembly_rootdir) && DirAccess::exists(bundled_config_dir)) { + assembly_rootdir = bundled_assembly_rootdir; + config_dir = bundled_config_dir; + } #else - mono_set_dirs(NULL, NULL); + // These are always the directories in export templates + assembly_rootdir = bundled_assembly_rootdir; + config_dir = bundled_config_dir; #endif + // Leak if we call mono_set_dirs more than once + mono_set_dirs(assembly_rootdir.length() ? assembly_rootdir.utf8().get_data() : NULL, + config_dir.length() ? config_dir.utf8().get_data() : NULL); + GDMonoAssembly::initialize(); #ifdef DEBUG_ENABLED @@ -202,7 +239,7 @@ void GDMono::initialize() { runtime_initialized = true; - OS::get_singleton()->print("Mono: Runtime initialized\n"); + print_verbose("Mono: Runtime initialized"); // mscorlib assembly MUST be present at initialization ERR_EXPLAIN("Mono: Failed to load mscorlib assembly"); @@ -226,22 +263,26 @@ void GDMono::initialize() { #ifdef DEBUG_ENABLED bool debugger_attached = _wait_for_debugger_msecs(500); if (!debugger_attached && OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->printerr("Mono: Debugger wait timeout\n"); + print_error("Mono: Debugger wait timeout"); #endif _register_internal_calls(); // The following assemblies are not required at initialization -#ifndef MONO_GLUE_DISABLED +#ifdef MONO_GLUE_ENABLED if (_load_api_assemblies()) { - if (!core_api_assembly_out_of_sync && !editor_api_assembly_out_of_sync && GDMonoUtils::mono_cache.godot_api_cache_updated) { - // Everything is fine with the api assemblies, load the project assembly - _load_project_assembly(); - } else { + // 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 - // The assembly was successfuly 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. + || (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("The loaded Core API assembly is out of sync"); @@ -256,7 +297,7 @@ void GDMono::initialize() { metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true); } - OS::get_singleton()->print("Mono: Proceeding to unload scripts domain because of invalid API assemblies\n"); + print_line("Mono: Proceeding to unload scripts domain because of invalid API assemblies."); Error err = _unload_scripts_domain(); if (err != OK) { @@ -265,18 +306,17 @@ void GDMono::initialize() { #else ERR_PRINT("The loaded API assembly is invalid"); CRASH_NOW(); -#endif +#endif // TOOLS_ENABLED } } #else - if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->print("Mono: Glue disabled, ignoring script assemblies\n"); -#endif + print_verbose("Mono: Glue disabled, ignoring script assemblies."); +#endif // MONO_GLUE_ENABLED - OS::get_singleton()->print("Mono: INITIALIZED\n"); + print_verbose("Mono: INITIALIZED"); } -#ifndef MONO_GLUE_DISABLED +#ifdef MONO_GLUE_ENABLED namespace GodotSharpBindings { uint64_t get_core_api_hash(); @@ -284,19 +324,18 @@ uint64_t get_core_api_hash(); uint64_t get_editor_api_hash(); #endif // TOOLS_ENABLED uint32_t get_bindings_version(); -uint32_t get_cs_glue_version(); void register_generated_icalls(); } // namespace GodotSharpBindings #endif void GDMono::_register_internal_calls() { -#ifndef MONO_GLUE_DISABLED +#ifdef MONO_GLUE_ENABLED GodotSharpBindings::register_generated_icalls(); #endif #ifdef TOOLS_ENABLED - GodotSharpBuilds::_register_internal_calls(); + GodotSharpEditor::register_internal_calls(); #endif } @@ -305,7 +344,7 @@ void GDMono::_initialize_and_check_api_hashes() { api_core_hash = ClassDB::get_api_hash(ClassDB::API_CORE); -#ifndef MONO_GLUE_DISABLED +#ifdef MONO_GLUE_ENABLED if (api_core_hash != GodotSharpBindings::get_core_api_hash()) { ERR_PRINT("Mono: Core API hash mismatch!"); } @@ -314,7 +353,7 @@ void GDMono::_initialize_and_check_api_hashes() { #ifdef TOOLS_ENABLED api_editor_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR); -#ifndef MONO_GLUE_DISABLED +#ifdef MONO_GLUE_ENABLED if (api_editor_hash != GodotSharpBindings::get_editor_api_hash()) { ERR_PRINT("Mono: Editor API hash mismatch!"); } @@ -352,8 +391,7 @@ bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMo CRASH_COND(!r_assembly); - if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->print((String() + "Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...\n").utf8()); + print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); MonoImageOpenStatus status = MONO_IMAGE_OK; MonoAssembly *assembly = mono_assembly_load_full(p_aname, NULL, &status, p_refonly); @@ -372,8 +410,33 @@ bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMo *r_assembly = *stored_assembly; - if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->print(String("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path() + "\n").utf8()); + print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path()); + + return true; +} + +bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly) { + + CRASH_COND(!r_assembly); + + print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); + + GDMonoAssembly *assembly = GDMonoAssembly::load_from(p_name, p_path, p_refonly); + + if (!assembly) + return false; + +#ifdef DEBUG_ENABLED + uint32_t domain_id = mono_domain_get_id(mono_domain_get()); + GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name); + + ERR_FAIL_COND_V(stored_assembly == NULL, false); + ERR_FAIL_COND_V(*stored_assembly != assembly, false); +#endif + + *r_assembly = assembly; + + print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path()); return true; } @@ -427,20 +490,33 @@ bool GDMono::_load_core_api_assembly() { return true; #ifdef TOOLS_ENABLED - if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) + if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) { + print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated"); return false; + } #endif - bool success = load_assembly(API_ASSEMBLY_NAME, &core_api_assembly); + String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + + if (!FileAccess::exists(assembly_path)) + return false; + + bool success = load_assembly_from(CORE_API_ASSEMBLY_NAME, + assembly_path, + &core_api_assembly); if (success) { -#ifndef MONO_GLUE_DISABLED +#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 || - GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; -#endif + CS_GLUE_VERSION != api_assembly_ver.cs_glue_version; + if (!core_api_assembly_out_of_sync) { + GDMonoUtils::update_godot_api_cache(); + } +#else GDMonoUtils::update_godot_api_cache(); +#endif } return success; @@ -452,19 +528,26 @@ bool GDMono::_load_editor_api_assembly() { if (editor_api_assembly) return true; -#ifdef TOOLS_ENABLED - if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) + if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) { + print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated"); + return false; + } + + String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + + if (!FileAccess::exists(assembly_path)) return false; -#endif - bool success = load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly); + bool success = load_assembly_from(EDITOR_API_ASSEMBLY_NAME, + assembly_path, + &editor_api_assembly); if (success) { -#ifndef MONO_GLUE_DISABLED +#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 || - GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; + CS_GLUE_VERSION != api_assembly_ver.cs_glue_version; #endif } @@ -498,9 +581,11 @@ bool GDMono::_load_project_assembly() { if (success) { mono_assembly_set_main(project_assembly->get_assembly()); + + CSharpLanguage::get_singleton()->project_assembly_loaded(); } else { if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->printerr("Mono: Failed to load project assembly\n"); + print_error("Mono: Failed to load project assembly"); } return success; @@ -510,18 +595,24 @@ bool GDMono::_load_api_assemblies() { if (!_load_core_api_assembly()) { if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->printerr("Mono: Failed to load Core API assembly\n"); + print_error("Mono: Failed to load Core API assembly"); return false; - } else { + } + + 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()) - OS::get_singleton()->printerr("Mono: Failed to load Editor API assembly\n"); - return false; - } -#endif + if (!_load_editor_api_assembly()) { + if (OS::get_singleton()->is_stdout_verbose()) + print_error("Mono: Failed to load Editor API assembly"); + return false; } + if (editor_api_assembly_out_of_sync) + return false; +#endif + return true; } @@ -544,7 +635,7 @@ void GDMono::metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, String assembly_path = GodotSharpDirs::get_res_assemblies_dir() .plus_file(p_api_type == APIAssembly::API_CORE ? - API_ASSEMBLY_NAME ".dll" : + CORE_API_ASSEMBLY_NAME ".dll" : EDITOR_API_ASSEMBLY_NAME ".dll"); ERR_FAIL_COND(!FileAccess::exists(assembly_path)); @@ -575,7 +666,7 @@ bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type) String assembly_path = GodotSharpDirs::get_res_assemblies_dir() .plus_file(p_api_type == APIAssembly::API_CORE ? - API_ASSEMBLY_NAME ".dll" : + CORE_API_ASSEMBLY_NAME ".dll" : EDITOR_API_ASSEMBLY_NAME ".dll"); if (!FileAccess::exists(assembly_path)) @@ -593,9 +684,7 @@ Error GDMono::_load_scripts_domain() { ERR_FAIL_COND_V(scripts_domain != NULL, ERR_BUG); - if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Mono: Loading scripts domain...\n"); - } + print_verbose("Mono: Loading scripts domain..."); scripts_domain = GDMonoUtils::create_domain("GodotEngine.ScriptsDomain"); @@ -611,11 +700,7 @@ Error GDMono::_unload_scripts_domain() { ERR_FAIL_NULL_V(scripts_domain, ERR_BUG); - if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Mono: Unloading scripts domain...\n"); - } - - _GodotSharp::get_singleton()->_dispose_callback(); + print_verbose("Mono: Unloading scripts domain..."); if (mono_domain_get() != root_domain) mono_domain_set(root_domain, true); @@ -623,7 +708,11 @@ Error GDMono::_unload_scripts_domain() { mono_gc_collect(mono_gc_max_generation()); finalizing_scripts_domain = true; - mono_domain_finalize(scripts_domain, 2000); + + if (!mono_domain_finalize(scripts_domain, 2000)) { + ERR_PRINT("Mono: Domain finalization timeout"); + } + finalizing_scripts_domain = false; mono_gc_collect(mono_gc_max_generation()); @@ -642,8 +731,6 @@ Error GDMono::_unload_scripts_domain() { MonoDomain *domain = scripts_domain; scripts_domain = NULL; - _GodotSharp::get_singleton()->_dispose_callback(); - MonoException *exc = NULL; mono_domain_try_unload(domain, (MonoObject **)&exc); @@ -661,9 +748,7 @@ Error GDMono::_load_tools_domain() { ERR_FAIL_COND_V(tools_domain != NULL, ERR_BUG); - if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Mono: Loading tools domain...\n"); - } + print_verbose("Mono: Loading tools domain..."); tools_domain = GDMonoUtils::create_domain("GodotEngine.ToolsDomain"); @@ -693,44 +778,44 @@ Error GDMono::reload_scripts_domain() { return err; } -#ifndef MONO_GLUE_DISABLED +#ifdef MONO_GLUE_ENABLED if (!_load_api_assemblies()) { - return ERR_CANT_OPEN; - } + if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)) || + (editor_api_assembly && editor_api_assembly_out_of_sync)) { + // 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 && !editor_api_assembly_out_of_sync && GDMonoUtils::mono_cache.godot_api_cache_updated) { - // Everything is fine with the api assemblies, load the project assembly - _load_project_assembly(); - } else { - // The assembly was successfuly 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) { - metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); - } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) { - ERR_PRINT("Core API assembly is in sync, but the cache update failed"); - metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); - } + if (core_api_assembly_out_of_sync) { + ERR_PRINT("The loaded Core API assembly is out of sync"); + 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) { - metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true); - } + if (editor_api_assembly_out_of_sync) { + ERR_PRINT("The loaded Editor API assembly is out of sync"); + metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true); + } - Error err = _unload_scripts_domain(); - if (err != OK) { - WARN_PRINT("Mono: Failed to unload scripts domain"); - } + Error err = _unload_scripts_domain(); + if (err != OK) { + WARN_PRINT("Mono: Failed to unload scripts domain"); + } - return ERR_CANT_RESOLVE; + return ERR_CANT_RESOLVE; + } else { + return ERR_CANT_OPEN; + } } - if (!_load_project_assembly()) + if (!_load_project_assembly()) { return ERR_CANT_OPEN; + } #else - if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->print("Mono: Glue disabled, ignoring script assemblies\n"); -#endif + print_verbose("Mono: Glue disabled, ignoring script assemblies."); +#endif // MONO_GLUE_ENABLED return OK; } @@ -742,15 +827,15 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { String domain_name = mono_domain_get_friendly_name(p_domain); - if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print(String("Mono: Unloading domain `" + domain_name + "`...\n").utf8()); - } + print_verbose("Mono: Unloading domain `" + domain_name + "`..."); - if (mono_domain_get() != root_domain) + if (mono_domain_get() == p_domain) mono_domain_set(root_domain, true); mono_gc_collect(mono_gc_max_generation()); - mono_domain_finalize(p_domain, 2000); + if (!mono_domain_finalize(p_domain, 2000)) { + ERR_PRINT("Mono: Domain finalization timeout"); + } mono_gc_collect(mono_gc_max_generation()); _domain_assemblies_cleanup(mono_domain_get_id(p_domain)); @@ -805,9 +890,9 @@ void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) { void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) { -// This method will be called by the runtime when a thrown exception is not handled. -// It won't be called when we manually treat a thrown exception as unhandled. -// We assume the exception was already printed before calling this hook. + // This method will be called by the runtime when a thrown exception is not handled. + // It won't be called when we manually treat a thrown exception as unhandled. + // We assume the exception was already printed before calling this hook. #ifdef DEBUG_ENABLED GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc); @@ -854,7 +939,7 @@ GDMono::GDMono() { GDMono::~GDMono() { - if (runtime_initialized) { + if (is_runtime_initialized()) { if (scripts_domain) { @@ -877,10 +962,11 @@ GDMono::~GDMono() { GDMonoUtils::clear_cache(); - OS::get_singleton()->print("Mono: Runtime cleanup...\n"); + print_verbose("Mono: Runtime cleanup..."); - runtime_initialized = false; mono_jit_cleanup(root_domain); + + runtime_initialized = false; } if (gdmono_log) @@ -891,51 +977,6 @@ GDMono::~GDMono() { _GodotSharp *_GodotSharp::singleton = NULL; -void _GodotSharp::_dispose_object(Object *p_object) { - - if (p_object->get_script_instance()) { - CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_object->get_script_instance()); - if (cs_instance) { - cs_instance->mono_object_disposed(); - return; - } - } - - // Unsafe refcount decrement. The managed instance also counts as a reference. - // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) - if (Object::cast_to<Reference>(p_object)->unreference()) { - memdelete(p_object); - } -} - -void _GodotSharp::_dispose_callback() { - -#ifndef NO_THREADS - queue_mutex->lock(); -#endif - - for (List<Object *>::Element *E = obj_delete_queue.front(); E; E = E->next()) { - _dispose_object(E->get()); - } - - for (List<NodePath *>::Element *E = np_delete_queue.front(); E; E = E->next()) { - memdelete(E->get()); - } - - for (List<RID *>::Element *E = rid_delete_queue.front(); E; E = E->next()) { - memdelete(E->get()); - } - - obj_delete_queue.clear(); - np_delete_queue.clear(); - rid_delete_queue.clear(); - queue_empty = true; - -#ifndef NO_THREADS - queue_mutex->unlock(); -#endif -} - void _GodotSharp::attach_thread() { GDMonoUtils::attach_current_thread(); @@ -946,81 +987,57 @@ void _GodotSharp::detach_thread() { GDMonoUtils::detach_current_thread(); } -bool _GodotSharp::is_finalizing_domain() { +int32_t _GodotSharp::get_domain_id() { - return GDMono::get_singleton()->is_finalizing_scripts_domain(); + MonoDomain *domain = mono_domain_get(); + CRASH_COND(!domain); // User must check if runtime is initialized before calling this method + return mono_domain_get_id(domain); } -bool _GodotSharp::is_domain_loaded() { +int32_t _GodotSharp::get_scripts_domain_id() { - return GDMono::get_singleton()->get_scripts_domain() != NULL; + MonoDomain *domain = SCRIPTS_DOMAIN; + CRASH_COND(!domain); // User must check if scripts domain is loaded before calling this method + return mono_domain_get_id(domain); } -#define ENQUEUE_FOR_DISPOSAL(m_queue, m_inst) \ - m_queue.push_back(m_inst); \ - if (queue_empty) { \ - queue_empty = false; \ - if (!is_finalizing_domain()) { /* call_deferred may not be safe here */ \ - call_deferred("_dispose_callback"); \ - } \ - } +bool _GodotSharp::is_scripts_domain_loaded() { -void _GodotSharp::queue_dispose(MonoObject *p_mono_object, Object *p_object) { + return GDMono::get_singleton()->is_runtime_initialized() && SCRIPTS_DOMAIN != NULL; +} - if (GDMonoUtils::is_main_thread() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { - _dispose_object(p_object); - } else { -#ifndef NO_THREADS - queue_mutex->lock(); -#endif +bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) { - // This is our last chance to invoke notification predelete (this is being called from the finalizer) - // We must use the MonoObject* passed by the finalizer, because the weak GC handle target returns NULL at this point - CSharpInstance *si = CAST_CSHARP_INSTANCE(p_object->get_script_instance()); - if (si) { - si->call_notification_no_check(p_mono_object, Object::NOTIFICATION_PREDELETE); - } + return is_domain_finalizing_for_unload(p_domain_id); +} - ENQUEUE_FOR_DISPOSAL(obj_delete_queue, p_object); +bool _GodotSharp::is_domain_finalizing_for_unload() { -#ifndef NO_THREADS - queue_mutex->unlock(); -#endif - } + return is_domain_finalizing_for_unload(mono_domain_get()); } -void _GodotSharp::queue_dispose(NodePath *p_node_path) { +bool _GodotSharp::is_domain_finalizing_for_unload(int32_t p_domain_id) { - if (GDMonoUtils::is_main_thread() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { - memdelete(p_node_path); - } else { -#ifndef NO_THREADS - queue_mutex->lock(); -#endif + return is_domain_finalizing_for_unload(mono_domain_get_by_id(p_domain_id)); +} - ENQUEUE_FOR_DISPOSAL(np_delete_queue, p_node_path); +bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) { -#ifndef NO_THREADS - queue_mutex->unlock(); -#endif - } + if (!p_domain) + return true; + if (p_domain == SCRIPTS_DOMAIN && GDMono::get_singleton()->is_finalizing_scripts_domain()) + return true; + return mono_domain_is_unloading(p_domain); } -void _GodotSharp::queue_dispose(RID *p_rid) { +bool _GodotSharp::is_runtime_shutting_down() { - if (GDMonoUtils::is_main_thread() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { - memdelete(p_rid); - } else { -#ifndef NO_THREADS - queue_mutex->lock(); -#endif + return mono_runtime_is_shutting_down(); +} - ENQUEUE_FOR_DISPOSAL(rid_delete_queue, p_rid); +bool _GodotSharp::is_runtime_initialized() { -#ifndef NO_THREADS - queue_mutex->unlock(); -#endif - } + return GDMono::get_singleton()->is_runtime_initialized(); } void _GodotSharp::_bind_methods() { @@ -1028,10 +1045,13 @@ void _GodotSharp::_bind_methods() { ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread); ClassDB::bind_method(D_METHOD("detach_thread"), &_GodotSharp::detach_thread); - ClassDB::bind_method(D_METHOD("is_finalizing_domain"), &_GodotSharp::is_finalizing_domain); - ClassDB::bind_method(D_METHOD("is_domain_loaded"), &_GodotSharp::is_domain_loaded); + ClassDB::bind_method(D_METHOD("get_domain_id"), &_GodotSharp::get_domain_id); + ClassDB::bind_method(D_METHOD("get_scripts_domain_id"), &_GodotSharp::get_scripts_domain_id); + ClassDB::bind_method(D_METHOD("is_scripts_domain_loaded"), &_GodotSharp::is_scripts_domain_loaded); + ClassDB::bind_method(D_METHOD("is_domain_finalizing_for_unload", "domain_id"), &_GodotSharp::_is_domain_finalizing_for_unload); - ClassDB::bind_method(D_METHOD("_dispose_callback"), &_GodotSharp::_dispose_callback); + 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); } _GodotSharp::_GodotSharp() { diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index e0ec6ced5e..fd9551b6de 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -142,7 +142,7 @@ class GDMono { GDMonoLog *gdmono_log; -#ifdef WINDOWS_ENABLED +#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) MonoRegInfo mono_reg_info; #endif @@ -170,8 +170,9 @@ public: void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly); GDMonoAssembly **get_loaded_assembly(const String &p_name); - _FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized; } - _FORCE_INLINE_ bool is_finalizing_scripts_domain() const { return finalizing_scripts_domain; } + _FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized && !mono_runtime_is_shutting_down() /* stays true after shutdown finished */; } + + _FORCE_INLINE_ bool is_finalizing_scripts_domain() { return finalizing_scripts_domain; } _FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; } #ifdef TOOLS_ENABLED @@ -186,7 +187,7 @@ public: _FORCE_INLINE_ GDMonoAssembly *get_editor_tools_assembly() const { return editor_tools_assembly; } #endif -#ifdef WINDOWS_ENABLED +#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) const MonoRegInfo &get_mono_reg_info() { return mono_reg_info; } #endif @@ -198,6 +199,8 @@ public: bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false); bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false); + bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false); + Error finalize_and_unload_domain(MonoDomain *p_domain); void initialize(); @@ -206,12 +209,14 @@ public: ~GDMono(); }; -class GDMonoScopeDomain { +namespace gdmono { + +class ScopeDomain { MonoDomain *prev_domain; public: - GDMonoScopeDomain(MonoDomain *p_domain) { + ScopeDomain(MonoDomain *p_domain) { MonoDomain *prev_domain = mono_domain_get(); if (prev_domain != p_domain) { this->prev_domain = prev_domain; @@ -221,26 +226,43 @@ public: } } - ~GDMonoScopeDomain() { + ~ScopeDomain() { if (prev_domain) mono_domain_set(prev_domain, false); } }; -#define _GDMONO_SCOPE_DOMAIN_(m_mono_domain) \ - GDMonoScopeDomain __gdmono__scope__domain__(m_mono_domain); \ +class ScopeExitDomainUnload { + MonoDomain *domain; + +public: + ScopeExitDomainUnload(MonoDomain *p_domain) : + domain(p_domain) { + } + + ~ScopeExitDomainUnload() { + if (domain) + GDMono::get_singleton()->finalize_and_unload_domain(domain); + } +}; + +} // namespace gdmono + +#define _GDMONO_SCOPE_DOMAIN_(m_mono_domain) \ + gdmono::ScopeDomain __gdmono__scope__domain__(m_mono_domain); \ (void)__gdmono__scope__domain__; +#define _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(m_mono_domain) \ + gdmono::ScopeExitDomainUnload __gdmono__scope__exit__domain__unload__(m_mono_domain); \ + (void)__gdmono__scope__exit__domain__unload__; + class _GodotSharp : public Object { GDCLASS(_GodotSharp, Object) friend class GDMono; - void _dispose_object(Object *p_object); - - void _dispose_callback(); + bool _is_domain_finalizing_for_unload(int32_t p_domain_id); - List<Object *> obj_delete_queue; List<NodePath *> np_delete_queue; List<RID *> rid_delete_queue; @@ -260,12 +282,17 @@ public: void attach_thread(); void detach_thread(); - bool is_finalizing_domain(); - bool is_domain_loaded(); + int32_t get_domain_id(); + int32_t get_scripts_domain_id(); + + bool is_scripts_domain_loaded(); + + bool is_domain_finalizing_for_unload(); + bool is_domain_finalizing_for_unload(int32_t p_domain_id); + bool is_domain_finalizing_for_unload(MonoDomain *p_domain); - void queue_dispose(MonoObject *p_mono_object, Object *p_object); - void queue_dispose(NodePath *p_node_path); - void queue_dispose(RID *p_rid); + bool is_runtime_shutting_down(); + bool is_runtime_initialized(); _GodotSharp(); ~_GodotSharp(); diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 9d3bee2176..72b0204672 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -42,8 +42,48 @@ #include "gd_mono_class.h" bool GDMonoAssembly::no_search = false; +bool GDMonoAssembly::in_preload = false; + Vector<String> GDMonoAssembly::search_dirs; +void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config) { + + const char *rootdir = mono_assembly_getrootdir(); + if (rootdir) { + String framework_dir = String(rootdir).plus_file("mono").plus_file("4.5"); + r_search_dirs.push_back(framework_dir); + r_search_dirs.push_back(framework_dir.plus_file("Facades")); + } + + if (p_custom_config.length()) { + r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(p_custom_config)); + } else { + r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir()); + } + + r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_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()); +#endif +} + +void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) { + + if (no_search) + return; + + // If our search and preload hooks fail to load the assembly themselves, the mono runtime still might. + // Just do Assembly.LoadFrom("/Full/Path/On/Disk.dll"); + // In this case, we wouldn't have the assembly known in GDMono, which causes crashes + // if any class inside the assembly is looked up by Godot. + // And causing a lookup like that is as easy as throwing an exception defined in it... + // No, we can't make the assembly load hooks smart enough because they get passed a MonoAssemblyName* only, + // not the disk path passed to say Assembly.LoadFrom(). + _wrap_mono_assembly(assembly); +} + MonoAssembly *GDMonoAssembly::assembly_search_hook(MonoAssemblyName *aname, void *user_data) { return GDMonoAssembly::_search_hook(aname, user_data, false); } @@ -76,100 +116,95 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d no_search = true; // Avoid the recursion madness - String path; - GDMonoAssembly *res = NULL; - - for (int i = 0; i < search_dirs.size(); i++) { - const String &search_dir = search_dirs[i]; - - if (has_extension) { - path = search_dir.plus_file(name); - if (FileAccess::exists(path)) { - res = _load_assembly_from(name.get_basename(), path, refonly); - if (res != NULL) - break; - } - } else { - path = search_dir.plus_file(name + ".dll"); - if (FileAccess::exists(path)) { - res = _load_assembly_from(name, path, refonly); - if (res != NULL) - break; - } - - path = search_dir.plus_file(name + ".exe"); - if (FileAccess::exists(path)) { - res = _load_assembly_from(name, path, refonly); - if (res != NULL) - break; - } - } - } + GDMonoAssembly *res = _load_assembly_search(name, search_dirs, refonly); no_search = false; return res ? res->get_assembly() : NULL; } -MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly) { +static _THREAD_LOCAL_(MonoImage *) image_corlib_loading = NULL; + +MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) { (void)user_data; // UNUSED if (search_dirs.empty()) { - search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir()); - search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); - search_dirs.push_back(OS::get_singleton()->get_resource_dir()); - search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir()); -#ifdef GD_MONO_EDITOR_ASSEMBLIES_DIR - search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir().plus_file(_MKSTR(GD_MONO_EDITOR_ASSEMBLIES_DIR)).simplify_path()); -#endif - - const char *rootdir = mono_assembly_getrootdir(); - if (rootdir) { - search_dirs.push_back(String(rootdir).plus_file("mono").plus_file("4.5")); - search_dirs.push_back(String(rootdir).plus_file("mono").plus_file("4.5").plus_file("Facades")); - } + fill_search_dirs(search_dirs); + } - if (assemblies_path) { - while (*assemblies_path) { - search_dirs.push_back(*assemblies_path); - ++assemblies_path; - } + { + // If we find the assembly here, we load it with `mono_assembly_load_from_full`, + // which in turn invokes load hooks before returning the MonoAssembly to us. + // One of the load hooks is `load_aot_module`. This hook can end up calling preload hooks + // again for the same assembly in certain in certain circumstances (the `do_load_image` part). + // If this is the case and we return NULL due to the no_search condition below, + // it will result in an internal crash later on. Therefore we need to return the assembly we didn't + // get yet from `mono_assembly_load_from_full`. Luckily we have the image, which already got it. + // This must be done here. If done in search hooks, it would cause `mono_assembly_load_from_full` + // to think another MonoAssembly for this assembly was already loaded, making it delete its own, + // when in fact both pointers were the same... This hooks thing is confusing. + if (image_corlib_loading) { + return mono_image_get_assembly(image_corlib_loading); } } + if (no_search) + return NULL; + + no_search = true; + in_preload = true; + String name = mono_assembly_name_get_name(aname); bool has_extension = name.ends_with(".dll"); + GDMonoAssembly *res = NULL; if (has_extension ? name == "mscorlib.dll" : name == "mscorlib") { GDMonoAssembly **stored_assembly = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name); if (stored_assembly) return (*stored_assembly)->get_assembly(); - String path; - GDMonoAssembly *res = NULL; + res = _load_assembly_search("mscorlib.dll", search_dirs, refonly); + } - for (int i = 0; i < search_dirs.size(); i++) { - const String &search_dir = search_dirs[i]; + no_search = false; + in_preload = false; - if (has_extension) { - path = search_dir.plus_file(name); - if (FileAccess::exists(path)) { - res = _load_assembly_from(name.get_basename(), path, refonly); - if (res != NULL) - break; - } - } else { - path = search_dir.plus_file(name + ".dll"); - if (FileAccess::exists(path)) { - res = _load_assembly_from(name, path, refonly); - if (res != NULL) - break; - } + return res ? res->get_assembly() : NULL; +} + +GDMonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) { + + GDMonoAssembly *res = NULL; + String path; + + bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe"); + + for (int i = 0; i < p_search_dirs.size(); i++) { + const String &search_dir = p_search_dirs[i]; + + if (has_extension) { + path = search_dir.plus_file(p_name); + if (FileAccess::exists(path)) { + res = _load_assembly_from(p_name.get_basename(), path, p_refonly); + if (res != NULL) + return res; + } + } else { + path = search_dir.plus_file(p_name + ".dll"); + if (FileAccess::exists(path)) { + res = _load_assembly_from(p_name, path, p_refonly); + if (res != NULL) + return res; } - } - return res ? res->get_assembly() : NULL; + path = search_dir.plus_file(p_name + ".exe"); + if (FileAccess::exists(path)) { + res = _load_assembly_from(p_name, path, p_refonly); + if (res != NULL) + return res; + } + } } return NULL; @@ -192,12 +227,30 @@ GDMonoAssembly *GDMonoAssembly::_load_assembly_from(const String &p_name, const return assembly; } +void GDMonoAssembly::_wrap_mono_assembly(MonoAssembly *assembly) { + String name = mono_assembly_name_get_name(mono_assembly_get_name(assembly)); + + MonoImage *image = mono_assembly_get_image(assembly); + + GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, mono_image_get_filename(image))); + Error err = gdassembly->wrapper_for_image(image); + + if (err != OK) { + memdelete(gdassembly); + ERR_FAIL(); + } + + MonoDomain *domain = mono_domain_get(); + GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, gdassembly); +} + void GDMonoAssembly::initialize() { mono_install_assembly_search_hook(&assembly_search_hook, NULL); mono_install_assembly_refonly_search_hook(&assembly_refonly_search_hook, NULL); mono_install_assembly_preload_hook(&assembly_preload_hook, NULL); mono_install_assembly_refonly_preload_hook(&assembly_refonly_preload_hook, NULL); + mono_install_assembly_load_hook(&assembly_load_hook, NULL); } Error GDMonoAssembly::load(bool p_refonly) { @@ -241,8 +294,16 @@ no_pdb: #endif + bool is_corlib_preload = in_preload && name == "mscorlib"; + + if (is_corlib_preload) + image_corlib_loading = image; + assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, refonly); + if (is_corlib_preload) + image_corlib_loading = NULL; + ERR_FAIL_COND_V(status != MONO_IMAGE_OK || assembly == NULL, ERR_FILE_CANT_OPEN); loaded = true; @@ -401,11 +462,12 @@ GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_ GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); if (loaded_asm) return *loaded_asm; - +#ifdef DEBUG_ENABLED + CRASH_COND(!FileAccess::exists(p_path)); +#endif no_search = true; GDMonoAssembly *res = _load_assembly_from(p_name, p_path, p_refonly); no_search = false; - return res; } diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h index 5cf744a5a2..2af40ec901 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ b/modules/mono/mono_gd/gd_mono_assembly.h @@ -34,10 +34,10 @@ #include <mono/jit/jit.h> #include <mono/metadata/assembly.h> +#include "core/hash_map.h" +#include "core/map.h" +#include "core/ustring.h" #include "gd_mono_utils.h" -#include "hash_map.h" -#include "map.h" -#include "ustring.h" class GDMonoAssembly { @@ -89,8 +89,10 @@ class GDMonoAssembly { #endif static bool no_search; + static bool in_preload; static Vector<String> search_dirs; + static void assembly_load_hook(MonoAssembly *assembly, void *user_data); static MonoAssembly *assembly_search_hook(MonoAssemblyName *aname, void *user_data); static MonoAssembly *assembly_refonly_search_hook(MonoAssemblyName *aname, void *user_data); static MonoAssembly *assembly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data); @@ -100,6 +102,8 @@ class GDMonoAssembly { static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly); static GDMonoAssembly *_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly); + static GDMonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly); + static void _wrap_mono_assembly(MonoAssembly *assembly); friend class GDMono; static void initialize(); @@ -122,6 +126,8 @@ public: GDMonoClass *get_object_derived_class(const StringName &p_class); + static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String()); + static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly); GDMonoAssembly(const String &p_name, const String &p_path = String()); diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp index 66339d7ae6..c55f9160bd 100644 --- a/modules/mono/mono_gd/gd_mono_class.cpp +++ b/modules/mono/mono_gd/gd_mono_class.cpp @@ -33,23 +33,35 @@ #include <mono/metadata/attrdefs.h> #include "gd_mono_assembly.h" +#include "gd_mono_marshal.h" -MonoType *GDMonoClass::get_raw_type(GDMonoClass *p_class) { +String GDMonoClass::get_full_name(MonoClass *p_mono_class) { + // mono_type_get_full_name is not exposed to embedders, but this seems to do the job + MonoReflectionType *type_obj = mono_type_get_object(mono_domain_get(), get_mono_type(p_mono_class)); - return mono_class_get_type(p_class->get_mono_ptr()); -} + MonoException *exc = NULL; + MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); -bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const { + return GDMonoMarshal::mono_string_to_godot(str); +} - return mono_class_is_assignable_from(mono_class, p_from->mono_class); +MonoType *GDMonoClass::get_mono_type(MonoClass *p_mono_class) { + return mono_class_get_type(p_mono_class); } String GDMonoClass::get_full_name() const { + return get_full_name(mono_class); +} - String res = namespace_name; - if (res.length()) - res += "."; - return res + class_name; +MonoType *GDMonoClass::get_mono_type() { + // Care, you cannot compare MonoType pointers + return get_mono_type(mono_class); +} + +bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const { + + return mono_class_is_assignable_from(mono_class, p_from->mono_class); } GDMonoClass *GDMonoClass::get_parent_class() { @@ -139,6 +151,7 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != NULL) { StringName name = mono_method_get_name(raw_method); + // get_method implicitly fetches methods and adds them to this->methods GDMonoMethod *method = get_method(raw_method, name); ERR_CONTINUE(!method); @@ -308,6 +321,12 @@ GDMonoMethod *GDMonoClass::get_method_with_desc(const String &p_description, boo return get_method(method); } +void *GDMonoClass::get_method_thunk(const StringName &p_name, int p_params_count) { + + GDMonoMethod *method = get_method(p_name, p_params_count); + return method ? method->get_thunk() : NULL; +} + GDMonoField *GDMonoClass::get_field(const StringName &p_name) { Map<StringName, GDMonoField *>::Element *result = fields.find(p_name); @@ -431,6 +450,21 @@ const Vector<GDMonoClass *> &GDMonoClass::get_all_delegates() { return delegates_list; } +const Vector<GDMonoMethod *> &GDMonoClass::get_all_methods() { + + if (!method_list_fetched) { + void *iter = NULL; + MonoMethod *raw_method = NULL; + while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != NULL) { + method_list.push_back(memnew(GDMonoMethod(mono_method_get_name(raw_method), raw_method))); + } + + method_list_fetched = true; + } + + return method_list; +} + GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly) { namespace_name = p_namespace; @@ -442,6 +476,7 @@ GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name attributes = NULL; methods_fetched = false; + method_list_fetched = false; fields_fetched = false; properties_fetched = false; delegates_fetched = false; @@ -483,7 +518,7 @@ GDMonoClass::~GDMonoClass() { } } - deleted_methods[offset] = method; + deleted_methods.write[offset] = method; ++offset; memdelete(method); @@ -494,4 +529,8 @@ GDMonoClass::~GDMonoClass() { methods.clear(); } + + for (int i = 0; i < method_list.size(); ++i) { + memdelete(method_list[i]); + } } diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h index 417c138594..689001f494 100644 --- a/modules/mono/mono_gd/gd_mono_class.h +++ b/modules/mono/mono_gd/gd_mono_class.h @@ -33,8 +33,8 @@ #include <mono/metadata/debug-helpers.h> -#include "map.h" -#include "ustring.h" +#include "core/map.h" +#include "core/ustring.h" #include "gd_mono_field.h" #include "gd_mono_header.h" @@ -79,9 +79,14 @@ class GDMonoClass { bool attrs_fetched; MonoCustomAttrInfo *attributes; + // This contains both the original method names and remapped method names from the native Godot identifiers to the C# functions. + // Most method-related functions refer to this and it's possible this is unintuitive for outside users; this may be a prime location for refactoring or renaming. bool methods_fetched; HashMap<MethodKey, GDMonoMethod *, MethodKey::Hasher> methods; + bool method_list_fetched; + Vector<GDMonoMethod *> method_list; + bool fields_fetched; Map<StringName, GDMonoField *> fields; Vector<GDMonoField *> fields_list; @@ -98,7 +103,11 @@ class GDMonoClass { GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly); public: - static MonoType *get_raw_type(GDMonoClass *p_class); + static String get_full_name(MonoClass *p_mono_class); + static MonoType *get_mono_type(MonoClass *p_mono_class); + + String get_full_name() const; + MonoType *get_mono_type(); bool is_assignable_from(GDMonoClass *p_from) const; @@ -108,8 +117,6 @@ public: _FORCE_INLINE_ MonoClass *get_mono_ptr() const { return mono_class; } _FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; } - String get_full_name() const; - GDMonoClass *get_parent_class(); #ifdef TOOLS_ENABLED @@ -131,6 +138,8 @@ public: GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name, int p_params_count); GDMonoMethod *get_method_with_desc(const String &p_description, bool p_include_namespace); + void *get_method_thunk(const StringName &p_name, int p_params_count = 0); + GDMonoField *get_field(const StringName &p_name); const Vector<GDMonoField *> &get_all_fields(); @@ -139,6 +148,8 @@ public: const Vector<GDMonoClass *> &get_all_delegates(); + const Vector<GDMonoMethod *> &get_all_methods(); + ~GDMonoClass(); }; diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp index 3b91777ed4..f09e93e662 100644 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -40,65 +40,71 @@ void GDMonoField::set_value_raw(MonoObject *p_object, void *p_ptr) { } void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_value) { -#define SET_FROM_STRUCT_AND_BREAK(m_type) \ - { \ - const m_type &val = p_value.operator ::m_type(); \ - MARSHALLED_OUT(m_type, val, raw); \ - mono_field_set_value(p_object, mono_field, raw); \ - break; \ +#define SET_FROM_STRUCT(m_type) \ + { \ + GDMonoMarshal::M_##m_type from = MARSHALLED_OUT(m_type, p_value.operator ::m_type()); \ + mono_field_set_value(p_object, mono_field, &from); \ } -#define SET_FROM_PRIMITIVE(m_type) \ - { \ - m_type val = p_value.operator m_type(); \ - mono_field_set_value(p_object, mono_field, &val); \ - break; \ - } - -#define SET_FROM_ARRAY_AND_BREAK(m_type) \ - { \ - MonoArray *managed = GDMonoMarshal::m_type##_to_mono_array(p_value.operator m_type()); \ - mono_field_set_value(p_object, mono_field, &managed); \ - break; \ +#define SET_FROM_ARRAY(m_type) \ + { \ + MonoArray *managed = GDMonoMarshal::m_type##_to_mono_array(p_value.operator ::m_type()); \ + mono_field_set_value(p_object, mono_field, &managed); \ } switch (type.type_encoding) { case MONO_TYPE_BOOLEAN: { - SET_FROM_PRIMITIVE(bool); + MonoBoolean val = p_value.operator bool(); + mono_field_set_value(p_object, mono_field, &val); + } break; + + case MONO_TYPE_CHAR: { + int16_t val = p_value.operator unsigned short(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_I1: { - SET_FROM_PRIMITIVE(signed char); + int8_t val = p_value.operator signed char(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_I2: { - SET_FROM_PRIMITIVE(signed short); + int16_t val = p_value.operator signed short(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_I4: { - SET_FROM_PRIMITIVE(signed int); + int32_t val = p_value.operator signed int(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_I8: { - SET_FROM_PRIMITIVE(int64_t); + int64_t val = p_value.operator int64_t(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_U1: { - SET_FROM_PRIMITIVE(unsigned char); + uint8_t val = p_value.operator unsigned char(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_U2: { - SET_FROM_PRIMITIVE(unsigned short); + uint16_t val = p_value.operator unsigned short(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_U4: { - SET_FROM_PRIMITIVE(unsigned int); + uint32_t val = p_value.operator unsigned int(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_U8: { - SET_FROM_PRIMITIVE(uint64_t); + uint64_t val = p_value.operator uint64_t(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_R4: { - SET_FROM_PRIMITIVE(float); + float val = p_value.operator float(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_R8: { - SET_FROM_PRIMITIVE(double); + double val = p_value.operator double(); + mono_field_set_value(p_object, mono_field, &val); } break; case MONO_TYPE_STRING: { @@ -109,38 +115,117 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ case MONO_TYPE_VALUETYPE: { GDMonoClass *tclass = type.type_class; - if (tclass == CACHED_CLASS(Vector2)) - SET_FROM_STRUCT_AND_BREAK(Vector2); + if (tclass == CACHED_CLASS(Vector2)) { + SET_FROM_STRUCT(Vector2); + break; + } - if (tclass == CACHED_CLASS(Rect2)) - SET_FROM_STRUCT_AND_BREAK(Rect2); + if (tclass == CACHED_CLASS(Rect2)) { + SET_FROM_STRUCT(Rect2); + break; + } - if (tclass == CACHED_CLASS(Transform2D)) - SET_FROM_STRUCT_AND_BREAK(Transform2D); + if (tclass == CACHED_CLASS(Transform2D)) { + SET_FROM_STRUCT(Transform2D); + break; + } - if (tclass == CACHED_CLASS(Vector3)) - SET_FROM_STRUCT_AND_BREAK(Vector3); + if (tclass == CACHED_CLASS(Vector3)) { + SET_FROM_STRUCT(Vector3); + break; + } - if (tclass == CACHED_CLASS(Basis)) - SET_FROM_STRUCT_AND_BREAK(Basis); + if (tclass == CACHED_CLASS(Basis)) { + SET_FROM_STRUCT(Basis); + break; + } - if (tclass == CACHED_CLASS(Quat)) - SET_FROM_STRUCT_AND_BREAK(Quat); + if (tclass == CACHED_CLASS(Quat)) { + SET_FROM_STRUCT(Quat); + break; + } - if (tclass == CACHED_CLASS(Transform)) - SET_FROM_STRUCT_AND_BREAK(Transform); + if (tclass == CACHED_CLASS(Transform)) { + SET_FROM_STRUCT(Transform); + break; + } - if (tclass == CACHED_CLASS(AABB)) - SET_FROM_STRUCT_AND_BREAK(AABB); + if (tclass == CACHED_CLASS(AABB)) { + SET_FROM_STRUCT(AABB); + break; + } - if (tclass == CACHED_CLASS(Color)) - SET_FROM_STRUCT_AND_BREAK(Color); + if (tclass == CACHED_CLASS(Color)) { + SET_FROM_STRUCT(Color); + break; + } - if (tclass == CACHED_CLASS(Plane)) - SET_FROM_STRUCT_AND_BREAK(Plane); + if (tclass == CACHED_CLASS(Plane)) { + SET_FROM_STRUCT(Plane); + break; + } + + if (mono_class_is_enum(tclass->get_mono_ptr())) { + MonoType *enum_basetype = mono_class_enum_basetype(tclass->get_mono_ptr()); + switch (mono_type_get_type(enum_basetype)) { + case MONO_TYPE_BOOLEAN: { + MonoBoolean val = p_value.operator bool(); + mono_field_set_value(p_object, mono_field, &val); + break; + } + case MONO_TYPE_CHAR: { + uint16_t val = p_value.operator unsigned short(); + mono_field_set_value(p_object, mono_field, &val); + break; + } + case MONO_TYPE_I1: { + int8_t val = p_value.operator signed char(); + mono_field_set_value(p_object, mono_field, &val); + break; + } + case MONO_TYPE_I2: { + int16_t val = p_value.operator signed short(); + mono_field_set_value(p_object, mono_field, &val); + break; + } + case MONO_TYPE_I4: { + int32_t val = p_value.operator signed int(); + mono_field_set_value(p_object, mono_field, &val); + break; + } + case MONO_TYPE_I8: { + int64_t val = p_value.operator int64_t(); + mono_field_set_value(p_object, mono_field, &val); + break; + } + case MONO_TYPE_U1: { + uint8_t val = p_value.operator unsigned char(); + mono_field_set_value(p_object, mono_field, &val); + break; + } + case MONO_TYPE_U2: { + uint16_t val = p_value.operator unsigned short(); + mono_field_set_value(p_object, mono_field, &val); + break; + } + case MONO_TYPE_U4: { + uint32_t val = p_value.operator unsigned int(); + mono_field_set_value(p_object, mono_field, &val); + break; + } + case MONO_TYPE_U8: { + uint64_t val = p_value.operator uint64_t(); + mono_field_set_value(p_object, mono_field, &val); + break; + } + default: { + ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed enum value of unmarshallable base type."); + ERR_FAIL(); + } + } - if (mono_class_is_enum(tclass->get_mono_ptr())) - SET_FROM_PRIMITIVE(signed int); + break; + } ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + tclass->get_name()); ERR_FAIL(); @@ -148,31 +233,47 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ case MONO_TYPE_ARRAY: case MONO_TYPE_SZARRAY: { - MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(type.type_class)); + MonoArrayType *array_type = mono_type_get_array_type(type.type_class->get_mono_type()); - if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) - SET_FROM_ARRAY_AND_BREAK(Array); + if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) { + SET_FROM_ARRAY(Array); + break; + } - if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) - SET_FROM_ARRAY_AND_BREAK(PoolByteArray); + if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) { + SET_FROM_ARRAY(PoolByteArray); + break; + } - if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) - SET_FROM_ARRAY_AND_BREAK(PoolIntArray); + if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) { + SET_FROM_ARRAY(PoolIntArray); + break; + } - if (array_type->eklass == REAL_T_MONOCLASS) - SET_FROM_ARRAY_AND_BREAK(PoolRealArray); + if (array_type->eklass == REAL_T_MONOCLASS) { + SET_FROM_ARRAY(PoolRealArray); + break; + } - if (array_type->eklass == CACHED_CLASS_RAW(String)) - SET_FROM_ARRAY_AND_BREAK(PoolStringArray); + if (array_type->eklass == CACHED_CLASS_RAW(String)) { + SET_FROM_ARRAY(PoolStringArray); + break; + } - if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) - SET_FROM_ARRAY_AND_BREAK(PoolVector2Array); + if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) { + SET_FROM_ARRAY(PoolVector2Array); + break; + } - if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) - SET_FROM_ARRAY_AND_BREAK(PoolVector3Array); + if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) { + SET_FROM_ARRAY(PoolVector3Array); + break; + } - if (array_type->eklass == CACHED_CLASS_RAW(Color)) - SET_FROM_ARRAY_AND_BREAK(PoolColorArray); + if (array_type->eklass == CACHED_CLASS_RAW(Color)) { + SET_FROM_ARRAY(PoolColorArray); + break; + } ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed array of unmarshallable element type."); ERR_FAIL(); @@ -200,6 +301,18 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } + if (CACHED_CLASS(Dictionary) == type_class) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary)); + mono_field_set_value(p_object, mono_field, managed); + break; + } + + if (CACHED_CLASS(Array) == type_class) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(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()); ERR_FAIL(); } break; @@ -208,32 +321,56 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ // Variant switch (p_value.get_type()) { case Variant::BOOL: { - SET_FROM_PRIMITIVE(bool); + MonoBoolean val = p_value.operator bool(); + mono_field_set_value(p_object, mono_field, &val); } break; case Variant::INT: { - SET_FROM_PRIMITIVE(int); + int32_t val = p_value.operator signed int(); + mono_field_set_value(p_object, mono_field, &val); } break; case Variant::REAL: { #ifdef REAL_T_IS_DOUBLE - SET_FROM_PRIMITIVE(double); + double val = p_value.operator double(); + mono_field_set_value(p_object, mono_field, &val); #else - SET_FROM_PRIMITIVE(float); + float val = p_value.operator float(); + mono_field_set_value(p_object, mono_field, &val); #endif } break; case Variant::STRING: { MonoString *mono_string = GDMonoMarshal::mono_string_from_godot(p_value); mono_field_set_value(p_object, mono_field, mono_string); } break; - case Variant::VECTOR2: SET_FROM_STRUCT_AND_BREAK(Vector2); - case Variant::RECT2: SET_FROM_STRUCT_AND_BREAK(Rect2); - case Variant::VECTOR3: SET_FROM_STRUCT_AND_BREAK(Vector3); - case Variant::TRANSFORM2D: SET_FROM_STRUCT_AND_BREAK(Transform2D); - case Variant::PLANE: SET_FROM_STRUCT_AND_BREAK(Plane); - case Variant::QUAT: SET_FROM_STRUCT_AND_BREAK(Quat); - case Variant::AABB: SET_FROM_STRUCT_AND_BREAK(AABB); - case Variant::BASIS: SET_FROM_STRUCT_AND_BREAK(Basis); - case Variant::TRANSFORM: SET_FROM_STRUCT_AND_BREAK(Transform); - case Variant::COLOR: SET_FROM_STRUCT_AND_BREAK(Color); + case Variant::VECTOR2: { + SET_FROM_STRUCT(Vector2); + } break; + case Variant::RECT2: { + SET_FROM_STRUCT(Rect2); + } break; + case Variant::VECTOR3: { + SET_FROM_STRUCT(Vector3); + } break; + case Variant::TRANSFORM2D: { + SET_FROM_STRUCT(Transform2D); + } break; + case Variant::PLANE: { + SET_FROM_STRUCT(Plane); + } break; + case Variant::QUAT: { + SET_FROM_STRUCT(Quat); + } break; + case Variant::AABB: { + SET_FROM_STRUCT(AABB); + } break; + case Variant::BASIS: { + SET_FROM_STRUCT(Basis); + } break; + case Variant::TRANSFORM: { + SET_FROM_STRUCT(Transform); + } break; + case Variant::COLOR: { + SET_FROM_STRUCT(Color); + } break; case Variant::NODE_PATH: { MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator NodePath()); mono_field_set_value(p_object, mono_field, managed); @@ -248,25 +385,61 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } case Variant::DICTIONARY: { - MonoObject *managed = GDMonoMarshal::Dictionary_to_mono_object(p_value.operator Dictionary()); + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary)); mono_field_set_value(p_object, mono_field, managed); } break; - case Variant::ARRAY: SET_FROM_ARRAY_AND_BREAK(Array); - case Variant::POOL_BYTE_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolByteArray); - case Variant::POOL_INT_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolIntArray); - case Variant::POOL_REAL_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolRealArray); - case Variant::POOL_STRING_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolStringArray); - case Variant::POOL_VECTOR2_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolVector2Array); - case Variant::POOL_VECTOR3_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolVector3Array); - case Variant::POOL_COLOR_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolColorArray); -#undef SET_FROM_ARRAY_AND_BREAK + case Variant::ARRAY: { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); + mono_field_set_value(p_object, mono_field, managed); + } break; + case Variant::POOL_BYTE_ARRAY: { + SET_FROM_ARRAY(PoolByteArray); + } break; + case Variant::POOL_INT_ARRAY: { + SET_FROM_ARRAY(PoolIntArray); + } break; + case Variant::POOL_REAL_ARRAY: { + SET_FROM_ARRAY(PoolRealArray); + } break; + case Variant::POOL_STRING_ARRAY: { + SET_FROM_ARRAY(PoolStringArray); + } break; + case Variant::POOL_VECTOR2_ARRAY: { + SET_FROM_ARRAY(PoolVector2Array); + } break; + case Variant::POOL_VECTOR3_ARRAY: { + SET_FROM_ARRAY(PoolVector3Array); + } break; + case Variant::POOL_COLOR_ARRAY: { + SET_FROM_ARRAY(PoolColorArray); + } break; default: break; } } break; case MONO_TYPE_GENERICINST: { - if (CACHED_RAW_MONO_CLASS(Dictionary) == type.type_class->get_mono_ptr()) { - MonoObject *managed = GDMonoMarshal::Dictionary_to_mono_object(p_value.operator Dictionary()); + MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type()); + + MonoException *exc = NULL; + + GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); + MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_dict) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), type.type_class); + mono_field_set_value(p_object, mono_field, managed); + break; + } + + exc = NULL; + + GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); + MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_array) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), type.type_class); mono_field_set_value(p_object, mono_field, managed); break; } @@ -277,8 +450,8 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } break; } +#undef SET_FROM_ARRAY_AND_BREAK #undef SET_FROM_STRUCT_AND_BREAK -#undef SET_FROM_PRIMITIVE } MonoObject *GDMonoField::get_value(MonoObject *p_object) { diff --git a/modules/mono/mono_gd/gd_mono_header.h b/modules/mono/mono_gd/gd_mono_header.h index 2b5110f0b9..51d0c9b356 100644 --- a/modules/mono/mono_gd/gd_mono_header.h +++ b/modules/mono/mono_gd/gd_mono_header.h @@ -31,7 +31,7 @@ #ifndef GD_MONO_HEADER_H #define GD_MONO_HEADER_H -#include "int_types.h" +#include "core/int_types.h" class GDMonoAssembly; class GDMonoClass; @@ -44,19 +44,15 @@ struct ManagedType { int type_encoding; GDMonoClass *type_class; - ManagedType() { - type_class = 0; + ManagedType() : + type_encoding(0), + type_class(NULL) { } -}; - -typedef union { - uint32_t _uint32; - float _float; -} mono_float; -typedef union { - uint64_t _uint64; - float _double; -} mono_double; + ManagedType(int p_type_encoding, GDMonoClass *p_type_class) : + type_encoding(p_type_encoding), + type_class(p_type_class) { + } +}; #endif // GD_MONO_HEADER_H diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp index eabea8dc3c..b7bb2cb2d6 100644 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -33,8 +33,8 @@ #include <mono/utils/mono-logger.h> #include <stdlib.h> // abort -#include "os/dir_access.h" -#include "os/os.h" +#include "core/os/dir_access.h" +#include "core/os/os.h" #include "../godotsharp_dirs.h" @@ -152,8 +152,7 @@ void GDMonoLog::initialize() { log_level_id = log_level_get_id(log_level); if (log_file) { - if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->print(String("Mono: Logfile is " + log_file_path + "\n").utf8()); + print_verbose("Mono: Logfile is " + log_file_path); mono_trace_set_log_handler(gdmono_MonoLogCallback, this); } else { OS::get_singleton()->printerr("Mono: No log file, using default log handler\n"); diff --git a/modules/mono/mono_gd/gd_mono_log.h b/modules/mono/mono_gd/gd_mono_log.h index a7e374858c..3b4ff07b7b 100644 --- a/modules/mono/mono_gd/gd_mono_log.h +++ b/modules/mono/mono_gd/gd_mono_log.h @@ -31,7 +31,7 @@ #ifndef GD_MONO_LOG_H #define GD_MONO_LOG_H -#include "os/file_access.h" +#include "core/os/file_access.h" class GDMonoLog { diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index 31c5bbb2fb..3f0a5d6e50 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -35,20 +35,6 @@ namespace GDMonoMarshal { -#define RETURN_BOXED_STRUCT(m_t, m_var_in) \ - { \ - const m_t &m_in = m_var_in->operator ::m_t(); \ - MARSHALLED_OUT(m_t, m_in, raw); \ - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(m_t), raw); \ - } - -#define RETURN_UNBOXED_STRUCT(m_t, m_var_in) \ - { \ - float *raw = (float *)mono_object_unbox(m_var_in); \ - MARSHALLED_IN(m_t, raw, ret); \ - return ret; \ - } - Variant::Type managed_to_variant_type(const ManagedType &p_type) { switch (p_type.type_encoding) { case MONO_TYPE_BOOLEAN: @@ -120,7 +106,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { case MONO_TYPE_ARRAY: case MONO_TYPE_SZARRAY: { - MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(p_type.type_class)); + MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type()); if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) return Variant::ARRAY; @@ -162,12 +148,36 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { if (CACHED_CLASS(RID) == type_class) { return Variant::_RID; } + + if (CACHED_CLASS(Dictionary) == type_class) { + return Variant::DICTIONARY; + } + + if (CACHED_CLASS(Array) == type_class) { + return Variant::ARRAY; + } } break; case MONO_TYPE_GENERICINST: { - if (CACHED_RAW_MONO_CLASS(Dictionary) == p_type.type_class->get_mono_ptr()) { + MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type()); + + MonoException *exc = NULL; + GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); + MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_dict) { return Variant::DICTIONARY; } + + exc = NULL; + GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); + MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_array) { + return Variant::ARRAY; + } } break; default: { @@ -182,8 +192,11 @@ String mono_to_utf8_string(MonoString *p_mono_string) { MonoError error; char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error); - ERR_EXPLAIN("Conversion of MonoString to UTF8 failed."); - ERR_FAIL_COND_V(!mono_error_ok(&error), String()); + if (!mono_error_ok(&error)) { + ERR_PRINTS(String("Failed to convert MonoString* to UTF-8: ") + mono_error_get_message(&error)); + mono_error_cleanup(&error); + return String(); + } String ret = String::utf8(utf8); @@ -216,6 +229,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var) { ManagedType type; type.type_encoding = MONO_TYPE_OBJECT; + // type.type_class is not needed when we specify the MONO_TYPE_OBJECT encoding return variant_to_mono_object(p_var, type); } @@ -227,16 +241,21 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return BOX_BOOLEAN(val); } + case MONO_TYPE_CHAR: { + uint16_t val = p_var->operator unsigned short(); + return BOX_UINT16(val); + } + case MONO_TYPE_I1: { - char val = p_var->operator signed char(); + int8_t val = p_var->operator signed char(); return BOX_INT8(val); } case MONO_TYPE_I2: { - short val = p_var->operator signed short(); + int16_t val = p_var->operator signed short(); return BOX_INT16(val); } case MONO_TYPE_I4: { - int val = p_var->operator signed int(); + int32_t val = p_var->operator signed int(); return BOX_INT32(val); } case MONO_TYPE_I8: { @@ -245,15 +264,15 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } case MONO_TYPE_U1: { - char val = p_var->operator unsigned char(); + uint8_t val = p_var->operator unsigned char(); return BOX_UINT8(val); } case MONO_TYPE_U2: { - short val = p_var->operator unsigned short(); + uint16_t val = p_var->operator unsigned short(); return BOX_UINT16(val); } case MONO_TYPE_U4: { - int val = p_var->operator unsigned int(); + uint32_t val = p_var->operator unsigned int(); return BOX_UINT32(val); } case MONO_TYPE_U8: { @@ -277,45 +296,111 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty case MONO_TYPE_VALUETYPE: { GDMonoClass *tclass = p_type.type_class; - if (tclass == CACHED_CLASS(Vector2)) - RETURN_BOXED_STRUCT(Vector2, p_var); + if (tclass == CACHED_CLASS(Vector2)) { + GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_var->operator ::Vector2()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from); + } - if (tclass == CACHED_CLASS(Rect2)) - RETURN_BOXED_STRUCT(Rect2, p_var); + if (tclass == CACHED_CLASS(Rect2)) { + GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_var->operator ::Rect2()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2), &from); + } - if (tclass == CACHED_CLASS(Transform2D)) - RETURN_BOXED_STRUCT(Transform2D, p_var); + if (tclass == CACHED_CLASS(Transform2D)) { + GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_var->operator ::Transform2D()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform2D), &from); + } - if (tclass == CACHED_CLASS(Vector3)) - RETURN_BOXED_STRUCT(Vector3, p_var); + if (tclass == CACHED_CLASS(Vector3)) { + GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_var->operator ::Vector3()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3), &from); + } - if (tclass == CACHED_CLASS(Basis)) - RETURN_BOXED_STRUCT(Basis, p_var); + if (tclass == CACHED_CLASS(Basis)) { + GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_var->operator ::Basis()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Basis), &from); + } - if (tclass == CACHED_CLASS(Quat)) - RETURN_BOXED_STRUCT(Quat, p_var); + if (tclass == CACHED_CLASS(Quat)) { + GDMonoMarshal::M_Quat from = MARSHALLED_OUT(Quat, p_var->operator ::Quat()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Quat), &from); + } - if (tclass == CACHED_CLASS(Transform)) - RETURN_BOXED_STRUCT(Transform, p_var); + if (tclass == CACHED_CLASS(Transform)) { + GDMonoMarshal::M_Transform from = MARSHALLED_OUT(Transform, p_var->operator ::Transform()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform), &from); + } - if (tclass == CACHED_CLASS(AABB)) - RETURN_BOXED_STRUCT(AABB, p_var); + if (tclass == CACHED_CLASS(AABB)) { + GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_var->operator ::AABB()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(AABB), &from); + } - if (tclass == CACHED_CLASS(Color)) - RETURN_BOXED_STRUCT(Color, p_var); + if (tclass == CACHED_CLASS(Color)) { + GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_var->operator ::Color()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Color), &from); + } - if (tclass == CACHED_CLASS(Plane)) - RETURN_BOXED_STRUCT(Plane, p_var); + if (tclass == CACHED_CLASS(Plane)) { + GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_var->operator ::Plane()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Plane), &from); + } if (mono_class_is_enum(tclass->get_mono_ptr())) { - int val = p_var->operator signed int(); - return BOX_ENUM(tclass->get_mono_ptr(), val); + MonoType *enum_basetype = mono_class_enum_basetype(tclass->get_mono_ptr()); + MonoClass *enum_baseclass = mono_class_from_mono_type(enum_basetype); + switch (mono_type_get_type(enum_basetype)) { + case MONO_TYPE_BOOLEAN: { + MonoBoolean val = p_var->operator bool(); + return BOX_ENUM(enum_baseclass, val); + } + case MONO_TYPE_CHAR: { + uint16_t val = p_var->operator unsigned short(); + return BOX_ENUM(enum_baseclass, val); + } + case MONO_TYPE_I1: { + int8_t val = p_var->operator signed char(); + return BOX_ENUM(enum_baseclass, val); + } + case MONO_TYPE_I2: { + int16_t val = p_var->operator signed short(); + return BOX_ENUM(enum_baseclass, val); + } + case MONO_TYPE_I4: { + int32_t val = p_var->operator signed int(); + return BOX_ENUM(enum_baseclass, val); + } + case MONO_TYPE_I8: { + int64_t val = p_var->operator int64_t(); + return BOX_ENUM(enum_baseclass, val); + } + case MONO_TYPE_U1: { + uint8_t val = p_var->operator unsigned char(); + return BOX_ENUM(enum_baseclass, val); + } + case MONO_TYPE_U2: { + uint16_t val = p_var->operator unsigned short(); + return BOX_ENUM(enum_baseclass, val); + } + case MONO_TYPE_U4: { + uint32_t val = p_var->operator unsigned int(); + return BOX_ENUM(enum_baseclass, val); + } + case MONO_TYPE_U8: { + uint64_t val = p_var->operator uint64_t(); + return BOX_ENUM(enum_baseclass, val); + } + default: { + ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed enum value of unmarshallable base type."); + ERR_FAIL_V(NULL); + } + } } } break; case MONO_TYPE_ARRAY: case MONO_TYPE_SZARRAY: { - MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(p_type.type_class)); + MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type()); if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) return (MonoObject *)Array_to_mono_array(p_var->operator Array()); @@ -360,6 +445,14 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty if (CACHED_CLASS(RID) == type_class) { return GDMonoUtils::create_managed_from(p_var->operator RID()); } + + if (CACHED_CLASS(Dictionary) == type_class) { + return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary)); + } + + if (CACHED_CLASS(Array) == type_class) { + return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); + } } break; case MONO_TYPE_OBJECT: { // Variant @@ -369,7 +462,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return BOX_BOOLEAN(val); } case Variant::INT: { - int val = p_var->operator signed int(); + int32_t val = p_var->operator signed int(); return BOX_INT32(val); } case Variant::REAL: { @@ -383,37 +476,56 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } case Variant::STRING: return (MonoObject *)mono_string_from_godot(p_var->operator String()); - case Variant::VECTOR2: - RETURN_BOXED_STRUCT(Vector2, p_var); - case Variant::RECT2: - RETURN_BOXED_STRUCT(Rect2, p_var); - case Variant::VECTOR3: - RETURN_BOXED_STRUCT(Vector3, p_var); - case Variant::TRANSFORM2D: - RETURN_BOXED_STRUCT(Transform2D, p_var); - case Variant::PLANE: - RETURN_BOXED_STRUCT(Plane, p_var); - case Variant::QUAT: - RETURN_BOXED_STRUCT(Quat, p_var); - case Variant::AABB: - RETURN_BOXED_STRUCT(AABB, p_var); - case Variant::BASIS: - RETURN_BOXED_STRUCT(Basis, p_var); - case Variant::TRANSFORM: - RETURN_BOXED_STRUCT(Transform, p_var); - case Variant::COLOR: - RETURN_BOXED_STRUCT(Color, p_var); + case Variant::VECTOR2: { + GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_var->operator ::Vector2()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from); + } + case Variant::RECT2: { + GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_var->operator ::Rect2()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2), &from); + } + case Variant::VECTOR3: { + GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_var->operator ::Vector3()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3), &from); + } + case Variant::TRANSFORM2D: { + GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_var->operator ::Transform2D()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform2D), &from); + } + case Variant::PLANE: { + GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_var->operator ::Plane()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Plane), &from); + } + case Variant::QUAT: { + GDMonoMarshal::M_Quat from = MARSHALLED_OUT(Quat, p_var->operator ::Quat()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Quat), &from); + } + case Variant::AABB: { + GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_var->operator ::AABB()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(AABB), &from); + } + case Variant::BASIS: { + GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_var->operator ::Basis()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Basis), &from); + } + case Variant::TRANSFORM: { + GDMonoMarshal::M_Transform from = MARSHALLED_OUT(Transform, p_var->operator ::Transform()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform), &from); + } + case Variant::COLOR: { + GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_var->operator ::Color()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Color), &from); + } case Variant::NODE_PATH: return GDMonoUtils::create_managed_from(p_var->operator NodePath()); case Variant::_RID: return GDMonoUtils::create_managed_from(p_var->operator RID()); - case Variant::OBJECT: { + case Variant::OBJECT: return GDMonoUtils::unmanaged_get_managed(p_var->operator Object *()); - } case Variant::DICTIONARY: - return Dictionary_to_mono_object(p_var->operator Dictionary()); + return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary)); case Variant::ARRAY: - return (MonoObject *)Array_to_mono_array(p_var->operator Array()); + return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); case Variant::POOL_BYTE_ARRAY: return (MonoObject *)PoolByteArray_to_mono_array(p_var->operator PoolByteArray()); case Variant::POOL_INT_ARRAY: @@ -433,8 +545,24 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } break; case MONO_TYPE_GENERICINST: { - if (CACHED_RAW_MONO_CLASS(Dictionary) == p_type.type_class->get_mono_ptr()) { - return Dictionary_to_mono_object(p_var->operator Dictionary()); + MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type()); + + MonoException *exc = NULL; + GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); + MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_dict) { + return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), p_type.type_class); + } + + exc = NULL; + GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); + MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_array) { + return GDMonoUtils::create_managed_from(p_var->operator Array(), p_type.type_class); } } break; } break; @@ -452,7 +580,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) { GDMonoClass *tclass = GDMono::get_singleton()->get_class(mono_object_get_class(p_obj)); ERR_FAIL_COND_V(!tclass, Variant()); - MonoType *raw_type = tclass->get_raw_type(tclass); + MonoType *raw_type = tclass->get_mono_type(); ManagedType type; @@ -463,6 +591,9 @@ Variant mono_object_to_variant(MonoObject *p_obj) { case MONO_TYPE_BOOLEAN: return (bool)unbox<MonoBoolean>(p_obj); + case MONO_TYPE_CHAR: + return unbox<uint16_t>(p_obj); + case MONO_TYPE_I1: return unbox<int8_t>(p_obj); case MONO_TYPE_I2: @@ -496,34 +627,34 @@ Variant mono_object_to_variant(MonoObject *p_obj) { GDMonoClass *tclass = type.type_class; if (tclass == CACHED_CLASS(Vector2)) - RETURN_UNBOXED_STRUCT(Vector2, p_obj); + return MARSHALLED_IN(Vector2, (GDMonoMarshal::M_Vector2 *)mono_object_unbox(p_obj)); if (tclass == CACHED_CLASS(Rect2)) - RETURN_UNBOXED_STRUCT(Rect2, p_obj); + return MARSHALLED_IN(Rect2, (GDMonoMarshal::M_Rect2 *)mono_object_unbox(p_obj)); if (tclass == CACHED_CLASS(Transform2D)) - RETURN_UNBOXED_STRUCT(Transform2D, p_obj); + return MARSHALLED_IN(Transform2D, (GDMonoMarshal::M_Transform2D *)mono_object_unbox(p_obj)); if (tclass == CACHED_CLASS(Vector3)) - RETURN_UNBOXED_STRUCT(Vector3, p_obj); + return MARSHALLED_IN(Vector3, (GDMonoMarshal::M_Vector3 *)mono_object_unbox(p_obj)); if (tclass == CACHED_CLASS(Basis)) - RETURN_UNBOXED_STRUCT(Basis, p_obj); + return MARSHALLED_IN(Basis, (GDMonoMarshal::M_Basis *)mono_object_unbox(p_obj)); if (tclass == CACHED_CLASS(Quat)) - RETURN_UNBOXED_STRUCT(Quat, p_obj); + return MARSHALLED_IN(Quat, (GDMonoMarshal::M_Quat *)mono_object_unbox(p_obj)); if (tclass == CACHED_CLASS(Transform)) - RETURN_UNBOXED_STRUCT(Transform, p_obj); + return MARSHALLED_IN(Transform, (GDMonoMarshal::M_Transform *)mono_object_unbox(p_obj)); if (tclass == CACHED_CLASS(AABB)) - RETURN_UNBOXED_STRUCT(AABB, p_obj); + return MARSHALLED_IN(AABB, (GDMonoMarshal::M_AABB *)mono_object_unbox(p_obj)); if (tclass == CACHED_CLASS(Color)) - RETURN_UNBOXED_STRUCT(Color, p_obj); + return MARSHALLED_IN(Color, (GDMonoMarshal::M_Color *)mono_object_unbox(p_obj)); if (tclass == CACHED_CLASS(Plane)) - RETURN_UNBOXED_STRUCT(Plane, p_obj); + return MARSHALLED_IN(Plane, (GDMonoMarshal::M_Plane *)mono_object_unbox(p_obj)); if (mono_class_is_enum(tclass->get_mono_ptr())) return unbox<int32_t>(p_obj); @@ -531,7 +662,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) { case MONO_TYPE_ARRAY: case MONO_TYPE_SZARRAY: { - MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(type.type_class)); + MonoArrayType *array_type = mono_type_get_array_type(type.type_class->get_mono_type()); if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) return mono_array_to_Array((MonoArray *)p_obj); @@ -579,11 +710,49 @@ Variant mono_object_to_variant(MonoObject *p_obj) { RID *ptr = unbox<RID *>(CACHED_FIELD(RID, ptr)->get_value(p_obj)); return ptr ? Variant(*ptr) : Variant(); } + + if (CACHED_CLASS(Array) == type_class) { + MonoException *exc = NULL; + Array *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Array, GetPtr), p_obj, (MonoObject **)&exc); + UNLIKELY_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, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + return ptr ? Variant(*ptr) : Variant(); + } } break; case MONO_TYPE_GENERICINST: { - if (CACHED_RAW_MONO_CLASS(Dictionary) == type.type_class->get_mono_ptr()) { - return mono_object_to_Dictionary(p_obj); + MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type()); + + MonoException *exc = NULL; + + GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); + MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_dict) { + MonoException *exc = NULL; + MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + return *unbox<Dictionary *>(ret); + } + + exc = NULL; + + GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); + MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_array) { + MonoException *exc = NULL; + MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + return *unbox<Array *>(ret); } } break; } @@ -619,13 +788,13 @@ Array mono_array_to_Array(MonoArray *p_array) { return ret; } -// TODO Optimize reading/writing from/to PoolArrays - MonoArray *PoolIntArray_to_mono_array(const PoolIntArray &p_array) { + PoolIntArray::Read r = p_array.read(); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int32_t), p_array.size()); for (int i = 0; i < p_array.size(); i++) { - mono_array_set(ret, int32_t, i, p_array[i]); + mono_array_set(ret, int32_t, i, r[i]); } return ret; @@ -637,19 +806,22 @@ PoolIntArray mono_array_to_PoolIntArray(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); + PoolIntArray::Write w = ret.write(); + for (int i = 0; i < length; i++) { - int32_t elem = mono_array_get(p_array, int32_t, i); - ret.set(i, elem); + w[i] = mono_array_get(p_array, int32_t, i); } return ret; } MonoArray *PoolByteArray_to_mono_array(const PoolByteArray &p_array) { + PoolByteArray::Read r = p_array.read(); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), p_array.size()); for (int i = 0; i < p_array.size(); i++) { - mono_array_set(ret, uint8_t, i, p_array[i]); + mono_array_set(ret, uint8_t, i, r[i]); } return ret; @@ -661,20 +833,22 @@ PoolByteArray mono_array_to_PoolByteArray(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); + PoolByteArray::Write w = ret.write(); for (int i = 0; i < length; i++) { - uint8_t elem = mono_array_get(p_array, uint8_t, i); - ret.set(i, elem); + w[i] = mono_array_get(p_array, uint8_t, i); } return ret; } MonoArray *PoolRealArray_to_mono_array(const PoolRealArray &p_array) { + PoolRealArray::Read r = p_array.read(); + MonoArray *ret = mono_array_new(mono_domain_get(), REAL_T_MONOCLASS, p_array.size()); for (int i = 0; i < p_array.size(); i++) { - mono_array_set(ret, real_t, i, p_array[i]); + mono_array_set(ret, real_t, i, r[i]); } return ret; @@ -686,20 +860,22 @@ PoolRealArray mono_array_to_PoolRealArray(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); + PoolRealArray::Write w = ret.write(); for (int i = 0; i < length; i++) { - real_t elem = mono_array_get(p_array, real_t, i); - ret.set(i, elem); + w[i] = mono_array_get(p_array, real_t, i); } return ret; } MonoArray *PoolStringArray_to_mono_array(const PoolStringArray &p_array) { + PoolStringArray::Read r = p_array.read(); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_array.size()); for (int i = 0; i < p_array.size(); i++) { - MonoString *boxed = mono_string_from_godot(p_array[i]); + MonoString *boxed = mono_string_from_godot(r[i]); mono_array_set(ret, MonoString *, i, boxed); } @@ -712,29 +888,24 @@ PoolStringArray mono_array_to_PoolStringArray(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); + PoolStringArray::Write w = ret.write(); for (int i = 0; i < length; i++) { MonoString *elem = mono_array_get(p_array, MonoString *, i); - ret.set(i, mono_string_to_godot(elem)); + w[i] = mono_string_to_godot(elem); } return ret; } MonoArray *PoolColorArray_to_mono_array(const PoolColorArray &p_array) { + PoolColorArray::Read r = p_array.read(); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Color), p_array.size()); for (int i = 0; i < p_array.size(); i++) { -#ifdef YOLOCOPY - mono_array_set(ret, Color, i, p_array[i]); -#else - real_t *raw = (real_t *)mono_array_addr_with_size(ret, sizeof(real_t) * 4, i); - const Color &elem = p_array[i]; - raw[0] = elem.r; - raw[1] = elem.g; - raw[2] = elem.b; - raw[3] = elem.a; -#endif + M_Color *raw = (M_Color *)mono_array_addr_with_size(ret, sizeof(M_Color), i); + *raw = MARSHALLED_OUT(Color, r[i]); } return ret; @@ -746,28 +917,23 @@ PoolColorArray mono_array_to_PoolColorArray(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); + PoolColorArray::Write w = ret.write(); for (int i = 0; i < length; i++) { - real_t *raw_elem = (real_t *)mono_array_addr_with_size(p_array, sizeof(real_t) * 4, i); - MARSHALLED_IN(Color, raw_elem, elem); - ret.set(i, elem); + w[i] = MARSHALLED_IN(Color, (M_Color *)mono_array_addr_with_size(p_array, sizeof(M_Color), i)); } return ret; } MonoArray *PoolVector2Array_to_mono_array(const PoolVector2Array &p_array) { + PoolVector2Array::Read r = p_array.read(); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector2), p_array.size()); for (int i = 0; i < p_array.size(); i++) { -#ifdef YOLOCOPY - mono_array_set(ret, Vector2, i, p_array[i]); -#else - real_t *raw = (real_t *)mono_array_addr_with_size(ret, sizeof(real_t) * 2, i); - const Vector2 &elem = p_array[i]; - raw[0] = elem.x; - raw[1] = elem.y; -#endif + M_Vector2 *raw = (M_Vector2 *)mono_array_addr_with_size(ret, sizeof(M_Vector2), i); + *raw = MARSHALLED_OUT(Vector2, r[i]); } return ret; @@ -779,29 +945,23 @@ PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); + PoolVector2Array::Write w = ret.write(); for (int i = 0; i < length; i++) { - real_t *raw_elem = (real_t *)mono_array_addr_with_size(p_array, sizeof(real_t) * 2, i); - MARSHALLED_IN(Vector2, raw_elem, elem); - ret.set(i, elem); + w[i] = MARSHALLED_IN(Vector2, (M_Vector2 *)mono_array_addr_with_size(p_array, sizeof(M_Vector2), i)); } return ret; } MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array) { + PoolVector3Array::Read r = p_array.read(); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector3), p_array.size()); for (int i = 0; i < p_array.size(); i++) { -#ifdef YOLOCOPY - mono_array_set(ret, Vector3, i, p_array[i]); -#else - real_t *raw = (real_t *)mono_array_addr_with_size(ret, sizeof(real_t) * 3, i); - const Vector3 &elem = p_array[i]; - raw[0] = elem.x; - raw[1] = elem.y; - raw[2] = elem.z; -#endif + M_Vector3 *raw = (M_Vector3 *)mono_array_addr_with_size(ret, sizeof(M_Vector3), i); + *raw = MARSHALLED_OUT(Vector3, r[i]); } return ret; @@ -813,75 +973,12 @@ PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); + PoolVector3Array::Write w = ret.write(); for (int i = 0; i < length; i++) { - real_t *raw_elem = (real_t *)mono_array_addr_with_size(p_array, sizeof(real_t) * 3, i); - MARSHALLED_IN(Vector3, raw_elem, elem); - ret.set(i, elem); + w[i] = MARSHALLED_IN(Vector3, (M_Vector3 *)mono_array_addr_with_size(p_array, sizeof(M_Vector3), i)); } return ret; } - -MonoObject *Dictionary_to_mono_object(const Dictionary &p_dict) { - MonoArray *keys = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_dict.size()); - MonoArray *values = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_dict.size()); - - int i = 0; - const Variant *dkey = NULL; - while ((dkey = p_dict.next(dkey))) { - mono_array_set(keys, MonoObject *, i, variant_to_mono_object(dkey)); - mono_array_set(values, MonoObject *, i, variant_to_mono_object(p_dict[*dkey])); - i++; - } - - GDMonoUtils::MarshalUtils_ArraysToDict arrays_to_dict = CACHED_METHOD_THUNK(MarshalUtils, ArraysToDictionary); - - MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = arrays_to_dict(keys, values, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - ERR_FAIL_V(NULL); - } - - return ret; -} - -Dictionary mono_object_to_Dictionary(MonoObject *p_dict) { - Dictionary ret; - - if (!p_dict) - return ret; - - GDMonoUtils::MarshalUtils_DictToArrays dict_to_arrays = CACHED_METHOD_THUNK(MarshalUtils, DictionaryToArrays); - - MonoArray *keys = NULL; - MonoArray *values = NULL; - MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - dict_to_arrays(p_dict, &keys, &values, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - ERR_FAIL_V(Dictionary()); - } - - int length = mono_array_length(keys); - - for (int i = 0; i < length; i++) { - MonoObject *key_obj = mono_array_get(keys, MonoObject *, i); - MonoObject *value_obj = mono_array_get(values, MonoObject *, i); - - Variant key = key_obj ? mono_object_to_variant(key_obj) : Variant(); - Variant value = value_obj ? mono_object_to_variant(value_obj) : Variant(); - - ret[key] = value; - } - - return ret; -} -} +} // namespace GDMonoMarshal diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h index 6572408ab5..4002f2a225 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.h +++ b/modules/mono/mono_gd/gd_mono_marshal.h @@ -31,9 +31,9 @@ #ifndef GDMONOMARSHAL_H #define GDMONOMARSHAL_H +#include "core/variant.h" #include "gd_mono.h" #include "gd_mono_utils.h" -#include "variant.h" namespace GDMonoMarshal { @@ -97,10 +97,14 @@ _FORCE_INLINE_ MonoString *mono_string_from_godot(const String &p_string) { MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_type); MonoObject *variant_to_mono_object(const Variant *p_var); -_FORCE_INLINE_ MonoObject *variant_to_mono_object(Variant p_var) { +_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant &p_var) { return variant_to_mono_object(&p_var); } +_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant &p_var, const ManagedType &p_type) { + return variant_to_mono_object(&p_var, p_type); +} + Variant mono_object_to_variant(MonoObject *p_obj); // Array @@ -143,83 +147,271 @@ PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array); MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array); PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array); -// Dictionary +// Structures + +namespace InteropLayout { -MonoObject *Dictionary_to_mono_object(const Dictionary &p_dict); -Dictionary mono_object_to_Dictionary(MonoObject *p_dict); +enum { + MATCHES_float = (sizeof(float) == sizeof(uint32_t)), -#ifdef YOLO_COPY -#define MARSHALLED_OUT(m_t, m_in, m_out) m_t *m_out = (m_t *)&m_in; -#define MARSHALLED_IN(m_t, m_in, m_out) m_t m_out = *reinterpret_cast<m_t *>(m_in); + MATCHES_double = (sizeof(double) == sizeof(uint64_t)), + +#ifdef REAL_T_IS_DOUBLE + MATCHES_real_t = (sizeof(real_t) == sizeof(uint64_t)), #else + MATCHES_real_t = (sizeof(real_t) == sizeof(uint32_t)), +#endif -// Expects m_in to be of type float* + MATCHES_Vector2 = (MATCHES_real_t && (sizeof(Vector2) == (sizeof(real_t) * 2)) && + offsetof(Vector2, x) == (sizeof(real_t) * 0) && + offsetof(Vector2, y) == (sizeof(real_t) * 1)), -#define MARSHALLED_OUT(m_t, m_in, m_out) MARSHALLED_OUT_##m_t(m_in, m_out) -#define MARSHALLED_IN(m_t, m_in, m_out) MARSHALLED_IN_##m_t(m_in, m_out) + MATCHES_Rect2 = (MATCHES_Vector2 && (sizeof(Rect2) == (sizeof(Vector2) * 2)) && + offsetof(Rect2, position) == (sizeof(Vector2) * 0) && + offsetof(Rect2, size) == (sizeof(Vector2) * 1)), -// Vector2 + MATCHES_Transform2D = (MATCHES_Vector2 && (sizeof(Transform2D) == (sizeof(Vector2) * 3))), // No field offset required, it stores an array -#define MARSHALLED_OUT_Vector2(m_in, m_out) real_t m_out[2] = { m_in.x, m_in.y }; -#define MARSHALLED_IN_Vector2(m_in, m_out) Vector2 m_out(m_in[0], m_in[1]); + MATCHES_Vector3 = (MATCHES_real_t && (sizeof(Vector3) == (sizeof(real_t) * 3)) && + offsetof(Vector3, x) == (sizeof(real_t) * 0) && + offsetof(Vector3, y) == (sizeof(real_t) * 1) && + offsetof(Vector3, z) == (sizeof(real_t) * 2)), -// Rect2 + MATCHES_Basis = (MATCHES_Vector3 && (sizeof(Basis) == (sizeof(Vector3) * 3))), // No field offset required, it stores an array -#define MARSHALLED_OUT_Rect2(m_in, m_out) real_t m_out[4] = { m_in.position.x, m_in.position.y, m_in.size.width, m_in.size.height }; -#define MARSHALLED_IN_Rect2(m_in, m_out) Rect2 m_out(m_in[0], m_in[1], m_in[2], m_in[3]); + MATCHES_Quat = (MATCHES_real_t && (sizeof(Quat) == (sizeof(real_t) * 4)) && + offsetof(Quat, x) == (sizeof(real_t) * 0) && + offsetof(Quat, y) == (sizeof(real_t) * 1) && + offsetof(Quat, z) == (sizeof(real_t) * 2) && + offsetof(Quat, w) == (sizeof(real_t) * 3)), -// Transform2D + MATCHES_Transform = (MATCHES_Basis && MATCHES_Vector3 && (sizeof(Transform) == (sizeof(Basis) + sizeof(Vector3))) && + offsetof(Transform, basis) == 0 && + offsetof(Transform, origin) == sizeof(Basis)), -#define MARSHALLED_OUT_Transform2D(m_in, m_out) real_t m_out[6] = { m_in[0].x, m_in[0].y, m_in[1].x, m_in[1].y, m_in[2].x, m_in[2].y }; -#define MARSHALLED_IN_Transform2D(m_in, m_out) Transform2D m_out(m_in[0], m_in[1], m_in[2], m_in[3], m_in[4], m_in[5]); + MATCHES_AABB = (MATCHES_Vector3 && (sizeof(AABB) == (sizeof(Vector3) * 2)) && + offsetof(AABB, position) == (sizeof(Vector3) * 0) && + offsetof(AABB, size) == (sizeof(Vector3) * 1)), -// Vector3 + MATCHES_Color = (MATCHES_float && (sizeof(Color) == (sizeof(float) * 4)) && + offsetof(Color, r) == (sizeof(float) * 0) && + offsetof(Color, g) == (sizeof(float) * 1) && + offsetof(Color, b) == (sizeof(float) * 2) && + offsetof(Color, a) == (sizeof(float) * 3)), + + MATCHES_Plane = (MATCHES_Vector3 && MATCHES_real_t && (sizeof(Plane) == (sizeof(Vector3) + sizeof(real_t))) && + offsetof(Plane, normal) == 0 && + offsetof(Plane, d) == sizeof(Vector3)) +}; + +// In the future we may force this if we want to ref return these structs +#ifdef GD_MONO_FORCE_INTEROP_STRUCT_COPY +// Sometimes clang-format can be an ass +GD_STATIC_ASSERT(MATCHES_Vector2 &&MATCHES_Rect2 &&MATCHES_Transform2D &&MATCHES_Vector3 && + MATCHES_Basis &&MATCHES_Quat &&MATCHES_Transform &&MATCHES_AABB &&MATCHES_Color &&MATCHES_Plane); +#endif -#define MARSHALLED_OUT_Vector3(m_in, m_out) real_t m_out[3] = { m_in.x, m_in.y, m_in.z }; -#define MARSHALLED_IN_Vector3(m_in, m_out) Vector3 m_out(m_in[0], m_in[1], m_in[2]); +} // namespace InteropLayout -// Basis +#pragma pack(push, 1) -#define MARSHALLED_OUT_Basis(m_in, m_out) real_t m_out[9] = { \ - m_in[0].x, m_in[0].y, m_in[0].z, \ - m_in[1].x, m_in[1].y, m_in[1].z, \ - m_in[2].x, m_in[2].y, m_in[2].z \ +struct M_Vector2 { + real_t x, y; + + static _FORCE_INLINE_ Vector2 convert_to(const M_Vector2 &p_from) { + return Vector2(p_from.x, p_from.y); + } + + static _FORCE_INLINE_ M_Vector2 convert_from(const Vector2 &p_from) { + M_Vector2 ret = { p_from.x, p_from.y }; + return ret; + } }; -#define MARSHALLED_IN_Basis(m_in, m_out) Basis m_out(m_in[0], m_in[1], m_in[2], m_in[3], m_in[4], m_in[5], m_in[6], m_in[7], m_in[8]); -// Quat +struct M_Rect2 { + M_Vector2 position; + M_Vector2 size; -#define MARSHALLED_OUT_Quat(m_in, m_out) real_t m_out[4] = { m_in.x, m_in.y, m_in.z, m_in.w }; -#define MARSHALLED_IN_Quat(m_in, m_out) Quat m_out(m_in[0], m_in[1], m_in[2], m_in[3]); + static _FORCE_INLINE_ Rect2 convert_to(const M_Rect2 &p_from) { + return Rect2(M_Vector2::convert_to(p_from.position), + M_Vector2::convert_to(p_from.size)); + } -// Transform + static _FORCE_INLINE_ M_Rect2 convert_from(const Rect2 &p_from) { + M_Rect2 ret = { M_Vector2::convert_from(p_from.position), M_Vector2::convert_from(p_from.size) }; + return ret; + } +}; -#define MARSHALLED_OUT_Transform(m_in, m_out) real_t m_out[12] = { \ - m_in.basis[0].x, m_in.basis[0].y, m_in.basis[0].z, \ - m_in.basis[1].x, m_in.basis[1].y, m_in.basis[1].z, \ - m_in.basis[2].x, m_in.basis[2].y, m_in.basis[2].z, \ - m_in.origin.x, m_in.origin.y, m_in.origin.z \ +struct M_Transform2D { + M_Vector2 elements[3]; + + static _FORCE_INLINE_ Transform2D convert_to(const M_Transform2D &p_from) { + return Transform2D(p_from.elements[0].x, p_from.elements[0].y, + p_from.elements[1].x, p_from.elements[1].y, + p_from.elements[2].x, p_from.elements[2].y); + } + + static _FORCE_INLINE_ M_Transform2D convert_from(const Transform2D &p_from) { + M_Transform2D ret = { + M_Vector2::convert_from(p_from.elements[0]), + M_Vector2::convert_from(p_from.elements[1]), + M_Vector2::convert_from(p_from.elements[2]) + }; + return ret; + } }; -#define MARSHALLED_IN_Transform(m_in, m_out) Transform m_out( \ - Basis(m_in[0], m_in[1], m_in[2], m_in[3], m_in[4], m_in[5], m_in[6], m_in[7], m_in[8]), \ - Vector3(m_in[9], m_in[10], m_in[11])); -// AABB +struct M_Vector3 { + real_t x, y, z; + + static _FORCE_INLINE_ Vector3 convert_to(const M_Vector3 &p_from) { + return Vector3(p_from.x, p_from.y, p_from.z); + } -#define MARSHALLED_OUT_AABB(m_in, m_out) real_t m_out[6] = { m_in.position.x, m_in.position.y, m_in.position.z, m_in.size.x, m_in.size.y, m_in.size.z }; -#define MARSHALLED_IN_AABB(m_in, m_out) AABB m_out(Vector3(m_in[0], m_in[1], m_in[2]), Vector3(m_in[3], m_in[4], m_in[5])); + static _FORCE_INLINE_ M_Vector3 convert_from(const Vector3 &p_from) { + M_Vector3 ret = { p_from.x, p_from.y, p_from.z }; + return ret; + } +}; -// Color +struct M_Basis { + M_Vector3 elements[3]; + + static _FORCE_INLINE_ Basis convert_to(const M_Basis &p_from) { + return Basis(M_Vector3::convert_to(p_from.elements[0]), + M_Vector3::convert_to(p_from.elements[1]), + M_Vector3::convert_to(p_from.elements[2])); + } + + static _FORCE_INLINE_ M_Basis convert_from(const Basis &p_from) { + M_Basis ret = { + M_Vector3::convert_from(p_from.elements[0]), + M_Vector3::convert_from(p_from.elements[1]), + M_Vector3::convert_from(p_from.elements[2]) + }; + return ret; + } +}; -#define MARSHALLED_OUT_Color(m_in, m_out) real_t m_out[4] = { m_in.r, m_in.g, m_in.b, m_in.a }; -#define MARSHALLED_IN_Color(m_in, m_out) Color m_out(m_in[0], m_in[1], m_in[2], m_in[3]); +struct M_Quat { + real_t x, y, z, w; -// Plane + static _FORCE_INLINE_ Quat convert_to(const M_Quat &p_from) { + return Quat(p_from.x, p_from.y, p_from.z, p_from.w); + } -#define MARSHALLED_OUT_Plane(m_in, m_out) real_t m_out[4] = { m_in.normal.x, m_in.normal.y, m_in.normal.z, m_in.d }; -#define MARSHALLED_IN_Plane(m_in, m_out) Plane m_out(m_in[0], m_in[1], m_in[2], m_in[3]); + static _FORCE_INLINE_ M_Quat convert_from(const Quat &p_from) { + M_Quat ret = { p_from.x, p_from.y, p_from.z, p_from.w }; + return ret; + } +}; -#endif +struct M_Transform { + M_Basis basis; + M_Vector3 origin; + + static _FORCE_INLINE_ Transform convert_to(const M_Transform &p_from) { + return Transform(M_Basis::convert_to(p_from.basis), M_Vector3::convert_to(p_from.origin)); + } + + static _FORCE_INLINE_ M_Transform convert_from(const Transform &p_from) { + M_Transform ret = { M_Basis::convert_from(p_from.basis), M_Vector3::convert_from(p_from.origin) }; + return ret; + } +}; + +struct M_AABB { + M_Vector3 position; + M_Vector3 size; + + static _FORCE_INLINE_ AABB convert_to(const M_AABB &p_from) { + return AABB(M_Vector3::convert_to(p_from.position), M_Vector3::convert_to(p_from.size)); + } + + static _FORCE_INLINE_ M_AABB convert_from(const AABB &p_from) { + M_AABB ret = { M_Vector3::convert_from(p_from.position), M_Vector3::convert_from(p_from.size) }; + return ret; + } +}; + +struct M_Color { + float r, g, b, a; + + static _FORCE_INLINE_ Color convert_to(const M_Color &p_from) { + return Color(p_from.r, p_from.g, p_from.b, p_from.a); + } + + static _FORCE_INLINE_ M_Color convert_from(const Color &p_from) { + M_Color ret = { p_from.r, p_from.g, p_from.b, p_from.a }; + return ret; + } +}; + +struct M_Plane { + M_Vector3 normal; + real_t d; + + static _FORCE_INLINE_ Plane convert_to(const M_Plane &p_from) { + return Plane(M_Vector3::convert_to(p_from.normal), p_from.d); + } + + static _FORCE_INLINE_ M_Plane convert_from(const Plane &p_from) { + M_Plane ret = { M_Vector3::convert_from(p_from.normal), p_from.d }; + return ret; + } +}; + +#pragma pack(pop) + +#define DECL_TYPE_MARSHAL_TEMPLATES(m_type) \ + template <int> \ + _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl(const M_##m_type *p_from); \ + \ + template <> \ + _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl<0>(const M_##m_type *p_from) { \ + return M_##m_type::convert_to(*p_from); \ + } \ + \ + template <> \ + _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl<1>(const M_##m_type *p_from) { \ + return *reinterpret_cast<const m_type *>(p_from); \ + } \ + \ + _FORCE_INLINE_ m_type marshalled_in_##m_type(const M_##m_type *p_from) { \ + return marshalled_in_##m_type##_impl<InteropLayout::MATCHES_##m_type>(p_from); \ + } \ + \ + template <int> \ + _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl(const m_type &p_from); \ + \ + template <> \ + _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl<0>(const m_type &p_from) { \ + return M_##m_type::convert_from(p_from); \ + } \ + \ + template <> \ + _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl<1>(const m_type &p_from) { \ + return *reinterpret_cast<const M_##m_type *>(&p_from); \ + } \ + \ + _FORCE_INLINE_ M_##m_type marshalled_out_##m_type(const m_type &p_from) { \ + return marshalled_out_##m_type##_impl<InteropLayout::MATCHES_##m_type>(p_from); \ + } + +DECL_TYPE_MARSHAL_TEMPLATES(Vector2) +DECL_TYPE_MARSHAL_TEMPLATES(Rect2) +DECL_TYPE_MARSHAL_TEMPLATES(Transform2D) +DECL_TYPE_MARSHAL_TEMPLATES(Vector3) +DECL_TYPE_MARSHAL_TEMPLATES(Basis) +DECL_TYPE_MARSHAL_TEMPLATES(Quat) +DECL_TYPE_MARSHAL_TEMPLATES(Transform) +DECL_TYPE_MARSHAL_TEMPLATES(AABB) +DECL_TYPE_MARSHAL_TEMPLATES(Color) +DECL_TYPE_MARSHAL_TEMPLATES(Plane) + +#define MARSHALLED_IN(m_type, m_from_ptr) (GDMonoMarshal::marshalled_in_##m_type(m_from_ptr)) +#define MARSHALLED_OUT(m_type, m_from) (GDMonoMarshal::marshalled_out_##m_type(m_from)) } // namespace GDMonoMarshal diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp index c8df1038ce..6ef6e97f5a 100644 --- a/modules/mono/mono_gd/gd_mono_method.cpp +++ b/modules/mono/mono_gd/gd_mono_method.cpp @@ -68,6 +68,10 @@ void GDMonoMethod::_update_signature(MonoMethodSignature *p_method_sig) { param_types.push_back(param_type); } + + // clear the cache + method_info_fetched = false; + method_info = MethodInfo(); } bool GDMonoMethod::is_static() { @@ -105,9 +109,7 @@ MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, } MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = mono_runtime_invoke_array(mono_method, p_object, params, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; + MonoObject *ret = GDMonoUtils::runtime_invoke_array(mono_method, p_object, params, &exc); if (exc) { ret = NULL; @@ -121,9 +123,7 @@ MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, return ret; } else { MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - mono_runtime_invoke(mono_method, p_object, NULL, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; + GDMonoUtils::runtime_invoke(mono_method, p_object, NULL, &exc); if (exc) { if (r_exc) { @@ -144,9 +144,7 @@ MonoObject *GDMonoMethod::invoke(MonoObject *p_object, MonoException **r_exc) { MonoObject *GDMonoMethod::invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc) { MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = mono_runtime_invoke(mono_method, p_object, p_params, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; + MonoObject *ret = GDMonoUtils::runtime_invoke(mono_method, p_object, p_params, &exc); if (exc) { ret = NULL; @@ -252,11 +250,34 @@ void GDMonoMethod::get_parameter_types(Vector<ManagedType> &types) const { } } +const MethodInfo &GDMonoMethod::get_method_info() { + + if (!method_info_fetched) { + method_info.name = name; + method_info.return_val = PropertyInfo(GDMonoMarshal::managed_to_variant_type(return_type), ""); + + Vector<StringName> names; + get_parameter_names(names); + + for (int i = 0; i < params_count; ++i) { + method_info.arguments.push_back(PropertyInfo(GDMonoMarshal::managed_to_variant_type(param_types[i]), names[i])); + } + + // TODO: default arguments + + method_info_fetched = true; + } + + return method_info; +} + GDMonoMethod::GDMonoMethod(StringName p_name, MonoMethod *p_method) { name = p_name; mono_method = p_method; + method_info_fetched = false; + attrs_fetched = false; attributes = NULL; diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h index 444ec2a67d..6c3ae5fce0 100644 --- a/modules/mono/mono_gd/gd_mono_method.h +++ b/modules/mono/mono_gd/gd_mono_method.h @@ -43,6 +43,9 @@ class GDMonoMethod : public GDMonoClassMember { ManagedType return_type; Vector<ManagedType> param_types; + bool method_info_fetched; + MethodInfo method_info; + bool attrs_fetched; MonoCustomAttrInfo *attributes; @@ -83,6 +86,8 @@ public: void get_parameter_names(Vector<StringName> &names) const; void get_parameter_types(Vector<ManagedType> &types) const; + const MethodInfo &get_method_info(); + GDMonoMethod(StringName p_name, MonoMethod *p_method); ~GDMonoMethod(); }; diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp index 1f837a2d78..ce66e0c8db 100644 --- a/modules/mono/mono_gd/gd_mono_property.cpp +++ b/modules/mono/mono_gd/gd_mono_property.cpp @@ -140,15 +140,10 @@ bool GDMonoProperty::has_setter() { void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc) { MonoMethod *prop_method = mono_property_get_set_method(mono_property); - MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), 1); mono_array_set(params, MonoObject *, 0, p_value); - MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - mono_runtime_invoke_array(prop_method, p_object, params, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; - + GDMonoUtils::runtime_invoke_array(prop_method, p_object, params, &exc); if (exc) { if (r_exc) { *r_exc = exc; @@ -160,9 +155,7 @@ void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoEx void GDMonoProperty::set_value(MonoObject *p_object, void **p_params, MonoException **r_exc) { MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - mono_property_set_value(mono_property, p_object, p_params, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; + GDMonoUtils::property_set_value(mono_property, p_object, p_params, &exc); if (exc) { if (r_exc) { @@ -175,9 +168,7 @@ void GDMonoProperty::set_value(MonoObject *p_object, void **p_params, MonoExcept MonoObject *GDMonoProperty::get_value(MonoObject *p_object, MonoException **r_exc) { MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = mono_property_get_value(mono_property, p_object, NULL, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; + MonoObject *ret = GDMonoUtils::property_get_value(mono_property, p_object, NULL, &exc); if (exc) { ret = NULL; diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index a229552b76..211987d242 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -32,10 +32,10 @@ #include <mono/metadata/exception.h> -#include "os/dir_access.h" -#include "os/os.h" -#include "project_settings.h" -#include "reference.h" +#include "core/os/dir_access.h" +#include "core/os/os.h" +#include "core/project_settings.h" +#include "core/reference.h" #include "../csharp_script.h" #include "../utils/macros.h" @@ -87,6 +87,8 @@ void MonoCache::clear_members() { method_System_Diagnostics_StackTrace_ctor_Exception_bool = NULL; #endif + class_KeyNotFoundException = NULL; + rawclass_Dictionary = NULL; class_Vector2 = NULL; @@ -107,6 +109,8 @@ void MonoCache::clear_members() { class_Control = NULL; class_Spatial = NULL; class_WeakRef = NULL; + class_Array = NULL; + class_Dictionary = NULL; class_MarshalUtils = NULL; #ifdef DEBUG_ENABLED @@ -122,10 +126,11 @@ void MonoCache::clear_members() { class_RemoteAttribute = NULL; class_SyncAttribute = NULL; class_MasterAttribute = NULL; + class_PuppetAttribute = NULL; class_SlaveAttribute = NULL; class_RemoteSyncAttribute = NULL; class_MasterSyncAttribute = NULL; - class_SlaveSyncAttribute = NULL; + class_PuppetSyncAttribute = NULL; class_GodotMethodAttribute = NULL; field_GodotMethodAttribute_methodName = NULL; @@ -134,8 +139,11 @@ void MonoCache::clear_members() { field_Image_ptr = NULL; field_RID_ptr = NULL; - methodthunk_MarshalUtils_DictionaryToArrays = NULL; - methodthunk_MarshalUtils_ArraysToDictionary = NULL; + methodthunk_GodotObject_Dispose = NULL; + methodthunk_Array_GetPtr = NULL; + methodthunk_Dictionary_GetPtr = NULL; + methodthunk_MarshalUtils_IsArrayGenericType = NULL; + methodthunk_MarshalUtils_IsDictionaryGenericType = NULL; methodthunk_SignalAwaiter_SignalCallback = NULL; methodthunk_SignalAwaiter_FailureCallback = NULL; methodthunk_GodotTaskScheduler_Activate = NULL; @@ -150,6 +158,7 @@ void MonoCache::cleanup() { } #define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class)) +#define GODOT_API_NS_CLAS(m_ns, m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(m_ns, #m_class)) void update_corlib_cache() { @@ -170,11 +179,13 @@ void update_corlib_cache() { #ifdef DEBUG_ENABLED CACHE_CLASS_AND_CHECK(System_Diagnostics_StackTrace, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Diagnostics", "StackTrace")); - CACHE_METHOD_THUNK_AND_CHECK(System_Diagnostics_StackTrace, GetFrames, (StackTrace_GetFrames)CACHED_CLASS(System_Diagnostics_StackTrace)->get_method("GetFrames")->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(System_Diagnostics_StackTrace, GetFrames, (StackTrace_GetFrames)CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_thunk("GetFrames")); CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(bool)", true)); CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_Exception_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(System.Exception,bool)", true)); #endif + CACHE_CLASS_AND_CHECK(KeyNotFoundException, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections.Generic", "KeyNotFoundException")); + mono_cache.corlib_cache_updated = true; } @@ -198,6 +209,8 @@ void update_godot_api_cache() { CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control)); CACHE_CLASS_AND_CHECK(Spatial, GODOT_API_CLASS(Spatial)); CACHE_CLASS_AND_CHECK(WeakRef, GODOT_API_CLASS(WeakRef)); + CACHE_CLASS_AND_CHECK(Array, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array)); + CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)); CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils)); #ifdef DEBUG_ENABLED @@ -213,10 +226,11 @@ void update_godot_api_cache() { CACHE_CLASS_AND_CHECK(RemoteAttribute, GODOT_API_CLASS(RemoteAttribute)); CACHE_CLASS_AND_CHECK(SyncAttribute, GODOT_API_CLASS(SyncAttribute)); CACHE_CLASS_AND_CHECK(MasterAttribute, GODOT_API_CLASS(MasterAttribute)); + CACHE_CLASS_AND_CHECK(PuppetAttribute, GODOT_API_CLASS(PuppetAttribute)); CACHE_CLASS_AND_CHECK(SlaveAttribute, GODOT_API_CLASS(SlaveAttribute)); CACHE_CLASS_AND_CHECK(RemoteSyncAttribute, GODOT_API_CLASS(RemoteSyncAttribute)); CACHE_CLASS_AND_CHECK(MasterSyncAttribute, GODOT_API_CLASS(MasterSyncAttribute)); - CACHE_CLASS_AND_CHECK(SlaveSyncAttribute, GODOT_API_CLASS(SlaveSyncAttribute)); + CACHE_CLASS_AND_CHECK(PuppetSyncAttribute, GODOT_API_CLASS(PuppetSyncAttribute)); CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute)); CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); @@ -224,34 +238,22 @@ void update_godot_api_cache() { CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD)); CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryToArrays, (MarshalUtils_DictToArrays)CACHED_CLASS(MarshalUtils)->get_method("DictionaryToArrays", 3)->get_thunk()); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArraysToDictionary, (MarshalUtils_ArraysToDict)CACHED_CLASS(MarshalUtils)->get_method("ArraysToDictionary", 2)->get_thunk()); - CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, SignalCallback, (SignalAwaiter_SignalCallback)GODOT_API_CLASS(SignalAwaiter)->get_method("SignalCallback", 1)->get_thunk()); - CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, (SignalAwaiter_FailureCallback)GODOT_API_CLASS(SignalAwaiter)->get_method("FailureCallback", 0)->get_thunk()); - CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, (GodotTaskScheduler_Activate)GODOT_API_CLASS(GodotTaskScheduler)->get_method("Activate", 0)->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(GodotObject, Dispose, (GodotObject_Dispose)CACHED_CLASS(GodotObject)->get_method_thunk("Dispose", 0)); + CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, (Array_GetPtr)GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array)->get_method_thunk("GetPtr", 0)); + CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, (Dictionary_GetPtr)GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)->get_method_thunk("GetPtr", 0)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IsArrayGenericType, (IsArrayGenericType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("IsArrayGenericType", 1)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IsDictionaryGenericType, (IsDictionaryGenericType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("IsDictionaryGenericType", 1)); + CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, SignalCallback, (SignalAwaiter_SignalCallback)GODOT_API_CLASS(SignalAwaiter)->get_method_thunk("SignalCallback", 1)); + CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, (SignalAwaiter_FailureCallback)GODOT_API_CLASS(SignalAwaiter)->get_method_thunk("FailureCallback", 0)); + CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, (GodotTaskScheduler_Activate)GODOT_API_CLASS(GodotTaskScheduler)->get_method_thunk("Activate", 0)); #ifdef DEBUG_ENABLED - CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, (DebugUtils_StackFrameInfo)GODOT_API_CLASS(DebuggingUtils)->get_method("GetStackFrameInfo", 4)->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, (DebugUtils_StackFrameInfo)GODOT_API_CLASS(DebuggingUtils)->get_method_thunk("GetStackFrameInfo", 4)); #endif - { - /* - * TODO Right now we only support Dictionary<object, object>. - * It would be great if we could support other key/value types - * without forcing the user to copy the entries. - */ - GDMonoMethod *method_get_dict_type = CACHED_CLASS(MarshalUtils)->get_method("GetDictionaryType", 0); - ERR_FAIL_NULL(method_get_dict_type); - MonoReflectionType *dict_refl_type = (MonoReflectionType *)method_get_dict_type->invoke(NULL); - ERR_FAIL_NULL(dict_refl_type); - MonoType *dict_type = mono_reflection_type_get_type(dict_refl_type); - ERR_FAIL_NULL(dict_type); - - CACHE_RAW_MONO_CLASS_AND_CHECK(Dictionary, mono_class_from_mono_type(dict_type)); - } - + // TODO Move to CSharpLanguage::init() and do handle disposal MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr()); - mono_runtime_object_init(task_scheduler); + GDMonoUtils::runtime_object_init(task_scheduler); mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler); mono_cache.godot_api_cache_updated = true; @@ -272,11 +274,48 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) { } } - // Only called if the owner does not have a CSharpInstance + // If the owner does not have a CSharpInstance... + void *data = unmanaged->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); if (data) { - return ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->value()->get_target(); + CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->value(); + + Ref<MonoGCHandle> &gchandle = script_binding.gchandle; + ERR_FAIL_COND_V(gchandle.is_null(), NULL); + + MonoObject *target = gchandle->get_target(); + + if (target) + return target; + + CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); + + // Create a new one + +#ifdef DEBUG_ENABLED + CRASH_COND(script_binding.type_name == StringName()); + CRASH_COND(script_binding.wrapper_class == NULL); +#endif + + MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged); + ERR_FAIL_NULL_V(mono_object, NULL); + + gchandle->set_handle(MonoGCHandle::new_strong_handle(mono_object), MonoGCHandle::STRONG_HANDLE); + + // Tie managed to unmanaged + Reference *ref = Object::cast_to<Reference>(unmanaged); + + 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) + + ref->reference(); + } + + return mono_object; } } @@ -304,6 +343,13 @@ MonoThread *get_current_thread() { return mono_thread_current(); } +void runtime_object_init(MonoObject *p_this_obj) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + // FIXME: Do not use mono_runtime_object_init, it aborts if an exception is thrown + mono_runtime_object_init(p_this_obj); + GD_MONO_END_RUNTIME_INVOKE; +} + GDMonoClass *get_object_class(MonoObject *p_object) { return GDMono::get_singleton()->get_class(mono_object_get_class(p_object)); } @@ -358,7 +404,7 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object); // Construct - mono_runtime_object_init(mono_object); + GDMonoUtils::runtime_object_init(mono_object); return mono_object; } @@ -368,7 +414,7 @@ MonoObject *create_managed_from(const NodePath &p_from) { ERR_FAIL_NULL_V(mono_object, NULL); // Construct - mono_runtime_object_init(mono_object); + GDMonoUtils::runtime_object_init(mono_object); CACHED_FIELD(NodePath, ptr)->set_value_raw(mono_object, memnew(NodePath(p_from))); @@ -380,13 +426,73 @@ MonoObject *create_managed_from(const RID &p_from) { ERR_FAIL_NULL_V(mono_object, NULL); // Construct - mono_runtime_object_init(mono_object); + GDMonoUtils::runtime_object_init(mono_object); CACHED_FIELD(RID, ptr)->set_value_raw(mono_object, memnew(RID(p_from))); return mono_object; } +MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) { + MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr()); + ERR_FAIL_NULL_V(mono_object, NULL); + + // Search constructor that takes a pointer as parameter + MonoMethod *m; + void *iter = NULL; + while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) { + if (strcmp(mono_method_get_name(m), ".ctor") == 0) { + MonoMethodSignature *sig = mono_method_signature(m); + void *front = NULL; + if (mono_signature_get_param_count(sig) == 1 && + mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) { + break; + } + } + } + + CRASH_COND(m == NULL); + + Array *new_array = memnew(Array(p_from)); + void *args[1] = { &new_array }; + + MonoException *exc = NULL; + GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); + UNLIKELY_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()); + ERR_FAIL_NULL_V(mono_object, NULL); + + // Search constructor that takes a pointer as parameter + MonoMethod *m; + void *iter = NULL; + while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) { + if (strcmp(mono_method_get_name(m), ".ctor") == 0) { + MonoMethodSignature *sig = mono_method_signature(m); + void *front = NULL; + if (mono_signature_get_param_count(sig) == 1 && + mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) { + break; + } + } + } + + CRASH_COND(m == NULL); + + Dictionary *new_dict = memnew(Dictionary(p_from)); + void *args[1] = { &new_dict }; + + MonoException *exc = NULL; + GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + return mono_object; +} + MonoDomain *create_domain(const String &p_friendly_name) { MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), NULL); @@ -400,10 +506,10 @@ MonoDomain *create_domain(const String &p_friendly_name) { return domain; } -String get_exception_name_and_message(MonoException *p_ex) { +String get_exception_name_and_message(MonoException *p_exc) { String res; - MonoClass *klass = mono_object_get_class((MonoObject *)p_ex); + MonoClass *klass = mono_object_get_class((MonoObject *)p_exc); MonoType *type = mono_class_get_type(klass); char *full_name = mono_type_full_name(type); @@ -413,12 +519,20 @@ String get_exception_name_and_message(MonoException *p_ex) { res += ": "; MonoProperty *prop = mono_class_get_property_from_name(klass, "Message"); - MonoString *msg = (MonoString *)mono_property_get_value(prop, (MonoObject *)p_ex, NULL, NULL); + MonoString *msg = (MonoString *)property_get_value(prop, (MonoObject *)p_exc, NULL, NULL); res += GDMonoMarshal::mono_string_to_godot(msg); return res; } +void set_exception_message(MonoException *p_exc, String message) { + MonoClass *klass = mono_object_get_class((MonoObject *)p_exc); + MonoProperty *prop = mono_class_get_property_from_name(klass, "Message"); + MonoString *msg = GDMonoMarshal::mono_string_from_godot(message); + void *params[1] = { msg }; + property_set_value(prop, (MonoObject *)p_exc, params, NULL); +} + void debug_print_unhandled_exception(MonoException *p_exc) { print_unhandled_exception(p_exc); debug_send_unhandled_exception_error(p_exc); @@ -517,4 +631,71 @@ void set_pending_exception(MonoException *p_exc) { _THREAD_LOCAL_(int) current_invoke_count = 0; +MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + MonoObject *ret = mono_runtime_invoke(p_method, p_obj, p_params, (MonoObject **)r_exc); + GD_MONO_END_RUNTIME_INVOKE; + return ret; +} + +MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **r_exc) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + MonoObject *ret = mono_runtime_invoke_array(p_method, p_obj, p_params, (MonoObject **)r_exc); + GD_MONO_END_RUNTIME_INVOKE; + return ret; +} + +MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + MonoString *ret = mono_object_to_string(p_obj, (MonoObject **)r_exc); + GD_MONO_END_RUNTIME_INVOKE; + return ret; +} + +void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + mono_property_set_value(p_prop, p_obj, p_params, (MonoObject **)r_exc); + GD_MONO_END_RUNTIME_INVOKE; +} + +MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + MonoObject *ret = mono_property_get_value(p_prop, p_obj, p_params, (MonoObject **)r_exc); + GD_MONO_END_RUNTIME_INVOKE; + return ret; +} + +uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error) { + r_error = false; + switch (mono_type_get_type(p_enum_basetype)) { + case MONO_TYPE_BOOLEAN: + return (bool)GDMonoMarshal::unbox<MonoBoolean>(p_boxed) ? 1 : 0; + case MONO_TYPE_CHAR: + return GDMonoMarshal::unbox<uint16_t>(p_boxed); + case MONO_TYPE_U1: + return GDMonoMarshal::unbox<uint8_t>(p_boxed); + case MONO_TYPE_U2: + return GDMonoMarshal::unbox<uint16_t>(p_boxed); + case MONO_TYPE_U4: + return GDMonoMarshal::unbox<uint32_t>(p_boxed); + case MONO_TYPE_U8: + return GDMonoMarshal::unbox<uint64_t>(p_boxed); + case MONO_TYPE_I1: + return GDMonoMarshal::unbox<int8_t>(p_boxed); + case MONO_TYPE_I2: + return GDMonoMarshal::unbox<int16_t>(p_boxed); + case MONO_TYPE_I4: + return GDMonoMarshal::unbox<int32_t>(p_boxed); + case MONO_TYPE_I8: + return GDMonoMarshal::unbox<int64_t>(p_boxed); + default: + r_error = true; + return 0; + } +} + +void dispose(MonoObject *p_mono_object, MonoException **r_exc) { + invoke_method_thunk(CACHED_METHOD_THUNK(GodotObject, Dispose), p_mono_object, (MonoObject **)r_exc); +} + } // namespace GDMonoUtils diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index 4f8e5932cd..170df32991 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -38,17 +38,28 @@ #include "../utils/thread_local.h" #include "gd_mono_header.h" -#include "object.h" -#include "reference.h" +#include "core/object.h" +#include "core/reference.h" + +#define UNLIKELY_UNHANDLED_EXCEPTION(m_exc) \ + if (unlikely(m_exc != NULL)) { \ + GDMonoUtils::debug_unhandled_exception(m_exc); \ + _UNREACHABLE_(); \ + } namespace GDMonoUtils { -typedef MonoObject *(*MarshalUtils_DictToArrays)(MonoObject *, MonoArray **, MonoArray **, MonoObject **); -typedef MonoObject *(*MarshalUtils_ArraysToDict)(MonoArray *, MonoArray *, MonoObject **); +typedef void (*GodotObject_Dispose)(MonoObject *, MonoObject **); +typedef Array *(*Array_GetPtr)(MonoObject *, MonoObject **); +typedef Dictionary *(*Dictionary_GetPtr)(MonoObject *, MonoObject **); typedef MonoObject *(*SignalAwaiter_SignalCallback)(MonoObject *, MonoArray *, MonoObject **); typedef MonoObject *(*SignalAwaiter_FailureCallback)(MonoObject *, MonoObject **); typedef MonoObject *(*GodotTaskScheduler_Activate)(MonoObject *, MonoObject **); typedef MonoArray *(*StackTrace_GetFrames)(MonoObject *, MonoObject **); +typedef MonoBoolean (*IsArrayGenericType)(MonoObject *, MonoObject **); +typedef MonoBoolean (*IsDictionaryGenericType)(MonoObject *, MonoObject **); +typedef MonoBoolean (*IsArrayGenericType)(MonoObject *, MonoObject **); +typedef MonoBoolean (*IsDictionaryGenericType)(MonoObject *, MonoObject **); typedef void (*DebugUtils_StackFrameInfo)(MonoObject *, MonoString **, int *, MonoString **, MonoObject **); struct MonoCache { @@ -79,6 +90,8 @@ struct MonoCache { GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_Exception_bool; #endif + GDMonoClass *class_KeyNotFoundException; + MonoClass *rawclass_Dictionary; // ----------------------------------------------- @@ -100,6 +113,8 @@ struct MonoCache { GDMonoClass *class_Control; GDMonoClass *class_Spatial; GDMonoClass *class_WeakRef; + GDMonoClass *class_Array; + GDMonoClass *class_Dictionary; GDMonoClass *class_MarshalUtils; #ifdef DEBUG_ENABLED @@ -116,8 +131,9 @@ struct MonoCache { GDMonoClass *class_SyncAttribute; GDMonoClass *class_RemoteSyncAttribute; GDMonoClass *class_MasterSyncAttribute; - GDMonoClass *class_SlaveSyncAttribute; + GDMonoClass *class_PuppetSyncAttribute; GDMonoClass *class_MasterAttribute; + GDMonoClass *class_PuppetAttribute; GDMonoClass *class_SlaveAttribute; GDMonoClass *class_GodotMethodAttribute; GDMonoField *field_GodotMethodAttribute_methodName; @@ -127,8 +143,11 @@ struct MonoCache { GDMonoField *field_Image_ptr; GDMonoField *field_RID_ptr; - MarshalUtils_DictToArrays methodthunk_MarshalUtils_DictionaryToArrays; - MarshalUtils_ArraysToDict methodthunk_MarshalUtils_ArraysToDictionary; + GodotObject_Dispose methodthunk_GodotObject_Dispose; + Array_GetPtr methodthunk_Array_GetPtr; + Dictionary_GetPtr methodthunk_Dictionary_GetPtr; + IsArrayGenericType methodthunk_MarshalUtils_IsArrayGenericType; + IsDictionaryGenericType methodthunk_MarshalUtils_IsDictionaryGenericType; SignalAwaiter_SignalCallback methodthunk_SignalAwaiter_SignalCallback; SignalAwaiter_FailureCallback methodthunk_SignalAwaiter_FailureCallback; GodotTaskScheduler_Activate methodthunk_GodotTaskScheduler_Activate; @@ -175,6 +194,8 @@ _FORCE_INLINE_ bool is_main_thread() { return mono_domain_get() != NULL && mono_thread_get_main() == mono_thread_current(); } +void runtime_object_init(MonoObject *p_this_obj); + GDMonoClass *get_object_class(MonoObject *p_object); GDMonoClass *type_get_proxy_class(const StringName &p_type); GDMonoClass *get_class_native_base(GDMonoClass *p_class); @@ -183,10 +204,13 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa MonoObject *create_managed_from(const NodePath &p_from); MonoObject *create_managed_from(const RID &p_from); +MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class); +MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class); MonoDomain *create_domain(const String &p_friendly_name); -String get_exception_name_and_message(MonoException *p_ex); +String get_exception_name_and_message(MonoException *p_exc); +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); @@ -209,6 +233,18 @@ _FORCE_INLINE_ int &get_runtime_invoke_count_ref() { return current_invoke_count; } +MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc); +MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **r_exc); + +MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc); + +void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc); +MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc); + +uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error); + +void dispose(MonoObject *p_mono_object, MonoException **r_exc); + } // namespace GDMonoUtils #define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL))) @@ -233,4 +269,93 @@ _FORCE_INLINE_ int &get_runtime_invoke_count_ref() { #define GD_MONO_END_RUNTIME_INVOKE \ _runtime_invoke_count_ref -= 1; +inline void invoke_method_thunk(void (*p_method_thunk)()) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + p_method_thunk(); + GD_MONO_END_RUNTIME_INVOKE; +} + +template <class R> +R invoke_method_thunk(R (*p_method_thunk)()) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + R r = p_method_thunk(); + GD_MONO_END_RUNTIME_INVOKE; + return r; +} + +template <class P1> +void invoke_method_thunk(void (*p_method_thunk)(P1), P1 p_arg1) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + p_method_thunk(p_arg1); + GD_MONO_END_RUNTIME_INVOKE; +} + +template <class R, class P1> +R invoke_method_thunk(R (*p_method_thunk)(P1), P1 p_arg1) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + R r = p_method_thunk(p_arg1); + GD_MONO_END_RUNTIME_INVOKE; + return r; +} + +template <class P1, class P2> +void invoke_method_thunk(void (*p_method_thunk)(P1, P2), P1 p_arg1, P2 p_arg2) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + p_method_thunk(p_arg1, p_arg2); + GD_MONO_END_RUNTIME_INVOKE; +} + +template <class R, class P1, class P2> +R invoke_method_thunk(R (*p_method_thunk)(P1, P2), P1 p_arg1, P2 p_arg2) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + R r = p_method_thunk(p_arg1, p_arg2); + GD_MONO_END_RUNTIME_INVOKE; + return r; +} + +template <class P1, class P2, class P3> +void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3), P1 p_arg1, P2 p_arg2, P3 p_arg3) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + p_method_thunk(p_arg1, p_arg2, p_arg3); + GD_MONO_END_RUNTIME_INVOKE; +} + +template <class R, class P1, class P2, class P3> +R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3), P1 p_arg1, P2 p_arg2, P3 p_arg3) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + R r = p_method_thunk(p_arg1, p_arg2, p_arg3); + GD_MONO_END_RUNTIME_INVOKE; + return r; +} + +template <class P1, class P2, class P3, class P4> +void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3, P4), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4); + GD_MONO_END_RUNTIME_INVOKE; +} + +template <class R, class P1, class P2, class P3, class P4> +R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3, P4), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + R r = p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4); + GD_MONO_END_RUNTIME_INVOKE; + return r; +} + +template <class P1, class P2, class P3, class P4, class P5> +void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3, P4, P5), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4, P5 p_arg5) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4, p_arg5); + GD_MONO_END_RUNTIME_INVOKE; +} + +template <class R, class P1, class P2, class P3, class P4, class P5> +R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3, P4, P5), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4, P5 p_arg5) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + R r = p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4, p_arg5); + GD_MONO_END_RUNTIME_INVOKE; + return r; +} + #endif // GD_MONOUTILS_H diff --git a/modules/mono/register_types.cpp b/modules/mono/register_types.cpp index 4410996546..f6cb143e8e 100644 --- a/modules/mono/register_types.cpp +++ b/modules/mono/register_types.cpp @@ -30,7 +30,7 @@ #include "register_types.h" -#include "engine.h" +#include "core/engine.h" #include "csharp_script.h" diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index 54720652fa..c6748309f3 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -42,8 +42,7 @@ Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p ERR_FAIL_NULL_V(p_source, ERR_INVALID_DATA); ERR_FAIL_NULL_V(p_target, ERR_INVALID_DATA); - uint32_t awaiter_handle = MonoGCHandle::make_strong_handle(p_awaiter); - Ref<SignalAwaiterHandle> sa_con = memnew(SignalAwaiterHandle(awaiter_handle)); + Ref<SignalAwaiterHandle> sa_con = memnew(SignalAwaiterHandle(p_awaiter)); #ifdef DEBUG_ENABLED sa_con->set_connection_target(p_target); #endif @@ -99,11 +98,9 @@ Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argc mono_array_set(signal_args, MonoObject *, i, boxed); } - GDMonoUtils::SignalAwaiter_SignalCallback thunk = CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback); - MonoException *exc = NULL; GD_MONO_BEGIN_RUNTIME_INVOKE; - thunk(get_target(), signal_args, (MonoObject **)&exc); + invoke_method_thunk(CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback), get_target(), signal_args, (MonoObject **)&exc); GD_MONO_END_RUNTIME_INVOKE; if (exc) { @@ -119,8 +116,8 @@ void SignalAwaiterHandle::_bind_methods() { ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_callback", &SignalAwaiterHandle::_signal_callback, MethodInfo("_signal_callback")); } -SignalAwaiterHandle::SignalAwaiterHandle(uint32_t p_managed_handle) : - MonoGCHandle(p_managed_handle) { +SignalAwaiterHandle::SignalAwaiterHandle(MonoObject *p_managed) : + MonoGCHandle(MonoGCHandle::new_strong_handle(p_managed), STRONG_HANDLE) { #ifdef DEBUG_ENABLED conn_target_id = 0; @@ -130,19 +127,17 @@ SignalAwaiterHandle::SignalAwaiterHandle(uint32_t p_managed_handle) : SignalAwaiterHandle::~SignalAwaiterHandle() { if (!completed) { - GDMonoUtils::SignalAwaiter_FailureCallback thunk = CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback); - MonoObject *awaiter = get_target(); if (awaiter) { MonoException *exc = NULL; GD_MONO_BEGIN_RUNTIME_INVOKE; - thunk(awaiter, (MonoObject **)&exc); + invoke_method_thunk(CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback), awaiter, (MonoObject **)&exc); GD_MONO_END_RUNTIME_INVOKE; if (exc) { GDMonoUtils::set_pending_exception(exc); - ERR_FAIL_V(); + ERR_FAIL(); } } } diff --git a/modules/mono/signal_awaiter_utils.h b/modules/mono/signal_awaiter_utils.h index a6a205ff8d..4ec860537b 100644 --- a/modules/mono/signal_awaiter_utils.h +++ b/modules/mono/signal_awaiter_utils.h @@ -31,8 +31,8 @@ #ifndef SIGNAL_AWAITER_UTILS_H #define SIGNAL_AWAITER_UTILS_H +#include "core/reference.h" #include "mono_gc_handle.h" -#include "reference.h" namespace SignalAwaiterUtils { @@ -64,7 +64,7 @@ public: } #endif - SignalAwaiterHandle(uint32_t p_managed_handle); + SignalAwaiterHandle(MonoObject *p_managed); ~SignalAwaiterHandle(); }; diff --git a/modules/mono/utils/macros.h b/modules/mono/utils/macros.h index 337a86870e..c801fb2f33 100644 --- a/modules/mono/utils/macros.h +++ b/modules/mono/utils/macros.h @@ -31,8 +31,19 @@ #ifndef UTIL_MACROS_H #define UTIL_MACROS_H +#define _GD_VARNAME_CONCAT_B(m_ignore, m_name) m_name +#define _GD_VARNAME_CONCAT_A(m_a, m_b, m_c) _GD_VARNAME_CONCAT_B(hello there, m_a##m_b##m_c) +#define _GD_VARNAME_CONCAT(m_a, m_b, m_c) _GD_VARNAME_CONCAT_A(m_a, m_b, m_c) +#define GD_UNIQUE_NAME(m_name) _GD_VARNAME_CONCAT(m_name, _, __COUNTER__) + // noreturn +#if __cpp_static_assert +#define GD_STATIC_ASSERT(m_cond) static_assert((m_cond), "Condition '" #m_cond "' failed") +#else +#define GD_STATIC_ASSERT(m_cond) typedef int GD_UNIQUE_NAME(godot_static_assert)[((m_cond) ? 1 : -1)] +#endif + #undef _NO_RETURN_ #ifdef __GNUC__ diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp index 7b23cd7579..6bb6efa92a 100644 --- a/modules/mono/utils/mono_reg_utils.cpp +++ b/modules/mono/utils/mono_reg_utils.cpp @@ -32,7 +32,7 @@ #ifdef WINDOWS_ENABLED -#include "os/os.h" +#include "core/os/os.h" // Here, after os/os.h #include <windows.h> @@ -228,4 +228,4 @@ cleanup: } } // namespace MonoRegUtils -#endif WINDOWS_ENABLED +#endif // WINDOWS_ENABLED diff --git a/modules/mono/utils/mono_reg_utils.h b/modules/mono/utils/mono_reg_utils.h index edf31f5a07..26f7e2d3c2 100644 --- a/modules/mono/utils/mono_reg_utils.h +++ b/modules/mono/utils/mono_reg_utils.h @@ -33,7 +33,7 @@ #ifdef WINDOWS_ENABLED -#include "ustring.h" +#include "core/ustring.h" struct MonoRegInfo { diff --git a/modules/mono/utils/mutex_utils.h b/modules/mono/utils/mutex_utils.h new file mode 100644 index 0000000000..07d659b6eb --- /dev/null +++ b/modules/mono/utils/mutex_utils.h @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* mutex_utils.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef MUTEX_UTILS_H +#define MUTEX_UTILS_H + +#include "core/error_macros.h" +#include "core/os/mutex.h" + +#include "macros.h" + +class ScopedMutexLock { + Mutex *mutex; + +public: + ScopedMutexLock(Mutex *mutex) { + this->mutex = mutex; +#ifndef NO_THREADS +#ifdef DEBUG_ENABLED + CRASH_COND(!mutex); +#endif + this->mutex->lock(); +#endif + } + + ~ScopedMutexLock() { +#ifndef NO_THREADS +#ifdef DEBUG_ENABLED + CRASH_COND(!mutex); +#endif + mutex->unlock(); +#endif + } +}; + +#define SCOPED_MUTEX_LOCK(m_mutex) ScopedMutexLock GD_UNIQUE_NAME(__scoped_mutex_lock__)(m_mutex); + +// TODO: Add version that receives a lambda instead, once C++11 is allowed + +#endif // MUTEX_UTILS_H diff --git a/modules/mono/utils/osx_utils.cpp b/modules/mono/utils/osx_utils.cpp new file mode 100644 index 0000000000..f520706a0c --- /dev/null +++ b/modules/mono/utils/osx_utils.cpp @@ -0,0 +1,62 @@ +/*************************************************************************/ +/* osx_utils.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 "osx_utils.h" + +#include "core/print_string.h" + +#ifdef OSX_ENABLED + +#include <CoreFoundation/CoreFoundation.h> +#include <CoreServices/CoreServices.h> + +bool osx_is_app_bundle_installed(const String &p_bundle_id) { + + CFURLRef app_url = NULL; + CFStringRef bundle_id = CFStringCreateWithCString(NULL, p_bundle_id.utf8(), kCFStringEncodingUTF8); + OSStatus result = LSFindApplicationForInfo(kLSUnknownCreator, bundle_id, NULL, NULL, &app_url); + CFRelease(bundle_id); + + if (app_url) + CFRelease(app_url); + + switch (result) { + case noErr: + return true; + case kLSApplicationNotFoundErr: + break; + default: + break; + } + + return false; +} + +#endif diff --git a/modules/mono/utils/osx_utils.h b/modules/mono/utils/osx_utils.h new file mode 100644 index 0000000000..68479b4f44 --- /dev/null +++ b/modules/mono/utils/osx_utils.h @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* osx_utils.h */ +/*************************************************************************/ +/* 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 "core/ustring.h" + +#ifndef OSX_UTILS_H + +#ifdef OSX_ENABLED + +bool osx_is_app_bundle_installed(const String &p_bundle_id); + +#endif + +#endif // OSX_UTILS_H diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index 4b77aeb54e..e663ee3c4a 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -30,10 +30,10 @@ #include "path_utils.h" -#include "os/dir_access.h" -#include "os/file_access.h" -#include "os/os.h" -#include "project_settings.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "core/project_settings.h" #ifdef WINDOWS_ENABLED #define ENV_PATH_SEP ";" @@ -88,7 +88,7 @@ void fix_path(const String &p_path, String &r_out) { bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path) { #ifdef WINDOWS_ENABLED CharType ret[_MAX_PATH]; - if (_wfullpath(ret, p_existing_path.c_str(), _MAX_PATH)) { + if (::_wfullpath(ret, p_existing_path.c_str(), _MAX_PATH)) { String abspath = String(ret).replace("\\", "/"); int pos = abspath.find(":/"); if (pos != -1) { @@ -99,10 +99,12 @@ bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path) { return true; } #else - char ret[PATH_MAX]; - if (realpath(p_existing_path.utf8().get_data(), ret)) { + char *resolved_path = ::realpath(p_existing_path.utf8().get_data(), NULL); + if (resolved_path) { String retstr; - if (!retstr.parse_utf8(ret)) { + bool success = !retstr.parse_utf8(resolved_path); + ::free(resolved_path); + if (success) { r_abs_path = retstr; return true; } diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h index 184cacfac7..3c7b36c0d4 100644 --- a/modules/mono/utils/path_utils.h +++ b/modules/mono/utils/path_utils.h @@ -31,7 +31,7 @@ #ifndef PATH_UTILS_H #define PATH_UTILS_H -#include "ustring.h" +#include "core/ustring.h" _FORCE_INLINE_ String path_join(const String &e1, const String &e2) { return e1.plus_file(e2); diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 8691932f9a..6900866725 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -30,6 +30,8 @@ #include "string_utils.h" +#include "core/os/file_access.h" + namespace { int sfind(const String &p_text, int p_from) { @@ -128,6 +130,7 @@ String sformat(const String &p_text, const Variant &p1, const Variant &p2, const return new_string; } +#ifdef TOOLS_ENABLED bool is_csharp_keyword(const String &p_name) { // Reserved keywords @@ -156,3 +159,28 @@ bool is_csharp_keyword(const String &p_name) { String escape_csharp_keyword(const String &p_name) { return is_csharp_keyword(p_name) ? "@" + p_name : p_name; } +#endif + +Error read_all_file_utf8(const String &p_path, String &r_content) { + PoolVector<uint8_t> sourcef; + Error err; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); + ERR_FAIL_COND_V(err != OK, err); + + int len = f->get_len(); + sourcef.resize(len + 1); + PoolVector<uint8_t>::Write w = sourcef.write(); + int r = f->get_buffer(w.ptr(), len); + f->close(); + memdelete(f); + ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN); + w[len] = 0; + + String source; + if (source.parse_utf8((const char *)w.ptr())) { + ERR_FAIL_V(ERR_INVALID_DATA); + } + + r_content = source; + return OK; +} diff --git a/modules/mono/utils/string_utils.h b/modules/mono/utils/string_utils.h index 5dddaee6e8..ee803bd720 100644 --- a/modules/mono/utils/string_utils.h +++ b/modules/mono/utils/string_utils.h @@ -31,8 +31,8 @@ #ifndef STRING_FORMAT_H #define STRING_FORMAT_H -#include "ustring.h" -#include "variant.h" +#include "core/ustring.h" +#include "core/variant.h" String sformat(const String &p_text, const Variant &p1 = Variant(), const Variant &p2 = Variant(), const Variant &p3 = Variant(), const Variant &p4 = Variant(), const Variant &p5 = Variant()); @@ -42,4 +42,6 @@ bool is_csharp_keyword(const String &p_name); String escape_csharp_keyword(const String &p_name); #endif +Error read_all_file_utf8(const String &p_path, String &r_content); + #endif // STRING_FORMAT_H diff --git a/modules/mono/utils/thread_local.cpp b/modules/mono/utils/thread_local.cpp index 6f8b0f90bc..a0e28fca5f 100644 --- a/modules/mono/utils/thread_local.cpp +++ b/modules/mono/utils/thread_local.cpp @@ -63,7 +63,13 @@ struct ThreadLocalStorage::Impl { #endif } - Impl(void (*p_destr_callback_func)(void *)) { +#ifdef WINDOWS_ENABLED +#define _CALLBACK_FUNC_ __stdcall +#else +#define _CALLBACK_FUNC_ +#endif + + Impl(void(_CALLBACK_FUNC_ *p_destr_callback_func)(void *)) { #ifdef WINDOWS_ENABLED dwFlsIndex = FlsAlloc(p_destr_callback_func); ERR_FAIL_COND(dwFlsIndex == FLS_OUT_OF_INDEXES); @@ -89,10 +95,12 @@ void ThreadLocalStorage::set_value(void *p_value) const { pimpl->set_value(p_value); } -void ThreadLocalStorage::alloc(void (*p_destr_callback)(void *)) { +void ThreadLocalStorage::alloc(void(_CALLBACK_FUNC_ *p_destr_callback)(void *)) { pimpl = memnew(ThreadLocalStorage::Impl(p_destr_callback)); } +#undef _CALLBACK_FUNC_ + void ThreadLocalStorage::free() { memdelete(pimpl); pimpl = NULL; diff --git a/modules/mono/utils/thread_local.h b/modules/mono/utils/thread_local.h index 7ff10b4efc..d7d98c47e2 100644 --- a/modules/mono/utils/thread_local.h +++ b/modules/mono/utils/thread_local.h @@ -65,12 +65,18 @@ #include "core/typedefs.h" +#ifdef WINDOWS_ENABLED +#define _CALLBACK_FUNC_ __stdcall +#else +#define _CALLBACK_FUNC_ +#endif + struct ThreadLocalStorage { void *get_value() const; void set_value(void *p_value) const; - void alloc(void (*p_dest_callback)(void *)); + void alloc(void(_CALLBACK_FUNC_ *p_dest_callback)(void *)); void free(); private: @@ -85,18 +91,10 @@ class ThreadLocal { T init_val; -#ifdef WINDOWS_ENABLED -#define _CALLBACK_FUNC_ __stdcall -#else -#define _CALLBACK_FUNC_ -#endif - static void _CALLBACK_FUNC_ destr_callback(void *tls_data) { memdelete(static_cast<T *>(tls_data)); } -#undef _CALLBACK_FUNC_ - T *_tls_get_value() const { void *tls_data = storage.get_value(); @@ -110,17 +108,23 @@ class ThreadLocal { return data; } + void _initialize(const T &p_init_val) { + init_val = p_init_val; + storage.alloc(&destr_callback); + } + public: - ThreadLocal() : - ThreadLocal(T()) {} + ThreadLocal() { + _initialize(T()); + } - ThreadLocal(const T &p_init_val) : - init_val(p_init_val) { - storage.alloc(&destr_callback); + ThreadLocal(const T &p_init_val) { + _initialize(p_init_val); } - ThreadLocal(const ThreadLocal &other) : - ThreadLocal(*other._tls_get_value()) {} + ThreadLocal(const ThreadLocal &other) { + _initialize(*other._tls_get_value()); + } ~ThreadLocal() { storage.free(); @@ -156,6 +160,8 @@ private: bool &flag; }; +#undef _CALLBACK_FUNC_ + #define _TLS_RECURSION_GUARD_V_(m_ret) \ static _THREAD_LOCAL_(bool) _recursion_flag_ = false; \ if (_recursion_flag_) \ diff --git a/modules/ogg/SCsub b/modules/ogg/SCsub index 5e559bd4db..765a9fc11a 100644 --- a/modules/ogg/SCsub +++ b/modules/ogg/SCsub @@ -14,8 +14,11 @@ if env['builtin_libogg']: ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_ogg.add_source_files(env.modules_sources, thirdparty_sources) env_ogg.Append(CPPPATH=[thirdparty_dir]) + env_thirdparty = env_ogg.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Godot source files env_ogg.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/opensimplex/SCsub b/modules/opensimplex/SCsub new file mode 100644 index 0000000000..4235f6a0b9 --- /dev/null +++ b/modules/opensimplex/SCsub @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_opensimplex = env_modules.Clone() + +# Thirdparty source files +thirdparty_dir = "#thirdparty/misc/" +thirdparty_sources = [ + "open-simplex-noise.c", +] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +env_opensimplex.Append(CPPPATH=[thirdparty_dir]) + +env_thirdparty = env_opensimplex.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + +# Godot's own source files +env_opensimplex.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/opensimplex/config.py b/modules/opensimplex/config.py new file mode 100644 index 0000000000..c1010ad433 --- /dev/null +++ b/modules/opensimplex/config.py @@ -0,0 +1,14 @@ +def can_build(env, platform): + return True + +def configure(env): + pass + +def get_doc_classes(): + return [ + "NoiseTexture", + "OpenSimplexNoise" + ] + +def get_doc_path(): + return "doc_classes" diff --git a/modules/opensimplex/doc_classes/NoiseTexture.xml b/modules/opensimplex/doc_classes/NoiseTexture.xml new file mode 100644 index 0000000000..ba54160a90 --- /dev/null +++ b/modules/opensimplex/doc_classes/NoiseTexture.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="NoiseTexture" inherits="Texture" category="Core" version="3.1"> + <brief_description> + [OpenSimplexNoise] filled texture. + </brief_description> + <description> + Uses an [OpenSimplexNoise] to fill the texture data. You can specify the texture size but keep in mind that larger textures will take longer to generate and seamless noise only works with square sized textures. + NoiseTexture can also generate normalmap textures. + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="as_normalmap" type="bool" setter="set_as_normalmap" getter="is_normalmap"> + If true, the resulting texture contains a normal map created from the original noise interpreted as a bump map. + </member> + <member name="height" type="int" setter="set_height" getter="get_height"> + Height of the generated texture. + </member> + <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"> + Whether the texture can be tiled without visible seams or not. Seamless textures take longer to generate. + </member> + <member name="width" type="int" setter="set_width" getter="get_width"> + Width of the generated texture. + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/opensimplex/doc_classes/OpenSimplexNoise.xml b/modules/opensimplex/doc_classes/OpenSimplexNoise.xml new file mode 100644 index 0000000000..31f13f341c --- /dev/null +++ b/modules/opensimplex/doc_classes/OpenSimplexNoise.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenSimplexNoise" inherits="Resource" category="Core" version="3.1"> + <brief_description> + Noise generator based on Open Simplex. + </brief_description> + <description> + This resource allows you to configure and sample a fractal noise space. Here is a brief usage example that configures an OpenSimplexNoise and gets samples at various positions and dimensions: + [codeblock] + var noise = OpenSimplexNoise.new() + + # Configure + noise.seed = randi() + noise.octaves = 4 + noise.period = 20.0 + noise.persistence = 0.8 + + # Sample + print("Values:") + print(noise.get_noise_2d(1.0, 1.0)) + print(noise.get_noise_3d(0.5, 3.0, 15.0)) + print(noise.get_noise_4d(0.5, 1.9, 4.7, 0.0)) + [/codeblock] + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="get_image"> + <return type="Image"> + </return> + <argument index="0" name="width" type="int"> + </argument> + <argument index="1" name="height" type="int"> + </argument> + <description> + Generate a noise image with the requested [code]width[/code] and [code]height[/code], based on the current noise parameters. + </description> + </method> + <method name="get_noise_2d"> + <return type="float"> + </return> + <argument index="0" name="x" type="float"> + </argument> + <argument index="1" name="y" type="float"> + </argument> + <description> + Returns the 2D noise value [code][-1,1][/code] at the given position. + </description> + </method> + <method name="get_noise_2dv"> + <return type="float"> + </return> + <argument index="0" name="pos" type="Vector2"> + </argument> + <description> + Returns the 2D noise value [code][-1,1][/code] at the given position. + </description> + </method> + <method name="get_noise_3d"> + <return type="float"> + </return> + <argument index="0" name="x" type="float"> + </argument> + <argument index="1" name="y" type="float"> + </argument> + <argument index="2" name="z" type="float"> + </argument> + <description> + Returns the 3D noise value [code][-1,1][/code] at the given position. + </description> + </method> + <method name="get_noise_3dv"> + <return type="float"> + </return> + <argument index="0" name="pos" type="Vector3"> + </argument> + <description> + Returns the 3D noise value [code][-1,1][/code] at the given position. + </description> + </method> + <method name="get_noise_4d"> + <return type="float"> + </return> + <argument index="0" name="x" type="float"> + </argument> + <argument index="1" name="y" type="float"> + </argument> + <argument index="2" name="z" type="float"> + </argument> + <argument index="3" name="w" type="float"> + </argument> + <description> + Returns the 4D noise value [code][-1,1][/code] at the given position. + </description> + </method> + <method name="get_seamless_image"> + <return type="Image"> + </return> + <argument index="0" name="size" type="int"> + </argument> + <description> + Generate a tileable noise image, based on the current noise parameters. Generated seamless images are always square ([code]size[/code] x [code]size[/code]). + </description> + </method> + </methods> + <members> + <member name="lacunarity" type="float" setter="set_lacunarity" getter="get_lacunarity"> + Difference in period between [member octaves]. + </member> + <member name="octaves" type="int" setter="set_octaves" getter="get_octaves"> + Number of OpenSimplex noise layers that are sampled to get the fractal noise. + </member> + <member name="period" type="float" setter="set_period" getter="get_period"> + Period of the base octave. A lower period results in a higher-frequency noise (more value changes across the same distance). + </member> + <member name="persistence" type="float" setter="set_persistence" getter="get_persistence"> + Contribution factor of the different octaves. A [code]persistence[/code] value of 1 means all the octaves have the same contribution, a value of 0.5 means each octave contributes half as much as the previous one. + </member> + <member name="seed" type="int" setter="set_seed" getter="get_seed"> + Seed used to generate random values, different seeds will generate different noise maps. + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/opensimplex/noise_texture.cpp b/modules/opensimplex/noise_texture.cpp new file mode 100644 index 0000000000..be522a9ab1 --- /dev/null +++ b/modules/opensimplex/noise_texture.cpp @@ -0,0 +1,233 @@ +/*************************************************************************/ +/* noise_texture.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 "noise_texture.h" + +#include "core/core_string_names.h" + +NoiseTexture::NoiseTexture() { + update_queued = false; + noise_thread = NULL; + regen_queued = false; + first_time = true; + + size = Vector2i(512, 512); + seamless = false; + as_normalmap = false; + flags = FLAGS_DEFAULT; + + noise = Ref<OpenSimplexNoise>(); + + texture = VS::get_singleton()->texture_create(); + + _queue_update(); +} + +NoiseTexture::~NoiseTexture() { + VS::get_singleton()->free(texture); +} + +void NoiseTexture::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_width", "width"), &NoiseTexture::set_width); + ClassDB::bind_method(D_METHOD("set_height", "height"), &NoiseTexture::set_height); + + ClassDB::bind_method(D_METHOD("set_noise", "noise"), &NoiseTexture::set_noise); + ClassDB::bind_method(D_METHOD("get_noise"), &NoiseTexture::get_noise); + + ClassDB::bind_method(D_METHOD("set_seamless", "seamless"), &NoiseTexture::set_seamless); + ClassDB::bind_method(D_METHOD("get_seamless"), &NoiseTexture::get_seamless); + + ClassDB::bind_method(D_METHOD("set_as_normalmap", "as_normalmap"), &NoiseTexture::set_as_normalmap); + ClassDB::bind_method(D_METHOD("is_normalmap"), &NoiseTexture::is_normalmap); + + ClassDB::bind_method(D_METHOD("_update_texture"), &NoiseTexture::_update_texture); + ClassDB::bind_method(D_METHOD("_generate_texture"), &NoiseTexture::_generate_texture); + ClassDB::bind_method(D_METHOD("_thread_done", "image"), &NoiseTexture::_thread_done); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,2048,1,or_greater"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "height", PROPERTY_HINT_RANGE, "1,2048,1,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "seamless"), "set_seamless", "get_seamless"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "as_normalmap"), "set_as_normalmap", "is_normalmap"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "OpenSimplexNoise"), "set_noise", "get_noise"); +} + +void NoiseTexture::_set_texture_data(const Ref<Image> &p_image) { + data = p_image; + if (data.is_valid()) { + VS::get_singleton()->texture_allocate(texture, size.x, size.y, 0, Image::FORMAT_RGBA8, VS::TEXTURE_TYPE_2D, flags); + VS::get_singleton()->texture_set_data(texture, p_image); + } + emit_changed(); +} + +void NoiseTexture::_thread_done(const Ref<Image> &p_image) { + + _set_texture_data(p_image); + Thread::wait_to_finish(noise_thread); + memdelete(noise_thread); + noise_thread = NULL; + if (regen_queued) { + noise_thread = Thread::create(_thread_function, this); + regen_queued = false; + } +} + +void NoiseTexture::_thread_function(void *p_ud) { + NoiseTexture *tex = (NoiseTexture *)p_ud; + tex->call_deferred("_thread_done", tex->_generate_texture()); +} + +void NoiseTexture::_queue_update() { + + if (update_queued) + return; + + update_queued = true; + call_deferred("_update_texture"); +} + +Ref<Image> NoiseTexture::_generate_texture() { + + update_queued = false; + + if (noise.is_null()) return Ref<Image>(); + + Ref<Image> image; + + if (seamless) { + image = noise->get_seamless_image(size.x); + } else { + image = noise->get_image(size.x, size.y); + } + + if (as_normalmap) { + image->bumpmap_to_normalmap(); + } + + return image; +} + +void NoiseTexture::_update_texture() { + bool use_thread = true; + if (first_time) { + use_thread = false; + first_time = false; + } +#ifdef NO_THREADS + use_thread = false; +#endif + if (use_thread) { + + if (!noise_thread) { + noise_thread = Thread::create(_thread_function, this); + regen_queued = false; + } else { + regen_queued = true; + } + + } else { + Ref<Image> image = _generate_texture(); + _set_texture_data(image); + } +} + +void NoiseTexture::set_noise(Ref<OpenSimplexNoise> p_noise) { + if (p_noise == noise) + return; + if (noise.is_valid()) { + noise->disconnect(CoreStringNames::get_singleton()->changed, this, "_update_texture"); + } + noise = p_noise; + if (noise.is_valid()) { + noise->connect(CoreStringNames::get_singleton()->changed, this, "_update_texture"); + } + _queue_update(); +} + +Ref<OpenSimplexNoise> NoiseTexture::get_noise() { + return noise; +} + +void NoiseTexture::set_width(int p_width) { + if (p_width == size.x) return; + size.x = p_width; + _queue_update(); +} + +void NoiseTexture::set_height(int p_height) { + if (p_height == size.y) return; + size.y = p_height; + _queue_update(); +} + +void NoiseTexture::set_seamless(bool p_seamless) { + if (p_seamless == seamless) return; + seamless = p_seamless; + _queue_update(); +} + +bool NoiseTexture::get_seamless() { + return seamless; +} + +void NoiseTexture::set_as_normalmap(bool p_as_normalmap) { + if (p_as_normalmap == as_normalmap) return; + as_normalmap = p_as_normalmap; + _queue_update(); +} + +bool NoiseTexture::is_normalmap() { + return as_normalmap; +} + +int NoiseTexture::get_width() const { + + return size.x; +} + +int NoiseTexture::get_height() const { + + return size.y; +} + +void NoiseTexture::set_flags(uint32_t p_flags) { + flags = p_flags; + VS::get_singleton()->texture_set_flags(texture, flags); +} + +uint32_t NoiseTexture::get_flags() const { + return flags; +} + +Ref<Image> NoiseTexture::get_data() const { + + return data; +} diff --git a/modules/opensimplex/noise_texture.h b/modules/opensimplex/noise_texture.h new file mode 100644 index 0000000000..2a4c32d633 --- /dev/null +++ b/modules/opensimplex/noise_texture.h @@ -0,0 +1,101 @@ +/*************************************************************************/ +/* noise_texture.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef NOISE_TEXTURE_H +#define NOISE_TEXTURE_H + +#include "open_simplex_noise.h" + +#include "core/image.h" +#include "core/reference.h" +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "editor/property_editor.h" + +class NoiseTexture : public Texture { + GDCLASS(NoiseTexture, Texture) + +private: + Ref<Image> data; + + Thread *noise_thread; + + bool first_time; + bool update_queued; + bool regen_queued; + + RID texture; + uint32_t flags; + + Ref<OpenSimplexNoise> noise; + Vector2i size; + bool seamless; + bool as_normalmap; + + void _thread_done(const Ref<Image> &p_image); + static void _thread_function(void *p_ud); + + void _queue_update(); + Ref<Image> _generate_texture(); + void _update_texture(); + void _set_texture_data(const Ref<Image> &p_image); + +protected: + static void _bind_methods(); + +public: + void set_noise(Ref<OpenSimplexNoise> p_noise); + Ref<OpenSimplexNoise> get_noise(); + + void set_width(int p_width); + void set_height(int p_hieght); + + void set_seamless(bool p_seamless); + bool get_seamless(); + + void set_as_normalmap(bool p_seamless); + bool is_normalmap(); + + int get_width() const; + int get_height() const; + + virtual void set_flags(uint32_t p_flags); + virtual uint32_t get_flags() const; + + virtual RID get_rid() const { return texture; } + virtual bool has_alpha() const { return false; } + + virtual Ref<Image> get_data() const; + + NoiseTexture(); + virtual ~NoiseTexture(); +}; + +#endif // NOISE_TEXTURE_H diff --git a/modules/opensimplex/open_simplex_noise.cpp b/modules/opensimplex/open_simplex_noise.cpp new file mode 100644 index 0000000000..bfc2732ff4 --- /dev/null +++ b/modules/opensimplex/open_simplex_noise.cpp @@ -0,0 +1,257 @@ +/*************************************************************************/ +/* open_simplex_noise.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 "open_simplex_noise.h" + +#include "core/core_string_names.h" + +OpenSimplexNoise::OpenSimplexNoise() { + + seed = 0; + persistence = 0.5; + octaves = 3; + period = 64; + lacunarity = 2.0; + + _init_seeds(); +} + +OpenSimplexNoise::~OpenSimplexNoise() { +} + +void OpenSimplexNoise::_init_seeds() { + for (int i = 0; i < 6; ++i) { + open_simplex_noise(seed + i * 2, &(contexts[i])); + } +} + +void OpenSimplexNoise::set_seed(int p_seed) { + + if (seed == p_seed) + return; + + seed = p_seed; + + _init_seeds(); + + emit_changed(); +} + +int OpenSimplexNoise::get_seed() { + + return seed; +} + +void OpenSimplexNoise::set_octaves(int p_octaves) { + if (p_octaves == octaves) return; + octaves = CLAMP(p_octaves, 1, 6); + emit_changed(); +} + +void OpenSimplexNoise::set_period(float p_period) { + if (p_period == period) return; + period = p_period; + emit_changed(); +} + +void OpenSimplexNoise::set_persistence(float p_persistence) { + if (p_persistence == persistence) return; + persistence = p_persistence; + emit_changed(); +} + +void OpenSimplexNoise::set_lacunarity(float p_lacunarity) { + if (p_lacunarity == lacunarity) return; + lacunarity = p_lacunarity; + emit_changed(); +} + +Ref<Image> OpenSimplexNoise::get_image(int p_width, int p_height) { + + PoolVector<uint8_t> data; + data.resize(p_width * p_height * 4); + + PoolVector<uint8_t>::Write wd8 = data.write(); + + for (int i = 0; i < p_height; i++) { + for (int j = 0; j < p_width; j++) { + float v = get_noise_2d(i, j); + v = v * 0.5 + 0.5; // Normalize [0..1] + uint8_t value = uint8_t(CLAMP(v * 255.0, 0, 255)); + wd8[(i * p_width + j) * 4 + 0] = value; + wd8[(i * p_width + j) * 4 + 1] = value; + wd8[(i * p_width + j) * 4 + 2] = value; + wd8[(i * p_width + j) * 4 + 3] = 255; + } + } + + Ref<Image> image = memnew(Image(p_width, p_height, false, Image::FORMAT_RGBA8, data)); + return image; +} + +Ref<Image> OpenSimplexNoise::get_seamless_image(int p_size) { + + PoolVector<uint8_t> data; + data.resize(p_size * p_size * 4); + + PoolVector<uint8_t>::Write wd8 = data.write(); + + for (int i = 0; i < p_size; i++) { + for (int j = 0; j < p_size; j++) { + + float ii = (float)i / (float)p_size; + float jj = (float)j / (float)p_size; + + ii *= 2.0 * Math_PI; + jj *= 2.0 * Math_PI; + + float radius = p_size / (2.0 * Math_PI); + + float x = radius * Math::sin(jj); + float y = radius * Math::cos(jj); + float z = radius * Math::sin(ii); + float w = radius * Math::cos(ii); + float v = get_noise_4d(x, y, z, w); + + v = v * 0.5 + 0.5; // Normalize [0..1] + uint8_t value = uint8_t(CLAMP(v * 255.0, 0, 255)); + wd8[(i * p_size + j) * 4 + 0] = value; + wd8[(i * p_size + j) * 4 + 1] = value; + wd8[(i * p_size + j) * 4 + 2] = value; + wd8[(i * p_size + j) * 4 + 3] = 255; + } + } + + Ref<Image> image = memnew(Image(p_size, p_size, false, Image::FORMAT_RGBA8, data)); + return image; +} + +void OpenSimplexNoise::_bind_methods() { + + ClassDB::bind_method(D_METHOD("get_seed"), &OpenSimplexNoise::get_seed); + ClassDB::bind_method(D_METHOD("set_seed", "seed"), &OpenSimplexNoise::set_seed); + + ClassDB::bind_method(D_METHOD("set_octaves", "octave_count"), &OpenSimplexNoise::set_octaves); + ClassDB::bind_method(D_METHOD("get_octaves"), &OpenSimplexNoise::get_octaves); + + ClassDB::bind_method(D_METHOD("set_period", "period"), &OpenSimplexNoise::set_period); + ClassDB::bind_method(D_METHOD("get_period"), &OpenSimplexNoise::get_period); + + ClassDB::bind_method(D_METHOD("set_persistence", "persistence"), &OpenSimplexNoise::set_persistence); + ClassDB::bind_method(D_METHOD("get_persistence"), &OpenSimplexNoise::get_persistence); + + ClassDB::bind_method(D_METHOD("set_lacunarity", "lacunarity"), &OpenSimplexNoise::set_lacunarity); + ClassDB::bind_method(D_METHOD("get_lacunarity"), &OpenSimplexNoise::get_lacunarity); + + ClassDB::bind_method(D_METHOD("get_image", "width", "height"), &OpenSimplexNoise::get_image); + ClassDB::bind_method(D_METHOD("get_seamless_image", "size"), &OpenSimplexNoise::get_seamless_image); + + ClassDB::bind_method(D_METHOD("get_noise_2d", "x", "y"), &OpenSimplexNoise::get_noise_2d); + ClassDB::bind_method(D_METHOD("get_noise_3d", "x", "y", "z"), &OpenSimplexNoise::get_noise_3d); + ClassDB::bind_method(D_METHOD("get_noise_4d", "x", "y", "z", "w"), &OpenSimplexNoise::get_noise_4d); + + ClassDB::bind_method(D_METHOD("get_noise_2dv", "pos"), &OpenSimplexNoise::get_noise_2dv); + ClassDB::bind_method(D_METHOD("get_noise_3dv", "pos"), &OpenSimplexNoise::get_noise_3dv); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "seed"), "set_seed", "get_seed"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "octaves", PROPERTY_HINT_RANGE, "1,6,1"), "set_octaves", "get_octaves"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "period", PROPERTY_HINT_RANGE, "0.1,256.0,0.1"), "set_period", "get_period"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "persistence", PROPERTY_HINT_RANGE, "0.0,1.0,0.001"), "set_persistence", "get_persistence"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "lacunarity", PROPERTY_HINT_RANGE, "0.1,4.0,0.01"), "set_lacunarity", "get_lacunarity"); +} + +float OpenSimplexNoise::get_noise_2d(float x, float y) { + + x /= period; + y /= period; + + float amp = 1.0; + float max = 1.0; + float sum = _get_octave_noise_2d(0, x, y); + + int i = 0; + while (++i < octaves) { + x *= lacunarity; + y *= lacunarity; + amp *= persistence; + max += amp; + sum += _get_octave_noise_2d(i, x, y) * amp; + } + + return sum / max; +} + +float OpenSimplexNoise::get_noise_3d(float x, float y, float z) { + + x /= period; + y /= period; + z /= period; + + float amp = 1.0; + float max = 1.0; + float sum = _get_octave_noise_3d(0, x, y, z); + + int i = 0; + while (++i < octaves) { + x *= lacunarity; + y *= lacunarity; + z *= lacunarity; + amp *= persistence; + max += amp; + sum += _get_octave_noise_3d(i, x, y, z) * amp; + } + + return sum / max; +} + +float OpenSimplexNoise::get_noise_4d(float x, float y, float z, float w) { + + x /= period; + y /= period; + z /= period; + w /= period; + + float amp = 1.0; + float max = 1.0; + float sum = _get_octave_noise_4d(0, x, y, z, w); + + int i = 0; + while (++i < octaves) { + x *= lacunarity; + y *= lacunarity; + z *= lacunarity; + w *= lacunarity; + amp *= persistence; + max += amp; + sum += _get_octave_noise_4d(i, x, y, z, w) * amp; + } + + return sum / max; +} diff --git a/modules/opensimplex/open_simplex_noise.h b/modules/opensimplex/open_simplex_noise.h new file mode 100644 index 0000000000..a9bee266e8 --- /dev/null +++ b/modules/opensimplex/open_simplex_noise.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* open_simplex_noise.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef OPEN_SIMPLEX_NOISE_H +#define OPEN_SIMPLEX_NOISE_H + +#include "core/image.h" +#include "core/reference.h" +#include "scene/resources/texture.h" + +#include "thirdparty/misc/open-simplex-noise.h" + +class OpenSimplexNoise : public Resource { + GDCLASS(OpenSimplexNoise, Resource) + OBJ_SAVE_TYPE(OpenSimplexNoise); + + osn_context contexts[6]; + + int seed; + float persistence; // Controls details, value in [0,1]. Higher increases grain, lower increases smoothness. + int octaves; // Number of noise layers + float period; // Distance above which we start to see similarities. The higher, the longer "hills" will be on a terrain. + float lacunarity; // Controls period change across octaves. 2 is usually a good value to address all detail levels. + +public: + OpenSimplexNoise(); + ~OpenSimplexNoise(); + + void _init_seeds(); + + void set_seed(int seed); + int get_seed(); + + void set_octaves(int p_octaves); + int get_octaves() const { return octaves; } + + void set_period(float p_period); + float get_period() const { return period; } + + void set_persistence(float p_persistence); + float get_persistence() const { return persistence; } + + void set_lacunarity(float p_lacunarity); + float get_lacunarity() const { return lacunarity; } + + Ref<Image> get_image(int p_width, int p_height); + Ref<Image> get_seamless_image(int p_size); + + float get_noise_2d(float x, float y); + float get_noise_3d(float x, float y, float z); + float get_noise_4d(float x, float y, float z, float w); + + _FORCE_INLINE_ float _get_octave_noise_2d(int octave, float x, float y) { return open_simplex_noise2(&(contexts[octave]), x, y); } + _FORCE_INLINE_ float _get_octave_noise_3d(int octave, float x, float y, float z) { return open_simplex_noise3(&(contexts[octave]), x, y, z); } + _FORCE_INLINE_ float _get_octave_noise_4d(int octave, float x, float y, float z, float w) { return open_simplex_noise4(&(contexts[octave]), x, y, z, w); } + + // Convenience + + _FORCE_INLINE_ float get_noise_2dv(Vector2 v) { return get_noise_2d(v.x, v.y); } + _FORCE_INLINE_ float get_noise_3dv(Vector3 v) { return get_noise_3d(v.x, v.y, v.z); } + +protected: + static void _bind_methods(); +}; + +#endif // OPEN_SIMPLEX_NOISE_H diff --git a/modules/opensimplex/register_types.cpp b/modules/opensimplex/register_types.cpp new file mode 100644 index 0000000000..d1c77da257 --- /dev/null +++ b/modules/opensimplex/register_types.cpp @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* register_types.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 "register_types.h" +#include "noise_texture.h" +#include "open_simplex_noise.h" + +void register_opensimplex_types() { + + ClassDB::register_class<OpenSimplexNoise>(); + ClassDB::register_class<NoiseTexture>(); +} + +void unregister_opensimplex_types() { +} diff --git a/modules/opensimplex/register_types.h b/modules/opensimplex/register_types.h new file mode 100644 index 0000000000..5e71a30ea6 --- /dev/null +++ b/modules/opensimplex/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +void register_opensimplex_types(); +void unregister_opensimplex_types(); diff --git a/modules/opus/SCsub b/modules/opus/SCsub index 6f643ef08c..508aec7057 100644 --- a/modules/opus/SCsub +++ b/modules/opus/SCsub @@ -3,7 +3,6 @@ Import('env') Import('env_modules') - stub = True env_opus = env_modules.Clone() @@ -198,7 +197,10 @@ if env['builtin_opus']: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources + opus_sources_silk] - env_opus.add_source_files(env.modules_sources, thirdparty_sources) + # also requires libogg + if env['builtin_libogg']: + env_opus.Append(CPPPATH=["#thirdparty/libogg"]) + env_opus.Append(CFLAGS=["-DHAVE_CONFIG_H"]) thirdparty_include_paths = [ @@ -211,9 +213,9 @@ if env['builtin_opus']: ] env_opus.Append(CPPPATH=[thirdparty_dir + "/" + dir for dir in thirdparty_include_paths]) - # also requires libogg - if env['builtin_libogg']: - env_opus.Append(CPPPATH=["#thirdparty/libogg"]) + env_thirdparty = env_opus.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) if not stub: # Module files diff --git a/modules/opus/audio_stream_opus.cpp b/modules/opus/audio_stream_opus.cpp index 8323ff33ac..3920b3cd6e 100644 --- a/modules/opus/audio_stream_opus.cpp +++ b/modules/opus/audio_stream_opus.cpp @@ -142,7 +142,7 @@ Error AudioStreamPlaybackOpus::_load_stream() { } break; case OP_EBADLINK: // - Failed to find old data after seeking. case OP_EBADTIMESTAMP: // - Timestamp failed the validity checks. - case OP_EBADHEADER: { // - Invalid or mising Opus bitstream header. + case OP_EBADHEADER: { // - Invalid or missing Opus bitstream header. memdelete(f); f = NULL; ERR_FAIL_V(ERR_FILE_CORRUPT); @@ -208,7 +208,7 @@ Error AudioStreamPlaybackOpus::set_file(const String &p_file) { } break; case OP_EBADLINK: // - Failed to find old data after seeking. case OP_EBADTIMESTAMP: // - Timestamp failed the validity checks. - case OP_EBADHEADER: { // - Invalid or mising Opus bitstream header. + case OP_EBADHEADER: { // - Invalid or missing Opus bitstream header. memdelete(f); f = NULL; ERR_FAIL_V(ERR_FILE_CORRUPT); diff --git a/modules/opus/audio_stream_opus.h b/modules/opus/audio_stream_opus.h index 3ffdaf2c18..c004adeb77 100644 --- a/modules/opus/audio_stream_opus.h +++ b/modules/opus/audio_stream_opus.h @@ -31,8 +31,8 @@ #ifndef AUDIO_STREAM_OPUS_H #define AUDIO_STREAM_OPUS_H -#include "io/resource_loader.h" -#include "os/file_access.h" +#include "core/io/resource_loader.h" +#include "core/os/file_access.h" #include "scene/resources/audio_stream.h" #include <opus/opusfile.h> diff --git a/modules/pvr/SCsub b/modules/pvr/SCsub index ddca7a794e..2e4a792a36 100644 --- a/modules/pvr/SCsub +++ b/modules/pvr/SCsub @@ -17,8 +17,11 @@ thirdparty_sources = [ ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env_pvr.add_source_files(env.modules_sources, thirdparty_sources) env_pvr.Append(CPPPATH=[thirdparty_dir]) +env_thirdparty = env_pvr.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Godot source files env_pvr.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp index 8174bccdb7..e6718eb4a2 100644 --- a/modules/pvr/texture_loader_pvr.cpp +++ b/modules/pvr/texture_loader_pvr.cpp @@ -31,7 +31,7 @@ #include "texture_loader_pvr.h" #include "PvrTcEncoder.h" #include "RgbaBitmap.h" -#include "os/file_access.h" +#include "core/os/file_access.h" #include <string.h> static void _pvrtc_decompress(Image *p_img); @@ -93,7 +93,7 @@ RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path, print_line("bmask: "+itos(bmask)); print_line("amask: "+itos(amask)); print_line("surfcount: "+itos(surfcount)); -*/ + */ PoolVector<uint8_t> data; data.resize(surfsize); @@ -159,8 +159,6 @@ RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path, if (mipmaps) tex_flags |= Texture::FLAG_MIPMAPS; - print_line("flip: " + itos(flags & PVR_VFLIP)); - Ref<Image> image = memnew(Image(width, height, mipmaps, format, data)); ERR_FAIL_COND_V(image->empty(), RES()); @@ -240,11 +238,11 @@ ResourceFormatPVR::ResourceFormatPVR() { Image::_image_compress_pvrtc2_func = _compress_pvrtc4; } - ///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// - //PVRTC decompressor, Based on PVRTC decompressor by IMGTEC. +//PVRTC decompressor, Based on PVRTC decompressor by IMGTEC. - ///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// #define PT_INDEX 2 #define BLK_Y_SIZE 4 @@ -646,12 +644,6 @@ static void decompress_pvrtc(PVRTCBlock *p_comp_img, const int p_2bit, const int static void _pvrtc_decompress(Image *p_img) { - /* - static void decompress_pvrtc(const void *p_comp_img, const int p_2bit, const int p_width, const int p_height, unsigned char* p_dst) { - decompress_pvrtc((PVRTCBlock*)p_comp_img,p_2bit,p_width,p_height,1,p_dst); - } - */ - ERR_FAIL_COND(p_img->get_format() != Image::FORMAT_PVRTC2 && p_img->get_format() != Image::FORMAT_PVRTC2A && p_img->get_format() != Image::FORMAT_PVRTC4 && p_img->get_format() != Image::FORMAT_PVRTC4A); bool _2bit = (p_img->get_format() == Image::FORMAT_PVRTC2 || p_img->get_format() == Image::FORMAT_PVRTC2A); @@ -665,12 +657,6 @@ 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()); - /* - for(int i=0;i<newdata.size();i++) { - print_line(itos(w[i])); - } - */ - w = PoolVector<uint8_t>::Write(); r = PoolVector<uint8_t>::Read(); diff --git a/modules/pvr/texture_loader_pvr.h b/modules/pvr/texture_loader_pvr.h index 9369178336..c859a4cdda 100644 --- a/modules/pvr/texture_loader_pvr.h +++ b/modules/pvr/texture_loader_pvr.h @@ -31,7 +31,7 @@ #ifndef TEXTURE_LOADER_PVR_H #define TEXTURE_LOADER_PVR_H -#include "io/resource_loader.h" +#include "core/io/resource_loader.h" #include "scene/resources/texture.h" class ResourceFormatPVR : public ResourceFormatLoader { diff --git a/modules/recast/SCsub b/modules/recast/SCsub index f56be72b24..4a06653968 100644 --- a/modules/recast/SCsub +++ b/modules/recast/SCsub @@ -23,10 +23,11 @@ if env['builtin_recast']: ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_recast.add_source_files(env.modules_sources, thirdparty_sources) env_recast.Append(CPPPATH=[thirdparty_dir + "/Include"]) + env_thirdparty = env_recast.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Godot source files env_recast.add_source_files(env.modules_sources, "*.cpp") - -Export('env') diff --git a/modules/recast/navigation_mesh_editor_plugin.cpp b/modules/recast/navigation_mesh_editor_plugin.cpp index 8556b7aa0a..d121a82d9e 100644 --- a/modules/recast/navigation_mesh_editor_plugin.cpp +++ b/modules/recast/navigation_mesh_editor_plugin.cpp @@ -30,8 +30,8 @@ #include "navigation_mesh_editor_plugin.h" -#include "io/marshalls.h" -#include "io/resource_saver.h" +#include "core/io/marshalls.h" +#include "core/io/resource_saver.h" #include "scene/3d/mesh_instance.h" #include "scene/gui/box_container.h" @@ -103,24 +103,25 @@ void NavigationMeshEditor::_bind_methods() { NavigationMeshEditor::NavigationMeshEditor() { bake_hbox = memnew(HBoxContainer); + button_bake = memnew(ToolButton); - button_bake->set_text(TTR("Bake!")); + bake_hbox->add_child(button_bake); button_bake->set_toggle_mode(true); - button_reset = memnew(Button); - button_bake->set_tooltip(TTR("Bake the navigation mesh.") + "\n"); + button_bake->set_text(TTR("Bake NavMesh")); + button_bake->connect("pressed", this, "_bake_pressed"); - bake_info = memnew(Label); - bake_hbox->add_child(button_bake); + button_reset = memnew(ToolButton); bake_hbox->add_child(button_reset); + // No button text, we only use a revert icon which is set when entering the tree. + button_reset->set_tooltip(TTR("Clear the navigation mesh.")); + button_reset->connect("pressed", this, "_clear_pressed"); + + bake_info = memnew(Label); bake_hbox->add_child(bake_info); err_dialog = memnew(AcceptDialog); add_child(err_dialog); node = NULL; - - button_bake->connect("pressed", this, "_bake_pressed"); - button_reset->connect("pressed", this, "_clear_pressed"); - button_reset->set_tooltip(TTR("Clear the navigation mesh.")); } NavigationMeshEditor::~NavigationMeshEditor() { diff --git a/modules/recast/navigation_mesh_editor_plugin.h b/modules/recast/navigation_mesh_editor_plugin.h index 4f3e222002..914d9ab8b9 100644 --- a/modules/recast/navigation_mesh_editor_plugin.h +++ b/modules/recast/navigation_mesh_editor_plugin.h @@ -43,8 +43,8 @@ class NavigationMeshEditor : public Control { AcceptDialog *err_dialog; HBoxContainer *bake_hbox; - Button *button_bake; - Button *button_reset; + ToolButton *button_bake; + ToolButton *button_reset; Label *bake_info; NavigationMeshInstance *node; diff --git a/modules/recast/navigation_mesh_generator.cpp b/modules/recast/navigation_mesh_generator.cpp index 64c4b85269..c1ce2592a4 100644 --- a/modules/recast/navigation_mesh_generator.cpp +++ b/modules/recast/navigation_mesh_generator.cpp @@ -126,9 +126,9 @@ void NavigationMeshGenerator::_convert_detail_mesh_to_native_navigation_mesh(con for (unsigned int j = 0; j < ntris; j++) { Vector<int> nav_indices; nav_indices.resize(3); - nav_indices[0] = ((int)(bverts + tris[j * 4 + 0])); - nav_indices[1] = ((int)(bverts + tris[j * 4 + 1])); - nav_indices[2] = ((int)(bverts + tris[j * 4 + 2])); + nav_indices.write[0] = ((int)(bverts + tris[j * 4 + 0])); + nav_indices.write[1] = ((int)(bverts + tris[j * 4 + 1])); + nav_indices.write[2] = ((int)(bverts + tris[j * 4 + 2])); p_nav_mesh->add_polygon(nav_indices); } } diff --git a/modules/recast/navigation_mesh_generator.h b/modules/recast/navigation_mesh_generator.h index 3588539ef1..2f2f57d721 100644 --- a/modules/recast/navigation_mesh_generator.h +++ b/modules/recast/navigation_mesh_generator.h @@ -31,9 +31,9 @@ #ifndef NAVIGATION_MESH_GENERATOR_H #define NAVIGATION_MESH_GENERATOR_H +#include "core/os/thread.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" -#include "os/thread.h" #include "scene/3d/mesh_instance.h" #include "scene/3d/navigation_mesh.h" #include "scene/resources/shape.h" diff --git a/modules/regex/SCsub b/modules/regex/SCsub index 4b8d5e9283..99c25add45 100644 --- a/modules/regex/SCsub +++ b/modules/regex/SCsub @@ -4,15 +4,16 @@ Import('env') Import('env_modules') env_regex = env_modules.Clone() -env_regex.Append(CPPFLAGS=["-DPCRE2_CODE_UNIT_WIDTH=0"]) -env_regex.add_source_files(env.modules_sources, "*.cpp") if env['builtin_pcre2']: jit_blacklist = ['javascript', 'uwp'] + thirdparty_dir = '#thirdparty/pcre2/src/' thirdparty_flags = ['-DPCRE2_STATIC', '-DHAVE_CONFIG_H'] + if 'platform' in env and env['platform'] not in jit_blacklist: thirdparty_flags.append('-DSUPPORT_JIT') + thirdparty_sources = [ "pcre2_auto_possess.c", "pcre2_chartables.c", @@ -42,15 +43,21 @@ if env['builtin_pcre2']: "pcre2_valid_utf.c", "pcre2_xclass.c", ] + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + env_regex.Append(CPPPATH=[thirdparty_dir]) env_regex.Append(CPPFLAGS=thirdparty_flags) + def pcre2_builtin(width): - env_pcre2 = env_modules.Clone() + env_pcre2 = env_regex.Clone() + env_pcre2.disable_warnings() env_pcre2["OBJSUFFIX"] = "_" + width + env_pcre2["OBJSUFFIX"] - env_pcre2.Append(CPPPATH=[thirdparty_dir]) env_pcre2.add_source_files(env.modules_sources, thirdparty_sources) - env_pcre2.Append(CPPFLAGS=thirdparty_flags) env_pcre2.Append(CPPFLAGS=["-DPCRE2_CODE_UNIT_WIDTH=" + width]) + pcre2_builtin("16") pcre2_builtin("32") + +env_regex.Append(CPPFLAGS=["-DPCRE2_CODE_UNIT_WIDTH=0"]) +env_regex.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp index 6f2bb46fc8..03ef024587 100644 --- a/modules/regex/regex.cpp +++ b/modules/regex/regex.cpp @@ -178,13 +178,17 @@ void RegEx::clear() { if (sizeof(CharType) == 2) { - if (code) + if (code) { pcre2_code_free_16((pcre2_code_16 *)code); + code = NULL; + } } else { - if (code) + if (code) { pcre2_code_free_32((pcre2_code_32 *)code); + code = NULL; + } } } @@ -205,6 +209,8 @@ Error RegEx::compile(const String &p_pattern) { code = pcre2_compile_16(p, pattern.length(), flags, &err, &offset, cctx); + pcre2_compile_context_free_16(cctx); + if (!code) { PCRE2_UCHAR16 buf[256]; pcre2_get_error_message_16(err, buf, 256); @@ -221,6 +227,8 @@ Error RegEx::compile(const String &p_pattern) { code = pcre2_compile_32(p, pattern.length(), flags, &err, &offset, cctx); + pcre2_compile_context_free_32(cctx); + if (!code) { PCRE2_UCHAR32 buf[256]; pcre2_get_error_message_32(err, buf, 256); @@ -265,8 +273,8 @@ Ref<RegExMatch> RegEx::search(const String &p_subject, int p_offset, int p_end) for (uint32_t i = 0; i < size; i++) { - result->data[i].start = ovector[i * 2]; - result->data[i].end = ovector[i * 2 + 1]; + result->data.write[i].start = ovector[i * 2]; + result->data.write[i].end = ovector[i * 2 + 1]; } pcre2_match_data_free_16(match); @@ -285,6 +293,8 @@ Ref<RegExMatch> RegEx::search(const String &p_subject, int p_offset, int p_end) if (res < 0) { pcre2_match_data_free_32(match); + pcre2_match_context_free_32(mctx); + return NULL; } @@ -295,8 +305,8 @@ Ref<RegExMatch> RegEx::search(const String &p_subject, int p_offset, int p_end) for (uint32_t i = 0; i < size; i++) { - result->data[i].start = ovector[i * 2]; - result->data[i].end = ovector[i * 2 + 1]; + result->data.write[i].start = ovector[i * 2]; + result->data.write[i].end = ovector[i * 2 + 1]; } pcre2_match_data_free_32(match); diff --git a/modules/regex/register_types.cpp b/modules/regex/register_types.cpp index 14eba69ee0..73e2c5022d 100644 --- a/modules/regex/register_types.cpp +++ b/modules/regex/register_types.cpp @@ -29,7 +29,7 @@ /*************************************************************************/ #include "register_types.h" -#include "class_db.h" +#include "core/class_db.h" #include "regex.h" void register_regex_types() { diff --git a/modules/squish/SCsub b/modules/squish/SCsub index 127f22d798..3be85a1efa 100644 --- a/modules/squish/SCsub +++ b/modules/squish/SCsub @@ -22,8 +22,11 @@ if env['builtin_squish']: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_squish.add_source_files(env.modules_sources, thirdparty_sources) env_squish.Append(CPPPATH=[thirdparty_dir]) + env_thirdparty = env_squish.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Godot source files env_squish.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/squish/config.py b/modules/squish/config.py index 098f1eafa9..1c8cd12a2d 100644 --- a/modules/squish/config.py +++ b/modules/squish/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return env['tools'] + return True def configure(env): pass diff --git a/modules/squish/image_compress_squish.cpp b/modules/squish/image_compress_squish.cpp index 0cf24dd8d8..4161a0f6ae 100644 --- a/modules/squish/image_compress_squish.cpp +++ b/modules/squish/image_compress_squish.cpp @@ -30,14 +30,6 @@ #include "image_compress_squish.h" -#include "print_string.h" - -#if defined(__SSE2__) -#define SQUISH_USE_SSE 2 -#elif defined(__SSE__) -#define SQUISH_USE_SSE 1 -#endif - #include <squish.h> void image_decompress_squish(Image *p_image) { @@ -46,7 +38,7 @@ void image_decompress_squish(Image *p_image) { Image::Format target_format = Image::FORMAT_RGBA8; PoolVector<uint8_t> data; - int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps() ? -1 : 0); + int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); int mm_count = p_image->get_mipmap_count(); data.resize(target_size); @@ -65,23 +57,25 @@ void image_decompress_squish(Image *p_image) { } else if (p_image->get_format() == Image::FORMAT_RGTC_RG) { squish_flags = squish::kBc5; } else { - print_line("Can't decompress unknown format: " + itos(p_image->get_format())); + ERR_EXPLAIN("Squish: Can't decompress unknown format: " + itos(p_image->get_format())); ERR_FAIL_COND(true); return; } - int dst_ofs = 0; - for (int i = 0; i <= mm_count; i++) { int src_ofs = 0, mipmap_size = 0, mipmap_w = 0, mipmap_h = 0; p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h); - squish::DecompressImage(&wb[dst_ofs], mipmap_w, mipmap_h, &rb[src_ofs], squish_flags); + int dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i); + squish::DecompressImage(&wb[dst_ofs], w, h, &rb[src_ofs], squish_flags); + w >>= 1; + h >>= 1; } p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } -void image_compress_squish(Image *p_image, Image::CompressSource p_source) { +#ifdef TOOLS_ENABLED +void image_compress_squish(Image *p_image, float p_lossy_quality, Image::CompressSource p_source) { if (p_image->get_format() >= Image::FORMAT_DXT1) return; //do not compress, already compressed @@ -92,10 +86,43 @@ void image_compress_squish(Image *p_image, Image::CompressSource p_source) { if (p_image->get_format() <= Image::FORMAT_RGBA8) { int squish_comp = squish::kColourRangeFit; + + if (p_lossy_quality > 0.85) + squish_comp = squish::kColourIterativeClusterFit; + else if (p_lossy_quality > 0.75) + squish_comp = squish::kColourClusterFit; + Image::Format target_format = Image::FORMAT_RGBA8; Image::DetectChannels dc = p_image->get_detected_channels(); + if (p_source == Image::COMPRESS_SOURCE_LAYERED) { + //keep what comes in + switch (p_image->get_format()) { + case Image::FORMAT_L8: { + dc = Image::DETECTED_L; + } break; + case Image::FORMAT_LA8: { + dc = Image::DETECTED_LA; + } break; + case Image::FORMAT_R8: { + dc = Image::DETECTED_R; + } break; + case Image::FORMAT_RG8: { + dc = Image::DETECTED_RG; + } break; + case Image::FORMAT_RGB8: { + dc = Image::DETECTED_RGB; + } break; + case Image::FORMAT_RGBA8: + case Image::FORMAT_RGBA4444: + case Image::FORMAT_RGBA5551: { + dc = Image::DETECTED_RGBA; + } break; + default: {} + } + } + p_image->convert(Image::FORMAT_RGBA8); //still uses RGBA to convert if (p_source == Image::COMPRESS_SOURCE_SRGB && (dc == Image::DETECTED_R || dc == Image::DETECTED_RG)) { @@ -148,7 +175,7 @@ void image_compress_squish(Image *p_image, Image::CompressSource p_source) { } PoolVector<uint8_t> data; - int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps() ? -1 : 0); + int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); int mm_count = p_image->has_mipmaps() ? Image::get_image_required_mipmaps(w, h, target_format) : 0; data.resize(target_size); int shift = Image::get_format_pixel_rshift(target_format); @@ -166,8 +193,8 @@ void image_compress_squish(Image *p_image, Image::CompressSource p_source) { int src_ofs = p_image->get_mipmap_offset(i); squish::CompressImage(&rb[src_ofs], w, h, &wb[dst_ofs], squish_comp); dst_ofs += (MAX(4, bw) * MAX(4, bh)) >> shift; - w >>= 1; - h >>= 1; + w = MAX(w / 2, 1); + h = MAX(h / 2, 1); } rb = PoolVector<uint8_t>::Read(); @@ -176,3 +203,4 @@ void image_compress_squish(Image *p_image, Image::CompressSource p_source) { p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } } +#endif diff --git a/modules/squish/image_compress_squish.h b/modules/squish/image_compress_squish.h index c022063fe5..dd53f2787a 100644 --- a/modules/squish/image_compress_squish.h +++ b/modules/squish/image_compress_squish.h @@ -31,9 +31,11 @@ #ifndef IMAGE_COMPRESS_SQUISH_H #define IMAGE_COMPRESS_SQUISH_H -#include "image.h" +#include "core/image.h" -void image_compress_squish(Image *p_image, Image::CompressSource p_source); +#ifdef TOOLS_ENABLED +void image_compress_squish(Image *p_image, float p_lossy_quality, Image::CompressSource p_source); +#endif void image_decompress_squish(Image *p_image); #endif // IMAGE_COMPRESS_SQUISH_H diff --git a/modules/squish/register_types.cpp b/modules/squish/register_types.cpp index d4ed676cce..9a5bb47f19 100644 --- a/modules/squish/register_types.cpp +++ b/modules/squish/register_types.cpp @@ -29,17 +29,14 @@ /*************************************************************************/ #include "register_types.h" - -#ifdef TOOLS_ENABLED - #include "image_compress_squish.h" void register_squish_types() { +#ifdef TOOLS_ENABLED Image::set_compress_bc_func(image_compress_squish); +#endif Image::_image_decompress_bc = image_decompress_squish; } void unregister_squish_types() {} - -#endif diff --git a/modules/squish/register_types.h b/modules/squish/register_types.h index 00f5c345c4..9dbd69c46b 100644 --- a/modules/squish/register_types.h +++ b/modules/squish/register_types.h @@ -28,7 +28,5 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef TOOLS_ENABLED void register_squish_types(); void unregister_squish_types(); -#endif diff --git a/modules/stb_vorbis/SCsub b/modules/stb_vorbis/SCsub index 897d05961c..d14939a3b1 100644 --- a/modules/stb_vorbis/SCsub +++ b/modules/stb_vorbis/SCsub @@ -3,8 +3,14 @@ Import('env') Import('env_modules') +env_stb_vorbis = env_modules.Clone() + # Thirdparty source files +thirdparty_sources = ["#thirdparty/misc/stb_vorbis.c"] -env_stb_vorbis = env_modules.Clone() +env_thirdparty = env_stb_vorbis.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +# Godot's own source files env_stb_vorbis.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp index c95a8ac2dd..5dbe0b4b00 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp @@ -30,12 +30,7 @@ #include "audio_stream_ogg_vorbis.h" -#include "os/file_access.h" - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#include "thirdparty/misc/stb_vorbis.c" -#pragma GCC diagnostic pop +#include "core/os/file_access.h" void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { @@ -208,8 +203,6 @@ void AudioStreamOGGVorbis::set_data(const PoolVector<uint8_t> &p_data) { //does this work? (it's less mem..) //decode_mem_size = ogg_alloc.alloc_buffer_length_in_bytes + info.setup_memory_required + info.temp_memory_required + info.max_frame_size; - //print_line("succeeded "+itos(ogg_alloc.alloc_buffer_length_in_bytes)+" setup "+itos(info.setup_memory_required)+" setup temp "+itos(info.setup_temp_memory_required)+" temp "+itos(info.temp_memory_required)+" maxframe"+itos(info.max_frame_size)); - length = stb_vorbis_stream_length_in_seconds(ogg_stream); stb_vorbis_close(ogg_stream); diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.h b/modules/stb_vorbis/audio_stream_ogg_vorbis.h index d7bc7cc0d7..8b42111847 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.h +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.h @@ -31,15 +31,10 @@ #ifndef AUDIO_STREAM_STB_VORBIS_H #define AUDIO_STREAM_STB_VORBIS_H -#include "io/resource_loader.h" +#include "core/io/resource_loader.h" #include "servers/audio/audio_stream.h" -#define STB_VORBIS_HEADER_ONLY -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#include "thirdparty/misc/stb_vorbis.c" -#pragma GCC diagnostic pop -#undef STB_VORBIS_HEADER_ONLY +#include "thirdparty/misc/stb_vorbis.h" class AudioStreamOGGVorbis; diff --git a/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp b/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp index c8acdb689a..74f2e68206 100644 --- a/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp +++ b/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp @@ -30,8 +30,8 @@ #include "resource_importer_ogg_vorbis.h" -#include "io/resource_saver.h" -#include "os/file_access.h" +#include "core/io/resource_saver.h" +#include "core/os/file_access.h" #include "scene/resources/texture.h" String ResourceImporterOGGVorbis::get_importer_name() const { diff --git a/modules/stb_vorbis/resource_importer_ogg_vorbis.h b/modules/stb_vorbis/resource_importer_ogg_vorbis.h index a1847545aa..82a03cd207 100644 --- a/modules/stb_vorbis/resource_importer_ogg_vorbis.h +++ b/modules/stb_vorbis/resource_importer_ogg_vorbis.h @@ -32,7 +32,7 @@ #define RESOURCEIMPORTEROGGVORBIS_H #include "audio_stream_ogg_vorbis.h" -#include "io/resource_import.h" +#include "core/io/resource_import.h" class ResourceImporterOGGVorbis : public ResourceImporter { GDCLASS(ResourceImporterOGGVorbis, ResourceImporter) diff --git a/modules/svg/SCsub b/modules/svg/SCsub index a41e0703bd..22f0b1e3eb 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -1,6 +1,9 @@ #!/usr/bin/env python Import('env') +Import('env_modules') + +env_svg = env_modules.Clone() # Thirdparty source files thirdparty_dir = "#thirdparty/nanosvg/" @@ -9,11 +12,15 @@ thirdparty_sources = [ ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env.add_source_files(env.modules_sources, thirdparty_sources) +env_svg.Append(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.Append(CPPPATH=[thirdparty_dir]) env.Append(CCFLAGS=["-DSVG_ENABLED"]) -# Godot's own source files -env.add_source_files(env.modules_sources, "*.cpp") +env_thirdparty = env_svg.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) -Export('env') +# Godot's own source files +env_svg.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp index 8ccd229f3d..ccb7d93885 100644 --- a/modules/svg/image_loader_svg.cpp +++ b/modules/svg/image_loader_svg.cpp @@ -30,10 +30,9 @@ #include "image_loader_svg.h" -#include "os/os.h" -#include "print_string.h" - -#include <ustring.h> +#include "core/os/os.h" +#include "core/print_string.h" +#include "core/ustring.h" void SVGRasterizer::rasterize(NSVGimage *p_image, float p_tx, float p_ty, float p_scale, unsigned char *p_dst, int p_w, int p_h, int p_stride) { nsvgRasterize(rasterizer, p_image, p_tx, p_ty, p_scale, p_dst, p_w, p_h, p_stride); diff --git a/modules/svg/image_loader_svg.h b/modules/svg/image_loader_svg.h index 63854da2f6..ff361ad800 100644 --- a/modules/svg/image_loader_svg.h +++ b/modules/svg/image_loader_svg.h @@ -31,8 +31,8 @@ #ifndef IMAGE_LOADER_SVG_H #define IMAGE_LOADER_SVG_H -#include "io/image_loader.h" -#include "ustring.h" +#include "core/io/image_loader.h" +#include "core/ustring.h" #include <nanosvg.h> #include <nanosvgrast.h> diff --git a/modules/tga/image_loader_tga.cpp b/modules/tga/image_loader_tga.cpp index d4fa88afa7..9bc24017fc 100644 --- a/modules/tga/image_loader_tga.cpp +++ b/modules/tga/image_loader_tga.cpp @@ -30,8 +30,8 @@ #include "image_loader_tga.h" -#include "os/os.h" -#include "print_string.h" +#include "core/os/os.h" +#include "core/print_string.h" Error ImageLoaderTGA::decode_tga_rle(const uint8_t *p_compressed_buffer, size_t p_pixel_size, uint8_t *p_uncompressed_buffer, size_t p_output_size) { Error error; diff --git a/modules/tga/image_loader_tga.h b/modules/tga/image_loader_tga.h index c4b10b7f49..0fe83a54a1 100644 --- a/modules/tga/image_loader_tga.h +++ b/modules/tga/image_loader_tga.h @@ -31,7 +31,7 @@ #ifndef IMAGE_LOADER_TGA_H #define IMAGE_LOADER_TGA_H -#include "io/image_loader.h" +#include "core/io/image_loader.h" /** @author SaracenOne diff --git a/modules/thekla_unwrap/SCsub b/modules/thekla_unwrap/SCsub index d23ba10d4c..c47c760d5f 100644 --- a/modules/thekla_unwrap/SCsub +++ b/modules/thekla_unwrap/SCsub @@ -54,11 +54,10 @@ if env['builtin_thekla_atlas']: ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_thekla_unwrap.add_source_files(env.modules_sources, thirdparty_sources) - env_thekla_unwrap.Append(CPPPATH=[thirdparty_dir, thirdparty_dir + "/poshlib", thirdparty_dir + "/nvcore", thirdparty_dir + "/nvmesh"]) + env_thekla_unwrap.Append(CPPPATH=[thirdparty_dir, thirdparty_dir + "poshlib", thirdparty_dir + "nvcore", thirdparty_dir + "nvmesh"]) # upstream uses c++11 - if (not env_thekla_unwrap.msvc): + if (not env.msvc): env_thekla_unwrap.Append(CXXFLAGS="-std=c++11") if env["platform"] == 'x11': @@ -78,5 +77,9 @@ if env['builtin_thekla_atlas']: env_thekla_unwrap.Append(CCFLAGS=["-DNV_OS_MINGW", "-DNV_CC_GNUC", "-DPOSH_COMPILER_GCC", "-U__STRICT_ANSI__"]) env.Append(LIBS=["dbghelp"]) + env_thirdparty = env_thekla_unwrap.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Godot source files env_thekla_unwrap.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/thekla_unwrap/config.py b/modules/thekla_unwrap/config.py index bd092bdc16..fad6095064 100644 --- a/modules/thekla_unwrap/config.py +++ b/modules/thekla_unwrap/config.py @@ -1,5 +1,6 @@ def can_build(env, platform): - return (env['tools'] and platform not in ["android", "ios"]) + #return (env['tools'] and platform not in ["android", "ios"]) + return False def configure(env): pass diff --git a/modules/thekla_unwrap/register_types.cpp b/modules/thekla_unwrap/register_types.cpp index c74cbd9d18..8e733d1ad2 100644 --- a/modules/thekla_unwrap/register_types.cpp +++ b/modules/thekla_unwrap/register_types.cpp @@ -29,7 +29,7 @@ /*************************************************************************/ #include "register_types.h" -#include "error_macros.h" +#include "core/error_macros.h" #include "thirdparty/thekla_atlas/thekla/thekla_atlas.h" #include <stdio.h> diff --git a/modules/theora/SCsub b/modules/theora/SCsub index 9015c2c354..98c4274a7e 100644 --- a/modules/theora/SCsub +++ b/modules/theora/SCsub @@ -70,7 +70,6 @@ if env['builtin_libtheora']: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_theora.add_source_files(env.modules_sources, thirdparty_sources) env_theora.Append(CPPPATH=[thirdparty_dir]) # also requires libogg and libvorbis @@ -79,5 +78,9 @@ if env['builtin_libtheora']: if env['builtin_libvorbis']: env_theora.Append(CPPPATH=["#thirdparty/libvorbis"]) + env_thirdparty = env_theora.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Godot source files env_theora.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index 881808873b..2a6bb0783b 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -30,8 +30,8 @@ #include "video_stream_theora.h" -#include "os/os.h" -#include "project_settings.h" +#include "core/os/os.h" +#include "core/project_settings.h" #include "thirdparty/misc/yuv2rgb.h" @@ -332,8 +332,8 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) { int w; int h; - w = (ti.pic_x + ti.frame_width + 1 & ~1) - (ti.pic_x & ~1); - h = (ti.pic_y + ti.frame_height + 1 & ~1) - (ti.pic_y & ~1); + w = ((ti.pic_x + ti.frame_width + 1) & ~1) - (ti.pic_x & ~1); + h = ((ti.pic_y + ti.frame_height + 1) & ~1) - (ti.pic_y & ~1); size.x = w; size.y = h; @@ -387,7 +387,6 @@ void VideoStreamPlaybackTheora::update(float p_delta) { thread_sem->post(); #endif - //print_line("play "+rtos(p_delta)); time += p_delta; if (videobuf_time > get_time()) { @@ -440,18 +439,10 @@ void VideoStreamPlaybackTheora::update(float p_delta) { } } - int tr = vorbis_synthesis_read(&vd, ret - to_read); - - if (vd.granulepos >= 0) { - //print_line("wrote: "+itos(audio_frames_wrote)+" gpos: "+itos(vd.granulepos)); - } - - //print_line("mix audio!"); + vorbis_synthesis_read(&vd, ret - to_read); audio_frames_wrote += ret - to_read; - //print_line("AGP: "+itos(vd.granulepos)+" added "+itos(ret-to_read)); - } else { /* no pending audio; is there a pending packet to decode? */ @@ -460,7 +451,6 @@ void VideoStreamPlaybackTheora::update(float p_delta) { vorbis_synthesis_blockin(&vd, &vb); } } else { /* we need more data; break out to suck in another page */ - //printf("need moar data\n"); break; }; } @@ -740,7 +730,6 @@ RES ResourceFormatLoaderTheora::load(const String &p_path, const String &p_origi if (r_error) { *r_error = ERR_CANT_OPEN; } - memdelete(f); return RES(); } diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h index 7cee1b491b..4be723f85b 100644 --- a/modules/theora/video_stream_theora.h +++ b/modules/theora/video_stream_theora.h @@ -31,11 +31,11 @@ #ifndef VIDEO_STREAM_THEORA_H #define VIDEO_STREAM_THEORA_H -#include "io/resource_loader.h" -#include "os/file_access.h" -#include "os/semaphore.h" -#include "os/thread.h" -#include "ring_buffer.h" +#include "core/io/resource_loader.h" +#include "core/os/file_access.h" +#include "core/os/semaphore.h" +#include "core/os/thread.h" +#include "core/ring_buffer.h" #include "scene/resources/video_stream.h" #include "servers/audio_server.h" @@ -163,7 +163,6 @@ public: class VideoStreamTheora : public VideoStream { GDCLASS(VideoStreamTheora, VideoStream); - RES_BASE_EXTENSION("ogv"); String file; int audio_track; diff --git a/modules/tinyexr/SCsub b/modules/tinyexr/SCsub index 38fd00cc65..3e7bda2bca 100644 --- a/modules/tinyexr/SCsub +++ b/modules/tinyexr/SCsub @@ -13,8 +13,11 @@ thirdparty_sources = [ ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env_tinyexr.add_source_files(env.modules_sources, thirdparty_sources) env_tinyexr.Append(CPPPATH=[thirdparty_dir]) +env_thirdparty = env_tinyexr.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Godot's own source files env_tinyexr.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/tinyexr/image_loader_tinyexr.cpp b/modules/tinyexr/image_loader_tinyexr.cpp index 0abefe11ee..63f0781028 100644 --- a/modules/tinyexr/image_loader_tinyexr.cpp +++ b/modules/tinyexr/image_loader_tinyexr.cpp @@ -30,8 +30,8 @@ #include "image_loader_tinyexr.h" -#include "os/os.h" -#include "print_string.h" +#include "core/os/os.h" +#include "core/print_string.h" #include "thirdparty/tinyexr/tinyexr.h" @@ -129,15 +129,45 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f PoolVector<uint8_t> imgdata; Image::Format format; + int output_channels = 0; if (idxA > 0) { imgdata.resize(exr_image.width * exr_image.height * 8); //RGBA16 format = Image::FORMAT_RGBAH; + output_channels = 4; } else { imgdata.resize(exr_image.width * exr_image.height * 6); //RGB16 format = Image::FORMAT_RGBH; + output_channels = 3; + } + + EXRTile single_image_tile; + int num_tiles; + int tile_width = 0; + int tile_height = 0; + + const EXRTile *exr_tiles; + + if (!exr_header.tiled) { + single_image_tile.images = exr_image.images; + single_image_tile.width = exr_image.width; + single_image_tile.height = exr_image.height; + single_image_tile.level_x = exr_image.width; + single_image_tile.level_y = exr_image.height; + single_image_tile.offset_x = 0; + single_image_tile.offset_y = 0; + + exr_tiles = &single_image_tile; + num_tiles = 1; + tile_width = exr_image.width; + tile_height = exr_image.height; + } else { + tile_width = exr_header.tile_size_x; + tile_height = exr_header.tile_size_y; + num_tiles = exr_image.num_tiles; + exr_tiles = exr_image.tiles; } { @@ -145,22 +175,51 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f uint16_t *iw = (uint16_t *)wd.ptr(); // Assume `out_rgba` have enough memory allocated. - for (int i = 0; i < exr_image.width * exr_image.height; i++) { + for (int tile_index = 0; tile_index < num_tiles; tile_index++) { - Color color( - reinterpret_cast<float **>(exr_image.images)[idxR][i], - reinterpret_cast<float **>(exr_image.images)[idxG][i], - reinterpret_cast<float **>(exr_image.images)[idxB][i]); + const EXRTile &tile = exr_tiles[tile_index]; - if (p_force_linear) - color = color.to_linear(); + int tw = tile.width; + int th = tile.height; - *iw++ = Math::make_half_float(color.r); - *iw++ = Math::make_half_float(color.g); - *iw++ = Math::make_half_float(color.b); + const float *r_channel_start = reinterpret_cast<const float *>(tile.images[idxR]); + const float *g_channel_start = reinterpret_cast<const float *>(tile.images[idxG]); + const float *b_channel_start = reinterpret_cast<const float *>(tile.images[idxB]); + const float *a_channel_start = NULL; if (idxA > 0) { - *iw++ = Math::make_half_float(reinterpret_cast<float **>(exr_image.images)[idxA][i]); + a_channel_start = reinterpret_cast<const float *>(tile.images[idxA]); + } + + uint16_t *first_row_w = iw + (tile.offset_y * tile_height * exr_image.width + tile.offset_x * tile_width) * output_channels; + + for (int y = 0; y < th; y++) { + const float *r_channel = r_channel_start + y * tile_width; + const float *g_channel = g_channel_start + y * tile_width; + const float *b_channel = b_channel_start + y * tile_width; + const float *a_channel = NULL; + + if (a_channel_start) { + a_channel = a_channel_start + y * tile_width; + } + + uint16_t *row_w = first_row_w + (y * exr_image.width * output_channels); + + for (int x = 0; x < tw; x++) { + + Color color(*r_channel++, *g_channel++, *b_channel++); + + if (p_force_linear) + color = color.to_linear(); + + *row_w++ = Math::make_half_float(color.r); + *row_w++ = Math::make_half_float(color.g); + *row_w++ = Math::make_half_float(color.b); + + if (idxA > 0) { + *row_w++ = Math::make_half_float(*a_channel++); + } + } } } } diff --git a/modules/tinyexr/image_loader_tinyexr.h b/modules/tinyexr/image_loader_tinyexr.h index 6706e0972a..a6ef9000e5 100644 --- a/modules/tinyexr/image_loader_tinyexr.h +++ b/modules/tinyexr/image_loader_tinyexr.h @@ -31,7 +31,7 @@ #ifndef IMAGE_LOADER_TINYEXR_H #define IMAGE_LOADER_TINYEXR_H -#include "io/image_loader.h" +#include "core/io/image_loader.h" /** @author Juan Linietsky <reduzio@gmail.com> diff --git a/modules/upnp/SCsub b/modules/upnp/SCsub index cde231867f..2b15f7aee2 100644 --- a/modules/upnp/SCsub +++ b/modules/upnp/SCsub @@ -25,8 +25,12 @@ if env['builtin_miniupnpc']: ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_upnp.add_source_files(env.modules_sources, thirdparty_sources) env_upnp.Append(CPPPATH=[thirdparty_dir]) env_upnp.Append(CPPFLAGS=["-DMINIUPNP_STATICLIB"]) + env_thirdparty = env_upnp.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + +# Godot source files env_upnp.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/upnp/doc_classes/UPNP.xml b/modules/upnp/doc_classes/UPNP.xml index 30be9c836b..b98327c60d 100644 --- a/modules/upnp/doc_classes/UPNP.xml +++ b/modules/upnp/doc_classes/UPNP.xml @@ -69,7 +69,7 @@ </argument> <description> Discovers local [UPNPDevice]s. Clears the list of previously discovered devices. - Filters for IGD (InternetGatewayDevice) type devices by default, as those manage port forwarding. [code]timeout[/code] is the time to wait for responses in miliseconds. [code]ttl[/code] is the time-to-live; only touch this if you know what you're doing. + Filters for IGD (InternetGatewayDevice) type devices by default, as those manage port forwarding. [code]timeout[/code] is the time to wait for responses in milliseconds. [code]ttl[/code] is the time-to-live; only touch this if you know what you're doing. See [enum UPNPResult] for possible return values. </description> </method> diff --git a/modules/upnp/register_types.cpp b/modules/upnp/register_types.cpp index c79155c4d2..13088c94dd 100644 --- a/modules/upnp/register_types.cpp +++ b/modules/upnp/register_types.cpp @@ -29,7 +29,9 @@ /*************************************************************************/ #include "register_types.h" -#include "error_macros.h" + +#include "core/error_macros.h" + #include "upnp.h" #include "upnpdevice.h" diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp index 32fdfe22f8..69e054f5c5 100644 --- a/modules/upnp/upnp.cpp +++ b/modules/upnp/upnp.cpp @@ -29,8 +29,10 @@ /*************************************************************************/ #include "upnp.h" -#include "miniupnpc/miniwget.h" -#include "upnpcommands.h" + +#include <miniupnpc/miniwget.h> +#include <miniupnpc/upnpcommands.h> + #include <stdlib.h> bool UPNP::is_common_device(const String &dev) const { diff --git a/modules/upnp/upnp.h b/modules/upnp/upnp.h index fb0c0f30a0..8f0a972c22 100644 --- a/modules/upnp/upnp.h +++ b/modules/upnp/upnp.h @@ -31,9 +31,11 @@ #ifndef GODOT_UPNP_H #define GODOT_UPNP_H -#include "miniupnpc/miniupnpc.h" +#include "core/reference.h" + #include "upnpdevice.h" -#include <reference.h> + +#include <miniupnpc/miniupnpc.h> class UPNP : public Reference { diff --git a/modules/upnp/upnpdevice.cpp b/modules/upnp/upnpdevice.cpp index a5959cf649..8f935fbd82 100644 --- a/modules/upnp/upnpdevice.cpp +++ b/modules/upnp/upnpdevice.cpp @@ -29,8 +29,10 @@ /*************************************************************************/ #include "upnpdevice.h" + #include "upnp.h" -#include "upnpcommands.h" + +#include <miniupnpc/upnpcommands.h> String UPNPDevice::query_external_address() const { ERR_FAIL_COND_V(!is_valid_gateway(), ""); diff --git a/modules/upnp/upnpdevice.h b/modules/upnp/upnpdevice.h index 25c9fe1ba3..e4c5eb691d 100644 --- a/modules/upnp/upnpdevice.h +++ b/modules/upnp/upnpdevice.h @@ -31,7 +31,7 @@ #ifndef GODOT_UPNPDEVICE_H #define GODOT_UPNPDEVICE_H -#include <reference.h> +#include "core/reference.h" class UPNPDevice : public Reference { diff --git a/modules/visual_script/SCsub b/modules/visual_script/SCsub index 96ee911ba0..3c3d2caa57 100644 --- a/modules/visual_script/SCsub +++ b/modules/visual_script/SCsub @@ -6,5 +6,3 @@ Import('env_modules') env_vs = env_modules.Clone() env_vs.add_source_files(env.modules_sources, "*.cpp") - -Export('env') diff --git a/modules/visual_script/doc_classes/VisualScript.xml b/modules/visual_script/doc_classes/VisualScript.xml index 28764aca40..70849c5a80 100644 --- a/modules/visual_script/doc_classes/VisualScript.xml +++ b/modules/visual_script/doc_classes/VisualScript.xml @@ -9,7 +9,7 @@ You are most likely to use this class via the Visual Script editor or when writing plugins for it. </description> <tutorials> - <link>http://docs.godotengine.org/en/3.0/getting_started/scripting/visual_script/index.html</link> + <link>https://docs.godotengine.org/en/latest/getting_started/scripting/visual_script/index.html</link> </tutorials> <demos> </demos> diff --git a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml index b8e77a1b0f..ff3ed66e81 100644 --- a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml +++ b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml @@ -125,12 +125,10 @@ </argument> <description> Execute the custom node's logic, returning the index of the output sequence port to use or a [String] when there is an error. - The [code]inputs[/code] array contains the values of the input ports. [code]outputs[/code] is an array whose indices should be set to the respective outputs. The [code]start_mode[/code] is usually [code]START_MODE_BEGIN_SEQUENCE[/code], unless you have used the STEP_* constants. [code]working_mem[/code] is an array which can be used to persist information between runs of the custom node. - When returning, you can mask the returned value with one of the STEP_* constants. </description> </method> diff --git a/modules/visual_script/register_types.cpp b/modules/visual_script/register_types.cpp index 11401c0460..6e081817f1 100644 --- a/modules/visual_script/register_types.cpp +++ b/modules/visual_script/register_types.cpp @@ -31,7 +31,7 @@ #include "register_types.h" #include "core/engine.h" -#include "io/resource_loader.h" +#include "core/io/resource_loader.h" #include "visual_script.h" #include "visual_script_builtin_funcs.h" #include "visual_script_editor.h" diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 9331092171..5b3b3a6769 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -30,8 +30,8 @@ #include "visual_script.h" -#include "os/os.h" -#include "project_settings.h" +#include "core/os/os.h" +#include "core/project_settings.h" #include "scene/main/node.h" #include "visual_script_nodes.h" @@ -48,20 +48,22 @@ bool VisualScriptNode::is_breakpoint() const { void VisualScriptNode::_notification(int p_what) { if (p_what == NOTIFICATION_POSTINITIALIZE) { - - int dvc = get_input_value_port_count(); - for (int i = 0; i < dvc; i++) { - Variant::Type expected = get_input_value_port_info(i).type; - Variant::CallError ce; - default_input_values.push_back(Variant::construct(expected, NULL, 0, ce, false)); - } + _update_input_ports(); } } -void VisualScriptNode::ports_changed_notify() { - +void VisualScriptNode::_update_input_ports() { default_input_values.resize(MAX(default_input_values.size(), get_input_value_port_count())); //let it grow as big as possible, we don't want to lose values on resize + int port_count = get_input_value_port_count(); + for (int i = 0; i < port_count; i++) { + Variant::Type expected = get_input_value_port_info(i).type; + Variant::CallError ce; + set_default_input_value(i, Variant::construct(expected, NULL, 0, ce, false)); + } +} +void VisualScriptNode::ports_changed_notify() { + _update_input_ports(); emit_signal("ports_changed"); } @@ -771,7 +773,7 @@ void VisualScript::custom_signal_set_argument_type(const StringName &p_func, int ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!custom_signals.has(p_func)); ERR_FAIL_INDEX(p_argidx, custom_signals[p_func].size()); - custom_signals[p_func][p_argidx].type = p_type; + custom_signals[p_func].write[p_argidx].type = p_type; } Variant::Type VisualScript::custom_signal_get_argument_type(const StringName &p_func, int p_argidx) const { @@ -783,7 +785,7 @@ void VisualScript::custom_signal_set_argument_name(const StringName &p_func, int ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!custom_signals.has(p_func)); ERR_FAIL_INDEX(p_argidx, custom_signals[p_func].size()); - custom_signals[p_func][p_argidx].name = p_name; + custom_signals[p_func].write[p_argidx].name = p_name; } String VisualScript::custom_signal_get_argument_name(const StringName &p_func, int p_argidx) const { @@ -811,7 +813,7 @@ void VisualScript::custom_signal_swap_argument(const StringName &p_func, int p_a ERR_FAIL_INDEX(p_argidx, custom_signals[p_func].size()); ERR_FAIL_INDEX(p_with_argidx, custom_signals[p_func].size()); - SWAP(custom_signals[p_func][p_argidx], custom_signals[p_func][p_with_argidx]); + SWAP(custom_signals[p_func].write[p_argidx], custom_signals[p_func].write[p_with_argidx]); } void VisualScript::remove_custom_signal(const StringName &p_name) { @@ -979,6 +981,10 @@ bool VisualScript::is_tool() const { return false; } +bool VisualScript::is_valid() const { + return true; //always valid +} + ScriptLanguage *VisualScript::get_language() const { return VisualScriptLanguage::singleton; @@ -1333,6 +1339,19 @@ VisualScript::VisualScript() { base_type = "Object"; } +Set<int> VisualScript::get_output_sequence_ports_connected(const String &edited_func, int from_node) { + List<VisualScript::SequenceConnection> *sc = memnew(List<VisualScript::SequenceConnection>); + get_sequence_connection_list(edited_func, sc); + Set<int> connected; + for (List<VisualScript::SequenceConnection>::Element *E = sc->front(); E; E = E->next()) { + if (E->get().from_node == from_node) { + connected.insert(E->get().from_output); + } + } + memdelete(sc); + return connected; +} + VisualScript::~VisualScript() { while (!functions.empty()) { @@ -2402,7 +2421,7 @@ void VisualScriptLanguage::make_template(const String &p_class_name, const Strin script->set_instance_base_type(p_base_class_name); } -bool VisualScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { +bool VisualScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { return false; } @@ -2684,11 +2703,11 @@ VisualScriptLanguage::VisualScriptLanguage() { _debug_parse_err_file = ""; _debug_call_stack_pos = 0; int dmcs = GLOBAL_DEF("debug/settings/visual_script/max_call_stack", 1024); + ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/visual_script/max_call_stack", PropertyInfo(Variant::INT, "debug/settings/visual_script/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater")); //minimum is 1024 + if (ScriptDebugger::get_singleton()) { //debugging enabled! _debug_max_call_stack = dmcs; - if (_debug_max_call_stack < 1024) - _debug_max_call_stack = 1024; _call_stack = memnew_arr(CallLevel, _debug_max_call_stack + 1); } else { diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index aaa6dfea11..cdc9159a73 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -31,8 +31,8 @@ #ifndef VISUAL_SCRIPT_H #define VISUAL_SCRIPT_H -#include "os/thread.h" -#include "script_language.h" +#include "core/os/thread.h" +#include "core/script_language.h" class VisualScriptInstance; class VisualScriptNodeInstance; @@ -52,6 +52,7 @@ class VisualScriptNode : public Resource { Array _get_default_input_values() const; void validate_input_default_values(); + void _update_input_ports(); protected: void _notification(int p_what); @@ -319,6 +320,7 @@ public: void custom_signal_swap_argument(const StringName &p_func, int p_argidx, int p_with_argidx); void remove_custom_signal(const StringName &p_name); void rename_custom_signal(const StringName &p_name, const StringName &p_new_name); + Set<int> get_output_sequence_ports_connected(const String &edited_func, int from_node); void get_custom_signal_list(List<StringName> *r_custom_signals) const; @@ -339,6 +341,7 @@ public: virtual Error reload(bool p_keep_state = false); virtual bool is_tool() const; + virtual bool is_valid() const; virtual ScriptLanguage *get_language() const; @@ -563,7 +566,7 @@ public: virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; virtual bool is_using_templates(); virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script); - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const; + virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, List<ScriptLanguage::Warning> *r_warnings = NULL, Set<int> *r_safe_lines = NULL) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp index 73b6d702c1..60bc54afe4 100644 --- a/modules/visual_script/visual_script_builtin_funcs.cpp +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -30,13 +30,13 @@ #include "visual_script_builtin_funcs.h" -#include "class_db.h" -#include "func_ref.h" -#include "io/marshalls.h" -#include "math_funcs.h" -#include "os/os.h" -#include "reference.h" -#include "variant_parser.h" +#include "core/class_db.h" +#include "core/func_ref.h" +#include "core/io/marshalls.h" +#include "core/math/math_funcs.h" +#include "core/os/os.h" +#include "core/reference.h" +#include "core/variant_parser.h" const char *VisualScriptBuiltinFunc::func_name[VisualScriptBuiltinFunc::FUNC_MAX] = { "sin", @@ -260,7 +260,12 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const case MATH_SQRT: { return PropertyInfo(Variant::REAL, "s"); } break; - case MATH_ATAN2: + case MATH_ATAN2: { + if (p_idx == 0) + return PropertyInfo(Variant::REAL, "y"); + else + return PropertyInfo(Variant::REAL, "x"); + } break; case MATH_FMOD: case MATH_FPOSMOD: { if (p_idx == 0) @@ -1144,15 +1149,12 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in case VisualScriptBuiltinFunc::TEXT_PRINTERR: { String str = *p_inputs[0]; - - //str+="\n"; print_error(str); } break; case VisualScriptBuiltinFunc::TEXT_PRINTRAW: { - String str = *p_inputs[0]; - //str+="\n"; + String str = *p_inputs[0]; OS::get_singleton()->print("%s", str.utf8().get_data()); } break; diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 873cc293c9..080d0643a2 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -30,11 +30,13 @@ #include "visual_script_editor.h" +#include "core/object.h" +#include "core/os/input.h" +#include "core/os/keyboard.h" #include "core/script_language.h" +#include "core/variant.h" #include "editor/editor_node.h" #include "editor/editor_resource_preview.h" -#include "os/input.h" -#include "os/keyboard.h" #include "visual_script_expression.h" #include "visual_script_flow_control.h" #include "visual_script_func_nodes.h" @@ -319,7 +321,7 @@ protected: p_list->push_back(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, argt)); p_list->push_back(PropertyInfo(script->get_variable_info(var).type, "value", script->get_variable_info(var).hint, script->get_variable_info(var).hint_string, PROPERTY_USAGE_DEFAULT)); // Update this when PropertyHint changes - p_list->push_back(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,ExpRange,Enum,ExpEasing,Length,SpriteFrame,KeyAccel,Flags,Layers2dRender,Layers2dPhysics,Layer3dRender,Layer3dPhysics,File,Dir,GlobalFile,GlobalDir,ResourceType,MultilineText,ColorNoAlpha,ImageCompressLossy,ImageCompressLossLess,ObjectId,String,NodePathToEditedNode,MethodOfVariantType,MethodOfBaseType,MethodOfInstance,MethodOfScript,PropertyOfVariantType,PropertyOfBaseType,PropertyOfInstance,PropertyOfScript,ObjectTooBig")); + p_list->push_back(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,ExpRange,Enum,ExpEasing,Length,SpriteFrame,KeyAccel,Flags,Layers2dRender,Layers2dPhysics,Layer3dRender,Layer3dPhysics,File,Dir,GlobalFile,GlobalDir,ResourceType,MultilineText,PlaceholderText,ColorNoAlpha,ImageCompressLossy,ImageCompressLossLess,ObjectId,String,NodePathToEditedNode,MethodOfVariantType,MethodOfBaseType,MethodOfInstance,MethodOfScript,PropertyOfVariantType,PropertyOfBaseType,PropertyOfInstance,PropertyOfScript,ObjectTooBig,NodePathValidTypes")); p_list->push_back(PropertyInfo(Variant::STRING, "hint_string")); p_list->push_back(PropertyInfo(Variant::BOOL, "export")); } @@ -763,6 +765,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { } void VisualScriptEditor::_update_members() { + ERR_FAIL_COND(!script.is_valid()); updating_members = true; @@ -880,7 +883,6 @@ void VisualScriptEditor::_member_selected() { ERR_FAIL_COND(!ti); selected = ti->get_metadata(0); - //print_line("selected: "+String(selected)); if (ti->get_parent() == members->get_root()->get_children()) { @@ -1293,7 +1295,7 @@ void VisualScriptEditor::_on_nodes_duplicate() { Ref<VisualScriptNode> node = script->get_node(edited_func, F->get()); - Ref<VisualScriptNode> dupe = node->duplicate(); + Ref<VisualScriptNode> dupe = node->duplicate(true); int new_id = idc++; to_select.insert(new_id); @@ -1327,6 +1329,10 @@ void VisualScriptEditor::_input(const Ref<InputEvent> &p_event) { } } +void VisualScriptEditor::_generic_search() { + new_connect_node_select->select_from_visual_script(String(""), false); +} + void VisualScriptEditor::_members_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> key = p_event; @@ -1780,7 +1786,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da call->set_base_type(node->get_class()); n = call; - method_select->select_method_from_instance(node); + method_select->select_from_instance(node); selecting_method_id = base_id; } @@ -1917,7 +1923,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } } -void VisualScriptEditor::_selected_method(const String &p_method) { +void VisualScriptEditor::_selected_method(const String &p_method, const String &p_type, const bool p_connecting) { Ref<VisualScriptFunctionCall> vsfc = script->get_node(edited_func, selecting_method_id); if (!vsfc.is_valid()) @@ -1935,7 +1941,7 @@ void VisualScriptEditor::_draw_color_over_button(Object *obj, Color p_color) { button->draw_rect(Rect2(normal->get_offset(), button->get_size() - normal->get_minimum_size()), p_color); } -void VisualScriptEditor::_button_resource_previewed(const String &p_path, const Ref<Texture> &p_preview, Variant p_ud) { +void VisualScriptEditor::_button_resource_previewed(const String &p_path, const Ref<Texture> &p_preview, const Ref<Texture> &p_small_preview, Variant p_ud) { Array ud = p_ud; ERR_FAIL_COND(ud.size() != 2); @@ -1962,22 +1968,16 @@ void VisualScriptEditor::_button_resource_previewed(const String &p_path, const void VisualScriptEditor::apply_code() { } -Ref<Script> VisualScriptEditor::get_edited_script() const { - +RES VisualScriptEditor::get_edited_resource() const { return script; } -Vector<String> VisualScriptEditor::get_functions() { - - return Vector<String>(); -} +void VisualScriptEditor::set_edited_resource(const RES &p_res) { -void VisualScriptEditor::set_edited_script(const Ref<Script> &p_script) { - - script = p_script; - signal_editor->script = p_script; + script = p_res; + signal_editor->script = script; signal_editor->undo_redo = undo_redo; - variable_editor->script = p_script; + variable_editor->script = script; variable_editor->undo_redo = undo_redo; script->connect("node_ports_changed", this, "_node_ports_changed"); @@ -1986,6 +1986,11 @@ void VisualScriptEditor::set_edited_script(const Ref<Script> &p_script) { _update_available_nodes(); } +Vector<String> VisualScriptEditor::get_functions() { + + return Vector<String>(); +} + void VisualScriptEditor::reload_text() { } @@ -2339,7 +2344,7 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, int from_port; if (!_get_out_slot(from_node, p_from_slot, from_port, from_seq)) - return; //can't connect this, it' s invalid + return; //can't connect this, it's invalid Ref<VisualScriptNode> to_node = script->get_node(edited_func, p_to.to_int()); ERR_FAIL_COND(!to_node.is_valid()); @@ -2348,7 +2353,7 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, int to_port; if (!_get_in_slot(to_node, p_to_slot, to_port, to_seq)) - return; //can't connect this, it' s invalid + return; //can't connect this, it's invalid ERR_FAIL_COND(from_seq != to_seq); @@ -2359,7 +2364,7 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int()); } else { - // disconect current, and connect the new one + // disconnect current, and connect the new one if (script->is_input_value_port_connected(edited_func, p_to.to_int(), to_port)) { int conn_from; int conn_port; @@ -2392,7 +2397,7 @@ void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_sl int from_port; if (!_get_out_slot(from_node, p_from_slot, from_port, from_seq)) - return; //can't connect this, it' s invalid + return; //can't connect this, it's invalid Ref<VisualScriptNode> to_node = script->get_node(edited_func, p_to.to_int()); ERR_FAIL_COND(!to_node.is_valid()); @@ -2401,7 +2406,7 @@ void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_sl int to_port; if (!_get_in_slot(to_node, p_to_slot, to_port, to_seq)) - return; //can't connect this, it' s invalid + return; //can't connect this, it's invalid ERR_FAIL_COND(from_seq != to_seq); @@ -2436,33 +2441,19 @@ void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_fro if (!vsn.is_valid()) return; - if (p_from_slot < vsn->get_output_sequence_port_count()) { + port_action_pos = p_release_pos; - port_action_popup->clear(); - port_action_popup->add_item(TTR("Condition"), CREATE_COND); - port_action_popup->add_item(TTR("Sequence"), CREATE_SEQUENCE); - port_action_popup->add_item(TTR("Switch"), CREATE_SWITCH); - port_action_popup->add_item(TTR("Iterator"), CREATE_ITERATOR); - port_action_popup->add_item(TTR("While"), CREATE_WHILE); - port_action_popup->add_item(TTR("Return"), CREATE_RETURN); + if (p_from_slot < vsn->get_output_sequence_port_count()) { port_action_node = p_from.to_int(); port_action_output = p_from_slot; - + _port_action_menu(CREATE_ACTION); } else { - port_action_popup->clear(); - port_action_popup->add_item(TTR("Call"), CREATE_CALL); - port_action_popup->add_item(TTR("Get"), CREATE_GET); - port_action_popup->add_item(TTR("Set"), CREATE_SET); port_action_output = p_from_slot - vsn->get_output_sequence_port_count(); port_action_node = p_from.to_int(); + _port_action_menu(CREATE_CALL_SET_GET); } - - port_action_pos = p_release_pos; - port_action_popup->set_size(Size2(1, 1)); - port_action_popup->set_position(graph->get_global_position() + p_release_pos); - port_action_popup->popup(); } VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_action_node, int p_port_action_output, Set<int> &visited_nodes) { @@ -2528,170 +2519,196 @@ void VisualScriptEditor::_port_action_menu(int p_option) { } ofs /= EDSCALE; - bool seq_connect = false; - - Ref<VisualScriptNode> vnode; Set<int> vn; switch (p_option) { - case CREATE_CALL: { - + case CREATE_CALL_SET_GET: { Ref<VisualScriptFunctionCall> n; n.instance(); - vnode = n; VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); - if (tg.type == Variant::OBJECT) { - n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE); - - if (tg.gdclass != StringName()) { - n->set_base_type(tg.gdclass); - } else { - n->set_base_type("Object"); - } + if (tg.gdclass != StringName()) { + n->set_base_type(tg.gdclass); + } else { + n->set_base_type("Object"); + } + String type_string = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + if (tg.type == Variant::OBJECT) { if (tg.script.is_valid()) { - n->set_base_script(tg.script->get_path()); - new_connect_node_select->select_method_from_script(tg.script); + new_connect_node_select->select_from_script(tg.script, ""); + } else if (type_string != String()) { + new_connect_node_select->select_from_base_type(type_string); } else { - new_connect_node_select->select_method_from_base_type(n->get_base_type()); + new_connect_node_select->select_from_base_type(n->get_base_type()); } - + } else if (tg.type == Variant::NIL) { + new_connect_node_select->select_from_base_type(""); } else { - n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE); - n->set_basic_type(tg.type); - new_connect_node_select->select_method_from_basic_type(tg.type); + new_connect_node_select->select_from_basic_type(tg.type); } - } break; - case CREATE_SET: { - - Ref<VisualScriptPropertySet> n; - n.instance(); - vnode = n; - + case CREATE_ACTION: { VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); - + PropertyInfo property_info = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output); if (tg.type == Variant::OBJECT) { - n->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); - - if (tg.gdclass != StringName()) { - n->set_base_type(tg.gdclass); - } else { - n->set_base_type("Object"); - } - - if (tg.script.is_valid()) { - n->set_base_script(tg.script->get_path()); - new_connect_node_select->select_property_from_script(tg.script); + if (property_info.type == Variant::OBJECT && property_info.hint_string != String()) { + new_connect_node_select->select_from_action(property_info.hint_string); } else { - new_connect_node_select->select_property_from_base_type(n->get_base_type()); + new_connect_node_select->select_from_action(""); } - + } else if (tg.type == Variant::NIL) { + new_connect_node_select->select_from_action(""); } else { - n->set_call_mode(VisualScriptPropertySet::CALL_MODE_BASIC_TYPE); - n->set_basic_type(tg.type); - new_connect_node_select->select_property_from_basic_type(tg.type); + new_connect_node_select->select_from_action(Variant::get_type_name(tg.type)); } } break; - case CREATE_GET: { + } +} - Ref<VisualScriptPropertyGet> n; - n.instance(); - vnode = n; +void VisualScriptEditor::new_node(Ref<VisualScriptNode> vnode, Vector2 ofs) { + Set<int> vn; + Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node); + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(this, "_update_graph", new_id); + undo_redo->add_undo_method(this, "_update_graph", new_id); + undo_redo->commit_action(); - VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + port_action_new_node = new_id; +} - if (tg.type == Variant::OBJECT) { - n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); +void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id) { + undo_redo->create_action(TTR("Connect Node Data")); + VisualScriptReturn *vnode_return = Object::cast_to<VisualScriptReturn>(vnode.ptr()); + if (vnode_return != NULL && vnode_old->get_output_value_port_count() > 0) { + vnode_return->set_enable_return_value(true); + } + if (vnode_old->get_output_value_port_count() <= 0) { + undo_redo->commit_action(); + return; + } + if (vnode->get_input_value_port_count() <= 0) { + undo_redo->commit_action(); + return; + } + int port = port_action_output; + int value_count = vnode_old->get_output_value_port_count(); + if (port >= value_count) { + port = 0; + } + undo_redo->add_do_method(script.ptr(), "data_connect", edited_func, port_action_node, port, new_id, 0); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", edited_func, port_action_node, port, new_id, 0); + undo_redo->commit_action(); +} - if (tg.gdclass != StringName()) { - n->set_base_type(tg.gdclass); - } else { - n->set_base_type("Object"); - } +void VisualScriptEditor::_selected_connect_node(const String &p_text, const String &p_category, const bool p_connecting) { + Vector2 ofs = graph->get_scroll_ofs() + port_action_pos; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap, snap)); + } + ofs /= EDSCALE; - if (tg.script.is_valid()) { - n->set_base_script(tg.script->get_path()); - new_connect_node_select->select_property_from_script(tg.script); - } else { - new_connect_node_select->select_property_from_base_type(n->get_base_type()); - } + Set<int> vn; + + if (p_category == "visualscript") { + Ref<VisualScriptNode> vnode_new = VisualScriptLanguage::singleton->create_node_from_name(p_text); + Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node); + int new_id = script->get_available_id(); + + if (Object::cast_to<VisualScriptOperator>(vnode_new.ptr()) && script->get_node(edited_func, port_action_node).is_valid()) { + Variant::Type type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).type; + Object::cast_to<VisualScriptOperator>(vnode_new.ptr())->set_typed(type); + } + if (Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr()) && script->get_node(edited_func, port_action_node).is_valid()) { + Variant::Type type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).type; + String hint_name = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + + if (type == Variant::OBJECT) { + Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(hint_name); + } else if (type == Variant::NIL) { + Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(""); } else { - n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_BASIC_TYPE); - n->set_basic_type(tg.type); - new_connect_node_select->select_property_from_basic_type(tg.type); + Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(Variant::get_type_name(type)); } + } + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode_new, ofs); + if (vnode_old.is_valid() && p_connecting) { + connect_seq(vnode_old, vnode_new, new_id); + connect_data(vnode_old, vnode_new, new_id); + } - } break; - case CREATE_COND: { + undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + return; + } - Ref<VisualScriptCondition> n; - n.instance(); - vnode = n; - seq_connect = true; + Ref<VisualScriptNode> vnode; - } break; - case CREATE_SEQUENCE: { + if (p_category == String("method")) { - Ref<VisualScriptSequence> n; + Ref<VisualScriptFunctionCall> n; + n.instance(); + vnode = n; + } else if (p_category == String("set")) { + + Ref<VisualScriptPropertySet> n; + n.instance(); + n->set_property(p_text); + vnode = n; + } else if (p_category == String("get")) { + + Ref<VisualScriptPropertyGet> n; + n.instance(); + n->set_property(p_text); + vnode = n; + } + + if (p_category == String("action")) { + if (p_text == "VisualScriptCondition") { + + Ref<VisualScriptCondition> n; n.instance(); vnode = n; - seq_connect = true; - - } break; - case CREATE_SWITCH: { + } + if (p_text == "VisualScriptSwitch") { Ref<VisualScriptSwitch> n; n.instance(); vnode = n; - seq_connect = true; + } else if (p_text == "VisualScriptSequence") { - } break; - case CREATE_ITERATOR: { + Ref<VisualScriptSequence> n; + n.instance(); + vnode = n; + } else if (p_text == "VisualScriptIterator") { Ref<VisualScriptIterator> n; n.instance(); vnode = n; - seq_connect = true; - - } break; - case CREATE_WHILE: { + } else if (p_text == "VisualScriptWhile") { Ref<VisualScriptWhile> n; n.instance(); vnode = n; - seq_connect = true; - - } break; - case CREATE_RETURN: { + } else if (p_text == "VisualScriptReturn") { Ref<VisualScriptReturn> n; n.instance(); vnode = n; - seq_connect = true; - - } break; + } } - int new_id = script->get_available_id(); - undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); - if (seq_connect) { - undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port_action_output, new_id); - } - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); - undo_redo->add_do_method(this, "_update_graph", new_id); - undo_redo->add_undo_method(this, "_update_graph", new_id); - undo_redo->commit_action(); - - port_action_new_node = new_id; -} - -void VisualScriptEditor::_selected_connect_node_method_or_setget(const String &p_text) { + new_node(vnode, ofs); Ref<VisualScriptNode> vsn = script->get_node(edited_func, port_action_new_node); @@ -2699,28 +2716,151 @@ void VisualScriptEditor::_selected_connect_node_method_or_setget(const String &p Ref<VisualScriptFunctionCall> vsfc = vsn; vsfc->set_function(p_text); - script->data_connect(edited_func, port_action_node, port_action_output, port_action_new_node, 0); + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + if (tg.type == Variant::OBJECT) { + vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE); + vsfc->set_base_type(String("")); + if (tg.gdclass != StringName()) { + vsfc->set_base_type(tg.gdclass); + + } else if (script->get_node(edited_func, port_action_node).is_valid()) { + PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + + if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) { + vsfc->set_base_type(base_type); + } + if (p_text == "call" || p_text == "call_deferred") { + vsfc->set_function(String("")); + } + } + if (tg.script.is_valid()) { + vsfc->set_base_script(tg.script->get_path()); + } + } else if (tg.type == Variant::NIL) { + vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE); + vsfc->set_base_type(String("")); + } else { + vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE); + vsfc->set_basic_type(tg.type); + } } if (Object::cast_to<VisualScriptPropertySet>(vsn.ptr())) { Ref<VisualScriptPropertySet> vsp = vsn; - vsp->set_property(p_text); - script->data_connect(edited_func, port_action_node, port_action_output, port_action_new_node, 0); + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + if (tg.type == Variant::OBJECT) { + vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + vsp->set_base_type(String("")); + if (tg.gdclass != StringName()) { + vsp->set_base_type(tg.gdclass); + + } else if (script->get_node(edited_func, port_action_node).is_valid()) { + PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + + if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) { + vsp->set_base_type(base_type); + } + } + if (tg.script.is_valid()) { + vsp->set_base_script(tg.script->get_path()); + } + } else if (tg.type == Variant::NIL) { + vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + vsp->set_base_type(String("")); + } else { + vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_BASIC_TYPE); + vsp->set_basic_type(tg.type); + } } if (Object::cast_to<VisualScriptPropertyGet>(vsn.ptr())) { - Ref<VisualScriptPropertyGet> vsp = vsn; - vsp->set_property(p_text); - script->data_connect(edited_func, port_action_node, port_action_output, port_action_new_node, 0); - } + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + if (tg.type == Variant::OBJECT) { + vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + vsp->set_base_type(String("")); + if (tg.gdclass != StringName()) { + vsp->set_base_type(tg.gdclass); + + } else if (script->get_node(edited_func, port_action_node).is_valid()) { + PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) { + vsp->set_base_type(base_type); + } + } + if (tg.script.is_valid()) { + vsp->set_base_script(tg.script->get_path()); + } + } else if (tg.type == Variant::NIL) { + vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + vsp->set_base_type(String("")); + } else { + vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_BASIC_TYPE); + vsp->set_basic_type(tg.type); + } + } + Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node); + if (vnode_old.is_valid() && p_connecting) { + connect_seq(vnode_old, vnode, port_action_new_node); + connect_data(vnode_old, vnode, port_action_new_node); + } _update_graph(port_action_new_node); _update_graph_connections(); } -void VisualScriptEditor::_selected_new_virtual_method(const String &p_text) { +void VisualScriptEditor::connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id) { + VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(vnode_new.ptr()); + if (vnode_operator != NULL && !vnode_operator->has_input_sequence_port()) { + return; + } + VisualScriptConstructor *vnode_constructor = Object::cast_to<VisualScriptConstructor>(vnode_new.ptr()); + if (vnode_constructor != NULL) { + return; + } + if (vnode_old->get_output_sequence_port_count() <= 0) { + return; + } + if (!vnode_new->has_input_sequence_port()) { + return; + } + + undo_redo->create_action(TTR("Connect Node Sequence")); + int pass_port = -vnode_old->get_output_sequence_port_count() + 1; + int return_port = port_action_output - 1; + if (vnode_old->get_output_value_port_info(port_action_output).name == String("pass") && + !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(pass_port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, pass_port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, pass_port, new_id); + } else if (vnode_old->get_output_value_port_info(port_action_output).name == String("return") && + !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(return_port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, return_port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, return_port, new_id); + } else { + for (int port = 0; port < vnode_old->get_output_sequence_port_count(); port++) { + int count = vnode_old->get_output_sequence_port_count(); + if (port_action_output < count && !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(port_action_output)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port_action_output, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, port_action_output, new_id); + break; + } else if (!script->get_output_sequence_ports_connected(edited_func, port_action_node).has(port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, port, new_id); + break; + } + } + } + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting) { String name = p_text; if (script->has_function(name)) { @@ -2753,7 +2893,7 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text) { undo_redo->add_do_method(script.ptr(), "add_function", name); for (int i = 0; i < minfo.arguments.size(); i++) { - func_node->add_argument(minfo.arguments[i].type, minfo.arguments[i].name); + func_node->add_argument(minfo.arguments[i].type, minfo.arguments[i].name, -1, minfo.arguments[i].hint, minfo.arguments[i].hint_string); } undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node); @@ -2777,12 +2917,29 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text) { _update_graph(); } -void VisualScriptEditor::_cancel_connect_node_method_or_setget() { - - script->remove_node(edited_func, port_action_new_node); +void VisualScriptEditor::_cancel_connect_node() { + // Causes crashes + //script->remove_node(edited_func, port_action_new_node); _update_graph(); } +void VisualScriptEditor::_create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point) { + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap, snap)); + } + ofs /= EDSCALE; + Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(p_text); + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + void VisualScriptEditor::_default_value_changed() { Ref<VisualScriptNode> vsn = script->get_node(edited_func, editing_id); @@ -2862,10 +3019,15 @@ void VisualScriptEditor::_node_filter_changed(const String &p_text) { void VisualScriptEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - node_filter->add_icon_override("right_icon", Control::get_icon("Search", "EditorIcons")); - variable_editor->connect("changed", this, "_update_members"); - signal_editor->connect("changed", this, "_update_members"); + if (p_what == NOTIFICATION_READY || (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree())) { + + node_filter->set_right_icon(Control::get_icon("Search", "EditorIcons")); + node_filter->set_clear_button_enabled(true); + + if (p_what == NOTIFICATION_READY) { + variable_editor->connect("changed", this, "_update_members"); + signal_editor->connect("changed", this, "_update_members"); + } Ref<Theme> tm = EditorNode::get_singleton()->get_theme_base()->get_theme(); @@ -2899,8 +3061,12 @@ void VisualScriptEditor::_notification(int p_what) { node_styles[E->get().first] = frame_style; } } - } - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + + if (is_visible_in_tree() && script.is_valid()) { + _update_members(); + _update_graph(); + } + } else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { left_vsplit->set_visible(is_visible_in_tree()); } } @@ -2980,9 +3146,7 @@ void VisualScriptEditor::_menu_option(int p_what) { } break; case EDIT_FIND_NODE_TYPE: { - //popup disappearing grabs focus to owner, so use call deferred - node_filter->call_deferred("grab_focus"); - node_filter->call_deferred("select_all"); + _generic_search(); } break; case EDIT_COPY_NODES: case EDIT_CUT_NODES: { @@ -3006,7 +3170,7 @@ void VisualScriptEditor::_menu_option(int p_what) { return; } if (node.is_valid()) { - clipboard->nodes[id] = node->duplicate(); + clipboard->nodes[id] = node->duplicate(true); clipboard->nodes_positions[id] = script->get_node_position(edited_func, id); } } @@ -3200,11 +3364,6 @@ void VisualScriptEditor::_member_option(int p_option) { undo_redo->add_undo_method(script.ptr(), "data_connect", name, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); } - /* - for(int i=0;i<script->function_get_argument_count(name);i++) { - undo_redo->add_undo_method(script.ptr(),"function_add_argument",name,script->function_get_argument_name(name,i),script->function_get_argument_type(name,i)); - } - */ undo_redo->add_do_method(this, "_update_members"); undo_redo->add_undo_method(this, "_update_members"); undo_redo->add_do_method(this, "_update_graph"); @@ -3286,10 +3445,11 @@ void VisualScriptEditor::_bind_methods() { ClassDB::bind_method("_comment_node_resized", &VisualScriptEditor::_comment_node_resized); ClassDB::bind_method("_button_resource_previewed", &VisualScriptEditor::_button_resource_previewed); ClassDB::bind_method("_port_action_menu", &VisualScriptEditor::_port_action_menu); - ClassDB::bind_method("_selected_connect_node_method_or_setget", &VisualScriptEditor::_selected_connect_node_method_or_setget); + ClassDB::bind_method("_selected_connect_node", &VisualScriptEditor::_selected_connect_node); ClassDB::bind_method("_selected_new_virtual_method", &VisualScriptEditor::_selected_new_virtual_method); - ClassDB::bind_method("_cancel_connect_node_method_or_setget", &VisualScriptEditor::_cancel_connect_node_method_or_setget); + ClassDB::bind_method("_cancel_connect_node", &VisualScriptEditor::_cancel_connect_node); + ClassDB::bind_method("_create_new_node", &VisualScriptEditor::_create_new_node); ClassDB::bind_method("_expression_text_changed", &VisualScriptEditor::_expression_text_changed); ClassDB::bind_method("get_drag_data_fw", &VisualScriptEditor::get_drag_data_fw); @@ -3318,6 +3478,8 @@ void VisualScriptEditor::_bind_methods() { ClassDB::bind_method("_member_option", &VisualScriptEditor::_member_option); ClassDB::bind_method("_update_available_nodes", &VisualScriptEditor::_update_available_nodes); + + ClassDB::bind_method("_generic_search", &VisualScriptEditor::_generic_search); } VisualScriptEditor::VisualScriptEditor() { @@ -3329,6 +3491,7 @@ VisualScriptEditor::VisualScriptEditor() { edit_menu = memnew(MenuButton); edit_menu->set_text(TTR("Edit")); + edit_menu->set_switch_on_hover(true); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/delete_selected"), EDIT_DELETE_NODES); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/toggle_breakpoint"), EDIT_TOGGLE_BREAKPOINT); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/find_node_type"), EDIT_FIND_NODE_TYPE); @@ -3445,8 +3608,7 @@ VisualScriptEditor::VisualScriptEditor() { edit_signal_dialog->set_title(TTR("Edit Signal Arguments:")); signal_editor = memnew(VisualScriptEditorSignalEdit); - edit_signal_edit = memnew(PropertyEditor); - edit_signal_edit->hide_top_label(); + edit_signal_edit = memnew(EditorInspector); edit_signal_dialog->add_child(edit_signal_edit); edit_signal_edit->edit(signal_editor); @@ -3457,8 +3619,7 @@ VisualScriptEditor::VisualScriptEditor() { edit_variable_dialog->set_title(TTR("Edit Variable:")); variable_editor = memnew(VisualScriptEditorVariableEdit); - edit_variable_edit = memnew(PropertyEditor); - edit_variable_edit->hide_top_label(); + edit_variable_edit = memnew(EditorInspector); edit_variable_dialog->add_child(edit_variable_edit); edit_variable_edit->edit(variable_editor); @@ -3480,24 +3641,20 @@ VisualScriptEditor::VisualScriptEditor() { add_child(default_value_edit); default_value_edit->connect("variant_changed", this, "_default_value_changed"); - method_select = memnew(PropertySelector); + method_select = memnew(VisualScriptPropertySelector); add_child(method_select); method_select->connect("selected", this, "_selected_method"); error_line = -1; - new_connect_node_select = memnew(PropertySelector); + new_connect_node_select = memnew(VisualScriptPropertySelector); add_child(new_connect_node_select); - new_connect_node_select->connect("selected", this, "_selected_connect_node_method_or_setget"); - new_connect_node_select->get_cancel()->connect("pressed", this, "_cancel_connect_node_method_or_setget"); + new_connect_node_select->connect("selected", this, "_selected_connect_node"); + new_connect_node_select->get_cancel()->connect("pressed", this, "_cancel_connect_node"); - new_virtual_method_select = memnew(PropertySelector); + 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()->connect("pressed", this, "_selected_new_virtual_method"); - - port_action_popup = memnew(PopupMenu); - add_child(port_action_popup); - port_action_popup->connect("id_pressed", this, "_port_action_menu"); + new_virtual_method_select->get_cancel(); member_popup = memnew(PopupMenu); add_child(member_popup); @@ -3515,9 +3672,9 @@ VisualScriptEditor::~VisualScriptEditor() { memdelete(variable_editor); } -static ScriptEditorBase *create_editor(const Ref<Script> &p_script) { +static ScriptEditorBase *create_editor(const RES &p_resource) { - if (Object::cast_to<VisualScript>(*p_script)) { + if (Object::cast_to<VisualScript>(*p_resource)) { return memnew(VisualScriptEditor); } diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 0bd64d6a1d..5f707c9e4c 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -34,9 +34,10 @@ #include "editor/create_dialog.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/property_editor.h" -#include "editor/property_selector.h" #include "scene/gui/graph_edit.h" #include "visual_script.h" +#include "visual_script_property_selector.h" + class VisualScriptEditorSignalEdit; class VisualScriptEditorVariableEdit; @@ -62,15 +63,8 @@ class VisualScriptEditor : public ScriptEditorBase { enum PortAction { - CREATE_CALL, - CREATE_SET, - CREATE_GET, - CREATE_COND, - CREATE_SEQUENCE, - CREATE_SWITCH, - CREATE_ITERATOR, - CREATE_WHILE, - CREATE_RETURN, + CREATE_CALL_SET_GET, + CREATE_ACTION, }; enum MemberAction { @@ -100,16 +94,16 @@ class VisualScriptEditor : public ScriptEditorBase { VisualScriptEditorSignalEdit *signal_editor; AcceptDialog *edit_signal_dialog; - PropertyEditor *edit_signal_edit; + EditorInspector *edit_signal_edit; - PropertySelector *method_select; - PropertySelector *new_connect_node_select; - PropertySelector *new_virtual_method_select; + VisualScriptPropertySelector *method_select; + VisualScriptPropertySelector *new_connect_node_select; + VisualScriptPropertySelector *new_virtual_method_select; VisualScriptEditorVariableEdit *variable_editor; AcceptDialog *edit_variable_dialog; - PropertyEditor *edit_variable_edit; + EditorInspector *edit_variable_edit; CustomPropertyEditor *default_value_edit; @@ -162,9 +156,7 @@ class VisualScriptEditor : public ScriptEditorBase { static Clipboard *clipboard; - PopupMenu *port_action_popup; PopupMenu *member_popup; - MemberType member_type; String member_name; @@ -174,9 +166,17 @@ class VisualScriptEditor : public ScriptEditorBase { Vector2 port_action_pos; int port_action_new_node; void _port_action_menu(int p_option); - void _selected_connect_node_method_or_setget(const String &p_text); - void _cancel_connect_node_method_or_setget(); - void _selected_new_virtual_method(const String &p_text); + + void new_node(Ref<VisualScriptNode> vnode, Vector2 ofs); + + void connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id); + + void _selected_connect_node(const String &p_text, const String &p_category, const bool p_connecting = true); + void connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id); + + void _cancel_connect_node(); + void _create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point); + void _selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting); int error_line; @@ -211,6 +211,9 @@ class VisualScriptEditor : public ScriptEditorBase { String revert_on_drag; void _input(const Ref<InputEvent> &p_event); + + void _generic_search(); + void _members_gui_input(const Ref<InputEvent> &p_event); void _on_nodes_delete(); void _on_nodes_duplicate(); @@ -231,10 +234,10 @@ class VisualScriptEditor : public ScriptEditorBase { void _comment_node_resized(const Vector2 &p_new_size, int p_node); int selecting_method_id; - void _selected_method(const String &p_method); + void _selected_method(const String &p_method, const String &p_type, const bool p_connecting); void _draw_color_over_button(Object *obj, Color p_color); - void _button_resource_previewed(const String &p_path, const Ref<Texture> &p_preview, Variant p_ud); + void _button_resource_previewed(const String &p_path, const Ref<Texture> &p_preview, const Ref<Texture> &p_small_preview, Variant p_ud); VisualScriptNode::TypeGuess _guess_output_type(int p_port_action_node, int p_port_action_output, Set<int> &visited_nodes); @@ -250,9 +253,9 @@ public: virtual void set_syntax_highlighter(SyntaxHighlighter *p_highlighter); virtual void apply_code(); - virtual Ref<Script> get_edited_script() const; + virtual RES get_edited_resource() const; + virtual void set_edited_resource(const RES &p_res); virtual Vector<String> get_functions(); - virtual void set_edited_script(const Ref<Script> &p_script); virtual void reload_text(); virtual String get_name(); virtual Ref<Texture> get_icon(); diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp index d5f9d21348..868d22b541 100644 --- a/modules/visual_script/visual_script_expression.cpp +++ b/modules/visual_script/visual_script_expression.cpp @@ -56,11 +56,11 @@ bool VisualScriptExpression::_set(const StringName &p_name, const Variant &p_val int from = inputs.size(); inputs.resize(int(p_value)); for (int i = from; i < inputs.size(); i++) { - inputs[i].name = String::chr('a' + i); + inputs.write[i].name = String::chr('a' + i); if (from == 0) { - inputs[i].type = output_type; + inputs.write[i].type = output_type; } else { - inputs[i].type = inputs[from - 1].type; + inputs.write[i].type = inputs[from - 1].type; } } expression_dirty = true; @@ -78,10 +78,10 @@ bool VisualScriptExpression::_set(const StringName &p_name, const Variant &p_val if (what == "type") { - inputs[idx].type = Variant::Type(int(p_value)); + inputs.write[idx].type = Variant::Type(int(p_value)); } else if (what == "name") { - inputs[idx].name = p_value; + inputs.write[idx].name = p_value; } else { return false; } @@ -1153,8 +1153,8 @@ VisualScriptExpression::ENode *VisualScriptExpression::_parse_expression() { op->op = expression[i].op; op->nodes[0] = expression[i + 1].node; op->nodes[1] = NULL; - expression[i].is_op = false; - expression[i].node = op; + expression.write[i].is_op = false; + expression.write[i].node = op; expression.remove(i + 1); } @@ -1188,7 +1188,7 @@ VisualScriptExpression::ENode *VisualScriptExpression::_parse_expression() { op->nodes[1] = expression[next_op + 1].node; //next expression goes as right //replace all 3 nodes by this operator and make it an expression - expression[next_op - 1].node = op; + expression.write[next_op - 1].node = op; expression.remove(next_op); expression.remove(next_op); } @@ -1370,8 +1370,8 @@ public: bool ret = _execute(p_inputs, constructor->arguments[i], value, r_error_str, ce); if (ret) return true; - arr[i] = value; - argp[i] = &arr[i]; + arr.write[i] = value; + argp.write[i] = &arr[i]; } r_ret = Variant::construct(constructor->data_type, (const Variant **)argp.ptr(), argp.size(), ce); @@ -1397,8 +1397,8 @@ public: bool ret = _execute(p_inputs, bifunc->arguments[i], value, r_error_str, ce); if (ret) return true; - arr[i] = value; - argp[i] = &arr[i]; + arr.write[i] = value; + argp.write[i] = &arr[i]; } VisualScriptBuiltinFunc::exec_func(bifunc->func, (const Variant **)argp.ptr(), &r_ret, ce, r_error_str); @@ -1429,8 +1429,8 @@ public: bool ret = _execute(p_inputs, call->arguments[i], value, r_error_str, ce); if (ret) return true; - arr[i] = value; - argp[i] = &arr[i]; + arr.write[i] = value; + argp.write[i] = &arr[i]; } r_ret = base.call(call->method, (const Variant **)argp.ptr(), argp.size(), ce); diff --git a/modules/visual_script/visual_script_flow_control.cpp b/modules/visual_script/visual_script_flow_control.cpp index ea23ab1b2a..c3ab949d24 100644 --- a/modules/visual_script/visual_script_flow_control.cpp +++ b/modules/visual_script/visual_script_flow_control.cpp @@ -30,9 +30,9 @@ #include "visual_script_flow_control.h" -#include "io/resource_loader.h" -#include "os/keyboard.h" -#include "project_settings.h" +#include "core/io/resource_loader.h" +#include "core/os/keyboard.h" +#include "core/project_settings.h" ////////////////////////////////////////// ////////////////RETURN//////////////////// @@ -684,7 +684,7 @@ bool VisualScriptSwitch::_set(const StringName &p_name, const Variant &p_value) int idx = String(p_name).get_slice("/", 1).to_int(); ERR_FAIL_INDEX_V(idx, case_values.size(), false); - case_values[idx].type = Variant::Type(int(p_value)); + case_values.write[idx].type = Variant::Type(int(p_value)); _change_notify(); ports_changed_notify(); @@ -767,7 +767,7 @@ PropertyInfo VisualScriptTypeCast::get_input_value_port_info(int p_idx) const { PropertyInfo VisualScriptTypeCast::get_output_value_port_info(int p_idx) const { - return PropertyInfo(Variant::OBJECT, ""); + return PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_TYPE_STRING, get_base_type()); } String VisualScriptTypeCast::get_caption() const { diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index bdf5705ecd..1913bfd8c7 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -30,9 +30,9 @@ #include "visual_script_func_nodes.h" -#include "engine.h" -#include "io/resource_loader.h" -#include "os/os.h" +#include "core/engine.h" +#include "core/io/resource_loader.h" +#include "core/os/os.h" #include "scene/main/node.h" #include "scene/main/scene_tree.h" #include "visual_script_nodes.h" @@ -43,7 +43,7 @@ int VisualScriptFunctionCall::get_output_sequence_port_count() const { - if (method_cache.flags & METHOD_FLAG_CONST || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function))) + if ((method_cache.flags & METHOD_FLAG_CONST && call_mode != CALL_MODE_INSTANCE) || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function))) return 0; else return 1; @@ -51,7 +51,7 @@ int VisualScriptFunctionCall::get_output_sequence_port_count() const { bool VisualScriptFunctionCall::has_input_sequence_port() const { - if (method_cache.flags & METHOD_FLAG_CONST || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function))) + if ((method_cache.flags & METHOD_FLAG_CONST && call_mode != CALL_MODE_INSTANCE) || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function))) return false; else return true; @@ -231,7 +231,7 @@ PropertyInfo VisualScriptFunctionCall::get_output_value_port_info(int p_idx) con if (call_mode == CALL_MODE_INSTANCE) { if (p_idx == 0) { - return PropertyInfo(Variant::OBJECT, "pass"); + return PropertyInfo(Variant::OBJECT, "pass", PROPERTY_HINT_TYPE_STRING, get_base_type()); } else { p_idx--; } @@ -394,7 +394,6 @@ void VisualScriptFunctionCall::_update_method_cache() { } } - //print_line("BASE: "+String(type)+" FUNC: "+String(function)); MethodBind *mb = ClassDB::get_method(type, function); if (mb) { use_default_args = mb->get_default_argument_count(); @@ -1055,7 +1054,7 @@ 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) { - return PropertyInfo(Variant::OBJECT, "pass"); + return PropertyInfo(Variant::OBJECT, "pass", PROPERTY_HINT_TYPE_STRING, get_base_type()); } else { return PropertyInfo(); } diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 8b7b809ec0..99748af8a1 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -30,11 +30,11 @@ #include "visual_script_nodes.h" -#include "engine.h" -#include "global_constants.h" -#include "os/input.h" -#include "os/os.h" -#include "project_settings.h" +#include "core/engine.h" +#include "core/global_constants.h" +#include "core/os/input.h" +#include "core/os/os.h" +#include "core/project_settings.h" #include "scene/main/node.h" #include "scene/main/scene_tree.h" @@ -54,8 +54,8 @@ bool VisualScriptFunction::_set(const StringName &p_name, const Variant &p_value arguments.resize(new_argc); for (int i = argc; i < new_argc; i++) { - arguments[i].name = "arg" + itos(i + 1); - arguments[i].type = Variant::NIL; + arguments.write[i].name = "arg" + itos(i + 1); + arguments.write[i].type = Variant::NIL; } ports_changed_notify(); _change_notify(); @@ -68,7 +68,7 @@ bool VisualScriptFunction::_set(const StringName &p_name, const Variant &p_value if (what == "type") { Variant::Type new_type = Variant::Type(int(p_value)); - arguments[idx].type = new_type; + arguments.write[idx].type = new_type; ports_changed_notify(); return true; @@ -76,7 +76,7 @@ bool VisualScriptFunction::_set(const StringName &p_name, const Variant &p_value if (what == "name") { - arguments[idx].name = p_value; + arguments.write[idx].name = p_value; ports_changed_notify(); return true; } @@ -167,7 +167,7 @@ void VisualScriptFunction::_get_property_list(List<PropertyInfo> *p_list) const p_list->push_back(PropertyInfo(Variant::INT, "stack/size", PROPERTY_HINT_RANGE, "1,100000")); } p_list->push_back(PropertyInfo(Variant::BOOL, "stack/stackless")); - p_list->push_back(PropertyInfo(Variant::INT, "rpc/mode", PROPERTY_HINT_ENUM, "Disabled,Remote,Sync,Master,Slave")); + p_list->push_back(PropertyInfo(Variant::INT, "rpc/mode", PROPERTY_HINT_ENUM, "Disabled,Remote,Master,Puppet,Remote Sync,Master Sync,Puppet Sync")); } int VisualScriptFunction::get_output_sequence_port_count() const { @@ -205,6 +205,8 @@ PropertyInfo VisualScriptFunction::get_output_value_port_info(int p_idx) const { PropertyInfo out; out.type = arguments[p_idx].type; out.name = arguments[p_idx].name; + out.hint = arguments[p_idx].hint; + out.hint_string = arguments[p_idx].hint_string; return out; } @@ -218,11 +220,13 @@ String VisualScriptFunction::get_text() const { return get_name(); //use name as function name I guess } -void VisualScriptFunction::add_argument(Variant::Type p_type, const String &p_name, int p_index) { +void VisualScriptFunction::add_argument(Variant::Type p_type, const String &p_name, int p_index, const PropertyHint p_hint, const String &p_hint_string) { Argument arg; arg.name = p_name; arg.type = p_type; + arg.hint = p_hint; + arg.hint_string = p_hint_string; if (p_index >= 0) arguments.insert(p_index, arg); else @@ -234,7 +238,7 @@ void VisualScriptFunction::set_argument_type(int p_argidx, Variant::Type p_type) ERR_FAIL_INDEX(p_argidx, arguments.size()); - arguments[p_argidx].type = p_type; + arguments.write[p_argidx].type = p_type; ports_changed_notify(); } Variant::Type VisualScriptFunction::get_argument_type(int p_argidx) const { @@ -246,7 +250,7 @@ void VisualScriptFunction::set_argument_name(int p_argidx, const String &p_name) ERR_FAIL_INDEX(p_argidx, arguments.size()); - arguments[p_argidx].name = p_name; + arguments.write[p_argidx].name = p_name; ports_changed_notify(); } String VisualScriptFunction::get_argument_name(int p_argidx) const { @@ -849,7 +853,7 @@ public: virtual int step(const Variant **p_inputs, Variant **p_outputs, StartMode p_start_mode, Variant *p_working_mem, Variant::CallError &r_error, String &r_error_str) { - if (instance->get_variable(variable, p_outputs[0]) == false) { + if (!instance->get_variable(variable, p_outputs[0])) { r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; r_error_str = RTR("VariableGet not found in script: ") + "'" + String(variable) + "'"; return false; @@ -971,7 +975,7 @@ public: virtual int step(const Variant **p_inputs, Variant **p_outputs, StartMode p_start_mode, Variant *p_working_mem, Variant::CallError &r_error, String &r_error_str) { - if (instance->set_variable(variable, *p_inputs[0]) == false) { + if (!instance->set_variable(variable, *p_inputs[0])) { r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; r_error_str = RTR("VariableSet not found in script: ") + "'" + String(variable) + "'"; @@ -1659,7 +1663,7 @@ Variant::Type VisualScriptBasicTypeConstant::get_basic_type() const { class VisualScriptNodeInstanceBasicTypeConstant : public VisualScriptNodeInstance { public: - int value; + Variant value; bool valid; //virtual int get_working_memory_size() const { return 0; } @@ -1678,7 +1682,7 @@ public: VisualScriptNodeInstance *VisualScriptBasicTypeConstant::instance(VisualScriptInstance *p_instance) { VisualScriptNodeInstanceBasicTypeConstant *instance = memnew(VisualScriptNodeInstanceBasicTypeConstant); - instance->value = Variant::get_numeric_constant_value(type, name, &instance->valid); + instance->value = Variant::get_constant_value(type, name, &instance->valid); return instance; } @@ -1687,7 +1691,7 @@ void VisualScriptBasicTypeConstant::_validate_property(PropertyInfo &property) c if (property.name == "constant") { List<StringName> constants; - Variant::get_numeric_constants_for_type(type, &constants); + Variant::get_constants_for_type(type, &constants); if (constants.size() == 0) { property.usage = 0; @@ -2198,7 +2202,7 @@ PropertyInfo VisualScriptSceneTree::get_input_value_port_info(int p_idx) const { PropertyInfo VisualScriptSceneTree::get_output_value_port_info(int p_idx) const { - return PropertyInfo(Variant::OBJECT, "Scene Tree"); + return PropertyInfo(Variant::OBJECT, "Scene Tree", PROPERTY_HINT_TYPE_STRING, "SceneTree"); } String VisualScriptSceneTree::get_caption() const { @@ -3560,8 +3564,8 @@ void VisualScriptDeconstruct::_set_elem_cache(const Array &p_elements) { ERR_FAIL_COND(p_elements.size() % 2 == 1); elements.resize(p_elements.size() / 2); for (int i = 0; i < elements.size(); i++) { - elements[i].name = p_elements[i * 2 + 0]; - elements[i].type = Variant::Type(int(p_elements[i * 2 + 1])); + elements.write[i].name = p_elements[i * 2 + 0]; + elements.write[i].type = Variant::Type(int(p_elements[i * 2 + 1])); } } @@ -3606,7 +3610,7 @@ VisualScriptNodeInstance *VisualScriptDeconstruct::instance(VisualScriptInstance instance->instance = p_instance; instance->outputs.resize(elements.size()); for (int i = 0; i < elements.size(); i++) { - instance->outputs[i] = elements[i].name; + instance->outputs.write[i] = elements[i].name; } return instance; @@ -3704,18 +3708,18 @@ void register_visual_script_nodes() { for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) { if (E->get().arguments.size() > 0) { - - String name = "functions/constructors/" + Variant::get_type_name(Variant::Type(i)) + " ( "; + String name = "functions/constructors/" + Variant::get_type_name(Variant::Type(i)) + "("; for (int j = 0; j < E->get().arguments.size(); j++) { - if (j > 0) + if (j > 0) { name += ", "; - if (E->get().arguments.size() == 1) + } + if (E->get().arguments.size() == 1) { name += Variant::get_type_name(E->get().arguments[j].type); - else + } else { name += E->get().arguments[j].name; + } } - name += ") "; - + name += ")"; VisualScriptLanguage::singleton->add_register_func(name, create_constructor_node); Pair<Variant::Type, MethodInfo> pair; pair.first = Variant::Type(i); diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h index 9bfbd46e47..f7ac995816 100644 --- a/modules/visual_script/visual_script_nodes.h +++ b/modules/visual_script/visual_script_nodes.h @@ -40,6 +40,8 @@ class VisualScriptFunction : public VisualScriptNode { struct Argument { String name; Variant::Type type; + PropertyHint hint; + String hint_string; }; Vector<Argument> arguments; @@ -70,7 +72,7 @@ public: virtual String get_text() const; virtual String get_category() const { return "flow_control"; } - void add_argument(Variant::Type p_type, const String &p_name, int p_index = -1); + void add_argument(Variant::Type p_type, const String &p_name, int p_index = -1, const PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = String("")); void set_argument_type(int p_argidx, Variant::Type p_type); Variant::Type get_argument_type(int p_argidx) const; void set_argument_name(int p_argidx, const String &p_name); diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp new file mode 100644 index 0000000000..e5d12cb495 --- /dev/null +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -0,0 +1,743 @@ +/*************************************************************************/ +/* visual_script_property_selector.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 "visual_script_property_selector.h" + +#include "core/os/keyboard.h" +#include "editor/editor_node.h" +#include "editor_scale.h" +#include "modules/visual_script/visual_script.h" +#include "modules/visual_script/visual_script_builtin_funcs.h" +#include "modules/visual_script/visual_script_flow_control.h" +#include "modules/visual_script/visual_script_func_nodes.h" +#include "modules/visual_script/visual_script_nodes.h" +#include "scene/main/node.h" +#include "scene/main/viewport.h" + +void VisualScriptPropertySelector::_text_changed(const String &p_newtext) { + _update_search(); +} + +void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { + + Ref<InputEventKey> k = p_ie; + + if (k.is_valid()) { + + switch (k->get_scancode()) { + case KEY_UP: + case KEY_DOWN: + case KEY_PAGEUP: + case KEY_PAGEDOWN: { + + search_options->call("_gui_input", k); + search_box->accept_event(); + + TreeItem *root = search_options->get_root(); + if (!root->get_children()) + break; + + TreeItem *current = search_options->get_selected(); + + TreeItem *item = search_options->get_next_selected(root); + while (item) { + item->deselect(0); + item = search_options->get_next_selected(item); + } + + current->select(0); + + } break; + } + } +} + +void VisualScriptPropertySelector::_update_search() { + set_title(TTR("Search VisualScript")); + + search_options->clear(); + help_bit->set_text(""); + + TreeItem *root = search_options->create_item(); + bool found = false; + + if (properties) { + + List<PropertyInfo> props; + + if (instance) { + instance->get_property_list(&props, true); + } else if (type != Variant::NIL) { + Variant v; + Variant::CallError ce; + v = Variant::construct(type, NULL, 0, ce); + + v.get_property_list(&props); + } else { + + Object *obj = ObjectDB::get_instance(script); + if (Object::cast_to<Script>(obj)) { + + props.push_back(PropertyInfo(Variant::NIL, "Script Variables", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CATEGORY)); + Object::cast_to<Script>(obj)->get_script_property_list(&props); + } + + StringName base = base_type; + while (base) { + props.push_back(PropertyInfo(Variant::NIL, base, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CATEGORY)); + ClassDB::get_property_list(base, &props, true); + base = ClassDB::get_parent_class_nocheck(base); + } + } + + TreeItem *category = NULL; + + Ref<Texture> type_icons[Variant::VARIANT_MAX] = { + Control::get_icon("Variant", "EditorIcons"), + Control::get_icon("bool", "EditorIcons"), + Control::get_icon("int", "EditorIcons"), + Control::get_icon("float", "EditorIcons"), + Control::get_icon("String", "EditorIcons"), + Control::get_icon("Vector2", "EditorIcons"), + Control::get_icon("Rect2", "EditorIcons"), + Control::get_icon("Vector3", "EditorIcons"), + Control::get_icon("Transform2D", "EditorIcons"), + Control::get_icon("Plane", "EditorIcons"), + Control::get_icon("Quat", "EditorIcons"), + Control::get_icon("AABB", "EditorIcons"), + Control::get_icon("Basis", "EditorIcons"), + Control::get_icon("Transform", "EditorIcons"), + Control::get_icon("Color", "EditorIcons"), + Control::get_icon("Path", "EditorIcons"), + Control::get_icon("RID", "EditorIcons"), + Control::get_icon("Object", "EditorIcons"), + Control::get_icon("Dictionary", "EditorIcons"), + Control::get_icon("Array", "EditorIcons"), + Control::get_icon("PoolByteArray", "EditorIcons"), + Control::get_icon("PoolIntArray", "EditorIcons"), + Control::get_icon("PoolRealArray", "EditorIcons"), + Control::get_icon("PoolStringArray", "EditorIcons"), + Control::get_icon("PoolVector2Array", "EditorIcons"), + Control::get_icon("PoolVector3Array", "EditorIcons"), + Control::get_icon("PoolColorArray", "EditorIcons") + }; + + if (!seq_connect && !visual_script_generic) { + get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box); + get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box); + get_visual_node_names("functions/by_type/" + Variant::get_type_name(type), Set<String>(), found, root, search_box); + get_visual_node_names("operators/compare/", Set<String>(), found, root, search_box); + if (type == Variant::INT) { + get_visual_node_names("operators/bitwise/", Set<String>(), found, root, search_box); + } + if (type == Variant::BOOL) { + get_visual_node_names("operators/logic/", Set<String>(), found, root, search_box); + } + if (type == Variant::BOOL || type == Variant::INT || type == Variant::REAL || type == Variant::VECTOR2 || type == Variant::VECTOR3) { + get_visual_node_names("operators/math/", Set<String>(), found, root, search_box); + } + } + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (E->get().usage == PROPERTY_USAGE_CATEGORY) { + if (category && category->get_children() == NULL) { + memdelete(category); //old category was unused + } + category = search_options->create_item(root); + category->set_text(0, E->get().name); + category->set_selectable(0, false); + + Ref<Texture> icon; + if (E->get().name == "Script Variables") { + icon = get_icon("Script", "EditorIcons"); + } else { + icon = EditorNode::get_singleton()->get_class_icon(E->get().name); + } + category->set_icon(0, icon); + continue; + } + + if (!(E->get().usage & PROPERTY_USAGE_EDITOR) && !(E->get().usage & PROPERTY_USAGE_SCRIPT_VARIABLE)) + continue; + + if (type_filter.size() && type_filter.find(E->get().type) == -1) + continue; + + // capitalize() also converts underscore to space, we'll match again both possible styles + String get_text_raw = String(vformat(TTR("Get %s"), E->get().name)); + String get_text = get_text_raw.capitalize(); + String set_text_raw = String(vformat(TTR("Set %s"), E->get().name)); + String set_text = set_text_raw.capitalize(); + String input = search_box->get_text().capitalize(); + + if (input == String() || get_text_raw.findn(input) != -1 || get_text.findn(input) != -1) { + TreeItem *item = search_options->create_item(category ? category : root); + item->set_text(0, get_text); + item->set_metadata(0, E->get().name); + item->set_icon(0, type_icons[E->get().type]); + item->set_metadata(1, "get"); + item->set_collapsed(1); + item->set_selectable(0, true); + item->set_selectable(1, false); + item->set_selectable(2, false); + item->set_metadata(2, connecting); + } + + if (input == String() || set_text_raw.findn(input) != -1 || set_text.findn(input) != -1) { + TreeItem *item = search_options->create_item(category ? category : root); + item->set_text(0, set_text); + item->set_metadata(0, E->get().name); + item->set_icon(0, type_icons[E->get().type]); + item->set_metadata(1, "set"); + item->set_selectable(0, true); + item->set_selectable(1, false); + item->set_selectable(2, false); + item->set_metadata(2, connecting); + } + } + + if (category && category->get_children() == NULL) { + memdelete(category); //old category was unused + } + } + + if (seq_connect && !visual_script_generic) { + String text = search_box->get_text(); + create_visualscript_item(String("VisualScriptCondition"), root, text, String("Condition")); + create_visualscript_item(String("VisualScriptSwitch"), root, text, String("Switch")); + create_visualscript_item(String("VisualScriptSequence"), root, text, String("Sequence")); + create_visualscript_item(String("VisualScriptIterator"), root, text, String("Iterator")); + create_visualscript_item(String("VisualScriptWhile"), root, text, String("While")); + create_visualscript_item(String("VisualScriptReturn"), root, text, String("Return")); + get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box); + get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box); + } + + if (visual_script_generic) { + get_visual_node_names("", Set<String>(), found, root, search_box); + } + + List<MethodInfo> methods; + + if (type != Variant::NIL) { + Variant v; + Variant::CallError ce; + v = Variant::construct(type, NULL, 0, ce); + v.get_method_list(&methods); + } else { + + Object *obj = ObjectDB::get_instance(script); + if (Object::cast_to<Script>(obj)) { + + methods.push_back(MethodInfo("*Script Methods")); + Object::cast_to<Script>(obj)->get_script_method_list(&methods); + } + + StringName base = base_type; + while (base) { + methods.push_back(MethodInfo("*" + String(base))); + ClassDB::get_method_list(base, &methods, true, true); + base = ClassDB::get_parent_class_nocheck(base); + } + } + TreeItem *category = NULL; + bool script_methods = false; + + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name.begins_with("*")) { + if (category && category->get_children() == NULL) { + memdelete(category); //old category was unused + } + category = search_options->create_item(root); + category->set_text(0, E->get().name.replace_first("*", "")); + category->set_selectable(0, false); + + Ref<Texture> icon; + script_methods = false; + String rep = E->get().name.replace("*", ""); + if (E->get().name == "*Script Methods") { + icon = get_icon("Script", "EditorIcons"); + script_methods = true; + } else { + icon = EditorNode::get_singleton()->get_class_icon(rep); + } + category->set_icon(0, icon); + + continue; + } + + String name = E->get().name.get_slice(":", 0); + if (!script_methods && name.begins_with("_") && !(E->get().flags & METHOD_FLAG_VIRTUAL)) + continue; + + if (virtuals_only && !(E->get().flags & METHOD_FLAG_VIRTUAL)) + continue; + + if (!virtuals_only && (E->get().flags & METHOD_FLAG_VIRTUAL)) + continue; + + MethodInfo mi = E->get(); + String desc_arguments; + if (mi.arguments.size() > 0) { + desc_arguments = "("; + for (int i = 0; i < mi.arguments.size(); i++) { + + if (i > 0) { + desc_arguments += ", "; + } + if (mi.arguments[i].type == Variant::NIL) { + desc_arguments += "var"; + } else if (mi.arguments[i].name.find(":") != -1) { + desc_arguments += mi.arguments[i].name.get_slice(":", 1); + mi.arguments[i].name = mi.arguments[i].name.get_slice(":", 0); + } else { + desc_arguments += Variant::get_type_name(mi.arguments[i].type); + } + } + desc_arguments += ")"; + } + String desc_raw = mi.name + desc_arguments; + String desc = desc_raw.capitalize().replace("( ", "("); + + if (search_box->get_text() != String() && + name.findn(search_box->get_text()) == -1 && + desc.findn(search_box->get_text()) == -1 && + desc_raw.findn(search_box->get_text()) == -1) { + continue; + } + + TreeItem *item = search_options->create_item(category ? category : root); + item->set_text(0, desc); + item->set_icon(0, get_icon("MemberMethod", "EditorIcons")); + item->set_metadata(0, name); + item->set_selectable(0, true); + + item->set_metadata(1, "method"); + item->set_collapsed(1); + item->set_selectable(1, false); + + item->set_selectable(2, false); + item->set_metadata(2, connecting); + + if (category && category->get_children() == NULL) { + memdelete(category); //old category was unused + } + } + + TreeItem *selected_item = search_options->search_item_text(search_box->get_text()); + if (!found && selected_item != NULL) { + selected_item->select(0); + found = true; + } + + if (category && category->get_children() == NULL) { + memdelete(category); //old category was unused + } + + get_ok()->set_disabled(root->get_children() == NULL); +} + +void VisualScriptPropertySelector::create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text) { + if (search_input == String() || text.findn(search_input) != -1) { + TreeItem *item = search_options->create_item(root); + item->set_text(0, text); + item->set_icon(0, get_icon("VisualScript", "EditorIcons")); + item->set_metadata(0, name); + item->set_metadata(1, "action"); + item->set_selectable(0, true); + item->set_collapsed(1); + item->set_selectable(1, false); + item->set_selectable(2, false); + item->set_metadata(2, connecting); + } +} + +void VisualScriptPropertySelector::get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box) { + Map<String, TreeItem *> path_cache; + + List<String> fnodes; + VisualScriptLanguage::singleton->get_registered_node_names(&fnodes); + + for (List<String>::Element *E = fnodes.front(); E; E = E->next()) { + if (!E->get().begins_with(root_filter)) { + continue; + } + Vector<String> path = E->get().split("/"); + bool is_filter = false; + for (Set<String>::Element *F = filter.front(); F; F = F->next()) { + if (path.size() >= 2 && path[1].findn(F->get()) != -1) { + is_filter = true; + break; + } + } + if (is_filter) { + continue; + } + + if (search_box->get_text() != String() && E->get().findn(search_box->get_text()) == -1) { + continue; + } + TreeItem *item = search_options->create_item(root); + VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(*VisualScriptLanguage::singleton->create_node_from_name(E->get())); + String type_name; + if (vnode_operator != NULL) { + String type; + if (path.size() >= 2) { + type = path[1]; + } + type_name = type.capitalize() + " "; + } + VisualScriptFunctionCall *vnode_function_call = Object::cast_to<VisualScriptFunctionCall>(*VisualScriptLanguage::singleton->create_node_from_name(E->get())); + if (vnode_function_call != NULL) { + String basic_type = Variant::get_type_name(vnode_function_call->get_basic_type()); + type_name = basic_type.capitalize() + " "; + } + + Vector<String> desc = path[path.size() - 1].replace("(", "( ").replace(")", " )").replace(",", ", ").split(" "); + for (size_t i = 0; i < desc.size(); i++) { + desc.write[i] = desc[i].capitalize(); + if (desc[i].ends_with(",")) { + desc.write[i] = desc[i].replace(",", ", "); + } + } + + item->set_text(0, type_name + String("").join(desc)); + item->set_icon(0, get_icon("VisualScript", "EditorIcons")); + item->set_selectable(0, true); + item->set_metadata(0, E->get()); + item->set_selectable(0, true); + item->set_metadata(1, "visualscript"); + item->set_selectable(1, false); + item->set_selectable(2, false); + item->set_metadata(2, connecting); + } +} + +void VisualScriptPropertySelector::_confirmed() { + + TreeItem *ti = search_options->get_selected(); + if (!ti) + return; + emit_signal("selected", ti->get_metadata(0), ti->get_metadata(1), ti->get_metadata(2)); + hide(); +} + +void VisualScriptPropertySelector::_item_selected() { + + help_bit->set_text(""); + + TreeItem *item = search_options->get_selected(); + if (!item) + return; + String name = item->get_metadata(0); + + String class_type; + if (type) { + class_type = Variant::get_type_name(type); + + } else { + class_type = base_type; + } + + DocData *dd = EditorHelp::get_doc_data(); + String text; + + String at_class = class_type; + + while (at_class != String()) { + + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(at_class); + if (E) { + for (int i = 0; i < E->get().properties.size(); i++) { + if (E->get().properties[i].name == name) { + text = E->get().properties[i].description; + } + } + } + + at_class = ClassDB::get_parent_class_nocheck(at_class); + } + at_class = class_type; + + while (at_class != String()) { + + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(at_class); + if (E) { + for (int i = 0; i < E->get().methods.size(); i++) { + if (E->get().methods[i].name == name) { + text = E->get().methods[i].description; + } + } + } + + at_class = ClassDB::get_parent_class_nocheck(at_class); + } + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(class_type); + if (E) { + for (int i = 0; i < E->get().methods.size(); i++) { + Vector<String> functions = name.rsplit("/", false, 1); + if (E->get().methods[i].name == functions[functions.size() - 1]) { + text = E->get().methods[i].description; + } + } + } + + List<String> *names = memnew(List<String>); + VisualScriptLanguage::singleton->get_registered_node_names(names); + if (names->find(name) != NULL) { + Ref<VisualScriptOperator> operator_node = VisualScriptLanguage::singleton->create_node_from_name(name); + if (operator_node.is_valid()) { + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(operator_node->get_class_name()); + if (E) { + text = Variant::get_operator_name(operator_node->get_operator()); + } + } + Ref<VisualScriptTypeCast> typecast_node = VisualScriptLanguage::singleton->create_node_from_name(name); + if (typecast_node.is_valid()) { + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(typecast_node->get_class_name()); + if (E) { + text = E->get().description; + } + } + + Ref<VisualScriptBuiltinFunc> builtin_node = VisualScriptLanguage::singleton->create_node_from_name(name); + if (builtin_node.is_valid()) { + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(builtin_node->get_class_name()); + if (E) { + for (int i = 0; i < E->get().constants.size(); i++) { + if (E->get().constants[i].value.to_int() == int(builtin_node->get_func())) { + text = E->get().constants[i].description; + } + } + } + } + } + + memdelete(names); + + if (text == String()) + return; + + help_bit->set_text(text); +} + +void VisualScriptPropertySelector::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + + connect("confirmed", this, "_confirmed"); + } +} + +void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const String &p_current, const bool p_virtuals_only, const bool p_connecting) { + + base_type = p_base; + selected = p_current; + type = Variant::NIL; + script = 0; + properties = false; + instance = NULL; + virtuals_only = p_virtuals_only; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + connecting = p_connecting; + + _update_search(); +} + +void VisualScriptPropertySelector::set_type_filter(const Vector<Variant::Type> &p_type_filter) { + type_filter = p_type_filter; +} + +void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_current, bool p_virtuals_only, bool p_seq_connect, const bool p_connecting) { + + base_type = p_base; + selected = p_current; + type = Variant::NIL; + script = 0; + properties = true; + visual_script_generic = false; + instance = NULL; + virtuals_only = p_virtuals_only; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + seq_connect = p_seq_connect; + connecting = p_connecting; + + _update_search(); +} + +void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const String &p_current, const bool p_connecting) { + ERR_FAIL_COND(p_script.is_null()); + + base_type = p_script->get_instance_base_type(); + selected = p_current; + type = Variant::NIL; + script = p_script->get_instance_id(); + properties = true; + visual_script_generic = false; + instance = NULL; + virtuals_only = false; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + seq_connect = false; + connecting = p_connecting; + + _update_search(); +} + +void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const String &p_current, const bool p_connecting) { + ERR_FAIL_COND(p_type == Variant::NIL); + base_type = ""; + selected = p_current; + type = p_type; + script = 0; + properties = true; + visual_script_generic = false; + instance = NULL; + virtuals_only = false; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + seq_connect = false; + connecting = p_connecting; + + _update_search(); +} + +void VisualScriptPropertySelector::select_from_action(const String &p_type, const String &p_current, const bool p_connecting) { + base_type = p_type; + selected = p_current; + type = Variant::NIL; + script = 0; + properties = false; + visual_script_generic = false; + instance = NULL; + virtuals_only = false; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + seq_connect = true; + connecting = p_connecting; + + _update_search(); +} + +void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const String &p_current, const bool p_connecting) { + base_type = ""; + selected = p_current; + type = Variant::NIL; + script = 0; + properties = true; + visual_script_generic = false; + instance = p_instance; + virtuals_only = false; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + seq_connect = false; + connecting = p_connecting; + + _update_search(); +} + +void VisualScriptPropertySelector::select_from_visual_script(const String &p_base, const bool p_connecting) { + base_type = p_base; + selected = ""; + type = Variant::NIL; + script = 0; + properties = true; + visual_script_generic = true; + instance = NULL; + virtuals_only = false; + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + connecting = p_connecting; + + _update_search(); +} + +void VisualScriptPropertySelector::show_window(float p_screen_ratio) { + Rect2 rect; + Point2 window_size = get_viewport_rect().size; + rect.size = (window_size * p_screen_ratio).floor(); + rect.size.x = rect.size.x / 1.25f; + rect.position = ((window_size - rect.size) / 2.0f).floor(); + popup(rect); +} + +void VisualScriptPropertySelector::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_text_changed"), &VisualScriptPropertySelector::_text_changed); + ClassDB::bind_method(D_METHOD("_confirmed"), &VisualScriptPropertySelector::_confirmed); + ClassDB::bind_method(D_METHOD("_sbox_input"), &VisualScriptPropertySelector::_sbox_input); + ClassDB::bind_method(D_METHOD("_item_selected"), &VisualScriptPropertySelector::_item_selected); + + ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "category"), PropertyInfo(Variant::BOOL, "connecting"))); +} + +VisualScriptPropertySelector::VisualScriptPropertySelector() { + + VBoxContainer *vbc = memnew(VBoxContainer); + add_child(vbc); + //set_child_rect(vbc); + search_box = memnew(LineEdit); + vbc->add_margin_child(TTR("Search:"), search_box); + search_box->connect("text_changed", this, "_text_changed"); + search_box->connect("gui_input", this, "_sbox_input"); + search_options = memnew(Tree); + vbc->add_margin_child(TTR("Matches:"), search_options, true); + get_ok()->set_text(TTR("Open")); + get_ok()->set_disabled(true); + register_text_enter(search_box); + set_hide_on_ok(false); + search_options->connect("item_activated", this, "_confirmed"); + search_options->connect("cell_selected", this, "_item_selected"); + search_options->set_hide_root(true); + search_options->set_hide_folding(true); + virtuals_only = false; + seq_connect = false; + help_bit = memnew(EditorHelpBit); + vbc->add_margin_child(TTR("Description:"), help_bit); + help_bit->connect("request_hide", this, "_closed"); + search_options->set_columns(3); + search_options->set_column_expand(1, false); + search_options->set_column_expand(2, false); +} diff --git a/modules/visual_script/visual_script_property_selector.h b/modules/visual_script/visual_script_property_selector.h new file mode 100644 index 0000000000..f974ee3355 --- /dev/null +++ b/modules/visual_script/visual_script_property_selector.h @@ -0,0 +1,92 @@ +/*************************************************************************/ +/* visual_script_property_selector.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef VISUALSCRIPT_PROPERTYSELECTOR_H +#define VISUALSCRIPT_PROPERTYSELECTOR_H + +#include "editor/property_editor.h" +#include "editor_help.h" +#include "scene/gui/rich_text_label.h" + +class VisualScriptPropertySelector : public ConfirmationDialog { + GDCLASS(VisualScriptPropertySelector, ConfirmationDialog) + + LineEdit *search_box; + Tree *search_options; + + void _update_search(); + + void create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text); + + void get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box); + + void _sbox_input(const Ref<InputEvent> &p_ie); + + void _confirmed(); + void _text_changed(const String &p_newtext); + + EditorHelpBit *help_bit; + + bool properties; + bool visual_script_generic; + bool connecting; + String selected; + Variant::Type type; + String base_type; + ObjectID script; + Object *instance; + bool virtuals_only; + bool seq_connect; + + void _item_selected(); + + Vector<Variant::Type> type_filter; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void select_method_from_base_type(const String &p_base, const String &p_current = "", const bool p_virtuals_only = false, const bool p_connecting = true); + void select_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false, bool p_seq_connect = false, const bool p_connecting = true); + void select_from_script(const Ref<Script> &p_script, const String &p_current = "", const bool p_connecting = true); + void select_from_basic_type(Variant::Type p_type, const String &p_current = "", const bool p_connecting = true); + void select_from_action(const String &p_type, const String &p_current = "", const bool p_connecting = true); + void select_from_instance(Object *p_instance, const String &p_current = "", const bool p_connecting = true); + void select_from_visual_script(const String &p_base, const bool p_connecting = true); + + void show_window(float p_screen_ratio); + + void set_type_filter(const Vector<Variant::Type> &p_type_filter); + + VisualScriptPropertySelector(); +}; + +#endif // VISUALSCRIPT_PROPERTYSELECTOR_H diff --git a/modules/visual_script/visual_script_yield_nodes.cpp b/modules/visual_script/visual_script_yield_nodes.cpp index a96e8408c0..a21fff67fe 100644 --- a/modules/visual_script/visual_script_yield_nodes.cpp +++ b/modules/visual_script/visual_script_yield_nodes.cpp @@ -30,7 +30,7 @@ #include "visual_script_yield_nodes.h" -#include "os/os.h" +#include "core/os/os.h" #include "scene/main/node.h" #include "scene/main/scene_tree.h" #include "visual_script_nodes.h" diff --git a/modules/vorbis/SCsub b/modules/vorbis/SCsub index 55a112585b..19587563ab 100644 --- a/modules/vorbis/SCsub +++ b/modules/vorbis/SCsub @@ -40,13 +40,16 @@ if env['builtin_libvorbis']: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_vorbis.add_source_files(env.modules_sources, thirdparty_sources) env_vorbis.Append(CPPPATH=[thirdparty_dir]) # also requires libogg if env['builtin_libogg']: env_vorbis.Append(CPPPATH=["#thirdparty/libogg"]) + env_thirdparty = env_vorbis.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + if not stub: # Module files env_vorbis.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index bae8f7be5f..5bc82f267f 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -48,7 +48,7 @@ size_t AudioStreamPlaybackOGGVorbis::_ov_read_func(void *p_dst, size_t p_data, s int AudioStreamPlaybackOGGVorbis::_ov_seek_func(void *_f, ogg_int64_t offs, int whence) { -//printf("seek to %p, offs %i, whence %i\n",_f,(int)offs,whence); + //printf("seek to %p, offs %i, whence %i\n",_f,(int)offs,whence); #ifdef SEEK_SET //printf("seek set defined\n"); diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h index 01de8a3143..73c4b5f3f4 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.h +++ b/modules/vorbis/audio_stream_ogg_vorbis.h @@ -31,9 +31,9 @@ #ifndef AUDIO_STREAM_OGG_VORBIS_H #define AUDIO_STREAM_OGG_VORBIS_H -#include "io/resource_loader.h" -#include "os/file_access.h" -#include "os/thread_safe.h" +#include "core/io/resource_loader.h" +#include "core/os/file_access.h" +#include "core/os/thread_safe.h" #include "scene/resources/audio_stream.h" #include <vorbis/vorbisfile.h> diff --git a/modules/webm/SCsub b/modules/webm/SCsub index 33561da098..cb35b926ab 100644 --- a/modules/webm/SCsub +++ b/modules/webm/SCsub @@ -6,17 +6,16 @@ Import('env_modules') env_webm = env_modules.Clone() # Thirdparty source files -thirdparty_libsimplewebm_dir = "#thirdparty/libsimplewebm/" -thirdparty_libsimplewebm_sources = [ +thirdparty_dir = "#thirdparty/libsimplewebm/" +thirdparty_sources = [ "libwebm/mkvparser/mkvparser.cc", "OpusVorbisDecoder.cpp", "VPXDecoder.cpp", "WebMDemuxer.cpp", ] -thirdparty_libsimplewebm_sources = [thirdparty_libsimplewebm_dir + file for file in thirdparty_libsimplewebm_sources] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env_webm.add_source_files(env.modules_sources, thirdparty_libsimplewebm_sources) -env_webm.Append(CPPPATH=[thirdparty_libsimplewebm_dir, thirdparty_libsimplewebm_dir + "libwebm/"]) +env_webm.Append(CPPPATH=[thirdparty_dir, thirdparty_dir + "libwebm/"]) # upstream uses c++11 if (not env_webm.msvc): @@ -31,8 +30,12 @@ if env['builtin_opus']: env_webm.Append(CPPPATH=["#thirdparty/opus"]) if env['builtin_libvpx']: - Export('env_webm') + env_webm.Append(CPPPATH=["#thirdparty/libvpx"]) SConscript("libvpx/SCsub") +env_thirdparty = env_webm.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Godot source files env_webm.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/webm/libvpx/SCsub b/modules/webm/libvpx/SCsub index c681e2b34f..98e38b9027 100644 --- a/modules/webm/libvpx/SCsub +++ b/modules/webm/libvpx/SCsub @@ -1,5 +1,10 @@ #!/usr/bin/env python +Import('env') +Import('env_modules') + +# Thirdparty sources + libvpx_dir = "#thirdparty/libvpx/" libvpx_sources = [ @@ -38,7 +43,6 @@ libvpx_sources = [ "vp8/decoder/decodemv.c", "vp8/decoder/detokenize.c", "vp8/decoder/onyxd_if.c", - "vp8/decoder/threading.c", "vp9/vp9_dx_iface.c", @@ -102,6 +106,10 @@ libvpx_sources = [ "vpx_util/vpx_thread.c" ] +libvpx_sources_mt = [ + "vp8/decoder/threading.c", +] + libvpx_sources_intrin_x86 = [ "vp8/common/x86/filter_x86.c", "vp8/common/x86/loopfilter_x86.c", @@ -231,6 +239,7 @@ libvpx_sources_arm_neon_gas_apple = [ ] libvpx_sources = [libvpx_dir + file for file in libvpx_sources] +libvpx_sources_mt = [libvpx_dir + file for file in libvpx_sources_mt] libvpx_sources_intrin_x86 = [libvpx_dir + file for file in libvpx_sources_intrin_x86] libvpx_sources_intrin_x86_mmx = [libvpx_dir + file for file in libvpx_sources_intrin_x86_mmx] libvpx_sources_intrin_x86_sse2 = [libvpx_dir + file for file in libvpx_sources_intrin_x86_sse2] @@ -245,14 +254,12 @@ libvpx_sources_arm_neon_armasm_ms = [libvpx_dir + file for file in libvpx_source libvpx_sources_arm_neon_gas_apple = [libvpx_dir + file for file in libvpx_sources_arm_neon_gas_apple] -Import('env') -Import('env_webm') - -env_webm.Append(CPPPATH=[libvpx_dir]) - -env_libvpx = env.Clone() +env_libvpx = env_modules.Clone() +env_libvpx.disable_warnings() env_libvpx.Append(CPPPATH=[libvpx_dir]) +webm_multithread = env["platform"] != 'javascript' + cpu_bits = env["bits"] webm_cpu_x86 = False webm_cpu_arm = False @@ -338,8 +345,12 @@ if webm_simd_optimizations == False: print("WebM SIMD optimizations are disabled. Check if your CPU architecture, CPU bits or platform are supported!") env_libvpx.add_source_files(env.modules_sources, libvpx_sources) + +if webm_multithread: + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_mt) + if webm_cpu_x86: - is_clang_or_gcc = ('gcc' in env["CC"]) or ('clang' in env["CC"]) or ("OSXCROSS_ROOT" in os.environ) + is_clang_or_gcc = ('gcc' in os.path.basename(env["CC"])) or ('clang' in os.path.basename(env["CC"])) or ("OSXCROSS_ROOT" in os.environ) env_libvpx_mmx = env_libvpx.Clone() if cpu_bits == '32' and is_clang_or_gcc: diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp index 1bb9a43886..675fc97b55 100644 --- a/modules/webm/video_stream_webm.cpp +++ b/modules/webm/video_stream_webm.cpp @@ -35,9 +35,9 @@ #include "mkvparser/mkvparser.h" -#include "os/file_access.h" -#include "os/os.h" -#include "project_settings.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "core/project_settings.h" #include "thirdparty/misc/yuv2rgb.h" @@ -453,7 +453,6 @@ RES ResourceFormatLoaderWebm::load(const String &p_path, const String &p_origina if (r_error) { *r_error = ERR_CANT_OPEN; } - memdelete(f); return RES(); } diff --git a/modules/webm/video_stream_webm.h b/modules/webm/video_stream_webm.h index 08be50846d..3739a73114 100644 --- a/modules/webm/video_stream_webm.h +++ b/modules/webm/video_stream_webm.h @@ -31,7 +31,7 @@ #ifndef VIDEO_STREAM_WEBM_H #define VIDEO_STREAM_WEBM_H -#include "io/resource_loader.h" +#include "core/io/resource_loader.h" #include "scene/resources/video_stream.h" class WebMFrame; @@ -109,7 +109,6 @@ private: class VideoStreamWebm : public VideoStream { GDCLASS(VideoStreamWebm, VideoStream); - RES_BASE_EXTENSION("webm"); String file; int audio_track; diff --git a/modules/webp/SCsub b/modules/webp/SCsub index 21ae0ce7c2..d215f19cef 100644 --- a/modules/webp/SCsub +++ b/modules/webp/SCsub @@ -42,7 +42,6 @@ if env['builtin_libwebp']: "dsp/dec_neon.c", "dsp/dec_sse2.c", "dsp/dec_sse41.c", - "dsp/enc_avx2.c", "dsp/enc.c", "dsp/enc_mips32.c", "dsp/enc_mips_dsp_r2.c", @@ -90,7 +89,6 @@ if env['builtin_libwebp']: "enc/backward_references_enc.c", "enc/config_enc.c", "enc/cost_enc.c", - "enc/delta_palettization_enc.c", "enc/filter_enc.c", "enc/frame_enc.c", "enc/histogram_enc.c", @@ -127,8 +125,11 @@ if env['builtin_libwebp']: ] thirdparty_sources = [thirdparty_dir + "src/" + file for file in thirdparty_sources] - env_webp.add_source_files(env.modules_sources, thirdparty_sources) env_webp.Append(CPPPATH=[thirdparty_dir, thirdparty_dir + "src/"]) + env_thirdparty = env_webp.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + # Godot source files env_webp.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/webp/image_loader_webp.cpp b/modules/webp/image_loader_webp.cpp index cdf2d75e96..6734ae90d9 100644 --- a/modules/webp/image_loader_webp.cpp +++ b/modules/webp/image_loader_webp.cpp @@ -30,9 +30,9 @@ #include "image_loader_webp.h" -#include "io/marshalls.h" -#include "os/os.h" -#include "print_string.h" +#include "core/io/marshalls.h" +#include "core/os/os.h" +#include "core/print_string.h" #include <stdlib.h> #include <webp/decode.h> @@ -116,64 +116,73 @@ static Ref<Image> _webp_lossy_unpack(const PoolVector<uint8_t> &p_buffer) { return img; } -Error ImageLoaderWEBP::load_image(Ref<Image> p_image, FileAccess *f, bool p_force_linear, float p_scale) { +Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len) { - uint32_t size = f->get_len(); - PoolVector<uint8_t> src_image; - src_image.resize(size); + ERR_FAIL_NULL_V(p_image, ERR_INVALID_PARAMETER); WebPBitstreamFeatures features; - - PoolVector<uint8_t>::Write src_w = src_image.write(); - f->get_buffer(src_w.ptr(), size); - ERR_FAIL_COND_V(f->eof_reached(), ERR_FILE_EOF); - - if (WebPGetFeatures(src_w.ptr(), size, &features) != VP8_STATUS_OK) { - f->close(); - //ERR_EXPLAIN("Error decoding WEBP image: "+p_file); + if (WebPGetFeatures(p_buffer, p_buffer_len, &features) != VP8_STATUS_OK) { + // ERR_EXPLAIN("Error decoding WEBP image"); ERR_FAIL_V(ERR_FILE_CORRUPT); } - /* - print_line("width: " + itos(features.width)); - print_line("height: " + itos(features.height)); - print_line("alpha: " + itos(features.has_alpha)); - */ - - src_w = PoolVector<uint8_t>::Write(); - PoolVector<uint8_t> dst_image; int datasize = features.width * features.height * (features.has_alpha ? 4 : 3); dst_image.resize(datasize); - - PoolVector<uint8_t>::Read src_r = src_image.read(); PoolVector<uint8_t>::Write dst_w = dst_image.write(); bool errdec = false; if (features.has_alpha) { - errdec = WebPDecodeRGBAInto(src_r.ptr(), size, dst_w.ptr(), datasize, 4 * features.width) == NULL; + errdec = WebPDecodeRGBAInto(p_buffer, p_buffer_len, dst_w.ptr(), datasize, 4 * features.width) == NULL; } else { - errdec = WebPDecodeRGBInto(src_r.ptr(), size, dst_w.ptr(), datasize, 3 * features.width) == NULL; + errdec = WebPDecodeRGBInto(p_buffer, p_buffer_len, dst_w.ptr(), datasize, 3 * features.width) == NULL; } + dst_w = PoolVector<uint8_t>::Write(); - //ERR_EXPLAIN("Error decoding webp! - "+p_file); + //ERR_EXPLAIN("Error decoding webp!"); ERR_FAIL_COND_V(errdec, ERR_FILE_CORRUPT); - src_r = PoolVector<uint8_t>::Read(); - dst_w = PoolVector<uint8_t>::Write(); - p_image->create(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image); return OK; } +static Ref<Image> _webp_mem_loader_func(const uint8_t *p_png, int p_size) { + + Ref<Image> img; + img.instance(); + Error err = webp_load_image_from_buffer(img.ptr(), p_png, p_size); + ERR_FAIL_COND_V(err, Ref<Image>()); + return img; +} + +Error ImageLoaderWEBP::load_image(Ref<Image> p_image, FileAccess *f, bool p_force_linear, float p_scale) { + + PoolVector<uint8_t> src_image; + int src_image_len = f->get_len(); + ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT); + src_image.resize(src_image_len); + + PoolVector<uint8_t>::Write w = src_image.write(); + + f->get_buffer(&w[0], src_image_len); + + f->close(); + + Error err = webp_load_image_from_buffer(p_image.ptr(), w.ptr(), src_image_len); + + w = PoolVector<uint8_t>::Write(); + + return err; +} + void ImageLoaderWEBP::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("webp"); } ImageLoaderWEBP::ImageLoaderWEBP() { - + Image::_webp_mem_loader_func = _webp_mem_loader_func; Image::lossy_packer = _webp_lossy_pack; Image::lossy_unpacker = _webp_lossy_unpack; } diff --git a/modules/webp/image_loader_webp.h b/modules/webp/image_loader_webp.h index f051fed4b8..256c787a16 100644 --- a/modules/webp/image_loader_webp.h +++ b/modules/webp/image_loader_webp.h @@ -31,7 +31,7 @@ #ifndef IMAGE_LOADER_WEBP_H #define IMAGE_LOADER_WEBP_H -#include "io/image_loader.h" +#include "core/io/image_loader.h" /** @author Juan Linietsky <reduzio@gmail.com> diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub index 15a88773e7..b67a836fe8 100644 --- a/modules/websocket/SCsub +++ b/modules/websocket/SCsub @@ -7,87 +7,87 @@ Import('env_modules') env_lws = env_modules.Clone() -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" -] - -if env_lws["platform"] == "android": # Builtin getifaddrs - thirdparty_sources += ["misc/getifaddrs.c"] - -if env_lws["platform"] == "windows" or env_lws["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] - -if env_lws["platform"] == "javascript": # No need to add third party libraries at all - pass -else: - env_lws.add_source_files(env.modules_sources, thirdparty_sources) - env_lws.Append(CPPPATH=[thirdparty_dir]) +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" + ] + + 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] - wrapper_includes = ["#thirdparty/libwebsockets/tls/mbedtls/wrapper/include/" + inc for inc in ["internal", "openssl", "platform", ""]] - env_lws.Prepend(CPPPATH=wrapper_includes) + env_lws.Append(CPPPATH=[thirdparty_dir]) if env['builtin_mbedtls']: mbedtls_includes = "#thirdparty/mbedtls/include" env_lws.Prepend(CPPPATH=[mbedtls_includes]) - if env_lws["platform"] == "windows" or env_lws["platform"] == "uwp": + wrapper_includes = ["#thirdparty/libwebsockets/tls/mbedtls/wrapper/include/" + inc for inc in ["internal", "openssl", "platform", ""]] + env_lws.Prepend(CPPPATH=wrapper_includes) + + if env["platform"] == "windows" or env["platform"] == "uwp": env_lws.Append(CPPPATH=[thirdparty_dir + helper_dir]) - if env_lws["platform"] == "uwp": + if env["platform"] == "uwp": env_lws.Append(CCFLAGS=["/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") diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml index 2e11e1a44c..b3ea535495 100644 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -25,14 +25,18 @@ </argument> <description> Connect to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. - If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a network peer for the [MultiplayerAPI]. Note: connnections to non Godot servers will not work, and [signal data_received] will not be emitted when this option is true. + If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a network peer for the [MultiplayerAPI]. Note: connections to non Godot servers will not work, and [signal data_received] will not be emitted when this option is true. </description> </method> <method name="disconnect_from_host"> <return type="void"> </return> + <argument index="0" name="code" type="int" default="1000"> + </argument> + <argument index="1" name="reason" type="String" default=""""> + </argument> <description> - Disconnect from the server if currently connected. + Disconnect this client from the connected host. See [method WebSocketPeer.close] for more info. </description> </method> </methods> @@ -43,8 +47,10 @@ </members> <signals> <signal name="connection_closed"> + <argument index="0" name="was_clean_close" type="bool"> + </argument> <description> - Emitted when the connection to the server is closed. + Emitted when the connection to the server is closed. [code]was_clean_close[/code] will be [code]true[/code] if the connection was shutdown cleanly. </description> </signal> <signal name="connection_error"> @@ -64,6 +70,15 @@ Emitted when a WebSocket message is received. Note: This signal is NOT emitted when used as high level multiplayer peer. </description> </signal> + <signal name="server_close_request"> + <argument index="0" name="code" type="int"> + </argument> + <argument index="1" name="reason" type="String"> + </argument> + <description> + Emitted when the server requests a clean close. You should keep polling until you get a [signal connection_closed] signal to achieve the clean close. See [method WebSocketPeer.close] for more details. + </description> + </signal> </signals> <constants> </constants> diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml index 85a08e0c0b..cc59706916 100644 --- a/modules/websocket/doc_classes/WebSocketPeer.xml +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -15,8 +15,14 @@ <method name="close"> <return type="void"> </return> + <argument index="0" name="code" type="int" default="1000"> + </argument> + <argument index="1" name="reason" type="String" default=""""> + </argument> <description> - Close this WebSocket connection, actively disconnecting the peer. + Close this WebSocket connection. [code]code[/code] is the status code for the closure (see RFC6455 section 7.4 for a list of valid status codes). [reason] is the human readable reason for closing the connection (can be any UTF8 string, must be less than 123 bytes). + Note: To achieve a clean close, you will need to keep polling until either [signal WebSocketClient.connection_closed] or [signal WebSocketServer.client_disconnected] is received. + Note: HTML5 export might not support all status codes. Please refer to browsers-specific documentation for more details. </description> </method> <method name="get_connected_host" qualifiers="const"> diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index a1061e446b..ba66fdd89b 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -18,8 +18,12 @@ </return> <argument index="0" name="id" type="int"> </argument> + <argument index="1" name="code" type="int" default="1000"> + </argument> + <argument index="2" name="reason" type="String" default=""""> + </argument> <description> - Disconnects the given peer. + Disconnects the peer identified by [code]id[/code] from the server. See [method WebSocketPeer.close] for more info. </description> </method> <method name="get_peer_address" qualifiers="const"> @@ -68,7 +72,7 @@ <description> Start 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 use this server as a network peer for [MultiplayerAPI] by passing true as "gd_mp_api". Note: [signal data_received] will not be fired and clients other than Godot will not work in this case. + You can use this server as a network peer for [MultiplayerAPI] by passing [code]true[/code] as [code]gd_mp_api[/code]. Note: [signal data_received] will not be fired and clients other than Godot will not work in this case. </description> </method> <method name="stop"> @@ -80,6 +84,17 @@ </method> </methods> <signals> + <signal name="client_close_request"> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="code" type="int"> + </argument> + <argument index="2" name="reason" type="String"> + </argument> + <description> + Emitted when a client requests a clean close. You should keep polling until you get a [signal client_disconnected] signal with the same [code]id[/code] to achieve the clean close. See [method WebSocketPeer.close] for more details. + </description> + </signal> <signal name="client_connected"> <argument index="0" name="id" type="int"> </argument> @@ -92,8 +107,10 @@ <signal name="client_disconnected"> <argument index="0" name="id" type="int"> </argument> + <argument index="1" name="was_clean_close" type="bool"> + </argument> <description> - Emitted when a client disconnects. + Emitted when a client disconnects. [code]was_clean_close[/code] will be [code]true[/code] if the connection was shutdown cleanly. </description> </signal> <signal name="data_received"> diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp index 1405fa98b0..82a577790e 100644 --- a/modules/websocket/emws_client.cpp +++ b/modules/websocket/emws_client.cpp @@ -31,6 +31,7 @@ #include "emws_client.h" #include "core/io/ip.h" +#include "core/project_settings.h" #include "emscripten.h" extern "C" { @@ -43,8 +44,9 @@ EMSCRIPTEN_KEEPALIVE void _esws_on_connect(void *obj, char *proto) { EMSCRIPTEN_KEEPALIVE void _esws_on_message(void *obj, uint8_t *p_data, int p_data_size, int p_is_string) { EMWSClient *client = static_cast<EMWSClient *>(obj); - static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1); - client->_on_peer_packet(); + Error err = static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1); + if (err == OK) + client->_on_peer_packet(); } EMSCRIPTEN_KEEPALIVE void _esws_on_error(void *obj) { @@ -55,33 +57,31 @@ EMSCRIPTEN_KEEPALIVE void _esws_on_error(void *obj) { EMSCRIPTEN_KEEPALIVE void _esws_on_close(void *obj, int code, char *reason, int was_clean) { EMWSClient *client = static_cast<EMWSClient *>(obj); + client->_on_close_request(code, String(reason)); client->_is_connecting = false; - client->_on_disconnect(); + client->_on_disconnect(was_clean != 0); } } Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { + String proto_string = p_protocols.join(","); String str = "ws://"; - String proto_string = ""; - int i = 0; if (p_ssl) str = "wss://"; str += p_host + ":" + itos(p_port) + p_path; - for (int i = 0; i < p_protocols.size(); i++) { - proto_string += p_protocols[i]; - proto_string += ","; - } - if (proto_string == "") - proto_string = "binary,"; - - proto_string = proto_string.substr(0, proto_string.length() - 1); _is_connecting = true; /* clang-format off */ int peer_sock = EM_ASM_INT({ - var socket = new WebSocket(UTF8ToString($1), UTF8ToString($2).split(",")); + var proto_str = UTF8ToString($2); + var socket = null; + if (proto_str) { + socket = new WebSocket(UTF8ToString($1), proto_str.split(",")); + } else { + socket = new WebSocket(UTF8ToString($1)); + } var c_ptr = Module.IDHandler.get($0); socket.binaryType = "arraybuffer"; @@ -148,7 +148,7 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, if (!Module.IDHandler.has($0)) return; // Godot Object is gone! var was_clean = 0; - if (event.was_clean) + if (event.wasClean) was_clean = 1; ccall("_esws_on_close", "void", @@ -161,7 +161,7 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, }, _js_id, str.utf8().get_data(), proto_string.utf8().get_data()); /* clang-format on */ - static_cast<Ref<EMWSPeer> >(_peer)->set_sock(peer_sock); + static_cast<Ref<EMWSPeer> >(_peer)->set_sock(peer_sock, _in_buf_size, _in_pkt_size); return OK; }; @@ -185,9 +185,9 @@ NetworkedMultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() c return CONNECTION_DISCONNECTED; }; -void EMWSClient::disconnect_from_host() { +void EMWSClient::disconnect_from_host(int p_code, String p_reason) { - _peer->close(); + _peer->close(p_code, p_reason); }; IP_Address EMWSClient::get_connected_host() const { @@ -200,7 +200,13 @@ uint16_t EMWSClient::get_connected_port() const { return 1025; }; +int EMWSClient::get_max_packet_size() const { + return (1 << _in_buf_size) - PROTO_SIZE; +} + EMWSClient::EMWSClient() { + _in_buf_size = GLOBAL_GET(WSC_IN_BUF); + _in_pkt_size = GLOBAL_GET(WSC_IN_PKT); _is_connecting = false; _peer = Ref<EMWSPeer>(memnew(EMWSPeer)); /* clang-format off */ diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h index 6c2fa23b53..a21090a1a3 100644 --- a/modules/websocket/emws_client.h +++ b/modules/websocket/emws_client.h @@ -41,6 +41,8 @@ class EMWSClient : public WebSocketClient { GDCIIMPL(EMWSClient, WebSocketClient); private: + int _in_buf_size; + int _in_pkt_size; int _js_id; public: @@ -48,10 +50,11 @@ public: Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); Ref<WebSocketPeer> get_peer(int p_peer_id) const; - void disconnect_from_host(); + void disconnect_from_host(int p_code = 1000, String p_reason = ""); IP_Address get_connected_host() const; uint16_t get_connected_port() const; virtual ConnectionStatus get_connection_status() const; + int get_max_packet_size() const; virtual void poll(); EMWSClient(); ~EMWSClient(); diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index e0b987b4d7..bb97934824 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -32,11 +32,11 @@ #include "emws_peer.h" #include "core/io/ip.h" -void EMWSPeer::set_sock(int p_sock) { +void EMWSPeer::set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size) { peer_sock = p_sock; - in_buffer.clear(); - queue_count = 0; + _in_buffer.resize(p_in_pkt_size, p_in_buf_size); + _packet_buffer.resize((1 << p_in_buf_size)); } void EMWSPeer::set_write_mode(WriteMode p_mode) { @@ -47,18 +47,10 @@ EMWSPeer::WriteMode EMWSPeer::get_write_mode() const { return write_mode; } -void EMWSPeer::read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string) { - - if (in_buffer.space_left() < p_size + 5) { - ERR_EXPLAIN("Buffer full! Dropping data"); - ERR_FAIL(); - } +Error EMWSPeer::read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string) { uint8_t is_string = p_is_string ? 1 : 0; - in_buffer.write((uint8_t *)&p_size, 4); - in_buffer.write((uint8_t *)&is_string, 1); - in_buffer.write(p_data, p_size); - queue_count++; + return _in_buffer.write_packet(p_data, p_size, &is_string); } Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { @@ -89,40 +81,28 @@ Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { Error EMWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - if (queue_count == 0) + if (_in_buffer.packets_left() == 0) return ERR_UNAVAILABLE; - uint32_t to_read = 0; - uint32_t left = 0; - uint8_t is_string = 0; - r_buffer_size = 0; - - in_buffer.read((uint8_t *)&to_read, 4); - --queue_count; - left = in_buffer.data_left(); + PoolVector<uint8_t>::Write rw = _packet_buffer.write(); + int read = 0; + Error err = _in_buffer.read_packet(rw.ptr(), _packet_buffer.size(), &_is_string, read); + ERR_FAIL_COND_V(err != OK, err); - if (left < to_read + 1) { - in_buffer.advance_read(left); - return FAILED; - } - - in_buffer.read(&is_string, 1); - _was_string = is_string == 1; - in_buffer.read(packet_buffer, to_read); - *r_buffer = packet_buffer; - r_buffer_size = to_read; + *r_buffer = rw.ptr(); + r_buffer_size = read; return OK; }; int EMWSPeer::get_available_packet_count() const { - return queue_count; + return _in_buffer.packets_left(); }; bool EMWSPeer::was_string_packet() const { - return _was_string; + return _is_string; }; bool EMWSPeer::is_connected_to_host() const { @@ -130,20 +110,22 @@ bool EMWSPeer::is_connected_to_host() const { return peer_sock != -1; }; -void EMWSPeer::close() { +void EMWSPeer::close(int p_code, String p_reason) { if (peer_sock != -1) { /* clang-format off */ EM_ASM({ var sock = Module.IDHandler.get($0); - sock.close(); + var code = $1; + var reason = UTF8ToString($2); + sock.close(code, reason); Module.IDHandler.remove($0); - }, peer_sock); + }, peer_sock, p_code, p_reason.utf8().get_data()); /* clang-format on */ } + _is_string = 0; + _in_buffer.clear(); peer_sock = -1; - queue_count = 0; - in_buffer.clear(); }; IP_Address EMWSPeer::get_connected_host() const { @@ -160,15 +142,12 @@ uint16_t EMWSPeer::get_connected_port() const { EMWSPeer::EMWSPeer() { peer_sock = -1; - queue_count = 0; - _was_string = false; - in_buffer.resize(16); write_mode = WRITE_MODE_BINARY; + close(); }; EMWSPeer::~EMWSPeer() { - in_buffer.resize(0); close(); }; diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h index e06f725265..4beb86d45b 100644 --- a/modules/websocket/emws_peer.h +++ b/modules/websocket/emws_peer.h @@ -36,6 +36,7 @@ #include "core/io/packet_peer.h" #include "core/ring_buffer.h" #include "emscripten.h" +#include "packet_buffer.h" #include "websocket_peer.h" class EMWSPeer : public WebSocketPeer { @@ -43,27 +44,22 @@ class EMWSPeer : public WebSocketPeer { GDCIIMPL(EMWSPeer, WebSocketPeer); private: - enum { - PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for for type - }; - int peer_sock; WriteMode write_mode; - uint8_t packet_buffer[PACKET_BUFFER_SIZE]; - RingBuffer<uint8_t> in_buffer; - int queue_count; - bool _was_string; + PoolVector<uint8_t> _packet_buffer; + PacketBuffer<uint8_t> _in_buffer; + uint8_t _is_string; public: - void read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string); - void set_sock(int sock); + Error read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string); + void set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size); 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 int get_max_packet_size() const { return _packet_buffer.size(); }; - virtual void close(); + virtual void close(int p_code = 1000, String p_reason = ""); virtual bool is_connected_to_host() const; virtual IP_Address get_connected_host() const; virtual uint16_t get_connected_port() const; @@ -72,10 +68,6 @@ public: virtual void set_write_mode(WriteMode p_mode); virtual bool was_string_packet() const; - void set_wsi(struct lws *wsi); - Error read_wsi(void *in, size_t len); - Error write_wsi(); - EMWSPeer(); ~EMWSPeer(); }; diff --git a/modules/websocket/emws_server.cpp b/modules/websocket/emws_server.cpp index 3eb93e4152..09f9c1ceec 100644 --- a/modules/websocket/emws_server.cpp +++ b/modules/websocket/emws_server.cpp @@ -68,7 +68,14 @@ int EMWSServer::get_peer_port(int p_peer_id) const { return 0; } -void EMWSServer::disconnect_peer(int p_peer_id) { +void EMWSServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { +} + +void EMWSServer::poll() { +} + +int EMWSServer::get_max_packet_size() const { + return 0; } EMWSServer::EMWSServer() { diff --git a/modules/websocket/emws_server.h b/modules/websocket/emws_server.h index 9ec4ce72c8..2dc455c389 100644 --- a/modules/websocket/emws_server.h +++ b/modules/websocket/emws_server.h @@ -48,7 +48,8 @@ public: Ref<WebSocketPeer> get_peer(int p_id) const; 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); + void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = ""); + int get_max_packet_size() const; virtual void poll(); virtual PoolVector<String> get_protocols() const; diff --git a/modules/websocket/lws_client.cpp b/modules/websocket/lws_client.cpp index 06f97aaf05..fa0bb4cfb2 100644 --- a/modules/websocket/lws_client.cpp +++ b/modules/websocket/lws_client.cpp @@ -32,6 +32,7 @@ #include "lws_client.h" #include "core/io/ip.h" #include "core/io/stream_peer_ssl.h" +#include "core/project_settings.h" #include "tls/mbedtls/wrapper/include/openssl/ssl.h" Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { @@ -48,12 +49,10 @@ Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); - // prepare protocols - if (p_protocols.size() == 0) // default to binary protocol - p_protocols.append("binary"); + // Prepare protocols _lws_make_protocols(this, &LWSClient::_lws_gd_callback, p_protocols, &_lws_ref); - // init lws client + // Init lws client struct lws_context_creation_info info; struct lws_client_connect_info i; @@ -78,20 +77,11 @@ Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, ERR_FAIL_V(FAILED); } - char abuf[1024]; - char hbuf[1024]; - char pbuf[2048]; - String addr_str = (String)addr; - strncpy(abuf, addr_str.ascii().get_data(), 1024); - strncpy(hbuf, p_host.utf8().get_data(), 1024); - strncpy(pbuf, p_path.utf8().get_data(), 2048); - i.context = context; - i.protocol = _lws_ref->lws_names; - i.address = abuf; - i.host = hbuf; - i.path = pbuf; - i.port = p_port; + if (p_protocols.size() > 0) + i.protocol = _lws_ref->lws_names; + else + i.protocol = NULL; if (p_ssl) { i.ssl_connection = LCCSCF_USE_SSL; @@ -101,10 +91,24 @@ Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, 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(); @@ -125,31 +129,31 @@ int LWSClient::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, voi } break; case LWS_CALLBACK_CLIENT_ESTABLISHED: - peer->set_wsi(wsi); + peer->set_wsi(wsi, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); peer_data->peer_id = 0; - peer_data->in_size = 0; - peer_data->in_count = 0; - peer_data->out_count = 0; - peer_data->rbw.resize(16); - peer_data->rbr.resize(16); 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) + return -1; // We should close the connection (would probably happen anyway) + + case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: { + int code; + String reason = peer->get_close_reason(in, len, code); + peer_data->clean_close = true; + _on_close_request(code, reason); + return 0; + } case LWS_CALLBACK_CLIENT_CLOSED: - peer_data->in_count = 0; - peer_data->out_count = 0; - peer_data->rbw.resize(0); - peer_data->rbr.resize(0); peer->close(); destroy_context(); - _on_disconnect(); - return 0; // we can end here + _on_disconnect(peer_data->clean_close); + return 0; // We can end here case LWS_CALLBACK_CLIENT_RECEIVE: peer->read_wsi(in, len); @@ -158,8 +162,10 @@ int LWSClient::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, voi break; case LWS_CALLBACK_CLIENT_WRITEABLE: - if (peer_data->force_close) + if (peer_data->force_close) { + peer->send_close_status(wsi); return -1; + } peer->write_wsi(); break; @@ -187,13 +193,12 @@ NetworkedMultiplayerPeer::ConnectionStatus LWSClient::get_connection_status() co return CONNECTION_CONNECTING; } -void LWSClient::disconnect_from_host() { +void LWSClient::disconnect_from_host(int p_code, String p_reason) { if (context == NULL) return; - _peer->close(); - destroy_context(); + _peer->close(p_code, p_reason); }; IP_Address LWSClient::get_connected_host() const { @@ -207,6 +212,11 @@ uint16_t LWSClient::get_connected_port() const { }; 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)); @@ -216,6 +226,7 @@ LWSClient::~LWSClient() { invalidate_lws_ref(); // We do not want any more callback disconnect_from_host(); + destroy_context(); _peer = Ref<LWSPeer>(); }; diff --git a/modules/websocket/lws_client.h b/modules/websocket/lws_client.h index 8850683cb5..fdecb99925 100644 --- a/modules/websocket/lws_client.h +++ b/modules/websocket/lws_client.h @@ -43,10 +43,17 @@ class LWSClient : public WebSocketClient { LWS_HELPER(LWSClient); +private: + int _in_buf_size; + int _in_pkt_size; + int _out_buf_size; + int _out_pkt_size; + public: Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); + int get_max_packet_size() const; Ref<WebSocketPeer> get_peer(int p_peer_id) const; - void disconnect_from_host(); + void disconnect_from_host(int p_code = 1000, String p_reason = ""); IP_Address get_connected_host() const; uint16_t get_connected_port() const; virtual ConnectionStatus get_connection_status() const; diff --git a/modules/websocket/lws_helper.cpp b/modules/websocket/lws_helper.cpp new file mode 100644 index 0000000000..b5216615e9 --- /dev/null +++ b/modules/websocket/lws_helper.cpp @@ -0,0 +1,157 @@ +/*************************************************************************/ +/* lws_helper.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. */ +/*************************************************************************/ + +#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 index a4920c3d54..def4f5cfd0 100644 --- a/modules/websocket/lws_helper.h +++ b/modules/websocket/lws_helper.h @@ -49,132 +49,18 @@ struct _LWSRef { char *lws_names; }; -static _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; -} - -static void _lws_free_ref(_LWSRef *ref) { - // Free strings and structs - memfree(ref->lws_structs); - memfree(ref->lws_names); - // Free ref - memfree(ref); -} - -static 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; -} - -static 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 - */ -static 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. 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; - - copymem(names_ptr, strings.get_data(), str_len); - names_ptr[str_len] = '\0'; /* NULL terminator */ - /* NULL terminated strings to be used in protocol structs */ - 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 always http-only */ - structs_ptr[0].name = "http-only"; - 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; -} +_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) { \ \ @@ -186,6 +72,7 @@ protected: \ if (!ref->is_valid) \ return 0; \ CNAME *helper = (CNAME *)ref->obj; \ + helper->_keep_servicing = true; \ return helper->_handle_cb(wsi, reason, user, in, len); \ } \ \ @@ -206,15 +93,18 @@ public: \ \ void _lws_poll() { \ ERR_FAIL_COND(context == NULL); \ - \ - if (::_lws_poll(context, _lws_ref)) { \ - context = NULL; \ - _lws_ref = NULL; \ - } \ + do { \ + _keep_servicing = false; \ + if (::_lws_poll(context, _lws_ref)) { \ + context = NULL; \ + _lws_ref = NULL; \ + break; \ + } \ + } while (_keep_servicing); \ } \ \ protected: - /* clang-format on */ +/* clang-format on */ #endif // LWS_HELPER_H diff --git a/modules/websocket/lws_peer.cpp b/modules/websocket/lws_peer.cpp index 96acb99cc4..04e6e7c951 100644 --- a/modules/websocket/lws_peer.cpp +++ b/modules/websocket/lws_peer.cpp @@ -30,6 +30,7 @@ #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 @@ -38,9 +39,14 @@ #include <sys/socket.h> #endif -#include "drivers/unix/socket_helpers.h" +#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); -void LWSPeer::set_wsi(struct lws *p_wsi) { + _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; }; @@ -56,25 +62,29 @@ Error LWSPeer::read_wsi(void *in, size_t len) { ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - PeerData *peer_data = (PeerData *)(lws_wsi_user(wsi)); - uint32_t size = peer_data->in_size; - uint8_t is_string = lws_frame_is_binary(wsi) ? 0 : 1; + if (lws_is_first_fragment(wsi)) + _in_size = 0; + else if (_in_size == -1) // Trash this frame + return ERR_FILE_CORRUPT; - if (peer_data->rbr.space_left() < len + 5) { - ERR_EXPLAIN("Buffer full! Dropping data"); - ERR_FAIL_V(FAILED); + 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); } - copymem(&(peer_data->input_buffer[size]), in, len); - size += len; + _in_size += len; - peer_data->in_size = size; if (lws_is_final_fragment(wsi)) { - peer_data->rbr.write((uint8_t *)&size, 4); - peer_data->rbr.write((uint8_t *)&is_string, 1); - peer_data->rbr.write(peer_data->input_buffer, size); - peer_data->in_count++; - peer_data->in_size = 0; + 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; @@ -84,28 +94,21 @@ Error LWSPeer::write_wsi() { ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - PeerData *peer_data = (PeerData *)(lws_wsi_user(wsi)); PoolVector<uint8_t> tmp; - int left = peer_data->rbw.data_left(); - uint32_t to_write = 0; + int count = _out_buffer.packets_left(); - if (left == 0 || peer_data->out_count == 0) + if (count == 0) return OK; - peer_data->rbw.read((uint8_t *)&to_write, 4); - peer_data->out_count--; + int read = 0; + uint8_t is_string; + PoolVector<uint8_t>::Write rw = _packet_buffer.write(); + _out_buffer.read_packet(&(rw[LWS_PRE]), _packet_buffer.size() - LWS_PRE, &is_string, read); - if (left < to_write) { - peer_data->rbw.advance_read(left); - return FAILED; - } + enum lws_write_protocol mode = is_string ? LWS_WRITE_TEXT : LWS_WRITE_BINARY; + lws_write(wsi, &(rw[LWS_PRE]), read, mode); - tmp.resize(LWS_PRE + to_write); - peer_data->rbw.read(&(tmp.write()[LWS_PRE]), to_write); - lws_write(wsi, &(tmp.write()[LWS_PRE]), to_write, (enum lws_write_protocol)write_mode); - tmp.resize(0); - - if (peer_data->out_count > 0) + if (count > 1) lws_callback_on_writable(wsi); // we want to write more! return OK; @@ -115,43 +118,27 @@ Error LWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - PeerData *peer_data = (PeerData *)lws_wsi_user(wsi); - peer_data->rbw.write((uint8_t *)&p_buffer_size, 4); - peer_data->rbw.write(p_buffer, MIN(p_buffer_size, peer_data->rbw.space_left())); - peer_data->out_count++; - + 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) { - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + r_buffer_size = 0; - PeerData *peer_data = (PeerData *)lws_wsi_user(wsi); + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - if (peer_data->in_count == 0) + if (_in_buffer.packets_left() == 0) return ERR_UNAVAILABLE; - uint32_t to_read = 0; - uint32_t left = 0; - uint8_t is_string = 0; - r_buffer_size = 0; - - peer_data->rbr.read((uint8_t *)&to_read, 4); - peer_data->in_count--; - left = peer_data->rbr.data_left(); - - if (left < to_read + 1) { - peer_data->rbr.advance_read(left); - return FAILED; - } + int read = 0; + PoolVector<uint8_t>::Write rw = _packet_buffer.write(); + _in_buffer.read_packet(rw.ptr(), _packet_buffer.size(), &_is_string, read); - peer_data->rbr.read(&is_string, 1); - peer_data->rbr.read(packet_buffer, to_read); - *r_buffer = packet_buffer; - r_buffer_size = to_read; - _was_string = is_string; + *r_buffer = rw.ptr(); + r_buffer_size = read; return OK; }; @@ -161,12 +148,12 @@ int LWSPeer::get_available_packet_count() const { if (!is_connected_to_host()) return 0; - return ((PeerData *)lws_wsi_user(wsi))->in_count; + return _in_buffer.packets_left(); }; bool LWSPeer::was_string_packet() const { - return _was_string; + return _is_string; }; bool LWSPeer::is_connected_to_host() const { @@ -174,14 +161,56 @@ bool LWSPeer::is_connected_to_host() const { return wsi != NULL; }; -void LWSPeer::close() { +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) { - struct lws *tmp = wsi; + close_code = p_code; + close_reason = p_reason; PeerData *data = ((PeerData *)lws_wsi_user(wsi)); data->force_close = true; - wsi = NULL; - lws_callback_on_writable(tmp); // notify that we want to disconnect + 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 { @@ -189,7 +218,7 @@ IP_Address LWSPeer::get_connected_host() const { ERR_FAIL_COND_V(!is_connected_to_host(), IP_Address()); IP_Address ip; - int port = 0; + uint16_t port = 0; struct sockaddr_storage addr; socklen_t len = sizeof(addr); @@ -200,7 +229,7 @@ IP_Address LWSPeer::get_connected_host() const { int ret = getpeername(fd, (struct sockaddr *)&addr, &len); ERR_FAIL_COND_V(ret != 0, IP_Address()); - _set_ip_addr_port(ip, port, &addr); + NetSocketPosix::_set_ip_port(&addr, ip, port); return ip; }; @@ -210,7 +239,7 @@ uint16_t LWSPeer::get_connected_port() const { ERR_FAIL_COND_V(!is_connected_to_host(), 0); IP_Address ip; - int port = 0; + uint16_t port = 0; struct sockaddr_storage addr; socklen_t len = sizeof(addr); @@ -221,15 +250,15 @@ uint16_t LWSPeer::get_connected_port() const { int ret = getpeername(fd, (struct sockaddr *)&addr, &len); ERR_FAIL_COND_V(ret != 0, 0); - _set_ip_addr_port(ip, port, &addr); + NetSocketPosix::_set_ip_port(&addr, ip, port); return port; }; LWSPeer::LWSPeer() { wsi = NULL; - _was_string = false; write_mode = WRITE_MODE_BINARY; + close(); }; LWSPeer::~LWSPeer() { diff --git a/modules/websocket/lws_peer.h b/modules/websocket/lws_peer.h index e96b38b168..3ded3810d1 100644 --- a/modules/websocket/lws_peer.h +++ b/modules/websocket/lws_peer.h @@ -37,6 +37,7 @@ #include "core/ring_buffer.h" #include "libwebsockets.h" #include "lws_config.h" +#include "packet_buffer.h" #include "websocket_peer.h" class LWSPeer : public WebSocketPeer { @@ -44,33 +45,33 @@ class LWSPeer : public WebSocketPeer { GDCIIMPL(LWSPeer, WebSocketPeer); private: - enum { - PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for the type - }; + int _in_size; + 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; - uint8_t packet_buffer[PACKET_BUFFER_SIZE]; struct lws *wsi; WriteMode write_mode; - bool _was_string; + + int close_code; + String close_reason; public: struct PeerData { uint32_t peer_id; bool force_close; - RingBuffer<uint8_t> rbw; - RingBuffer<uint8_t> rbr; - mutable uint8_t input_buffer[PACKET_BUFFER_SIZE]; - uint32_t in_size; - int in_count; - int out_count; + bool clean_close; }; 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 int get_max_packet_size() const { return _packet_buffer.size(); }; - virtual void close(); + virtual void close(int p_code = 1000, String p_reason = ""); virtual bool is_connected_to_host() const; virtual IP_Address get_connected_host() const; virtual uint16_t get_connected_port() const; @@ -79,9 +80,11 @@ public: virtual void set_write_mode(WriteMode p_mode); virtual bool was_string_packet() const; - void set_wsi(struct lws *wsi); + 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); LWSPeer(); ~LWSPeer(); diff --git a/modules/websocket/lws_server.cpp b/modules/websocket/lws_server.cpp index 8d13dc7a98..0e551eb318 100644 --- a/modules/websocket/lws_server.cpp +++ b/modules/websocket/lws_server.cpp @@ -31,6 +31,7 @@ #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) { @@ -41,9 +42,6 @@ Error LWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_a struct lws_context_creation_info info; memset(&info, 0, sizeof info); - if (p_protocols.size() == 0) // default to binary protocol - p_protocols.append(String("binary")); - // Prepare lws protocol structs _lws_make_protocols(this, &LWSServer::_lws_gd_callback, p_protocols, &_lws_ref); @@ -70,6 +68,10 @@ 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; @@ -88,34 +90,41 @@ int LWSServer::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, voi int32_t id = _gen_unique_id(); Ref<LWSPeer> peer = Ref<LWSPeer>(memnew(LWSPeer)); - peer->set_wsi(wsi); + 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->in_size = 0; - peer_data->in_count = 0; - peer_data->out_count = 0; - peer_data->rbw.resize(16); - peer_data->rbr.resize(16); 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 reason = peer->get_close_reason(in, len, code); + peer_data->clean_close = true; + _on_close_request(id, code, reason); + } + 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); } - peer_data->in_count = 0; - peer_data->out_count = 0; - peer_data->rbr.resize(0); - peer_data->rbw.resize(0); - _on_disconnect(id); + _on_disconnect(id, clean); return 0; // we can end here } @@ -130,10 +139,15 @@ int LWSServer::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, voi } case LWS_CALLBACK_SERVER_WRITEABLE: { - if (peer_data->force_close) + 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; + } - int id = peer_data->peer_id; if (_peer_map.has(id)) static_cast<Ref<LWSPeer> >(_peer_map[id])->write_wsi(); break; @@ -176,13 +190,17 @@ int LWSServer::get_peer_port(int p_peer_id) const { return _peer_map[p_peer_id]->get_connected_port(); } -void LWSServer::disconnect_peer(int p_peer_id) { +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(); + get_peer(p_peer_id)->close(p_code, p_reason); } 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; } diff --git a/modules/websocket/lws_server.h b/modules/websocket/lws_server.h index 9e3fb9b775..c43044f194 100644 --- a/modules/websocket/lws_server.h +++ b/modules/websocket/lws_server.h @@ -45,16 +45,21 @@ class LWSServer : public WebSocketServer { private: Map<int, Ref<LWSPeer> > peer_map; + int _in_buf_size; + int _in_pkt_size; + int _out_buf_size; + int _out_pkt_size; public: Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); void stop(); bool is_listening() const; + int get_max_packet_size() const; bool has_peer(int p_id) const; Ref<WebSocketPeer> get_peer(int p_id) const; 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); + void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = ""); virtual void poll() { _lws_poll(); } LWSServer(); diff --git a/modules/websocket/packet_buffer.h b/modules/websocket/packet_buffer.h new file mode 100644 index 0000000000..a3af7f728a --- /dev/null +++ b/modules/websocket/packet_buffer.h @@ -0,0 +1,122 @@ +/*************************************************************************/ +/* packet_buffer.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef PACKET_BUFFER_H +#define PACKET_BUFFER_H + +#include "core/os/copymem.h" +#include "core/ring_buffer.h" + +template <class T> +class PacketBuffer { + +private: + typedef struct { + uint32_t size; + T info; + } _Packet; + + RingBuffer<_Packet> _packets; + RingBuffer<uint8_t> _payload; + +public: + Error write_packet(const uint8_t *p_payload, uint32_t p_size, const T *p_info) { +#ifdef TOOLS_ENABLED + // Verbose buffer warnings + if (p_payload && _payload.space_left() < p_size) { + ERR_PRINT("Buffer payload full! Dropping data."); + ERR_FAIL_V(ERR_OUT_OF_MEMORY); + } + if (p_info && _packets.space_left() < 1) { + ERR_PRINT("Too many packets in queue! Dropping data."); + ERR_FAIL_V(ERR_OUT_OF_MEMORY); + } +#else + ERR_FAIL_COND_V(p_payload && _payload.space_left() < p_size, ERR_OUT_OF_MEMORY); + ERR_FAIL_COND_V(p_info && _packets.space_left() < 1, ERR_OUT_OF_MEMORY); +#endif + + // If p_info is NULL, only the payload is written + if (p_info) { + _Packet p; + p.size = p_size; + copymem(&p.info, p_info, sizeof(T)); + _packets.write(p); + } + + // If p_payload is NULL, only the packet information is written. + if (p_payload) { + _payload.write((const uint8_t *)p_payload, p_size); + } + + return OK; + } + + Error read_packet(uint8_t *r_payload, int p_bytes, T *r_info, int &r_read) { + ERR_FAIL_COND_V(_packets.data_left() < 1, ERR_UNAVAILABLE); + _Packet p; + _packets.read(&p, 1); + ERR_FAIL_COND_V(_payload.data_left() < p.size, ERR_BUG); + ERR_FAIL_COND_V(p_bytes < p.size, ERR_OUT_OF_MEMORY); + + r_read = p.size; + copymem(r_info, &p.info, sizeof(T)); + _payload.read(r_payload, p.size); + return OK; + } + + void discard_payload(int p_size) { + _packets.decrease_write(p_size); + } + + void resize(int p_pkt_shift, int p_buf_shift) { + _packets.resize(p_pkt_shift); + _payload.resize(p_buf_shift); + } + + int packets_left() const { + return _packets.data_left(); + } + + void clear() { + _payload.resize(0); + _packets.resize(0); + } + + PacketBuffer() { + clear(); + } + + ~PacketBuffer() { + clear(); + } +}; + +#endif // PACKET_BUFFER_H diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp index 721f3cc330..8946faffa9 100644 --- a/modules/websocket/register_types.cpp +++ b/modules/websocket/register_types.cpp @@ -28,7 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "register_types.h" -#include "error_macros.h" +#include "core/error_macros.h" +#include "core/project_settings.h" #ifdef JAVASCRIPT_ENABLED #include "emscripten.h" #include "emws_client.h" @@ -41,6 +42,22 @@ #endif void register_websocket_types() { +#define _SET_HINT(NAME, _VAL_, _MAX_) \ + GLOBAL_DEF(NAME, _VAL_); \ + ProjectSettings::get_singleton()->set_custom_property_info(NAME, PropertyInfo(Variant::INT, NAME, PROPERTY_HINT_RANGE, "2," #_MAX_ ",1,or_greater")); + + // Client buffers project settings + _SET_HINT(WSC_IN_BUF, 64, 4096); + _SET_HINT(WSC_IN_PKT, 1024, 16384); + _SET_HINT(WSC_OUT_BUF, 64, 4096); + _SET_HINT(WSC_OUT_PKT, 1024, 16384); + + // Server buffers project settings + _SET_HINT(WSS_IN_BUF, 64, 4096); + _SET_HINT(WSS_IN_PKT, 1024, 16384); + _SET_HINT(WSS_OUT_BUF, 64, 4096); + _SET_HINT(WSS_OUT_PKT, 1024, 16384); + #ifdef JAVASCRIPT_ENABLED EM_ASM({ var IDHandler = {}; diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp index 7701163085..6c5018bb79 100644 --- a/modules/websocket/websocket_client.cpp +++ b/modules/websocket/websocket_client.cpp @@ -107,12 +107,17 @@ void WebSocketClient::_on_connect(String p_protocol) { } } -void WebSocketClient::_on_disconnect() { +void WebSocketClient::_on_close_request(int p_code, String p_reason) { + + emit_signal("server_close_request", p_code, p_reason); +} + +void WebSocketClient::_on_disconnect(bool p_was_clean) { if (_is_multiplayer) { emit_signal("connection_failed"); } else { - emit_signal("connection_closed"); + emit_signal("connection_closed", p_was_clean); } } @@ -127,14 +132,15 @@ void WebSocketClient::_on_error() { void WebSocketClient::_bind_methods() { ClassDB::bind_method(D_METHOD("connect_to_url", "url", "protocols", "gd_mp_api"), &WebSocketClient::connect_to_url, DEFVAL(PoolVector<String>()), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("disconnect_from_host"), &WebSocketClient::disconnect_from_host); + ClassDB::bind_method(D_METHOD("disconnect_from_host", "code", "reason"), &WebSocketClient::disconnect_from_host, DEFVAL(1000), DEFVAL("")); ClassDB::bind_method(D_METHOD("set_verify_ssl_enabled", "enabled"), &WebSocketClient::set_verify_ssl_enabled); ClassDB::bind_method(D_METHOD("is_verify_ssl_enabled"), &WebSocketClient::is_verify_ssl_enabled); - ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "verify_ssl", PROPERTY_HINT_NONE, "", 0), "set_verify_ssl_enabled", "is_verify_ssl_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "verify_ssl", PROPERTY_HINT_NONE, "", 0), "set_verify_ssl_enabled", "is_verify_ssl_enabled"); ADD_SIGNAL(MethodInfo("data_received")); ADD_SIGNAL(MethodInfo("connection_established", PropertyInfo(Variant::STRING, "protocol"))); - ADD_SIGNAL(MethodInfo("connection_closed")); + ADD_SIGNAL(MethodInfo("server_close_request", PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason"))); + ADD_SIGNAL(MethodInfo("connection_closed", PropertyInfo(Variant::BOOL, "was_clean_close"))); ADD_SIGNAL(MethodInfo("connection_error")); } diff --git a/modules/websocket/websocket_client.h b/modules/websocket/websocket_client.h index 6165f37d40..948f128e9f 100644 --- a/modules/websocket/websocket_client.h +++ b/modules/websocket/websocket_client.h @@ -53,7 +53,7 @@ public: virtual void poll() = 0; virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()) = 0; - virtual void disconnect_from_host() = 0; + virtual void disconnect_from_host(int p_code = 1000, String p_reason = "") = 0; virtual IP_Address get_connected_host() const = 0; virtual uint16_t get_connected_port() const = 0; @@ -62,7 +62,8 @@ public: void _on_peer_packet(); void _on_connect(String p_protocol); - void _on_disconnect(); + void _on_close_request(int p_code, String p_reason); + void _on_disconnect(bool p_was_clean); void _on_error(); WebSocketClient(); diff --git a/modules/websocket/websocket_macros.h b/modules/websocket/websocket_macros.h index d27fb4d778..45dd30d0ce 100644 --- a/modules/websocket/websocket_macros.h +++ b/modules/websocket/websocket_macros.h @@ -30,6 +30,16 @@ #ifndef WEBSOCKETMACTOS_H #define WEBSOCKETMACTOS_H +#define WSC_IN_BUF "network/limits/websocket_client/max_in_buffer_kb" +#define WSC_IN_PKT "network/limits/websocket_client/max_in_packets" +#define WSC_OUT_BUF "network/limits/websocket_client/max_out_buffer_kb" +#define WSC_OUT_PKT "network/limits/websocket_client/max_out_packets" + +#define WSS_IN_BUF "network/limits/websocket_server/max_in_buffer_kb" +#define WSS_IN_PKT "network/limits/websocket_server/max_in_packets" +#define WSS_OUT_BUF "network/limits/websocket_server/max_out_buffer_kb" +#define WSS_OUT_PKT "network/limits/websocket_server/max_out_packets" + /* clang-format off */ #define GDCICLASS(CNAME) \ public:\ diff --git a/modules/websocket/websocket_multiplayer.cpp b/modules/websocket/websocket_multiplayer.cpp index b948c439df..9a95c17e47 100644 --- a/modules/websocket/websocket_multiplayer.cpp +++ b/modules/websocket/websocket_multiplayer.cpp @@ -100,13 +100,6 @@ int WebSocketMultiplayerPeer::get_available_packet_count() const { return _incoming_packets.size(); } -int WebSocketMultiplayerPeer::get_max_packet_size() const { - - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); - - return MAX_PACKET_SIZE; -} - Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { r_buffer_size = 0; @@ -313,7 +306,7 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u } else if (to < 0) { // All but one, for us if not excluded - if (_peer_id != -p_peer_id) + if (_peer_id != -(int32_t)p_peer_id) _store_pkt(from, to, in_buffer, data_size); } else { diff --git a/modules/websocket/websocket_multiplayer.h b/modules/websocket/websocket_multiplayer.h index 8edfc5296e..3cba0011fc 100644 --- a/modules/websocket/websocket_multiplayer.h +++ b/modules/websocket/websocket_multiplayer.h @@ -51,9 +51,7 @@ protected: SYS_DEL = 2, SYS_ID = 3, - PROTO_SIZE = 9, - SYS_PACKET_SIZE = 13, - MAX_PACKET_SIZE = 65536 - 14 // 5 websocket, 9 multiplayer + PROTO_SIZE = 9 }; struct Packet { @@ -93,7 +91,7 @@ public: /* PacketPeer */ virtual int get_available_packet_count() const; - virtual int get_max_packet_size() const; + virtual int get_max_packet_size() const = 0; 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); diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp index 61f783e377..3ecb32ce19 100644 --- a/modules/websocket/websocket_peer.cpp +++ b/modules/websocket/websocket_peer.cpp @@ -42,7 +42,7 @@ void WebSocketPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_write_mode", "mode"), &WebSocketPeer::set_write_mode); ClassDB::bind_method(D_METHOD("is_connected_to_host"), &WebSocketPeer::is_connected_to_host); ClassDB::bind_method(D_METHOD("was_string_packet"), &WebSocketPeer::was_string_packet); - ClassDB::bind_method(D_METHOD("close"), &WebSocketPeer::close); + ClassDB::bind_method(D_METHOD("close", "code", "reason"), &WebSocketPeer::close, DEFVAL(1000), DEFVAL("")); ClassDB::bind_method(D_METHOD("get_connected_host"), &WebSocketPeer::get_connected_host); ClassDB::bind_method(D_METHOD("get_connected_port"), &WebSocketPeer::get_connected_port); diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h index ad451e9cc7..4966cdfc72 100644 --- a/modules/websocket/websocket_peer.h +++ b/modules/websocket/websocket_peer.h @@ -32,7 +32,6 @@ #include "core/error_list.h" #include "core/io/packet_peer.h" -#include "core/ring_buffer.h" #include "websocket_macros.h" class WebSocketPeer : public PacketPeer { @@ -58,7 +57,7 @@ public: virtual WriteMode get_write_mode() const = 0; virtual void set_write_mode(WriteMode p_mode) = 0; - virtual void close() = 0; + virtual void close(int p_code = 1000, String p_reason = "") = 0; virtual bool is_connected_to_host() const = 0; virtual IP_Address get_connected_host() const = 0; diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp index 53dd7b51b7..c631ed70d5 100644 --- a/modules/websocket/websocket_server.cpp +++ b/modules/websocket/websocket_server.cpp @@ -46,9 +46,10 @@ void WebSocketServer::_bind_methods() { ClassDB::bind_method(D_METHOD("has_peer", "id"), &WebSocketServer::has_peer); ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketServer::get_peer_address); ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketServer::get_peer_port); - ClassDB::bind_method(D_METHOD("disconnect_peer", "id"), &WebSocketServer::disconnect_peer); + ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "code", "reason"), &WebSocketServer::disconnect_peer, DEFVAL(1000), DEFVAL("")); - ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("client_close_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason"))); + ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::BOOL, "was_clean_close"))); ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol"))); ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::INT, "id"))); } @@ -85,13 +86,18 @@ void WebSocketServer::_on_connect(int32_t p_peer_id, String p_protocol) { } } -void WebSocketServer::_on_disconnect(int32_t p_peer_id) { +void WebSocketServer::_on_disconnect(int32_t p_peer_id, bool p_was_clean) { if (_is_multiplayer) { // Send delete to clients _send_del(p_peer_id); emit_signal("peer_disconnected", p_peer_id); } else { - emit_signal("client_disconnected", p_peer_id); + emit_signal("client_disconnected", p_peer_id, p_was_clean); } } + +void WebSocketServer::_on_close_request(int32_t p_peer_id, int p_code, String p_reason) { + + emit_signal("client_close_request", p_peer_id, p_code, p_reason); +} diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h index 64935f8a58..156f25897c 100644 --- a/modules/websocket/websocket_server.h +++ b/modules/websocket/websocket_server.h @@ -54,11 +54,12 @@ public: virtual IP_Address get_peer_address(int p_peer_id) const = 0; virtual int get_peer_port(int p_peer_id) const = 0; - virtual void disconnect_peer(int p_peer_id) = 0; + virtual void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = "") = 0; void _on_peer_packet(int32_t p_peer_id); void _on_connect(int32_t p_peer_id, String p_protocol); - void _on_disconnect(int32_t p_peer_id); + void _on_disconnect(int32_t p_peer_id, bool p_was_clean); + void _on_close_request(int32_t p_peer_id, int p_code, String p_reason); WebSocketServer(); ~WebSocketServer(); diff --git a/modules/xatlas_unwrap/SCsub b/modules/xatlas_unwrap/SCsub new file mode 100644 index 0000000000..ad364d5aaf --- /dev/null +++ b/modules/xatlas_unwrap/SCsub @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +import platform + +Import('env') +Import('env_modules') + +env_xatlas_unwrap = env_modules.Clone() + +# Thirdparty source files +if env['builtin_xatlas']: + thirdparty_dir = "#thirdparty/xatlas/" + thirdparty_sources = [ + "xatlas.cpp", + ] + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_xatlas_unwrap.Append(CPPPATH=[thirdparty_dir]) + + # upstream uses c++11 + if (not env.msvc): + env_xatlas_unwrap.Append(CXXFLAGS="-std=c++11") + + if env["platform"] == 'x11': + # if not specifically one of the *BSD, then use LINUX as default + if platform.system() == "FreeBSD": + env_xatlas_unwrap.Append(CCFLAGS=["-DNV_OS_FREEBSD", "-DPOSH_COMPILER_GCC"]) + elif platform.system() == "OpenBSD": + env_xatlas_unwrap.Append(CCFLAGS=["-DNV_OS_OPENBSD", "-DPOSH_COMPILER_GCC"]) + else: + env_xatlas_unwrap.Append(CCFLAGS=["-DNV_OS_LINUX", "-DPOSH_COMPILER_GCC"]) + elif env["platform"] == 'osx': + env_xatlas_unwrap.Append(CCFLAGS=["-DNV_OS_DARWIN", "-DPOSH_COMPILER_GCC"]) + elif env["platform"] == 'windows': + if env.msvc: + env_xatlas_unwrap.Append(CCFLAGS=["-DNV_OS_WIN32", "-DNV_CC_MSVC", "-DPOSH_COMPILER_MSVC" ]) + else: + env_xatlas_unwrap.Append(CCFLAGS=["-DNV_OS_MINGW", "-DNV_CC_GNUC", "-DPOSH_COMPILER_GCC", "-U__STRICT_ANSI__"]) + env.Append(LIBS=["dbghelp"]) + + env_thirdparty = env_xatlas_unwrap.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + +# Godot source files +env_xatlas_unwrap.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/xatlas_unwrap/config.py b/modules/xatlas_unwrap/config.py new file mode 100644 index 0000000000..2dda5db7e0 --- /dev/null +++ b/modules/xatlas_unwrap/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + #return False #xatlas is buggy + return (env['tools'] and platform not in ["android", "ios"]) + +def configure(env): + pass diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp new file mode 100644 index 0000000000..57eea4eda6 --- /dev/null +++ b/modules/xatlas_unwrap/register_types.cpp @@ -0,0 +1,143 @@ +/*************************************************************************/ +/* register_types.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 "register_types.h" +#include "core/error_macros.h" +#include "thirdparty/xatlas/xatlas.h" + +#include <stdio.h> +#include <stdlib.h> +extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, const int *p_face_materials, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y); + +bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, const int *p_face_materials, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y) { + + //set up input mesh + xatlas::InputMesh input_mesh; + input_mesh.indexData = malloc(sizeof(int) * p_index_count); + 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.vertexCount = p_vertex_count; + input_mesh.vertexPositionData = malloc(sizeof(float) * p_vertex_count * 3); + 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.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; + + 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; + + 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("generate..\n"); + xatlas::Generate(atlas, chart_options, pack_options); + + *r_size_hint_x = xatlas::GetWidth(atlas); + *r_size_hint_y = xatlas::GetHeight(atlas); + + float w = *r_size_hint_x; + float h = *r_size_hint_y; + + if (w == 0 || h == 0) { + return false; //could not bake + } + + const xatlas::OutputMesh *const *output_meshes = xatlas::GetOutputMeshes(atlas); + + const xatlas::OutputMesh *output = output_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); + + float max_x = 0; + float max_y = 0; + for (int 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; + + for (int i = 0; i < output->indexCount; i++) { + (*r_index)[i] = output->indexArray[i]; + } + + *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"); + return true; +} + +void register_xatlas_unwrap_types() { + + array_mesh_lightmap_unwrap_callback = xatlas_mesh_lightmap_unwrap_callback; +} + +void unregister_xatlas_unwrap_types() { +} diff --git a/modules/xatlas_unwrap/register_types.h b/modules/xatlas_unwrap/register_types.h new file mode 100644 index 0000000000..fd8d56fa53 --- /dev/null +++ b/modules/xatlas_unwrap/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +void register_xatlas_unwrap_types(); +void unregister_xatlas_unwrap_types(); |