godotengine / godot-cpp

C++ bindings for the Godot script API
MIT License
1.74k stars 574 forks source link

Use gdextension in the project, directly flash back after starting the godot engine #1234

Closed BeyondXinXin closed 1 year ago

BeyondXinXin commented 1 year ago

Godot version

4.1.1

godot-cpp version

(tags) godot-4.1.1-stable

System information

win64

Issue description

Wrote a simple link library using GDExtension After launching the game for the first time using the Godot engine, the engine crashes.

First time means: delete .goidot

Error message:

C:\Users\77935\Downloads\Godot_v4.1.1-stable_win64.exe>C:\Users\77935\Downloads\Godot_v4.1.1-stable_win64.exe\Godot_v4.1.1-stable_win64_console.exe
Godot Engine v4.1.1.stable.official.bd6af8e0e - https://godotengine.org
OpenGL API 3.3.0 NVIDIA 532.09 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce RTX 3050 Ti Laptop GPU

Editing project: F:/github/extension_test/game
Godot Engine v4.1.1.stable.official.bd6af8e0e - https://godotengine.org
OpenGL API 3.3.0 NVIDIA 532.09 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce RTX 3050 Ti Laptop GPU

ERROR: Condition "!texture_allocs_cache.has(p_id)" is true.
   at: texture_free_data (drivers/gles3/storage/utilities.h:111)
ERROR: Cubemap Arrays are not supported in the GL Compatibility backend.
   at: (drivers/gles3/storage/texture_storage.cpp:763)
ERROR: Attempting to use an uninitialized RID
   at: (./core/templates/rid_owner.h:199)
ERROR: Condition "!t" is true.
   at: texture_free (drivers/gles3/storage/texture_storage.cpp:705)
ERROR: FATAL: Condition "!exists" is true.
   at: operator[] (./core/templates/hash_map.h:504)

Preceding fewERROR not important, last ERROR: FATAL Flashback.


However, after Flashback, as long as it is not deleted .goidot.Everything is normal,library Work according to my expectations

image

Steps to reproduce

Open engine, load project, engine prompt ERROR: FATAL: Condition "!exists" is true.

Minimal reproduction project

extension_test.zip

code

extends Node2D
@onready var navigation_region: GridBasedNavigationRegion2D = $GridBasedNavigationRegion2D
@onready var tileMap: TileMap = $TileMap

func _ready() -> void:
    print("ready")
    navigation_region.set_tile_map(get_node("TileMap"))
    call_deferred("_custom_setup")

func _custom_setup():
    print("_custom_setup")
    navigation_region.add_blocking_nav_layer(1)
    navigation_region.spawn_navigation_mesh()
#include "grid_based_navigation_region2d.h"

#include "godot_cpp/classes/global_constants.hpp"
#include "godot_cpp/classes/label.hpp"
#include "godot_cpp/core/class_db.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
#include "godot_cpp/classes/tile_data.hpp"
#include "godot_cpp/classes/navigation_polygon.hpp"

namespace godot {
    void GridBasedNavigationRegion2D::_bind_methods()
    {
        ClassDB::bind_method(D_METHOD("set_debugging_mode"), &GridBasedNavigationRegion2D::set_debugging_mode);

        ClassDB::bind_method(D_METHOD("set_tile_map", "tile_map"), &GridBasedNavigationRegion2D::set_tile_map);
        ClassDB::bind_method(D_METHOD("add_blocking_nav_layer"), &GridBasedNavigationRegion2D::add_blocking_nav_layer);

        ClassDB::bind_method(D_METHOD("spawn_navigation_mesh"), &GridBasedNavigationRegion2D::spawn_navigation_mesh);
        ClassDB::bind_method(D_METHOD("refresh_navigation_mesh"), &GridBasedNavigationRegion2D::refresh_navigation_mesh);

    }

    GridBasedNavigationRegion2D::GridBasedNavigationRegion2D()
    {
        UtilityFunctions::print("GridBasedNavigationRegion2D");
    }

    GridBasedNavigationRegion2D::~GridBasedNavigationRegion2D()
    {
        UtilityFunctions::print("~GridBasedNavigationRegion2D");
    }

