diff options
Diffstat (limited to 'modules')
197 files changed, 8338 insertions, 2956 deletions
diff --git a/modules/assimp/import_utils.h b/modules/assimp/import_utils.h index f78931add3..e3510c2cb3 100644 --- a/modules/assimp/import_utils.h +++ b/modules/assimp/import_utils.h @@ -202,20 +202,34 @@ public: */ static float get_fbx_fps(int32_t time_mode, const aiScene *p_scene) { switch (time_mode) { - case AssetImportFbx::TIME_MODE_DEFAULT: return 24; //hack - case AssetImportFbx::TIME_MODE_120: return 120; - case AssetImportFbx::TIME_MODE_100: return 100; - case AssetImportFbx::TIME_MODE_60: return 60; - case AssetImportFbx::TIME_MODE_50: return 50; - case AssetImportFbx::TIME_MODE_48: return 48; - case AssetImportFbx::TIME_MODE_30: return 30; - case AssetImportFbx::TIME_MODE_30_DROP: return 30; - case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: return 29.9700262f; - case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: return 29.9700262f; - case AssetImportFbx::TIME_MODE_PAL: return 25; - case AssetImportFbx::TIME_MODE_CINEMA: return 24; - case AssetImportFbx::TIME_MODE_1000: return 1000; - case AssetImportFbx::TIME_MODE_CINEMA_ND: return 23.976f; + case AssetImportFbx::TIME_MODE_DEFAULT: + return 24; //hack + case AssetImportFbx::TIME_MODE_120: + return 120; + case AssetImportFbx::TIME_MODE_100: + return 100; + case AssetImportFbx::TIME_MODE_60: + return 60; + case AssetImportFbx::TIME_MODE_50: + return 50; + case AssetImportFbx::TIME_MODE_48: + return 48; + case AssetImportFbx::TIME_MODE_30: + return 30; + case AssetImportFbx::TIME_MODE_30_DROP: + return 30; + case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: + return 29.9700262f; + case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: + return 29.9700262f; + case AssetImportFbx::TIME_MODE_PAL: + return 25; + case AssetImportFbx::TIME_MODE_CINEMA: + return 24; + case AssetImportFbx::TIME_MODE_1000: + return 1000; + case AssetImportFbx::TIME_MODE_CINEMA_ND: + return 23.976f; case AssetImportFbx::TIME_MODE_CUSTOM: int32_t frame_rate = -1; p_scene->mMetaData->Get("FrameRate", frame_rate); diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index ea2b2b548f..f69f3a43a4 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -153,7 +153,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, if (p_color_buffer == nullptr || color_table_size == 0) { // regular pixels - p_image->create(width, height, 0, Image::FORMAT_RGBA8, data); + p_image->create(width, height, false, Image::FORMAT_RGBA8, data); } else { // data is in indexed format, extend it @@ -193,7 +193,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, dest += 4; } - p_image->create(width, height, 0, Image::FORMAT_RGBA8, extended_data); + p_image->create(width, height, false, Image::FORMAT_RGBA8, extended_data); } } return err; diff --git a/modules/bullet/area_bullet.cpp b/modules/bullet/area_bullet.cpp index 4d727529ef..a4a86ab751 100644 --- a/modules/bullet/area_bullet.cpp +++ b/modules/bullet/area_bullet.cpp @@ -44,18 +44,7 @@ */ AreaBullet::AreaBullet() : - RigidCollisionObjectBullet(CollisionObjectBullet::TYPE_AREA), - monitorable(true), - spOv_mode(PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED), - spOv_gravityPoint(false), - spOv_gravityPointDistanceScale(0), - spOv_gravityPointAttenuation(1), - spOv_gravityVec(0, -1, 0), - spOv_gravityMag(10), - spOv_linearDump(0.1), - spOv_angularDump(1), - spOv_priority(0), - isScratched(false) { + RigidCollisionObjectBullet(CollisionObjectBullet::TYPE_AREA) { btGhost = bulletnew(btGhostObject); reload_shapes(); diff --git a/modules/bullet/area_bullet.h b/modules/bullet/area_bullet.h index 0272350510..cde889c1ba 100644 --- a/modules/bullet/area_bullet.h +++ b/modules/bullet/area_bullet.h @@ -61,12 +61,10 @@ public: }; struct OverlappingObjectData { - CollisionObjectBullet *object; - OverlapState state; + CollisionObjectBullet *object = nullptr; + OverlapState state = OVERLAP_STATE_ENTER; - OverlappingObjectData() : - object(nullptr), - state(OVERLAP_STATE_ENTER) {} + OverlappingObjectData() {} OverlappingObjectData(CollisionObjectBullet *p_object, OverlapState p_state) : object(p_object), state(p_state) {} @@ -86,19 +84,19 @@ private: btGhostObject *btGhost; Vector<OverlappingObjectData> overlappingObjects; - bool monitorable; - - PhysicsServer3D::AreaSpaceOverrideMode spOv_mode; - bool spOv_gravityPoint; - real_t spOv_gravityPointDistanceScale; - real_t spOv_gravityPointAttenuation; - Vector3 spOv_gravityVec; - real_t spOv_gravityMag; - real_t spOv_linearDump; - real_t spOv_angularDump; - int spOv_priority; - - bool isScratched; + bool monitorable = true; + + PhysicsServer3D::AreaSpaceOverrideMode spOv_mode = PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED; + bool spOv_gravityPoint = false; + real_t spOv_gravityPointDistanceScale = 0; + real_t spOv_gravityPointAttenuation = 1; + Vector3 spOv_gravityVec = Vector3(0, -1, 0); + real_t spOv_gravityMag = 10; + real_t spOv_linearDump = 0.1; + real_t spOv_angularDump = 1; + int spOv_priority = 0; + + bool isScratched = false; InOutEventCallback eventsCallbacks[2]; diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp index 2705c749a2..09a5f6f983 100644 --- a/modules/bullet/bullet_physics_server.cpp +++ b/modules/bullet/bullet_physics_server.cpp @@ -79,9 +79,7 @@ void BulletPhysicsServer3D::_bind_methods() { } BulletPhysicsServer3D::BulletPhysicsServer3D() : - PhysicsServer3D(), - active(true), - active_spaces_count(0) {} + PhysicsServer3D() {} BulletPhysicsServer3D::~BulletPhysicsServer3D() {} diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h index ea9c5e589e..558d1ce5f7 100644 --- a/modules/bullet/bullet_physics_server.h +++ b/modules/bullet/bullet_physics_server.h @@ -40,6 +40,7 @@ #include "shape_bullet.h" #include "soft_body_bullet.h" #include "space_bullet.h" + /** @author AndreaCatania */ @@ -49,8 +50,8 @@ class BulletPhysicsServer3D : public PhysicsServer3D { friend class BulletPhysicsDirectSpaceState; - bool active; - char active_spaces_count; + bool active = true; + char active_spaces_count = 0; Vector<SpaceBullet *> active_spaces; mutable RID_PtrOwner<SpaceBullet> space_owner; diff --git a/modules/bullet/collision_object_bullet.cpp b/modules/bullet/collision_object_bullet.cpp index 1b72c2f577..9ad74ad262 100644 --- a/modules/bullet/collision_object_bullet.cpp +++ b/modules/bullet/collision_object_bullet.cpp @@ -89,18 +89,7 @@ void CollisionObjectBullet::ShapeWrapper::claim_bt_shape(const btVector3 &body_s CollisionObjectBullet::CollisionObjectBullet(Type p_type) : RIDBullet(), - type(p_type), - instance_id(ObjectID()), - collisionLayer(0), - collisionMask(0), - collisionsEnabled(true), - m_isStatic(false), - ray_pickable(false), - bt_collision_object(nullptr), - body_scale(1., 1., 1.), - force_shape_reset(false), - space(nullptr), - isTransformChanged(false) {} + type(p_type) {} CollisionObjectBullet::~CollisionObjectBullet() { // Remove all overlapping, notify is not required since godot take care of it @@ -225,11 +214,6 @@ void CollisionObjectBullet::notify_transform_changed() { isTransformChanged = true; } -RigidCollisionObjectBullet::RigidCollisionObjectBullet(Type p_type) : - CollisionObjectBullet(p_type), - mainShape(nullptr) { -} - RigidCollisionObjectBullet::~RigidCollisionObjectBullet() { remove_all_shapes(true, true); if (mainShape && mainShape->isCompound()) { diff --git a/modules/bullet/collision_object_bullet.h b/modules/bullet/collision_object_bullet.h index 25176458a7..f1423a69e4 100644 --- a/modules/bullet/collision_object_bullet.h +++ b/modules/bullet/collision_object_bullet.h @@ -69,27 +69,22 @@ public: }; struct ShapeWrapper { - ShapeBullet *shape; - btCollisionShape *bt_shape; + ShapeBullet *shape = nullptr; + btCollisionShape *bt_shape = nullptr; btTransform transform; btVector3 scale; - bool active; + bool active = true; - ShapeWrapper() : - shape(nullptr), - bt_shape(nullptr), - active(true) {} + ShapeWrapper() {} ShapeWrapper(ShapeBullet *p_shape, const btTransform &p_transform, bool p_active) : shape(p_shape), - bt_shape(nullptr), active(p_active) { set_transform(p_transform); } ShapeWrapper(ShapeBullet *p_shape, const Transform &p_transform, bool p_active) : shape(p_shape), - bt_shape(nullptr), active(p_active) { set_transform(p_transform); } @@ -117,15 +112,15 @@ public: protected: Type type; ObjectID instance_id; - uint32_t collisionLayer; - uint32_t collisionMask; - bool collisionsEnabled; - bool m_isStatic; - bool ray_pickable; - btCollisionObject *bt_collision_object; - Vector3 body_scale; - bool force_shape_reset; - SpaceBullet *space; + uint32_t collisionLayer = 0; + uint32_t collisionMask = 0; + bool collisionsEnabled = true; + bool m_isStatic = false; + bool ray_pickable = false; + btCollisionObject *bt_collision_object = nullptr; + Vector3 body_scale = Vector3(1, 1, 1); + bool force_shape_reset = false; + SpaceBullet *space = nullptr; VSet<RID> exceptions; @@ -133,7 +128,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; + bool isTransformChanged = false; public: CollisionObjectBullet(Type p_type); @@ -218,11 +213,12 @@ public: class RigidCollisionObjectBullet : public CollisionObjectBullet, public ShapeOwnerBullet { protected: - btCollisionShape *mainShape; + btCollisionShape *mainShape = nullptr; Vector<ShapeWrapper> shapes; public: - RigidCollisionObjectBullet(Type p_type); + RigidCollisionObjectBullet(Type p_type) : + CollisionObjectBullet(p_type) {} ~RigidCollisionObjectBullet(); _FORCE_INLINE_ const Vector<ShapeWrapper> &get_shapes_wrappers() const { return shapes; } diff --git a/modules/bullet/constraint_bullet.cpp b/modules/bullet/constraint_bullet.cpp index 469b58521e..c47a23e75f 100644 --- a/modules/bullet/constraint_bullet.cpp +++ b/modules/bullet/constraint_bullet.cpp @@ -37,10 +37,7 @@ @author AndreaCatania */ -ConstraintBullet::ConstraintBullet() : - space(nullptr), - constraint(nullptr), - disabled_collisions_between_bodies(true) {} +ConstraintBullet::ConstraintBullet() {} void ConstraintBullet::setup(btTypedConstraint *p_constraint) { constraint = p_constraint; diff --git a/modules/bullet/constraint_bullet.h b/modules/bullet/constraint_bullet.h index 1946807bad..125940439f 100644 --- a/modules/bullet/constraint_bullet.h +++ b/modules/bullet/constraint_bullet.h @@ -47,9 +47,9 @@ class btTypedConstraint; class ConstraintBullet : public RIDBullet { protected: - SpaceBullet *space; - btTypedConstraint *constraint; - bool disabled_collisions_between_bodies; + SpaceBullet *space = nullptr; + btTypedConstraint *constraint = nullptr; + bool disabled_collisions_between_bodies = true; public: ConstraintBullet(); diff --git a/modules/bullet/godot_ray_world_algorithm.cpp b/modules/bullet/godot_ray_world_algorithm.cpp index 2ef277cf5b..2caa75c2a7 100644 --- a/modules/bullet/godot_ray_world_algorithm.cpp +++ b/modules/bullet/godot_ray_world_algorithm.cpp @@ -52,7 +52,6 @@ GodotRayWorldAlgorithm::GodotRayWorldAlgorithm(const btDiscreteDynamicsWorld *wo btActivatingCollisionAlgorithm(ci, body0Wrap, body1Wrap), m_world(world), m_manifoldPtr(mf), - m_ownManifold(false), m_isSwapped(isSwapped) {} GodotRayWorldAlgorithm::~GodotRayWorldAlgorithm() { diff --git a/modules/bullet/godot_ray_world_algorithm.h b/modules/bullet/godot_ray_world_algorithm.h index 2cdea6c133..ec7f68dc51 100644 --- a/modules/bullet/godot_ray_world_algorithm.h +++ b/modules/bullet/godot_ray_world_algorithm.h @@ -45,7 +45,7 @@ class GodotRayWorldAlgorithm : public btActivatingCollisionAlgorithm { const btDiscreteDynamicsWorld *m_world; btPersistentManifold *m_manifoldPtr; - bool m_ownManifold; + bool m_ownManifold = false; bool m_isSwapped; public: diff --git a/modules/bullet/godot_result_callbacks.h b/modules/bullet/godot_result_callbacks.h index 7e74a2b22e..8636ca8eb6 100644 --- a/modules/bullet/godot_result_callbacks.h +++ b/modules/bullet/godot_result_callbacks.h @@ -56,8 +56,8 @@ struct GodotFilterCallback : public btOverlapFilterCallback { /// It performs an additional check allow exclusions. struct GodotClosestRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { const Set<RID> *m_exclude; - bool m_pickRay; - int m_shapeId; + bool m_pickRay = false; + int m_shapeId = 0; bool collide_with_bodies; bool collide_with_areas; @@ -66,8 +66,6 @@ public: 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), collide_with_bodies(p_collide_with_bodies), collide_with_areas(p_collide_with_areas) {} @@ -88,13 +86,12 @@ public: PhysicsDirectSpaceState3D::ShapeResult *m_results; int m_resultMax; const Set<RID> *m_exclude; - int count; + int count = 0; GodotAllConvexResultCallback(PhysicsDirectSpaceState3D::ShapeResult *p_results, int p_resultMax, const Set<RID> *p_exclude) : m_results(p_results), m_resultMax(p_resultMax), - m_exclude(p_exclude), - count(0) {} + m_exclude(p_exclude) {} virtual bool needsCollision(btBroadphaseProxy *proxy0) const; @@ -117,7 +114,7 @@ public: struct GodotClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { public: const Set<RID> *m_exclude; - int m_shapeId; + int m_shapeId = 0; bool collide_with_bodies; bool collide_with_areas; @@ -125,7 +122,6 @@ public: 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_shapeId(0), collide_with_bodies(p_collide_with_bodies), collide_with_areas(p_collide_with_areas) {} @@ -140,7 +136,7 @@ public: PhysicsDirectSpaceState3D::ShapeResult *m_results; int m_resultMax; const Set<RID> *m_exclude; - int m_count; + int m_count = 0; bool collide_with_bodies; bool collide_with_areas; @@ -150,7 +146,6 @@ public: m_results(p_results), m_resultMax(p_resultMax), m_exclude(p_exclude), - m_count(0), collide_with_bodies(p_collide_with_bodies), collide_with_areas(p_collide_with_areas) {} @@ -166,7 +161,7 @@ public: Vector3 *m_results; int m_resultMax; const Set<RID> *m_exclude; - int m_count; + int m_count = 0; bool collide_with_bodies; bool collide_with_areas; @@ -176,7 +171,6 @@ public: m_results(p_results), m_resultMax(p_resultMax), m_exclude(p_exclude), - m_count(0), collide_with_bodies(p_collide_with_bodies), collide_with_areas(p_collide_with_areas) {} @@ -190,8 +184,8 @@ public: const btCollisionObject *m_self_object; PhysicsDirectSpaceState3D::ShapeRestInfo *m_result; const Set<RID> *m_exclude; - bool m_collided; - real_t m_min_distance; + bool m_collided = false; + real_t m_min_distance = 0; const btCollisionObject *m_rest_info_collision_object; btVector3 m_rest_info_bt_point; bool collide_with_bodies; @@ -201,8 +195,6 @@ public: m_self_object(p_self_object), m_result(p_result), m_exclude(p_exclude), - m_collided(false), - m_min_distance(0), collide_with_bodies(p_collide_with_bodies), collide_with_areas(p_collide_with_areas) {} @@ -214,13 +206,11 @@ public: struct GodotDeepPenetrationContactResultCallback : public btManifoldResult { btVector3 m_pointNormalWorld; btVector3 m_pointWorld; - btScalar m_penetration_distance; - int m_other_compound_shape_index; + btScalar m_penetration_distance = 0; + int m_other_compound_shape_index = 0; GodotDeepPenetrationContactResultCallback(const btCollisionObjectWrapper *body0Wrap, const btCollisionObjectWrapper *body1Wrap) : - btManifoldResult(body0Wrap, body1Wrap), - m_penetration_distance(0), - m_other_compound_shape_index(0) {} + btManifoldResult(body0Wrap, body1Wrap) {} void reset() { m_penetration_distance = 0; diff --git a/modules/bullet/hinge_joint_bullet.cpp b/modules/bullet/hinge_joint_bullet.cpp index 4bea9f87c0..e7f3d75c10 100644 --- a/modules/bullet/hinge_joint_bullet.cpp +++ b/modules/bullet/hinge_joint_bullet.cpp @@ -162,7 +162,8 @@ void HingeJointBullet::set_flag(PhysicsServer3D::HingeJointFlag p_flag, bool p_v case PhysicsServer3D::HINGE_JOINT_FLAG_ENABLE_MOTOR: hingeConstraint->enableMotor(p_value); break; - case PhysicsServer3D::HINGE_JOINT_FLAG_MAX: break; // Can't happen, but silences warning + case PhysicsServer3D::HINGE_JOINT_FLAG_MAX: + break; // Can't happen, but silences warning } } diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index e393396713..7a244b8c32 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -256,25 +256,7 @@ void RigidBodyBullet::KinematicUtilities::just_delete_shapes(int new_size) { } RigidBodyBullet::RigidBodyBullet() : - RigidCollisionObjectBullet(CollisionObjectBullet::TYPE_RIGID_BODY), - kinematic_utilities(nullptr), - locked_axis(0), - mass(1), - gravity_scale(1), - linearDamp(0), - angularDamp(0), - can_sleep(true), - omit_forces_integration(false), - can_integrate_forces(false), - maxCollisionsDetection(0), - collisionsCount(0), - prev_collision_count(0), - maxAreasWhereIam(10), - areaWhereIamCount(0), - countGravityPointSpaces(0), - isScratchedSpaceOverrideModificator(false), - previousActiveState(true), - force_integration_callback(nullptr) { + RigidCollisionObjectBullet(CollisionObjectBullet::TYPE_RIGID_BODY) { godotMotionState = bulletnew(GodotMotionState(this)); diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h index 420b5cc443..f94dea8036 100644 --- a/modules/bullet/rigid_body_bullet.h +++ b/modules/bullet/rigid_body_bullet.h @@ -162,11 +162,10 @@ public: /// Used to hold shapes struct KinematicShape { - class btConvexShape *shape; + class btConvexShape *shape = nullptr; btTransform transform; - KinematicShape() : - shape(nullptr) {} + KinematicShape() {} bool is_active() const { return shape; } }; @@ -190,19 +189,19 @@ private: friend class BulletPhysicsDirectBodyState3D; // This is required only for Kinematic movement - KinematicUtilities *kinematic_utilities; + KinematicUtilities *kinematic_utilities = nullptr; PhysicsServer3D::BodyMode mode; GodotMotionState *godotMotionState; btRigidBody *btBody; - uint16_t locked_axis; - real_t mass; - real_t gravity_scale; - real_t linearDamp; - real_t angularDamp; - bool can_sleep; - bool omit_forces_integration; - bool can_integrate_forces; + uint16_t locked_axis = 0; + real_t mass = 1; + real_t gravity_scale = 1; + real_t linearDamp = 0; + real_t angularDamp = 0; + bool can_sleep = true; + bool omit_forces_integration = false; + bool can_integrate_forces = false; Vector<CollisionData> collisions; Vector<RigidBodyBullet *> collision_traces_1; @@ -211,21 +210,21 @@ private: Vector<RigidBodyBullet *> *curr_collision_traces; // these parameters are used to avoid vector resize - int maxCollisionsDetection; - int collisionsCount; - int prev_collision_count; + int maxCollisionsDetection = 0; + int collisionsCount = 0; + int prev_collision_count = 0; Vector<AreaBullet *> areasWhereIam; // these parameters are used to avoid vector resize - int maxAreasWhereIam; - int areaWhereIamCount; + int maxAreasWhereIam = 10; + int areaWhereIamCount = 0; // Used to know if the area is used as gravity point - int countGravityPointSpaces; - bool isScratchedSpaceOverrideModificator; + int countGravityPointSpaces = 0; + bool isScratchedSpaceOverrideModificator = false; - bool previousActiveState; // Last check state + bool previousActiveState = true; // Last check state - ForceIntegrationCallback *force_integration_callback; + ForceIntegrationCallback *force_integration_callback = nullptr; public: RigidBodyBullet(); diff --git a/modules/bullet/shape_bullet.cpp b/modules/bullet/shape_bullet.cpp index 8ac26a0fdb..e3b869ad8d 100644 --- a/modules/bullet/shape_bullet.cpp +++ b/modules/bullet/shape_bullet.cpp @@ -46,8 +46,7 @@ @author AndreaCatania */ -ShapeBullet::ShapeBullet() : - margin(0.04) {} +ShapeBullet::ShapeBullet() {} ShapeBullet::~ShapeBullet() {} @@ -81,7 +80,8 @@ void ShapeBullet::add_owner(ShapeOwnerBullet *p_owner) { void ShapeBullet::remove_owner(ShapeOwnerBullet *p_owner, bool p_permanentlyFromThisBody) { Map<ShapeOwnerBullet *, int>::Element *E = owners.find(p_owner); - if (!E) return; + if (!E) + return; E->get()--; if (p_permanentlyFromThisBody || 0 >= E->get()) { owners.erase(E); @@ -361,8 +361,7 @@ btCollisionShape *ConvexPolygonShapeBullet::create_bt_shape(const btVector3 &p_i /* Concave polygon */ ConcavePolygonShapeBullet::ConcavePolygonShapeBullet() : - ShapeBullet(), - meshShape(nullptr) {} + ShapeBullet() {} ConcavePolygonShapeBullet::~ConcavePolygonShapeBullet() { if (meshShape) { @@ -562,9 +561,7 @@ btCollisionShape *HeightMapShapeBullet::create_bt_shape(const btVector3 &p_impli /* Ray shape */ RayShapeBullet::RayShapeBullet() : - ShapeBullet(), - length(1), - slips_on_slope(false) {} + ShapeBullet() {} void RayShapeBullet::set_data(const Variant &p_data) { diff --git a/modules/bullet/shape_bullet.h b/modules/bullet/shape_bullet.h index 0dbc616fe5..88b62b6dc9 100644 --- a/modules/bullet/shape_bullet.h +++ b/modules/bullet/shape_bullet.h @@ -52,7 +52,7 @@ class btBvhTriangleMeshShape; class ShapeBullet : public RIDBullet { Map<ShapeOwnerBullet *, int> owners; - real_t margin; + real_t margin = 0.04; protected: /// return self @@ -200,7 +200,7 @@ private: }; class ConcavePolygonShapeBullet : public ShapeBullet { - class btBvhTriangleMeshShape *meshShape; + class btBvhTriangleMeshShape *meshShape = nullptr; public: Vector<Vector3> faces; @@ -240,8 +240,8 @@ private: class RayShapeBullet : public ShapeBullet { public: - real_t length; - bool slips_on_slope; + real_t length = 1; + bool slips_on_slope = false; RayShapeBullet(); diff --git a/modules/bullet/slider_joint_bullet.cpp b/modules/bullet/slider_joint_bullet.cpp index f193daef39..de47c91e5d 100644 --- a/modules/bullet/slider_joint_bullet.cpp +++ b/modules/bullet/slider_joint_bullet.cpp @@ -344,56 +344,123 @@ real_t SliderJointBullet::getLinearPos() { void SliderJointBullet::set_param(PhysicsServer3D::SliderJointParam p_param, real_t p_value) { switch (p_param) { - case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER: setUpperLinLimit(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER: setLowerLinLimit(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS: setSoftnessLimLin(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION: setRestitutionLimLin(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING: setDampingLimLin(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS: setSoftnessDirLin(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION: setRestitutionDirLin(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING: setDampingDirLin(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS: setSoftnessOrthoLin(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION: setRestitutionOrthoLin(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING: setDampingOrthoLin(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER: setUpperAngLimit(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER: setLowerAngLimit(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS: setSoftnessLimAng(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION: setRestitutionLimAng(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING: setDampingLimAng(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS: setSoftnessDirAng(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION: setRestitutionDirAng(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING: setDampingDirAng(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS: setSoftnessOrthoAng(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION: setRestitutionOrthoAng(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING: setDampingOrthoAng(p_value); break; - case PhysicsServer3D::SLIDER_JOINT_MAX: break; // Can't happen, but silences warning + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER: + setUpperLinLimit(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER: + setLowerLinLimit(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS: + setSoftnessLimLin(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION: + setRestitutionLimLin(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING: + setDampingLimLin(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS: + setSoftnessDirLin(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION: + setRestitutionDirLin(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING: + setDampingDirLin(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS: + setSoftnessOrthoLin(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION: + setRestitutionOrthoLin(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING: + setDampingOrthoLin(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER: + setUpperAngLimit(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER: + setLowerAngLimit(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS: + setSoftnessLimAng(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION: + setRestitutionLimAng(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING: + setDampingLimAng(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS: + setSoftnessDirAng(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION: + setRestitutionDirAng(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING: + setDampingDirAng(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS: + setSoftnessOrthoAng(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION: + setRestitutionOrthoAng(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING: + setDampingOrthoAng(p_value); + break; + case PhysicsServer3D::SLIDER_JOINT_MAX: + break; // Can't happen, but silences warning } } real_t SliderJointBullet::get_param(PhysicsServer3D::SliderJointParam p_param) const { switch (p_param) { - case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER: return getUpperLinLimit(); - case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER: return getLowerLinLimit(); - case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS: return getSoftnessLimLin(); - case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION: return getRestitutionLimLin(); - case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING: return getDampingLimLin(); - case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS: return getSoftnessDirLin(); - case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION: return getRestitutionDirLin(); - case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING: return getDampingDirLin(); - case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS: return getSoftnessOrthoLin(); - case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION: return getRestitutionOrthoLin(); - case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING: return getDampingOrthoLin(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER: return getUpperAngLimit(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER: return getLowerAngLimit(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS: return getSoftnessLimAng(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION: return getRestitutionLimAng(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING: return getDampingLimAng(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS: return getSoftnessDirAng(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION: return getRestitutionDirAng(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING: return getDampingDirAng(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS: return getSoftnessOrthoAng(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION: return getRestitutionOrthoAng(); - case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING: return getDampingOrthoAng(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER: + return getUpperLinLimit(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER: + return getLowerLinLimit(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS: + return getSoftnessLimLin(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION: + return getRestitutionLimLin(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING: + return getDampingLimLin(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS: + return getSoftnessDirLin(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION: + return getRestitutionDirLin(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING: + return getDampingDirLin(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS: + return getSoftnessOrthoLin(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION: + return getRestitutionOrthoLin(); + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING: + return getDampingOrthoLin(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER: + return getUpperAngLimit(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER: + return getLowerAngLimit(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS: + return getSoftnessLimAng(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION: + return getRestitutionLimAng(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING: + return getDampingLimAng(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS: + return getSoftnessDirAng(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION: + return getRestitutionDirAng(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING: + return getDampingDirAng(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS: + return getSoftnessOrthoAng(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION: + return getRestitutionOrthoAng(); + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING: + return getDampingOrthoAng(); default: return 0; } diff --git a/modules/bullet/soft_body_bullet.cpp b/modules/bullet/soft_body_bullet.cpp index 236bdc7c8a..bbaa23e064 100644 --- a/modules/bullet/soft_body_bullet.cpp +++ b/modules/bullet/soft_body_bullet.cpp @@ -36,18 +36,7 @@ #include "space_bullet.h" SoftBodyBullet::SoftBodyBullet() : - CollisionObjectBullet(CollisionObjectBullet::TYPE_SOFT_BODY), - bt_soft_body(nullptr), - isScratched(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.) {} + CollisionObjectBullet(CollisionObjectBullet::TYPE_SOFT_BODY) {} SoftBodyBullet::~SoftBodyBullet() { } diff --git a/modules/bullet/soft_body_bullet.h b/modules/bullet/soft_body_bullet.h index 3c6871e0d6..d28af7d61d 100644 --- a/modules/bullet/soft_body_bullet.h +++ b/modules/bullet/soft_body_bullet.h @@ -58,22 +58,22 @@ class SoftBodyBullet : public CollisionObjectBullet { private: - btSoftBody *bt_soft_body; + btSoftBody *bt_soft_body = nullptr; Vector<Vector<int>> indices_table; btSoftBody::Material *mat0; // This is just a copy of pointer managed by btSoftBody - bool isScratched; + bool isScratched = false; Ref<Mesh> soft_mesh; - int simulation_precision; - 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] + int simulation_precision = 5; + real_t total_mass = 1.; + real_t linear_stiffness = 0.5; // [0,1] + real_t areaAngular_stiffness = 0.5; // [0,1] + real_t volume_stiffness = 0.5; // [0,1] + real_t pressure_coefficient = 0.; // [-inf,+inf] + real_t pose_matching_coefficient = 0.; // [0,1] + real_t damping_coefficient = 0.01; // [0,1] + real_t drag_coefficient = 0.; // [0,1] Vector<int> pinned_nodes; // Other property to add diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index d49e635fd5..aff203d7b4 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -205,7 +205,7 @@ bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transf /// 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 p_collide_with_bodies, bool p_collide_with_areas) { if (p_result_max <= 0) - return 0; + return false; ShapeBullet *shape = space->get_physics_server()->get_shape_owner()->getornull(p_shape); @@ -213,7 +213,7 @@ bool BulletPhysicsDirectSpaceState::collide_shape(RID p_shape, const Transform & if (!btShape->isConvex()) { bulletdelete(btShape); ERR_PRINT("The shape is not a convex shape, then is not supported: shape type: " + itos(shape->get_type())); - return 0; + return false; } btConvexShape *btConvex = static_cast<btConvexShape *>(btShape); @@ -245,7 +245,7 @@ bool BulletPhysicsDirectSpaceState::rest_info(RID p_shape, const Transform &p_sh if (!btShape->isConvex()) { bulletdelete(btShape); ERR_PRINT("The shape is not a convex shape, then is not supported: shape type: " + itos(shape->get_type())); - return 0; + return false; } btConvexShape *btConvex = static_cast<btConvexShape *>(btShape); @@ -331,22 +331,7 @@ Vector3 BulletPhysicsDirectSpaceState::get_closest_point_to_object_volume(RID p_ } } -SpaceBullet::SpaceBullet() : - broadphase(nullptr), - collisionConfiguration(nullptr), - dispatcher(nullptr), - solver(nullptr), - dynamicsWorld(nullptr), - soft_body_world_info(nullptr), - ghostPairCallback(nullptr), - godotFilterCallback(nullptr), - gravityDirection(0, -1, 0), - gravityMagnitude(10), - linear_damp(0.0), - angular_damp(0.0), - contactDebugCount(0), - delta_time(0.) { - +SpaceBullet::SpaceBullet() { create_empty_world(GLOBAL_DEF("physics/3d/active_soft_world", true)); direct_access = memnew(BulletPhysicsDirectSpaceState(this)); } diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h index 3d4a2aeceb..6fe4571bba 100644 --- a/modules/bullet/space_bullet.h +++ b/modules/bullet/space_bullet.h @@ -92,30 +92,30 @@ class SpaceBullet : public RIDBullet { friend void onBulletTickCallback(btDynamicsWorld *world, btScalar timeStep); friend class BulletPhysicsDirectSpaceState; - btBroadphaseInterface *broadphase; - btDefaultCollisionConfiguration *collisionConfiguration; - btCollisionDispatcher *dispatcher; - btConstraintSolver *solver; - btDiscreteDynamicsWorld *dynamicsWorld; - btSoftBodyWorldInfo *soft_body_world_info; - btGhostPairCallback *ghostPairCallback; - GodotFilterCallback *godotFilterCallback; + btBroadphaseInterface *broadphase = nullptr; + btDefaultCollisionConfiguration *collisionConfiguration = nullptr; + btCollisionDispatcher *dispatcher = nullptr; + btConstraintSolver *solver = nullptr; + btDiscreteDynamicsWorld *dynamicsWorld = nullptr; + btSoftBodyWorldInfo *soft_body_world_info = nullptr; + btGhostPairCallback *ghostPairCallback = nullptr; + GodotFilterCallback *godotFilterCallback = nullptr; btGjkEpaPenetrationDepthSolver *gjk_epa_pen_solver; btVoronoiSimplexSolver *gjk_simplex_solver; BulletPhysicsDirectSpaceState *direct_access; - Vector3 gravityDirection; - real_t gravityMagnitude; + Vector3 gravityDirection = Vector3(0, -1, 0); + real_t gravityMagnitude = 10; - real_t linear_damp; - real_t angular_damp; + real_t linear_damp = 0.0; + real_t angular_damp = 0.0; Vector<AreaBullet *> areas; Vector<Vector3> contactDebug; - int contactDebugCount; - real_t delta_time; + int contactDebugCount = 0; + real_t delta_time = 0.; public: SpaceBullet(); @@ -170,7 +170,8 @@ public: contactDebugCount = 0; } _FORCE_INLINE_ void add_debug_contact(const Vector3 &p_contact) { - if (contactDebugCount < contactDebug.size()) contactDebug.write[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; } @@ -193,22 +194,15 @@ private: void check_body_collision(); struct RecoverResult { - bool hasPenetration; - btVector3 normal; - btVector3 pointWorld; - btScalar penetration_distance; // Negative mean penetration - int other_compound_shape_index; - const btCollisionObject *other_collision_object; - int local_shape_most_recovered; - - RecoverResult() : - hasPenetration(false), - normal(0, 0, 0), - pointWorld(0, 0, 0), - penetration_distance(1e20), - other_compound_shape_index(0), - other_collision_object(nullptr), - local_shape_most_recovered(0) {} + bool hasPenetration = false; + btVector3 normal = btVector3(0, 0, 0); + btVector3 pointWorld = btVector3(0, 0, 0); + btScalar penetration_distance = 1e20; // Negative mean penetration + int other_compound_shape_index = 0; + const btCollisionObject *other_collision_object = nullptr; + int local_shape_most_recovered = 0; + + RecoverResult() {} }; bool recover_from_penetration(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = nullptr); diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp index 6714db76bb..a6951a9320 100644 --- a/modules/csg/csg.cpp +++ b/modules/csg/csg.cpp @@ -138,10 +138,12 @@ inline bool is_point_in_triangle(const Vector3 &p_point, const Vector3 p_vertice lambda[2] = p_vertices[0].cross(p_vertices[1]).dot(p_point) / det; // Point is in the plane if all lambdas sum to 1. - if (!Math::is_equal_approx(lambda[0] + lambda[1] + lambda[2], 1)) return false; + if (!Math::is_equal_approx(lambda[0] + lambda[1] + lambda[2], 1)) + return false; // Point is inside the triangle if all lambdas are positive. - if (lambda[0] < 0 || lambda[1] < 0 || lambda[2] < 0) return false; + if (lambda[0] < 0 || lambda[1] < 0 || lambda[2] < 0) + return false; return true; } @@ -524,7 +526,8 @@ void CSGBrushOperation::MeshMerge::_add_distance(List<real_t> &r_intersectionsA, // Check if distance exists. for (const List<real_t>::Element *E = intersections.front(); E; E = E->next()) - if (Math::abs(**E - p_distance) < vertex_snap) return; + if (Math::abs(**E - p_distance) < vertex_snap) + return; intersections.push_back(p_distance); } @@ -790,7 +793,8 @@ int CSGBrushOperation::Build2DFaces::_add_vertex(const Vertex2D &p_vertex) { // Check if vertex exists. int vertex_id = _get_point_idx(p_vertex.point); - if (vertex_id != -1) return vertex_id; + if (vertex_id != -1) + return vertex_id; vertices.push_back(p_vertex); return vertices.size() - 1; @@ -816,7 +820,8 @@ void CSGBrushOperation::Build2DFaces::_add_vertex_idx_sorted(Vector<int> &r_vert // Sort along the axis with the greatest difference. int axis = 0; - if (Math::abs(new_point.x - first_point.x) < Math::abs(new_point.y - first_point.y)) axis = 1; + if (Math::abs(new_point.x - first_point.x) < Math::abs(new_point.y - first_point.y)) + axis = 1; // Add it to the beginning or the end appropriately. if (new_point[axis] < first_point[axis]) @@ -834,7 +839,8 @@ void CSGBrushOperation::Build2DFaces::_add_vertex_idx_sorted(Vector<int> &r_vert // Determine axis being sorted against i.e. the axis with the greatest difference. int axis = 0; - if (Math::abs(last_point.x - first_point.x) < Math::abs(last_point.y - first_point.y)) axis = 1; + if (Math::abs(last_point.x - first_point.x) < Math::abs(last_point.y - first_point.y)) + axis = 1; // Insert the point at the appropriate index. for (int insert_idx = 0; insert_idx < r_vertex_indices.size(); ++insert_idx) { @@ -853,7 +859,8 @@ void CSGBrushOperation::Build2DFaces::_add_vertex_idx_sorted(Vector<int> &r_vert void CSGBrushOperation::Build2DFaces::_merge_faces(const Vector<int> &p_segment_indices) { int segments = p_segment_indices.size() - 1; - if (segments < 2) return; + if (segments < 2) + return; // Faces around an inner vertex are merged by moving the inner vertex to the first vertex. for (int sorted_idx = 1; sorted_idx < segments; ++sorted_idx) { @@ -893,7 +900,8 @@ void CSGBrushOperation::Build2DFaces::_merge_faces(const Vector<int> &p_segment_ // Skip flattened faces. if (outer_edge_idx[0] == p_segment_indices[closest_idx] || - outer_edge_idx[1] == p_segment_indices[closest_idx]) continue; + outer_edge_idx[1] == p_segment_indices[closest_idx]) + continue; //Don't create degenerate triangles. Vector2 edge1[2] = { @@ -924,7 +932,8 @@ void CSGBrushOperation::Build2DFaces::_merge_faces(const Vector<int> &p_segment_ for (int i = 0; i < merge_faces_idx.size(); ++i) faces.remove(merge_faces_idx[i]); - if (degenerate_points.size() == 0) continue; + if (degenerate_points.size() == 0) + continue; // Split faces using degenerate points. for (int face_idx = 0; face_idx < faces.size(); ++face_idx) { @@ -954,7 +963,8 @@ void CSGBrushOperation::Build2DFaces::_merge_faces(const Vector<int> &p_segment_ break; } } - if (existing) continue; + if (existing) + continue; // Check if point is on an each edge. for (int face_edge_idx = 0; face_edge_idx < 3; ++face_edge_idx) { @@ -1043,10 +1053,12 @@ void CSGBrushOperation::Build2DFaces::_find_edge_intersections(const Vector2 p_s // Check if intersection point is an edge point. if ((intersection_point - edge_points[0]).length_squared() < vertex_snap2 || - (intersection_point - edge_points[1]).length_squared() < vertex_snap2) continue; + (intersection_point - edge_points[1]).length_squared() < vertex_snap2) + continue; // Check if edge exists, by checking if the intersecting segment is parallel to the edge. - if (are_segements_parallel(p_segment_points, edge_points, vertex_snap2)) continue; + if (are_segements_parallel(p_segment_points, edge_points, vertex_snap2)) + continue; // Add the intersection point as a new vertex. Vertex2D new_vertex; @@ -1384,7 +1396,8 @@ void CSGBrushOperation::update_faces(const CSGBrush &p_brush_a, const int p_face p_collection.build2DFacesB[p_face_idx_b] = Build2DFaces(); has_degenerate = true; } - if (has_degenerate) return; + if (has_degenerate) + return; // Ensure B has points either side of or in the plane of A. int in_plane_count = 0, over_count = 0, under_count = 0; @@ -1400,7 +1413,8 @@ void CSGBrushOperation::update_faces(const CSGBrush &p_brush_a, const int p_face under_count++; } // If all points under or over the plane, there is no intesection. - if (over_count == 3 || under_count == 3) return; + if (over_count == 3 || under_count == 3) + return; // Ensure A has points either side of or in the plane of B. in_plane_count = 0; @@ -1418,7 +1432,8 @@ void CSGBrushOperation::update_faces(const CSGBrush &p_brush_a, const int p_face under_count++; } // If all points under or over the plane, there is no intesection. - if (over_count == 3 || under_count == 3) return; + if (over_count == 3 || under_count == 3) + return; // Check for intersection using the SAT theorem. { diff --git a/modules/csg/csg_gizmos.cpp b/modules/csg/csg_gizmos.cpp index fa176cb94e..a859ef80a6 100644 --- a/modules/csg/csg_gizmos.cpp +++ b/modules/csg/csg_gizmos.cpp @@ -90,9 +90,12 @@ Variant CSGShape3DGizmoPlugin::get_handle_value(EditorNode3DGizmo *p_gizmo, int CSGBox3D *s = Object::cast_to<CSGBox3D>(cs); switch (p_idx) { - case 0: return s->get_width(); - case 1: return s->get_height(); - case 2: return s->get_depth(); + case 0: + return s->get_width(); + case 1: + return s->get_height(); + case 2: + return s->get_depth(); } } @@ -157,9 +160,15 @@ void CSGShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Ca d = 0.001; switch (p_idx) { - case 0: s->set_width(d * 2); break; - case 1: s->set_height(d * 2); break; - case 2: s->set_depth(d * 2); 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; } } @@ -229,9 +238,15 @@ void CSGShape3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, CSGBox3D *s = Object::cast_to<CSGBox3D>(cs); if (p_cancel) { switch (p_idx) { - case 0: s->set_width(p_restore); break; - case 1: s->set_height(p_restore); break; - case 2: s->set_depth(p_restore); break; + case 0: + s->set_width(p_restore); + break; + case 1: + s->set_height(p_restore); + break; + case 2: + s->set_depth(p_restore); + break; } return; } @@ -241,9 +256,15 @@ void CSGShape3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, static const char *method[3] = { "set_width", "set_height", "set_depth" }; float current = 0; switch (p_idx) { - case 0: current = s->get_width(); break; - case 1: current = s->get_height(); break; - case 2: current = s->get_depth(); break; + case 0: + current = s->get_width(); + break; + case 1: + current = s->get_height(); + break; + case 2: + current = s->get_depth(); + break; } ur->add_do_method(s, method[p_idx], current); diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 550a919d0d..a5b664eeab 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -185,9 +185,15 @@ CSGBrush *CSGShape3D::_get_brush() { CSGBrushOperation bop; switch (child->get_operation()) { - case CSGShape3D::OPERATION_UNION: bop.merge_brushes(CSGBrushOperation::OPERATION_UNION, *n, *nn2, *nn, snap); break; - case CSGShape3D::OPERATION_INTERSECTION: bop.merge_brushes(CSGBrushOperation::OPERATION_INTERSECTION, *n, *nn2, *nn, snap); break; - case CSGShape3D::OPERATION_SUBTRACTION: bop.merge_brushes(CSGBrushOperation::OPERATION_SUBSTRACTION, *n, *nn2, *nn, snap); break; + case CSGShape3D::OPERATION_UNION: + bop.merge_brushes(CSGBrushOperation::OPERATION_UNION, *n, *nn2, *nn, snap); + break; + case CSGShape3D::OPERATION_INTERSECTION: + bop.merge_brushes(CSGBrushOperation::OPERATION_INTERSECTION, *n, *nn2, *nn, snap); + break; + case CSGShape3D::OPERATION_SUBTRACTION: + bop.merge_brushes(CSGBrushOperation::OPERATION_SUBSTRACTION, *n, *nn2, *nn, snap); + break; } memdelete(n); memdelete(nn2); @@ -340,21 +346,31 @@ void CSGShape3D::_update_shape() { } } - //fill arrays - Vector<Vector3> physics_faces; - bool fill_physics_faces = false; + // Update collision faces. if (root_collision_shape.is_valid()) { + + Vector<Vector3> physics_faces; physics_faces.resize(n->faces.size() * 3); - fill_physics_faces = true; - } + Vector3 *physicsw = physics_faces.ptrw(); - { - Vector3 *physicsw; + for (int i = 0; i < n->faces.size(); i++) { + + int order[3] = { 0, 1, 2 }; + + if (n->faces[i].invert) { + SWAP(order[1], order[2]); + } - if (fill_physics_faces) { - physicsw = physics_faces.ptrw(); + physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]]; + physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]]; + physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]]; } + root_collision_shape->set_faces(physics_faces); + } + + //fill arrays + { for (int i = 0; i < n->faces.size(); i++) { int order[3] = { 0, 1, 2 }; @@ -363,12 +379,6 @@ void CSGShape3D::_update_shape() { SWAP(order[1], order[2]); } - if (fill_physics_faces) { - physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]]; - physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]]; - physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]]; - } - int mat = n->faces[i].material; ERR_CONTINUE(mat < -1 || mat >= face_count.size()); int idx = mat == -1 ? face_count.size() - 1 : mat; @@ -452,10 +462,6 @@ void CSGShape3D::_update_shape() { root_mesh->surface_set_material(idx, surfaces[i].material); } - if (root_collision_shape.is_valid()) { - root_collision_shape->set_faces(physics_faces); - } - set_base(root_mesh->get_rid()); } AABB CSGShape3D::get_aabb() const { @@ -1783,11 +1789,15 @@ CSGBrush *CSGPolygon3D::_build_brush() { 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; + 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; @@ -1826,8 +1836,12 @@ CSGBrush *CSGPolygon3D::_build_brush() { int face_count = 0; switch (mode) { - case MODE_DEPTH: face_count = triangles.size() * 2 / 3 + (final_polygon.size()) * 2; break; - case MODE_SPIN: face_count = (spin_degrees < 360 ? triangles.size() * 2 / 3 : 0) + (final_polygon.size()) * 2 * spin_sides; break; + case MODE_DEPTH: + face_count = triangles.size() * 2 / 3 + (final_polygon.size()) * 2; + break; + case MODE_SPIN: + face_count = (spin_degrees < 360 ? triangles.size() * 2 / 3 : 0) + (final_polygon.size()) * 2 * spin_sides; + break; case MODE_PATH: { float bl = curve->get_baked_length(); int splits = MAX(2, Math::ceil(bl / path_interval)); diff --git a/modules/denoise/SCsub b/modules/denoise/SCsub new file mode 100644 index 0000000000..0fa65c6296 --- /dev/null +++ b/modules/denoise/SCsub @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +import resource_to_cpp +from platform_methods import run_in_subprocess + +Import("env") +Import("env_modules") + +env_oidn = env_modules.Clone() + +# Thirdparty source files +thirdparty_dir = "#thirdparty/oidn/" +thirdparty_sources = [ + "core/api.cpp", + "core/device.cpp", + "core/filter.cpp", + "core/network.cpp", + "core/autoencoder.cpp", + "core/transfer_function.cpp", + "weights/rtlightmap_hdr.gen.cpp", + "mkl-dnn/src/common/batch_normalization.cpp", + "mkl-dnn/src/common/concat.cpp", + "mkl-dnn/src/common/convolution.cpp", + "mkl-dnn/src/common/convolution_pd.cpp", + "mkl-dnn/src/common/deconvolution.cpp", + "mkl-dnn/src/common/eltwise.cpp", + "mkl-dnn/src/common/engine.cpp", + "mkl-dnn/src/common/inner_product.cpp", + "mkl-dnn/src/common/inner_product_pd.cpp", + "mkl-dnn/src/common/lrn.cpp", + "mkl-dnn/src/common/memory.cpp", + "mkl-dnn/src/common/memory_desc_wrapper.cpp", + "mkl-dnn/src/common/mkldnn_debug.cpp", + "mkl-dnn/src/common/mkldnn_debug_autogenerated.cpp", + "mkl-dnn/src/common/pooling.cpp", + "mkl-dnn/src/common/primitive.cpp", + "mkl-dnn/src/common/primitive_attr.cpp", + "mkl-dnn/src/common/primitive_desc.cpp", + "mkl-dnn/src/common/primitive_exec_types.cpp", + "mkl-dnn/src/common/primitive_iterator.cpp", + "mkl-dnn/src/common/query.cpp", + "mkl-dnn/src/common/reorder.cpp", + "mkl-dnn/src/common/rnn.cpp", + "mkl-dnn/src/common/scratchpad.cpp", + "mkl-dnn/src/common/shuffle.cpp", + "mkl-dnn/src/common/softmax.cpp", + "mkl-dnn/src/common/stream.cpp", + "mkl-dnn/src/common/sum.cpp", + "mkl-dnn/src/common/utils.cpp", + "mkl-dnn/src/common/verbose.cpp", + "mkl-dnn/src/cpu/cpu_barrier.cpp", + "mkl-dnn/src/cpu/cpu_concat.cpp", + "mkl-dnn/src/cpu/cpu_engine.cpp", + "mkl-dnn/src/cpu/cpu_memory.cpp", + "mkl-dnn/src/cpu/cpu_reducer.cpp", + "mkl-dnn/src/cpu/cpu_reorder.cpp", + "mkl-dnn/src/cpu/cpu_sum.cpp", + "mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.cpp", + "mkl-dnn/src/cpu/jit_avx2_convolution.cpp", + "mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.cpp", + "mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.cpp", + "mkl-dnn/src/cpu/jit_avx512_common_convolution.cpp", + "mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.cpp", + "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp", + "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.cpp", + "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.cpp", + "mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.cpp", + "mkl-dnn/src/cpu/jit_sse42_convolution.cpp", + "mkl-dnn/src/cpu/jit_transpose_src_utils.cpp", + "mkl-dnn/src/cpu/jit_uni_eltwise.cpp", + "mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.cpp", + "mkl-dnn/src/cpu/jit_uni_pooling.cpp", + "mkl-dnn/src/cpu/jit_uni_reorder.cpp", + "mkl-dnn/src/cpu/jit_uni_reorder_utils.cpp", + "mkl-dnn/src/cpu/jit_utils/jit_utils.cpp", + "mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.c", + "common/platform.cpp", + "common/thread.cpp", + "common/tensor.cpp", +] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +thirdparty_include_dirs = [ + "", + "include", + "mkl-dnn/include", + "mkl-dnn/src", + "mkl-dnn/src/common", + "mkl-dnn/src/cpu/xbyak", + "mkl-dnn/src/cpu", +] +thirdparty_include_dirs = [thirdparty_dir + file for file in thirdparty_include_dirs] + + +env_oidn.Prepend(CPPPATH=thirdparty_include_dirs) +env_oidn.Append( + CPPDEFINES=[ + "MKLDNN_THR=MKLDNN_THR_SEQ", + "OIDN_STATIC_LIB", + "__STDC_CONSTANT_MACROS", + "__STDC_LIMIT_MACROS", + "DISABLE_VERBOSE", + "MKLDNN_ENABLE_CONCURRENT_EXEC", + "NDEBUG", + ] +) + +env_thirdparty = env_oidn.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + +weights_in_path = thirdparty_dir + "weights/rtlightmap_hdr.tza" +weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp" + +env_thirdparty.Depends(weights_out_path, weights_in_path) +env_thirdparty.CommandNoCache(weights_out_path, weights_in_path, resource_to_cpp.tza_to_cpp) + +env_oidn.add_source_files(env.modules_sources, "denoise_wrapper.cpp") +env_modules.add_source_files(env.modules_sources, ["register_types.cpp", "lightmap_denoiser.cpp"]) diff --git a/modules/denoise/config.py b/modules/denoise/config.py new file mode 100644 index 0000000000..53b8f2f2e3 --- /dev/null +++ b/modules/denoise/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return env["tools"] + + +def configure(env): + pass diff --git a/modules/denoise/denoise_wrapper.cpp b/modules/denoise/denoise_wrapper.cpp new file mode 100644 index 0000000000..feeeaef507 --- /dev/null +++ b/modules/denoise/denoise_wrapper.cpp @@ -0,0 +1,34 @@ +#include "denoise_wrapper.h" +#include "thirdparty/oidn/include/OpenImageDenoise/oidn.h" +#include <stdio.h> + +void *oidn_denoiser_init() { + OIDNDeviceImpl *device = oidnNewDevice(OIDN_DEVICE_TYPE_CPU); + oidnCommitDevice(device); + return device; +} + +bool oidn_denoise(void *deviceptr, float *p_floats, int p_width, int p_height) { + OIDNDeviceImpl *device = (OIDNDeviceImpl *)deviceptr; + OIDNFilter filter = oidnNewFilter(device, "RTLightmap"); + oidnSetSharedFilterImage(filter, "color", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); + oidnSetSharedFilterImage(filter, "output", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); + oidnSetFilter1b(filter, "hdr", true); + //oidnSetFilter1f(filter, "hdrScale", 1.0f); + oidnCommitFilter(filter); + oidnExecuteFilter(filter); + + const char *msg; + bool success = true; + if (oidnGetDeviceError(device, &msg) != OIDN_ERROR_NONE) { + printf("LightmapDenoiser: %s\n", msg); + success = false; + } + + oidnReleaseFilter(filter); + return success; +} + +void oidn_denoiser_finish(void *device) { + oidnReleaseDevice((OIDNDeviceImpl *)device); +} diff --git a/modules/denoise/denoise_wrapper.h b/modules/denoise/denoise_wrapper.h new file mode 100644 index 0000000000..3aef326e22 --- /dev/null +++ b/modules/denoise/denoise_wrapper.h @@ -0,0 +1,8 @@ +#ifndef DENOISE_WRAPPER_H +#define DENOISE_WRAPPER_H + +void *oidn_denoiser_init(); +bool oidn_denoise(void *device, float *p_floats, int p_width, int p_height); +void oidn_denoiser_finish(void *device); + +#endif // DENOISE_WRAPPER_H diff --git a/modules/denoise/lightmap_denoiser.cpp b/modules/denoise/lightmap_denoiser.cpp new file mode 100644 index 0000000000..c821b22d85 --- /dev/null +++ b/modules/denoise/lightmap_denoiser.cpp @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* lightmap_denoiser.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "lightmap_denoiser.h" +#include "denoise_wrapper.h" + +LightmapDenoiser *LightmapDenoiserOIDN::create_oidn_denoiser() { + return memnew(LightmapDenoiserOIDN); +} + +void LightmapDenoiserOIDN::make_default_denoiser() { + create_function = create_oidn_denoiser; +} + +Ref<Image> LightmapDenoiserOIDN::denoise_image(const Ref<Image> &p_image) { + + Ref<Image> img = p_image->duplicate(); + + img->convert(Image::FORMAT_RGBF); + + Vector<uint8_t> data = img->get_data(); + if (!oidn_denoise(device, (float *)data.ptrw(), img->get_width(), img->get_height())) { + return p_image; + } + + img->create(img->get_width(), img->get_height(), false, img->get_format(), data); + return img; +} + +LightmapDenoiserOIDN::LightmapDenoiserOIDN() { + device = oidn_denoiser_init(); +} + +LightmapDenoiserOIDN::~LightmapDenoiserOIDN() { + oidn_denoiser_finish(device); +} diff --git a/modules/denoise/lightmap_denoiser.h b/modules/denoise/lightmap_denoiser.h new file mode 100644 index 0000000000..ac0cc8b9db --- /dev/null +++ b/modules/denoise/lightmap_denoiser.h @@ -0,0 +1,57 @@ +/*************************************************************************/ +/* lightmap_denoiser.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 LIGHTMAP_DENOISER_H +#define LIGHTMAP_DENOISER_H + +#include "core/object.h" +#include "scene/3d/lightmapper.h" + +struct OIDNDeviceImpl; + +class LightmapDenoiserOIDN : public LightmapDenoiser { + + GDCLASS(LightmapDenoiserOIDN, LightmapDenoiser); + +protected: + void *device = nullptr; + +public: + static LightmapDenoiser *create_oidn_denoiser(); + + Ref<Image> denoise_image(const Ref<Image> &p_image); + + static void make_default_denoiser(); + + LightmapDenoiserOIDN(); + ~LightmapDenoiserOIDN(); +}; + +#endif // LIGHTMAP_DENOISER_H diff --git a/modules/denoise/register_types.cpp b/modules/denoise/register_types.cpp new file mode 100644 index 0000000000..b6b92701c8 --- /dev/null +++ b/modules/denoise/register_types.cpp @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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/engine.h" +#include "lightmap_denoiser.h" + +void register_denoise_types() { + + LightmapDenoiserOIDN::make_default_denoiser(); +} + +void unregister_denoise_types() { +} diff --git a/modules/denoise/register_types.h b/modules/denoise/register_types.h new file mode 100644 index 0000000000..2ffc36ee2c --- /dev/null +++ b/modules/denoise/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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_denoise_types(); +void unregister_denoise_types(); diff --git a/modules/denoise/resource_to_cpp.py b/modules/denoise/resource_to_cpp.py new file mode 100644 index 0000000000..4c0b67f701 --- /dev/null +++ b/modules/denoise/resource_to_cpp.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +## ======================================================================== ## +## Copyright 2009-2019 Intel Corporation ## +## ## +## Licensed under the Apache License, Version 2.0 (the "License"); ## +## you may not use this file except in compliance with the License. ## +## You may obtain a copy of the License at ## +## ## +## http://www.apache.org/licenses/LICENSE-2.0 ## +## ## +## Unless required by applicable law or agreed to in writing, software ## +## distributed under the License is distributed on an "AS IS" BASIS, ## +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## +## See the License for the specific language governing permissions and ## +## limitations under the License. ## +## ======================================================================== ## + +import os +import sys +import argparse +from array import array + +# Generates a C++ file from the specified binary resource file +def generate(in_path, out_path): + + namespace = "oidn::weights" + scopes = namespace.split("::") + + file_name = os.path.basename(in_path) + var_name = os.path.splitext(file_name)[0] + + with open(in_path, "rb") as in_file, open(out_path, "w") as out_file: + # Header + out_file.write("// Generated from: %s\n" % file_name) + out_file.write("#include <cstddef>\n\n") + + # Open the namespaces + for s in scopes: + out_file.write("namespace %s {\n" % s) + if scopes: + out_file.write("\n") + + # Read the file + in_data = array("B", in_file.read()) + + # Write the size + out_file.write("//const size_t %s_size = %d;\n\n" % (var_name, len(in_data))) + + # Write the data + out_file.write("unsigned char %s[] = {" % var_name) + for i in range(len(in_data)): + c = in_data[i] + if i > 0: + out_file.write(",") + if (i + 1) % 20 == 1: + out_file.write("\n") + out_file.write("%d" % c) + out_file.write("\n};\n") + + # Close the namespaces + if scopes: + out_file.write("\n") + for scope in reversed(scopes): + out_file.write("} // namespace %s\n" % scope) + + +def tza_to_cpp(target, source, env): + for x in zip(source, target): + generate(str(x[0]), str(x[1])) diff --git a/modules/gdnative/gdnative/gdnative.cpp b/modules/gdnative/gdnative/gdnative.cpp index 3175340448..1216d1d9d3 100644 --- a/modules/gdnative/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative/gdnative.cpp @@ -177,7 +177,8 @@ void *godot_get_class_tag(const godot_string_name *p_class) { } godot_object *godot_object_cast_to(const godot_object *p_object, void *p_class_tag) { - if (!p_object) return nullptr; + if (!p_object) + return nullptr; Object *o = (Object *)p_object; return o->is_class_ptr(p_class_tag) ? (godot_object *)o : nullptr; diff --git a/modules/gdnative/include/videodecoder/godot_videodecoder.h b/modules/gdnative/include/videodecoder/godot_videodecoder.h index 3e91a2e9ac..16c92abd22 100644 --- a/modules/gdnative/include/videodecoder/godot_videodecoder.h +++ b/modules/gdnative/include/videodecoder/godot_videodecoder.h @@ -46,7 +46,7 @@ typedef struct void *next; void *(*constructor)(godot_object *); void (*destructor)(void *); - const char *(*get_plugin_name)(void); + const char *(*get_plugin_name)(); const char **(*get_supported_extensions)(int *count); godot_bool (*open_file)(void *, void *); // data struct, and a FileAccess pointer godot_real (*get_length)(const void *); diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index 7e7598e06c..d24b247e59 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -43,6 +43,7 @@ #include "scene/main/node.h" #include "modules/gdnative/gdnative.h" + #include <nativescript/godot_nativescript.h> struct NativeScriptDesc { @@ -54,6 +55,7 @@ struct NativeScriptDesc { uint16_t rpc_method_id; String documentation; }; + struct Property { godot_property_set_func setter; godot_property_get_func getter; @@ -69,9 +71,9 @@ struct NativeScriptDesc { String documentation; }; - uint16_t rpc_count; + uint16_t rpc_count = 0; Map<StringName, Method> methods; - uint16_t rset_count; + uint16_t rset_count = 0; OrderedHashMap<StringName, Property> properties; Map<StringName, Signal> signals_; // QtCreator doesn't like the name signals StringName base; @@ -82,20 +84,11 @@ struct NativeScriptDesc { String documentation; - const void *type_tag; + const void *type_tag = nullptr; bool is_tool; - inline NativeScriptDesc() : - rpc_count(0), - methods(), - rset_count(0), - properties(), - signals_(), - base(), - base_native_type(), - documentation(), - type_tag(nullptr) { + inline NativeScriptDesc() { zeromem(&create_func, sizeof(godot_instance_create_func)); zeromem(&destroy_func, sizeof(godot_instance_destroy_func)); } @@ -396,14 +389,13 @@ inline NativeScriptDesc *NativeScript::get_script_desc() const { class NativeReloadNode : public Node { GDCLASS(NativeReloadNode, Node); - bool unloaded; + bool unloaded = false; public: static void _bind_methods(); void _notification(int p_what); - NativeReloadNode() : - unloaded(false) {} + NativeReloadNode() {} }; class ResourceFormatLoaderNativeScript : public ResourceFormatLoader { diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp index 6b303c8716..9b00d654d1 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.cpp +++ b/modules/gdnative/pluginscript/pluginscript_script.cpp @@ -550,11 +550,6 @@ MultiplayerAPI::RPCMode PluginScript::get_rset_mode(const StringName &p_variable } PluginScript::PluginScript() : - _data(nullptr), - _desc(nullptr), - _language(nullptr), - _tool(false), - _valid(false), _script_list(this) { } diff --git a/modules/gdnative/pluginscript/pluginscript_script.h b/modules/gdnative/pluginscript/pluginscript_script.h index 70b9ca980b..287f42bf7b 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.h +++ b/modules/gdnative/pluginscript/pluginscript_script.h @@ -45,11 +45,11 @@ class PluginScript : public Script { friend class PluginScriptLanguage; private: - godot_pluginscript_script_data *_data; - const godot_pluginscript_script_desc *_desc; - PluginScriptLanguage *_language; - bool _tool; - bool _valid; + godot_pluginscript_script_data *_data = nullptr; + const godot_pluginscript_script_desc *_desc = nullptr; + PluginScriptLanguage *_language = nullptr; + bool _tool = false; + bool _valid = false; Ref<Script> _ref_base_parent; StringName _native_parent; diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.cpp b/modules/gdnative/videodecoder/video_stream_gdnative.cpp index f7d87595af..a2ff376e31 100644 --- a/modules/gdnative/videodecoder/video_stream_gdnative.cpp +++ b/modules/gdnative/videodecoder/video_stream_gdnative.cpp @@ -202,22 +202,7 @@ void VideoStreamPlaybackGDNative::update_texture() { // ctor and dtor VideoStreamPlaybackGDNative::VideoStreamPlaybackGDNative() : - texture(Ref<ImageTexture>(memnew(ImageTexture))), - playing(false), - paused(false), - mix_udata(nullptr), - mix_callback(nullptr), - num_channels(-1), - time(0), - seek_backward(false), - mix_rate(0), - delay_compensation(0), - pcm(nullptr), - pcm_write_idx(0), - samples_decoded(0), - file(nullptr), - interface(nullptr), - data_struct(nullptr) {} + texture(Ref<ImageTexture>(memnew(ImageTexture))) {} VideoStreamPlaybackGDNative::~VideoStreamPlaybackGDNative() { cleanup(); diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.h b/modules/gdnative/videodecoder/video_stream_gdnative.h index 092e10a0f5..f1bae22801 100644 --- a/modules/gdnative/videodecoder/video_stream_gdnative.h +++ b/modules/gdnative/videodecoder/video_stream_gdnative.h @@ -37,13 +37,11 @@ #include "scene/resources/video_stream.h" struct VideoDecoderGDNative { - const godot_videodecoder_interface_gdnative *interface; - String plugin_name; + const godot_videodecoder_interface_gdnative *interface = nullptr; + String plugin_name = "none"; Vector<String> supported_extensions; - VideoDecoderGDNative() : - interface(nullptr), - plugin_name("none") {} + VideoDecoderGDNative() {} VideoDecoderGDNative(const godot_videodecoder_interface_gdnative *p_interface) : interface(p_interface), @@ -111,23 +109,23 @@ class VideoStreamPlaybackGDNative : public VideoStreamPlayback { GDCLASS(VideoStreamPlaybackGDNative, VideoStreamPlayback); Ref<ImageTexture> texture; - bool playing; - bool paused; + bool playing = false; + bool paused = false; Vector2 texture_size; - void *mix_udata; - AudioMixCallback mix_callback; + void *mix_udata = nullptr; + AudioMixCallback mix_callback = nullptr; - int num_channels; - float time; - bool seek_backward; - int mix_rate; - double delay_compensation; + int num_channels = -1; + float time = 0; + bool seek_backward = false; + int mix_rate = 0; + double delay_compensation = 0; - float *pcm; - int pcm_write_idx; - int samples_decoded; + float *pcm = nullptr; + int pcm_write_idx = 0; + int samples_decoded = 0; void cleanup(); void update_texture(); @@ -135,10 +133,10 @@ class VideoStreamPlaybackGDNative : public VideoStreamPlayback { protected: String file_name; - FileAccess *file; + FileAccess *file = nullptr; - const godot_videodecoder_interface_gdnative *interface; - void *data_struct; + const godot_videodecoder_interface_gdnative *interface = nullptr; + void *data_struct = nullptr; public: VideoStreamPlaybackGDNative(); @@ -181,7 +179,7 @@ class VideoStreamGDNative : public VideoStream { GDCLASS(VideoStreamGDNative, VideoStream); String file; - int audio_track; + int audio_track = 0; protected: static void @@ -194,7 +192,7 @@ public: virtual void set_audio_track(int p_track); virtual Ref<VideoStreamPlayback> instance_playback(); - VideoStreamGDNative() { audio_track = 0; } + VideoStreamGDNative() {} }; class ResourceFormatLoaderVideoStreamGDNative : public ResourceFormatLoader { diff --git a/modules/gdnavigation/gd_navigation_server.cpp b/modules/gdnavigation/gd_navigation_server.cpp index 278c27ae22..3792098af4 100644 --- a/modules/gdnavigation/gd_navigation_server.cpp +++ b/modules/gdnavigation/gd_navigation_server.cpp @@ -114,8 +114,7 @@ void GdNavigationServer::MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3) GdNavigationServer::GdNavigationServer() : - NavigationServer3D(), - active(true) { + NavigationServer3D() { } GdNavigationServer::~GdNavigationServer() { diff --git a/modules/gdnavigation/gd_navigation_server.h b/modules/gdnavigation/gd_navigation_server.h index 01d1a4fba9..e3e02f3d7c 100644 --- a/modules/gdnavigation/gd_navigation_server.h +++ b/modules/gdnavigation/gd_navigation_server.h @@ -78,7 +78,7 @@ class GdNavigationServer : public NavigationServer3D { mutable RID_PtrOwner<NavRegion> region_owner; mutable RID_PtrOwner<RvoAgent> agent_owner; - bool active; + bool active = true; Vector<NavMap *> active_maps; public: diff --git a/modules/gdnavigation/nav_map.cpp b/modules/gdnavigation/nav_map.cpp index 7e6a3f7a26..d6dd95d6e7 100644 --- a/modules/gdnavigation/nav_map.cpp +++ b/modules/gdnavigation/nav_map.cpp @@ -33,6 +33,7 @@ #include "core/os/threaded_array_processor.h" #include "nav_region.h" #include "rvo_agent.h" + #include <algorithm> /** @@ -41,16 +42,6 @@ #define USE_ENTRY_POINT -NavMap::NavMap() : - up(0, 1, 0), - cell_size(0.3), - edge_connection_margin(5.0), - regenerate_polygons(true), - regenerate_links(true), - agents_dirty(false), - deltatime(0.0), - map_update_id(0) {} - void NavMap::set_up(Vector3 p_up) { up = p_up; regenerate_polygons = true; diff --git a/modules/gdnavigation/nav_map.h b/modules/gdnavigation/nav_map.h index 4543f00926..d39e301511 100644 --- a/modules/gdnavigation/nav_map.h +++ b/modules/gdnavigation/nav_map.h @@ -48,17 +48,17 @@ class NavRegion; class NavMap : public NavRid { /// Map Up - Vector3 up; + Vector3 up = Vector3(0, 1, 0); /// To find the polygons edges the vertices are displaced in a grid where /// each cell has the following cell_size. - real_t cell_size; + real_t cell_size = 0.3; /// This value is used to detect the near edges to connect. - real_t edge_connection_margin; + real_t edge_connection_margin = 5.0; - bool regenerate_polygons; - bool regenerate_links; + bool regenerate_polygons = true; + bool regenerate_links = true; std::vector<NavRegion *> regions; @@ -69,7 +69,7 @@ class NavMap : public NavRid { RVO::KdTree rvo; /// Is agent array modified? - bool agents_dirty; + bool agents_dirty = false; /// All the Agents (even the controlled one) std::vector<RvoAgent *> agents; @@ -78,13 +78,13 @@ class NavMap : public NavRid { std::vector<RvoAgent *> controlled_agents; /// Physics delta time - real_t deltatime; + real_t deltatime = 0.0; /// Change the id each time the map is updated. - uint32_t map_update_id; + uint32_t map_update_id = 0; public: - NavMap(); + NavMap() {} void set_up(Vector3 p_up); Vector3 get_up() const { diff --git a/modules/gdnavigation/nav_region.cpp b/modules/gdnavigation/nav_region.cpp index b91376f761..2bd42ba980 100644 --- a/modules/gdnavigation/nav_region.cpp +++ b/modules/gdnavigation/nav_region.cpp @@ -36,11 +36,6 @@ @author AndreaCatania */ -NavRegion::NavRegion() : - map(nullptr), - polygons_dirty(true) { -} - void NavRegion::set_map(NavMap *p_map) { map = p_map; polygons_dirty = true; diff --git a/modules/gdnavigation/nav_region.h b/modules/gdnavigation/nav_region.h index f35ee4bea0..731855bfb5 100644 --- a/modules/gdnavigation/nav_region.h +++ b/modules/gdnavigation/nav_region.h @@ -45,17 +45,17 @@ class NavMap; class NavRegion; class NavRegion : public NavRid { - NavMap *map; + NavMap *map = nullptr; Transform transform; Ref<NavigationMesh> mesh; - bool polygons_dirty; + bool polygons_dirty = true; /// Cache std::vector<gd::Polygon> polygons; public: - NavRegion(); + NavRegion() {} void scratch_polygons() { polygons_dirty = true; diff --git a/modules/gdnavigation/nav_utils.h b/modules/gdnavigation/nav_utils.h index 3401284c31..388e53a66a 100644 --- a/modules/gdnavigation/nav_utils.h +++ b/modules/gdnavigation/nav_utils.h @@ -32,6 +32,7 @@ #define NAV_UTILS_H #include "core/math/vector3.h" + #include <vector> /** @@ -80,19 +81,15 @@ struct Point { struct Edge { /// This edge ID - int this_edge; + int this_edge = -1; /// Other Polygon - Polygon *other_polygon; + Polygon *other_polygon = nullptr; /// The other `Polygon` at this edge id has this `Polygon`. - int other_edge; + int other_edge = -1; - Edge() { - this_edge = -1; - other_polygon = nullptr; - other_edge = -1; - } + Edge() {} }; struct Polygon { @@ -113,39 +110,29 @@ struct Polygon { struct Connection { - Polygon *A; - int A_edge; - Polygon *B; - int B_edge; + Polygon *A = nullptr; + int A_edge = -1; + Polygon *B = nullptr; + int B_edge = -1; - Connection() { - A = nullptr; - B = nullptr; - A_edge = -1; - B_edge = -1; - } + Connection() {} }; struct NavigationPoly { - uint32_t self_id; + uint32_t self_id = 0; /// This poly. const Polygon *poly; /// The previous navigation poly (id in the `navigation_poly` array). - int prev_navigation_poly_id; + int prev_navigation_poly_id = -1; /// The edge id in this `Poly` to reach the `prev_navigation_poly_id`. - uint32_t back_navigation_edge; + uint32_t back_navigation_edge = 0; /// The entry location of this poly. Vector3 entry; /// The distance to the destination. - float traveled_distance; + float traveled_distance = 0.0; NavigationPoly(const Polygon *p_poly) : - self_id(0), - poly(p_poly), - prev_navigation_poly_id(-1), - back_navigation_edge(0), - traveled_distance(0.0) { - } + poly(p_poly) {} bool operator==(const NavigationPoly &other) const { return this->poly == other.poly; diff --git a/modules/gdnavigation/rvo_agent.cpp b/modules/gdnavigation/rvo_agent.cpp index 3c39f02c26..1e1bdbd07d 100644 --- a/modules/gdnavigation/rvo_agent.cpp +++ b/modules/gdnavigation/rvo_agent.cpp @@ -36,8 +36,7 @@ @author AndreaCatania */ -RvoAgent::RvoAgent() : - map(nullptr) { +RvoAgent::RvoAgent() { callback.id = ObjectID(); } diff --git a/modules/gdnavigation/rvo_agent.h b/modules/gdnavigation/rvo_agent.h index 914cbaa7d9..f5c579ba84 100644 --- a/modules/gdnavigation/rvo_agent.h +++ b/modules/gdnavigation/rvo_agent.h @@ -49,7 +49,7 @@ class RvoAgent : public NavRid { Variant new_velocity; }; - NavMap *map; + NavMap *map = nullptr; RVO::Agent agent; AvoidanceComputedCallback callback; uint32_t map_update_id; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index cdd5deb7ee..8559fac57c 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -376,10 +376,15 @@ void GDScript::_update_exports_values(Map<StringName, Variant> &values, List<Pro } #endif -bool GDScript::_update_exports() { +bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) { #ifdef TOOLS_ENABLED + static Vector<GDScript *> base_caches; + if (!p_recursive_call) + base_caches.clear(); + base_caches.append(this); + bool changed = false; if (source_changed_cache) { @@ -473,7 +478,22 @@ bool GDScript::_update_exports() { placeholder_fallback_enabled = false; if (base_cache.is_valid() && base_cache->is_valid()) { - if (base_cache->_update_exports()) { + for (int i = 0; i < base_caches.size(); i++) { + if (base_caches[i] == base_cache.ptr()) { + if (r_err) + *r_err = true; + valid = false; // to show error in the editor + base_cache->valid = false; + base_cache->inheriters_cache.clear(); // to prevent future stackoverflows + base_cache.unref(); + base.unref(); + _base = nullptr; + ERR_FAIL_V_MSG(false, "Cyclic inheritance in script class."); + } + } + if (base_cache->_update_exports(r_err, true)) { + if (r_err && *r_err) + return false; changed = true; } } @@ -501,7 +521,10 @@ void GDScript::update_exports() { #ifdef TOOLS_ENABLED - _update_exports(); + bool cyclic_error = false; + _update_exports(&cyclic_error); + if (cyclic_error) + return; Set<ObjectID> copy = inheriters_cache; //might get modified @@ -635,12 +658,14 @@ uint16_t GDScript::get_rpc_method_id(const StringName &p_method) const { } StringName GDScript::get_rpc_method(const uint16_t p_rpc_method_id) const { - if (p_rpc_method_id >= rpc_functions.size()) return StringName(); + if (p_rpc_method_id >= rpc_functions.size()) + return StringName(); return rpc_functions[p_rpc_method_id].name; } MultiplayerAPI::RPCMode GDScript::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const { - if (p_rpc_method_id >= rpc_functions.size()) return MultiplayerAPI::RPC_MODE_DISABLED; + if (p_rpc_method_id >= rpc_functions.size()) + return MultiplayerAPI::RPC_MODE_DISABLED; return rpc_functions[p_rpc_method_id].mode; } @@ -662,12 +687,14 @@ uint16_t GDScript::get_rset_property_id(const StringName &p_variable) const { } StringName GDScript::get_rset_property(const uint16_t p_rset_member_id) const { - if (p_rset_member_id >= rpc_variables.size()) return StringName(); + if (p_rset_member_id >= rpc_variables.size()) + return StringName(); return rpc_variables[p_rset_member_id].name; } MultiplayerAPI::RPCMode GDScript::get_rset_mode_by_id(const uint16_t p_rset_member_id) const { - if (p_rset_member_id >= rpc_variables.size()) return MultiplayerAPI::RPC_MODE_DISABLED; + if (p_rset_member_id >= rpc_variables.size()) + return MultiplayerAPI::RPC_MODE_DISABLED; return rpc_variables[p_rset_member_id].mode; } @@ -2157,7 +2184,8 @@ String GDScriptWarning::get_message() const { case STANDALONE_TERNARY: { return "Standalone ternary conditional operator: the return value is being discarded."; } - case WARNING_MAX: break; // Can't happen, but silences warning + case WARNING_MAX: + break; // Can't happen, but silences warning } ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + "."); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 2dbc2252fa..3cba621578 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -135,7 +135,7 @@ class GDScript : public Script { #endif - bool _update_exports(); + bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false); void _save_orphaned_subclasses(); void _init_rpc_methods_properties(); @@ -334,18 +334,18 @@ struct GDScriptWarning { DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced STANDALONE_TERNARY, // Return value of ternary expression is discarded WARNING_MAX, - } code; + }; + + Code code = WARNING_MAX; Vector<String> symbols; - int line; + int line = -1; 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) {} + GDScriptWarning() {} }; #endif // DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 2bbec29043..473b6fab05 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -177,16 +177,36 @@ int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDS switch (p_expression->op) { - case GDScriptParser::OperatorNode::OP_ASSIGN_ADD: var_op = Variant::OP_ADD; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_SUB: var_op = Variant::OP_SUBTRACT; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_MUL: var_op = Variant::OP_MULTIPLY; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_DIV: var_op = Variant::OP_DIVIDE; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_MOD: var_op = Variant::OP_MODULE; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT: var_op = Variant::OP_SHIFT_LEFT; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT: var_op = Variant::OP_SHIFT_RIGHT; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_AND: var_op = Variant::OP_BIT_AND; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_OR: var_op = Variant::OP_BIT_OR; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_XOR: var_op = Variant::OP_BIT_XOR; break; + case GDScriptParser::OperatorNode::OP_ASSIGN_ADD: + var_op = Variant::OP_ADD; + break; + case GDScriptParser::OperatorNode::OP_ASSIGN_SUB: + var_op = Variant::OP_SUBTRACT; + break; + case GDScriptParser::OperatorNode::OP_ASSIGN_MUL: + var_op = Variant::OP_MULTIPLY; + break; + case GDScriptParser::OperatorNode::OP_ASSIGN_DIV: + var_op = Variant::OP_DIVIDE; + break; + case GDScriptParser::OperatorNode::OP_ASSIGN_MOD: + var_op = Variant::OP_MODULE; + break; + case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT: + var_op = Variant::OP_SHIFT_LEFT; + break; + case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT: + var_op = Variant::OP_SHIFT_RIGHT; + break; + case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_AND: + var_op = Variant::OP_BIT_AND; + break; + case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_OR: + var_op = Variant::OP_BIT_OR; + break; + case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_XOR: + var_op = Variant::OP_BIT_XOR; + break; case GDScriptParser::OperatorNode::OP_INIT_ASSIGN: case GDScriptParser::OperatorNode::OP_ASSIGN: { @@ -861,71 +881,92 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: } break; //unary operators case GDScriptParser::OperatorNode::OP_NEG: { - if (!_create_unary_operator(codegen, on, Variant::OP_NEGATE, p_stack_level)) return -1; + if (!_create_unary_operator(codegen, on, Variant::OP_NEGATE, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_POS: { - if (!_create_unary_operator(codegen, on, Variant::OP_POSITIVE, p_stack_level)) return -1; + if (!_create_unary_operator(codegen, on, Variant::OP_POSITIVE, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_NOT: { - if (!_create_unary_operator(codegen, on, Variant::OP_NOT, p_stack_level)) return -1; + if (!_create_unary_operator(codegen, on, Variant::OP_NOT, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_BIT_INVERT: { - if (!_create_unary_operator(codegen, on, Variant::OP_BIT_NEGATE, p_stack_level)) return -1; + if (!_create_unary_operator(codegen, on, Variant::OP_BIT_NEGATE, p_stack_level)) + return -1; } 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; + if (!_create_binary_operator(codegen, on, Variant::OP_IN, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_EQUAL: { - if (!_create_binary_operator(codegen, on, Variant::OP_EQUAL, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_EQUAL, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_NOT_EQUAL: { - if (!_create_binary_operator(codegen, on, Variant::OP_NOT_EQUAL, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_NOT_EQUAL, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_LESS: { - if (!_create_binary_operator(codegen, on, Variant::OP_LESS, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_LESS, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_LESS_EQUAL: { - if (!_create_binary_operator(codegen, on, Variant::OP_LESS_EQUAL, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_LESS_EQUAL, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_GREATER: { - if (!_create_binary_operator(codegen, on, Variant::OP_GREATER, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_GREATER, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_GREATER_EQUAL: { - if (!_create_binary_operator(codegen, on, Variant::OP_GREATER_EQUAL, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_GREATER_EQUAL, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_ADD: { - if (!_create_binary_operator(codegen, on, Variant::OP_ADD, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_ADD, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_SUB: { - if (!_create_binary_operator(codegen, on, Variant::OP_SUBTRACT, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_SUBTRACT, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_MUL: { - if (!_create_binary_operator(codegen, on, Variant::OP_MULTIPLY, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_MULTIPLY, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_DIV: { - if (!_create_binary_operator(codegen, on, Variant::OP_DIVIDE, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_DIVIDE, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_MOD: { - if (!_create_binary_operator(codegen, on, Variant::OP_MODULE, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_MODULE, p_stack_level)) + return -1; } break; //case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_LEFT,p_stack_level)) return -1;} break; //case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_RIGHT,p_stack_level)) return -1;} break; case GDScriptParser::OperatorNode::OP_BIT_AND: { - if (!_create_binary_operator(codegen, on, Variant::OP_BIT_AND, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_BIT_AND, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_BIT_OR: { - if (!_create_binary_operator(codegen, on, Variant::OP_BIT_OR, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_BIT_OR, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_BIT_XOR: { - if (!_create_binary_operator(codegen, on, Variant::OP_BIT_XOR, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_BIT_XOR, p_stack_level)) + return -1; } break; //shift case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: { - if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_LEFT, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_LEFT, p_stack_level)) + return -1; } break; case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: { - if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_RIGHT, p_stack_level)) return -1; + if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_RIGHT, p_stack_level)) + return -1; } break; //assignment operators case GDScriptParser::OperatorNode::OP_ASSIGN_ADD: diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 34b066b5c7..08e1ec74af 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -123,10 +123,12 @@ class GDScriptCompiler { Vector<int> opcodes; void alloc_stack(int p_level) { - if (p_level >= stack_max) stack_max = p_level + 1; + if (p_level >= stack_max) + stack_max = p_level + 1; } void alloc_call(int p_params) { - if (p_params >= call_max) call_max = p_params; + if (p_params >= call_max) + call_max = p_params; } int current_line; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index ab3228d076..56381e8af7 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -492,31 +492,24 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na struct GDScriptCompletionContext { - const GDScriptParser::ClassNode *_class; - const GDScriptParser::FunctionNode *function; - const GDScriptParser::BlockNode *block; - Object *base; + const GDScriptParser::ClassNode *_class = nullptr; + const GDScriptParser::FunctionNode *function = nullptr; + const GDScriptParser::BlockNode *block = nullptr; + Object *base = nullptr; String base_path; - int line; - uint32_t depth; - - GDScriptCompletionContext() : - _class(nullptr), - function(nullptr), - block(nullptr), - base(nullptr), - line(0), - depth(0) {} + int line = 0; + uint32_t depth = 0; + + GDScriptCompletionContext() {} }; struct GDScriptCompletionIdentifier { GDScriptParser::DataType type; String enumeration; Variant value; - const GDScriptParser::Node *assigned_expression; + const GDScriptParser::Node *assigned_expression = nullptr; - GDScriptCompletionIdentifier() : - assigned_expression(nullptr) {} + GDScriptCompletionIdentifier() {} }; static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) { @@ -1082,16 +1075,36 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G 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; + 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: { } } diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index df0fac956c..4e31ffe2a4 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -1289,7 +1289,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a gdfs->state.instance = p_instance; p_instance->pending_func_states.add(&gdfs->instances_list); } else { - gdfs->state.instance = NULL; + gdfs->state.instance = nullptr; } } #ifdef DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index d38b6d0739..7043c9b69b 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -43,20 +43,24 @@ class GDScriptInstance; class GDScript; struct GDScriptDataType { - bool has_type; - enum { + enum Kind { UNINITIALIZED, BUILTIN, NATIVE, SCRIPT, GDSCRIPT, - } kind; - Variant::Type builtin_type; + }; + + Kind kind = UNINITIALIZED; + + bool has_type = false; + Variant::Type builtin_type = Variant::NIL; 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 + if (!has_type) + return true; // Can't type check switch (kind) { case UNINITIALIZED: @@ -146,10 +150,7 @@ struct GDScriptDataType { return info; } - GDScriptDataType() : - has_type(false), - kind(UNINITIALIZED), - builtin_type(Variant::NIL) {} + GDScriptDataType() {} }; class GDScriptFunction { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index b3ec42a7fb..8cc6986f34 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -72,6 +72,16 @@ bool GDScriptParser::_end_statement() { return false; } +void GDScriptParser::_set_end_statement_error(String p_name) { + String error_msg; + if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER) { + error_msg = vformat("Expected end of statement (\"%s\"), got %s (\"%s\") instead.", p_name, tokenizer->get_token_name(tokenizer->get_token()), tokenizer->get_token_identifier()); + } else { + error_msg = vformat("Expected end of statement (\"%s\"), got %s instead.", p_name, tokenizer->get_token_name(tokenizer->get_token())); + } + _set_error(error_msg); +} + bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { if (tokenizer->get_token() != GDScriptTokenizer::TK_COLON) { @@ -910,10 +920,18 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s e.is_op = true; switch (tokenizer->get_token()) { - case GDScriptTokenizer::TK_OP_ADD: e.op = OperatorNode::OP_POS; break; - case GDScriptTokenizer::TK_OP_SUB: e.op = OperatorNode::OP_NEG; break; - case GDScriptTokenizer::TK_OP_NOT: e.op = OperatorNode::OP_NOT; break; - case GDScriptTokenizer::TK_OP_BIT_INVERT: e.op = OperatorNode::OP_BIT_INVERT; break; + case GDScriptTokenizer::TK_OP_ADD: + e.op = OperatorNode::OP_POS; + break; + case GDScriptTokenizer::TK_OP_SUB: + e.op = OperatorNode::OP_NEG; + break; + case GDScriptTokenizer::TK_OP_NOT: + e.op = OperatorNode::OP_NOT; + break; + case GDScriptTokenizer::TK_OP_BIT_INVERT: + e.op = OperatorNode::OP_BIT_INVERT; + break; default: { } } @@ -1329,25 +1347,55 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s switch (tokenizer->get_token()) { //see operator - case GDScriptTokenizer::TK_OP_IN: op = OperatorNode::OP_IN; break; - case GDScriptTokenizer::TK_OP_EQUAL: op = OperatorNode::OP_EQUAL; break; - case GDScriptTokenizer::TK_OP_NOT_EQUAL: op = OperatorNode::OP_NOT_EQUAL; break; - case GDScriptTokenizer::TK_OP_LESS: op = OperatorNode::OP_LESS; break; - case GDScriptTokenizer::TK_OP_LESS_EQUAL: op = OperatorNode::OP_LESS_EQUAL; break; - case GDScriptTokenizer::TK_OP_GREATER: op = OperatorNode::OP_GREATER; break; - case GDScriptTokenizer::TK_OP_GREATER_EQUAL: op = OperatorNode::OP_GREATER_EQUAL; break; - case GDScriptTokenizer::TK_OP_AND: op = OperatorNode::OP_AND; break; - case GDScriptTokenizer::TK_OP_OR: op = OperatorNode::OP_OR; break; - case GDScriptTokenizer::TK_OP_ADD: op = OperatorNode::OP_ADD; break; - case GDScriptTokenizer::TK_OP_SUB: op = OperatorNode::OP_SUB; break; - case GDScriptTokenizer::TK_OP_MUL: op = OperatorNode::OP_MUL; break; - case GDScriptTokenizer::TK_OP_DIV: op = OperatorNode::OP_DIV; break; + case GDScriptTokenizer::TK_OP_IN: + op = OperatorNode::OP_IN; + break; + case GDScriptTokenizer::TK_OP_EQUAL: + op = OperatorNode::OP_EQUAL; + break; + case GDScriptTokenizer::TK_OP_NOT_EQUAL: + op = OperatorNode::OP_NOT_EQUAL; + break; + case GDScriptTokenizer::TK_OP_LESS: + op = OperatorNode::OP_LESS; + break; + case GDScriptTokenizer::TK_OP_LESS_EQUAL: + op = OperatorNode::OP_LESS_EQUAL; + break; + case GDScriptTokenizer::TK_OP_GREATER: + op = OperatorNode::OP_GREATER; + break; + case GDScriptTokenizer::TK_OP_GREATER_EQUAL: + op = OperatorNode::OP_GREATER_EQUAL; + break; + case GDScriptTokenizer::TK_OP_AND: + op = OperatorNode::OP_AND; + break; + case GDScriptTokenizer::TK_OP_OR: + op = OperatorNode::OP_OR; + break; + case GDScriptTokenizer::TK_OP_ADD: + op = OperatorNode::OP_ADD; + break; + case GDScriptTokenizer::TK_OP_SUB: + op = OperatorNode::OP_SUB; + break; + case GDScriptTokenizer::TK_OP_MUL: + op = OperatorNode::OP_MUL; + break; + case GDScriptTokenizer::TK_OP_DIV: + op = OperatorNode::OP_DIV; + break; case GDScriptTokenizer::TK_OP_MOD: op = OperatorNode::OP_MOD; break; //case GDScriptTokenizer::TK_OP_NEG: op=OperatorNode::OP_NEG ; break; - case GDScriptTokenizer::TK_OP_SHIFT_LEFT: op = OperatorNode::OP_SHIFT_LEFT; break; - case GDScriptTokenizer::TK_OP_SHIFT_RIGHT: op = OperatorNode::OP_SHIFT_RIGHT; break; + case GDScriptTokenizer::TK_OP_SHIFT_LEFT: + op = OperatorNode::OP_SHIFT_LEFT; + break; + case GDScriptTokenizer::TK_OP_SHIFT_RIGHT: + op = OperatorNode::OP_SHIFT_RIGHT; + break; case GDScriptTokenizer::TK_OP_ASSIGN: { _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN; @@ -1364,23 +1412,57 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } } break; - case GDScriptTokenizer::TK_OP_ASSIGN_ADD: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_ADD; break; - case GDScriptTokenizer::TK_OP_ASSIGN_SUB: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SUB; break; - case GDScriptTokenizer::TK_OP_ASSIGN_MUL: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MUL; break; - case GDScriptTokenizer::TK_OP_ASSIGN_DIV: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_DIV; break; - case GDScriptTokenizer::TK_OP_ASSIGN_MOD: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MOD; break; - case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_LEFT; break; - case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_RIGHT; break; - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_AND; break; - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_OR; break; - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_XOR; break; - case GDScriptTokenizer::TK_OP_BIT_AND: op = OperatorNode::OP_BIT_AND; break; - case GDScriptTokenizer::TK_OP_BIT_OR: op = OperatorNode::OP_BIT_OR; break; - case GDScriptTokenizer::TK_OP_BIT_XOR: op = OperatorNode::OP_BIT_XOR; break; - case GDScriptTokenizer::TK_PR_IS: op = OperatorNode::OP_IS; break; - case GDScriptTokenizer::TK_CF_IF: op = OperatorNode::OP_TERNARY_IF; break; - case GDScriptTokenizer::TK_CF_ELSE: op = OperatorNode::OP_TERNARY_ELSE; break; - default: valid = false; break; + case GDScriptTokenizer::TK_OP_ASSIGN_ADD: + _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_ADD; + break; + case GDScriptTokenizer::TK_OP_ASSIGN_SUB: + _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SUB; + break; + case GDScriptTokenizer::TK_OP_ASSIGN_MUL: + _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MUL; + break; + case GDScriptTokenizer::TK_OP_ASSIGN_DIV: + _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_DIV; + break; + case GDScriptTokenizer::TK_OP_ASSIGN_MOD: + _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MOD; + break; + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: + _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_LEFT; + break; + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: + _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_RIGHT; + break; + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: + _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_AND; + break; + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: + _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_OR; + break; + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: + _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_XOR; + break; + case GDScriptTokenizer::TK_OP_BIT_AND: + op = OperatorNode::OP_BIT_AND; + break; + case GDScriptTokenizer::TK_OP_BIT_OR: + op = OperatorNode::OP_BIT_OR; + break; + case GDScriptTokenizer::TK_OP_BIT_XOR: + op = OperatorNode::OP_BIT_XOR; + break; + case GDScriptTokenizer::TK_PR_IS: + op = OperatorNode::OP_IS; + break; + case GDScriptTokenizer::TK_CF_IF: + op = OperatorNode::OP_TERNARY_IF; + break; + case GDScriptTokenizer::TK_CF_ELSE: + op = OperatorNode::OP_TERNARY_ELSE; + break; + default: + valid = false; + break; } if (valid) { @@ -1433,36 +1515,74 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s unary = true; break; - case OperatorNode::OP_MUL: priority = 2; break; - case OperatorNode::OP_DIV: priority = 2; break; - case OperatorNode::OP_MOD: priority = 2; break; + case OperatorNode::OP_MUL: + priority = 2; + break; + case OperatorNode::OP_DIV: + priority = 2; + break; + case OperatorNode::OP_MOD: + priority = 2; + break; - case OperatorNode::OP_ADD: priority = 3; break; - case OperatorNode::OP_SUB: priority = 3; break; + case OperatorNode::OP_ADD: + priority = 3; + break; + case OperatorNode::OP_SUB: + priority = 3; + break; - case OperatorNode::OP_SHIFT_LEFT: priority = 4; break; - case OperatorNode::OP_SHIFT_RIGHT: priority = 4; break; + case OperatorNode::OP_SHIFT_LEFT: + priority = 4; + break; + case OperatorNode::OP_SHIFT_RIGHT: + priority = 4; + break; - case OperatorNode::OP_BIT_AND: priority = 5; break; - case OperatorNode::OP_BIT_XOR: priority = 6; break; - case OperatorNode::OP_BIT_OR: priority = 7; break; + case OperatorNode::OP_BIT_AND: + priority = 5; + break; + case OperatorNode::OP_BIT_XOR: + priority = 6; + break; + case OperatorNode::OP_BIT_OR: + priority = 7; + break; - case OperatorNode::OP_LESS: priority = 8; break; - case OperatorNode::OP_LESS_EQUAL: priority = 8; break; - case OperatorNode::OP_GREATER: priority = 8; break; - case OperatorNode::OP_GREATER_EQUAL: priority = 8; break; + case OperatorNode::OP_LESS: + priority = 8; + break; + case OperatorNode::OP_LESS_EQUAL: + priority = 8; + break; + case OperatorNode::OP_GREATER: + priority = 8; + break; + case OperatorNode::OP_GREATER_EQUAL: + priority = 8; + break; - case OperatorNode::OP_EQUAL: priority = 8; break; - case OperatorNode::OP_NOT_EQUAL: priority = 8; break; + case OperatorNode::OP_EQUAL: + priority = 8; + break; + case OperatorNode::OP_NOT_EQUAL: + priority = 8; + break; - case OperatorNode::OP_IN: priority = 10; break; + case OperatorNode::OP_IN: + priority = 10; + break; case OperatorNode::OP_NOT: priority = 11; unary = true; break; - case OperatorNode::OP_AND: priority = 12; break; - case OperatorNode::OP_OR: priority = 13; break; + case OperatorNode::OP_AND: + priority = 12; + break; + case OperatorNode::OP_OR: + priority = 13; + break; case OperatorNode::OP_TERNARY_IF: priority = 14; @@ -1475,17 +1595,39 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s // Rigth-to-left should be false in this case, otherwise it would always error. break; - case OperatorNode::OP_ASSIGN: priority = 15; break; - case OperatorNode::OP_ASSIGN_ADD: priority = 15; break; - case OperatorNode::OP_ASSIGN_SUB: priority = 15; break; - case OperatorNode::OP_ASSIGN_MUL: priority = 15; break; - case OperatorNode::OP_ASSIGN_DIV: priority = 15; break; - case OperatorNode::OP_ASSIGN_MOD: priority = 15; break; - case OperatorNode::OP_ASSIGN_SHIFT_LEFT: priority = 15; break; - case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: priority = 15; break; - case OperatorNode::OP_ASSIGN_BIT_AND: priority = 15; break; - case OperatorNode::OP_ASSIGN_BIT_OR: priority = 15; break; - case OperatorNode::OP_ASSIGN_BIT_XOR: priority = 15; break; + case OperatorNode::OP_ASSIGN: + priority = 15; + break; + case OperatorNode::OP_ASSIGN_ADD: + priority = 15; + break; + case OperatorNode::OP_ASSIGN_SUB: + priority = 15; + break; + case OperatorNode::OP_ASSIGN_MUL: + priority = 15; + break; + case OperatorNode::OP_ASSIGN_DIV: + priority = 15; + break; + case OperatorNode::OP_ASSIGN_MOD: + priority = 15; + break; + case OperatorNode::OP_ASSIGN_SHIFT_LEFT: + priority = 15; + break; + case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: + priority = 15; + break; + case OperatorNode::OP_ASSIGN_BIT_AND: + priority = 15; + break; + case OperatorNode::OP_ASSIGN_BIT_OR: + priority = 15; + break; + case OperatorNode::OP_ASSIGN_BIT_XOR: + priority = 15; + break; default: { _set_error("GDScriptParser bug, invalid operator in expression: " + itos(expression[i].op)); @@ -2037,7 +2179,8 @@ bool GDScriptParser::_reduce_export_var_type(Variant &p_value, int p_line) { if (p_value.get_type() == Variant::ARRAY) { Array arr = p_value; for (int i = 0; i < arr.size(); i++) { - if (!_reduce_export_var_type(arr[i], p_line)) return false; + if (!_reduce_export_var_type(arr[i], p_line)) + return false; } return true; } @@ -2046,7 +2189,8 @@ bool GDScriptParser::_reduce_export_var_type(Variant &p_value, int p_line) { Dictionary dict = p_value; for (int i = 0; i < dict.size(); i++) { Variant value = dict.get_value_at_index(i); - if (!_reduce_export_var_type(value, p_line)) return false; + if (!_reduce_export_var_type(value, p_line)) + return false; } return true; } @@ -2938,7 +3082,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { lv->assign = assigned; if (!_end_statement()) { - _set_error("Expected end of statement (\"var\")."); + _set_end_statement_error("var"); return; } @@ -3160,6 +3304,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { ConstantNode *c = static_cast<ConstantNode *>(op->arguments[i]); if (c->value.get_type() == Variant::FLOAT || c->value.get_type() == Variant::INT) { constants.push_back(c->value); + } else { + constant = false; } } else { constant = false; @@ -3172,9 +3318,15 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { ConstantNode *cn = alloc_node<ConstantNode>(); switch (args.size()) { - case 1: cn->value = (int)constants[0]; break; - case 2: cn->value = Vector2(constants[0], constants[1]); break; - case 3: cn->value = Vector3(constants[0], constants[1], constants[2]); break; + case 1: + cn->value = (int64_t)constants[0]; + break; + case 2: + cn->value = Vector2i(constants[0], constants[1]); + break; + case 3: + cn->value = Vector3i(constants[0], constants[1], constants[2]); + break; } cn->datatype = _type_from_variant(cn->value); container = cn; @@ -3186,9 +3338,15 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { on->arguments.push_back(tn); switch (args.size()) { - case 1: tn->vtype = Variant::INT; break; - case 2: tn->vtype = Variant::VECTOR2; break; - case 3: tn->vtype = Variant::VECTOR3; break; + case 1: + tn->vtype = Variant::INT; + break; + case 2: + tn->vtype = Variant::VECTOR2I; + break; + case 3: + tn->vtype = Variant::VECTOR3I; + break; } for (int i = 0; i < args.size(); i++) { @@ -3249,7 +3407,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { cf_continue->cf_type = ControlFlowNode::CF_CONTINUE; p_block->statements.push_back(cf_continue); if (!_end_statement()) { - _set_error("Expected end of statement (\"continue\")."); + _set_end_statement_error("continue"); return; } } break; @@ -3261,7 +3419,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { cf_break->cf_type = ControlFlowNode::CF_BREAK; p_block->statements.push_back(cf_break); if (!_end_statement()) { - _set_error("Expected end of statement (\"break\")."); + _set_end_statement_error("break"); return; } } break; @@ -3290,7 +3448,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { cf_return->arguments.push_back(retexpr); p_block->statements.push_back(cf_return); if (!_end_statement()) { - _set_error("Expected end of statement after return expression."); + _set_end_statement_error("return"); return; } } @@ -3327,7 +3485,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { _parse_pattern_block(compiled_branches, match_node->branches, p_static); - if (error_set) return; + if (error_set) + return; ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>(); match_cf_node->cf_type = ControlFlowNode::CF_MATCH; @@ -3379,7 +3538,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { p_block->statements.push_back(an); if (!_end_statement()) { - _set_error("Expected end of statement after \"assert\".", assert_line); + _set_end_statement_error("assert"); return; } } break; @@ -3390,7 +3549,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { p_block->statements.push_back(bn); if (!_end_statement()) { - _set_error("Expected end of statement after \"breakpoint\"."); + _set_end_statement_error("breakpoint"); return; } } break; @@ -3409,7 +3568,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { _set_error("Unexpected ':=', use '=' instead. Expected end of statement after expression."); } else { - _set_error(String() + "Expected end of statement after expression, got " + tokenizer->get_token_name(tokenizer->get_token()) + " instead"); + _set_error(vformat("Expected end of statement after expression, got %s instead.", tokenizer->get_token_name(tokenizer->get_token()))); } return; } @@ -3599,7 +3758,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (error_set) return; if (!_end_statement()) { - _set_error("Expected end of statement after \"extends\"."); + _set_end_statement_error("extends"); return; } @@ -4104,7 +4263,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { p_class->_signals.push_back(sig); if (!_end_statement()) { - _set_error("Expected end of statement (\"signal\")."); + _set_end_statement_error("signal"); return; } } break; @@ -4924,7 +5083,8 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } - if (!_reduce_export_var_type(cn->value, member.line)) return; + if (!_reduce_export_var_type(cn->value, member.line)) + return; member._export.type = cn->value.get_type(); member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; @@ -5047,7 +5207,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { p_class->variables.push_back(member); if (!_end_statement()) { - _set_error("Expected end of statement (\"continue\")."); + _set_end_statement_error("var"); return; } } break; @@ -5127,7 +5287,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { p_class->constant_expressions.insert(const_id, constant); if (!_end_statement()) { - _set_error("Expected end of statement (constant).", line); + _set_end_statement_error("const"); return; } @@ -5281,7 +5441,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } if (!_end_statement()) { - _set_error("Expected end of statement (\"enum\")."); + _set_end_statement_error("enum"); return; } @@ -5441,8 +5601,10 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive } } - if (base_class) break; - if (found) continue; + if (base_class) + break; + if (found) + continue; if (p->constant_expressions.has(base)) { if (p->constant_expressions[base].expression->type != Node::TYPE_CONSTANT) { @@ -5544,10 +5706,12 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive } String GDScriptParser::DataType::to_string() const { - if (!has_type) return "var"; + if (!has_type) + return "var"; switch (kind) { case BUILTIN: { - if (builtin_type == Variant::NIL) return "null"; + if (builtin_type == Variant::NIL) + return "null"; return Variant::get_type_name(builtin_type); } break; case NATIVE: { @@ -5711,8 +5875,10 @@ bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) { } 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; + 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; @@ -6952,7 +7118,8 @@ bool GDScriptParser::_get_function_signature(DataType &p_base_type, const String native = "_" + native.operator String(); } if (!ClassDB::class_exists(native)) { - if (!check_types) return false; + if (!check_types) + return false; ERR_FAIL_V_MSG(false, "Parser bug: Class '" + String(native) + "' not found."); } @@ -7043,7 +7210,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat par_types.write[i - 1] = _reduce_node_type(p_call->arguments[i]); } - if (error_set) return DataType(); + if (error_set) + return DataType(); // Special case: check copy constructor. Those are defined implicitly in Variant. if (par_types.size() == 1) { @@ -7111,7 +7279,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat err += "' matches the signature '"; err += Variant::get_type_name(tn->vtype) + "("; for (int i = 0; i < par_types.size(); i++) { - if (i > 0) err += ", "; + if (i > 0) + err += ", "; err += par_types[i].to_string(); } err += ")'."; @@ -7340,7 +7509,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat return return_type; } -bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const { +bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type, bool *r_is_const) const { DataType base_type = p_base_type; // Check classes in current file @@ -7351,6 +7520,8 @@ bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringN while (base) { if (base->constant_expressions.has(p_member)) { + if (r_is_const) + *r_is_const = true; r_member_type = base->constant_expressions[p_member].expression->get_datatype(); return true; } @@ -7469,7 +7640,8 @@ bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringN native = "_" + native.operator String(); } if (!ClassDB::class_exists(native)) { - if (!check_types) return false; + if (!check_types) + return false; ERR_FAIL_V_MSG(false, "Parser bug: Class \"" + String(native) + "\" not found."); } @@ -7574,8 +7746,9 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType base_type = DataType(*p_base_type); } - if (_get_member_type(base_type, p_identifier, member_type)) { - if (!p_base_type && current_function && current_function->_static) { + bool is_const = false; + if (_get_member_type(base_type, p_identifier, member_type, &is_const)) { + if (!p_base_type && current_function && current_function->_static && !is_const) { _set_error("Can't access member variable (\"" + p_identifier.operator String() + "\") from a static function.", p_line); return DataType(); } @@ -7766,12 +7939,14 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { // 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; + 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; + if (error_set) + return; } // Class variables @@ -7786,6 +7961,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { _mark_line_as_safe(v.line); v.data_type = _resolve_type(v.data_type, v.line); + v.initial_assignment->arguments[0]->set_datatype(v.data_type); if (v.expression) { DataType expr_type = _reduce_node_type(v.expression); @@ -7810,7 +7986,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { ConstantNode *tgt_type = alloc_node<ConstantNode>(); tgt_type->line = v.line; - tgt_type->value = (int)v.data_type.builtin_type; + tgt_type->value = (int64_t)v.data_type.builtin_type; OperatorNode *convert_call = alloc_node<OperatorNode>(); convert_call->line = v.line; @@ -7846,7 +8022,8 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { } // Setter and getter - if (v.setter == StringName() && v.getter == StringName()) continue; + if (v.setter == StringName() && v.getter == StringName()) + continue; bool found_getter = false; bool found_setter = false; @@ -7889,10 +8066,12 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { return; } } - if (found_getter && found_setter) break; + if (found_getter && found_setter) + break; } - if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) continue; + 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++) { @@ -7923,7 +8102,8 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) { 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; + if (error_set) + return; current_class = p_class; } } @@ -8060,7 +8240,8 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) { _check_block_types(current_block); current_block = nullptr; current_function = nullptr; - if (error_set) return; + if (error_set) + return; } for (int i = 0; i < p_class->functions.size(); i++) { @@ -8070,7 +8251,8 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) { _check_block_types(current_block); current_block = nullptr; current_function = nullptr; - if (error_set) return; + if (error_set) + return; } #ifdef DEBUG_ENABLED @@ -8091,7 +8273,8 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) { 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; + if (error_set) + return; current_class = p_class; } } @@ -8181,7 +8364,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { ConstantNode *tgt_type = alloc_node<ConstantNode>(); tgt_type->line = lv->line; - tgt_type->value = (int)lv->datatype.builtin_type; + tgt_type->value = (int64_t)lv->datatype.builtin_type; tgt_type->datatype = _type_from_variant(tgt_type->value); OperatorNode *convert_call = alloc_node<OperatorNode>(); @@ -8359,7 +8542,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { _add_warning(GDScriptWarning::RETURN_VALUE_DISCARDED, op->line, func_name); } #endif // DEBUG_ENABLED - if (error_set) return; + if (error_set) + return; } break; case OperatorNode::OP_YIELD: { _mark_line_as_safe(op->line); @@ -8394,7 +8578,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { } } - if (!function_type.has_type) break; + if (!function_type.has_type) + break; if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) { // Return void, should not have arguments @@ -8454,7 +8639,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { current_block = p_block->sub_blocks[i]; _check_block_types(current_block); current_block = p_block; - if (error_set) return; + if (error_set) + return; } #ifdef DEBUG_ENABLED @@ -8597,7 +8783,8 @@ Error GDScriptParser::_parse(const String &p_base_path) { current_function = nullptr; current_block = nullptr; - if (for_completion) check_types = false; + if (for_completion) + check_types = false; // Resolve all class-level stuff before getting into function blocks _check_class_level_types(main_class); @@ -8786,7 +8973,7 @@ int GDScriptParser::get_completion_argument_index() { return completion_argument; } -int GDScriptParser::get_completion_identifier_is_function() { +bool GDScriptParser::get_completion_identifier_is_function() { return completion_ident_is_call; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index f254352423..035af30b6a 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -45,25 +45,27 @@ public: struct ClassNode; struct DataType { - enum { + enum Kind { BUILTIN, NATIVE, SCRIPT, GDSCRIPT, CLASS, UNRESOLVED - } kind; + }; + + Kind kind = UNRESOLVED; - 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 + bool has_type = false; + bool is_constant = false; + bool is_meta_type = false; // Whether the value can be used as a type + bool infer_type = false; + bool may_yield = false; // For function calls - Variant::Type builtin_type; + Variant::Type builtin_type = Variant::NIL; StringName native_type; Ref<Script> script_type; - ClassNode *class_type; + ClassNode *class_type = nullptr; String to_string() const; @@ -94,15 +96,7 @@ public: return false; } - DataType() : - kind(UNRESOLVED), - has_type(false), - is_constant(false), - is_meta_type(false), - infer_type(false), - may_yield(false), - builtin_type(Variant::NIL), - class_type(nullptr) {} + DataType() {} }; struct Node { @@ -236,66 +230,63 @@ public: struct BlockNode : public Node { - ClassNode *parent_class; - BlockNode *parent_block; + ClassNode *parent_class = nullptr; + BlockNode *parent_block = nullptr; List<Node *> statements; Map<StringName, LocalVarNode *> variables; - bool has_return; + bool has_return = false; - Node *if_condition; //tiny hack to improve code completion on if () blocks + Node *if_condition = nullptr; //tiny hack to improve code completion on if () blocks //the following is useful for code completion List<BlockNode *> sub_blocks; - int end_line; + int end_line = -1; + BlockNode() { - if_condition = nullptr; type = TYPE_BLOCK; - end_line = -1; - parent_block = nullptr; - parent_class = nullptr; - has_return = false; } }; struct TypeNode : public Node { - Variant::Type vtype; - TypeNode() { type = TYPE_TYPE; } + + TypeNode() { + type = TYPE_TYPE; + } }; + struct BuiltInFunctionNode : public Node { GDScriptFunctions::Function function; - BuiltInFunctionNode() { type = TYPE_BUILT_IN_FUNCTION; } + + BuiltInFunctionNode() { + type = TYPE_BUILT_IN_FUNCTION; + } }; struct IdentifierNode : public Node { - StringName name; - BlockNode *declared_block; // Simplify lookup by checking if it is declared locally + BlockNode *declared_block = nullptr; // 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 = nullptr; } }; struct LocalVarNode : public Node { - StringName name; - Node *assign; - OperatorNode *assign_op; - int assignments; - int usages; + Node *assign = nullptr; + OperatorNode *assign_op = nullptr; + int assignments = 0; + int usages = 0; 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 = nullptr; - assign_op = nullptr; - assignments = 0; - usages = 0; } }; @@ -304,15 +295,18 @@ public: DataType datatype; virtual DataType get_datatype() const { return datatype; } virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } - ConstantNode() { type = TYPE_CONSTANT; } + + ConstantNode() { + type = TYPE_CONSTANT; + } }; struct ArrayNode : public Node { - Vector<Node *> elements; 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; @@ -324,7 +318,6 @@ public: struct DictionaryNode : public Node { struct Pair { - Node *key; Node *value; }; @@ -333,6 +326,7 @@ public: 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; @@ -342,7 +336,9 @@ public: }; struct SelfNode : public Node { - SelfNode() { type = TYPE_SELF; } + SelfNode() { + type = TYPE_SELF; + } }; struct OperatorNode : public Node { @@ -404,7 +400,9 @@ public: DataType datatype; virtual DataType get_datatype() const { return datatype; } virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } - OperatorNode() { type = TYPE_OPERATOR; } + OperatorNode() { + type = TYPE_OPERATOR; + } }; struct PatternNode : public Node { @@ -454,19 +452,17 @@ public: CF_MATCH }; - CFType cf_type; + CFType cf_type = CF_IF; Vector<Node *> arguments; - BlockNode *body; - BlockNode *body_else; + BlockNode *body = nullptr; + BlockNode *body_else = nullptr; MatchNode *match; ControlFlowNode *_else; //used for if + ControlFlowNode() { type = TYPE_CONTROL_FLOW; - cf_type = CF_IF; - body = nullptr; - body_else = nullptr; } }; @@ -476,29 +472,34 @@ public: 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; } + + CastNode() { + type = TYPE_CAST; + } }; struct AssertNode : public Node { - Node *condition; - Node *message; - AssertNode() : - condition(0), - message(0) { + Node *condition = nullptr; + Node *message = nullptr; + + AssertNode() { type = TYPE_ASSERT; } }; struct BreakpointNode : public Node { - BreakpointNode() { type = TYPE_BREAKPOINT; } + BreakpointNode() { + type = TYPE_BREAKPOINT; + } }; struct NewLineNode : public Node { - NewLineNode() { type = TYPE_NEWLINE; } + NewLineNode() { + type = TYPE_NEWLINE; + } }; struct Expression { - bool is_op; union { OperatorNode::Operator op; @@ -553,8 +554,8 @@ private: int pending_newline; struct IndentLevel { - int indent; - int tabs; + int indent = 0; + int tabs = 0; bool is_mixed(IndentLevel other) { return ( @@ -563,9 +564,7 @@ private: (indent < other.indent && tabs > other.tabs)); } - IndentLevel() : - indent(0), - tabs(0) {} + IndentLevel() {} IndentLevel(int p_indent, int p_tabs) : indent(p_indent), @@ -624,6 +623,7 @@ private: void _parse_extends(ClassNode *p_class); void _parse_class(ClassNode *p_class); bool _end_statement(); + void _set_end_statement_error(String p_name); void _determine_inheritance(ClassNode *p_class, bool p_recursive = true); bool _parse_type(DataType &r_type, bool p_can_be_void = false); @@ -634,7 +634,7 @@ private: 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 _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type, bool *r_is_const = nullptr) const; bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const; Node *_get_default_value_for_type(const DataType &p_type, int p_line = -1); @@ -647,12 +647,14 @@ private: 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); + 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); + if (safe_lines) + safe_lines->erase(p_line); #endif // DEBUG_ENABLED } @@ -683,7 +685,7 @@ public: BlockNode *get_completion_block(); FunctionNode *get_completion_function(); int get_completion_argument_index(); - int get_completion_identifier_is_function(); + bool get_completion_identifier_is_function(); const List<String> &get_dependencies() const { return dependencies; } diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 76f42ead5f..1c8282e13e 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -803,16 +803,36 @@ void GDScriptTokenizerText::_advance() { switch (next) { - case 'a': res = '\a'; break; - case 'b': res = '\b'; break; - case 't': res = '\t'; break; - case 'n': res = '\n'; break; - case 'v': res = '\v'; break; - case 'f': res = '\f'; break; - case 'r': res = '\r'; break; - case '\'': res = '\''; break; - case '\"': res = '\"'; break; - case '\\': res = '\\'; break; + case 'a': + res = '\a'; + break; + case 'b': + res = '\b'; + break; + case 't': + res = '\t'; + break; + case 'n': + res = '\n'; + break; + case 'v': + res = '\v'; + break; + case 'f': + res = '\f'; + break; + case 'r': + res = '\r'; + break; + case '\'': + res = '\''; + break; + case '\"': + res = '\"'; + break; + case '\\': + res = '\\'; + break; case 'u': { // hex number diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 180ec3c77e..76410433de 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -176,7 +176,7 @@ public: virtual bool is_ignoring_warnings() const = 0; #endif // DEBUG_ENABLED - virtual ~GDScriptTokenizer(){}; + virtual ~GDScriptTokenizer() {} }; class GDScriptTokenizerText : public GDScriptTokenizer { diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index b2c6b0e1ab..a6b749059a 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -385,7 +385,8 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { int start_line = p_docs_down ? p_line : p_line - 1; for (int i = start_line; true; i += step) { - if (i < 0 || i >= lines.size()) break; + if (i < 0 || i >= lines.size()) + break; String line_comment = lines[i].strip_edges(true, false); if (line_comment.begins_with("#")) { diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 32fc8f36f0..be036b44c4 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -185,7 +185,8 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) { } Error GDScriptWorkspace::initialize() { - if (initialized) return OK; + if (initialized) + return OK; DocData *doc = EditorHelp::get_doc_data(); for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 124fcbfed8..e469a26df8 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -282,7 +282,8 @@ struct Command { Dictionary dict; dict["title"] = title; dict["command"] = command; - if (arguments.size()) dict["arguments"] = arguments; + if (arguments.size()) + dict["arguments"] = arguments; return dict; } }; @@ -946,16 +947,20 @@ struct CompletionItem { dict["preselect"] = preselect; dict["sortText"] = sortText; dict["filterText"] = filterText; - if (commitCharacters.size()) dict["commitCharacters"] = commitCharacters; + if (commitCharacters.size()) + dict["commitCharacters"] = commitCharacters; dict["command"] = command.to_json(); } return dict; } void load(const Dictionary &p_dict) { - if (p_dict.has("label")) label = p_dict["label"]; - if (p_dict.has("kind")) kind = p_dict["kind"]; - if (p_dict.has("detail")) detail = p_dict["detail"]; + if (p_dict.has("label")) + label = p_dict["label"]; + if (p_dict.has("kind")) + kind = p_dict["kind"]; + if (p_dict.has("detail")) + detail = p_dict["detail"]; if (p_dict.has("documentation")) { Variant doc = p_dict["documentation"]; if (doc.get_type() == Variant::STRING) { @@ -965,12 +970,18 @@ struct CompletionItem { documentation.value = v["value"]; } } - if (p_dict.has("deprecated")) deprecated = p_dict["deprecated"]; - if (p_dict.has("preselect")) preselect = p_dict["preselect"]; - if (p_dict.has("sortText")) sortText = p_dict["sortText"]; - if (p_dict.has("filterText")) filterText = p_dict["filterText"]; - if (p_dict.has("insertText")) insertText = p_dict["insertText"]; - if (p_dict.has("data")) data = p_dict["data"]; + if (p_dict.has("deprecated")) + deprecated = p_dict["deprecated"]; + if (p_dict.has("preselect")) + preselect = p_dict["preselect"]; + if (p_dict.has("sortText")) + sortText = p_dict["sortText"]; + if (p_dict.has("filterText")) + filterText = p_dict["filterText"]; + if (p_dict.has("insertText")) + insertText = p_dict["insertText"]; + if (p_dict.has("data")) + data = p_dict["data"]; } }; diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp index 2540ba476c..5203460830 100644 --- a/modules/glslang/register_types.cpp +++ b/modules/glslang/register_types.cpp @@ -130,15 +130,15 @@ static const TBuiltInResource default_builtin_resource = { /*maxTaskWorkGroupSizeZ_NV*/ 0, /*maxMeshViewCountNV*/ 0, /*limits*/ { - /*nonInductiveForLoops*/ 1, - /*whileLoops*/ 1, - /*doWhileLoops*/ 1, - /*generalUniformIndexing*/ 1, - /*generalAttributeMatrixVectorIndexing*/ 1, - /*generalVaryingIndexing*/ 1, - /*generalSamplerIndexing*/ 1, - /*generalVariableIndexing*/ 1, - /*generalConstantMatrixVectorIndexing*/ 1, + /*nonInductiveForLoops*/ true, + /*whileLoops*/ true, + /*doWhileLoops*/ true, + /*generalUniformIndexing*/ true, + /*generalAttributeMatrixVectorIndexing*/ true, + /*generalVaryingIndexing*/ true, + /*generalSamplerIndexing*/ true, + /*generalVariableIndexing*/ true, + /*generalConstantMatrixVectorIndexing*/ true, } }; diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 9abbac6a0b..fc545430f5 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -1405,8 +1405,8 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { Vector3 points[4]; for (int j = 0; j < 4; j++) { - static const bool orderx[4] = { 0, 1, 1, 0 }; - static const bool ordery[4] = { 0, 0, 1, 1 }; + static const bool orderx[4] = { false, true, true, false }; + static const bool ordery[4] = { false, false, true, true }; Vector3 sp; if (orderx[j]) { diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp index 9e87d11ac1..1a2afeb5b9 100644 --- a/modules/jpg/image_loader_jpegd.cpp +++ b/modules/jpg/image_loader_jpegd.cpp @@ -96,7 +96,7 @@ Error jpeg_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p else fmt = Image::FORMAT_RGB8; - p_image->create(image_width, image_height, 0, fmt, data); + p_image->create(image_width, image_height, false, fmt, data); return OK; } diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp index 393269d422..208ce24f3d 100644 --- a/modules/jsonrpc/jsonrpc.cpp +++ b/modules/jsonrpc/jsonrpc.cpp @@ -148,7 +148,8 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem String JSONRPC::process_string(const String &p_input) { - if (p_input.empty()) return String(); + if (p_input.empty()) + return String(); Variant ret; Variant input; diff --git a/modules/lightmapper_rd/SCsub b/modules/lightmapper_rd/SCsub new file mode 100644 index 0000000000..2f04f1833e --- /dev/null +++ b/modules/lightmapper_rd/SCsub @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_lightmapper_rd = env_modules.Clone() +env_lightmapper_rd.GLSL_HEADER("lm_raster.glsl") +env_lightmapper_rd.GLSL_HEADER("lm_compute.glsl") +env_lightmapper_rd.GLSL_HEADER("lm_blendseams.glsl") + +# Godot source files +env_lightmapper_rd.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/lightmapper_rd/config.py b/modules/lightmapper_rd/config.py new file mode 100644 index 0000000000..d22f9454ed --- /dev/null +++ b/modules/lightmapper_rd/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return True + + +def configure(env): + pass diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp new file mode 100644 index 0000000000..6983c222c0 --- /dev/null +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -0,0 +1,1754 @@ +#include "lightmapper_rd.h" +#include "core/math/geometry.h" +#include "core/project_settings.h" +#include "lm_blendseams.glsl.gen.h" +#include "lm_compute.glsl.gen.h" +#include "lm_raster.glsl.gen.h" +#include "servers/rendering/rendering_device_binds.h" + +//uncomment this if you want to see textures from all the process saved +//#define DEBUG_TEXTURES + +void LightmapperRD::add_mesh(const MeshData &p_mesh) { + ERR_FAIL_COND(p_mesh.albedo_on_uv2.is_null() || p_mesh.albedo_on_uv2->empty()); + ERR_FAIL_COND(p_mesh.emission_on_uv2.is_null() || p_mesh.emission_on_uv2->empty()); + ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_width() != p_mesh.emission_on_uv2->get_width()); + ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_height() != p_mesh.emission_on_uv2->get_height()); + ERR_FAIL_COND(p_mesh.points.size() == 0); + MeshInstance mi; + mi.data = p_mesh; + mesh_instances.push_back(mi); +} + +void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance) { + Light l; + l.type = LIGHT_TYPE_DIRECTIONAL; + l.direction[0] = p_direction.x; + l.direction[1] = p_direction.y; + l.direction[2] = p_direction.z; + l.color[0] = p_color.r; + l.color[1] = p_color.g; + l.color[2] = p_color.b; + l.energy = p_energy; + l.static_bake = p_static; + l.size = p_angular_distance; + lights.push_back(l); +} +void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size) { + Light l; + l.type = LIGHT_TYPE_OMNI; + l.position[0] = p_position.x; + l.position[1] = p_position.y; + l.position[2] = p_position.z; + l.range = p_range; + l.attenuation = p_attenuation; + l.color[0] = p_color.r; + l.color[1] = p_color.g; + l.color[2] = p_color.b; + l.energy = p_energy; + l.static_bake = p_static; + l.size = p_size; + lights.push_back(l); +} +void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size) { + + Light l; + l.type = LIGHT_TYPE_SPOT; + l.position[0] = p_position.x; + l.position[1] = p_position.y; + l.position[2] = p_position.z; + l.direction[0] = p_direction.x; + l.direction[1] = p_direction.y; + l.direction[2] = p_direction.z; + l.range = p_range; + l.attenuation = p_attenuation; + l.spot_angle = Math::deg2rad(p_spot_angle); + l.spot_attenuation = p_spot_attenuation; + l.color[0] = p_color.r; + l.color[1] = p_color.g; + l.color[2] = p_color.b; + l.energy = p_energy; + l.static_bake = p_static; + l.size = p_size; + lights.push_back(l); +} + +void LightmapperRD::add_probe(const Vector3 &p_position) { + Probe probe; + probe.position[0] = p_position.x; + probe.position[1] = p_position.y; + probe.position[2] = p_position.z; + probe.position[3] = 0; + probe_positions.push_back(probe); +} + +void LightmapperRD::_plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[3], uint32_t p_triangle_index, LocalVector<TriangleSort> &triangles, uint32_t p_grid_size) { + + int half_size = p_size / 2; + + for (int i = 0; i < 8; i++) { + + AABB aabb = p_bounds; + aabb.size *= 0.5; + Vector3i n = p_ofs; + + if (i & 1) { + aabb.position.x += aabb.size.x; + n.x += half_size; + } + if (i & 2) { + aabb.position.y += aabb.size.y; + n.y += half_size; + } + if (i & 4) { + aabb.position.z += aabb.size.z; + n.z += half_size; + } + + { + Vector3 qsize = aabb.size * 0.5; //quarter size, for fast aabb test + + if (!Geometry::triangle_box_overlap(aabb.position + qsize, qsize, p_points)) { + //does not fit in child, go on + continue; + } + } + + if (half_size == 1) { + //got to the end + TriangleSort ts; + ts.cell_index = n.x + (n.y * p_grid_size) + (n.z * p_grid_size * p_grid_size); + ts.triangle_index = p_triangle_index; + triangles.push_back(ts); + } else { + _plot_triangle_into_triangle_index_list(half_size, n, aabb, p_points, p_triangle_index, triangles, p_grid_size); + } + } +} + +Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata) { + + Vector<Size2i> sizes; + + for (int m_i = 0; m_i < mesh_instances.size(); m_i++) { + + MeshInstance &mi = mesh_instances.write[m_i]; + Size2i s = Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height()); + sizes.push_back(s); + atlas_size.width = MAX(atlas_size.width, s.width); + atlas_size.height = MAX(atlas_size.height, s.height); + } + + int max = nearest_power_of_2_templated(atlas_size.width); + max = MAX(max, nearest_power_of_2_templated(atlas_size.height)); + + if (max > p_max_texture_size) { + return BAKE_ERROR_LIGHTMAP_TOO_SMALL; + } + + if (p_step_function) { + p_step_function(0.1, TTR("Determining optimal atlas size"), p_bake_userdata, true); + } + + atlas_size = Size2i(max, max); + + Size2i best_atlas_size; + int best_atlas_slices = 0; + int best_atlas_memory = 0x7FFFFFFF; + Vector<Vector3i> best_atlas_offsets; + + //determine best texture array atlas size by bruteforce fitting + while (atlas_size.x <= p_max_texture_size && atlas_size.y <= p_max_texture_size) { + + Vector<Vector2i> source_sizes = sizes; + Vector<int> source_indices; + source_indices.resize(source_sizes.size()); + for (int i = 0; i < source_indices.size(); i++) { + source_indices.write[i] = i; + } + Vector<Vector3i> atlas_offsets; + atlas_offsets.resize(source_sizes.size()); + + int slices = 0; + + while (source_sizes.size() > 0) { + + Vector<Vector3i> offsets = Geometry::partial_pack_rects(source_sizes, atlas_size); + Vector<int> new_indices; + Vector<Vector2i> new_sources; + for (int i = 0; i < offsets.size(); i++) { + Vector3i ofs = offsets[i]; + int sidx = source_indices[i]; + if (ofs.z > 0) { + //valid + ofs.z = slices; + atlas_offsets.write[sidx] = ofs; + } else { + new_indices.push_back(sidx); + new_sources.push_back(source_sizes[i]); + } + } + + source_sizes = new_sources; + source_indices = new_indices; + slices++; + } + + int mem_used = atlas_size.x * atlas_size.y * slices; + if (mem_used < best_atlas_memory) { + best_atlas_size = atlas_size; + best_atlas_offsets = atlas_offsets; + best_atlas_slices = slices; + best_atlas_memory = mem_used; + } + + if (atlas_size.width == atlas_size.height) { + atlas_size.width *= 2; + } else { + atlas_size.height *= 2; + } + } + atlas_size = best_atlas_size; + atlas_slices = best_atlas_slices; + + // apply the offsets and slice to all images, and also blit albedo and emission + albedo_images.resize(atlas_slices); + emission_images.resize(atlas_slices); + + if (p_step_function) { + p_step_function(0.2, TTR("Blitting albedo and emission"), p_bake_userdata, true); + } + + for (int i = 0; i < atlas_slices; i++) { + Ref<Image> albedo; + albedo.instance(); + albedo->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBA8); + albedo->set_as_black(); + albedo_images.write[i] = albedo; + + Ref<Image> emission; + emission.instance(); + emission->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH); + emission->set_as_black(); + emission_images.write[i] = emission; + } + + //assign uv positions + + for (int m_i = 0; m_i < mesh_instances.size(); m_i++) { + + MeshInstance &mi = mesh_instances.write[m_i]; + mi.offset.x = best_atlas_offsets[m_i].x; + mi.offset.y = best_atlas_offsets[m_i].y; + mi.slice = best_atlas_offsets[m_i].z; + albedo_images.write[mi.slice]->blit_rect(mi.data.albedo_on_uv2, Rect2(Vector2(), Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height())), mi.offset); + emission_images.write[mi.slice]->blit_rect(mi.data.emission_on_uv2, Rect2(Vector2(), Size2i(mi.data.emission_on_uv2->get_width(), mi.data.emission_on_uv2->get_height())), mi.offset); + } + + return BAKE_OK; +} + +void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &grid_texture_sdf, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) { + + HashMap<Vertex, uint32_t, VertexHash> vertex_map; + + //fill triangles array and vertex array + LocalVector<Triangle> triangles; + LocalVector<Vertex> vertex_array; + LocalVector<Box> box_array; + LocalVector<Seam> seams; + + slice_triangle_count.resize(atlas_slices); + slice_seam_count.resize(atlas_slices); + + for (int i = 0; i < atlas_slices; i++) { + slice_triangle_count.write[i] = 0; + slice_seam_count.write[i] = 0; + } + + bounds = AABB(); + + for (int m_i = 0; m_i < mesh_instances.size(); m_i++) { + + if (p_step_function) { + float p = float(m_i + 1) / mesh_instances.size() * 0.1; + p_step_function(0.3 + p, vformat(TTR("Plotting mesh into acceleration structure %d/%d"), m_i + 1, mesh_instances.size()), p_bake_userdata, false); + } + + HashMap<Edge, EdgeUV2, EdgeHash> edges; + + MeshInstance &mi = mesh_instances.write[m_i]; + + Vector2 uv_scale = Vector2(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height()) / Vector2(atlas_size); + Vector2 uv_offset = Vector2(mi.offset) / Vector2(atlas_size); + if (m_i == 0) { + bounds.position = mi.data.points[0]; + } + + for (int i = 0; i < mi.data.points.size(); i += 3) { + + Vector3 vtxs[3] = { mi.data.points[i + 0], mi.data.points[i + 1], mi.data.points[i + 2] }; + Vector2 uvs[3] = { mi.data.uv2[i + 0] * uv_scale + uv_offset, mi.data.uv2[i + 1] * uv_scale + uv_offset, mi.data.uv2[i + 2] * uv_scale + uv_offset }; + Vector3 normal[3] = { mi.data.normal[i + 0], mi.data.normal[i + 1], mi.data.normal[i + 2] }; + + AABB taabb; + Triangle t; + t.slice = mi.slice; + for (int k = 0; k < 3; k++) { + + bounds.expand_to(vtxs[k]); + + Vertex v; + v.position[0] = vtxs[k].x; + v.position[1] = vtxs[k].y; + v.position[2] = vtxs[k].z; + v.uv[0] = uvs[k].x; + v.uv[1] = uvs[k].y; + v.normal_xy[0] = normal[k].x; + v.normal_xy[1] = normal[k].y; + v.normal_z = normal[k].z; + + uint32_t *indexptr = vertex_map.getptr(v); + + if (indexptr) { + t.indices[k] = *indexptr; + } else { + uint32_t new_index = vertex_map.size(); + t.indices[k] = new_index; + vertex_map[v] = new_index; + vertex_array.push_back(v); + } + + if (k == 0) { + taabb.position = vtxs[k]; + } else { + taabb.expand_to(vtxs[k]); + } + } + + //compute seams that will need to be blended later + for (int k = 0; k < 3; k++) { + int n = (k + 1) % 3; + + Edge edge(vtxs[k], vtxs[n], normal[k], normal[n]); + Vector2i edge_indices(t.indices[k], t.indices[n]); + EdgeUV2 uv2(uvs[k], uvs[n], edge_indices); + + if (edge.b == edge.a) { + continue; //degenerate, somehow + } + if (edge.b < edge.a) { + SWAP(edge.a, edge.b); + SWAP(edge.na, edge.nb); + SWAP(uv2.a, uv2.b); + SWAP(edge_indices.x, edge_indices.y); + } + + EdgeUV2 *euv2 = edges.getptr(edge); + if (!euv2) { + edges[edge] = uv2; + } else { + if (*euv2 == uv2) { + continue; // seam shared UV space, no need to blend + } + if (euv2->seam_found) { + continue; //bad geometry + } + + Seam seam; + seam.a = edge_indices; + seam.b = euv2->indices; + seam.slice = mi.slice; + seams.push_back(seam); + slice_seam_count.write[mi.slice]++; + euv2->seam_found = true; + } + } + + Box box; + box.min_bounds[0] = taabb.position.x; + box.min_bounds[1] = taabb.position.y; + box.min_bounds[2] = taabb.position.z; + box.max_bounds[0] = taabb.position.x + MAX(taabb.size.x, 0.0001); + box.max_bounds[1] = taabb.position.y + MAX(taabb.size.y, 0.0001); + box.max_bounds[2] = taabb.position.z + MAX(taabb.size.z, 0.0001); + box.pad0 = box.pad1 = 0; //make valgrind not complain + box_array.push_back(box); + + triangles.push_back(t); + slice_triangle_count.write[t.slice]++; + } + } + + //also consider probe positions for bounds + for (int i = 0; i < probe_positions.size(); i++) { + Vector3 pp(probe_positions[i].position[0], probe_positions[i].position[1], probe_positions[i].position[2]); + bounds.expand_to(pp); + } + bounds.grow_by(0.1); //grow a bit to avoid numerical error + + triangles.sort(); //sort by slice + seams.sort(); + + if (p_step_function) { + p_step_function(0.4, TTR("Optimizing acceleration structure"), p_bake_userdata, true); + } + + //fill list of triangles in grid + LocalVector<TriangleSort> triangle_sort; + for (uint32_t i = 0; i < triangles.size(); i++) { + + const Triangle &t = triangles[i]; + Vector3 face[3] = { + Vector3(vertex_array[t.indices[0]].position[0], vertex_array[t.indices[0]].position[1], vertex_array[t.indices[0]].position[2]), + Vector3(vertex_array[t.indices[1]].position[0], vertex_array[t.indices[1]].position[1], vertex_array[t.indices[1]].position[2]), + Vector3(vertex_array[t.indices[2]].position[0], vertex_array[t.indices[2]].position[1], vertex_array[t.indices[2]].position[2]) + }; + _plot_triangle_into_triangle_index_list(grid_size, Vector3i(), bounds, face, i, triangle_sort, grid_size); + } + //sort it + triangle_sort.sort(); + + Vector<uint32_t> triangle_indices; + triangle_indices.resize(triangle_sort.size()); + Vector<uint32_t> grid_indices; + grid_indices.resize(grid_size * grid_size * grid_size * 2); + zeromem(grid_indices.ptrw(), grid_indices.size() * sizeof(uint32_t)); + Vector<bool> solid; + solid.resize(grid_size * grid_size * grid_size); + zeromem(solid.ptrw(), solid.size() * sizeof(bool)); + + { + uint32_t *tiw = triangle_indices.ptrw(); + uint32_t last_cell = 0xFFFFFFFF; + uint32_t *giw = grid_indices.ptrw(); + bool *solidw = solid.ptrw(); + for (uint32_t i = 0; i < triangle_sort.size(); i++) { + uint32_t cell = triangle_sort[i].cell_index; + if (cell != last_cell) { + //cell changed, update pointer to indices + giw[cell * 2 + 1] = i; + last_cell = cell; + solidw[cell] = true; + } + tiw[i] = triangle_sort[i].triangle_index; + giw[cell * 2]++; //update counter + last_cell = cell; + } + } +#if 0 + for (int i = 0; i < grid_size; i++) { + for (int j = 0; j < grid_size; j++) { + for (int k = 0; k < grid_size; k++) { + uint32_t index = i * (grid_size * grid_size) + j * grid_size + k; + grid_indices.write[index * 2] = float(i) / grid_size * 255; + grid_indices.write[index * 2 + 1] = float(j) / grid_size * 255; + } + } + } +#endif + +#if 0 + for (int i = 0; i < grid_size; i++) { + Vector<uint8_t> grid_usage; + grid_usage.resize(grid_size * grid_size); + for (int j = 0; j < grid_usage.size(); j++) { + uint32_t ofs = i * grid_size * grid_size + j; + uint32_t count = grid_indices[ofs * 2]; + grid_usage.write[j] = count > 0 ? 255 : 0; + } + + Ref<Image> img; + img.instance(); + img->create(grid_size, grid_size, false, Image::FORMAT_L8, grid_usage); + img->save_png("res://grid_layer_" + itos(1000 + i).substr(1, 3) + ".png"); + } +#endif + if (p_step_function) { + p_step_function(0.45, TTR("Generating Signed Distance Field"), p_bake_userdata, true); + } + + //generate SDF for raytracing + Vector<uint32_t> euclidean_pos = Geometry::generate_edf(solid, Vector3i(grid_size, grid_size, grid_size), false); + Vector<uint32_t> euclidean_neg = Geometry::generate_edf(solid, Vector3i(grid_size, grid_size, grid_size), true); + Vector<int8_t> sdf8 = Geometry::generate_sdf8(euclidean_pos, euclidean_neg); + + /*****************************/ + /*** CREATE GPU STRUCTURES ***/ + /*****************************/ + + lights.sort(); + + Vector<Vector2i> seam_buffer_vec; + seam_buffer_vec.resize(seams.size() * 2); + for (uint32_t i = 0; i < seams.size(); i++) { + seam_buffer_vec.write[i * 2 + 0] = seams[i].a; + seam_buffer_vec.write[i * 2 + 1] = seams[i].b; + } + + { //buffers + Vector<uint8_t> vb = vertex_array.to_byte_array(); + vertex_buffer = rd->storage_buffer_create(vb.size(), vb); + + Vector<uint8_t> tb = triangles.to_byte_array(); + triangle_buffer = rd->storage_buffer_create(tb.size(), tb); + + Vector<uint8_t> bb = box_array.to_byte_array(); + box_buffer = rd->storage_buffer_create(bb.size(), bb); + + Vector<uint8_t> tib = triangle_indices.to_byte_array(); + triangle_cell_indices_buffer = rd->storage_buffer_create(tib.size(), tib); + + Vector<uint8_t> lb = lights.to_byte_array(); + if (lb.size() == 0) { + lb.resize(sizeof(Light)); //even if no lights, the buffer must exist + } + lights_buffer = rd->storage_buffer_create(lb.size(), lb); + + Vector<uint8_t> sb = seam_buffer_vec.to_byte_array(); + if (sb.size() == 0) { + sb.resize(sizeof(Vector2i) * 2); //even if no seams, the buffer must exist + } + seams_buffer = rd->storage_buffer_create(sb.size(), sb); + + Vector<uint8_t> pb = probe_positions.to_byte_array(); + if (pb.size() == 0) { + pb.resize(sizeof(Probe)); + } + probe_positions_buffer = rd->storage_buffer_create(pb.size(), pb); + } + + { //grid + + RD::TextureFormat tf; + tf.width = grid_size; + tf.height = grid_size; + tf.depth = grid_size; + tf.type = RD::TEXTURE_TYPE_3D; + tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT; + + Vector<Vector<uint8_t>> texdata; + texdata.resize(1); + //grid and indices + tf.format = RD::DATA_FORMAT_R32G32_UINT; + texdata.write[0] = grid_indices.to_byte_array(); + grid_texture = rd->texture_create(tf, RD::TextureView(), texdata); + //sdf + tf.format = RD::DATA_FORMAT_R8_SNORM; + texdata.write[0] = sdf8.to_byte_array(); + grid_texture_sdf = rd->texture_create(tf, RD::TextureView(), texdata); + } +} + +void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform) { + + Vector<RID> framebuffers; + + for (int i = 0; i < atlas_slices; i++) { + RID slice_pos_tex = rd->texture_create_shared_from_slice(RD::TextureView(), position_tex, i, 0); + RID slice_unoc_tex = rd->texture_create_shared_from_slice(RD::TextureView(), unocclude_tex, i, 0); + RID slice_norm_tex = rd->texture_create_shared_from_slice(RD::TextureView(), normal_tex, i, 0); + Vector<RID> fb; + fb.push_back(slice_pos_tex); + fb.push_back(slice_norm_tex); + fb.push_back(slice_unoc_tex); + fb.push_back(raster_depth_buffer); + framebuffers.push_back(rd->framebuffer_create(fb)); + } + + RD::PipelineDepthStencilState ds; + ds.enable_depth_test = true; + ds.enable_depth_write = true; + ds.depth_compare_operator = RD::COMPARE_OP_LESS; //so it does render same pixel twice + + RID raster_pipeline = rd->render_pipeline_create(rasterize_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), ds, RD::PipelineColorBlendState::create_disabled(3), 0); + RID raster_pipeline_wire; + { + + RD::PipelineRasterizationState rw; + rw.wireframe = true; + raster_pipeline_wire = rd->render_pipeline_create(rasterize_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_TRIANGLES, rw, RD::PipelineMultisampleState(), ds, RD::PipelineColorBlendState::create_disabled(3), 0); + } + + uint32_t triangle_offset = 0; + Vector<Color> clear_colors; + clear_colors.push_back(Color(0, 0, 0, 0)); + clear_colors.push_back(Color(0, 0, 0, 0)); + clear_colors.push_back(Color(0, 0, 0, 0)); + + for (int i = 0; i < atlas_slices; i++) { + + RasterPushConstant raster_push_constant; + raster_push_constant.atlas_size[0] = atlas_size.x; + raster_push_constant.atlas_size[1] = atlas_size.y; + raster_push_constant.base_triangle = triangle_offset; + raster_push_constant.to_cell_offset[0] = bounds.position.x; + raster_push_constant.to_cell_offset[1] = bounds.position.y; + raster_push_constant.to_cell_offset[2] = bounds.position.z; + raster_push_constant.bias = p_bias; + raster_push_constant.to_cell_size[0] = (1.0 / bounds.size.x) * float(grid_size); + raster_push_constant.to_cell_size[1] = (1.0 / bounds.size.y) * float(grid_size); + raster_push_constant.to_cell_size[2] = (1.0 / bounds.size.z) * float(grid_size); + raster_push_constant.grid_size[0] = grid_size; + raster_push_constant.grid_size[1] = grid_size; + raster_push_constant.grid_size[2] = grid_size; + raster_push_constant.uv_offset[0] = 0; + raster_push_constant.uv_offset[1] = 0; + + RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + //draw opaque + rd->draw_list_bind_render_pipeline(draw_list, raster_pipeline); + rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0); + rd->draw_list_set_push_constant(draw_list, &raster_push_constant, sizeof(RasterPushConstant)); + rd->draw_list_draw(draw_list, false, 1, slice_triangle_count[i] * 3); + //draw wire + rd->draw_list_bind_render_pipeline(draw_list, raster_pipeline_wire); + rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0); + rd->draw_list_set_push_constant(draw_list, &raster_push_constant, sizeof(RasterPushConstant)); + rd->draw_list_draw(draw_list, false, 1, slice_triangle_count[i] * 3); + + rd->draw_list_end(); + + triangle_offset += slice_triangle_count[i]; + } +} + +LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata) { + + if (p_step_function) { + p_step_function(0.0, TTR("Begin Bake"), p_bake_userdata, true); + } + bake_textures.clear(); + int grid_size = 128; + + /* STEP 1: Fetch material textures and compute the bounds */ + + AABB bounds; + Size2i atlas_size; + int atlas_slices; + Vector<Ref<Image>> albedo_images; + Vector<Ref<Image>> emission_images; + + BakeError bake_error = _blit_meshes_into_atlas(p_max_texture_size, albedo_images, emission_images, bounds, atlas_size, atlas_slices, p_step_function, p_bake_userdata); + if (bake_error != BAKE_OK) { + return bake_error; + } + +#ifdef DEBUG_TEXTURES + for (int i = 0; i < atlas_slices; i++) { + albedo_images[i]->save_png("res://0_albedo_" + itos(i) + ".png"); + emission_images[i]->save_png("res://0_emission_" + itos(i) + ".png"); + } +#endif + + RenderingDevice *rd = RenderingDevice::get_singleton()->create_local_device(); + + RID albedo_array_tex; + RID emission_array_tex; + RID normal_tex; + RID position_tex; + RID unocclude_tex; + RID light_source_tex; + RID light_dest_tex; + RID light_accum_tex; + RID light_accum_tex2; + RID light_primary_dynamic_tex; + RID light_environment_tex; + +#define FREE_TEXTURES \ + rd->free(albedo_array_tex); \ + rd->free(emission_array_tex); \ + rd->free(normal_tex); \ + rd->free(position_tex); \ + rd->free(unocclude_tex); \ + rd->free(light_source_tex); \ + rd->free(light_accum_tex2); \ + rd->free(light_accum_tex); \ + rd->free(light_primary_dynamic_tex); \ + rd->free(light_environment_tex); + + { // create all textures + + Vector<Vector<uint8_t>> albedo_data; + Vector<Vector<uint8_t>> emission_data; + for (int i = 0; i < atlas_slices; i++) { + albedo_data.push_back(albedo_images[i]->get_data()); + emission_data.push_back(emission_images[i]->get_data()); + } + + RD::TextureFormat tf; + tf.width = atlas_size.width; + tf.height = atlas_size.height; + tf.array_layers = atlas_slices; + tf.type = RD::TEXTURE_TYPE_2D_ARRAY; + tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT; + tf.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + + albedo_array_tex = rd->texture_create(tf, RD::TextureView(), albedo_data); + + tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT; + + emission_array_tex = rd->texture_create(tf, RD::TextureView(), emission_data); + + //this will be rastered to + tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_STORAGE_BIT; + normal_tex = rd->texture_create(tf, RD::TextureView()); + tf.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; + position_tex = rd->texture_create(tf, RD::TextureView()); + unocclude_tex = rd->texture_create(tf, RD::TextureView()); + + tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT; + tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT; + + light_source_tex = rd->texture_create(tf, RD::TextureView()); + rd->texture_clear(light_source_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices); + light_primary_dynamic_tex = rd->texture_create(tf, RD::TextureView()); + rd->texture_clear(light_primary_dynamic_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices); + + if (p_bake_sh) { + tf.array_layers *= 4; + } + light_accum_tex = rd->texture_create(tf, RD::TextureView()); + rd->texture_clear(light_accum_tex, Color(0, 0, 0, 0), 0, 1, 0, tf.array_layers); + light_dest_tex = rd->texture_create(tf, RD::TextureView()); + rd->texture_clear(light_dest_tex, Color(0, 0, 0, 0), 0, 1, 0, tf.array_layers); + light_accum_tex2 = light_dest_tex; + + //env + { + Ref<Image> panorama_tex; + if (p_environment_panorama.is_valid()) { + panorama_tex = p_environment_panorama; + panorama_tex->convert(Image::FORMAT_RGBAF); + } else { + panorama_tex.instance(); + panorama_tex->create(8, 8, false, Image::FORMAT_RGBAF); + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + panorama_tex->set_pixel(i, j, Color(0, 0, 0, 1)); + } + } + } + + RD::TextureFormat tfp; + tfp.width = panorama_tex->get_width(); + tfp.height = panorama_tex->get_height(); + tfp.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT; + tfp.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; + + Vector<Vector<uint8_t>> tdata; + tdata.push_back(panorama_tex->get_data()); + light_environment_tex = rd->texture_create(tfp, RD::TextureView(), tdata); + +#ifdef DEBUG_TEXTURES + panorama_tex->convert(Image::FORMAT_RGB8); + panorama_tex->save_png("res://0_panorama.png"); +#endif + } + } + + /* STEP 2: create the acceleration structure for the GPU*/ + + Vector<int> slice_triangle_count; + RID vertex_buffer; + RID triangle_buffer; + RID box_buffer; + RID lights_buffer; + RID triangle_cell_indices_buffer; + RID grid_texture; + RID grid_texture_sdf; + RID seams_buffer; + RID probe_positions_buffer; + + Vector<int> slice_seam_count; + +#define FREE_BUFFERS \ + rd->free(vertex_buffer); \ + rd->free(triangle_buffer); \ + rd->free(box_buffer); \ + rd->free(lights_buffer); \ + rd->free(triangle_cell_indices_buffer); \ + rd->free(grid_texture); \ + rd->free(grid_texture_sdf); \ + rd->free(seams_buffer); \ + rd->free(probe_positions_buffer); + + _create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, box_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, grid_texture_sdf, seams_buffer, p_step_function, p_bake_userdata); + + if (p_step_function) { + p_step_function(0.47, TTR("Preparing shaders"), p_bake_userdata, true); + } + + //shaders + Ref<RDShaderFile> raster_shader; + raster_shader.instance(); + Error err = raster_shader->parse_versions_from_text(lm_raster_shader_glsl); + if (err != OK) { + raster_shader->print_errors("raster_shader"); + + FREE_TEXTURES + FREE_BUFFERS + + memdelete(rd); + } + ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); + + RID rasterize_shader = rd->shader_create_from_bytecode(raster_shader->get_bytecode()); + + ERR_FAIL_COND_V(rasterize_shader.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //this is a bug check, though, should not happen + + RID sampler; + { + RD::SamplerState s; + s.mag_filter = RD::SAMPLER_FILTER_LINEAR; + s.min_filter = RD::SAMPLER_FILTER_LINEAR; + s.max_lod = 0; + + sampler = rd->sampler_create(s); + } + + Vector<RD::Uniform> base_uniforms; + { + { + + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 1; + u.ids.push_back(vertex_buffer); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 2; + u.ids.push_back(triangle_buffer); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 3; + u.ids.push_back(box_buffer); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 4; + u.ids.push_back(triangle_cell_indices_buffer); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 5; + u.ids.push_back(lights_buffer); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 6; + u.ids.push_back(seams_buffer); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 7; + u.ids.push_back(probe_positions_buffer); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 8; + u.ids.push_back(grid_texture); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 9; + u.ids.push_back(grid_texture_sdf); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 10; + u.ids.push_back(albedo_array_tex); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 11; + u.ids.push_back(emission_array_tex); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_SAMPLER; + u.binding = 12; + u.ids.push_back(sampler); + base_uniforms.push_back(u); + } + } + + RID raster_base_uniform = rd->uniform_set_create(base_uniforms, rasterize_shader, 0); + RID raster_depth_buffer; + { + RD::TextureFormat tf; + tf.width = atlas_size.width; + tf.height = atlas_size.height; + tf.depth = 1; + tf.type = RD::TEXTURE_TYPE_2D; + tf.usage_bits = RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + tf.format = RD::DATA_FORMAT_D32_SFLOAT; + + raster_depth_buffer = rd->texture_create(tf, RD::TextureView()); + } + + rd->submit(); + rd->sync(); + + /* STEP 3: Raster the geometry to UV2 coords in the atlas textures GPU*/ + + _raster_geometry(rd, atlas_size, atlas_slices, grid_size, bounds, p_bias, slice_triangle_count, position_tex, unocclude_tex, normal_tex, raster_depth_buffer, rasterize_shader, raster_base_uniform); + +#ifdef DEBUG_TEXTURES + + for (int i = 0; i < atlas_slices; i++) { + Vector<uint8_t> s = rd->texture_get_data(position_tex, i); + Ref<Image> img; + img.instance(); + img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAF, s); + img->convert(Image::FORMAT_RGBA8); + img->save_png("res://1_position_" + itos(i) + ".png"); + + s = rd->texture_get_data(normal_tex, i); + img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); + img->convert(Image::FORMAT_RGBA8); + img->save_png("res://1_normal_" + itos(i) + ".png"); + } +#endif + +#define FREE_RASTER_RESOURCES \ + rd->free(rasterize_shader); \ + rd->free(sampler); \ + rd->free(raster_depth_buffer); + + /* Plot direct light */ + + Ref<RDShaderFile> compute_shader; + compute_shader.instance(); + err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, p_bake_sh ? "\n#define USE_SH_LIGHTMAPS\n" : ""); + if (err != OK) { + + FREE_TEXTURES + FREE_BUFFERS + FREE_RASTER_RESOURCES + memdelete(rd); + compute_shader->print_errors("compute_shader"); + } + ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); + + //unoccluder + RID compute_shader_unocclude = rd->shader_create_from_bytecode(compute_shader->get_bytecode("unocclude")); + ERR_FAIL_COND_V(compute_shader_unocclude.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); // internal check, should not happen + RID compute_shader_unocclude_pipeline = rd->compute_pipeline_create(compute_shader_unocclude); + + //direct light + RID compute_shader_primary = rd->shader_create_from_bytecode(compute_shader->get_bytecode("primary")); + ERR_FAIL_COND_V(compute_shader_primary.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); // internal check, should not happen + RID compute_shader_primary_pipeline = rd->compute_pipeline_create(compute_shader_primary); + + //indirect light + RID compute_shader_secondary = rd->shader_create_from_bytecode(compute_shader->get_bytecode("secondary")); + ERR_FAIL_COND_V(compute_shader_secondary.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen + RID compute_shader_secondary_pipeline = rd->compute_pipeline_create(compute_shader_secondary); + + //dilate + RID compute_shader_dilate = rd->shader_create_from_bytecode(compute_shader->get_bytecode("dilate")); + ERR_FAIL_COND_V(compute_shader_dilate.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen + RID compute_shader_dilate_pipeline = rd->compute_pipeline_create(compute_shader_dilate); + + //dilate + RID compute_shader_light_probes = rd->shader_create_from_bytecode(compute_shader->get_bytecode("light_probes")); + ERR_FAIL_COND_V(compute_shader_light_probes.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen + RID compute_shader_light_probes_pipeline = rd->compute_pipeline_create(compute_shader_light_probes); + + RID compute_base_uniform_set = rd->uniform_set_create(base_uniforms, compute_shader_primary, 0); + +#define FREE_COMPUTE_RESOURCES \ + rd->free(compute_shader_unocclude); \ + rd->free(compute_shader_primary); \ + rd->free(compute_shader_secondary); \ + rd->free(compute_shader_dilate); \ + rd->free(compute_shader_light_probes); + + PushConstant push_constant; + { + //set defaults + push_constant.atlas_size[0] = atlas_size.width; + push_constant.atlas_size[1] = atlas_size.height; + push_constant.world_size[0] = bounds.size.x; + push_constant.world_size[1] = bounds.size.y; + push_constant.world_size[2] = bounds.size.z; + push_constant.to_cell_offset[0] = bounds.position.x; + push_constant.to_cell_offset[1] = bounds.position.y; + push_constant.to_cell_offset[2] = bounds.position.z; + push_constant.bias = p_bias; + push_constant.to_cell_size[0] = (1.0 / bounds.size.x) * float(grid_size); + push_constant.to_cell_size[1] = (1.0 / bounds.size.y) * float(grid_size); + push_constant.to_cell_size[2] = (1.0 / bounds.size.z) * float(grid_size); + push_constant.light_count = lights.size(); + push_constant.grid_size = grid_size; + push_constant.atlas_slice = 0; + push_constant.region_ofs[0] = 0; + push_constant.region_ofs[1] = 0; + push_constant.environment_xform[0] = p_environment_transform.elements[0][0]; + push_constant.environment_xform[1] = p_environment_transform.elements[1][0]; + push_constant.environment_xform[2] = p_environment_transform.elements[2][0]; + push_constant.environment_xform[3] = 0; + push_constant.environment_xform[4] = p_environment_transform.elements[0][1]; + push_constant.environment_xform[5] = p_environment_transform.elements[1][1]; + push_constant.environment_xform[6] = p_environment_transform.elements[2][1]; + push_constant.environment_xform[7] = 0; + push_constant.environment_xform[8] = p_environment_transform.elements[0][2]; + push_constant.environment_xform[9] = p_environment_transform.elements[1][2]; + push_constant.environment_xform[10] = p_environment_transform.elements[2][2]; + push_constant.environment_xform[11] = 0; + } + + Vector3i group_size((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1); + rd->submit(); + rd->sync(); + + if (p_step_function) { + p_step_function(0.49, TTR("Un-occluding geometry"), p_bake_userdata, true); + } + + /* UNOCCLUDE */ + { + + Vector<RD::Uniform> uniforms; + { + { + + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 0; + u.ids.push_back(position_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 1; + u.ids.push_back(unocclude_tex); //will be unused + uniforms.push_back(u); + } + } + + RID unocclude_uniform_set = rd->uniform_set_create(uniforms, compute_shader_unocclude, 1); + + RD::ComputeListID compute_list = rd->compute_list_begin(); + rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_unocclude_pipeline); + rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); + rd->compute_list_bind_uniform_set(compute_list, unocclude_uniform_set, 1); + + for (int i = 0; i < atlas_slices; i++) { + push_constant.atlas_slice = i; + rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); + rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z); + //no barrier, let them run all together + } + rd->compute_list_end(); //done + } + + if (p_step_function) { + p_step_function(0.5, TTR("Plot direct lighting"), p_bake_userdata, true); + } + + /* PRIMARY (direct) LIGHT PASS */ + { + + Vector<RD::Uniform> uniforms; + { + { + + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 0; + u.ids.push_back(light_source_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 1; + u.ids.push_back(light_dest_tex); //will be unused + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 2; + u.ids.push_back(position_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 3; + u.ids.push_back(normal_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 4; + u.ids.push_back(light_accum_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 5; + u.ids.push_back(light_primary_dynamic_tex); + uniforms.push_back(u); + } + } + + RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1); + + RD::ComputeListID compute_list = rd->compute_list_begin(); + rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_primary_pipeline); + rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); + rd->compute_list_bind_uniform_set(compute_list, light_uniform_set, 1); + + for (int i = 0; i < atlas_slices; i++) { + push_constant.atlas_slice = i; + rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); + rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z); + //no barrier, let them run all together + } + rd->compute_list_end(); //done + } + +#ifdef DEBUG_TEXTURES + + for (int i = 0; i < atlas_slices; i++) { + Vector<uint8_t> s = rd->texture_get_data(light_source_tex, i); + Ref<Image> img; + img.instance(); + img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); + img->convert(Image::FORMAT_RGBA8); + img->save_png("res://2_light_primary_" + itos(i) + ".png"); + } +#endif + + /* SECONDARY (indirect) LIGHT PASS(ES) */ + if (p_step_function) { + p_step_function(0.6, TTR("Integrate indirect lighting"), p_bake_userdata, true); + } + + if (p_bounces > 0) { + + Vector<RD::Uniform> uniforms; + { + { + + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 0; + u.ids.push_back(light_dest_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 1; + u.ids.push_back(light_source_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 2; + u.ids.push_back(position_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 3; + u.ids.push_back(normal_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 4; + u.ids.push_back(light_accum_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 5; + u.ids.push_back(unocclude_tex); //reuse unocclude tex + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 6; + u.ids.push_back(light_environment_tex); //reuse unocclude tex + uniforms.push_back(u); + } + } + + RID secondary_uniform_set[2]; + secondary_uniform_set[0] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1); + uniforms.write[0].ids.write[0] = light_source_tex; + uniforms.write[1].ids.write[0] = light_dest_tex; + secondary_uniform_set[1] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1); + + switch (p_quality) { + case BAKE_QUALITY_LOW: { + push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/low_quality_ray_count"); + } break; + case BAKE_QUALITY_MEDIUM: { + push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/medium_quality_ray_count"); + } break; + case BAKE_QUALITY_HIGH: { + push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/high_quality_ray_count"); + } break; + case BAKE_QUALITY_ULTRA: { + push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/ultra_quality_ray_count"); + } break; + } + + push_constant.ray_count = CLAMP(push_constant.ray_count, 16, 8192); + + int max_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/gpu_lightmapper/performance/region_size"))); + int max_rays = GLOBAL_GET("rendering/gpu_lightmapper/performance/max_rays_per_pass"); + + int x_regions = (atlas_size.width - 1) / max_region_size + 1; + int y_regions = (atlas_size.height - 1) / max_region_size + 1; + int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1; + + rd->submit(); + rd->sync(); + + for (int b = 0; b < p_bounces; b++) { + int count = 0; + if (b > 0) { + SWAP(light_source_tex, light_dest_tex); + SWAP(secondary_uniform_set[0], secondary_uniform_set[1]); + } + + for (int s = 0; s < atlas_slices; s++) { + push_constant.atlas_slice = s; + + for (int i = 0; i < x_regions; i++) { + for (int j = 0; j < y_regions; j++) { + + int x = i * max_region_size; + int y = j * max_region_size; + int w = MIN((i + 1) * max_region_size, atlas_size.width) - x; + int h = MIN((j + 1) * max_region_size, atlas_size.height) - y; + + push_constant.region_ofs[0] = x; + push_constant.region_ofs[1] = y; + + group_size = Vector3i((w - 1) / 8 + 1, (h - 1) / 8 + 1, 1); + + for (int k = 0; k < ray_iterations; k++) { + + RD::ComputeListID compute_list = rd->compute_list_begin(); + rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_secondary_pipeline); + rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); + rd->compute_list_bind_uniform_set(compute_list, secondary_uniform_set[0], 1); + + push_constant.ray_from = k * max_rays; + push_constant.ray_to = MIN((k + 1) * max_rays, int32_t(push_constant.ray_count)); + rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); + rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z); + + rd->compute_list_end(); //done + rd->submit(); + rd->sync(); + + count++; + if (p_step_function) { + int total = (atlas_slices * x_regions * y_regions * ray_iterations); + int percent = count * 100 / total; + float p = float(count) / total * 0.1; + p_step_function(0.6 + p, vformat(TTR("Bounce %d/%d: Integrate indirect lighting %d%%"), b + 1, p_bounces, percent), p_bake_userdata, false); + } + } + } + } + } + } + } + + /* LIGHPROBES */ + + RID light_probe_buffer; + + if (probe_positions.size()) { + + light_probe_buffer = rd->storage_buffer_create(sizeof(float) * 4 * 9 * probe_positions.size()); + + if (p_step_function) { + p_step_function(0.7, TTR("Baking lightprobes"), p_bake_userdata, true); + } + + Vector<RD::Uniform> uniforms; + { + + { + + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 0; + u.ids.push_back(light_probe_buffer); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 1; + u.ids.push_back(light_dest_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 2; + u.ids.push_back(light_primary_dynamic_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 3; + u.ids.push_back(light_environment_tex); + uniforms.push_back(u); + } + } + RID light_probe_uniform_set = rd->uniform_set_create(uniforms, compute_shader_light_probes, 1); + + switch (p_quality) { + case BAKE_QUALITY_LOW: { + push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/low_quality_probe_ray_count"); + } break; + case BAKE_QUALITY_MEDIUM: { + push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/medium_quality_probe_ray_count"); + } break; + case BAKE_QUALITY_HIGH: { + push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/high_quality_probe_ray_count"); + } break; + case BAKE_QUALITY_ULTRA: { + push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/ultra_quality_probe_ray_count"); + } break; + } + + push_constant.atlas_size[0] = probe_positions.size(); + push_constant.ray_count = CLAMP(push_constant.ray_count, 16, 8192); + + int max_rays = GLOBAL_GET("rendering/gpu_lightmapper/performance/max_rays_per_probe_pass"); + int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1; + + for (int i = 0; i < ray_iterations; i++) { + + RD::ComputeListID compute_list = rd->compute_list_begin(); + rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_light_probes_pipeline); + rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); + rd->compute_list_bind_uniform_set(compute_list, light_probe_uniform_set, 1); + + push_constant.ray_from = i * max_rays; + push_constant.ray_to = MIN((i + 1) * max_rays, int32_t(push_constant.ray_count)); + rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); + rd->compute_list_dispatch(compute_list, (probe_positions.size() - 1) / 64 + 1, 1, 1); + + rd->compute_list_end(); //done + rd->submit(); + rd->sync(); + + if (p_step_function) { + int percent = i * 100 / ray_iterations; + float p = float(i) / ray_iterations * 0.1; + p_step_function(0.7 + p, vformat(TTR("Integrating light probes %d%%"), percent), p_bake_userdata, false); + } + } + + push_constant.atlas_size[0] = atlas_size.x; //restore + } + +#if 0 + for (int i = 0; i < probe_positions.size(); i++) { + Ref<Image> img; + img.instance(); + img->create(6, 4, false, Image::FORMAT_RGB8); + for (int j = 0; j < 6; j++) { + Vector<uint8_t> s = rd->texture_get_data(lightprobe_tex, i * 6 + j); + Ref<Image> img2; + img2.instance(); + img2->create(2, 2, false, Image::FORMAT_RGBAF, s); + img2->convert(Image::FORMAT_RGB8); + img->blit_rect(img2, Rect2(0, 0, 2, 2), Point2((j % 3) * 2, (j / 3) * 2)); + } + img->save_png("res://3_light_probe_" + itos(i) + ".png"); + } +#endif + + /* DENOISE */ + + if (p_use_denoiser) { + if (p_step_function) { + p_step_function(0.8, TTR("Denoising"), p_bake_userdata, true); + } + + Ref<LightmapDenoiser> denoiser = LightmapDenoiser::create(); + if (denoiser.is_valid()) { + for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { + Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i); + Ref<Image> img; + img.instance(); + img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); + + Ref<Image> denoised = denoiser->denoise_image(img); + if (denoised != img) { + denoised->convert(Image::FORMAT_RGBAH); + Vector<uint8_t> ds = denoised->get_data(); + denoised.unref(); //avoid copy on write + { //restore alpha + uint32_t count = s.size() / 2; //uint16s + const uint16_t *src = (const uint16_t *)s.ptr(); + uint16_t *dst = (uint16_t *)ds.ptrw(); + for (uint32_t j = 0; j < count; j += 4) { + dst[j + 3] = src[j + 3]; + } + } + rd->texture_update(light_accum_tex, i, ds, true); + } + } + } + } + +#ifdef DEBUG_TEXTURES + + for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { + Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i); + Ref<Image> img; + img.instance(); + img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); + img->convert(Image::FORMAT_RGBA8); + img->save_png("res://4_light_secondary_" + itos(i) + ".png"); + } +#endif + + /* DILATE LIGHTMAP */ + { + + SWAP(light_accum_tex, light_accum_tex2); + + Vector<RD::Uniform> uniforms; + { + { + + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 0; + u.ids.push_back(light_accum_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 1; + u.ids.push_back(light_accum_tex2); + uniforms.push_back(u); + } + } + + RID dilate_uniform_set = rd->uniform_set_create(uniforms, compute_shader_dilate, 1); + + RD::ComputeListID compute_list = rd->compute_list_begin(); + rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_dilate_pipeline); + rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); + rd->compute_list_bind_uniform_set(compute_list, dilate_uniform_set, 1); + push_constant.region_ofs[0] = 0; + push_constant.region_ofs[1] = 0; + group_size = Vector3i((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1); //restore group size + + for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { + push_constant.atlas_slice = i; + rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); + rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z); + //no barrier, let them run all together + } + rd->compute_list_end(); + } + +#ifdef DEBUG_TEXTURES + + for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { + Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i); + Ref<Image> img; + img.instance(); + img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); + img->convert(Image::FORMAT_RGBA8); + img->save_png("res://5_dilated_" + itos(i) + ".png"); + } +#endif + + /* BLEND SEAMS */ + //shaders + Ref<RDShaderFile> blendseams_shader; + blendseams_shader.instance(); + err = blendseams_shader->parse_versions_from_text(lm_blendseams_shader_glsl); + if (err != OK) { + FREE_TEXTURES + FREE_BUFFERS + FREE_RASTER_RESOURCES + FREE_COMPUTE_RESOURCES + memdelete(rd); + blendseams_shader->print_errors("blendseams_shader"); + } + ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); + + RID blendseams_line_raster_shader = rd->shader_create_from_bytecode(blendseams_shader->get_bytecode("lines")); + + ERR_FAIL_COND_V(blendseams_line_raster_shader.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); + + RID blendseams_triangle_raster_shader = rd->shader_create_from_bytecode(blendseams_shader->get_bytecode("triangles")); + + ERR_FAIL_COND_V(blendseams_triangle_raster_shader.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); + +#define FREE_BLENDSEAMS_RESOURCES \ + rd->free(blendseams_line_raster_shader); \ + rd->free(blendseams_triangle_raster_shader); + + { + + //pre copy + for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { + rd->texture_copy(light_accum_tex, light_accum_tex2, Vector3(), Vector3(), Vector3(atlas_size.width, atlas_size.height, 1), 0, 0, i, i, true); + } + + Vector<RID> framebuffers; + for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { + RID slice_tex = rd->texture_create_shared_from_slice(RD::TextureView(), light_accum_tex, i, 0); + Vector<RID> fb; + fb.push_back(slice_tex); + fb.push_back(raster_depth_buffer); + framebuffers.push_back(rd->framebuffer_create(fb)); + } + + Vector<RD::Uniform> uniforms; + { + { + + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 0; + u.ids.push_back(light_accum_tex2); + uniforms.push_back(u); + } + } + + RID blendseams_raster_uniform = rd->uniform_set_create(uniforms, blendseams_line_raster_shader, 1); + + bool debug = false; + RD::PipelineColorBlendState bs = RD::PipelineColorBlendState::create_blend(1); + bs.attachments.write[0].src_alpha_blend_factor = RD::BLEND_FACTOR_ZERO; + bs.attachments.write[0].dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE; + + RD::PipelineDepthStencilState ds; + ds.enable_depth_test = true; + ds.enable_depth_write = true; + ds.depth_compare_operator = RD::COMPARE_OP_LESS; //so it does not render same pixel twice, this avoids wrong blending + + RID blendseams_line_raster_pipeline = rd->render_pipeline_create(blendseams_line_raster_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_LINES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), ds, bs, 0); + RID blendseams_triangle_raster_pipeline = rd->render_pipeline_create(blendseams_triangle_raster_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), ds, bs, 0); + + uint32_t seam_offset = 0; + uint32_t triangle_offset = 0; + + Vector<Color> clear_colors; + clear_colors.push_back(Color(0, 0, 0, 1)); + for (int i = 0; i < atlas_slices; i++) { + + int subslices = (p_bake_sh ? 4 : 1); + for (int k = 0; k < subslices; k++) { + + RasterSeamsPushConstant seams_push_constant; + seams_push_constant.slice = uint32_t(i * subslices + k); + seams_push_constant.debug = debug; + + RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + + rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0); + rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1); + + const int uv_offset_count = 9; + static const Vector3 uv_offsets[uv_offset_count] = { + Vector3(0, 0, 0.5), //using zbuffer, so go inwards-outwards + Vector3(0, 1, 0.2), + Vector3(0, -1, 0.2), + Vector3(1, 0, 0.2), + Vector3(-1, 0, 0.2), + Vector3(-1, -1, 0.1), + Vector3(1, -1, 0.1), + Vector3(1, 1, 0.1), + Vector3(-1, 1, 0.1), + }; + + /* step 1 use lines to blend the edges */ + { + seams_push_constant.base_index = seam_offset; + rd->draw_list_bind_render_pipeline(draw_list, blendseams_line_raster_pipeline); + seams_push_constant.uv_offset[0] = uv_offsets[0].x / float(atlas_size.width); + seams_push_constant.uv_offset[1] = uv_offsets[0].y / float(atlas_size.height); + seams_push_constant.blend = uv_offsets[0].z; + + rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant)); + rd->draw_list_draw(draw_list, false, 1, slice_seam_count[i] * 4); + } + + /* step 2 use triangles to mask the interior */ + + { + seams_push_constant.base_index = triangle_offset; + rd->draw_list_bind_render_pipeline(draw_list, blendseams_triangle_raster_pipeline); + seams_push_constant.blend = 0; //do not draw them, just fill the z-buffer so its used as a mask + + rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant)); + rd->draw_list_draw(draw_list, false, 1, slice_triangle_count[i] * 3); + } + /* step 3 blend around the triangle */ + + rd->draw_list_bind_render_pipeline(draw_list, blendseams_line_raster_pipeline); + + for (int j = 1; j < uv_offset_count; j++) { + + seams_push_constant.base_index = seam_offset; + seams_push_constant.uv_offset[0] = uv_offsets[j].x / float(atlas_size.width); + seams_push_constant.uv_offset[1] = uv_offsets[j].y / float(atlas_size.height); + seams_push_constant.blend = uv_offsets[0].z; + + rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant)); + rd->draw_list_draw(draw_list, false, 1, slice_seam_count[i] * 4); + } + rd->draw_list_end(); + } + seam_offset += slice_seam_count[i]; + triangle_offset += slice_triangle_count[i]; + } + } + +#ifdef DEBUG_TEXTURES + + for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { + Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i); + Ref<Image> img; + img.instance(); + img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); + img->convert(Image::FORMAT_RGBA8); + img->save_png("res://5_blendseams" + itos(i) + ".png"); + } +#endif + if (p_step_function) { + p_step_function(0.9, TTR("Retrieving textures"), p_bake_userdata, true); + } + + for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { + Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i); + Ref<Image> img; + img.instance(); + img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); + img->convert(Image::FORMAT_RGBH); //remove alpha + bake_textures.push_back(img); + } + + if (probe_positions.size() > 0) { + probe_values.resize(probe_positions.size() * 9); + Vector<uint8_t> probe_data = rd->buffer_get_data(light_probe_buffer); + copymem(probe_values.ptrw(), probe_data.ptr(), probe_data.size()); + rd->free(light_probe_buffer); + +#ifdef DEBUG_TEXTURES + { + Ref<Image> img2; + img2.instance(); + img2->create(probe_values.size(), 1, false, Image::FORMAT_RGBAF, probe_data); + img2->save_png("res://6_lightprobes.png"); + } +#endif + } + + FREE_TEXTURES + FREE_BUFFERS + FREE_RASTER_RESOURCES + FREE_COMPUTE_RESOURCES + FREE_BLENDSEAMS_RESOURCES + + memdelete(rd); + + return BAKE_OK; +} + +int LightmapperRD::get_bake_texture_count() const { + return bake_textures.size(); +} +Ref<Image> LightmapperRD::get_bake_texture(int p_index) const { + ERR_FAIL_INDEX_V(p_index, bake_textures.size(), Ref<Image>()); + return bake_textures[p_index]; +} +int LightmapperRD::get_bake_mesh_count() const { + return mesh_instances.size(); +} +Variant LightmapperRD::get_bake_mesh_userdata(int p_index) const { + ERR_FAIL_INDEX_V(p_index, mesh_instances.size(), Variant()); + return mesh_instances[p_index].data.userdata; +} +Rect2 LightmapperRD::get_bake_mesh_uv_scale(int p_index) const { + + ERR_FAIL_COND_V(bake_textures.size() == 0, Rect2()); + Rect2 uv_ofs; + Vector2 atlas_size = Vector2(bake_textures[0]->get_width(), bake_textures[0]->get_height()); + uv_ofs.position = Vector2(mesh_instances[p_index].offset) / atlas_size; + uv_ofs.size = Vector2(mesh_instances[p_index].data.albedo_on_uv2->get_width(), mesh_instances[p_index].data.albedo_on_uv2->get_height()) / atlas_size; + return uv_ofs; +} +int LightmapperRD::get_bake_mesh_texture_slice(int p_index) const { + ERR_FAIL_INDEX_V(p_index, mesh_instances.size(), Variant()); + return mesh_instances[p_index].slice; +} + +int LightmapperRD::get_bake_probe_count() const { + return probe_positions.size(); +} + +Vector3 LightmapperRD::get_bake_probe_point(int p_probe) const { + ERR_FAIL_INDEX_V(p_probe, probe_positions.size(), Variant()); + return Vector3(probe_positions[p_probe].position[0], probe_positions[p_probe].position[1], probe_positions[p_probe].position[2]); +} + +Vector<Color> LightmapperRD::get_bake_probe_sh(int p_probe) const { + ERR_FAIL_INDEX_V(p_probe, probe_positions.size(), Vector<Color>()); + Vector<Color> ret; + ret.resize(9); + copymem(ret.ptrw(), &probe_values[p_probe * 9], sizeof(Color) * 9); + return ret; +} + +LightmapperRD::LightmapperRD() { +} diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h new file mode 100644 index 0000000000..cb98efbeaa --- /dev/null +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -0,0 +1,229 @@ +#ifndef LIGHTMAPPER_RD_H +#define LIGHTMAPPER_RD_H + +#include "core/local_vector.h" +#include "scene/3d/lightmapper.h" +#include "scene/resources/mesh.h" +#include "servers/rendering/rendering_device.h" + +class LightmapperRD : public Lightmapper { + GDCLASS(LightmapperRD, Lightmapper) + + struct MeshInstance { + MeshData data; + int slice = 0; + Vector2i offset; + }; + + struct Light { + float position[3]; + uint32_t type = LIGHT_TYPE_DIRECTIONAL; + float direction[3]; + float energy; + float color[3]; + float size; + float range; + float attenuation; + float spot_angle; + float spot_attenuation; + uint32_t static_bake; + uint32_t pad[3]; + + bool operator<(const Light &p_light) const { + return type < p_light.type; + } + }; + + struct Vertex { + float position[3]; + float normal_z; + float uv[2]; + float normal_xy[2]; + + bool operator==(const Vertex &p_vtx) const { + return (position[0] == p_vtx.position[0]) && + (position[1] == p_vtx.position[1]) && + (position[2] == p_vtx.position[2]) && + (uv[0] == p_vtx.uv[0]) && + (uv[1] == p_vtx.uv[1]) && + (normal_xy[0] == p_vtx.normal_xy[0]) && + (normal_xy[1] == p_vtx.normal_xy[1]) && + (normal_z == p_vtx.normal_z); + } + }; + + struct Edge { + Vector3 a; + Vector3 b; + Vector3 na; + Vector3 nb; + bool operator==(const Edge &p_seam) const { + return a == p_seam.a && b == p_seam.b && na == p_seam.na && nb == p_seam.nb; + } + Edge() { + } + + Edge(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_na, const Vector3 &p_nb) { + a = p_a; + b = p_b; + na = p_na; + nb = p_nb; + } + }; + + struct Probe { + float position[4]; + }; + + Vector<Probe> probe_positions; + + struct EdgeHash { + _FORCE_INLINE_ static uint32_t hash(const Edge &p_edge) { + uint32_t h = hash_djb2_one_float(p_edge.a.x); + h = hash_djb2_one_float(p_edge.a.y, h); + h = hash_djb2_one_float(p_edge.a.z, h); + h = hash_djb2_one_float(p_edge.b.x, h); + h = hash_djb2_one_float(p_edge.b.y, h); + h = hash_djb2_one_float(p_edge.b.z, h); + return h; + } + }; + struct EdgeUV2 { + Vector2 a; + Vector2 b; + Vector2i indices; + bool operator==(const EdgeUV2 &p_uv2) const { + return a == p_uv2.a && b == p_uv2.b; + } + bool seam_found = false; + EdgeUV2(Vector2 p_a, Vector2 p_b, Vector2i p_indices) { + a = p_a; + b = p_b; + indices = p_indices; + } + EdgeUV2() {} + }; + + struct Seam { + Vector2i a; + Vector2i b; + uint32_t slice; + bool operator<(const Seam &p_seam) const { + return slice < p_seam.slice; + } + }; + + struct VertexHash { + _FORCE_INLINE_ static uint32_t hash(const Vertex &p_vtx) { + uint32_t h = hash_djb2_one_float(p_vtx.position[0]); + h = hash_djb2_one_float(p_vtx.position[1], h); + h = hash_djb2_one_float(p_vtx.position[2], h); + h = hash_djb2_one_float(p_vtx.uv[0], h); + h = hash_djb2_one_float(p_vtx.uv[1], h); + h = hash_djb2_one_float(p_vtx.normal_xy[0], h); + h = hash_djb2_one_float(p_vtx.normal_xy[1], h); + h = hash_djb2_one_float(p_vtx.normal_z, h); + return h; + } + }; + + struct Box { + float min_bounds[3]; + float pad0; + float max_bounds[3]; + float pad1; + }; + + struct Triangle { + uint32_t indices[3]; + uint32_t slice; + bool operator<(const Triangle &p_triangle) const { + return slice < p_triangle.slice; + } + }; + + Vector<MeshInstance> mesh_instances; + + Vector<Light> lights; + + struct TriangleSort { + uint32_t cell_index; + uint32_t triangle_index; + bool operator<(const TriangleSort &p_triangle_sort) const { + return cell_index < p_triangle_sort.cell_index; //sorting by triangle index in this case makes no sense + } + }; + + void _plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[], uint32_t p_triangle_index, LocalVector<TriangleSort> &triangles, uint32_t p_grid_size); + + struct RasterPushConstant { + float atlas_size[2]; + float uv_offset[2]; + float to_cell_size[3]; + uint32_t base_triangle; + float to_cell_offset[3]; + float bias; + int32_t grid_size[3]; + uint32_t pad2; + }; + + struct RasterSeamsPushConstant { + + uint32_t base_index; + uint32_t slice; + float uv_offset[2]; + uint32_t debug; + float blend; + uint32_t pad[2]; + }; + + struct PushConstant { + int32_t atlas_size[2]; + uint32_t ray_count; + uint32_t ray_to; + + float world_size[3]; + float bias; + + float to_cell_offset[3]; + uint32_t ray_from; + + float to_cell_size[3]; + uint32_t light_count; + + int32_t grid_size; + int32_t atlas_slice; + int32_t region_ofs[2]; + + float environment_xform[12]; + }; + + Vector<Ref<Image>> bake_textures; + Vector<Color> probe_values; + + BakeError _blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata); + void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &grid_texture_sdf, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); + void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform); + +public: + virtual void add_mesh(const MeshData &p_mesh); + virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance); + virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size); + virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size); + virtual void add_probe(const Vector3 &p_position); + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr); + + int get_bake_texture_count() const; + Ref<Image> get_bake_texture(int p_index) const; + int get_bake_mesh_count() const; + Variant get_bake_mesh_userdata(int p_index) const; + Rect2 get_bake_mesh_uv_scale(int p_index) const; + int get_bake_mesh_texture_slice(int p_index) const; + int get_bake_probe_count() const; + Vector3 get_bake_probe_point(int p_probe) const; + Vector<Color> get_bake_probe_sh(int p_probe) const; + + LightmapperRD(); +}; + +#endif // LIGHTMAPPER_H diff --git a/modules/lightmapper_rd/lm_blendseams.glsl b/modules/lightmapper_rd/lm_blendseams.glsl new file mode 100644 index 0000000000..ef1ece8ea1 --- /dev/null +++ b/modules/lightmapper_rd/lm_blendseams.glsl @@ -0,0 +1,117 @@ +/* clang-format off */ +[versions] + +lines = "#define MODE_LINES" +triangles = "#define MODE_TRIANGLES" + +[vertex] + +#version 450 + +VERSION_DEFINES + +#include "lm_common_inc.glsl" + + /* clang-format on */ + + layout(push_constant, binding = 0, std430) uniform Params { + uint base_index; + uint slice; + vec2 uv_offset; + bool debug; + float blend; + uint pad[2]; + } params; + +layout(location = 0) out vec3 uv_interp; + +void main() { + +#ifdef MODE_TRIANGLES + + uint triangle_idx = params.base_index + gl_VertexIndex / 3; + uint triangle_subidx = gl_VertexIndex % 3; + + vec2 uv; + if (triangle_subidx == 0) { + uv = vertices.data[triangles.data[triangle_idx].indices.x].uv; + } else if (triangle_subidx == 1) { + uv = vertices.data[triangles.data[triangle_idx].indices.y].uv; + } else { + uv = vertices.data[triangles.data[triangle_idx].indices.z].uv; + } + + uv_interp = vec3(uv, float(params.slice)); + gl_Position = vec4((uv + params.uv_offset) * 2.0 - 1.0, 0.0001, 1.0); + +#endif + +#ifdef MODE_LINES + uint seam_idx = params.base_index + gl_VertexIndex / 4; + uint seam_subidx = gl_VertexIndex % 4; + + uint src_idx; + uint dst_idx; + + if (seam_subidx == 0) { + src_idx = seams.data[seam_idx].b.x; + dst_idx = seams.data[seam_idx].a.x; + } else if (seam_subidx == 1) { + src_idx = seams.data[seam_idx].b.y; + dst_idx = seams.data[seam_idx].a.y; + } else if (seam_subidx == 2) { + src_idx = seams.data[seam_idx].a.x; + dst_idx = seams.data[seam_idx].b.x; + } else if (seam_subidx == 3) { + src_idx = seams.data[seam_idx].a.y; + dst_idx = seams.data[seam_idx].b.y; + } + + vec2 src_uv = vertices.data[src_idx].uv; + vec2 dst_uv = vertices.data[dst_idx].uv + params.uv_offset; + + uv_interp = vec3(src_uv, float(params.slice)); + gl_Position = vec4(dst_uv * 2.0 - 1.0, 0.0001, 1.0); + ; +#endif +} + +/* clang-format off */ +[fragment] + +#version 450 + +VERSION_DEFINES + +#include "lm_common_inc.glsl" + + /* clang-format on */ + + layout(push_constant, binding = 0, std430) uniform Params { + uint base_index; + uint slice; + vec2 uv_offset; + bool debug; + float blend; + uint pad[2]; + } params; + +layout(location = 0) in vec3 uv_interp; + +layout(location = 0) out vec4 dst_color; + +layout(set = 1, binding = 0) uniform texture2DArray src_color_tex; + +void main() { + + if (params.debug) { +#ifdef MODE_TRIANGLES + dst_color = vec4(1, 0, 1, 1); +#else + dst_color = vec4(1, 1, 0, 1); +#endif + } else { + vec4 src_color = textureLod(sampler2DArray(src_color_tex, linear_sampler), uv_interp, 0.0); + dst_color = vec4(src_color.rgb, params.blend); //mix + } +} diff --git a/modules/lightmapper_rd/lm_common_inc.glsl b/modules/lightmapper_rd/lm_common_inc.glsl new file mode 100644 index 0000000000..0ff455936e --- /dev/null +++ b/modules/lightmapper_rd/lm_common_inc.glsl @@ -0,0 +1,92 @@ + +/* SET 0, static data that does not change between any call */ + +struct Vertex { + vec3 position; + float normal_z; + vec2 uv; + vec2 normal_xy; +}; + +layout(set = 0, binding = 1, std430) restrict readonly buffer Vertices { + Vertex data[]; +} +vertices; + +struct Triangle { + uvec3 indices; + uint slice; +}; + +layout(set = 0, binding = 2, std430) restrict readonly buffer Triangles { + Triangle data[]; +} +triangles; + +struct Box { + vec3 min_bounds; + uint pad0; + vec3 max_bounds; + uint pad1; +}; + +layout(set = 0, binding = 3, std430) restrict readonly buffer Boxes { + Box data[]; +} +boxes; + +layout(set = 0, binding = 4, std430) restrict readonly buffer GridIndices { + uint data[]; +} +grid_indices; + +#define LIGHT_TYPE_DIRECTIONAL 0 +#define LIGHT_TYPE_OMNI 1 +#define LIGHT_TYPE_SPOT 2 + +struct Light { + vec3 position; + uint type; + + vec3 direction; + float energy; + + vec3 color; + float size; + + float range; + float attenuation; + float spot_angle; + float spot_attenuation; + + bool static_bake; + uint pad[3]; +}; + +layout(set = 0, binding = 5, std430) restrict readonly buffer Lights { + Light data[]; +} +lights; + +struct Seam { + uvec2 a; + uvec2 b; +}; + +layout(set = 0, binding = 6, std430) restrict readonly buffer Seams { + Seam data[]; +} +seams; + +layout(set = 0, binding = 7, std430) restrict readonly buffer Probes { + vec4 data[]; +} +probe_positions; + +layout(set = 0, binding = 8) uniform utexture3D grid; +layout(set = 0, binding = 9) uniform texture3D grid_sdf; + +layout(set = 0, binding = 10) uniform texture2DArray albedo_tex; +layout(set = 0, binding = 11) uniform texture2DArray emission_tex; + +layout(set = 0, binding = 12) uniform sampler linear_sampler; diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl new file mode 100644 index 0000000000..a178bd9b2e --- /dev/null +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -0,0 +1,657 @@ +/* clang-format off */ +[versions] + +primary = "#define MODE_DIRECT_LIGHT" +secondary = "#define MODE_BOUNCE_LIGHT" +dilate = "#define MODE_DILATE" +unocclude = "#define MODE_UNOCCLUDE" +light_probes = "#define MODE_LIGHT_PROBES" + +[compute] + +#version 450 + +VERSION_DEFINES + +// One 2D local group focusing in one layer at a time, though all +// in parallel (no barriers) makes more sense than a 3D local group +// as this can take more advantage of the cache for each group. + +#ifdef MODE_LIGHT_PROBES + +layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +#else + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +#endif + +#include "lm_common_inc.glsl" + +/* clang-format on */ + +#ifdef MODE_LIGHT_PROBES + +layout(set = 1, binding = 0, std430) restrict buffer LightProbeData { + vec4 data[]; +} +light_probes; + +layout(set = 1, binding = 1) uniform texture2DArray source_light; +layout(set = 1, binding = 2) uniform texture2DArray source_direct_light; //also need the direct light, which was omitted +layout(set = 1, binding = 3) uniform texture2D environment; +#endif + +#ifdef MODE_UNOCCLUDE + +layout(rgba32f, set = 1, binding = 0) uniform restrict image2DArray position; +layout(rgba32f, set = 1, binding = 1) uniform restrict readonly image2DArray unocclude; + +#endif + +#if defined(MODE_DIRECT_LIGHT) || defined(MODE_BOUNCE_LIGHT) + +layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light; +layout(set = 1, binding = 1) uniform texture2DArray source_light; +layout(set = 1, binding = 2) uniform texture2DArray source_position; +layout(set = 1, binding = 3) uniform texture2DArray source_normal; +layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light; + +#endif + +#ifdef MODE_BOUNCE_LIGHT +layout(rgba32f, set = 1, binding = 5) uniform restrict image2DArray bounce_accum; +layout(set = 1, binding = 6) uniform texture2D environment; +#endif +#ifdef MODE_DIRECT_LIGHT +layout(rgba32f, set = 1, binding = 5) uniform restrict writeonly image2DArray primary_dynamic; +#endif + +#ifdef MODE_DILATE +layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light; +layout(set = 1, binding = 1) uniform texture2DArray source_light; +#endif + +layout(push_constant, binding = 0, std430) uniform Params { + ivec2 atlas_size; // x used for light probe mode total probes + uint ray_count; + uint ray_to; + + vec3 world_size; + float bias; + + vec3 to_cell_offset; + uint ray_from; + + vec3 to_cell_size; + uint light_count; + + int grid_size; + int atlas_slice; + ivec2 region_ofs; + + mat3x4 env_transform; +} +params; + +//check it, but also return distance and barycentric coords (for uv lookup) +bool ray_hits_triangle(vec3 from, vec3 dir, float max_dist, vec3 p0, vec3 p1, vec3 p2, out float r_distance, out vec3 r_barycentric) { + + const vec3 e0 = p1 - p0; + const vec3 e1 = p0 - p2; + vec3 triangleNormal = cross(e1, e0); + + const vec3 e2 = (1.0 / dot(triangleNormal, dir)) * (p0 - from); + const vec3 i = cross(dir, e2); + + r_barycentric.y = dot(i, e1); + r_barycentric.z = dot(i, e0); + r_barycentric.x = 1.0 - (r_barycentric.z + r_barycentric.y); + r_distance = dot(triangleNormal, e2); + return (r_distance > params.bias) && (r_distance < max_dist) && all(greaterThanEqual(r_barycentric, vec3(0.0))); +} + +bool trace_ray(vec3 p_from, vec3 p_to +#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) + , + out uint r_triangle, out vec3 r_barycentric +#endif +#if defined(MODE_UNOCCLUDE) + , + out float r_distance, out vec3 r_normal +#endif +) { + + /* world coords */ + + vec3 rel = p_to - p_from; + float rel_len = length(rel); + vec3 dir = normalize(rel); + vec3 inv_dir = 1.0 / dir; + + /* cell coords */ + + vec3 from_cell = (p_from - params.to_cell_offset) * params.to_cell_size; + vec3 to_cell = (p_to - params.to_cell_offset) * params.to_cell_size; + + //prepare DDA + vec3 rel_cell = to_cell - from_cell; + ivec3 icell = ivec3(from_cell); + ivec3 iendcell = ivec3(to_cell); + vec3 dir_cell = normalize(rel_cell); + vec3 delta = abs(1.0 / dir_cell); //vec3(length(rel_cell)) / rel_cell); + ivec3 step = ivec3(sign(rel_cell)); + vec3 side = (sign(rel_cell) * (vec3(icell) - from_cell) + (sign(rel_cell) * 0.5) + 0.5) * delta; + + uint iters = 0; + while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(params.grid_size))) && iters < 1000) { + + uvec2 cell_data = texelFetch(usampler3D(grid, linear_sampler), icell, 0).xy; + if (cell_data.x > 0) { //triangles here + + bool hit = false; +#if defined(MODE_UNOCCLUDE) + bool hit_backface = false; +#endif + float best_distance = 1e20; + + for (uint i = 0; i < cell_data.x; i++) { + uint tidx = grid_indices.data[cell_data.y + i]; + + //Ray-Box test + vec3 t0 = (boxes.data[tidx].min_bounds - p_from) * inv_dir; + vec3 t1 = (boxes.data[tidx].max_bounds - p_from) * inv_dir; + vec3 tmin = min(t0, t1), tmax = max(t0, t1); + + if (max(tmin.x, max(tmin.y, tmin.z)) <= min(tmax.x, min(tmax.y, tmax.z))) { + continue; //ray box failed + } + + //prepare triangle vertices + vec3 vtx0 = vertices.data[triangles.data[tidx].indices.x].position; + vec3 vtx1 = vertices.data[triangles.data[tidx].indices.y].position; + vec3 vtx2 = vertices.data[triangles.data[tidx].indices.z].position; +#if defined(MODE_UNOCCLUDE) + vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2))); + + bool backface = dot(normal, dir) >= 0.0; +#endif + float distance; + vec3 barycentric; + + if (ray_hits_triangle(p_from, dir, rel_len, vtx0, vtx1, vtx2, distance, barycentric)) { +#ifdef MODE_DIRECT_LIGHT + return true; //any hit good +#endif + +#if defined(MODE_UNOCCLUDE) + if (!backface) { + // the case of meshes having both a front and back face in the same plane is more common than + // expected, so if this is a front-face, bias it closer to the ray origin, so it always wins over the back-face + distance = max(params.bias, distance - params.bias); + } + + hit = true; + + if (distance < best_distance) { + hit_backface = backface; + best_distance = distance; + r_distance = distance; + r_normal = normal; + } + +#endif + +#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) + + hit = true; + if (distance < best_distance) { + best_distance = distance; + r_triangle = tidx; + r_barycentric = barycentric; + } + +#endif + } + } +#if defined(MODE_UNOCCLUDE) + + if (hit) { + return hit_backface; + } +#endif +#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) + if (hit) { + return true; + } +#endif + } + + if (icell == iendcell) { + break; + } + + bvec3 mask = lessThanEqual(side.xyz, min(side.yzx, side.zxy)); + side += vec3(mask) * delta; + icell += ivec3(vec3(mask)) * step; + + iters++; + } + + return false; +} + +const float PI = 3.14159265f; +const float GOLDEN_ANGLE = PI * (3.0 - sqrt(5.0)); + +vec3 vogel_hemisphere(uint p_index, uint p_count, float p_offset) { + float r = sqrt(float(p_index) + 0.5f) / sqrt(float(p_count)); + float theta = float(p_index) * GOLDEN_ANGLE + p_offset; + float y = cos(r * PI * 0.5); + float l = sin(r * PI * 0.5); + return vec3(l * cos(theta), l * sin(theta), y); +} + +float quick_hash(vec2 pos) { + return fract(sin(dot(pos * 19.19, vec2(49.5791, 97.413))) * 49831.189237); +} + +void main() { + +#ifdef MODE_LIGHT_PROBES + int probe_index = int(gl_GlobalInvocationID.x); + if (probe_index >= params.atlas_size.x) { //too large, do nothing + return; + } + +#else + ivec2 atlas_pos = ivec2(gl_GlobalInvocationID.xy) + params.region_ofs; + if (any(greaterThanEqual(atlas_pos, params.atlas_size))) { //too large, do nothing + return; + } +#endif + +#ifdef MODE_DIRECT_LIGHT + + vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + if (length(normal) < 0.5) { + return; //empty texel, no process + } + vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + + //go through all lights + //start by own light (emissive) + vec3 static_light = vec3(0.0); + vec3 dynamic_light = vec3(0.0); + +#ifdef USE_SH_LIGHTMAPS + vec4 sh_accum[4] = vec4[]( + vec4(0.0, 0.0, 0.0, 1.0), + vec4(0.0, 0.0, 0.0, 1.0), + vec4(0.0, 0.0, 0.0, 1.0), + vec4(0.0, 0.0, 0.0, 1.0)); +#endif + + for (uint i = 0; i < params.light_count; i++) { + + vec3 light_pos; + float attenuation; + if (lights.data[i].type == LIGHT_TYPE_DIRECTIONAL) { + vec3 light_vec = lights.data[i].direction; + light_pos = position - light_vec * length(params.world_size); + attenuation = 1.0; + } else { + light_pos = lights.data[i].position; + float d = distance(position, light_pos); + if (d > lights.data[i].range) { + continue; + } + + d /= lights.data[i].range; + + attenuation = pow(max(1.0 - d, 0.0), lights.data[i].attenuation); + + if (lights.data[i].type == LIGHT_TYPE_SPOT) { + + vec3 rel = normalize(position - light_pos); + float angle = acos(dot(rel, lights.data[i].direction)); + if (angle > lights.data[i].spot_angle) { + continue; //invisible, dont try + } + + float d = clamp(angle / lights.data[i].spot_angle, 0, 1); + attenuation *= pow(1.0 - d, lights.data[i].spot_attenuation); + } + } + + vec3 light_dir = normalize(light_pos - position); + attenuation *= max(0.0, dot(normal, light_dir)); + + if (attenuation <= 0.0001) { + continue; //no need to do anything + } + + if (!trace_ray(position + light_dir * params.bias, light_pos)) { + vec3 light = lights.data[i].color * lights.data[i].energy * attenuation; + if (lights.data[i].static_bake) { + static_light += light; +#ifdef USE_SH_LIGHTMAPS + + float c[4] = float[]( + 0.282095, //l0 + 0.488603 * light_dir.y, //l1n1 + 0.488603 * light_dir.z, //l1n0 + 0.488603 * light_dir.x //l1p1 + ); + + for (uint j = 0; j < 4; j++) { + sh_accum[j].rgb += light * c[j] * (1.0 / 3.0); + } +#endif + + } else { + dynamic_light += light; + } + } + } + + vec3 albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb; + vec3 emissive = texelFetch(sampler2DArray(emission_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb; + + dynamic_light *= albedo; //if it will bounce, must multiply by albedo + dynamic_light += emissive; + + //keep for lightprobes + imageStore(primary_dynamic, ivec3(atlas_pos, params.atlas_slice), vec4(dynamic_light, 1.0)); + + dynamic_light += static_light * albedo; //send for bounces + imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(dynamic_light, 1.0)); + +#ifdef USE_SH_LIGHTMAPS + //keep for adding at the end + imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 0), sh_accum[0]); + imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 1), sh_accum[1]); + imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 2), sh_accum[2]); + imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 3), sh_accum[3]); + +#else + imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(static_light, 1.0)); +#endif + +#endif + +#ifdef MODE_BOUNCE_LIGHT + + vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + if (length(normal) < 0.5) { + return; //empty texel, no process + } + + vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + + vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0); + vec3 tangent = normalize(cross(v0, normal)); + vec3 bitangent = normalize(cross(tangent, normal)); + mat3 normal_mat = mat3(tangent, bitangent, normal); + +#ifdef USE_SH_LIGHTMAPS + vec4 sh_accum[4] = vec4[]( + vec4(0.0, 0.0, 0.0, 1.0), + vec4(0.0, 0.0, 0.0, 1.0), + vec4(0.0, 0.0, 0.0, 1.0), + vec4(0.0, 0.0, 0.0, 1.0)); +#endif + vec3 light_average = vec3(0.0); + for (uint i = params.ray_from; i < params.ray_to; i++) { + vec3 ray_dir = normal_mat * vogel_hemisphere(i, params.ray_count, quick_hash(vec2(atlas_pos))); + + uint tidx; + vec3 barycentric; + + vec3 light; + if (trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric)) { + //hit a triangle + vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv; + vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv; + vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv; + vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice)); + + light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb; + } else { + //did not hit a triangle, reach out for the sky + vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir); + + vec2 st = vec2( + atan(sky_dir.x, sky_dir.z), + acos(sky_dir.y)); + + if (st.x < 0.0) + st.x += PI * 2.0; + + st /= vec2(PI * 2.0, PI); + + light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb; + } + + light_average += light; + +#ifdef USE_SH_LIGHTMAPS + + float c[4] = float[]( + 0.282095, //l0 + 0.488603 * ray_dir.y, //l1n1 + 0.488603 * ray_dir.z, //l1n0 + 0.488603 * ray_dir.x //l1p1 + ); + + for (uint j = 0; j < 4; j++) { + sh_accum[j].rgb += light * c[j] * (8.0 / float(params.ray_count)); + } +#endif + } + + vec3 light_total; + if (params.ray_from == 0) { + light_total = vec3(0.0); + } else { + light_total = imageLoad(bounce_accum, ivec3(atlas_pos, params.atlas_slice)).rgb; + } + + light_total += light_average; + +#ifdef USE_SH_LIGHTMAPS + + for (int i = 0; i < 4; i++) { + vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i)); + accum.rgb += sh_accum[i].rgb; + imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), accum); + } + +#endif + if (params.ray_to == params.ray_count) { + light_total /= float(params.ray_count); + imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, 1.0)); +#ifndef USE_SH_LIGHTMAPS + vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice)); + accum.rgb += light_total; + imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), accum); +#endif + } else { + imageStore(bounce_accum, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, 1.0)); + } + +#endif + +#ifdef MODE_UNOCCLUDE + + //texel_size = 0.5; + //compute tangents + + vec4 position_alpha = imageLoad(position, ivec3(atlas_pos, params.atlas_slice)); + if (position_alpha.a < 0.5) { + return; + } + + vec3 vertex_pos = position_alpha.xyz; + vec4 normal_tsize = imageLoad(unocclude, ivec3(atlas_pos, params.atlas_slice)); + + vec3 face_normal = normal_tsize.xyz; + float texel_size = normal_tsize.w; + + vec3 v0 = abs(face_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0); + vec3 tangent = normalize(cross(v0, face_normal)); + vec3 bitangent = normalize(cross(tangent, face_normal)); + vec3 base_pos = vertex_pos + face_normal * params.bias; //raise a bit + + vec3 rays[4] = vec3[](tangent, bitangent, -tangent, -bitangent); + float min_d = 1e20; + for (int i = 0; i < 4; i++) { + vec3 ray_to = base_pos + rays[i] * texel_size; + float d; + vec3 norm; + + if (trace_ray(base_pos, ray_to, d, norm)) { + + if (d < min_d) { + vertex_pos = base_pos + rays[i] * d + norm * params.bias * 10.0; //this bias needs to be greater than the regular bias, because otherwise later, rays will go the other side when pointing back. + min_d = d; + } + } + } + + position_alpha.xyz = vertex_pos; + + imageStore(position, ivec3(atlas_pos, params.atlas_slice), position_alpha); + +#endif + +#ifdef MODE_LIGHT_PROBES + + vec3 position = probe_positions.data[probe_index].xyz; + + vec4 probe_sh_accum[9] = vec4[]( + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0)); + + for (uint i = params.ray_from; i < params.ray_to; i++) { + vec3 ray_dir = vogel_hemisphere(i, params.ray_count, quick_hash(vec2(float(probe_index), 0.0))); + if (bool(i & 1)) { + //throw to both sides, so alternate them + ray_dir.z *= -1.0; + } + + uint tidx; + vec3 barycentric; + vec3 light; + + if (trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric)) { + vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv; + vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv; + vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv; + vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice)); + + light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb; + light += textureLod(sampler2DArray(source_direct_light, linear_sampler), uvw, 0.0).rgb; + } else { + + //did not hit a triangle, reach out for the sky + vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir); + + vec2 st = vec2( + atan(sky_dir.x, sky_dir.z), + acos(sky_dir.y)); + + if (st.x < 0.0) + st.x += PI * 2.0; + + st /= vec2(PI * 2.0, PI); + + light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb; + } + + { + float c[9] = float[]( + 0.282095, //l0 + 0.488603 * ray_dir.y, //l1n1 + 0.488603 * ray_dir.z, //l1n0 + 0.488603 * ray_dir.x, //l1p1 + 1.092548 * ray_dir.x * ray_dir.y, //l2n2 + 1.092548 * ray_dir.y * ray_dir.z, //l2n1 + //0.315392 * (ray_dir.x * ray_dir.x + ray_dir.y * ray_dir.y + 2.0 * ray_dir.z * ray_dir.z), //l20 + 0.315392 * (3.0 * ray_dir.z * ray_dir.z - 1.0), //l20 + 1.092548 * ray_dir.x * ray_dir.z, //l2p1 + 0.546274 * (ray_dir.x * ray_dir.x - ray_dir.y * ray_dir.y) //l2p2 + ); + + for (uint j = 0; j < 9; j++) { + probe_sh_accum[j].rgb += light * c[j]; + } + } + } + + if (params.ray_from > 0) { + for (uint j = 0; j < 9; j++) { //accum from existing + probe_sh_accum[j] += light_probes.data[probe_index * 9 + j]; + } + } + + if (params.ray_to == params.ray_count) { + for (uint j = 0; j < 9; j++) { //accum from existing + probe_sh_accum[j] *= 4.0 / float(params.ray_count); + } + } + + for (uint j = 0; j < 9; j++) { //accum from existing + light_probes.data[probe_index * 9 + j] = probe_sh_accum[j]; + } + +#endif + +#ifdef MODE_DILATE + + vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0); + //sides first, as they are closer + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 0), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, 1), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 0), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, -1), params.atlas_slice), 0); + //endpoints second + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, -1), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 1), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, -1), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 1), params.atlas_slice), 0); + + //far sides third + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 0), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, 2), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 0), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, -2), params.atlas_slice), 0); + + //far-mid endpoints + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, -1), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 1), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, -1), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 1), params.atlas_slice), 0); + + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, -2), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 2), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, -2), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 2), params.atlas_slice), 0); + //far endpoints + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, -2), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 2), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, -2), params.atlas_slice), 0); + c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 2), params.atlas_slice), 0); + + imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), c); + +#endif +} diff --git a/modules/lightmapper_rd/lm_raster.glsl b/modules/lightmapper_rd/lm_raster.glsl new file mode 100644 index 0000000000..ae3038aead --- /dev/null +++ b/modules/lightmapper_rd/lm_raster.glsl @@ -0,0 +1,170 @@ +/* clang-format off */ +[vertex] + +#version 450 + +VERSION_DEFINES + +#include "lm_common_inc.glsl" + + /* clang-format on */ + + layout(location = 0) out vec3 vertex_interp; +layout(location = 1) out vec3 normal_interp; +layout(location = 2) out vec2 uv_interp; +layout(location = 3) out vec3 barycentric; +layout(location = 4) flat out uvec3 vertex_indices; +layout(location = 5) flat out vec3 face_normal; + +layout(push_constant, binding = 0, std430) uniform Params { + vec2 atlas_size; + vec2 uv_offset; + vec3 to_cell_size; + uint base_triangle; + vec3 to_cell_offset; + float bias; + ivec3 grid_size; + uint pad2; +} +params; + +/* clang-format on */ + +void main() { + + uint triangle_idx = params.base_triangle + gl_VertexIndex / 3; + uint triangle_subidx = gl_VertexIndex % 3; + + vertex_indices = triangles.data[triangle_idx].indices; + + uint vertex_idx; + if (triangle_subidx == 0) { + vertex_idx = vertex_indices.x; + barycentric = vec3(1, 0, 0); + } else if (triangle_subidx == 1) { + vertex_idx = vertex_indices.y; + barycentric = vec3(0, 1, 0); + } else { + vertex_idx = vertex_indices.z; + barycentric = vec3(0, 0, 1); + } + + vertex_interp = vertices.data[vertex_idx].position; + uv_interp = vertices.data[vertex_idx].uv; + normal_interp = vec3(vertices.data[vertex_idx].normal_xy, vertices.data[vertex_idx].normal_z); + + face_normal = -normalize(cross((vertices.data[vertex_indices.x].position - vertices.data[vertex_indices.y].position), (vertices.data[vertex_indices.x].position - vertices.data[vertex_indices.z].position))); + + gl_Position = vec4((uv_interp + params.uv_offset) * 2.0 - 1.0, 0.0001, 1.0); + ; +} + +/* clang-format off */ + +[fragment] + +#version 450 + +VERSION_DEFINES + +#include "lm_common_inc.glsl" + + +layout(push_constant, binding = 0, std430) uniform Params { + vec2 atlas_size; + vec2 uv_offset; + vec3 to_cell_size; + uint base_triangle; + vec3 to_cell_offset; + float bias; + ivec3 grid_size; + uint pad2; +} params; + +/* clang-format on */ + +layout(location = 0) in vec3 vertex_interp; +layout(location = 1) in vec3 normal_interp; +layout(location = 2) in vec2 uv_interp; +layout(location = 3) in vec3 barycentric; +layout(location = 4) in flat uvec3 vertex_indices; +layout(location = 5) in flat vec3 face_normal; + +layout(location = 0) out vec4 position; +layout(location = 1) out vec4 normal; +layout(location = 2) out vec4 unocclude; + +void main() { + + vec3 vertex_pos = vertex_interp; + + { + // smooth out vertex position by interpolating its projection in the 3 normal planes (normal plane is created by vertex pos and normal) + // because we don't want to interpolate inwards, normals found pointing inwards are pushed out. + + vec3 pos_a = vertices.data[vertex_indices.x].position; + vec3 pos_b = vertices.data[vertex_indices.y].position; + vec3 pos_c = vertices.data[vertex_indices.z].position; + vec3 center = (pos_a + pos_b + pos_c) * 0.3333333; + vec3 norm_a = vec3(vertices.data[vertex_indices.x].normal_xy, vertices.data[vertex_indices.x].normal_z); + vec3 norm_b = vec3(vertices.data[vertex_indices.y].normal_xy, vertices.data[vertex_indices.y].normal_z); + vec3 norm_c = vec3(vertices.data[vertex_indices.z].normal_xy, vertices.data[vertex_indices.z].normal_z); + + { + vec3 dir_a = normalize(pos_a - center); + float d_a = dot(dir_a, norm_a); + if (d_a < 0) { + //pointing inwards + norm_a = normalize(norm_a - dir_a * d_a); + } + } + { + vec3 dir_b = normalize(pos_b - center); + float d_b = dot(dir_b, norm_b); + if (d_b < 0) { + //pointing inwards + norm_b = normalize(norm_b - dir_b * d_b); + } + } + { + vec3 dir_c = normalize(pos_c - center); + float d_c = dot(dir_c, norm_c); + if (d_c < 0) { + //pointing inwards + norm_c = normalize(norm_c - dir_c * d_c); + } + } + + float d_a = dot(norm_a, pos_a); + float d_b = dot(norm_b, pos_b); + float d_c = dot(norm_c, pos_c); + + vec3 proj_a = vertex_pos - norm_a * (dot(norm_a, vertex_pos) - d_a); + vec3 proj_b = vertex_pos - norm_b * (dot(norm_b, vertex_pos) - d_b); + vec3 proj_c = vertex_pos - norm_c * (dot(norm_c, vertex_pos) - d_c); + + vec3 smooth_position = proj_a * barycentric.x + proj_b * barycentric.y + proj_c * barycentric.z; + + if (dot(face_normal, smooth_position) > dot(face_normal, vertex_pos)) { //only project outwards + vertex_pos = smooth_position; + } + } + + { + // unocclusion technique based on: + // https://ndotl.wordpress.com/2018/08/29/baking-artifact-free-lightmaps/ + + /* compute texel size */ + vec3 delta_uv = max(abs(dFdx(vertex_interp)), abs(dFdy(vertex_interp))); + float texel_size = max(delta_uv.x, max(delta_uv.y, delta_uv.z)); + texel_size *= sqrt(2.0); //expand to unit box edge length (again, worst case) + + unocclude.xyz = face_normal; + unocclude.w = texel_size; + + //continued on lm_compute.glsl + } + + position = vec4(vertex_pos, 1.0); + normal = vec4(normalize(normal_interp), 1.0); +} diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp new file mode 100644 index 0000000000..f3938f3190 --- /dev/null +++ b/modules/lightmapper_rd/register_types.cpp @@ -0,0 +1,64 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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/project_settings.h" +#include "lightmapper_rd.h" +#include "scene/3d/lightmapper.h" + +#ifndef _3D_DISABLED +static Lightmapper *create_lightmapper_rd() { + return memnew(LightmapperRD); +} +#endif + +void register_lightmapper_rd_types() { + + GLOBAL_DEF("rendering/gpu_lightmapper/quality/low_quality_ray_count", 16); + GLOBAL_DEF("rendering/gpu_lightmapper/quality/medium_quality_ray_count", 64); + GLOBAL_DEF("rendering/gpu_lightmapper/quality/high_quality_ray_count", 256); + GLOBAL_DEF("rendering/gpu_lightmapper/quality/ultra_quality_ray_count", 1024); + GLOBAL_DEF("rendering/gpu_lightmapper/performance/max_rays_per_pass", 32); + GLOBAL_DEF("rendering/gpu_lightmapper/performance/region_size", 512); + + GLOBAL_DEF("rendering/gpu_lightmapper/quality/low_quality_probe_ray_count", 64); + GLOBAL_DEF("rendering/gpu_lightmapper/quality/medium_quality_probe_ray_count", 256); + GLOBAL_DEF("rendering/gpu_lightmapper/quality/high_quality_probe_ray_count", 512); + GLOBAL_DEF("rendering/gpu_lightmapper/quality/ultra_quality_probe_ray_count", 2048); + GLOBAL_DEF("rendering/gpu_lightmapper/performance/max_rays_per_probe_pass", 64); +#ifndef _3D_DISABLED + ClassDB::register_class<LightmapperRD>(); + Lightmapper::create_gpu = create_lightmapper_rd; +#endif +} + +void unregister_lightmapper_rd_types() { +} diff --git a/modules/lightmapper_rd/register_types.h b/modules/lightmapper_rd/register_types.h new file mode 100644 index 0000000000..b0e15a927f --- /dev/null +++ b/modules/lightmapper_rd/register_types.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 LIGHTMAPPER_RD_REGISTER_TYPES_H +#define LIGHTMAPPER_RD_REGISTER_TYPES_H + +void register_lightmapper_rd_types(); +void unregister_lightmapper_rd_types(); + +#endif // XATLAS_UNWRAP_REGISTER_TYPES_H diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp index b2aa5f5827..37477e1246 100755..100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.cpp +++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp @@ -36,7 +36,8 @@ int PacketPeerMbedDTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) { - if (buf == nullptr || len <= 0) return 0; + if (buf == nullptr || len <= 0) + return 0; PacketPeerMbedDTLS *sp = (PacketPeerMbedDTLS *)ctx; @@ -53,7 +54,8 @@ int PacketPeerMbedDTLS::bio_send(void *ctx, const unsigned char *buf, size_t len int PacketPeerMbedDTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) { - if (buf == nullptr || len <= 0) return 0; + if (buf == nullptr || len <= 0) + return 0; PacketPeerMbedDTLS *sp = (PacketPeerMbedDTLS *)ctx; diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp index 983095c536..af36b29dac 100755..100644 --- a/modules/mbedtls/stream_peer_mbedtls.cpp +++ b/modules/mbedtls/stream_peer_mbedtls.cpp @@ -35,7 +35,8 @@ int StreamPeerMbedTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) { - if (buf == nullptr || len <= 0) return 0; + if (buf == nullptr || len <= 0) + return 0; StreamPeerMbedTLS *sp = (StreamPeerMbedTLS *)ctx; @@ -54,7 +55,8 @@ int StreamPeerMbedTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) int StreamPeerMbedTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) { - if (buf == nullptr || len <= 0) return 0; + if (buf == nullptr || len <= 0) + return 0; StreamPeerMbedTLS *sp = (StreamPeerMbedTLS *)ctx; diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp index 48276c0d3d..6d10de0096 100644 --- a/modules/mobile_vr/mobile_vr_interface.cpp +++ b/modules/mobile_vr/mobile_vr_interface.cpp @@ -61,13 +61,19 @@ Vector3 MobileVRInterface::scale_magneto(const Vector3 &p_magnetometer) { }; // adjust our min and max - if (mag_raw.x > mag_next_max.x) mag_next_max.x = mag_raw.x; - if (mag_raw.y > mag_next_max.y) mag_next_max.y = mag_raw.y; - if (mag_raw.z > mag_next_max.z) mag_next_max.z = mag_raw.z; - - if (mag_raw.x < mag_next_min.x) mag_next_min.x = mag_raw.x; - if (mag_raw.y < mag_next_min.y) mag_next_min.y = mag_raw.y; - if (mag_raw.z < mag_next_min.z) mag_next_min.z = mag_raw.z; + if (mag_raw.x > mag_next_max.x) + mag_next_max.x = mag_raw.x; + if (mag_raw.y > mag_next_max.y) + mag_next_max.y = mag_raw.y; + if (mag_raw.z > mag_next_max.z) + mag_next_max.z = mag_raw.z; + + if (mag_raw.x < mag_next_min.x) + mag_next_min.x = mag_raw.x; + if (mag_raw.y < mag_next_min.y) + mag_next_min.y = mag_raw.y; + if (mag_raw.z < mag_next_min.z) + mag_next_min.z = mag_raw.z; // scale our x, y and z if (!(mag_current_max.x - mag_current_min.x)) { diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py index 8e77f3bb44..7391e8790d 100644 --- a/modules/mono/build_scripts/godot_tools_build.py +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -15,7 +15,7 @@ def build_godot_tools(source, target, env): from .solution_builder import build_solution - build_solution(env, solution_path, build_config, restore=True) + build_solution(env, solution_path, build_config) # No need to copy targets. The GodotTools csproj takes care of copying them. diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py index e8ddb7114e..371819fd72 100644 --- a/modules/mono/build_scripts/solution_builder.py +++ b/modules/mono/build_scripts/solution_builder.py @@ -4,7 +4,29 @@ import os verbose = False -def find_msbuild_unix(filename): +def find_dotnet_cli(): + import os.path + + if os.name == "nt": + windows_exts = os.environ["PATHEXT"] + windows_exts = windows_exts.split(os.pathsep) if windows_exts else [] + + for hint_dir in os.environ["PATH"].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, "dotnet") + 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" + else: + for hint_dir in os.environ["PATH"].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, "dotnet") + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + + +def find_msbuild_unix(): import os.path import sys @@ -86,15 +108,7 @@ def run_command(command, args, env_override=None, name=None): raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode)) -def nuget_restore(env, *args): - global verbose - verbose = env["verbose"] - - # Do NuGet restore - run_command(nuget_path, ["restore"] + list(args), name="nuget restore") - - -def build_solution(env, solution_path, build_config, extra_msbuild_args=[], restore=False): +def build_solution(env, solution_path, build_config, extra_msbuild_args=[]): global verbose verbose = env["verbose"] @@ -104,27 +118,33 @@ def build_solution(env, solution_path, build_config, extra_msbuild_args=[], rest if "PLATFORM" in msbuild_env: del msbuild_env["PLATFORM"] - # Find MSBuild - if os.name == "nt": - msbuild_info = find_msbuild_windows(env) - if msbuild_info is None: - raise RuntimeError("Cannot find MSBuild executable") - msbuild_path = msbuild_info[0] - msbuild_env.update(msbuild_info[1]) + msbuild_args = [] + + dotnet_cli = find_dotnet_cli() + + if dotnet_cli: + msbuild_path = dotnet_cli + msbuild_args += ["msbuild"] # `dotnet msbuild` command else: - msbuild_path = find_msbuild_unix("msbuild") - if msbuild_path is None: - raise RuntimeError("Cannot find MSBuild executable") + # Find MSBuild + if os.name == "nt": + msbuild_info = find_msbuild_windows(env) + if msbuild_info is None: + raise RuntimeError("Cannot find MSBuild executable") + msbuild_path = msbuild_info[0] + msbuild_env.update(msbuild_info[1]) + else: + msbuild_path = find_msbuild_unix() + if msbuild_path is None: + raise RuntimeError("Cannot find MSBuild executable") print("MSBuild path: " + msbuild_path) # Build solution - targets = ["Build"] - if restore: - targets.insert(0, "Restore") + targets = ["Restore", "Build"] - msbuild_args = [solution_path, "/t:%s" % ",".join(targets), "/p:Configuration=" + build_config] + msbuild_args += [solution_path, "/t:%s" % ",".join(targets), "/p:Configuration=" + build_config] msbuild_args += extra_msbuild_args run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name="msbuild") diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 6c1c8b87ef..43c86d3e28 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -295,7 +295,7 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const { "when", "where", "yield", - 0 + nullptr }; const char **w = _reserved_words; @@ -2421,58 +2421,68 @@ void CSharpScript::_update_member_info_no_exports() { bool CSharpScript::_update_exports() { #ifdef TOOLS_ENABLED - if (!Engine::get_singleton()->is_editor_hint()) - return false; - - placeholder_fallback_enabled = true; // until proven otherwise - + bool is_editor = Engine::get_singleton()->is_editor_hint(); + if (is_editor) + placeholder_fallback_enabled = true; // until proven otherwise +#endif if (!valid) return false; bool changed = false; - if (exports_invalidated) { +#ifdef TOOLS_ENABLED + if (exports_invalidated) +#endif + { GD_MONO_SCOPE_THREAD_ATTACH; - exports_invalidated = false; - changed = true; member_info.clear(); - exported_members_cache.clear(); - exported_members_defval_cache.clear(); - // Here we create a temporary managed instance of the class to get the initial values +#ifdef TOOLS_ENABLED + MonoObject *tmp_object = nullptr; + Object *tmp_native = nullptr; + uint32_t tmp_pinned_gchandle = 0; - MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); + if (is_editor) { + exports_invalidated = false; - if (!tmp_object) { - ERR_PRINT("Failed to allocate temporary MonoObject."); - return false; - } + exported_members_cache.clear(); + exported_members_defval_cache.clear(); - uint32_t tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed) + // Here we create a temporary managed instance of the class to get the initial values + tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); - GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); + if (!tmp_object) { + ERR_PRINT("Failed to allocate temporary MonoObject."); + return false; + } - ERR_FAIL_NULL_V_MSG(ctor, false, - "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); + tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed) - MonoException *ctor_exc = nullptr; - ctor->invoke(tmp_object, nullptr, &ctor_exc); + GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - Object *tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); + ERR_FAIL_NULL_V_MSG(ctor, false, + "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); - if (ctor_exc) { - // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? + MonoException *ctor_exc = nullptr; + ctor->invoke(tmp_object, nullptr, &ctor_exc); - GDMonoUtils::free_gchandle(tmp_pinned_gchandle); - tmp_object = nullptr; + tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); - ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(ctor_exc); - return false; + if (ctor_exc) { + // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? + + GDMonoUtils::free_gchandle(tmp_pinned_gchandle); + tmp_object = nullptr; + + ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); + GDMonoUtils::debug_print_unhandled_exception(ctor_exc); + return false; + } } +#endif GDMonoClass *top = script_class; @@ -2488,16 +2498,16 @@ bool CSharpScript::_update_exports() { if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) { StringName member_name = field->get_name(); - if (exported) { - member_info[member_name] = prop_info; + member_info[member_name] = prop_info; +#ifdef TOOLS_ENABLED + if (is_editor && exported) { exported_members_cache.push_front(prop_info); if (tmp_object) { exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); } - } else { - member_info[member_name] = prop_info; } +#endif } } @@ -2509,10 +2519,10 @@ bool CSharpScript::_update_exports() { if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) { StringName member_name = property->get_name(); - if (exported) { - member_info[member_name] = prop_info; + member_info[member_name] = prop_info; +#ifdef TOOLS_ENABLED + if (is_editor && exported) { exported_members_cache.push_front(prop_info); - if (tmp_object) { MonoException *exc = nullptr; MonoObject *ret = property->get_value(tmp_object, &exc); @@ -2523,57 +2533,62 @@ bool CSharpScript::_update_exports() { exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret); } } - } else { - member_info[member_name] = prop_info; } +#endif } } top = top->get_parent_class(); } - // Need to check this here, before disposal - bool base_ref = Object::cast_to<Reference>(tmp_native) != nullptr; +#ifdef TOOLS_ENABLED + if (is_editor) { + // Need to check this here, before disposal + bool base_ref = Object::cast_to<Reference>(tmp_native) != nullptr; - // Dispose the temporary managed instance + // Dispose the temporary managed instance - MonoException *exc = nullptr; - GDMonoUtils::dispose(tmp_object, &exc); + MonoException *exc = nullptr; + GDMonoUtils::dispose(tmp_object, &exc); - if (exc) { - ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(exc); - } + if (exc) { + ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); + GDMonoUtils::debug_print_unhandled_exception(exc); + } - GDMonoUtils::free_gchandle(tmp_pinned_gchandle); - tmp_object = nullptr; + GDMonoUtils::free_gchandle(tmp_pinned_gchandle); + tmp_object = nullptr; - if (tmp_native && !base_ref) { - Node *node = Object::cast_to<Node>(tmp_native); - if (node && node->is_inside_tree()) { - ERR_PRINT("Temporary instance was added to the scene tree."); - } else { - memdelete(tmp_native); + if (tmp_native && !base_ref) { + Node *node = Object::cast_to<Node>(tmp_native); + if (node && node->is_inside_tree()) { + ERR_PRINT("Temporary instance was added to the scene tree."); + } else { + memdelete(tmp_native); + } } } +#endif } - placeholder_fallback_enabled = false; +#ifdef TOOLS_ENABLED + if (is_editor) { + placeholder_fallback_enabled = false; - if (placeholders.size()) { - // Update placeholders if any - Map<StringName, Variant> values; - List<PropertyInfo> propnames; - _update_exports_values(values, propnames); + if (placeholders.size()) { + // 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()->update(propnames, values); + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->update(propnames, values); + } } } +#endif return changed; -#endif - return false; } void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class) { @@ -2679,7 +2694,6 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_in return true; } -#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. @@ -2693,8 +2707,10 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect (m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name()) if (p_member->is_static()) { +#ifdef TOOLS_ENABLED if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) ERR_PRINT("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } @@ -2716,13 +2732,17 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) { GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); if (!property->has_getter()) { +#ifdef TOOLS_ENABLED if (exported) ERR_PRINT("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } if (!property->has_setter()) { +#ifdef TOOLS_ENABLED if (exported) ERR_PRINT("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } } @@ -2742,10 +2762,13 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect String hint_string; if (variant_type == Variant::NIL && !nil_is_variant) { +#ifdef TOOLS_ENABLED ERR_PRINT("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } +#ifdef TOOLS_ENABLED int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string); ERR_FAIL_COND_V_MSG(hint_res == -1, false, @@ -2756,6 +2779,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); } +#endif uint32_t prop_usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE; @@ -2772,6 +2796,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect #undef MEMBER_FULL_QUALIFIED_NAME } +#ifdef TOOLS_ENABLED int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) { if (p_variant_type == Variant::NIL) { @@ -3542,10 +3567,15 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const { return false; } -#ifndef _MSC_VER -#warning TODO: Implement CSharpScript::inherits_script and other relevant changes after GH-38063. -#endif - return false; + if (script_class == nullptr || cs->script_class == nullptr) { + return false; + } + + if (script_class == cs->script_class) { + return true; + } + + return cs->script_class->is_assignable_from(script_class); } Ref<Script> CSharpScript::get_base_script() const { diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 05e2857538..280c981a47 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -147,8 +147,9 @@ private: bool _get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> ¶ms); bool _update_exports(); -#ifdef TOOLS_ENABLED + bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported); +#ifdef TOOLS_ENABLED static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string); #endif @@ -325,17 +326,13 @@ public: }; struct CSharpScriptBinding { - bool inited; + bool inited = false; StringName type_name; - GDMonoClass *wrapper_class; + GDMonoClass *wrapper_class = nullptr; MonoGCHandleData gchandle; - Object *owner; + Object *owner = nullptr; - CSharpScriptBinding() : - inited(false), - wrapper_class(nullptr), - owner(nullptr) { - } + CSharpScriptBinding() {} }; class ManagedCallableMiddleman : public Object { diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index 6015cb22b6..c2549b4ad5 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -2,7 +2,6 @@ using System; using System.IO; using System.Security; using Microsoft.Build.Framework; -using GodotTools.Core; namespace GodotTools.BuildLogger { @@ -183,4 +182,17 @@ namespace GodotTools.BuildLogger private StreamWriter issuesStreamWriter; private int indent; } + + internal static class StringExtensions + { + public static string CsvEscape(this string value, char delimiter = ',') + { + bool hasSpecialChar = value.IndexOfAny(new[] { '\"', '\n', '\r', delimiter }) != -1; + + if (hasSpecialChar) + return "\"" + value.Replace("\"", "\"\"") + "\""; + + return value; + } + } } diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj index 8fdd485209..0afec970c6 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -1,60 +1,10 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid> - <OutputType>Library</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>GodotTools.BuildLogger</RootNamespace> - <AssemblyName>GodotTools.BuildLogger</AssemblyName> - <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> - <FileAlignment>512</FileAlignment> - <LangVersion>7</LangVersion> + <TargetFramework>netstandard2.0</TargetFramework> + <LangVersion>7.2</LangVersion> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <PlatformTarget>AnyCPU</PlatformTarget> - <DebugSymbols>true</DebugSymbols> - <DebugType>portable</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> - <DefineConstants>DEBUG;TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <PlatformTarget>AnyCPU</PlatformTarget> - <DebugType>portable</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> - <DefineConstants>TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <ItemGroup> - <Reference Include="Microsoft.Build.Framework" /> - <Reference Include="System" /> - <Reference Include="System.Core" /> - <Reference Include="System.Data" /> - <Reference Include="System.Xml" /> - </ItemGroup> - <ItemGroup> - <Compile Include="GodotBuildLogger.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - </ItemGroup> <ItemGroup> - <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> - <Project>{639e48bd-44e5-4091-8edd-22d36dc0768d}</Project> - <Name>GodotTools.Core</Name> - </ProjectReference> + <PackageReference Include="Microsoft.Build.Framework" Version="16.5.0" /> </ItemGroup> - <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <!-- To modify your build process, add your task inside one of the targets below and uncomment it. - Other similar extension points exist, see Microsoft.Common.targets. - <Target Name="BeforeBuild"> - </Target> - <Target Name="AfterBuild"> - </Target> - --> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs deleted file mode 100644 index 4374f21cfa..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("GodotTools.BuildLogger")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("Godot Engine contributors")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6CE9A984-37B1-4F8A-8FE9-609F05F071B3")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj index c9ea7d3a2c..d6d8962f90 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -1,40 +1,7 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid> - <OutputType>Library</OutputType> - <RootNamespace>GodotTools.Core</RootNamespace> - <AssemblyName>GodotTools.Core</AssemblyName> - <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> - <LangVersion>7</LangVersion> + <TargetFramework>netstandard2.0</TargetFramework> + <LangVersion>7.2</LangVersion> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug</OutputPath> - <DefineConstants>DEBUG;</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <Optimize>true</Optimize> - <OutputPath>bin\Release</OutputPath> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <ItemGroup> - <Reference Include="System" /> - </ItemGroup> - <ItemGroup> - <Compile Include="FileUtils.cs" /> - <Compile Include="ProcessExtensions.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="StringExtensions.cs" /> - </ItemGroup> - <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs deleted file mode 100644 index 699ae6e741..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("GodotTools.Core")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("Godot Engine contributors")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index 326c49f096..7ab5c5fc59 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -33,23 +33,13 @@ namespace GodotTools.Core return rooted ? Path.DirectorySeparatorChar + path : path; } - private static readonly string driveRoot = Path.GetPathRoot(Environment.CurrentDirectory); + private static readonly string DriveRoot = Path.GetPathRoot(Environment.CurrentDirectory); public static bool IsAbsolutePath(this string path) { return path.StartsWith("/", StringComparison.Ordinal) || path.StartsWith("\\", StringComparison.Ordinal) || - path.StartsWith(driveRoot, StringComparison.Ordinal); - } - - public static string CsvEscape(this string value, char delimiter = ',') - { - bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1; - - if (hasSpecialChar) - return "\"" + value.Replace("\"", "\"\"") + "\""; - - return value; + path.StartsWith(DriveRoot, StringComparison.Ordinal); } public static string ToSafeDirName(this string dirName, bool allowDirSeparator) diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs deleted file mode 100644 index 7a2ff2ca56..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace GodotTools.IdeConnection -{ - public class ConsoleLogger : ILogger - { - public void LogDebug(string message) - { - Console.WriteLine("DEBUG: " + message); - } - - public void LogInfo(string message) - { - Console.WriteLine("INFO: " + message); - } - - public void LogWarning(string message) - { - Console.WriteLine("WARN: " + message); - } - - public void LogError(string message) - { - Console.WriteLine("ERROR: " + message); - } - - public void LogError(string message, Exception e) - { - Console.WriteLine("EXCEPTION: " + message); - Console.WriteLine(e); - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs deleted file mode 100644 index be89638241..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using Path = System.IO.Path; - -namespace GodotTools.IdeConnection -{ - public class GodotIdeBase : IDisposable - { - private ILogger logger; - - public ILogger Logger - { - get => logger ?? (logger = new ConsoleLogger()); - set => logger = value; - } - - private readonly string projectMetadataDir; - - protected const string MetaFileName = "ide_server_meta.txt"; - protected string MetaFilePath => Path.Combine(projectMetadataDir, MetaFileName); - - private GodotIdeConnection connection; - protected readonly object ConnectionLock = new object(); - - public bool IsDisposed { get; private set; } = false; - - public bool IsConnected => connection != null && !connection.IsDisposed && connection.IsConnected; - - public event Action Connected - { - add - { - if (connection != null && !connection.IsDisposed) - connection.Connected += value; - } - remove - { - if (connection != null && !connection.IsDisposed) - connection.Connected -= value; - } - } - - protected GodotIdeConnection Connection - { - get => connection; - set - { - connection?.Dispose(); - connection = value; - } - } - - protected GodotIdeBase(string projectMetadataDir) - { - this.projectMetadataDir = projectMetadataDir; - } - - protected void DisposeConnection() - { - lock (ConnectionLock) - { - connection?.Dispose(); - } - } - - ~GodotIdeBase() - { - Dispose(disposing: false); - } - - public void Dispose() - { - if (IsDisposed) - return; - - lock (ConnectionLock) - { - if (IsDisposed) // lock may not be fair - return; - IsDisposed = true; - } - - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - connection?.Dispose(); - } - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs deleted file mode 100644 index 2bf3b83c75..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Threading; - -namespace GodotTools.IdeConnection -{ - public abstract class GodotIdeClient : GodotIdeBase - { - protected GodotIdeMetadata GodotIdeMetadata; - - private readonly FileSystemWatcher fsWatcher; - - protected GodotIdeClient(string projectMetadataDir) : base(projectMetadataDir) - { - messageHandlers = InitializeMessageHandlers(); - - // FileSystemWatcher requires an existing directory - if (!File.Exists(projectMetadataDir)) - Directory.CreateDirectory(projectMetadataDir); - - fsWatcher = new FileSystemWatcher(projectMetadataDir, MetaFileName); - } - - private void OnMetaFileChanged(object sender, FileSystemEventArgs e) - { - if (IsDisposed) - return; - - lock (ConnectionLock) - { - if (IsDisposed) - return; - - if (!File.Exists(MetaFilePath)) - return; - - var metadata = ReadMetadataFile(); - - if (metadata != null && metadata != GodotIdeMetadata) - { - GodotIdeMetadata = metadata.Value; - ConnectToServer(); - } - } - } - - private void OnMetaFileDeleted(object sender, FileSystemEventArgs e) - { - if (IsDisposed) - return; - - if (IsConnected) - DisposeConnection(); - - // The file may have been re-created - - lock (ConnectionLock) - { - if (IsDisposed) - return; - - if (IsConnected || !File.Exists(MetaFilePath)) - return; - - var metadata = ReadMetadataFile(); - - if (metadata != null) - { - GodotIdeMetadata = metadata.Value; - ConnectToServer(); - } - } - } - - private GodotIdeMetadata? ReadMetadataFile() - { - using (var reader = File.OpenText(MetaFilePath)) - { - string portStr = reader.ReadLine(); - - if (portStr == null) - return null; - - string editorExecutablePath = reader.ReadLine(); - - if (editorExecutablePath == null) - return null; - - if (!int.TryParse(portStr, out int port)) - return null; - - return new GodotIdeMetadata(port, editorExecutablePath); - } - } - - private void ConnectToServer() - { - var tcpClient = new TcpClient(); - - Connection = new GodotIdeConnectionClient(tcpClient, HandleMessage); - Connection.Logger = Logger; - - try - { - Logger.LogInfo("Connecting to Godot Ide Server"); - - tcpClient.Connect(IPAddress.Loopback, GodotIdeMetadata.Port); - - Logger.LogInfo("Connection open with Godot Ide Server"); - - var clientThread = new Thread(Connection.Start) - { - IsBackground = true, - Name = "Godot Ide Connection Client" - }; - clientThread.Start(); - } - catch (SocketException e) - { - if (e.SocketErrorCode == SocketError.ConnectionRefused) - Logger.LogError("The connection to the Godot Ide Server was refused"); - else - throw; - } - } - - public void Start() - { - Logger.LogInfo("Starting Godot Ide Client"); - - fsWatcher.Changed += OnMetaFileChanged; - fsWatcher.Deleted += OnMetaFileDeleted; - fsWatcher.EnableRaisingEvents = true; - - lock (ConnectionLock) - { - if (IsDisposed) - return; - - if (!File.Exists(MetaFilePath)) - { - Logger.LogInfo("There is no Godot Ide Server running"); - return; - } - - var metadata = ReadMetadataFile(); - - if (metadata != null) - { - GodotIdeMetadata = metadata.Value; - ConnectToServer(); - } - else - { - Logger.LogError("Failed to read Godot Ide metadata file"); - } - } - } - - public bool WriteMessage(Message message) - { - return Connection.WriteMessage(message); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - { - fsWatcher?.Dispose(); - } - } - - protected virtual bool HandleMessage(Message message) - { - if (messageHandlers.TryGetValue(message.Id, out var action)) - { - action(message.Arguments); - return true; - } - - return false; - } - - private readonly Dictionary<string, Action<string[]>> messageHandlers; - - private Dictionary<string, Action<string[]>> InitializeMessageHandlers() - { - return new Dictionary<string, Action<string[]>> - { - ["OpenFile"] = args => - { - switch (args.Length) - { - case 1: - OpenFile(file: args[0]); - return; - case 2: - OpenFile(file: args[0], line: int.Parse(args[1])); - return; - case 3: - OpenFile(file: args[0], line: int.Parse(args[1]), column: int.Parse(args[2])); - return; - default: - throw new ArgumentException(); - } - } - }; - } - - protected abstract void OpenFile(string file); - protected abstract void OpenFile(string file, int line); - protected abstract void OpenFile(string file, int line, int column); - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs deleted file mode 100644 index 6441be8d6e..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Net.Sockets; -using System.Text; - -namespace GodotTools.IdeConnection -{ - public abstract class GodotIdeConnection : IDisposable - { - protected const string Version = "1.0"; - - protected static readonly string ClientHandshake = $"Godot Ide Client Version {Version}"; - protected static readonly string ServerHandshake = $"Godot Ide Server Version {Version}"; - - private const int ClientWriteTimeout = 8000; - private readonly TcpClient tcpClient; - - private TextReader clientReader; - private TextWriter clientWriter; - - private readonly object writeLock = new object(); - - private readonly Func<Message, bool> messageHandler; - - public event Action Connected; - - private ILogger logger; - - public ILogger Logger - { - get => logger ?? (logger = new ConsoleLogger()); - set => logger = value; - } - - public bool IsDisposed { get; private set; } = false; - - public bool IsConnected => tcpClient.Client != null && tcpClient.Client.Connected; - - protected GodotIdeConnection(TcpClient tcpClient, Func<Message, bool> messageHandler) - { - this.tcpClient = tcpClient; - this.messageHandler = messageHandler; - } - - public void Start() - { - try - { - if (!StartConnection()) - return; - - string messageLine; - while ((messageLine = ReadLine()) != null) - { - if (!MessageParser.TryParse(messageLine, out Message msg)) - { - Logger.LogError($"Received message with invalid format: {messageLine}"); - continue; - } - - Logger.LogDebug($"Received message: {msg}"); - - if (msg.Id == "close") - { - Logger.LogInfo("Closing connection"); - return; - } - - try - { - try - { - Debug.Assert(messageHandler != null); - - if (!messageHandler(msg)) - Logger.LogError($"Received unknown message: {msg}"); - } - catch (Exception e) - { - Logger.LogError($"Message handler for '{msg}' failed with exception", e); - } - } - catch (Exception e) - { - Logger.LogError($"Exception thrown from message handler. Message: {msg}", e); - } - } - } - catch (Exception e) - { - Logger.LogError($"Unhandled exception in the Godot Ide Connection thread", e); - } - finally - { - Dispose(); - } - } - - private bool StartConnection() - { - NetworkStream clientStream = tcpClient.GetStream(); - - clientReader = new StreamReader(clientStream, Encoding.UTF8); - - lock (writeLock) - clientWriter = new StreamWriter(clientStream, Encoding.UTF8); - - clientStream.WriteTimeout = ClientWriteTimeout; - - if (!WriteHandshake()) - { - Logger.LogError("Could not write handshake"); - return false; - } - - if (!IsValidResponseHandshake(ReadLine())) - { - Logger.LogError("Received invalid handshake"); - return false; - } - - Connected?.Invoke(); - - Logger.LogInfo("Godot Ide connection started"); - - return true; - } - - private string ReadLine() - { - try - { - return clientReader?.ReadLine(); - } - catch (Exception e) - { - if (IsDisposed) - { - var se = e as SocketException ?? e.InnerException as SocketException; - if (se != null && se.SocketErrorCode == SocketError.Interrupted) - return null; - } - - throw; - } - } - - public bool WriteMessage(Message message) - { - Logger.LogDebug($"Sending message {message}"); - - var messageComposer = new MessageComposer(); - - messageComposer.AddArgument(message.Id); - foreach (string argument in message.Arguments) - messageComposer.AddArgument(argument); - - return WriteLine(messageComposer.ToString()); - } - - protected bool WriteLine(string text) - { - if (clientWriter == null || IsDisposed || !IsConnected) - return false; - - lock (writeLock) - { - try - { - clientWriter.WriteLine(text); - clientWriter.Flush(); - } - catch (Exception e) - { - if (!IsDisposed) - { - var se = e as SocketException ?? e.InnerException as SocketException; - if (se != null && se.SocketErrorCode == SocketError.Shutdown) - Logger.LogInfo("Client disconnected ungracefully"); - else - Logger.LogError("Exception thrown when trying to write to client", e); - - Dispose(); - } - } - } - - return true; - } - - protected abstract bool WriteHandshake(); - protected abstract bool IsValidResponseHandshake(string handshakeLine); - - public void Dispose() - { - if (IsDisposed) - return; - - IsDisposed = true; - - clientReader?.Dispose(); - clientWriter?.Dispose(); - ((IDisposable)tcpClient)?.Dispose(); - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs deleted file mode 100644 index 1b11a14358..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Net.Sockets; -using System.Threading.Tasks; - -namespace GodotTools.IdeConnection -{ - public class GodotIdeConnectionClient : GodotIdeConnection - { - public GodotIdeConnectionClient(TcpClient tcpClient, Func<Message, bool> messageHandler) - : base(tcpClient, messageHandler) - { - } - - protected override bool WriteHandshake() - { - return WriteLine(ClientHandshake); - } - - protected override bool IsValidResponseHandshake(string handshakeLine) - { - return handshakeLine == ServerHandshake; - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs deleted file mode 100644 index aa98dc7ca3..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Net.Sockets; -using System.Threading.Tasks; - -namespace GodotTools.IdeConnection -{ - public class GodotIdeConnectionServer : GodotIdeConnection - { - public GodotIdeConnectionServer(TcpClient tcpClient, Func<Message, bool> messageHandler) - : base(tcpClient, messageHandler) - { - } - - protected override bool WriteHandshake() - { - return WriteLine(ServerHandshake); - } - - protected override bool IsValidResponseHandshake(string handshakeLine) - { - return handshakeLine == ClientHandshake; - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj deleted file mode 100644 index 8454535fba..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid> - <OutputType>Library</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>GodotTools.IdeConnection</RootNamespace> - <AssemblyName>GodotTools.IdeConnection</AssemblyName> - <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> - <FileAlignment>512</FileAlignment> - <LangVersion>7</LangVersion> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <PlatformTarget>AnyCPU</PlatformTarget> - <DebugSymbols>true</DebugSymbols> - <DebugType>portable</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> - <DefineConstants>DEBUG;TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <PlatformTarget>AnyCPU</PlatformTarget> - <DebugType>portable</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> - <DefineConstants>TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <ItemGroup> - <Reference Include="System" /> - </ItemGroup> - <ItemGroup> - <Compile Include="ConsoleLogger.cs" /> - <Compile Include="GodotIdeMetadata.cs" /> - <Compile Include="GodotIdeBase.cs" /> - <Compile Include="GodotIdeClient.cs" /> - <Compile Include="GodotIdeConnection.cs" /> - <Compile Include="GodotIdeConnectionClient.cs" /> - <Compile Include="GodotIdeConnectionServer.cs" /> - <Compile Include="ILogger.cs" /> - <Compile Include="Message.cs" /> - <Compile Include="MessageComposer.cs" /> - <Compile Include="MessageParser.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - </ItemGroup> - <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> -</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs deleted file mode 100644 index f24d324ae3..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Linq; - -namespace GodotTools.IdeConnection -{ - public struct Message - { - public string Id { get; set; } - public string[] Arguments { get; set; } - - public Message(string id, params string[] arguments) - { - Id = id; - Arguments = arguments; - } - - public override string ToString() - { - return $"(Id: '{Id}', Arguments: '{string.Join(",", Arguments)}')"; - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs deleted file mode 100644 index 30ffe7a06e..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Linq; -using System.Text; - -namespace GodotTools.IdeConnection -{ - public class MessageComposer - { - private readonly StringBuilder stringBuilder = new StringBuilder(); - - private static readonly char[] CharsToEscape = { '\\', '"' }; - - public void AddArgument(string argument) - { - AddArgument(argument, quoted: argument.Contains(",")); - } - - public void AddArgument(string argument, bool quoted) - { - if (stringBuilder.Length > 0) - stringBuilder.Append(','); - - if (quoted) - { - stringBuilder.Append('"'); - - foreach (char @char in argument) - { - if (CharsToEscape.Contains(@char)) - stringBuilder.Append('\\'); - stringBuilder.Append(@char); - } - - stringBuilder.Append('"'); - } - else - { - stringBuilder.Append(argument); - } - } - - public override string ToString() - { - return stringBuilder.ToString(); - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs deleted file mode 100644 index 4365d69989..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace GodotTools.IdeConnection -{ - public static class MessageParser - { - public static bool TryParse(string messageLine, out Message message) - { - var arguments = new List<string>(); - var stringBuilder = new StringBuilder(); - - bool expectingArgument = true; - - for (int i = 0; i < messageLine.Length; i++) - { - char @char = messageLine[i]; - - if (@char == ',') - { - if (expectingArgument) - arguments.Add(string.Empty); - - expectingArgument = true; - continue; - } - - bool quoted = false; - - if (messageLine[i] == '"') - { - quoted = true; - i++; - } - - while (i < messageLine.Length) - { - @char = messageLine[i]; - - if (quoted && @char == '"') - { - i++; - break; - } - - if (@char == '\\') - { - i++; - if (i < messageLine.Length) - break; - - stringBuilder.Append(messageLine[i]); - } - else if (!quoted && @char == ',') - { - break; // We don't increment the counter to allow the colon to be parsed after this - } - else - { - stringBuilder.Append(@char); - } - - i++; - } - - arguments.Add(stringBuilder.ToString()); - stringBuilder.Clear(); - - expectingArgument = false; - } - - if (arguments.Count == 0) - { - message = new Message(); - return false; - } - - message = new Message - { - Id = arguments[0], - Arguments = arguments.Skip(1).ToArray() - }; - - return true; - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs deleted file mode 100644 index 0806d02ca0..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("GodotTools.IdeConnection")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("Godot Engine contributors")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("92600954-25F0-4291-8E11-1FEE9FC4BE20")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs new file mode 100644 index 0000000000..3cb6a6687e --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs @@ -0,0 +1,57 @@ +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using GodotTools.IdeMessaging.Utils; + +namespace GodotTools.IdeMessaging.CLI +{ + public class ForwarderMessageHandler : IMessageHandler + { + private readonly StreamWriter outputWriter; + private readonly SemaphoreSlim outputWriteSem = new SemaphoreSlim(1); + + public ForwarderMessageHandler(StreamWriter outputWriter) + { + this.outputWriter = outputWriter; + } + + public async Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger) + { + await WriteRequestToOutput(id, content); + return new MessageContent(MessageStatus.RequestNotSupported, "null"); + } + + private async Task WriteRequestToOutput(string id, MessageContent content) + { + using (await outputWriteSem.UseAsync()) + { + await outputWriter.WriteLineAsync("======= Request ======="); + await outputWriter.WriteLineAsync(id); + await outputWriter.WriteLineAsync(content.Body.Count(c => c == '\n').ToString()); + await outputWriter.WriteLineAsync(content.Body); + await outputWriter.WriteLineAsync("======================="); + await outputWriter.FlushAsync(); + } + } + + public async Task WriteResponseToOutput(string id, MessageContent content) + { + using (await outputWriteSem.UseAsync()) + { + await outputWriter.WriteLineAsync("======= Response ======="); + await outputWriter.WriteLineAsync(id); + await outputWriter.WriteLineAsync(content.Body.Count(c => c == '\n').ToString()); + await outputWriter.WriteLineAsync(content.Body); + await outputWriter.WriteLineAsync("========================"); + await outputWriter.FlushAsync(); + } + } + + public async Task WriteLineToOutput(string eventName) + { + using (await outputWriteSem.UseAsync()) + await outputWriter.WriteLineAsync($"======= {eventName} ======="); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj new file mode 100644 index 0000000000..ae78da27bc --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj @@ -0,0 +1,17 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <ProjectGuid>{B06C2951-C8E3-4F28-80B2-717CF327EB19}</ProjectGuid> + <OutputType>Exe</OutputType> + <TargetFramework>net472</TargetFramework> + <LangVersion>7.2</LangVersion> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" /> + </ItemGroup> + <ItemGroup> + <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> + </ItemGroup> +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs new file mode 100644 index 0000000000..99a55c471b --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using GodotTools.IdeMessaging.Requests; +using Newtonsoft.Json; + +namespace GodotTools.IdeMessaging.CLI +{ + internal static class Program + { + private static readonly ILogger Logger = new CustomLogger(); + + public static int Main(string[] args) + { + try + { + var mainTask = StartAsync(args, Console.OpenStandardInput(), Console.OpenStandardOutput()); + mainTask.Wait(); + return mainTask.Result; + } + catch (Exception ex) + { + Logger.LogError("Unhandled exception: ", ex); + return 1; + } + } + + private static async Task<int> StartAsync(string[] args, Stream inputStream, Stream outputStream) + { + var inputReader = new StreamReader(inputStream, Encoding.UTF8); + var outputWriter = new StreamWriter(outputStream, Encoding.UTF8); + + try + { + if (args.Length == 0) + { + Logger.LogError("Expected at least 1 argument"); + return 1; + } + + string godotProjectDir = args[0]; + + if (!Directory.Exists(godotProjectDir)) + { + Logger.LogError($"The specified Godot project directory does not exist: {godotProjectDir}"); + return 1; + } + + var forwarder = new ForwarderMessageHandler(outputWriter); + + using (var fwdClient = new Client("VisualStudioCode", godotProjectDir, forwarder, Logger)) + { + fwdClient.Start(); + + // ReSharper disable AccessToDisposedClosure + fwdClient.Connected += async () => await forwarder.WriteLineToOutput("Event=Connected"); + fwdClient.Disconnected += async () => await forwarder.WriteLineToOutput("Event=Disconnected"); + // ReSharper restore AccessToDisposedClosure + + // TODO: Await connected with timeout + + while (!fwdClient.IsDisposed) + { + string firstLine = await inputReader.ReadLineAsync(); + + if (firstLine == null || firstLine == "QUIT") + goto ExitMainLoop; + + string messageId = firstLine; + + string messageArgcLine = await inputReader.ReadLineAsync(); + + if (messageArgcLine == null) + { + Logger.LogInfo("EOF when expecting argument count"); + goto ExitMainLoop; + } + + if (!int.TryParse(messageArgcLine, out int messageArgc)) + { + Logger.LogError("Received invalid line for argument count: " + firstLine); + continue; + } + + var body = new StringBuilder(); + + for (int i = 0; i < messageArgc; i++) + { + string bodyLine = await inputReader.ReadLineAsync(); + + if (bodyLine == null) + { + Logger.LogInfo($"EOF when expecting body line #{i + 1}"); + goto ExitMainLoop; + } + + body.AppendLine(bodyLine); + } + + var response = await SendRequest(fwdClient, messageId, new MessageContent(MessageStatus.Ok, body.ToString())); + + if (response == null) + { + Logger.LogError($"Failed to write message to the server: {messageId}"); + } + else + { + var content = new MessageContent(response.Status, JsonConvert.SerializeObject(response)); + await forwarder.WriteResponseToOutput(messageId, content); + } + } + + ExitMainLoop: + + await forwarder.WriteLineToOutput("Event=Quit"); + } + + return 0; + } + catch (Exception e) + { + Logger.LogError("Unhandled exception", e); + return 1; + } + } + + private static async Task<Response> SendRequest(Client client, string id, MessageContent content) + { + var handlers = new Dictionary<string, Func<Task<Response>>> + { + [PlayRequest.Id] = async () => + { + var request = JsonConvert.DeserializeObject<PlayRequest>(content.Body); + return await client.SendRequest<PlayResponse>(request); + }, + [DebugPlayRequest.Id] = async () => + { + var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body); + return await client.SendRequest<DebugPlayResponse>(request); + }, + [ReloadScriptsRequest.Id] = async () => + { + var request = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body); + return await client.SendRequest<ReloadScriptsResponse>(request); + }, + [CodeCompletionRequest.Id] = async () => + { + var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body); + return await client.SendRequest<CodeCompletionResponse>(request); + } + }; + + if (handlers.TryGetValue(id, out var handler)) + return await handler(); + + Console.WriteLine("INVALID REQUEST"); + return null; + } + + private class CustomLogger : ILogger + { + private static string ThisAppPath => Assembly.GetExecutingAssembly().Location; + private static string ThisAppPathWithoutExtension => Path.ChangeExtension(ThisAppPath, null); + + private static readonly string LogPath = $"{ThisAppPathWithoutExtension}.log"; + + private static StreamWriter NewWriter() => new StreamWriter(LogPath, append: true, encoding: Encoding.UTF8); + + private static void Log(StreamWriter writer, string message) + { + writer.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}: {message}"); + } + + public void LogDebug(string message) + { + using (var writer = NewWriter()) + { + Log(writer, "DEBUG: " + message); + } + } + + public void LogInfo(string message) + { + using (var writer = NewWriter()) + { + Log(writer, "INFO: " + message); + } + } + + public void LogWarning(string message) + { + using (var writer = NewWriter()) + { + Log(writer, "WARN: " + message); + } + } + + public void LogError(string message) + { + using (var writer = NewWriter()) + { + Log(writer, "ERROR: " + message); + } + } + + public void LogError(string message, Exception e) + { + using (var writer = NewWriter()) + { + Log(writer, "EXCEPTION: " + message + '\n' + e); + } + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs new file mode 100644 index 0000000000..d069651dd3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs @@ -0,0 +1,332 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using Newtonsoft.Json; +using System.Threading; +using System.Threading.Tasks; +using GodotTools.IdeMessaging.Requests; +using GodotTools.IdeMessaging.Utils; + +namespace GodotTools.IdeMessaging +{ + // ReSharper disable once UnusedType.Global + public sealed class Client : IDisposable + { + private readonly ILogger logger; + + private readonly string identity; + + private string MetaFilePath { get; } + private GodotIdeMetadata godotIdeMetadata; + private readonly FileSystemWatcher fsWatcher; + + private readonly IMessageHandler messageHandler; + + private Peer peer; + private readonly SemaphoreSlim connectionSem = new SemaphoreSlim(1); + + private readonly Queue<NotifyAwaiter<bool>> clientConnectedAwaiters = new Queue<NotifyAwaiter<bool>>(); + private readonly Queue<NotifyAwaiter<bool>> clientDisconnectedAwaiters = new Queue<NotifyAwaiter<bool>>(); + + // ReSharper disable once UnusedMember.Global + public async Task<bool> AwaitConnected() + { + var awaiter = new NotifyAwaiter<bool>(); + clientConnectedAwaiters.Enqueue(awaiter); + return await awaiter; + } + + // ReSharper disable once UnusedMember.Global + public async Task<bool> AwaitDisconnected() + { + var awaiter = new NotifyAwaiter<bool>(); + clientDisconnectedAwaiters.Enqueue(awaiter); + return await awaiter; + } + + // ReSharper disable once MemberCanBePrivate.Global + public bool IsDisposed { get; private set; } + + // ReSharper disable once MemberCanBePrivate.Global + public bool IsConnected => peer != null && !peer.IsDisposed && peer.IsTcpClientConnected; + + // ReSharper disable once EventNeverSubscribedTo.Global + public event Action Connected + { + add + { + if (peer != null && !peer.IsDisposed) + peer.Connected += value; + } + remove + { + if (peer != null && !peer.IsDisposed) + peer.Connected -= value; + } + } + + // ReSharper disable once EventNeverSubscribedTo.Global + public event Action Disconnected + { + add + { + if (peer != null && !peer.IsDisposed) + peer.Disconnected += value; + } + remove + { + if (peer != null && !peer.IsDisposed) + peer.Disconnected -= value; + } + } + + ~Client() + { + Dispose(disposing: false); + } + + public async void Dispose() + { + if (IsDisposed) + return; + + using (await connectionSem.UseAsync()) + { + if (IsDisposed) // lock may not be fair + return; + IsDisposed = true; + } + + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + peer?.Dispose(); + fsWatcher?.Dispose(); + } + } + + public Client(string identity, string godotProjectDir, IMessageHandler messageHandler, ILogger logger) + { + this.identity = identity; + this.messageHandler = messageHandler; + this.logger = logger; + + string projectMetadataDir = Path.Combine(godotProjectDir, ".mono", "metadata"); + + MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName); + + // FileSystemWatcher requires an existing directory + if (!File.Exists(projectMetadataDir)) + Directory.CreateDirectory(projectMetadataDir); + + fsWatcher = new FileSystemWatcher(projectMetadataDir, GodotIdeMetadata.DefaultFileName); + } + + private async void OnMetaFileChanged(object sender, FileSystemEventArgs e) + { + if (IsDisposed) + return; + + using (await connectionSem.UseAsync()) + { + if (IsDisposed) + return; + + if (!File.Exists(MetaFilePath)) + return; + + var metadata = ReadMetadataFile(); + + if (metadata != null && metadata != godotIdeMetadata) + { + godotIdeMetadata = metadata.Value; + _ = Task.Run(ConnectToServer); + } + } + } + + private async void OnMetaFileDeleted(object sender, FileSystemEventArgs e) + { + if (IsDisposed) + return; + + if (IsConnected) + { + using (await connectionSem.UseAsync()) + peer?.Dispose(); + } + + // The file may have been re-created + + using (await connectionSem.UseAsync()) + { + if (IsDisposed) + return; + + if (IsConnected || !File.Exists(MetaFilePath)) + return; + + var metadata = ReadMetadataFile(); + + if (metadata != null) + { + godotIdeMetadata = metadata.Value; + _ = Task.Run(ConnectToServer); + } + } + } + + private GodotIdeMetadata? ReadMetadataFile() + { + using (var reader = File.OpenText(MetaFilePath)) + { + string portStr = reader.ReadLine(); + + if (portStr == null) + return null; + + string editorExecutablePath = reader.ReadLine(); + + if (editorExecutablePath == null) + return null; + + if (!int.TryParse(portStr, out int port)) + return null; + + return new GodotIdeMetadata(port, editorExecutablePath); + } + } + + private async Task AcceptClient(TcpClient tcpClient) + { + logger.LogDebug("Accept client..."); + + using (peer = new Peer(tcpClient, new ClientHandshake(), messageHandler, logger)) + { + // ReSharper disable AccessToDisposedClosure + peer.Connected += () => + { + logger.LogInfo("Connection open with Ide Client"); + + while (clientConnectedAwaiters.Count > 0) + clientConnectedAwaiters.Dequeue().SetResult(true); + }; + + peer.Disconnected += () => + { + while (clientDisconnectedAwaiters.Count > 0) + clientDisconnectedAwaiters.Dequeue().SetResult(true); + }; + // ReSharper restore AccessToDisposedClosure + + try + { + if (!await peer.DoHandshake(identity)) + { + logger.LogError("Handshake failed"); + return; + } + } + catch (Exception e) + { + logger.LogError("Handshake failed with unhandled exception: ", e); + return; + } + + await peer.Process(); + + logger.LogInfo("Connection closed with Ide Client"); + } + } + + private async Task ConnectToServer() + { + var tcpClient = new TcpClient(); + + try + { + logger.LogInfo("Connecting to Godot Ide Server"); + + await tcpClient.ConnectAsync(IPAddress.Loopback, godotIdeMetadata.Port); + + logger.LogInfo("Connection open with Godot Ide Server"); + + await AcceptClient(tcpClient); + } + catch (SocketException e) + { + if (e.SocketErrorCode == SocketError.ConnectionRefused) + logger.LogError("The connection to the Godot Ide Server was refused"); + else + throw; + } + } + + // ReSharper disable once UnusedMember.Global + public async void Start() + { + fsWatcher.Changed += OnMetaFileChanged; + fsWatcher.Deleted += OnMetaFileDeleted; + fsWatcher.EnableRaisingEvents = true; + + using (await connectionSem.UseAsync()) + { + if (IsDisposed) + return; + + if (IsConnected) + return; + + if (!File.Exists(MetaFilePath)) + { + logger.LogInfo("There is no Godot Ide Server running"); + return; + } + + var metadata = ReadMetadataFile(); + + if (metadata != null) + { + godotIdeMetadata = metadata.Value; + _ = Task.Run(ConnectToServer); + } + else + { + logger.LogError("Failed to read Godot Ide metadata file"); + } + } + } + + public async Task<TResponse> SendRequest<TResponse>(Request request) + where TResponse : Response, new() + { + if (!IsConnected) + { + logger.LogError("Cannot write request. Not connected to the Godot Ide Server."); + return null; + } + + string body = JsonConvert.SerializeObject(request); + return await peer.SendRequest<TResponse>(request.Id, body); + } + + public async Task<TResponse> SendRequest<TResponse>(string id, string body) + where TResponse : Response, new() + { + if (!IsConnected) + { + logger.LogError("Cannot write request. Not connected to the Godot Ide Server."); + return null; + } + + return await peer.SendRequest<TResponse>(id, body); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs new file mode 100644 index 0000000000..43041be7be --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs @@ -0,0 +1,44 @@ +using System.Text.RegularExpressions; + +namespace GodotTools.IdeMessaging +{ + public class ClientHandshake : IHandshake + { + private static readonly string ClientHandshakeBase = $"{Peer.ClientHandshakeName},Version={Peer.ProtocolVersionMajor}.{Peer.ProtocolVersionMinor}.{Peer.ProtocolVersionRevision}"; + private static readonly string ServerHandshakePattern = $@"{Regex.Escape(Peer.ServerHandshakeName)},Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{{0,63}})"; + + public string GetHandshakeLine(string identity) => $"{ClientHandshakeBase},{identity}"; + + public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger) + { + identity = null; + + var match = Regex.Match(handshake, ServerHandshakePattern); + + if (!match.Success) + return false; + + if (!uint.TryParse(match.Groups[1].Value, out uint serverMajor) || Peer.ProtocolVersionMajor != serverMajor) + { + logger.LogDebug("Incompatible major version: " + match.Groups[1].Value); + return false; + } + + if (!uint.TryParse(match.Groups[2].Value, out uint serverMinor) || Peer.ProtocolVersionMinor < serverMinor) + { + logger.LogDebug("Incompatible minor version: " + match.Groups[2].Value); + return false; + } + + if (!uint.TryParse(match.Groups[3].Value, out uint _)) // Revision + { + logger.LogDebug("Incompatible revision build: " + match.Groups[3].Value); + return false; + } + + identity = match.Groups[4].Value; + + return true; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs new file mode 100644 index 0000000000..64bcfd824c --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using GodotTools.IdeMessaging.Requests; +using Newtonsoft.Json; + +namespace GodotTools.IdeMessaging +{ + // ReSharper disable once UnusedType.Global + public abstract class ClientMessageHandler : IMessageHandler + { + private readonly Dictionary<string, Peer.RequestHandler> requestHandlers; + + protected ClientMessageHandler() + { + requestHandlers = InitializeRequestHandlers(); + } + + public async Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger) + { + if (!requestHandlers.TryGetValue(id, out var handler)) + { + logger.LogError($"Received unknown request: {id}"); + return new MessageContent(MessageStatus.RequestNotSupported, "null"); + } + + try + { + var response = await handler(peer, content); + return new MessageContent(response.Status, JsonConvert.SerializeObject(response)); + } + catch (JsonException) + { + logger.LogError($"Received request with invalid body: {id}"); + return new MessageContent(MessageStatus.InvalidRequestBody, "null"); + } + } + + private Dictionary<string, Peer.RequestHandler> InitializeRequestHandlers() + { + return new Dictionary<string, Peer.RequestHandler> + { + [OpenFileRequest.Id] = async (peer, content) => + { + var request = JsonConvert.DeserializeObject<OpenFileRequest>(content.Body); + return await HandleOpenFile(request); + } + }; + } + + protected abstract Task<Response> HandleOpenFile(OpenFileRequest request); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs index d16daba0e2..686202e81e 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs @@ -1,10 +1,12 @@ -namespace GodotTools.IdeConnection +namespace GodotTools.IdeMessaging { - public struct GodotIdeMetadata + public readonly struct GodotIdeMetadata { public int Port { get; } public string EditorExecutablePath { get; } + public const string DefaultFileName = "ide_messaging_meta.txt"; + public GodotIdeMetadata(int port, string editorExecutablePath) { Port = port; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj new file mode 100644 index 0000000000..67815959a6 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj @@ -0,0 +1,24 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid> + <TargetFramework>netstandard2.0</TargetFramework> + <LangVersion>7.2</LangVersion> + <PackageId>GodotTools.IdeMessaging</PackageId> + <Version>1.1.0</Version> + <AssemblyVersion>$(Version)</AssemblyVersion> + <Authors>Godot Engine contributors</Authors> + <Company /> + <PackageTags>godot</PackageTags> + <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/GodotTools/GodotTools.IdeMessaging</RepositoryUrl> + <PackageLicenseExpression>MIT</PackageLicenseExpression> + <Description> +This library enables communication with the Godot Engine editor (the version with .NET support). +It's intended for use in IDEs/editors plugins for a better experience working with Godot C# projects. + +A client using this library is only compatible with servers of the same major version and of a lower or equal minor version. + </Description> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> + </ItemGroup> +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs new file mode 100644 index 0000000000..6387145a28 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs @@ -0,0 +1,8 @@ +namespace GodotTools.IdeMessaging +{ + public interface IHandshake + { + string GetHandshakeLine(string identity); + bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ILogger.cs index 614bb30271..d2855f93a1 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ILogger.cs @@ -1,6 +1,6 @@ using System; -namespace GodotTools.IdeConnection +namespace GodotTools.IdeMessaging { public interface ILogger { diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs new file mode 100644 index 0000000000..9622fcc96d --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace GodotTools.IdeMessaging +{ + public interface IMessageHandler + { + Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs new file mode 100644 index 0000000000..6903ec197b --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs @@ -0,0 +1,52 @@ +namespace GodotTools.IdeMessaging +{ + public class Message + { + public MessageKind Kind { get; } + public string Id { get; } + public MessageContent Content { get; } + + public Message(MessageKind kind, string id, MessageContent content) + { + Kind = kind; + Id = id; + Content = content; + } + + public override string ToString() + { + return $"{Kind} | {Id}"; + } + } + + public enum MessageKind + { + Request, + Response + } + + public enum MessageStatus + { + Ok, + RequestNotSupported, + InvalidRequestBody + } + + public readonly struct MessageContent + { + public MessageStatus Status { get; } + public string Body { get; } + + public MessageContent(string body) + { + Status = MessageStatus.Ok; + Body = body; + } + + public MessageContent(MessageStatus status, string body) + { + Status = status; + Body = body; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs new file mode 100644 index 0000000000..a00575a2a1 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs @@ -0,0 +1,100 @@ +using System; +using System.Text; + +namespace GodotTools.IdeMessaging +{ + public class MessageDecoder + { + private class DecodedMessage + { + public MessageKind? Kind; + public string Id; + public MessageStatus? Status; + public readonly StringBuilder Body = new StringBuilder(); + public uint? PendingBodyLines; + + public void Clear() + { + Kind = null; + Id = null; + Status = null; + Body.Clear(); + PendingBodyLines = null; + } + + public Message ToMessage() + { + if (!Kind.HasValue || Id == null || !Status.HasValue || + !PendingBodyLines.HasValue || PendingBodyLines.Value > 0) + throw new InvalidOperationException(); + + return new Message(Kind.Value, Id, new MessageContent(Status.Value, Body.ToString())); + } + } + + public enum State + { + Decoding, + Decoded, + Errored + } + + private readonly DecodedMessage decodingMessage = new DecodedMessage(); + + public State Decode(string messageLine, out Message decodedMessage) + { + decodedMessage = null; + + if (!decodingMessage.Kind.HasValue) + { + if (!Enum.TryParse(messageLine, ignoreCase: true, out MessageKind kind)) + { + decodingMessage.Clear(); + return State.Errored; + } + + decodingMessage.Kind = kind; + } + else if (decodingMessage.Id == null) + { + decodingMessage.Id = messageLine; + } + else if (decodingMessage.Status == null) + { + if (!Enum.TryParse(messageLine, ignoreCase: true, out MessageStatus status)) + { + decodingMessage.Clear(); + return State.Errored; + } + + decodingMessage.Status = status; + } + else if (decodingMessage.PendingBodyLines == null) + { + if (!uint.TryParse(messageLine, out uint pendingBodyLines)) + { + decodingMessage.Clear(); + return State.Errored; + } + + decodingMessage.PendingBodyLines = pendingBodyLines; + } + else + { + if (decodingMessage.PendingBodyLines > 0) + { + decodingMessage.Body.AppendLine(messageLine); + decodingMessage.PendingBodyLines -= 1; + } + else + { + decodedMessage = decodingMessage.ToMessage(); + decodingMessage.Clear(); + return State.Decoded; + } + } + + return State.Decoding; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs new file mode 100644 index 0000000000..a4e86d6177 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs @@ -0,0 +1,302 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GodotTools.IdeMessaging.Requests; +using GodotTools.IdeMessaging.Utils; + +namespace GodotTools.IdeMessaging +{ + public sealed class Peer : IDisposable + { + /// <summary> + /// Major version. + /// There is no forward nor backward compatibility between different major versions. + /// Connection is refused if client and server have different major versions. + /// </summary> + public static readonly int ProtocolVersionMajor = Assembly.GetAssembly(typeof(Peer)).GetName().Version.Major; + + /// <summary> + /// Minor version, which clients must be backward compatible with. + /// Connection is refused if the client's minor version is lower than the server's. + /// </summary> + public static readonly int ProtocolVersionMinor = Assembly.GetAssembly(typeof(Peer)).GetName().Version.Minor; + + /// <summary> + /// Revision, which doesn't affect compatibility. + /// </summary> + public static readonly int ProtocolVersionRevision = Assembly.GetAssembly(typeof(Peer)).GetName().Version.Revision; + + public const string ClientHandshakeName = "GodotIdeClient"; + public const string ServerHandshakeName = "GodotIdeServer"; + + private const int ClientWriteTimeout = 8000; + + public delegate Task<Response> RequestHandler(Peer peer, MessageContent content); + + private readonly TcpClient tcpClient; + + private readonly TextReader clientReader; + private readonly TextWriter clientWriter; + + private readonly SemaphoreSlim writeSem = new SemaphoreSlim(1); + + private string remoteIdentity = string.Empty; + public string RemoteIdentity => remoteIdentity; + + public event Action Connected; + public event Action Disconnected; + + private ILogger Logger { get; } + + public bool IsDisposed { get; private set; } + + public bool IsTcpClientConnected => tcpClient.Client != null && tcpClient.Client.Connected; + + private bool IsConnected { get; set; } + + private readonly IHandshake handshake; + private readonly IMessageHandler messageHandler; + + private readonly Dictionary<string, Queue<ResponseAwaiter>> requestAwaiterQueues = new Dictionary<string, Queue<ResponseAwaiter>>(); + private readonly SemaphoreSlim requestsSem = new SemaphoreSlim(1); + + public Peer(TcpClient tcpClient, IHandshake handshake, IMessageHandler messageHandler, ILogger logger) + { + this.tcpClient = tcpClient; + this.handshake = handshake; + this.messageHandler = messageHandler; + + Logger = logger; + + NetworkStream clientStream = tcpClient.GetStream(); + clientStream.WriteTimeout = ClientWriteTimeout; + + clientReader = new StreamReader(clientStream, Encoding.UTF8); + clientWriter = new StreamWriter(clientStream, Encoding.UTF8) {NewLine = "\n"}; + } + + public async Task Process() + { + try + { + var decoder = new MessageDecoder(); + + string messageLine; + while ((messageLine = await ReadLine()) != null) + { + var state = decoder.Decode(messageLine, out var msg); + + if (state == MessageDecoder.State.Decoding) + continue; // Not finished decoding yet + + if (state == MessageDecoder.State.Errored) + { + Logger.LogError($"Received message line with invalid format: {messageLine}"); + continue; + } + + Logger.LogDebug($"Received message: {msg}"); + + try + { + try + { + if (msg.Kind == MessageKind.Request) + { + var responseContent = await messageHandler.HandleRequest(this, msg.Id, msg.Content, Logger); + await WriteMessage(new Message(MessageKind.Response, msg.Id, responseContent)); + } + else if (msg.Kind == MessageKind.Response) + { + ResponseAwaiter responseAwaiter; + + using (await requestsSem.UseAsync()) + { + if (!requestAwaiterQueues.TryGetValue(msg.Id, out var queue) || queue.Count <= 0) + { + Logger.LogError($"Received unexpected response: {msg.Id}"); + return; + } + + responseAwaiter = queue.Dequeue(); + } + + responseAwaiter.SetResult(msg.Content); + } + else + { + throw new IndexOutOfRangeException($"Invalid message kind {msg.Kind}"); + } + } + catch (Exception e) + { + Logger.LogError($"Message handler for '{msg}' failed with exception", e); + } + } + catch (Exception e) + { + Logger.LogError($"Exception thrown from message handler. Message: {msg}", e); + } + } + } + catch (Exception e) + { + Logger.LogError("Unhandled exception in the peer loop", e); + } + } + + public async Task<bool> DoHandshake(string identity) + { + if (!await WriteLine(handshake.GetHandshakeLine(identity))) + { + Logger.LogError("Could not write handshake"); + return false; + } + + var readHandshakeTask = ReadLine(); + + if (await Task.WhenAny(readHandshakeTask, Task.Delay(8000)) != readHandshakeTask) + { + Logger.LogError("Timeout waiting for the client handshake"); + return false; + } + + string peerHandshake = await readHandshakeTask; + + if (handshake == null || !handshake.IsValidPeerHandshake(peerHandshake, out remoteIdentity, Logger)) + { + Logger.LogError("Received invalid handshake: " + peerHandshake); + return false; + } + + IsConnected = true; + Connected?.Invoke(); + + Logger.LogInfo("Peer connection started"); + + return true; + } + + private async Task<string> ReadLine() + { + try + { + return await clientReader.ReadLineAsync(); + } + catch (Exception e) + { + if (IsDisposed) + { + var se = e as SocketException ?? e.InnerException as SocketException; + if (se != null && se.SocketErrorCode == SocketError.Interrupted) + return null; + } + + throw; + } + } + + private Task<bool> WriteMessage(Message message) + { + Logger.LogDebug($"Sending message: {message}"); + int bodyLineCount = message.Content.Body.Count(c => c == '\n'); + + bodyLineCount += 1; // Extra line break at the end + + var builder = new StringBuilder(); + + builder.AppendLine(message.Kind.ToString()); + builder.AppendLine(message.Id); + builder.AppendLine(message.Content.Status.ToString()); + builder.AppendLine(bodyLineCount.ToString()); + builder.AppendLine(message.Content.Body); + + return WriteLine(builder.ToString()); + } + + public async Task<TResponse> SendRequest<TResponse>(string id, string body) + where TResponse : Response, new() + { + ResponseAwaiter responseAwaiter; + + using (await requestsSem.UseAsync()) + { + bool written = await WriteMessage(new Message(MessageKind.Request, id, new MessageContent(body))); + + if (!written) + return null; + + if (!requestAwaiterQueues.TryGetValue(id, out var queue)) + { + queue = new Queue<ResponseAwaiter>(); + requestAwaiterQueues.Add(id, queue); + } + + responseAwaiter = new ResponseAwaiter<TResponse>(); + queue.Enqueue(responseAwaiter); + } + + return (TResponse)await responseAwaiter; + } + + private async Task<bool> WriteLine(string text) + { + if (clientWriter == null || IsDisposed || !IsTcpClientConnected) + return false; + + using (await writeSem.UseAsync()) + { + try + { + await clientWriter.WriteLineAsync(text); + await clientWriter.FlushAsync(); + } + catch (Exception e) + { + if (!IsDisposed) + { + var se = e as SocketException ?? e.InnerException as SocketException; + if (se != null && se.SocketErrorCode == SocketError.Shutdown) + Logger.LogInfo("Client disconnected ungracefully"); + else + Logger.LogError("Exception thrown when trying to write to client", e); + + Dispose(); + } + } + } + + return true; + } + + // ReSharper disable once UnusedMember.Global + public void ShutdownSocketSend() + { + tcpClient.Client.Shutdown(SocketShutdown.Send); + } + + public void Dispose() + { + if (IsDisposed) + return; + + IsDisposed = true; + + if (IsTcpClientConnected) + { + if (IsConnected) + Disconnected?.Invoke(); + } + + clientReader?.Dispose(); + clientWriter?.Dispose(); + ((IDisposable)tcpClient)?.Dispose(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs new file mode 100644 index 0000000000..1dd4f852e5 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs @@ -0,0 +1,116 @@ +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +using Newtonsoft.Json; + +namespace GodotTools.IdeMessaging.Requests +{ + public abstract class Request + { + [JsonIgnore] public string Id { get; } + + protected Request(string id) + { + Id = id; + } + } + + public abstract class Response + { + [JsonIgnore] public MessageStatus Status { get; set; } = MessageStatus.Ok; + } + + public sealed class CodeCompletionRequest : Request + { + public enum CompletionKind + { + InputActions = 0, + NodePaths, + ResourcePaths, + ScenePaths, + ShaderParams, + Signals, + ThemeColors, + ThemeConstants, + ThemeFonts, + ThemeStyles + } + + public CompletionKind Kind { get; set; } + public string ScriptFile { get; set; } + + public new const string Id = "CodeCompletion"; + + public CodeCompletionRequest() : base(Id) + { + } + } + + public sealed class CodeCompletionResponse : Response + { + public CodeCompletionRequest.CompletionKind Kind; + public string ScriptFile { get; set; } + public string[] Suggestions { get; set; } + } + + public sealed class PlayRequest : Request + { + public new const string Id = "Play"; + + public PlayRequest() : base(Id) + { + } + } + + public sealed class PlayResponse : Response + { + } + + public sealed class DebugPlayRequest : Request + { + public string DebuggerHost { get; set; } + public int DebuggerPort { get; set; } + public bool? BuildBeforePlaying { get; set; } + + public new const string Id = "DebugPlay"; + + public DebugPlayRequest() : base(Id) + { + } + } + + public sealed class DebugPlayResponse : Response + { + } + + public sealed class OpenFileRequest : Request + { + public string File { get; set; } + public int? Line { get; set; } + public int? Column { get; set; } + + public new const string Id = "OpenFile"; + + public OpenFileRequest() : base(Id) + { + } + } + + public sealed class OpenFileResponse : Response + { + } + + public sealed class ReloadScriptsRequest : Request + { + public new const string Id = "ReloadScripts"; + + public ReloadScriptsRequest() : base(Id) + { + } + } + + public sealed class ReloadScriptsResponse : Response + { + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs new file mode 100644 index 0000000000..548e7f06ee --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs @@ -0,0 +1,23 @@ +using GodotTools.IdeMessaging.Requests; +using GodotTools.IdeMessaging.Utils; +using Newtonsoft.Json; + +namespace GodotTools.IdeMessaging +{ + public abstract class ResponseAwaiter : NotifyAwaiter<Response> + { + public abstract void SetResult(MessageContent content); + } + + public class ResponseAwaiter<T> : ResponseAwaiter + where T : Response, new() + { + public override void SetResult(MessageContent content) + { + if (content.Status == MessageStatus.Ok) + SetResult(JsonConvert.DeserializeObject<T>(content.Body)); + else + SetResult(new T {Status = content.Status}); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs index 700b786752..d84a63c83c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs @@ -1,9 +1,9 @@ using System; using System.Runtime.CompilerServices; -namespace GodotTools.Utils +namespace GodotTools.IdeMessaging.Utils { - public sealed class NotifyAwaiter<T> : INotifyCompletion + public class NotifyAwaiter<T> : INotifyCompletion { private Action continuation; private Exception exception; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs new file mode 100644 index 0000000000..9d593fbf8a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace GodotTools.IdeMessaging.Utils +{ + public static class SemaphoreExtensions + { + public static ConfiguredTaskAwaitable<IDisposable> UseAsync(this SemaphoreSlim semaphoreSlim, CancellationToken cancellationToken = default(CancellationToken)) + { + var wrapper = new SemaphoreSlimWaitReleaseWrapper(semaphoreSlim, out Task waitAsyncTask, cancellationToken); + return waitAsyncTask.ContinueWith<IDisposable>(t => wrapper, cancellationToken).ConfigureAwait(false); + } + + private struct SemaphoreSlimWaitReleaseWrapper : IDisposable + { + private readonly SemaphoreSlim semaphoreSlim; + + public SemaphoreSlimWaitReleaseWrapper(SemaphoreSlim semaphoreSlim, out Task waitAsyncTask, CancellationToken cancellationToken = default(CancellationToken)) + { + this.semaphoreSlim = semaphoreSlim; + waitAsyncTask = this.semaphoreSlim.WaitAsync(cancellationToken); + } + + public void Dispose() + { + semaphoreSlim.Release(); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index b60e501beb..9cb50014b0 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -1,57 +1,23 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid> - <OutputType>Library</OutputType> - <RootNamespace>GodotTools.ProjectEditor</RootNamespace> - <AssemblyName>GodotTools.ProjectEditor</AssemblyName> - <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> - <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath> - <LangVersion>7</LangVersion> + <TargetFramework>net472</TargetFramework> + <LangVersion>7.2</LangVersion> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>portable</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug</OutputPath> - <DefineConstants>DEBUG;</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <Optimize>true</Optimize> - <OutputPath>bin\Release</OutputPath> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <ItemGroup> - <Reference Include="System" /> - <Reference Include="Microsoft.Build" /> - <Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL"> - <HintPath>$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> - </Reference> - </ItemGroup> <ItemGroup> - <Compile Include="ApiAssembliesInfo.cs" /> - <Compile Include="DotNetSolution.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="IdentifierUtils.cs" /> - <Compile Include="ProjectExtensions.cs" /> - <Compile Include="ProjectGenerator.cs" /> - <Compile Include="ProjectUtils.cs" /> + <PackageReference Include="Microsoft.Build" Version="16.5.0" /> + <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> </ItemGroup> <ItemGroup> - <None Include="packages.config" /> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> - <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project> - <Name>GodotTools.Core</Name> - </ProjectReference> + <!-- + The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described + here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486 + We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when + searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed. + --> + <None Include="MSBuild.exe" CopyToOutputDirectory="Always" /> </ItemGroup> - <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs index f0e0d1b33d..704f2ec194 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs @@ -2,8 +2,8 @@ using GodotTools.Core; using System; using System.Collections.Generic; using System.IO; -using DotNet.Globbing; using Microsoft.Build.Construction; +using Microsoft.Build.Globbing; namespace GodotTools.ProjectEditor { @@ -11,8 +11,6 @@ namespace GodotTools.ProjectEditor { public static ProjectItemElement FindItemOrNull(this ProjectRootElement root, string itemType, string include, bool noCondition = false) { - GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}}; - string normalizedInclude = include.NormalizePath(); foreach (var itemGroup in root.ItemGroups) @@ -25,7 +23,8 @@ namespace GodotTools.ProjectEditor if (item.ItemType != itemType) continue; - var glob = Glob.Parse(item.Include.NormalizePath(), globOptions); + //var glob = Glob.Parse(item.Include.NormalizePath(), globOptions); + var glob = MSBuildGlob.Parse(item.Include.NormalizePath()); if (glob.IsMatch(normalizedInclude)) return item; @@ -36,8 +35,6 @@ namespace GodotTools.ProjectEditor } public static ProjectItemElement FindItemOrNullAbs(this ProjectRootElement root, string itemType, string include, bool noCondition = false) { - GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}}; - string normalizedInclude = Path.GetFullPath(include).NormalizePath(); foreach (var itemGroup in root.ItemGroups) @@ -50,7 +47,7 @@ namespace GodotTools.ProjectEditor if (item.ItemType != itemType) continue; - var glob = Glob.Parse(Path.GetFullPath(item.Include).NormalizePath(), globOptions); + var glob = MSBuildGlob.Parse(Path.GetFullPath(item.Include).NormalizePath()); if (glob.IsMatch(normalizedInclude)) return item; diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index cbe3afaedd..fb2beb6995 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -125,6 +125,12 @@ namespace GodotTools.ProjectEditor // References var referenceGroup = root.AddItemGroup(); referenceGroup.AddItem("Reference", "System"); + var frameworkRefAssembliesItem = referenceGroup.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies"); + + // Use metadata (child nodes) instead of attributes for the PackageReference. + // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build. + frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0"); + frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All"); root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\")); diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index f2ebef1a7d..069a1edaa3 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -4,8 +4,8 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; -using DotNet.Globbing; using Microsoft.Build.Construction; +using Microsoft.Build.Globbing; namespace GodotTools.ProjectEditor { @@ -133,9 +133,6 @@ namespace GodotTools.ProjectEditor var result = new List<string>(); var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs"); - var globOptions = new GlobOptions(); - globOptions.Evaluation.CaseInsensitive = false; - var root = ProjectRootElement.Open(projectPath); Debug.Assert(root != null); @@ -151,7 +148,7 @@ namespace GodotTools.ProjectEditor string normalizedInclude = item.Include.NormalizePath(); - var glob = Glob.Parse(normalizedInclude, globOptions); + var glob = MSBuildGlob.Parse(normalizedInclude); // TODO Check somehow if path has no blob to avoid the following loop... @@ -176,7 +173,7 @@ namespace GodotTools.ProjectEditor void AddPropertyIfNotPresent(string name, string condition, string value) { if (root.PropertyGroups - .Any(g => (g.Condition == string.Empty || g.Condition.Trim() == condition) && + .Any(g => (string.IsNullOrEmpty(g.Condition) || g.Condition.Trim() == condition) && g.Properties .Any(p => p.Name == name && p.Value == value && @@ -267,7 +264,7 @@ namespace GodotTools.ProjectEditor bool hasGodotProjectGeneratorVersion = false; bool foundOldConfiguration = false; - foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition == string.Empty)) + foreach (var propertyGroup in root.PropertyGroups.Where(g => string.IsNullOrEmpty(g.Condition))) { if (!hasGodotProjectGeneratorVersion && propertyGroup.Properties.Any(p => p.Name == "GodotProjectGeneratorVersion")) hasGodotProjectGeneratorVersion = true; @@ -283,7 +280,7 @@ namespace GodotTools.ProjectEditor if (!hasGodotProjectGeneratorVersion) { - root.PropertyGroups.First(g => g.Condition == string.Empty)? + root.PropertyGroups.First(g => string.IsNullOrEmpty(g.Condition))? .AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString()); project.HasUnsavedChanges = true; } @@ -351,5 +348,25 @@ namespace GodotTools.ProjectEditor MigrateConfigurationConditions("Tools", "Debug"); // Must be last } } + + public static void EnsureHasNugetNetFrameworkRefAssemblies(MSBuildProject project) + { + var root = project.Root; + + bool found = root.ItemGroups.Any(g => string.IsNullOrEmpty(g.Condition) && g.Items.Any( + item => item.ItemType == "PackageReference" && item.Include == "Microsoft.NETFramework.ReferenceAssemblies")); + + if (found) + return; + + var frameworkRefAssembliesItem = root.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies"); + + // Use metadata (child nodes) instead of attributes for the PackageReference. + // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build. + frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0"); + frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All"); + + project.HasUnsavedChanges = true; + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs deleted file mode 100644 index 3a0464c9bc..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("GodotTools.ProjectEditor")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("Godot Engine contributors")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] - diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config deleted file mode 100644 index 2db030f9d8..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="DotNet.Glob" version="2.1.1" targetFramework="net45" /> -</packages> diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln index a3438ea5f3..f6147eb5bb 100644 --- a/modules/mono/editor/GodotTools/GodotTools.sln +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -9,7 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotToo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeConnection", "GodotTools.IdeConnection\GodotTools.IdeConnection.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeMessaging", "GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index 43c96d2e30..e55558c100 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -14,16 +14,6 @@ namespace GodotTools.Build { public static class BuildSystem { - private static string GetMsBuildPath() - { - string msbuildPath = MsBuildFinder.FindMsBuild(); - - if (msbuildPath == null) - throw new FileNotFoundException("Cannot find the MSBuild executable."); - - return msbuildPath; - } - private static string MonoWindowsBinDir { get @@ -46,8 +36,8 @@ namespace GodotTools.Build { if (OS.IsWindows) { - return (BuildManager.BuildTool)EditorSettings.GetSetting("mono/builds/build_tool") - == BuildManager.BuildTool.MsBuildMono; + return (BuildTool)EditorSettings.GetSetting("mono/builds/build_tool") + == BuildTool.MsBuildMono; } return false; @@ -57,16 +47,21 @@ namespace GodotTools.Build private static bool PrintBuildOutput => (bool)EditorSettings.GetSetting("mono/builds/print_build_output"); - private static Process LaunchBuild(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + private static Process LaunchBuild(string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) { + (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild(); + + if (msbuildPath == null) + throw new FileNotFoundException("Cannot find the MSBuild executable."); + var customPropertiesList = new List<string>(); if (customProperties != null) customPropertiesList.AddRange(customProperties); - string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList); + string compilerArgs = BuildArguments(buildTool, solution, targets, config, loggerOutputDir, customPropertiesList); - var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs); + var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs); bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput; @@ -90,7 +85,7 @@ namespace GodotTools.Build // Needed when running from Developer Command Prompt for VS RemovePlatformVariable(startInfo.EnvironmentVariables); - var process = new Process { StartInfo = startInfo }; + var process = new Process {StartInfo = startInfo}; process.Start(); @@ -105,19 +100,19 @@ namespace GodotTools.Build public static int Build(BuildInfo buildInfo) { - return Build(buildInfo.Solution, buildInfo.Configuration, + return Build(buildInfo.Solution, buildInfo.Targets, buildInfo.Configuration, buildInfo.LogsDirPath, buildInfo.CustomProperties); } - public static async Task<int> BuildAsync(BuildInfo buildInfo) + public static Task<int> BuildAsync(BuildInfo buildInfo) { - return await BuildAsync(buildInfo.Solution, buildInfo.Configuration, + return BuildAsync(buildInfo.Solution, buildInfo.Targets, buildInfo.Configuration, buildInfo.LogsDirPath, buildInfo.CustomProperties); } - public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + public static int Build(string solution, string[] targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) { - using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties)) + using (var process = LaunchBuild(solution, targets, config, loggerOutputDir, customProperties)) { process.WaitForExit(); @@ -125,9 +120,9 @@ namespace GodotTools.Build } } - public static async Task<int> BuildAsync(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + public static async Task<int> BuildAsync(string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) { - using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties)) + using (var process = LaunchBuild(solution, targets, config, loggerOutputDir, customProperties)) { await process.WaitForExitAsync(); @@ -135,10 +130,15 @@ namespace GodotTools.Build } } - private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties) + private static string BuildArguments(BuildTool buildTool, string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties) { - string arguments = $@"""{solution}"" /v:normal /t:Build ""/p:{"Configuration=" + config}"" " + - $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}"""; + string arguments = string.Empty; + + if (buildTool == BuildTool.DotnetCli) + arguments += "msbuild "; // `dotnet msbuild` command + + arguments += $@"""{solution}"" /v:normal /t:{string.Join(",", targets)} ""/p:{"Configuration=" + config}"" " + + $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}"""; foreach (string customProperty in customProperties) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs new file mode 100644 index 0000000000..a1a69334e3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs @@ -0,0 +1,10 @@ +namespace GodotTools.Build +{ + public enum BuildTool + { + MsBuildMono, + MsBuildVs, + JetBrainsMsBuild, + DotnetCli + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index af8d070cbd..f36e581a5f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -17,75 +17,96 @@ namespace GodotTools.Build private static string _msbuildToolsPath = string.Empty; private static string _msbuildUnixPath = string.Empty; - public static string FindMsBuild() + public static (string, BuildTool) FindMsBuild() { var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var buildTool = (BuildManager.BuildTool)editorSettings.GetSetting("mono/builds/build_tool"); + var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool"); if (OS.IsWindows) { switch (buildTool) { - case BuildManager.BuildTool.MsBuildVs: + case BuildTool.DotnetCli: { - if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath)) + string dotnetCliPath = OS.PathWhich("dotnet"); + if (!string.IsNullOrEmpty(dotnetCliPath)) + return (dotnetCliPath, BuildTool.DotnetCli); + GD.PushError("Cannot find dotnet CLI executable. Fallback to MSBuild from Visual Studio."); + goto case BuildTool.MsBuildVs; + } + case BuildTool.MsBuildVs: + { + if (string.IsNullOrEmpty(_msbuildToolsPath) || !File.Exists(_msbuildToolsPath)) { // Try to search it again if it wasn't found last time or if it was removed from its location _msbuildToolsPath = FindMsBuildToolsPathOnWindows(); - if (_msbuildToolsPath.Empty()) - { - throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'."); - } + if (string.IsNullOrEmpty(_msbuildToolsPath)) + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildVs}'."); } if (!_msbuildToolsPath.EndsWith("\\")) _msbuildToolsPath += "\\"; - return Path.Combine(_msbuildToolsPath, "MSBuild.exe"); + return (Path.Combine(_msbuildToolsPath, "MSBuild.exe"), BuildTool.MsBuildVs); } - case BuildManager.BuildTool.MsBuildMono: + case BuildTool.MsBuildMono: { string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat"); if (!File.Exists(msbuildPath)) - { - throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildMono}'. Tried with path: {msbuildPath}"); - } + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildMono}'. Tried with path: {msbuildPath}"); - return msbuildPath; + return (msbuildPath, BuildTool.MsBuildMono); } - case BuildManager.BuildTool.JetBrainsMsBuild: + case BuildTool.JetBrainsMsBuild: + { var editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName); + if (!File.Exists(editorPath)) throw new FileNotFoundException($"Cannot find Rider executable. Tried with path: {editorPath}"); - var riderDir = new FileInfo(editorPath).Directory.Parent; - return Path.Combine(riderDir.FullName, @"tools\MSBuild\Current\Bin\MSBuild.exe"); + + var riderDir = new FileInfo(editorPath).Directory?.Parent; + + string msbuildPath = Path.Combine(riderDir.FullName, @"tools\MSBuild\Current\Bin\MSBuild.exe"); + + if (!File.Exists(msbuildPath)) + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildJetBrains}'. Tried with path: {msbuildPath}"); + + return (msbuildPath, BuildTool.JetBrainsMsBuild); + } default: throw new IndexOutOfRangeException("Invalid build tool in editor settings"); } } - if (OS.IsUnixLike()) + if (OS.IsUnixLike) { - if (buildTool == BuildManager.BuildTool.MsBuildMono) + switch (buildTool) { - if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath)) + case BuildTool.DotnetCli: { - // Try to search it again if it wasn't found last time or if it was removed from its location - _msbuildUnixPath = FindBuildEngineOnUnix("msbuild"); + string dotnetCliPath = OS.PathWhich("dotnet"); + if (!string.IsNullOrEmpty(dotnetCliPath)) + return (dotnetCliPath, BuildTool.DotnetCli); + GD.PushError("Cannot find dotnet CLI executable. Fallback to MSBuild from Mono."); + goto case BuildTool.MsBuildMono; } - - if (_msbuildUnixPath.Empty()) + case BuildTool.MsBuildMono: { - throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMsbuildMono}'"); - } + if (string.IsNullOrEmpty(_msbuildUnixPath) || !File.Exists(_msbuildUnixPath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _msbuildUnixPath = FindBuildEngineOnUnix("msbuild"); + } - return _msbuildUnixPath; - } - else - { - throw new IndexOutOfRangeException("Invalid build tool in editor settings"); + if (string.IsNullOrEmpty(_msbuildUnixPath)) + throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMSBuildMono}'"); + + return (_msbuildUnixPath, BuildTool.MsBuildMono); + } + default: + throw new IndexOutOfRangeException("Invalid build tool in editor settings"); } } @@ -114,12 +135,12 @@ namespace GodotTools.Build { string ret = OS.PathWhich(name); - if (!ret.Empty()) + if (!string.IsNullOrEmpty(ret)) return ret; string retFallback = OS.PathWhich($"{name}.exe"); - if (!retFallback.Empty()) + if (!string.IsNullOrEmpty(retFallback)) return retFallback; foreach (string hintDir in MsBuildHintDirs) @@ -143,7 +164,7 @@ namespace GodotTools.Build string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)"); vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; - var vsWhereArgs = new[] { "-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild" }; + var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"}; var outputArray = new Godot.Collections.Array<string>(); int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs, @@ -171,7 +192,7 @@ namespace GodotTools.Build string value = line.Substring(sepIdx + 1).StripEdges(); - if (value.Empty()) + if (string.IsNullOrEmpty(value)) throw new FormatException("installationPath value is empty"); if (!value.EndsWith("\\")) diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs index 70bd552f2f..cca0983c01 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs @@ -10,6 +10,7 @@ namespace GodotTools public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization { public string Solution { get; } + public string[] Targets { get; } public string Configuration { get; } public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization @@ -38,9 +39,10 @@ namespace GodotTools { } - public BuildInfo(string solution, string configuration) + public BuildInfo(string solution, string[] targets, string configuration) { Solution = solution; + Targets = targets; Configuration = configuration; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs index 520e665595..598787ba03 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs @@ -15,20 +15,14 @@ namespace GodotTools { private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>(); - public const string PropNameMsbuildMono = "MSBuild (Mono)"; - public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)"; - public const string PropNameMsbuildJetBrains = "MSBuild (JetBrains Rider)"; + public const string PropNameMSBuildMono = "MSBuild (Mono)"; + public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)"; + public const string PropNameMSBuildJetBrains = "MSBuild (JetBrains Rider)"; + public const string PropNameDotnetCli = "dotnet CLI"; public const string MsBuildIssuesFileName = "msbuild_issues.csv"; public const string MsBuildLogFileName = "msbuild_log.txt"; - public enum BuildTool - { - MsBuildMono, - MsBuildVs, - JetBrainsMsBuild - } - private static void RemoveOldIssuesFile(BuildInfo buildInfo) { var issuesFile = GetIssuesFilePath(buildInfo); @@ -181,10 +175,12 @@ namespace GodotTools { pr.Step("Building project solution", 0); - var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, config); + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets: new[] {"Restore", "Build"}, config); + + bool escapeNeedsDoubleBackslash = buildTool == BuildTool.MsBuildMono || buildTool == BuildTool.DotnetCli; // Add Godot defines - string constants = buildTool != BuildTool.MsBuildMono ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\""; + string constants = !escapeNeedsDoubleBackslash ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\""; foreach (var godotDefine in godotDefines) constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};"; @@ -192,7 +188,7 @@ namespace GodotTools if (Internal.GodotIsRealTDouble()) constants += "GODOT_REAL_T_IS_DOUBLE;"; - constants += buildTool != BuildTool.MsBuildMono ? "\"" : "\\\""; + constants += !escapeNeedsDoubleBackslash ? "\"" : "\\\""; buildInfo.CustomProperties.Add(constants); @@ -219,7 +215,7 @@ namespace GodotTools if (File.Exists(editorScriptsMetadataPath)) File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); - var currentPlayRequest = GodotSharpEditor.Instance.GodotIdeManager.GodotIdeServer.CurrentPlayRequest; + var currentPlayRequest = GodotSharpEditor.Instance.CurrentPlaySettings; if (currentPlayRequest != null) { @@ -233,7 +229,8 @@ namespace GodotTools ",server=n"); } - return true; // Requested play from an external editor/IDE which already built the project + if (!currentPlayRequest.Value.BuildBeforePlaying) + return true; // Requested play from an external editor/IDE which already built the project } var godotDefines = new[] @@ -249,22 +246,44 @@ namespace GodotTools { // Build tool settings var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var msbuild = BuildTool.MsBuildMono; + + BuildTool msbuildDefault; + if (OS.IsWindows) - msbuild = RiderPathManager.IsExternalEditorSetToRider(editorSettings) - ? BuildTool.JetBrainsMsBuild - : BuildTool.MsBuildVs; + { + if (RiderPathManager.IsExternalEditorSetToRider(editorSettings)) + msbuildDefault = BuildTool.JetBrainsMsBuild; + else + msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildVs; + } + else + { + msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildMono; + } + + EditorDef("mono/builds/build_tool", msbuildDefault); - EditorDef("mono/builds/build_tool", msbuild); + string hintString; + + if (OS.IsWindows) + { + hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," + + $"{PropNameMSBuildVs}:{(int)BuildTool.MsBuildVs}," + + $"{PropNameMSBuildJetBrains}:{(int)BuildTool.JetBrainsMsBuild}," + + $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}"; + } + else + { + hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," + + $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}"; + } editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary { ["type"] = Godot.Variant.Type.Int, ["name"] = "mono/builds/build_tool", ["hint"] = Godot.PropertyHint.Enum, - ["hint_string"] = OS.IsWindows ? - $"{PropNameMsbuildMono},{PropNameMsbuildVs},{PropNameMsbuildJetBrains}" : - $"{PropNameMsbuildMono}" + ["hint_string"] = hintString }); EditorDef("mono/builds/print_build_output", false); diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs index 938c3d8be1..0106e1f1ac 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs @@ -72,7 +72,7 @@ namespace GodotTools { string[] csvColumns = file.GetCsvLine(); - if (csvColumns.Length == 1 && csvColumns[0].Empty()) + if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0])) return; if (csvColumns.Length != 7) @@ -115,12 +115,12 @@ namespace GodotTools // Get correct issue idx from issue list int issueIndex = (int)issuesList.GetItemMetadata(idx); - if (idx < 0 || idx >= issues.Count) + if (issueIndex < 0 || issueIndex >= issues.Count) throw new IndexOutOfRangeException("Issue index out of range"); BuildIssue issue = issues[issueIndex]; - if (issue.ProjectFile.Empty() && issue.File.Empty()) + if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) return; string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir(); @@ -158,14 +158,14 @@ namespace GodotTools string tooltip = string.Empty; tooltip += $"Message: {issue.Message}"; - if (!issue.Code.Empty()) + if (!string.IsNullOrEmpty(issue.Code)) tooltip += $"\nCode: {issue.Code}"; tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}"; string text = string.Empty; - if (!issue.File.Empty()) + if (!string.IsNullOrEmpty(issue.File)) { text += $"{issue.File}({issue.Line},{issue.Column}): "; @@ -174,7 +174,7 @@ namespace GodotTools tooltip += $"\nColumn: {issue.Column}"; } - if (!issue.ProjectFile.Empty()) + if (!string.IsNullOrEmpty(issue.ProjectFile)) tooltip += $"\nProject: {issue.ProjectFile}"; text += issue.Message; diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index f1765f7e19..f60e469503 100755 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -587,7 +587,7 @@ MONO_AOT_MODE_LAST = 1000, string arch = "x86_64"; return $"{platform}-{arch}"; } - case OS.Platforms.X11: + case OS.Platforms.LinuxBSD: case OS.Platforms.Server: { string arch = bits == "64" ? "x86_64" : "i686"; diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 2ceb4888a2..6bfbc62f3b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -414,7 +414,7 @@ namespace GodotTools.Export case OS.Platforms.UWP: return "net_4_x_win"; case OS.Platforms.OSX: - case OS.Platforms.X11: + case OS.Platforms.LinuxBSD: case OS.Platforms.Server: case OS.Platforms.Haiku: return "net_4_x"; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index c070cb16d9..eb7696685f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -37,6 +37,8 @@ namespace GodotTools public BottomPanel BottomPanel { get; private set; } + public PlaySettings? CurrentPlaySettings { get; set; } + public static string ProjectAssemblyName { get @@ -228,12 +230,12 @@ namespace GodotTools [UsedImplicitly] public Error OpenInExternalEditor(Script script, int line, int col) { - var editor = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor"); + var editorId = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor"); - switch (editor) + switch (editorId) { case ExternalEditorId.None: - // Tells the caller to fallback to the global external editor settings or the built-in editor + // Not an error. Tells the caller to fallback to the global external editor settings or the built-in editor. return Error.Unavailable; case ExternalEditorId.VisualStudio: throw new NotSupportedException(); @@ -249,17 +251,20 @@ namespace GodotTools { string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); - if (line >= 0) - GodotIdeManager.SendOpenFile(scriptPath, line + 1, col); - else - GodotIdeManager.SendOpenFile(scriptPath); + GodotIdeManager.LaunchIdeAsync().ContinueWith(launchTask => + { + var editorPick = launchTask.Result; + if (line >= 0) + editorPick?.SendOpenFile(scriptPath, line + 1, col); + else + editorPick?.SendOpenFile(scriptPath); + }); break; } - case ExternalEditorId.VsCode: { - if (_vsCodePath.Empty() || !File.Exists(_vsCodePath)) + if (string.IsNullOrEmpty(_vsCodePath) || !File.Exists(_vsCodePath)) { // Try to search it again if it wasn't found last time or if it was removed from its location _vsCodePath = VsCodeNames.SelectFirstNotNull(OS.PathWhich, orElse: string.Empty); @@ -300,7 +305,7 @@ namespace GodotTools if (line >= 0) { args.Add("-g"); - args.Add($"{scriptPath}:{line + 1}:{col}"); + args.Add($"{scriptPath}:{line}:{col}"); } else { @@ -311,7 +316,7 @@ namespace GodotTools if (OS.IsOSX) { - if (!osxAppBundleInstalled && _vsCodePath.Empty()) + if (!osxAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath)) { GD.PushError("Cannot find code editor: VSCode"); return Error.FileNotFound; @@ -321,7 +326,7 @@ namespace GodotTools } else { - if (_vsCodePath.Empty()) + if (string.IsNullOrEmpty(_vsCodePath)) { GD.PushError("Cannot find code editor: VSCode"); return Error.FileNotFound; @@ -341,7 +346,6 @@ namespace GodotTools break; } - default: throw new ArgumentOutOfRangeException(); } @@ -457,6 +461,9 @@ namespace GodotTools // Make sure the existing project has Api assembly references configured correctly ProjectUtils.FixApiHintPath(msbuildProject); + // Make sure the existing project references the Microsoft.NETFramework.ReferenceAssemblies nuget package + ProjectUtils.EnsureHasNugetNetFrameworkRefAssemblies(msbuildProject); + if (msbuildProject.HasUnsavedChanges) { // Save a copy of the project before replacing it @@ -505,7 +512,7 @@ namespace GodotTools $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + $",JetBrains Rider:{(int)ExternalEditorId.Rider}"; } - else if (OS.IsUnixLike()) + else if (OS.IsUnixLike) { settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index e1570d6465..ba527ca3b5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -1,14 +1,8 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid> - <OutputType>Library</OutputType> - <RootNamespace>GodotTools</RootNamespace> - <AssemblyName>GodotTools</AssemblyName> - <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> - <LangVersion>7</LangVersion> + <TargetFramework>net472</TargetFramework> + <LangVersion>7.2</LangVersion> <GodotApiConfiguration>Debug</GodotApiConfiguration> <!-- The Godot editor uses the Debug Godot API assemblies --> <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir> @@ -18,32 +12,13 @@ <!-- The project is part of the Godot source tree --> <!-- Use the Godot source tree output folder instead of '$(ProjectDir)/bin' --> <OutputPath>$(GodotOutputDataDir)/Tools</OutputPath> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>portable</DebugType> - <Optimize>false</Optimize> - <DefineConstants>DEBUG;</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <Optimize>true</Optimize> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> + <!-- Must not append '$(TargetFramework)' to the output path in this case --> + <AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath> </PropertyGroup> <ItemGroup> - <Reference Include="JetBrains.Annotations, Version=2019.1.3.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325"> - <HintPath>..\packages\JetBrains.Annotations.2019.1.3\lib\net20\JetBrains.Annotations.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed"> - <HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System" /> + <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" /> + <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> + <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <Reference Include="GodotSharp"> <HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath> <Private>False</Private> @@ -54,58 +29,9 @@ </Reference> </ItemGroup> <ItemGroup> - <Compile Include="Build\MsBuildFinder.cs" /> - <Compile Include="Export\AotBuilder.cs" /> - <Compile Include="Export\ExportPlugin.cs" /> - <Compile Include="Export\XcodeHelper.cs" /> - <Compile Include="ExternalEditorId.cs" /> - <Compile Include="Ides\GodotIdeManager.cs" /> - <Compile Include="Ides\GodotIdeServer.cs" /> - <Compile Include="Ides\MonoDevelop\EditorId.cs" /> - <Compile Include="Ides\MonoDevelop\Instance.cs" /> - <Compile Include="Ides\Rider\RiderPathLocator.cs" /> - <Compile Include="Ides\Rider\RiderPathManager.cs" /> - <Compile Include="Internals\EditorProgress.cs" /> - <Compile Include="Internals\GodotSharpDirs.cs" /> - <Compile Include="Internals\Internal.cs" /> - <Compile Include="Internals\ScriptClassParser.cs" /> - <Compile Include="Internals\Globals.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Build\BuildSystem.cs" /> - <Compile Include="Utils\Directory.cs" /> - <Compile Include="Utils\File.cs" /> - <Compile Include="Utils\NotifyAwaiter.cs" /> - <Compile Include="Utils\OS.cs" /> - <Compile Include="GodotSharpEditor.cs" /> - <Compile Include="BuildManager.cs" /> - <Compile Include="HotReloadAssemblyWatcher.cs" /> - <Compile Include="BuildInfo.cs" /> - <Compile Include="BuildTab.cs" /> - <Compile Include="BottomPanel.cs" /> - <Compile Include="CsProjOperations.cs" /> - <Compile Include="Utils\CollectionExtensions.cs" /> - <Compile Include="Utils\User32Dll.cs" /> - </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj"> - <Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project> - <Name>GodotTools.BuildLogger</Name> - </ProjectReference> - <ProjectReference Include="..\GodotTools.IdeConnection\GodotTools.IdeConnection.csproj"> - <Project>{92600954-25f0-4291-8e11-1fee9fc4be20}</Project> - <Name>GodotTools.IdeConnection</Name> - </ProjectReference> - <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj"> - <Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project> - <Name>GodotTools.ProjectEditor</Name> - </ProjectReference> - <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> - <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project> - <Name>GodotTools.Core</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> - <None Include="packages.config" /> + <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj" /> + <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" /> + <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj" /> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" /> </ItemGroup> - <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 54f0ffab96..e4932ca217 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -1,73 +1,104 @@ using System; using System.IO; +using System.Threading.Tasks; using Godot; -using GodotTools.IdeConnection; +using GodotTools.IdeMessaging; +using GodotTools.IdeMessaging.Requests; using GodotTools.Internals; namespace GodotTools.Ides { - public class GodotIdeManager : Node, ISerializationListener + public sealed class GodotIdeManager : Node, ISerializationListener { - public GodotIdeServer GodotIdeServer { get; private set; } + private MessagingServer MessagingServer { get; set; } private MonoDevelop.Instance monoDevelInstance; private MonoDevelop.Instance vsForMacInstance; - private GodotIdeServer GetRunningServer() + private MessagingServer GetRunningOrNewServer() { - if (GodotIdeServer != null && !GodotIdeServer.IsDisposed) - return GodotIdeServer; - StartServer(); - return GodotIdeServer; + if (MessagingServer != null && !MessagingServer.IsDisposed) + return MessagingServer; + + MessagingServer?.Dispose(); + MessagingServer = new MessagingServer(OS.GetExecutablePath(), ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir), new GodotLogger()); + + _ = MessagingServer.Listen(); + + return MessagingServer; } public override void _Ready() { - StartServer(); + _ = GetRunningOrNewServer(); } public void OnBeforeSerialize() { - GodotIdeServer?.Dispose(); } public void OnAfterDeserialize() { - StartServer(); + _ = GetRunningOrNewServer(); } - private ILogger logger; + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); - protected ILogger Logger + if (disposing) + { + MessagingServer?.Dispose(); + } + } + + private string GetExternalEditorIdentity(ExternalEditorId editorId) { - get => logger ?? (logger = new GodotLogger()); + // Manually convert to string to avoid breaking compatibility in case we rename the enum fields. + switch (editorId) + { + case ExternalEditorId.None: + return null; + case ExternalEditorId.VisualStudio: + return "VisualStudio"; + case ExternalEditorId.VsCode: + return "VisualStudioCode"; + case ExternalEditorId.Rider: + return "Rider"; + case ExternalEditorId.VisualStudioForMac: + return "VisualStudioForMac"; + case ExternalEditorId.MonoDevelop: + return "MonoDevelop"; + default: + throw new NotImplementedException(); + } } - private void StartServer() + public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000) { - GodotIdeServer?.Dispose(); - GodotIdeServer = new GodotIdeServer(LaunchIde, - OS.GetExecutablePath(), - ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir)); + var editorId = (ExternalEditorId)GodotSharpEditor.Instance.GetEditorInterface() + .GetEditorSettings().GetSetting("mono/editor/external_editor"); + string editorIdentity = GetExternalEditorIdentity(editorId); - GodotIdeServer.Logger = Logger; + var runningServer = GetRunningOrNewServer(); - GodotIdeServer.StartServer(); - } + if (runningServer.IsAnyConnected(editorIdentity)) + return new EditorPick(editorIdentity); - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); + LaunchIde(editorId, editorIdentity); + + var timeoutTask = Task.Delay(millisecondsTimeout); + var completedTask = await Task.WhenAny(timeoutTask, runningServer.AwaitClientConnected(editorIdentity)); + + if (completedTask != timeoutTask) + return new EditorPick(editorIdentity); - GodotIdeServer?.Dispose(); + return null; } - private void LaunchIde() + private void LaunchIde(ExternalEditorId editorId, string editorIdentity) { - var editor = (ExternalEditorId)GodotSharpEditor.Instance.GetEditorInterface() - .GetEditorSettings().GetSetting("mono/editor/external_editor"); - - switch (editor) + switch (editorId) { case ExternalEditorId.None: case ExternalEditorId.VisualStudio: @@ -80,14 +111,14 @@ namespace GodotTools.Ides { MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath) { - if (Utils.OS.IsOSX && editor == ExternalEditorId.VisualStudioForMac) + if (Utils.OS.IsOSX && editorId == ExternalEditorId.VisualStudioForMac) { - vsForMacInstance = vsForMacInstance ?? + vsForMacInstance = (vsForMacInstance?.IsDisposed ?? true ? null : vsForMacInstance) ?? new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac); return vsForMacInstance; } - monoDevelInstance = monoDevelInstance ?? + monoDevelInstance = (monoDevelInstance?.IsDisposed ?? true ? null : monoDevelInstance) ?? new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop); return monoDevelInstance; } @@ -96,12 +127,25 @@ namespace GodotTools.Ides { var instance = GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath); - if (!instance.IsRunning) + if (instance.IsRunning && !GetRunningOrNewServer().IsAnyConnected(editorIdentity)) + { + // After launch we wait up to 30 seconds for the IDE to connect to our messaging server. + var waitAfterLaunch = TimeSpan.FromSeconds(30); + var timeSinceLaunch = DateTime.Now - instance.LaunchTime; + if (timeSinceLaunch > waitAfterLaunch) + { + instance.Dispose(); + instance.Execute(); + } + } + else if (!instance.IsRunning) + { instance.Execute(); + } } catch (FileNotFoundException) { - string editorName = editor == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop"; + string editorName = editorId == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop"; GD.PushError($"Cannot find code editor: {editorName}"); } @@ -113,26 +157,45 @@ namespace GodotTools.Ides } } - private void WriteMessage(string id, params string[] arguments) + public readonly struct EditorPick { - GetRunningServer().WriteMessage(new Message(id, arguments)); - } + private readonly string identity; - public void SendOpenFile(string file) - { - WriteMessage("OpenFile", file); - } + public EditorPick(string identity) + { + this.identity = identity; + } - public void SendOpenFile(string file, int line) - { - WriteMessage("OpenFile", file, line.ToString()); - } + public bool IsAnyConnected() => + GodotSharpEditor.Instance.GodotIdeManager.GetRunningOrNewServer().IsAnyConnected(identity); - public void SendOpenFile(string file, int line, int column) - { - WriteMessage("OpenFile", file, line.ToString(), column.ToString()); + private void SendRequest<TResponse>(Request request) + where TResponse : Response, new() + { + // Logs an error if no client is connected with the specified identity + GodotSharpEditor.Instance.GodotIdeManager + .GetRunningOrNewServer() + .BroadcastRequest<TResponse>(identity, request); + } + + public void SendOpenFile(string file) + { + SendRequest<OpenFileResponse>(new OpenFileRequest {File = file}); + } + + public void SendOpenFile(string file, int line) + { + SendRequest<OpenFileResponse>(new OpenFileRequest {File = file, Line = line}); + } + + public void SendOpenFile(string file, int line, int column) + { + SendRequest<OpenFileResponse>(new OpenFileRequest {File = file, Line = line, Column = column}); + } } + public EditorPick PickEditor(ExternalEditorId editorId) => new EditorPick(GetExternalEditorIdentity(editorId)); + private class GodotLogger : ILogger { public void LogDebug(string message) diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs deleted file mode 100644 index 72676a8b24..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using GodotTools.IdeConnection; -using GodotTools.Internals; -using GodotTools.Utils; -using Directory = System.IO.Directory; -using File = System.IO.File; -using Thread = System.Threading.Thread; - -namespace GodotTools.Ides -{ - public class GodotIdeServer : GodotIdeBase - { - private readonly TcpListener listener; - private readonly FileStream metaFile; - private readonly Action launchIdeAction; - private readonly NotifyAwaiter<bool> clientConnectedAwaiter = new NotifyAwaiter<bool>(); - - private async Task<bool> AwaitClientConnected() - { - return await clientConnectedAwaiter.Reset(); - } - - public GodotIdeServer(Action launchIdeAction, string editorExecutablePath, string projectMetadataDir) - : base(projectMetadataDir) - { - messageHandlers = InitializeMessageHandlers(); - - this.launchIdeAction = launchIdeAction; - - // Make sure the directory exists - Directory.CreateDirectory(projectMetadataDir); - - // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing... - const FileShare metaFileShare = FileShare.ReadWrite; - - metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare); - - listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0)); - listener.Start(); - - int port = ((IPEndPoint)listener.Server.LocalEndPoint).Port; - using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8)) - { - metaFileWriter.WriteLine(port); - metaFileWriter.WriteLine(editorExecutablePath); - } - - StartServer(); - } - - public void StartServer() - { - var serverThread = new Thread(RunServerThread) { Name = "Godot Ide Connection Server" }; - serverThread.Start(); - } - - private void RunServerThread() - { - SynchronizationContext.SetSynchronizationContext(Godot.Dispatcher.SynchronizationContext); - - try - { - while (!IsDisposed) - { - TcpClient tcpClient = listener.AcceptTcpClient(); - - Logger.LogInfo("Connection open with Ide Client"); - - lock (ConnectionLock) - { - Connection = new GodotIdeConnectionServer(tcpClient, HandleMessage); - Connection.Logger = Logger; - } - - Connected += () => clientConnectedAwaiter.SetResult(true); - - Connection.Start(); - } - } - catch (Exception e) - { - if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted)) - throw; - } - } - - public async void WriteMessage(Message message) - { - async Task LaunchIde() - { - if (IsConnected) - return; - - launchIdeAction(); - await Task.WhenAny(Task.Delay(10000), AwaitClientConnected()); - } - - await LaunchIde(); - - if (!IsConnected) - { - Logger.LogError("Cannot write message: Godot Ide Server not connected"); - return; - } - - Connection.WriteMessage(message); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - { - listener?.Stop(); - - metaFile?.Dispose(); - - File.Delete(MetaFilePath); - } - } - - protected virtual bool HandleMessage(Message message) - { - if (messageHandlers.TryGetValue(message.Id, out var action)) - { - action(message.Arguments); - return true; - } - - return false; - } - - private readonly Dictionary<string, Action<string[]>> messageHandlers; - - private Dictionary<string, Action<string[]>> InitializeMessageHandlers() - { - return new Dictionary<string, Action<string[]>> - { - ["Play"] = args => - { - switch (args.Length) - { - case 0: - Play(); - return; - case 2: - Play(debuggerHost: args[0], debuggerPort: int.Parse(args[1])); - return; - default: - throw new ArgumentException(); - } - }, - ["ReloadScripts"] = args => ReloadScripts() - }; - } - - private void DispatchToMainThread(Action action) - { - var d = new SendOrPostCallback(state => action()); - Godot.Dispatcher.SynchronizationContext.Post(d, null); - } - - private void Play() - { - DispatchToMainThread(() => - { - CurrentPlayRequest = new PlayRequest(); - Internal.EditorRunPlay(); - CurrentPlayRequest = null; - }); - } - - private void Play(string debuggerHost, int debuggerPort) - { - DispatchToMainThread(() => - { - CurrentPlayRequest = new PlayRequest(debuggerHost, debuggerPort); - Internal.EditorRunPlay(); - CurrentPlayRequest = null; - }); - } - - private void ReloadScripts() - { - DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts); - } - - public PlayRequest? CurrentPlayRequest { get; private set; } - - public struct PlayRequest - { - public bool HasDebugger { get; } - public string DebuggerHost { get; } - public int DebuggerPort { get; } - - public PlayRequest(string debuggerHost, int debuggerPort) - { - HasDebugger = true; - DebuggerHost = debuggerHost; - DebuggerPort = debuggerPort; - } - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs new file mode 100644 index 0000000000..32f264d100 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs @@ -0,0 +1,360 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using GodotTools.IdeMessaging; +using GodotTools.IdeMessaging.Requests; +using GodotTools.IdeMessaging.Utils; +using GodotTools.Internals; +using Newtonsoft.Json; +using Directory = System.IO.Directory; +using File = System.IO.File; + +namespace GodotTools.Ides +{ + public sealed class MessagingServer : IDisposable + { + private readonly ILogger logger; + + private readonly FileStream metaFile; + private string MetaFilePath { get; } + + private readonly SemaphoreSlim peersSem = new SemaphoreSlim(1); + + private readonly TcpListener listener; + + private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> clientConnectedAwaiters = new Dictionary<string, Queue<NotifyAwaiter<bool>>>(); + private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> clientDisconnectedAwaiters = new Dictionary<string, Queue<NotifyAwaiter<bool>>>(); + + public async Task<bool> AwaitClientConnected(string identity) + { + if (!clientConnectedAwaiters.TryGetValue(identity, out var queue)) + { + queue = new Queue<NotifyAwaiter<bool>>(); + clientConnectedAwaiters.Add(identity, queue); + } + + var awaiter = new NotifyAwaiter<bool>(); + queue.Enqueue(awaiter); + return await awaiter; + } + + public async Task<bool> AwaitClientDisconnected(string identity) + { + if (!clientDisconnectedAwaiters.TryGetValue(identity, out var queue)) + { + queue = new Queue<NotifyAwaiter<bool>>(); + clientDisconnectedAwaiters.Add(identity, queue); + } + + var awaiter = new NotifyAwaiter<bool>(); + queue.Enqueue(awaiter); + return await awaiter; + } + + public bool IsDisposed { get; private set; } + + public bool IsAnyConnected(string identity) => string.IsNullOrEmpty(identity) ? + Peers.Count > 0 : + Peers.Any(c => c.RemoteIdentity == identity); + + private List<Peer> Peers { get; } = new List<Peer>(); + + ~MessagingServer() + { + Dispose(disposing: false); + } + + public async void Dispose() + { + if (IsDisposed) + return; + + using (await peersSem.UseAsync()) + { + if (IsDisposed) // lock may not be fair + return; + IsDisposed = true; + } + + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + foreach (var connection in Peers) + connection.Dispose(); + Peers.Clear(); + listener?.Stop(); + + metaFile?.Dispose(); + + File.Delete(MetaFilePath); + } + } + + public MessagingServer(string editorExecutablePath, string projectMetadataDir, ILogger logger) + { + this.logger = logger; + + MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName); + + // Make sure the directory exists + Directory.CreateDirectory(projectMetadataDir); + + // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing... + const FileShare metaFileShare = FileShare.ReadWrite; + + metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare); + + listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0)); + listener.Start(); + + int port = ((IPEndPoint)listener.Server.LocalEndPoint).Port; + using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8)) + { + metaFileWriter.WriteLine(port); + metaFileWriter.WriteLine(editorExecutablePath); + } + } + + private async Task AcceptClient(TcpClient tcpClient) + { + logger.LogDebug("Accept client..."); + + using (var peer = new Peer(tcpClient, new ServerHandshake(), new ServerMessageHandler(), logger)) + { + // ReSharper disable AccessToDisposedClosure + peer.Connected += () => + { + logger.LogInfo("Connection open with Ide Client"); + + if (clientConnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue)) + { + while (queue.Count > 0) + queue.Dequeue().SetResult(true); + clientConnectedAwaiters.Remove(peer.RemoteIdentity); + } + }; + + peer.Disconnected += () => + { + if (clientDisconnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue)) + { + while (queue.Count > 0) + queue.Dequeue().SetResult(true); + clientDisconnectedAwaiters.Remove(peer.RemoteIdentity); + } + }; + // ReSharper restore AccessToDisposedClosure + + try + { + if (!await peer.DoHandshake("server")) + { + logger.LogError("Handshake failed"); + return; + } + } + catch (Exception e) + { + logger.LogError("Handshake failed with unhandled exception: ", e); + return; + } + + using (await peersSem.UseAsync()) + Peers.Add(peer); + + try + { + await peer.Process(); + } + finally + { + using (await peersSem.UseAsync()) + Peers.Remove(peer); + } + } + } + + public async Task Listen() + { + try + { + while (!IsDisposed) + _ = AcceptClient(await listener.AcceptTcpClientAsync()); + } + catch (Exception e) + { + if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted)) + throw; + } + } + + public async void BroadcastRequest<TResponse>(string identity, Request request) + where TResponse : Response, new() + { + using (await peersSem.UseAsync()) + { + if (!IsAnyConnected(identity)) + { + logger.LogError("Cannot write request. No client connected to the Godot Ide Server."); + return; + } + + var selectedConnections = string.IsNullOrEmpty(identity) ? + Peers : + Peers.Where(c => c.RemoteIdentity == identity); + + string body = JsonConvert.SerializeObject(request); + + foreach (var connection in selectedConnections) + _ = connection.SendRequest<TResponse>(request.Id, body); + } + } + + private class ServerHandshake : IHandshake + { + private static readonly string ServerHandshakeBase = $"{Peer.ServerHandshakeName},Version={Peer.ProtocolVersionMajor}.{Peer.ProtocolVersionMinor}.{Peer.ProtocolVersionRevision}"; + private static readonly string ClientHandshakePattern = $@"{Regex.Escape(Peer.ClientHandshakeName)},Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{{0,63}})"; + + public string GetHandshakeLine(string identity) => $"{ServerHandshakeBase},{identity}"; + + public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger) + { + identity = null; + + var match = Regex.Match(handshake, ClientHandshakePattern); + + if (!match.Success) + return false; + + if (!uint.TryParse(match.Groups[1].Value, out uint clientMajor) || Peer.ProtocolVersionMajor != clientMajor) + { + logger.LogDebug("Incompatible major version: " + match.Groups[1].Value); + return false; + } + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (!uint.TryParse(match.Groups[2].Value, out uint clientMinor) || Peer.ProtocolVersionMinor > clientMinor) + { + logger.LogDebug("Incompatible minor version: " + match.Groups[2].Value); + return false; + } + + if (!uint.TryParse(match.Groups[3].Value, out uint _)) // Revision + { + logger.LogDebug("Incompatible revision build: " + match.Groups[3].Value); + return false; + } + + identity = match.Groups[4].Value; + + return true; + } + } + + private class ServerMessageHandler : IMessageHandler + { + private static void DispatchToMainThread(Action action) + { + var d = new SendOrPostCallback(state => action()); + Godot.Dispatcher.SynchronizationContext.Post(d, null); + } + + private readonly Dictionary<string, Peer.RequestHandler> requestHandlers = InitializeRequestHandlers(); + + public async Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger) + { + if (!requestHandlers.TryGetValue(id, out var handler)) + { + logger.LogError($"Received unknown request: {id}"); + return new MessageContent(MessageStatus.RequestNotSupported, "null"); + } + + try + { + var response = await handler(peer, content); + return new MessageContent(response.Status, JsonConvert.SerializeObject(response)); + } + catch (JsonException) + { + logger.LogError($"Received request with invalid body: {id}"); + return new MessageContent(MessageStatus.InvalidRequestBody, "null"); + } + } + + private static Dictionary<string, Peer.RequestHandler> InitializeRequestHandlers() + { + return new Dictionary<string, Peer.RequestHandler> + { + [PlayRequest.Id] = async (peer, content) => + { + _ = JsonConvert.DeserializeObject<PlayRequest>(content.Body); + return await HandlePlay(); + }, + [DebugPlayRequest.Id] = async (peer, content) => + { + var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body); + return await HandleDebugPlay(request); + }, + [ReloadScriptsRequest.Id] = async (peer, content) => + { + _ = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body); + return await HandleReloadScripts(); + }, + [CodeCompletionRequest.Id] = async (peer, content) => + { + var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body); + return await HandleCodeCompletionRequest(request); + } + }; + } + + private static Task<Response> HandlePlay() + { + DispatchToMainThread(() => + { + GodotSharpEditor.Instance.CurrentPlaySettings = new PlaySettings(); + Internal.EditorRunPlay(); + GodotSharpEditor.Instance.CurrentPlaySettings = null; + }); + return Task.FromResult<Response>(new PlayResponse()); + } + + private static Task<Response> HandleDebugPlay(DebugPlayRequest request) + { + DispatchToMainThread(() => + { + GodotSharpEditor.Instance.CurrentPlaySettings = + new PlaySettings(request.DebuggerHost, request.DebuggerPort, request.BuildBeforePlaying ?? true); + Internal.EditorRunPlay(); + GodotSharpEditor.Instance.CurrentPlaySettings = null; + }); + return Task.FromResult<Response>(new DebugPlayResponse()); + } + + private static Task<Response> HandleReloadScripts() + { + DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts); + return Task.FromResult<Response>(new ReloadScriptsResponse()); + } + + private static async Task<Response> HandleCodeCompletionRequest(CodeCompletionRequest request) + { + var response = new CodeCompletionResponse {Kind = request.Kind, ScriptFile = request.ScriptFile}; + response.Suggestions = await Task.Run(() => Internal.CodeCompletionRequest(response.Kind, response.ScriptFile)); + return response; + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index 6026c109ad..d6fa2eeba7 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs @@ -7,14 +7,16 @@ using GodotTools.Utils; namespace GodotTools.Ides.MonoDevelop { - public class Instance + public class Instance : IDisposable { + public DateTime LaunchTime { get; private set; } private readonly string solutionFile; private readonly EditorId editorId; private Process process; public bool IsRunning => process != null && !process.HasExited; + public bool IsDisposed { get; private set; } public void Execute() { @@ -59,6 +61,8 @@ namespace GodotTools.Ides.MonoDevelop if (command == null) throw new FileNotFoundException(); + LaunchTime = DateTime.Now; + if (newWindow) { process = Process.Start(new ProcessStartInfo @@ -88,6 +92,12 @@ namespace GodotTools.Ides.MonoDevelop this.editorId = editorId; } + public void Dispose() + { + IsDisposed = true; + process?.Dispose(); + } + private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames; private static readonly IReadOnlyDictionary<EditorId, string> BundleIds; @@ -118,7 +128,7 @@ namespace GodotTools.Ides.MonoDevelop {EditorId.MonoDevelop, "MonoDevelop.exe"} }; } - else if (OS.IsUnixLike()) + else if (OS.IsUnixLike) { ExecutableNames = new Dictionary<EditorId, string> { diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs index e3a4fa7b45..e22e9af919 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs @@ -36,7 +36,7 @@ namespace GodotTools.Ides.Rider { return CollectRiderInfosMac(); } - if (OS.IsUnixLike()) + if (OS.IsUnixLike) { return CollectAllRiderPathsLinux(); } @@ -141,16 +141,16 @@ namespace GodotTools.Ides.Rider if (OS.IsOSX) { var home = Environment.GetEnvironmentVariable("HOME"); - if (string.IsNullOrEmpty(home)) + if (string.IsNullOrEmpty(home)) return string.Empty; var localAppData = Path.Combine(home, @"Library/Application Support"); return GetToolboxRiderRootPath(localAppData); } - if (OS.IsUnixLike()) + if (OS.IsUnixLike) { var home = Environment.GetEnvironmentVariable("HOME"); - if (string.IsNullOrEmpty(home)) + if (string.IsNullOrEmpty(home)) return string.Empty; var localAppData = Path.Combine(home, @".local/share"); return GetToolboxRiderRootPath(localAppData); @@ -209,7 +209,7 @@ namespace GodotTools.Ides.Rider private static string GetRelativePathToBuildTxt() { - if (OS.IsWindows || OS.IsUnixLike()) + if (OS.IsWindows || OS.IsUnixLike) return "../../build.txt"; if (OS.IsOSX) return "Contents/Resources/build.txt"; @@ -322,7 +322,7 @@ namespace GodotTools.Ides.Rider class SettingsJson { public string install_location; - + [CanBeNull] public static string GetInstallLocationFromJson(string json) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 026a7db89c..7e5049e4b7 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -2,6 +2,7 @@ using System; using System.Runtime.CompilerServices; using Godot; using Godot.Collections; +using GodotTools.IdeMessaging.Requests; namespace GodotTools.Internals { @@ -52,6 +53,9 @@ namespace GodotTools.Internals public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts(); + public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind, string scriptFile) => + internal_CodeCompletionRequest((int)kind, scriptFile); + #region Internal [MethodImpl(MethodImplOptions.InternalCall)] @@ -111,6 +115,9 @@ namespace GodotTools.Internals [MethodImpl(MethodImplOptions.InternalCall)] private static extern void internal_ScriptEditorDebugger_ReloadScripts(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string[] internal_CodeCompletionRequest(int kind, string scriptFile); + #endregion } } diff --git a/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs b/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs new file mode 100644 index 0000000000..820d0c0b83 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs @@ -0,0 +1,19 @@ +namespace GodotTools +{ + public struct PlaySettings + { + public bool HasDebugger { get; } + public string DebuggerHost { get; } + public int DebuggerPort { get; } + + public bool BuildBeforePlaying { get; } + + public PlaySettings(string debuggerHost, int debuggerPort, bool buildBeforePlaying) + { + HasDebugger = true; + DebuggerHost = debuggerHost; + DebuggerPort = debuggerPort; + BuildBeforePlaying = buildBeforePlaying; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs deleted file mode 100644 index f5fe85c722..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("GodotTools")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("Godot Engine contributors")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index b057ac12c6..6c05891f2c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -22,7 +22,10 @@ namespace GodotTools.Utils { public const string Windows = "Windows"; public const string OSX = "OSX"; - public const string X11 = "X11"; + public const string Linux = "Linux"; + public const string FreeBSD = "FreeBSD"; + public const string NetBSD = "NetBSD"; + public const string BSD = "BSD"; public const string Server = "Server"; public const string UWP = "UWP"; public const string Haiku = "Haiku"; @@ -35,7 +38,7 @@ namespace GodotTools.Utils { public const string Windows = "windows"; public const string OSX = "osx"; - public const string X11 = "linuxbsd"; + public const string LinuxBSD = "linuxbsd"; public const string Server = "server"; public const string UWP = "uwp"; public const string Haiku = "haiku"; @@ -48,7 +51,10 @@ namespace GodotTools.Utils { [Names.Windows] = Platforms.Windows, [Names.OSX] = Platforms.OSX, - [Names.X11] = Platforms.X11, + [Names.Linux] = Platforms.LinuxBSD, + [Names.FreeBSD] = Platforms.LinuxBSD, + [Names.NetBSD] = Platforms.LinuxBSD, + [Names.BSD] = Platforms.LinuxBSD, [Names.Server] = Platforms.Server, [Names.UWP] = Platforms.UWP, [Names.Haiku] = Platforms.Haiku, @@ -62,38 +68,39 @@ namespace GodotTools.Utils return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); } + private static bool IsAnyOS(IEnumerable<string> names) + { + return names.Any(p => p.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase)); + } + + private static readonly IEnumerable<string> LinuxBSDPlatforms = + new[] {Names.Linux, Names.FreeBSD, Names.NetBSD, Names.BSD}; + + private static readonly IEnumerable<string> UnixLikePlatforms = + new[] {Names.OSX, Names.Server, Names.Haiku, Names.Android, Names.iOS} + .Concat(LinuxBSDPlatforms).ToArray(); + private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows)); private static readonly Lazy<bool> _isOSX = new Lazy<bool>(() => IsOS(Names.OSX)); - private static readonly Lazy<bool> _isX11 = new Lazy<bool>(() => IsOS(Names.X11)); + private static readonly Lazy<bool> _isLinuxBSD = new Lazy<bool>(() => IsAnyOS(LinuxBSDPlatforms)); private static readonly Lazy<bool> _isServer = new Lazy<bool>(() => IsOS(Names.Server)); private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP)); private static readonly Lazy<bool> _isHaiku = new Lazy<bool>(() => IsOS(Names.Haiku)); private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android)); private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS)); private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5)); + private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms)); public static bool IsWindows => _isWindows.Value || IsUWP; public static bool IsOSX => _isOSX.Value; - public static bool IsX11 => _isX11.Value; + public static bool IsLinuxBSD => _isLinuxBSD.Value; public static bool IsServer => _isServer.Value; public static bool IsUWP => _isUWP.Value; public static bool IsHaiku => _isHaiku.Value; public static bool IsAndroid => _isAndroid.Value; public static bool IsiOS => _isiOS.Value; public static bool IsHTML5 => _isHTML5.Value; - - private static bool? _isUnixCache; - private static readonly string[] UnixLikePlatforms = { Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android, Names.iOS }; - - public static bool IsUnixLike() - { - if (_isUnixCache.HasValue) - return _isUnixCache.Value; - - string osName = GetPlatformName(); - _isUnixCache = UnixLikePlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase)); - return _isUnixCache.Value; - } + public static bool IsUnixLike => _isUnixLike.Value; public static char PathSep => IsWindows ? ';' : ':'; @@ -121,10 +128,10 @@ namespace GodotTools.Utils return searchDirs.Select(dir => Path.Combine(dir, name)).FirstOrDefault(File.Exists); return (from dir in searchDirs - select Path.Combine(dir, name) + select Path.Combine(dir, name) into path - from ext in windowsExts - select path + ext).FirstOrDefault(File.Exists); + from ext in windowsExts + select path + ext).FirstOrDefault(File.Exists); } private static string PathWhichUnix([NotNull] string name) @@ -189,7 +196,7 @@ namespace GodotTools.Utils startInfo.UseShellExecute = false; - using (var process = new Process { StartInfo = startInfo }) + using (var process = new Process {StartInfo = startInfo}) { process.Start(); process.WaitForExit(); diff --git a/modules/mono/editor/GodotTools/GodotTools/packages.config b/modules/mono/editor/GodotTools/GodotTools/packages.config deleted file mode 100644 index dd3de2865a..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/packages.config +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="JetBrains.Annotations" version="2019.1.3" targetFramework="net45" /> - <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net45" /> -</packages> diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index bdf9cf965f..258b8ed3ed 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -1664,6 +1664,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf } if (!p_imethod.is_internal) { + // TODO: This alone adds ~0.2 MB of bloat to the core API assembly. It would be + // better to generate a table in the C++ glue instead. That way the strings wouldn't + // add that much extra bloat as they're already used in engine code. Also, it would + // probably be much faster than looking up the attributes when fetching methods. p_output.append(MEMBER_BEGIN "[GodotMethod(\""); p_output.append(p_imethod.name); p_output.append("\")]"); @@ -2139,7 +2143,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte if (return_type->ret_as_byref_arg) { p_output.append("\tif (" CS_PARAM_INSTANCE " == nullptr) { *arg_ret = "); p_output.append(fail_ret); - p_output.append("; ERR_FAIL_MSG(\"Parameter ' arg_ret ' is null.\"); }\n"); + p_output.append("; ERR_FAIL_MSG(\"Parameter ' " CS_PARAM_INSTANCE " ' is null.\"); }\n"); } else { p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE ", "); p_output.append(fail_ret); @@ -2390,6 +2394,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() { if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) continue; + if (property.name.find("/") >= 0) { + // Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector. + continue; + } + PropertyInterface iprop; iprop.cname = property.name; iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname); @@ -2402,7 +2411,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { bool valid = false; iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid); - ERR_FAIL_COND_V(!valid, false); + ERR_FAIL_COND_V_MSG(!valid, false, "Invalid property: '" + itype.name + "." + String(iprop.cname) + "'."); iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname)); @@ -2414,8 +2423,6 @@ bool BindingsGenerator::_populate_object_type_interfaces() { iprop.proxy_name += "_"; } - iprop.proxy_name = iprop.proxy_name.replace("/", "__"); // Some members have a slash... - iprop.prop_doc = nullptr; for (int i = 0; i < itype.class_doc->properties.size(); i++) { diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 7c87c688db..9aad9622d9 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -85,16 +85,12 @@ class BindingsGenerator { struct TypeReference { StringName cname; - bool is_enum; + bool is_enum = false; - TypeReference() : - is_enum(false) { - } + TypeReference() {} TypeReference(const StringName &p_cname) : - cname(p_cname), - is_enum(false) { - } + cname(p_cname) {} }; struct ArgumentInterface { @@ -107,7 +103,7 @@ class BindingsGenerator { TypeReference type; String name; - DefaultParamMode def_param_mode; + DefaultParamMode def_param_mode = CONSTANT; /** * Determines the expression for the parameter default value. @@ -116,9 +112,7 @@ class BindingsGenerator { */ String default_argument; - ArgumentInterface() { - def_param_mode = CONSTANT; - } + ArgumentInterface() {} }; struct MethodInterface { @@ -138,19 +132,19 @@ class BindingsGenerator { /** * Determines if the method has a variable number of arguments (VarArg) */ - bool is_vararg; + bool is_vararg = false; /** * Virtual methods ("virtual" as defined by the Godot API) are methods that by default do nothing, * but can be overridden by the user to add custom functionality. * e.g.: _ready, _process, etc. */ - bool is_virtual; + bool is_virtual = false; /** * Determines if the call should fallback to Godot's object.Call(string, params) in C#. */ - bool requires_object_call; + bool requires_object_call = false; /** * Determines if the method visibility is 'internal' (visible only to files in the same assembly). @@ -158,27 +152,20 @@ class BindingsGenerator { * but are required by properties as getters or setters. * Methods that are not meant to be exposed are those that begin with underscore and are not virtual. */ - bool is_internal; + bool is_internal = false; List<ArgumentInterface> arguments; - const DocData::MethodDoc *method_doc; + const DocData::MethodDoc *method_doc = nullptr; - bool is_deprecated; + bool is_deprecated = false; String deprecation_message; void add_argument(const ArgumentInterface &argument) { arguments.push_back(argument); } - MethodInterface() { - is_vararg = false; - is_virtual = false; - requires_object_call = false; - is_internal = false; - method_doc = nullptr; - is_deprecated = false; - } + MethodInterface() {} }; struct SignalInterface { @@ -192,19 +179,16 @@ class BindingsGenerator { List<ArgumentInterface> arguments; - const DocData::MethodDoc *method_doc; + const DocData::MethodDoc *method_doc = nullptr; - bool is_deprecated; + bool is_deprecated = false; String deprecation_message; void add_argument(const ArgumentInterface &argument) { arguments.push_back(argument); } - SignalInterface() { - method_doc = nullptr; - is_deprecated = false; - } + SignalInterface() {} }; struct TypeInterface { @@ -225,26 +209,26 @@ class BindingsGenerator { */ String proxy_name; - ClassDB::APIType api_type; + ClassDB::APIType api_type = ClassDB::API_NONE; - bool is_enum; - bool is_object_type; - bool is_singleton; - bool is_reference; + bool is_enum = false; + bool is_object_type = false; + bool is_singleton = false; + bool is_reference = false; /** * Used only by Object-derived types. * Determines if this type is not abstract (incomplete). * e.g.: CanvasItem cannot be instantiated. */ - bool is_instantiable; + bool is_instantiable = false; /** * Used only by Object-derived types. * Determines if the C# class owns the native handle and must free it somehow when disposed. * e.g.: Reference types must notify when the C# instance is disposed, for proper refcounting. */ - bool memory_own; + bool memory_own = false; /** * This must be set to true for any struct bigger than 32-bits. Those cannot be passed/returned by value @@ -252,7 +236,7 @@ class BindingsGenerator { * In this case, [c_out] and [cs_out] must have a different format, explained below. * The Mono IL interpreter icall trampolines don't support passing structs bigger than 32-bits by value (at least not on WASM). */ - bool ret_as_byref_arg; + bool ret_as_byref_arg = false; // !! 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 @@ -279,7 +263,7 @@ class BindingsGenerator { * Formatting elements: * %0 or %s: name of the parameter */ - String c_arg_in; + String c_arg_in = "%s"; /** * One or more statements that determine how a variable of this type is returned from a function. @@ -362,7 +346,7 @@ class BindingsGenerator { */ String im_type_out; - const DocData::ClassDoc *class_doc; + const DocData::ClassDoc *class_doc = nullptr; List<ConstantInterface> constants; List<EnumInterface> enums; @@ -482,24 +466,7 @@ class BindingsGenerator { r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name]; } - TypeInterface() { - - api_type = ClassDB::API_NONE; - - is_enum = false; - is_object_type = false; - is_singleton = false; - is_reference = false; - is_instantiable = false; - - memory_own = false; - - ret_as_byref_arg = false; - - c_arg_in = "%s"; - - class_doc = nullptr; - } + TypeInterface() {} }; struct InternalCall { @@ -532,8 +499,8 @@ class BindingsGenerator { } }; - bool log_print_enabled; - bool initialized; + bool log_print_enabled = true; + bool initialized = false; OrderedHashMap<StringName, TypeInterface> obj_types; @@ -619,7 +586,8 @@ class BindingsGenerator { const List<InternalCall>::Element *find_icall_by_name(const String &p_name, const List<InternalCall> &p_list) { const List<InternalCall>::Element *it = p_list.front(); while (it) { - if (it->get().name == p_name) return it; + if (it->get().name == p_name) + return it; it = it->next(); } return nullptr; @@ -696,9 +664,7 @@ public: static void handle_cmdline_args(const List<String> &p_cmdline_args); - BindingsGenerator() : - log_print_enabled(true), - initialized(false) { + BindingsGenerator() { _initialize(); } }; diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp new file mode 100644 index 0000000000..7a5e465e7a --- /dev/null +++ b/modules/mono/editor/code_completion.cpp @@ -0,0 +1,249 @@ +/*************************************************************************/ +/* code_completion.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "code_completion.h" + +#include "core/project_settings.h" +#include "editor/editor_file_system.h" +#include "editor/editor_settings.h" +#include "scene/gui/control.h" +#include "scene/main/node.h" + +namespace gdmono { + +// Almost everything here is taken from functions used by GDScript for code completion, adapted for C#. + +_FORCE_INLINE_ String quoted(const String &p_str) { + return "\"" + p_str + "\""; +} + +void _add_nodes_suggestions(const Node *p_base, const Node *p_node, PackedStringArray &r_suggestions) { + if (p_node != p_base && !p_node->get_owner()) + return; + + String path_relative_to_orig = p_base->get_path_to(p_node); + + r_suggestions.push_back(quoted(path_relative_to_orig)); + + for (int i = 0; i < p_node->get_child_count(); i++) { + _add_nodes_suggestions(p_base, p_node->get_child(i), r_suggestions); + } +} + +Node *_find_node_for_script(Node *p_base, Node *p_current, const Ref<Script> &p_script) { + if (p_current->get_owner() != p_base && p_base != p_current) + return nullptr; + + Ref<Script> c = p_current->get_script(); + + if (c == p_script) + return p_current; + + for (int i = 0; i < p_current->get_child_count(); i++) { + Node *found = _find_node_for_script(p_base, p_current->get_child(i), p_script); + if (found) + return found; + } + + return nullptr; +} + +void _get_directory_contents(EditorFileSystemDirectory *p_dir, PackedStringArray &r_suggestions) { + for (int i = 0; i < p_dir->get_file_count(); i++) { + r_suggestions.push_back(quoted(p_dir->get_file_path(i))); + } + + for (int i = 0; i < p_dir->get_subdir_count(); i++) { + _get_directory_contents(p_dir->get_subdir(i), r_suggestions); + } +} + +Node *_try_find_owner_node_in_tree(const Ref<Script> p_script) { + SceneTree *tree = SceneTree::get_singleton(); + if (!tree) + return nullptr; + Node *base = tree->get_edited_scene_root(); + if (base) { + base = _find_node_for_script(base, base, p_script); + } + return base; +} + +PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_script_file) { + PackedStringArray suggestions; + + switch (p_kind) { + case CompletionKind::INPUT_ACTIONS: { + List<PropertyInfo> project_props; + ProjectSettings::get_singleton()->get_property_list(&project_props); + + for (List<PropertyInfo>::Element *E = project_props.front(); E; E = E->next()) { + const PropertyInfo &prop = E->get(); + + if (!prop.name.begins_with("input/")) + continue; + + String name = prop.name.substr(prop.name.find("/") + 1, prop.name.length()); + suggestions.push_back(quoted(name)); + } + } break; + case CompletionKind::NODE_PATHS: { + { + // 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); + suggestions.push_back(quoted("/root/" + name)); + } + } + + { + // Current edited scene tree + Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path()); + Node *base = _try_find_owner_node_in_tree(script); + if (base) { + _add_nodes_suggestions(base, base, suggestions); + } + } + } break; + case CompletionKind::RESOURCE_PATHS: { + if (bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { + _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), suggestions); + } + } break; + case CompletionKind::SCENE_PATHS: { + DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES); + List<String> directories; + directories.push_back(dir_access->get_current_dir()); + + while (!directories.empty()) { + dir_access->change_dir(directories.back()->get()); + directories.pop_back(); + + dir_access->list_dir_begin(); + String filename = dir_access->get_next(); + + while (filename != "") { + if (filename == "." || filename == "..") { + filename = dir_access->get_next(); + continue; + } + + if (dir_access->dir_exists(filename)) { + directories.push_back(dir_access->get_current_dir().plus_file(filename)); + } else if (filename.ends_with(".tscn") || filename.ends_with(".scn")) { + suggestions.push_back(quoted(dir_access->get_current_dir().plus_file(filename))); + } + + filename = dir_access->get_next(); + } + } + } break; + case CompletionKind::SHADER_PARAMS: { + print_verbose("Shared params completion for C# not implemented."); + } break; + case CompletionKind::SIGNALS: { + Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path()); + + List<MethodInfo> signals; + script->get_script_signal_list(&signals); + + StringName native = script->get_instance_base_type(); + if (native != StringName()) { + ClassDB::get_signal_list(native, &signals, /* p_no_inheritance: */ false); + } + + for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { + const String &signal = E->get().name; + suggestions.push_back(quoted(signal)); + } + } break; + case CompletionKind::THEME_COLORS: { + Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path()); + Node *base = _try_find_owner_node_in_tree(script); + if (base && Object::cast_to<Control>(base)) { + List<StringName> sn; + Theme::get_default()->get_color_list(base->get_class(), &sn); + + for (List<StringName>::Element *E = sn.front(); E; E = E->next()) { + suggestions.push_back(quoted(E->get())); + } + } + } break; + case CompletionKind::THEME_CONSTANTS: { + Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path()); + Node *base = _try_find_owner_node_in_tree(script); + if (base && Object::cast_to<Control>(base)) { + List<StringName> sn; + Theme::get_default()->get_constant_list(base->get_class(), &sn); + + for (List<StringName>::Element *E = sn.front(); E; E = E->next()) { + suggestions.push_back(quoted(E->get())); + } + } + } break; + case CompletionKind::THEME_FONTS: { + Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path()); + Node *base = _try_find_owner_node_in_tree(script); + if (base && Object::cast_to<Control>(base)) { + List<StringName> sn; + Theme::get_default()->get_font_list(base->get_class(), &sn); + + for (List<StringName>::Element *E = sn.front(); E; E = E->next()) { + suggestions.push_back(quoted(E->get())); + } + } + } break; + case CompletionKind::THEME_STYLES: { + Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path()); + Node *base = _try_find_owner_node_in_tree(script); + if (base && Object::cast_to<Control>(base)) { + List<StringName> sn; + Theme::get_default()->get_stylebox_list(base->get_class(), &sn); + + for (List<StringName>::Element *E = sn.front(); E; E = E->next()) { + suggestions.push_back(quoted(E->get())); + } + } + } break; + default: + ERR_FAIL_V_MSG(suggestions, "Invalid completion kind."); + } + + return suggestions; +} + +} // namespace gdmono diff --git a/modules/mono/editor/code_completion.h b/modules/mono/editor/code_completion.h new file mode 100644 index 0000000000..77673b766f --- /dev/null +++ b/modules/mono/editor/code_completion.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* code_completion.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 CODE_COMPLETION_H +#define CODE_COMPLETION_H + +#include "core/ustring.h" +#include "core/variant.h" + +namespace gdmono { + +enum class CompletionKind { + INPUT_ACTIONS = 0, + NODE_PATHS, + RESOURCE_PATHS, + SCENE_PATHS, + SHADER_PARAMS, + SIGNALS, + THEME_COLORS, + THEME_CONSTANTS, + THEME_FONTS, + THEME_STYLES +}; + +PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_script_file); + +} // namespace gdmono + +#endif // CODE_COMPLETION_H diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index c3e7e67ae9..c9117f1312 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -48,6 +48,7 @@ #include "../mono_gd/gd_mono_marshal.h" #include "../utils/osx_utils.h" #include "bindings_generator.h" +#include "code_completion.h" #include "godotsharp_export.h" #include "script_class_parser.h" @@ -354,6 +355,12 @@ void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() { } } +MonoArray *godot_icall_Internal_CodeCompletionRequest(int32_t p_kind, MonoString *p_script_file) { + String script_file = GDMonoMarshal::mono_string_to_godot(p_script_file); + PackedStringArray suggestions = gdmono::get_code_completion((gdmono::CompletionKind)p_kind, script_file); + return GDMonoMarshal::PackedStringArray_to_mono_array(suggestions); +} + float godot_icall_Globals_EditorScale() { return EDSCALE; } @@ -454,6 +461,7 @@ void register_editor_internal_calls() { mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay); mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop); mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); + mono_add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", (void *)godot_icall_Internal_CodeCompletionRequest); // Globals mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale); diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 4126da16be..1cdb08d50e 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -32,68 +32,70 @@ #include <mono/metadata/image.h> +#include "core/io/file_access_pack.h" #include "core/os/os.h" +#include "core/project_settings.h" #include "../mono_gd/gd_mono.h" #include "../mono_gd/gd_mono_assembly.h" #include "../mono_gd/gd_mono_cache.h" +#include "../utils/macros.h" namespace GodotSharpExport { -String get_assemblyref_name(MonoImage *p_image, int index) { +struct AssemblyRefInfo { + String name; + uint16_t major; + uint16_t minor; + uint16_t build; + uint16_t revision; +}; + +AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) { const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF); uint32_t cols[MONO_ASSEMBLYREF_SIZE]; mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE); - return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])); + return { + String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])), + (uint16_t)cols[MONO_ASSEMBLYREF_MAJOR_VERSION], + (uint16_t)cols[MONO_ASSEMBLYREF_MINOR_VERSION], + (uint16_t)cols[MONO_ASSEMBLYREF_BUILD_NUMBER], + (uint16_t)cols[MONO_ASSEMBLYREF_REV_NUMBER] + }; } Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) { MonoImage *image = p_assembly->get_image(); for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) { - String ref_name = get_assemblyref_name(image, i); + AssemblyRefInfo ref_info = get_assemblyref_name(image, i); + + const String &ref_name = ref_info.name; if (r_assembly_dependencies.has(ref_name)) continue; GDMonoAssembly *ref_assembly = nullptr; - String path; - bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe"); - - for (int j = 0; j < p_search_dirs.size(); j++) { - const String &search_dir = p_search_dirs[j]; - - 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 != nullptr) - 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 != nullptr) - 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 != nullptr) - break; - } - } - } - ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); + { + MonoAssemblyName *ref_aname = mono_assembly_name_new("A"); // We can't allocate an empty MonoAssemblyName, hence "A" + CRASH_COND(ref_aname == nullptr); + SCOPE_EXIT { + mono_assembly_name_free(ref_aname); + mono_free(ref_aname); + }; + + mono_assembly_get_assemblyref(image, i, ref_aname); - // Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir. - r_assembly_dependencies[ref_name] = path; + if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) { + ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); + } + + r_assembly_dependencies[ref_name] = ref_assembly->get_path(); + } Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_assembly_dependencies); ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'."); @@ -113,6 +115,11 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies, Vector<String> search_dirs; GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir); + if (p_custom_bcl_dir.length()) { + // Only one mscorlib can be loaded. We need this workaround to make sure we get it from the right BCL directory. + r_assembly_dependencies["mscorlib"] = p_custom_bcl_dir.plus_file("mscorlib.dll").simplify_path(); + } + for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) { String assembly_name = *key; String assembly_path = p_initial_assemblies[*key]; diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp index bece23c9a6..3ffbf8ba14 100644 --- a/modules/mono/editor/script_class_parser.cpp +++ b/modules/mono/editor/script_class_parser.cpp @@ -181,14 +181,24 @@ ScriptClassParser::Token ScriptClassParser::get_token() { 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 '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; case '\\': res = '\\'; break; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index ba0bbd7630..b5ac124c9a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -30,6 +30,7 @@ <ConsolePause>false</ConsolePause> </PropertyGroup> <ItemGroup> + <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> <Reference Include="System" /> </ItemGroup> <ItemGroup> diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj index 22853797c1..8785931312 100644 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj @@ -30,6 +30,7 @@ <ConsolePause>false</ConsolePause> </PropertyGroup> <ItemGroup> + <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> <Reference Include="System" /> </ItemGroup> <ItemGroup> diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp index dfd78a8244..26347e9162 100644 --- a/modules/mono/managed_callable.cpp +++ b/modules/mono/managed_callable.cpp @@ -82,6 +82,7 @@ CallableCustom::CompareLessFunc ManagedCallable::get_compare_less_func() const { } ObjectID ManagedCallable::get_object() const { + // TODO: If the delegate target extends Godot.Object, use that instead! return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id(); } diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h index fbcb405b0d..005ee52b35 100644 --- a/modules/mono/mono_gc_handle.h +++ b/modules/mono/mono_gc_handle.h @@ -47,8 +47,8 @@ enum class GCHandleType : char { // Manual release of the GC handle must be done when using this struct struct MonoGCHandleData { - uint32_t handle; - gdmono::GCHandleType type; + uint32_t handle = 0; + gdmono::GCHandleType type = gdmono::GCHandleType::NIL; _FORCE_INLINE_ bool is_released() const { return !handle; } _FORCE_INLINE_ bool is_weak() const { return type == gdmono::GCHandleType::WEAK_HANDLE; } @@ -68,10 +68,7 @@ struct MonoGCHandleData { MonoGCHandleData(const MonoGCHandleData &) = default; - MonoGCHandleData() : - handle(0), - type(gdmono::GCHandleType::NIL) { - } + MonoGCHandleData() {} MonoGCHandleData(uint32_t p_handle, gdmono::GCHandleType p_type) : handle(p_handle), diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 3298c5da4c..fbaa81e83f 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -133,6 +133,10 @@ void gd_mono_debug_init() { CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8(); + if (da_args.length()) { + OS::get_singleton()->set_environment("GODOT_MONO_DEBUGGER_AGENT", String()); + } + #ifdef TOOLS_ENABLED int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685); bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false); @@ -515,8 +519,8 @@ void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) { GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) { - if (p_name == "mscorlib") - return get_corlib_assembly(); + if (p_name == "mscorlib" && corlib_assembly) + return corlib_assembly; MonoDomain *domain = mono_domain_get(); uint32_t domain_id = domain ? mono_domain_get_id(domain) : 0; @@ -526,7 +530,9 @@ GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) { bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) { +#ifdef DEBUG_ENABLED CRASH_COND(!r_assembly); +#endif MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8()); bool result = load_assembly(p_name, aname, r_assembly, p_refonly); @@ -538,26 +544,27 @@ bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bo bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) { +#ifdef DEBUG_ENABLED CRASH_COND(!r_assembly); +#endif - print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); + return load_assembly(p_name, p_aname, r_assembly, p_refonly, GDMonoAssembly::get_default_search_dirs()); +} - MonoImageOpenStatus status = MONO_IMAGE_OK; - MonoAssembly *assembly = mono_assembly_load_full(p_aname, nullptr, &status, p_refonly); +bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs) { - if (!assembly) - return false; - - ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false); +#ifdef DEBUG_ENABLED + CRASH_COND(!r_assembly); +#endif - uint32_t domain_id = mono_domain_get_id(mono_domain_get()); + print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); - GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name); + GDMonoAssembly *assembly = GDMonoAssembly::load(p_name, p_aname, p_refonly, p_search_dirs); - ERR_FAIL_COND_V(stored_assembly == nullptr, false); - ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false); + if (!assembly) + return false; - *r_assembly = *stored_assembly; + *r_assembly = assembly; print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path()); diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 4898833e8e..3b0be4c180 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -48,9 +48,9 @@ enum Type { }; struct Version { - uint64_t godot_api_hash; - uint32_t bindings_version; - uint32_t cs_glue_version; + uint64_t godot_api_hash = 0; + uint32_t bindings_version = 0; + uint32_t cs_glue_version = 0; bool operator==(const Version &p_other) const { return godot_api_hash == p_other.godot_api_hash && @@ -58,11 +58,7 @@ struct Version { cs_glue_version == p_other.cs_glue_version; } - Version() : - godot_api_hash(0), - bindings_version(0), - cs_glue_version(0) { - } + Version() {} Version(uint64_t p_godot_api_hash, uint32_t p_bindings_version, @@ -87,13 +83,10 @@ public: }; struct LoadedApiAssembly { - GDMonoAssembly *assembly; - bool out_of_sync; + GDMonoAssembly *assembly = nullptr; + bool out_of_sync = false; - LoadedApiAssembly() : - assembly(nullptr), - out_of_sync(false) { - } + LoadedApiAssembly() {} }; private: @@ -241,6 +234,7 @@ 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(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs); 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); diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 0f211eebc6..ca84338666 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -33,6 +33,7 @@ #include <mono/metadata/mono-debug.h> #include <mono/metadata/tokentype.h> +#include "core/io/file_access_pack.h" #include "core/list.h" #include "core/os/file_access.h" #include "core/os/os.h" @@ -99,7 +100,7 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin // - The 'load' hook is called after the assembly has been loaded. Its job is to add the // assembly to the list of loaded assemblies so that the 'search' hook can look it up. -void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) { +void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, [[maybe_unused]] void *user_data) { String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly))); @@ -133,9 +134,7 @@ MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *an return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true); } -MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly) { - - (void)user_data; // UNUSED +MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, [[maybe_unused]] void *user_data, bool refonly) { String name = String::utf8(mono_assembly_name_get_name(aname)); bool has_extension = name.ends_with(".dll") || name.ends_with(".exe"); @@ -147,15 +146,13 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d return nullptr; } -MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) { - - (void)user_data; // UNUSED +MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) { String name = String::utf8(mono_assembly_name_get_name(aname)); - return _load_assembly_search(name, search_dirs, refonly); + return _load_assembly_search(name, aname, refonly, search_dirs); } -MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) { +MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) { MonoAssembly *res = nullptr; String path; @@ -168,21 +165,21 @@ MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const if (has_extension) { path = search_dir.plus_file(p_name); if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly); + res = _real_load_assembly_from(path, p_refonly, p_aname); if (res != nullptr) return res; } } else { path = search_dir.plus_file(p_name + ".dll"); if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly); + res = _real_load_assembly_from(path, p_refonly, p_aname); if (res != nullptr) return res; } path = search_dir.plus_file(p_name + ".exe"); if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly); + res = _real_load_assembly_from(path, p_refonly, p_aname); if (res != nullptr) return res; } @@ -230,7 +227,7 @@ void GDMonoAssembly::initialize() { mono_install_assembly_load_hook(&assembly_load_hook, nullptr); } -MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly) { +MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) { Vector<uint8_t> data = FileAccess::get_file_as_array(p_path); ERR_FAIL_COND_V_MSG(data.empty(), nullptr, "Could read the assembly in the specified location"); @@ -255,7 +252,33 @@ MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, boo true, &status, p_refonly, image_filename.utf8()); - ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from the loaded data"); + ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from memory: '" + p_path + "'."); + + if (p_aname != nullptr) { + // Check assembly version + const MonoTableInfo *table = mono_image_get_table_info(image, MONO_TABLE_ASSEMBLY); + + ERR_FAIL_NULL_V(table, nullptr); + + if (mono_table_info_get_rows(table)) { + uint32_t cols[MONO_ASSEMBLY_SIZE]; + mono_metadata_decode_row(table, 0, cols, MONO_ASSEMBLY_SIZE); + + // Not sure about .NET's policy. We will only ensure major and minor are equal, and ignore build and revision. + uint16_t major = cols[MONO_ASSEMBLY_MAJOR_VERSION]; + uint16_t minor = cols[MONO_ASSEMBLY_MINOR_VERSION]; + + uint16_t required_minor; + uint16_t required_major = mono_assembly_name_get_version(p_aname, &required_minor, nullptr, nullptr); + + if (required_major != 0) { + if (major != required_major && minor != required_minor) { + mono_image_close(image); + return nullptr; + } + } + } + } #ifdef DEBUG_ENABLED Vector<uint8_t> pdb_data; @@ -425,6 +448,26 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) return match; } +GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) { + + if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) + return GDMono::get_singleton()->get_corlib_assembly(); + + // We need to manually call the search hook in this case, as it won't be called in the next step + MonoAssembly *assembly = mono_assembly_invoke_search_hook(p_aname); + + if (!assembly) { + assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs); + ERR_FAIL_NULL_V(assembly, nullptr); + } + + GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); + ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?"); + ERR_FAIL_COND_V(loaded_asm->get_assembly() != assembly, nullptr); + + return loaded_asm; +} + GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) { if (p_name == "mscorlib" || p_name == "mscorlib.dll") @@ -447,18 +490,7 @@ GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_ return loaded_asm; } -GDMonoAssembly::GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly) : - name(p_name), - image(p_image), - assembly(p_assembly), -#ifdef GD_MONO_HOT_RELOAD - modified_time(0), -#endif - gdobject_class_cache_updated(false) { -} - GDMonoAssembly::~GDMonoAssembly() { - if (image) unload(); } diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h index 43c8225b74..cc8d699558 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ b/modules/mono/mono_gd/gd_mono_assembly.h @@ -73,10 +73,10 @@ class GDMonoAssembly { MonoAssembly *assembly; #ifdef GD_MONO_HOT_RELOAD - uint64_t modified_time; + uint64_t modified_time = 0; #endif - bool gdobject_class_cache_updated; + bool gdobject_class_cache_updated = false; Map<StringName, GDMonoClass *> gdobject_class_cache; HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes; @@ -93,8 +93,8 @@ class GDMonoAssembly { static MonoAssembly *_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly); static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly); - static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly); - static MonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly); + static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname = nullptr); + static MonoAssembly *_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs); friend class GDMono; static void initialize(); @@ -120,10 +120,16 @@ public: static String find_assembly(const String &p_name); static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String()); + static const Vector<String> &get_default_search_dirs() { return search_dirs; } + static GDMonoAssembly *load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs); static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly); - GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly); + GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly) : + name(p_name), + image(p_image), + assembly(p_assembly) { + } ~GDMonoAssembly(); }; diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp index e76cb84d43..948170f51c 100644 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -501,7 +501,8 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ case Variant::PACKED_COLOR_ARRAY: { SET_FROM_ARRAY(PackedColorArray); } break; - default: break; + default: + break; } } break; diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp index ca16c2b76a..b56350ae1b 100644 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -175,7 +175,7 @@ void GDMonoLog::initialize() { log_level_id = get_log_level_id(log_level.get_data()); if (log_file) { - OS::get_singleton()->print("Mono: Logfile is: %s\n", log_file_path.utf8().get_data()); + OS::get_singleton()->print("Mono: Log file is: '%s'\n", log_file_path.utf8().get_data()); mono_trace_set_log_handler(mono_log_callback, this); } else { OS::get_singleton()->printerr("Mono: No log file, using default log handler\n"); diff --git a/modules/mono/mono_gd/gd_mono_method_thunk.h b/modules/mono/mono_gd/gd_mono_method_thunk.h index 0e05e974e9..82c6f32c81 100644 --- a/modules/mono/mono_gd/gd_mono_method_thunk.h +++ b/modules/mono/mono_gd/gd_mono_method_thunk.h @@ -50,7 +50,7 @@ struct GDMonoMethodThunk { typedef void(GD_MONO_STDCALL *M)(ParamTypes... p_args, MonoException **); - M mono_method_thunk; + M mono_method_thunk = nullptr; public: _FORCE_INLINE_ void invoke(ParamTypes... p_args, MonoException **r_exc) { @@ -81,9 +81,7 @@ public: mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); } - GDMonoMethodThunk() : - mono_method_thunk(nullptr) { - } + GDMonoMethodThunk() {} explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) { set_from_method(p_mono_method); @@ -95,7 +93,7 @@ struct GDMonoMethodThunkR { typedef R(GD_MONO_STDCALL *M)(ParamTypes... p_args, MonoException **); - M mono_method_thunk; + M mono_method_thunk = nullptr; public: _FORCE_INLINE_ R invoke(ParamTypes... p_args, MonoException **r_exc) { @@ -127,9 +125,7 @@ public: mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); } - GDMonoMethodThunkR() : - mono_method_thunk(nullptr) { - } + GDMonoMethodThunkR() {} explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) { #ifdef DEBUG_ENABLED @@ -248,7 +244,7 @@ struct VariadicInvokeMonoMethodR<1, R, P1> { template <class... ParamTypes> struct GDMonoMethodThunk { - GDMonoMethod *mono_method; + GDMonoMethod *mono_method = nullptr; public: _FORCE_INLINE_ void invoke(ParamTypes... p_args, MonoException **r_exc) { @@ -277,9 +273,7 @@ public: mono_method = p_mono_method; } - GDMonoMethodThunk() : - mono_method(nullptr) { - } + GDMonoMethodThunk() {} explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) { set_from_method(p_mono_method); @@ -289,7 +283,7 @@ public: template <class R, class... ParamTypes> struct GDMonoMethodThunkR { - GDMonoMethod *mono_method; + GDMonoMethod *mono_method = nullptr; public: _FORCE_INLINE_ R invoke(ParamTypes... p_args, MonoException **r_exc) { @@ -318,9 +312,7 @@ public: mono_method = p_mono_method; } - GDMonoMethodThunkR() : - mono_method(nullptr) { - } + GDMonoMethodThunkR() {} explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) { set_from_method(p_mono_method); diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index f9d492dabb..a2ae42ae9f 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -564,9 +564,10 @@ namespace Marshal { #ifdef MONO_GLUE_ENABLED #ifdef TOOLS_ENABLED -#define NO_GLUE_RET(m_ret) \ - { \ - if (!GDMonoCache::cached_data.godot_api_cache_updated) return m_ret; \ +#define NO_GLUE_RET(m_ret) \ + { \ + if (!GDMonoCache::cached_data.godot_api_cache_updated) \ + return m_ret; \ } #else #define NO_GLUE_RET(m_ret) \ @@ -663,8 +664,7 @@ GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, Mon } // namespace Marshal -ScopeThreadAttach::ScopeThreadAttach() : - mono_thread(nullptr) { +ScopeThreadAttach::ScopeThreadAttach() { if (likely(GDMono::get_singleton()->is_runtime_initialized()) && unlikely(!mono_domain_get())) { mono_thread = GDMonoUtils::attach_current_thread(); } diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index e3011ade5d..caf0c792b7 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -155,7 +155,7 @@ struct ScopeThreadAttach { ~ScopeThreadAttach(); private: - MonoThread *mono_thread; + MonoThread *mono_thread = nullptr; }; StringName get_native_godot_class_name(GDMonoClass *p_class); diff --git a/modules/mono/mono_gd/managed_type.h b/modules/mono/mono_gd/managed_type.h index 84d1837853..491a2f3d20 100644 --- a/modules/mono/mono_gd/managed_type.h +++ b/modules/mono/mono_gd/managed_type.h @@ -36,18 +36,15 @@ #include "gd_mono_header.h" struct ManagedType { - int type_encoding; - GDMonoClass *type_class; + int type_encoding = 0; + GDMonoClass *type_class = nullptr; static ManagedType from_class(GDMonoClass *p_class); static ManagedType from_class(MonoClass *p_mono_class); static ManagedType from_type(MonoType *p_mono_type); static ManagedType from_reftype(MonoReflectionType *p_mono_reftype); - ManagedType() : - type_encoding(0), - type_class(nullptr) { - } + ManagedType() {} ManagedType(int p_type_encoding, GDMonoClass *p_type_class) : type_encoding(p_type_encoding), diff --git a/modules/mono/utils/macros.h b/modules/mono/utils/macros.h index 8650d6cc09..dc542477f5 100644 --- a/modules/mono/utils/macros.h +++ b/modules/mono/utils/macros.h @@ -68,6 +68,6 @@ public: } // namespace gdmono #define SCOPE_EXIT \ - auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]() + auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]() -> void #endif // UTIL_MACROS_H diff --git a/modules/opensimplex/noise_texture.cpp b/modules/opensimplex/noise_texture.cpp index 2018f90e9f..16cd04b044 100644 --- a/modules/opensimplex/noise_texture.cpp +++ b/modules/opensimplex/noise_texture.cpp @@ -202,19 +202,22 @@ Ref<OpenSimplexNoise> NoiseTexture::get_noise() { } void NoiseTexture::set_width(int p_width) { - if (p_width == size.x) return; + 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; + if (p_height == size.y) + return; size.y = p_height; _queue_update(); } void NoiseTexture::set_seamless(bool p_seamless) { - if (p_seamless == seamless) return; + if (p_seamless == seamless) + return; seamless = p_seamless; _queue_update(); } @@ -224,7 +227,8 @@ bool NoiseTexture::get_seamless() { } void NoiseTexture::set_as_normalmap(bool p_as_normalmap) { - if (p_as_normalmap == as_normalmap) return; + if (p_as_normalmap == as_normalmap) + return; as_normalmap = p_as_normalmap; _queue_update(); _change_notify(); @@ -236,7 +240,8 @@ bool NoiseTexture::is_normalmap() { void NoiseTexture::set_bump_strength(float p_bump_strength) { - if (p_bump_strength == bump_strength) return; + if (p_bump_strength == bump_strength) + return; bump_strength = p_bump_strength; if (as_normalmap) _queue_update(); diff --git a/modules/opensimplex/open_simplex_noise.cpp b/modules/opensimplex/open_simplex_noise.cpp index 238faa4130..205c033614 100644 --- a/modules/opensimplex/open_simplex_noise.cpp +++ b/modules/opensimplex/open_simplex_noise.cpp @@ -70,7 +70,8 @@ int OpenSimplexNoise::get_seed() { } void OpenSimplexNoise::set_octaves(int p_octaves) { - if (p_octaves == octaves) return; + if (p_octaves == octaves) + return; ERR_FAIL_COND_MSG(p_octaves > MAX_OCTAVES, vformat("The number of OpenSimplexNoise octaves is limited to %d; ignoring the new value.", MAX_OCTAVES)); @@ -79,19 +80,22 @@ void OpenSimplexNoise::set_octaves(int p_octaves) { } void OpenSimplexNoise::set_period(float p_period) { - if (p_period == period) return; + if (p_period == period) + return; period = p_period; emit_changed(); } void OpenSimplexNoise::set_persistence(float p_persistence) { - if (p_persistence == persistence) return; + if (p_persistence == persistence) + return; persistence = p_persistence; emit_changed(); } void OpenSimplexNoise::set_lacunarity(float p_lacunarity) { - if (p_lacunarity == lacunarity) return; + if (p_lacunarity == lacunarity) + return; lacunarity = p_lacunarity; emit_changed(); } diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp index a8e8a9a2f1..d498c6d858 100644 --- a/modules/pvr/texture_loader_pvr.cpp +++ b/modules/pvr/texture_loader_pvr.cpp @@ -111,9 +111,13 @@ RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path, switch (flags & 0xFF) { case 0x18: - case 0xC: format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC2A : Image::FORMAT_PVRTC2; break; + case 0xC: + format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC2A : Image::FORMAT_PVRTC2; + break; case 0x19: - case 0xD: format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC4A : Image::FORMAT_PVRTC4; break; + case 0xD: + format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC4A : Image::FORMAT_PVRTC4; + break; case 0x16: format = Image::FORMAT_L8; break; @@ -257,9 +261,9 @@ struct PVRTCBlock { _FORCE_INLINE_ bool is_po2(uint32_t p_input) { if (p_input == 0) - return 0; + return false; uint32_t minus1 = p_input - 1; - return ((p_input | minus1) == (p_input ^ minus1)) ? 1 : 0; + return ((p_input | minus1) == (p_input ^ minus1)) ? true : false; } static void unpack_5554(const PVRTCBlock *p_block, int p_ab_colors[2][4]) { diff --git a/modules/tga/image_loader_tga.cpp b/modules/tga/image_loader_tga.cpp index fc9d727bb0..eb9f23d894 100644 --- a/modules/tga/image_loader_tga.cpp +++ b/modules/tga/image_loader_tga.cpp @@ -199,7 +199,7 @@ Error ImageLoaderTGA::convert_to_image(Ref<Image> p_image, const uint8_t *p_buff } } - p_image->create(width, height, 0, Image::FORMAT_RGBA8, image_data); + p_image->create(width, height, false, Image::FORMAT_RGBA8, image_data); return OK; } diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index b9f276fb12..3163b60bb8 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -80,7 +80,7 @@ int VideoStreamPlaybackTheora::queue_page(ogg_page *page) { return 0; } -void VideoStreamPlaybackTheora::video_write(void) { +void VideoStreamPlaybackTheora::video_write() { th_ycbcr_buffer yuv; th_decode_ycbcr_out(td, yuv); @@ -209,7 +209,8 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) { while (!stateflag) { int ret = buffer_data(); - if (ret == 0) break; + if (ret == 0) + break; while (ogg_sync_pageout(&oy, &og) > 0) { ogg_stream_state test; @@ -286,7 +287,8 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) { return; } vorbis_p++; - if (vorbis_p == 3) break; + if (vorbis_p == 3) + break; } /* The header pages/packets will arrive before anything else we diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h index f98368ed8b..d5a2640794 100644 --- a/modules/theora/video_stream_theora.h +++ b/modules/theora/video_stream_theora.h @@ -63,7 +63,7 @@ class VideoStreamPlaybackTheora : public VideoStreamPlayback { int buffer_data(); int queue_page(ogg_page *page); - void video_write(void); + void video_write(); float get_time() const; bool theora_eos; diff --git a/modules/tinyexr/image_saver_tinyexr.cpp b/modules/tinyexr/image_saver_tinyexr.cpp index 05080289bd..bc30f4e4fd 100644 --- a/modules/tinyexr/image_saver_tinyexr.cpp +++ b/modules/tinyexr/image_saver_tinyexr.cpp @@ -267,13 +267,21 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) header.channels = channel_infos; header.pixel_types = pixel_types; header.requested_pixel_types = requested_pixel_types; + header.compression_type = TINYEXR_COMPRESSIONTYPE_PIZ; - CharString utf8_filename = p_path.utf8(); - const char *err; - int ret = SaveEXRImageToFile(&image, &header, utf8_filename.ptr(), &err); - if (ret != TINYEXR_SUCCESS) { + unsigned char *mem = nullptr; + const char *err = nullptr; + + size_t bytes = SaveEXRImageToMemory(&image, &header, &mem, &err); + + if (bytes == 0) { print_error(String("Saving EXR failed. Error: {0}").format(varray(err))); return ERR_FILE_CANT_WRITE; + } else { + FileAccessRef ref = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V(!ref, ERR_FILE_CANT_WRITE); + ref->store_buffer(mem, bytes); + free(mem); } return OK; diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 9a4076bec4..3649486724 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -342,84 +342,212 @@ static Color _color_from_type(Variant::Type p_type, bool dark_theme = true) { Color color; if (dark_theme) switch (p_type) { - case Variant::NIL: color = Color(0.41, 0.93, 0.74); break; - - case Variant::BOOL: color = Color(0.55, 0.65, 0.94); break; - case Variant::INT: color = Color(0.49, 0.78, 0.94); break; - case Variant::FLOAT: color = Color(0.38, 0.85, 0.96); break; - case Variant::STRING: color = Color(0.42, 0.65, 0.93); break; - - case Variant::VECTOR2: color = Color(0.74, 0.57, 0.95); break; - case Variant::VECTOR2I: color = Color(0.74, 0.57, 0.95); break; - case Variant::RECT2: color = Color(0.95, 0.57, 0.65); break; - case Variant::RECT2I: color = Color(0.95, 0.57, 0.65); break; - case Variant::VECTOR3: color = Color(0.84, 0.49, 0.93); break; - case Variant::VECTOR3I: color = Color(0.84, 0.49, 0.93); break; - case Variant::TRANSFORM2D: color = Color(0.77, 0.93, 0.41); break; - case Variant::PLANE: color = Color(0.97, 0.44, 0.44); break; - case Variant::QUAT: color = Color(0.93, 0.41, 0.64); break; - case Variant::AABB: color = Color(0.93, 0.47, 0.57); break; - case Variant::BASIS: color = Color(0.89, 0.93, 0.41); break; - case Variant::TRANSFORM: color = Color(0.96, 0.66, 0.43); break; - - case Variant::COLOR: color = Color(0.62, 1.0, 0.44); break; - case Variant::NODE_PATH: color = Color(0.41, 0.58, 0.93); break; - case Variant::_RID: color = Color(0.41, 0.93, 0.6); break; - case Variant::OBJECT: color = Color(0.47, 0.95, 0.91); break; - case Variant::DICTIONARY: color = Color(0.47, 0.93, 0.69); break; - - case Variant::ARRAY: color = Color(0.88, 0.88, 0.88); break; - case Variant::PACKED_BYTE_ARRAY: color = Color(0.67, 0.96, 0.78); break; - case Variant::PACKED_INT32_ARRAY: color = Color(0.69, 0.86, 0.96); break; - case Variant::PACKED_FLOAT32_ARRAY: color = Color(0.59, 0.91, 0.97); break; - case Variant::PACKED_INT64_ARRAY: color = Color(0.69, 0.86, 0.96); break; - case Variant::PACKED_FLOAT64_ARRAY: color = Color(0.59, 0.91, 0.97); break; - case Variant::PACKED_STRING_ARRAY: color = Color(0.62, 0.77, 0.95); break; - case Variant::PACKED_VECTOR2_ARRAY: color = Color(0.82, 0.7, 0.96); break; - case Variant::PACKED_VECTOR3_ARRAY: color = Color(0.87, 0.61, 0.95); break; - case Variant::PACKED_COLOR_ARRAY: color = Color(0.91, 1.0, 0.59); break; + case Variant::NIL: + color = Color(0.41, 0.93, 0.74); + break; + + case Variant::BOOL: + color = Color(0.55, 0.65, 0.94); + break; + case Variant::INT: + color = Color(0.49, 0.78, 0.94); + break; + case Variant::FLOAT: + color = Color(0.38, 0.85, 0.96); + break; + case Variant::STRING: + color = Color(0.42, 0.65, 0.93); + break; + + case Variant::VECTOR2: + color = Color(0.74, 0.57, 0.95); + break; + case Variant::VECTOR2I: + color = Color(0.74, 0.57, 0.95); + break; + case Variant::RECT2: + color = Color(0.95, 0.57, 0.65); + break; + case Variant::RECT2I: + color = Color(0.95, 0.57, 0.65); + break; + case Variant::VECTOR3: + color = Color(0.84, 0.49, 0.93); + break; + case Variant::VECTOR3I: + color = Color(0.84, 0.49, 0.93); + break; + case Variant::TRANSFORM2D: + color = Color(0.77, 0.93, 0.41); + break; + case Variant::PLANE: + color = Color(0.97, 0.44, 0.44); + break; + case Variant::QUAT: + color = Color(0.93, 0.41, 0.64); + break; + case Variant::AABB: + color = Color(0.93, 0.47, 0.57); + break; + case Variant::BASIS: + color = Color(0.89, 0.93, 0.41); + break; + case Variant::TRANSFORM: + color = Color(0.96, 0.66, 0.43); + break; + + case Variant::COLOR: + color = Color(0.62, 1.0, 0.44); + break; + case Variant::NODE_PATH: + color = Color(0.41, 0.58, 0.93); + break; + case Variant::_RID: + color = Color(0.41, 0.93, 0.6); + break; + case Variant::OBJECT: + color = Color(0.47, 0.95, 0.91); + break; + case Variant::DICTIONARY: + color = Color(0.47, 0.93, 0.69); + break; + + case Variant::ARRAY: + color = Color(0.88, 0.88, 0.88); + break; + case Variant::PACKED_BYTE_ARRAY: + color = Color(0.67, 0.96, 0.78); + break; + case Variant::PACKED_INT32_ARRAY: + color = Color(0.69, 0.86, 0.96); + break; + case Variant::PACKED_FLOAT32_ARRAY: + color = Color(0.59, 0.91, 0.97); + break; + case Variant::PACKED_INT64_ARRAY: + color = Color(0.69, 0.86, 0.96); + break; + case Variant::PACKED_FLOAT64_ARRAY: + color = Color(0.59, 0.91, 0.97); + break; + case Variant::PACKED_STRING_ARRAY: + color = Color(0.62, 0.77, 0.95); + break; + case Variant::PACKED_VECTOR2_ARRAY: + color = Color(0.82, 0.7, 0.96); + break; + case Variant::PACKED_VECTOR3_ARRAY: + color = Color(0.87, 0.61, 0.95); + break; + case Variant::PACKED_COLOR_ARRAY: + color = Color(0.91, 1.0, 0.59); + break; default: color.set_hsv(p_type / float(Variant::VARIANT_MAX), 0.7, 0.7); } else switch (p_type) { - case Variant::NIL: color = Color(0.15, 0.89, 0.63); break; - - case Variant::BOOL: color = Color(0.43, 0.56, 0.92); break; - case Variant::INT: color = Color(0.31, 0.7, 0.91); break; - case Variant::FLOAT: color = Color(0.15, 0.8, 0.94); break; - case Variant::STRING: color = Color(0.27, 0.56, 0.91); break; - - case Variant::VECTOR2: color = Color(0.68, 0.46, 0.93); break; - case Variant::VECTOR2I: color = Color(0.68, 0.46, 0.93); break; - case Variant::RECT2: color = Color(0.93, 0.46, 0.56); break; - case Variant::RECT2I: color = Color(0.93, 0.46, 0.56); break; - case Variant::VECTOR3: color = Color(0.86, 0.42, 0.93); break; - case Variant::VECTOR3I: color = Color(0.86, 0.42, 0.93); break; - case Variant::TRANSFORM2D: color = Color(0.59, 0.81, 0.1); break; - case Variant::PLANE: color = Color(0.97, 0.44, 0.44); break; - case Variant::QUAT: color = Color(0.93, 0.41, 0.64); break; - case Variant::AABB: color = Color(0.93, 0.47, 0.57); break; - case Variant::BASIS: color = Color(0.7, 0.73, 0.1); break; - case Variant::TRANSFORM: color = Color(0.96, 0.56, 0.28); break; - - case Variant::COLOR: color = Color(0.24, 0.75, 0.0); break; - case Variant::NODE_PATH: color = Color(0.41, 0.58, 0.93); break; - case Variant::_RID: color = Color(0.17, 0.9, 0.45); break; - case Variant::OBJECT: color = Color(0.07, 0.84, 0.76); break; - case Variant::DICTIONARY: color = Color(0.34, 0.91, 0.62); break; - - case Variant::ARRAY: color = Color(0.45, 0.45, 0.45); break; - case Variant::PACKED_BYTE_ARRAY: color = Color(0.38, 0.92, 0.6); break; - case Variant::PACKED_INT32_ARRAY: color = Color(0.38, 0.73, 0.92); break; - case Variant::PACKED_FLOAT32_ARRAY: color = Color(0.25, 0.83, 0.95); break; - case Variant::PACKED_INT64_ARRAY: color = Color(0.38, 0.73, 0.92); break; - case Variant::PACKED_FLOAT64_ARRAY: color = Color(0.25, 0.83, 0.95); break; - case Variant::PACKED_STRING_ARRAY: color = Color(0.38, 0.62, 0.92); break; - case Variant::PACKED_VECTOR2_ARRAY: color = Color(0.62, 0.36, 0.92); break; - case Variant::PACKED_VECTOR3_ARRAY: color = Color(0.79, 0.35, 0.92); break; - case Variant::PACKED_COLOR_ARRAY: color = Color(0.57, 0.73, 0.0); break; + case Variant::NIL: + color = Color(0.15, 0.89, 0.63); + break; + + case Variant::BOOL: + color = Color(0.43, 0.56, 0.92); + break; + case Variant::INT: + color = Color(0.31, 0.7, 0.91); + break; + case Variant::FLOAT: + color = Color(0.15, 0.8, 0.94); + break; + case Variant::STRING: + color = Color(0.27, 0.56, 0.91); + break; + + case Variant::VECTOR2: + color = Color(0.68, 0.46, 0.93); + break; + case Variant::VECTOR2I: + color = Color(0.68, 0.46, 0.93); + break; + case Variant::RECT2: + color = Color(0.93, 0.46, 0.56); + break; + case Variant::RECT2I: + color = Color(0.93, 0.46, 0.56); + break; + case Variant::VECTOR3: + color = Color(0.86, 0.42, 0.93); + break; + case Variant::VECTOR3I: + color = Color(0.86, 0.42, 0.93); + break; + case Variant::TRANSFORM2D: + color = Color(0.59, 0.81, 0.1); + break; + case Variant::PLANE: + color = Color(0.97, 0.44, 0.44); + break; + case Variant::QUAT: + color = Color(0.93, 0.41, 0.64); + break; + case Variant::AABB: + color = Color(0.93, 0.47, 0.57); + break; + case Variant::BASIS: + color = Color(0.7, 0.73, 0.1); + break; + case Variant::TRANSFORM: + color = Color(0.96, 0.56, 0.28); + break; + + case Variant::COLOR: + color = Color(0.24, 0.75, 0.0); + break; + case Variant::NODE_PATH: + color = Color(0.41, 0.58, 0.93); + break; + case Variant::_RID: + color = Color(0.17, 0.9, 0.45); + break; + case Variant::OBJECT: + color = Color(0.07, 0.84, 0.76); + break; + case Variant::DICTIONARY: + color = Color(0.34, 0.91, 0.62); + break; + + case Variant::ARRAY: + color = Color(0.45, 0.45, 0.45); + break; + case Variant::PACKED_BYTE_ARRAY: + color = Color(0.38, 0.92, 0.6); + break; + case Variant::PACKED_INT32_ARRAY: + color = Color(0.38, 0.73, 0.92); + break; + case Variant::PACKED_FLOAT32_ARRAY: + color = Color(0.25, 0.83, 0.95); + break; + case Variant::PACKED_INT64_ARRAY: + color = Color(0.38, 0.73, 0.92); + break; + case Variant::PACKED_FLOAT64_ARRAY: + color = Color(0.25, 0.83, 0.95); + break; + case Variant::PACKED_STRING_ARRAY: + color = Color(0.38, 0.62, 0.92); + break; + case Variant::PACKED_VECTOR2_ARRAY: + color = Color(0.62, 0.36, 0.92); + break; + case Variant::PACKED_VECTOR3_ARRAY: + color = Color(0.79, 0.35, 0.92); + break; + case Variant::PACKED_COLOR_ARRAY: + color = Color(0.57, 0.73, 0.0); + break; default: color.set_hsv(p_type / float(Variant::VARIANT_MAX), 0.3, 0.3); diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp index 71ed483d65..616a621845 100644 --- a/modules/visual_script/visual_script_expression.cpp +++ b/modules/visual_script/visual_script_expression.cpp @@ -386,11 +386,21 @@ Error VisualScriptExpression::_get_token(Token &r_token) { 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 '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 'u': { // hex number for (int j = 0; j < 4; j++) { @@ -1005,27 +1015,69 @@ VisualScriptExpression::ENode *VisualScriptExpression::_parse_expression() { Variant::Operator op = Variant::OP_MAX; switch (tk.type) { - case TK_OP_IN: op = Variant::OP_IN; break; - case TK_OP_EQUAL: op = Variant::OP_EQUAL; break; - case TK_OP_NOT_EQUAL: op = Variant::OP_NOT_EQUAL; break; - case TK_OP_LESS: op = Variant::OP_LESS; break; - case TK_OP_LESS_EQUAL: op = Variant::OP_LESS_EQUAL; break; - case TK_OP_GREATER: op = Variant::OP_GREATER; break; - case TK_OP_GREATER_EQUAL: op = Variant::OP_GREATER_EQUAL; break; - case TK_OP_AND: op = Variant::OP_AND; break; - case TK_OP_OR: op = Variant::OP_OR; break; - case TK_OP_NOT: op = Variant::OP_NOT; break; - case TK_OP_ADD: op = Variant::OP_ADD; break; - case TK_OP_SUB: op = Variant::OP_SUBTRACT; break; - case TK_OP_MUL: op = Variant::OP_MULTIPLY; break; - case TK_OP_DIV: op = Variant::OP_DIVIDE; break; - case TK_OP_MOD: op = Variant::OP_MODULE; break; - case TK_OP_SHIFT_LEFT: op = Variant::OP_SHIFT_LEFT; break; - case TK_OP_SHIFT_RIGHT: op = Variant::OP_SHIFT_RIGHT; break; - case TK_OP_BIT_AND: op = Variant::OP_BIT_AND; break; - case TK_OP_BIT_OR: op = Variant::OP_BIT_OR; break; - case TK_OP_BIT_XOR: op = Variant::OP_BIT_XOR; break; - case TK_OP_BIT_INVERT: op = Variant::OP_BIT_NEGATE; break; + case TK_OP_IN: + op = Variant::OP_IN; + break; + case TK_OP_EQUAL: + op = Variant::OP_EQUAL; + break; + case TK_OP_NOT_EQUAL: + op = Variant::OP_NOT_EQUAL; + break; + case TK_OP_LESS: + op = Variant::OP_LESS; + break; + case TK_OP_LESS_EQUAL: + op = Variant::OP_LESS_EQUAL; + break; + case TK_OP_GREATER: + op = Variant::OP_GREATER; + break; + case TK_OP_GREATER_EQUAL: + op = Variant::OP_GREATER_EQUAL; + break; + case TK_OP_AND: + op = Variant::OP_AND; + break; + case TK_OP_OR: + op = Variant::OP_OR; + break; + case TK_OP_NOT: + op = Variant::OP_NOT; + break; + case TK_OP_ADD: + op = Variant::OP_ADD; + break; + case TK_OP_SUB: + op = Variant::OP_SUBTRACT; + break; + case TK_OP_MUL: + op = Variant::OP_MULTIPLY; + break; + case TK_OP_DIV: + op = Variant::OP_DIVIDE; + break; + case TK_OP_MOD: + op = Variant::OP_MODULE; + break; + case TK_OP_SHIFT_LEFT: + op = Variant::OP_SHIFT_LEFT; + break; + case TK_OP_SHIFT_RIGHT: + op = Variant::OP_SHIFT_RIGHT; + break; + case TK_OP_BIT_AND: + op = Variant::OP_BIT_AND; + break; + case TK_OP_BIT_OR: + op = Variant::OP_BIT_OR; + break; + case TK_OP_BIT_XOR: + op = Variant::OP_BIT_XOR; + break; + case TK_OP_BIT_INVERT: + op = Variant::OP_BIT_NEGATE; + break; default: { }; } @@ -1074,36 +1126,74 @@ VisualScriptExpression::ENode *VisualScriptExpression::_parse_expression() { unary = true; break; - case Variant::OP_MULTIPLY: priority = 2; break; - case Variant::OP_DIVIDE: priority = 2; break; - case Variant::OP_MODULE: priority = 2; break; + case Variant::OP_MULTIPLY: + priority = 2; + break; + case Variant::OP_DIVIDE: + priority = 2; + break; + case Variant::OP_MODULE: + priority = 2; + break; - case Variant::OP_ADD: priority = 3; break; - case Variant::OP_SUBTRACT: priority = 3; break; + case Variant::OP_ADD: + priority = 3; + break; + case Variant::OP_SUBTRACT: + priority = 3; + break; - case Variant::OP_SHIFT_LEFT: priority = 4; break; - case Variant::OP_SHIFT_RIGHT: priority = 4; break; + case Variant::OP_SHIFT_LEFT: + priority = 4; + break; + case Variant::OP_SHIFT_RIGHT: + priority = 4; + break; - case Variant::OP_BIT_AND: priority = 5; break; - case Variant::OP_BIT_XOR: priority = 6; break; - case Variant::OP_BIT_OR: priority = 7; break; + case Variant::OP_BIT_AND: + priority = 5; + break; + case Variant::OP_BIT_XOR: + priority = 6; + break; + case Variant::OP_BIT_OR: + priority = 7; + break; - case Variant::OP_LESS: priority = 8; break; - case Variant::OP_LESS_EQUAL: priority = 8; break; - case Variant::OP_GREATER: priority = 8; break; - case Variant::OP_GREATER_EQUAL: priority = 8; break; + case Variant::OP_LESS: + priority = 8; + break; + case Variant::OP_LESS_EQUAL: + priority = 8; + break; + case Variant::OP_GREATER: + priority = 8; + break; + case Variant::OP_GREATER_EQUAL: + priority = 8; + break; - case Variant::OP_EQUAL: priority = 8; break; - case Variant::OP_NOT_EQUAL: priority = 8; break; + case Variant::OP_EQUAL: + priority = 8; + break; + case Variant::OP_NOT_EQUAL: + priority = 8; + break; - case Variant::OP_IN: priority = 10; break; + case Variant::OP_IN: + priority = 10; + break; case Variant::OP_NOT: priority = 11; unary = true; break; - case Variant::OP_AND: priority = 12; break; - case Variant::OP_OR: priority = 13; break; + case Variant::OP_AND: + priority = 12; + break; + case Variant::OP_OR: + priority = 13; + break; default: { _set_error("Parser bug, invalid operator in expression: " + itos(expression[i].op)); diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp index f57853078d..b06cf513ba 100644 --- a/modules/visual_script/visual_script_property_selector.cpp +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -172,7 +172,7 @@ void VisualScriptPropertySelector::_update_search() { item->set_metadata(0, F->get().name); item->set_icon(0, type_icons[F->get().type]); item->set_metadata(1, "get"); - item->set_collapsed(1); + item->set_collapsed(true); item->set_selectable(0, true); item->set_selectable(1, false); item->set_selectable(2, false); @@ -257,7 +257,7 @@ void VisualScriptPropertySelector::_update_search() { item->set_selectable(0, true); item->set_metadata(1, "method"); - item->set_collapsed(1); + item->set_collapsed(true); item->set_selectable(1, false); item->set_selectable(2, false); @@ -320,7 +320,7 @@ void VisualScriptPropertySelector::create_visualscript_item(const String &name, item->set_metadata(0, name); item->set_metadata(1, "action"); item->set_selectable(0, true); - item->set_collapsed(1); + item->set_collapsed(true); item->set_selectable(1, false); item->set_selectable(2, false); item->set_metadata(2, connecting); diff --git a/modules/visual_script/visual_script_yield_nodes.cpp b/modules/visual_script/visual_script_yield_nodes.cpp index b300aec385..2296745ad0 100644 --- a/modules/visual_script/visual_script_yield_nodes.cpp +++ b/modules/visual_script/visual_script_yield_nodes.cpp @@ -81,10 +81,18 @@ String VisualScriptYield::get_caption() const { String VisualScriptYield::get_text() const { switch (yield_mode) { - case YIELD_RETURN: return ""; break; - case YIELD_FRAME: return "Next Frame"; break; - case YIELD_PHYSICS_FRAME: return "Next Physics Frame"; break; - case YIELD_WAIT: return rtos(wait_time) + " sec(s)"; break; + case YIELD_RETURN: + return ""; + break; + case YIELD_FRAME: + return "Next Frame"; + break; + case YIELD_PHYSICS_FRAME: + return "Next Physics Frame"; + break; + case YIELD_WAIT: + return rtos(wait_time) + " sec(s)"; + break; } return String(); @@ -122,9 +130,15 @@ public: case VisualScriptYield::YIELD_RETURN: ret = STEP_EXIT_FUNCTION_BIT; break; //return the yield - case VisualScriptYield::YIELD_FRAME: state->connect_to_signal(tree, "idle_frame", Array()); break; - case VisualScriptYield::YIELD_PHYSICS_FRAME: state->connect_to_signal(tree, "physics_frame", Array()); break; - case VisualScriptYield::YIELD_WAIT: state->connect_to_signal(tree->create_timer(wait_time).ptr(), "timeout", Array()); break; + case VisualScriptYield::YIELD_FRAME: + state->connect_to_signal(tree, "idle_frame", Array()); + break; + case VisualScriptYield::YIELD_PHYSICS_FRAME: + state->connect_to_signal(tree, "physics_frame", Array()); + break; + case VisualScriptYield::YIELD_WAIT: + state->connect_to_signal(tree->create_timer(wait_time).ptr(), "timeout", Array()); + break; } *p_working_mem = state; diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp index a2d0f78f5f..faf1f32124 100644 --- a/modules/webm/video_stream_webm.cpp +++ b/modules/webm/video_stream_webm.cpp @@ -95,26 +95,8 @@ private: /**/ VideoStreamPlaybackWebm::VideoStreamPlaybackWebm() : - audio_track(0), - webm(nullptr), - video(nullptr), - audio(nullptr), - video_frames(nullptr), - audio_frame(nullptr), - video_frames_pos(0), - video_frames_capacity(0), - num_decoded_samples(0), - samples_offset(-1), - mix_callback(nullptr), - mix_udata(nullptr), - playing(false), - paused(false), - delay_compensation(0.0), - time(0.0), - video_frame_delay(0.0), - video_pos(0.0), - texture(memnew(ImageTexture)), - pcm(nullptr) {} + + texture(memnew(ImageTexture)) {} VideoStreamPlaybackWebm::~VideoStreamPlaybackWebm() { delete_pointers(); @@ -438,8 +420,7 @@ void VideoStreamPlaybackWebm::delete_pointers() { /**/ -VideoStreamWebm::VideoStreamWebm() : - audio_track(0) {} +VideoStreamWebm::VideoStreamWebm() {} Ref<VideoStreamPlayback> VideoStreamWebm::instance_playback() { diff --git a/modules/webm/video_stream_webm.h b/modules/webm/video_stream_webm.h index 6677fb85aa..0a32dfc671 100644 --- a/modules/webm/video_stream_webm.h +++ b/modules/webm/video_stream_webm.h @@ -44,27 +44,27 @@ class VideoStreamPlaybackWebm : public VideoStreamPlayback { GDCLASS(VideoStreamPlaybackWebm, VideoStreamPlayback); String file_name; - int audio_track; + int audio_track = 0; - WebMDemuxer *webm; - VPXDecoder *video; - OpusVorbisDecoder *audio; + WebMDemuxer *webm = nullptr; + VPXDecoder *video = nullptr; + OpusVorbisDecoder *audio = nullptr; - WebMFrame **video_frames, *audio_frame; - int video_frames_pos, video_frames_capacity; + WebMFrame **video_frames = nullptr, *audio_frame = nullptr; + int video_frames_pos = 0, video_frames_capacity = 0; - int num_decoded_samples, samples_offset; - AudioMixCallback mix_callback; - void *mix_udata; + int num_decoded_samples = 0, samples_offset = -1; + AudioMixCallback mix_callback = nullptr; + void *mix_udata = nullptr; - bool playing, paused; - double delay_compensation; - double time, video_frame_delay, video_pos; + bool playing = false, paused = false; + double delay_compensation = 0.0; + double time = 0.0, video_frame_delay = 0.0, video_pos = 0.0; Vector<uint8_t> frame_data; Ref<ImageTexture> texture; - float *pcm; + float *pcm = nullptr; public: VideoStreamPlaybackWebm(); @@ -111,7 +111,7 @@ class VideoStreamWebm : public VideoStream { GDCLASS(VideoStreamWebm, VideoStream); String file; - int audio_track; + int audio_track = 0; protected: static void _bind_methods(); diff --git a/modules/webp/image_loader_webp.cpp b/modules/webp/image_loader_webp.cpp index 0998977bb4..6c778d2809 100644 --- a/modules/webp/image_loader_webp.cpp +++ b/modules/webp/image_loader_webp.cpp @@ -135,7 +135,7 @@ Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p ERR_FAIL_COND_V_MSG(errdec, ERR_FILE_CORRUPT, "Failed decoding WebP image."); - p_image->create(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image); + p_image->create(features.width, features.height, false, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image); return OK; } diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp index 1b360720a2..40c3f5801b 100644 --- a/modules/webrtc/webrtc_data_channel_js.cpp +++ b/modules/webrtc/webrtc_data_channel_js.cpp @@ -312,14 +312,14 @@ WebRTCDataChannelJS::WebRTCDataChannelJS(int js_id) { return; } var len = buffer.length*buffer.BYTES_PER_ELEMENT; - var out = Module._malloc(len); - Module.HEAPU8.set(buffer, out); + var out = _malloc(len); + HEAPU8.set(buffer, out); ccall("_emrtc_on_ch_message", "void", ["number", "number", "number", "number"], [c_ptr, out, len, is_string] ); - Module._free(out); + _free(out); } }, this, js_id); diff --git a/modules/webrtc/webrtc_multiplayer.cpp b/modules/webrtc/webrtc_multiplayer.cpp index 78a4d1e61a..f294733961 100644 --- a/modules/webrtc/webrtc_multiplayer.cpp +++ b/modules/webrtc/webrtc_multiplayer.cpp @@ -144,7 +144,8 @@ void WebRTCMultiplayer::poll() { void WebRTCMultiplayer::_find_next_peer() { Map<int, Ref<ConnectedPeer>>::Element *E = peer_map.find(next_packet_peer); - if (E) E = E->next(); + if (E) + E = E->next(); // After last. while (E) { for (List<Ref<WebRTCDataChannel>>::Element *F = E->get()->channels.front(); F; F = F->next()) { diff --git a/modules/websocket/editor_debugger_server_websocket.cpp b/modules/websocket/editor_debugger_server_websocket.cpp new file mode 100644 index 0000000000..0cf78eaa9e --- /dev/null +++ b/modules/websocket/editor_debugger_server_websocket.cpp @@ -0,0 +1,92 @@ +/*************************************************************************/ +/* script_editor_debugger_websocket.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_debugger_server_websocket.h" + +#include "core/project_settings.h" +#include "editor/editor_settings.h" +#include "modules/websocket/remote_debugger_peer_websocket.h" + +void EditorDebuggerServerWebSocket::_peer_connected(int p_id, String _protocol) { + pending_peers.push_back(p_id); +} + +void EditorDebuggerServerWebSocket::_peer_disconnected(int p_id, bool p_was_clean) { + if (pending_peers.find(p_id)) + pending_peers.erase(p_id); +} + +void EditorDebuggerServerWebSocket::poll() { + server->poll(); +} + +Error EditorDebuggerServerWebSocket::start() { + int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); + Vector<String> protocols; + protocols.push_back("binary"); // compatibility with EMSCRIPTEN TCP-to-WebSocket layer. + return server->listen(remote_port, protocols); +} + +void EditorDebuggerServerWebSocket::stop() { + server->stop(); + pending_peers.clear(); +} + +bool EditorDebuggerServerWebSocket::is_active() const { + return server->is_listening(); +} + +bool EditorDebuggerServerWebSocket::is_connection_available() const { + return pending_peers.size() > 0; +} + +Ref<RemoteDebuggerPeer> EditorDebuggerServerWebSocket::take_connection() { + ERR_FAIL_COND_V(!is_connection_available(), Ref<RemoteDebuggerPeer>()); + RemoteDebuggerPeer *peer = memnew(RemoteDebuggerPeerWebSocket(server->get_peer(pending_peers[0]))); + pending_peers.pop_front(); + return peer; +} + +EditorDebuggerServerWebSocket::EditorDebuggerServerWebSocket() { + server = Ref<WebSocketServer>(WebSocketServer::create()); + int max_pkts = (int)GLOBAL_GET("network/limits/debugger/max_queued_messages"); + server->set_buffers(8192, max_pkts, 8192, max_pkts); + server->connect("client_connected", callable_mp(this, &EditorDebuggerServerWebSocket::_peer_connected)); + server->connect("client_disconnected", callable_mp(this, &EditorDebuggerServerWebSocket::_peer_disconnected)); +} + +EditorDebuggerServerWebSocket::~EditorDebuggerServerWebSocket() { + stop(); +} + +EditorDebuggerServer *EditorDebuggerServerWebSocket::create(const String &p_protocol) { + ERR_FAIL_COND_V(p_protocol != "ws://", nullptr); + return memnew(EditorDebuggerServerWebSocket); +} diff --git a/modules/websocket/editor_debugger_server_websocket.h b/modules/websocket/editor_debugger_server_websocket.h new file mode 100644 index 0000000000..81a31d8364 --- /dev/null +++ b/modules/websocket/editor_debugger_server_websocket.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* script_editor_debugger_websocket.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCRIPT_EDITOR_DEBUGGER_WEBSOCKET_H +#define SCRIPT_EDITOR_DEBUGGER_WEBSOCKET_H + +#include "modules/websocket/websocket_server.h" + +#include "editor/debugger/editor_debugger_server.h" + +class EditorDebuggerServerWebSocket : public EditorDebuggerServer { + + GDCLASS(EditorDebuggerServerWebSocket, EditorDebuggerServer); + +private: + Ref<WebSocketServer> server; + List<int> pending_peers; + +public: + static EditorDebuggerServer *create(const String &p_protocol); + + void _peer_connected(int p_peer, String p_protocol); + void _peer_disconnected(int p_peer, bool p_was_clean); + + void poll(); + Error start(); + void stop(); + bool is_active() const; + bool is_connection_available() const; + Ref<RemoteDebuggerPeer> take_connection(); + + EditorDebuggerServerWebSocket(); + ~EditorDebuggerServerWebSocket(); +}; + +#endif // SCRIPT_EDITOR_DEBUGGER_WEBSOCKET_H diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp index bbe4d6dc5b..bc9d75d327 100644 --- a/modules/websocket/emws_client.cpp +++ b/modules/websocket/emws_client.cpp @@ -142,14 +142,14 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, } var len = buffer.length*buffer.BYTES_PER_ELEMENT; - var out = Module._malloc(len); - Module.HEAPU8.set(buffer, out); + var out = _malloc(len); + HEAPU8.set(buffer, out); ccall("_esws_on_message", "void", ["number", "number", "number", "number"], [c_ptr, out, len, is_string] ); - Module._free(out); + _free(out); }); socket.addEventListener("error", function (event) { @@ -231,8 +231,9 @@ Error EMWSClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffe } EMWSClient::EMWSClient() { - _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); + _in_buf_size = DEF_BUF_SHIFT; + _in_pkt_size = DEF_PKT_SHIFT; + _is_connecting = false; _peer = Ref<EMWSPeer>(memnew(EMWSPeer)); /* clang-format off */ diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp index e389d75ffd..1ad249e1eb 100644 --- a/modules/websocket/register_types.cpp +++ b/modules/websocket/register_types.cpp @@ -40,24 +40,12 @@ #include "wsl_client.h" #include "wsl_server.h" #endif +#ifdef TOOLS_ENABLED +#include "editor/debugger/editor_debugger_server.h" +#include "editor_debugger_server_websocket.h" +#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 EMWSPeer::make_default(); EMWSClient::make_default(); @@ -72,6 +60,10 @@ void register_websocket_types() { ClassDB::register_custom_instance_class<WebSocketServer>(); ClassDB::register_custom_instance_class<WebSocketClient>(); ClassDB::register_custom_instance_class<WebSocketPeer>(); + +#ifdef TOOLS_ENABLED + EditorDebuggerServer::register_protocol_handler("ws://", EditorDebuggerServerWebSocket::create); +#endif } void unregister_websocket_types() {} diff --git a/modules/websocket/remote_debugger_peer_websocket.cpp b/modules/websocket/remote_debugger_peer_websocket.cpp new file mode 100644 index 0000000000..d156a39f53 --- /dev/null +++ b/modules/websocket/remote_debugger_peer_websocket.cpp @@ -0,0 +1,134 @@ +/*************************************************************************/ +/* script_debugger_websocket.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "remote_debugger_peer_websocket.h" + +#include "core/project_settings.h" + +Error RemoteDebuggerPeerWebSocket::connect_to_host(const String &p_uri) { + + Vector<String> protocols; + protocols.push_back("binary"); // Compatibility for emscripten TCP-to-WebSocket. + + ws_client->connect_to_url(p_uri, protocols); + ws_client->poll(); + + if (ws_client->get_connection_status() == WebSocketClient::CONNECTION_DISCONNECTED) { + + ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(ws_client->get_connection_status()) + "."); + return FAILED; + } + + ws_peer = ws_client->get_peer(1); + + return OK; +} + +bool RemoteDebuggerPeerWebSocket::is_peer_connected() { + return ws_peer.is_valid() && ws_peer->is_connected_to_host(); +} + +void RemoteDebuggerPeerWebSocket::poll() { + ws_client->poll(); + + while (ws_peer->is_connected_to_host() && ws_peer->get_available_packet_count() > 0 && in_queue.size() < max_queued_messages) { + Variant var; + Error err = ws_peer->get_var(var); + ERR_CONTINUE(err != OK); + ERR_CONTINUE(var.get_type() != Variant::ARRAY); + in_queue.push_back(var); + } + + while (ws_peer->is_connected_to_host() && out_queue.size() > 0) { + Array var = out_queue[0]; + Error err = ws_peer->put_var(var); + ERR_BREAK(err != OK); // Peer buffer full? + out_queue.pop_front(); + } +} + +int RemoteDebuggerPeerWebSocket::get_max_message_size() const { + return 8 << 20; // 8 Mib +} + +bool RemoteDebuggerPeerWebSocket::has_message() { + return in_queue.size() > 0; +} + +Array RemoteDebuggerPeerWebSocket::get_message() { + ERR_FAIL_COND_V(in_queue.size() < 1, Array()); + Array msg = in_queue[0]; + in_queue.pop_front(); + return msg; +} + +Error RemoteDebuggerPeerWebSocket::put_message(const Array &p_arr) { + if (out_queue.size() >= max_queued_messages) + return ERR_OUT_OF_MEMORY; + out_queue.push_back(p_arr); + return OK; +} + +void RemoteDebuggerPeerWebSocket::close() { + if (ws_peer.is_valid()) { + ws_peer.unref(); + } + ws_client->disconnect_from_host(); +} + +bool RemoteDebuggerPeerWebSocket::can_block() const { +#ifdef JAVASCRIPT_ENABLED + return false; +#else + return true; +#endif +} + +RemoteDebuggerPeerWebSocket::RemoteDebuggerPeerWebSocket(Ref<WebSocketPeer> p_peer) { +#ifdef JAVASCRIPT_ENABLED + ws_client = Ref<WebSocketClient>(memnew(EMWSClient)); +#else + ws_client = Ref<WebSocketClient>(memnew(WSLClient)); +#endif + max_queued_messages = (int)GLOBAL_GET("network/limits/debugger/max_queued_messages"); + ws_client->set_buffers(8192, max_queued_messages, 8192, max_queued_messages); + ws_peer = p_peer; +} + +RemoteDebuggerPeer *RemoteDebuggerPeerWebSocket::create(const String &p_uri) { + ERR_FAIL_COND_V(!p_uri.begins_with("ws://") && !p_uri.begins_with("wss://"), nullptr); + RemoteDebuggerPeerWebSocket *peer = memnew(RemoteDebuggerPeerWebSocket); + Error err = peer->connect_to_host(p_uri); + if (err != OK) { + memdelete(peer); + return nullptr; + } + return peer; +} diff --git a/modules/websocket/remote_debugger_peer_websocket.h b/modules/websocket/remote_debugger_peer_websocket.h new file mode 100644 index 0000000000..fe46533bed --- /dev/null +++ b/modules/websocket/remote_debugger_peer_websocket.h @@ -0,0 +1,66 @@ +/*************************************************************************/ +/* script_debugger_websocket.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCRIPT_DEBUGGER_WEBSOCKET_H +#define SCRIPT_DEBUGGER_WEBSOCKET_H + +#ifdef JAVASCRIPT_ENABLED +#include "modules/websocket/emws_client.h" +#else +#include "modules/websocket/wsl_client.h" +#endif +#include "core/debugger/remote_debugger_peer.h" + +class RemoteDebuggerPeerWebSocket : public RemoteDebuggerPeer { + + Ref<WebSocketClient> ws_client; + Ref<WebSocketPeer> ws_peer; + List<Array> in_queue; + List<Array> out_queue; + + int max_queued_messages; + +public: + static RemoteDebuggerPeer *create(const String &p_uri); + + Error connect_to_host(const String &p_uri); + bool is_peer_connected(); + int get_max_message_size() const; + bool has_message(); + Error put_message(const Array &p_arr); + Array get_message(); + void close(); + void poll(); + bool can_block() const; + + RemoteDebuggerPeerWebSocket(Ref<WebSocketPeer> p_peer = Ref<WebSocketPeer>()); +}; + +#endif // SCRIPT_DEBUGGER_WEBSOCKET_H diff --git a/modules/websocket/websocket_macros.h b/modules/websocket/websocket_macros.h index f7eafcff1f..cf4545b435 100644 --- a/modules/websocket/websocket_macros.h +++ b/modules/websocket/websocket_macros.h @@ -31,15 +31,9 @@ #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" +// Defaults per peer buffers, 1024 packets with a shared 65536 bytes payload. +#define DEF_PKT_SHIFT 10 +#define DEF_BUF_SHIFT 16 /* clang-format off */ #define GDCICLASS(CNAME) \ diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index 9f05500eb9..0eaafe2d66 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -336,10 +336,10 @@ Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer } WSLClient::WSLClient() { - _in_buf_size = nearest_shift((int)GLOBAL_GET(WSC_IN_BUF) - 1) + 10; - _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_IN_PKT) - 1); - _out_buf_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_BUF) - 1) + 10; - _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_PKT) - 1); + _in_buf_size = DEF_BUF_SHIFT; + _in_pkt_size = DEF_PKT_SHIFT; + _out_buf_size = DEF_BUF_SHIFT; + _out_pkt_size = DEF_PKT_SHIFT; _peer.instance(); _tcp.instance(); diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index 6f155a6ffa..cc4685973e 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -298,10 +298,10 @@ Error WSLServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer } WSLServer::WSLServer() { - _in_buf_size = nearest_shift((int)GLOBAL_GET(WSS_IN_BUF) - 1) + 10; - _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_IN_PKT) - 1); - _out_buf_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_BUF) - 1) + 10; - _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_PKT) - 1); + _in_buf_size = DEF_BUF_SHIFT; + _in_pkt_size = DEF_PKT_SHIFT; + _out_buf_size = DEF_BUF_SHIFT; + _out_pkt_size = DEF_PKT_SHIFT; _server.instance(); } diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp index 8c5525bed3..f77646ce28 100644 --- a/modules/xatlas_unwrap/register_types.cpp +++ b/modules/xatlas_unwrap/register_types.cpp @@ -137,6 +137,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver pack_options.maxChartSize = 4096; pack_options.blockAlign = true; + pack_options.padding = 1; pack_options.texelsPerUnit = 1.0 / p_texel_size; xatlas::Atlas *atlas = xatlas::Create(); |