    void GridBasedNavigationRegion2D::set_debugging_mode()
    {
        debug = true;
    }

    void GridBasedNavigationRegion2D::set_tile_map(TileMap* _tile_map)
    {
        tile_map = _tile_map;
    }

    void GridBasedNavigationRegion2D::add_blocking_nav_layer(int layer)
    {
        using namespace std;
        blocking_nav_layers.push_back(layer);
        sort(blocking_nav_layers.begin(), blocking_nav_layers.end());
        blocking_nav_layers.erase(unique(blocking_nav_layers.begin(), blocking_nav_layers.end()), blocking_nav_layers.end());
    }

    void GridBasedNavigationRegion2D::spawn_navigation_mesh()
    {
        using namespace Clipper2Lib;
        using namespace std;

        Paths64 nav_paths = get_tile_map_nav_paths();
        Ref<NavigationPolygon> polygon = get_navigation_polygon();
        polygon->clear_outlines();

        for (const Path64& nav_path : nav_paths) {
            PackedVector2Array shape_outline;
            for (const Point64& scaled_point : nav_path) {
                shape_outline.push_back(Point2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y)));
            }
            polygon->add_outline(shape_outline);
            polygon->make_polygons_from_outlines();
        }

        set_navigation_polygon(polygon);
    }

    void GridBasedNavigationRegion2D::refresh_navigation_mesh()
    {
    }

    Clipper2Lib::Path64 GridBasedNavigationRegion2D::get_tile_map_nav_path(const int& tile_map_layer, const Vector2i& tile, const int& nav_layer)
    {
        using namespace Clipper2Lib;
        using namespace std;

        Vector2 tile_region = tile_map->map_to_local(tile);
        Transform2D tile_transform = Transform2D(Vector2(1, 0), Vector2(0, 1), -tile_region);
        Ref<NavigationPolygon> polygon = tile_map->get_cell_tile_data(tile_map_layer, tile)->get_navigation_polygon(nav_layer);
        if (polygon.is_null()) {
            return Path64();
        }
        if (polygon->get_outline_count() != 1) {
            return Path64();
        }
        PackedVector2Array polygon_bp = polygon->get_outline(0);
        PackedVector2Array polygon_tile = polygon_bp * tile_transform;
        const Vector2* r = polygon_tile.ptr();
        Path64 result;
        for (int j = 0; j < polygon_tile.size(); j++) {
            const Vector2& vertex = r[j];
            const Point64& point = Point64(vertex.x, vertex.y);
            result.push_back(point);
        }
        return result;
    }

    void GridBasedNavigationRegion2D::print_clipper2_path(Clipper2Lib::Path64 path)
    {
        if (!debug || path.size() < 3) {
            return;
        }
        String str = "xxx.push_back(Path64{";
        for (int i = 0; i < path.size() - 2; i++) {
            str += "Point64(", path[0].x, ",", path[0].y, "),";
        }
        str += "Point64(", path[path.size() - 1].x, ",", path[path.size() - 1].y, ")";
        str += "});";
        UtilityFunctions::print(str);
    }

    Clipper2Lib::Paths64 GridBasedNavigationRegion2D::get_tile_map_nav_paths()
    {
        using namespace Clipper2Lib;
        using namespace std;

        Paths64 result;

        TypedArray<Vector2i> tiles = tile_map->get_used_cells(0);
        for (int tile_i = 0; tile_i < tiles.size(); tile_i++) {
            Vector2i tile = tiles[tile_i];
            paths_nav_bg.push_back(get_tile_map_nav_path(0, tile, 0));
        }

        for (const int& tile_map_layer : blocking_nav_layers) {
            TypedArray<Vector2i> tiles = tile_map->get_used_cells(tile_map_layer);
            for (int tile_i = 0; tile_i < tiles.size(); tile_i++) {
                Vector2i tile = tiles[tile_i];
                paths_blocking_bg.push_back(get_tile_map_nav_path(tile_map_layer, tile, 0));
            }
        }

        UtilityFunctions::print("paths_nav_bg");
        for (Path64 path : paths_nav_bg) {
            print_clipper2_path(path);
        }

        UtilityFunctions::print("paths_blocking_bg");
        for (Path64 path : paths_blocking_bg) {
            print_clipper2_path(path);
        }

        paths_blocking_bg = Union(paths_blocking_bg, FillRule::NonZero);
        paths_blocking_bg = SimplifyPaths(paths_blocking_bg, 0.25);

        paths_nav_bg = Union(paths_nav_bg, FillRule::NonZero);
        paths_nav_bg = SimplifyPaths(paths_nav_bg, 0.25);

        result = Difference(paths_nav_bg, paths_blocking_bg, FillRule::NonZero);
        result = SimplifyPaths(result, 0.25);

        return result;
    }
}
BeyondXinXin commented 1 year ago

I tried to compile godot4.1.1 by myself. When I ran it for the first time, I got more detailed errors, and I still couldn't locate the problem.

Godot Engine v4.1.1.stable.custom_build.bd6af8e0e - https://godotengine.org
OpenGL API 3.3.0 NVIDIA 532.09 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce RTX 3050 Ti Laptop GPU

Editing project: F:/github/extension_test/game
Godot Engine v4.1.1.stable.custom_build.bd6af8e0e - https://godotengine.org
OpenGL API 3.3.0 NVIDIA 532.09 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce RTX 3050 Ti Laptop GPU

ERROR: Condition "!texture_allocs_cache.has(p_id)" is true.
   at: GLES3::Utilities::texture_free_data (F:\godot\drivers\gles3\storage\utilities.h:111)
ERROR: Cubemap Arrays are not supported in the GL Compatibility backend.
   at: (drivers\gles3\storage\texture_storage.cpp:763)
ERROR: Attempting to use an uninitialized RID
   at: (F:\godot\core/templates/rid_owner.h:199)
ERROR: Condition "!t" is true.
   at: GLES3::TextureStorage::texture_free (drivers\gles3\storage\texture_storage.cpp:705)
ERROR: FATAL: Condition "!exists" is true.
   at: HashMap<class StringName,int,struct HashMapHasherDefault,struct HashMapComparatorDefault<class StringName>,class DefaultTypedAllocator<struct HashMapElement<class StringName,int> > >::operator [] (F:\godot\core/templates/hash_map.h:504)

================================================================
CrashHandlerException: Program crashed
Engine version: Godot Engine v4.1.1.stable.custom_build (bd6af8e0ea69167dd0627f3bd54f9105bda0f8b5)
Dumping the backtrace. Please include this when reporting the bug to the project developer.
[0] HashMap<StringName,int,HashMapHasherDefault,HashMapComparatorDefault<StringName>,DefaultTypedAllocator<HashMapElement<StringName,int> > >::operator[] (F:\godot\core\templates\hash_map.h:504)
[1] HashMap<StringName,int,HashMapHasherDefault,HashMapComparatorDefault<StringName>,DefaultTypedAllocator<HashMapElement<StringName,int> > >::operator[] (F:\godot\core\templates\hash_map.h:504)
[2] GDScriptByteCodeGenerator::write_assign_with_conversion (F:\godot\modules\gdscript\gdscript_byte_codegen.cpp:889)
[3] GDScriptCompiler::_parse_function (F:\godot\modules\gdscript\gdscript_compiler.cpp:2234)
[4] GDScriptCompiler::_compile_class (F:\godot\modules\gdscript\gdscript_compiler.cpp:2802)
[5] GDScriptCompiler::compile (F:\godot\modules\gdscript\gdscript_compiler.cpp:2962)
[6] GDScript::reload (F:\godot\modules\gdscript\gdscript.cpp:787)
[7] GDScriptCache::get_full_script (F:\godot\modules\gdscript\gdscript_cache.cpp:307)
[8] ResourceFormatLoaderGDScript::load (F:\godot\modules\gdscript\gdscript.cpp:2701)
[9] ResourceLoader::_load (F:\godot\core\io\resource_loader.cpp:260)
[10] ResourceLoader::_thread_load_function (F:\godot\core\io\resource_loader.cpp:318)
[11] ResourceLoader::_load_start (F:\godot\core\io\resource_loader.cpp:498)
[12] ResourceLoader::load (F:\godot\core\io\resource_loader.cpp:414)
[13] EditorFileSystem::_update_script_classes (F:\godot\editor\editor_file_system.cpp:1592)
[14] EditorFileSystem::_update_pending_script_classes (F:\godot\editor\editor_file_system.cpp:1623)
[15] EditorFileSystem::reimport_files (F:\godot\editor\editor_file_system.cpp:2335)
[16] EditorFileSystem::_update_scan_actions (F:\godot\editor\editor_file_system.cpp:690)
[17] EditorFileSystem::_notification (F:\godot\editor\editor_file_system.cpp:1282)
[18] EditorFileSystem::_notificationv (F:\godot\editor\editor_file_system.h:146)
[19] Object::notification (F:\godot\core\object\object.cpp:798)
[20] SceneTree::_process_group (F:\godot\scene\main\scene_tree.cpp:949)
[21] SceneTree::_process (F:\godot\scene\main\scene_tree.cpp:1026)
[22] SceneTree::process (F:\godot\scene\main\scene_tree.cpp:510)
[23] Main::iteration (F:\godot\main\main.cpp:3425)
[24] OS_Windows::run (F:\godot\platform\windows\os_windows.cpp:1479)
[25] widechar_main (F:\godot\platform\windows\godot_windows.cpp:182)
[26] _main (F:\godot\platform\windows\godot_windows.cpp:204)
[27] main (F:\godot\platform\windows\godot_windows.cpp:218)
[28] WinMain (F:\godot\platform\windows\godot_windows.cpp:232)
[29] __scrt_common_main_seh (D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288)
[30] <couldn't map PC to fn name>
-- END OF BACKTRACE --
================================================================
dsnopek commented 1 year ago

Thanks for the report!

Hm, so it looks like the fatal error stems from GDScriptByteCodeGenerator::write_assign_with_conversion().

Can you try commenting out different parts of your GDScript code until you can narrow down which specific bit is leading to this error? Perhaps GDScript is having issues converting to/from a type from GDExtension?

BeyondXinXin commented 1 year ago

@dsnopek Hi. Thanks for the suggestion, I found the problem.

Reason

I follow the Static typing rules when writing scripts

@onready var navigation_region: GridBasedNavigationRegion2D = $GridBasedNavigationRegion2D

I try to understand the loading logic of GDExtension briefly: When I open the project from the project manager:

  1. If it exists .godot/extension_list.cfg GDExtensionManager will load the dll and register the class written by itself as NATIVE in GDscript.
  2. Compile all *.gd scripts.
  3. Find and parse the new *.gdextension resources.
  4. Again GDExtensionManager will load the dll and register the class written by itself as NATIVE in GDscript.

or

  1. Not present .godot/extension_list.cfg.
  2. Compile all *.gd scripts
  3. Find and parse all *.gdextension resources.
  4. FirstGDExtensionManager will load the dll and register the class written by itself as NATIVE in GDscript.

When the *.gd script is compiled without registration, the program crashes.

Repair method

When write_assign_with_conversion judging whether the type is registered I don't know how to prompt the error properly. Perhaps: Regardless of the existence of .godot/extension_list.cfg, all resources in *.gdextension should be parsed before starting to compile scripts.

image

void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_target, const Address &p_source) {
    switch (p_target.type.kind) {
        case GDScriptDataType::BUILTIN: {
                    ...
        } break;
        case GDScriptDataType::NATIVE: {
            if (!GDScriptLanguage::get_singleton()->get_global_map().has(p_target.type.native_type)) {
                ERR_PRINT(vformat(R"(Compiler Could not find type "%s" in the current scope.)", p_target.type.native_type));
                break;
            }
            int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type];
            Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx];
            class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
            append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE);
            append(p_target);
            append(p_source);
            append(class_idx);
        } break;
dsnopek commented 1 year ago

Great catch, thanks!

If you don't feel confident making a PR, perhaps at least make an issue?

The problem definitely does appear to be on the Godot side. There could be a bug with when the order of when GDExtensions are loaded, and does really seem like there's a GDScript bug too, given that it should probably handle a missing class more gracefully than crashing the whole engine.

BeyondXinXin commented 1 year ago

Okay, I'll close this and submit an issue to Godot