diff --git a/.gitignore b/.gitignore index 1897f0e2..e4c16424 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ cmake_build_out/* src/gen/* src/editor/my_telemetry_modules addons/debug_draw_3d/gen/ +addons/debug_draw_3d/native_api/ clang_rt.asan_* docs/images/classes/temp/ SUCCESS diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 2d9b4dfd..3a8120a1 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -18,6 +18,7 @@ "TYPED_METHOD_BIND", "HOT_RELOAD_ENABLED", "TOOLS_ENABLED", + "NATIVE_API_ENABLED", "TELEMETRY_ENABLED", "TELEMETRY_PROJECT_DD3D" ], diff --git a/SConstruct b/SConstruct index 61c1ff6c..86e45f6e 100644 --- a/SConstruct +++ b/SConstruct @@ -5,7 +5,7 @@ from SCons.Script.SConscript import SConsEnvironment import SCons, SCons.Script import sys, os, platform -import lib_utils, lib_utils_external +import lib_utils, lib_utils_external, lib_utils_gen_dd3d_api # Fixing the encoding of the console if platform.system() == "Windows": @@ -45,6 +45,8 @@ def setup_options(env: SConsEnvironment, arguments): ) ) + opts.Add(BoolVariable("build_cpp_api_tests", "Build only cpp api tests", False)) + opts.Add(BoolVariable("native_api_enabled", "Enable the native API module", True)) opts.Add(BoolVariable("telemetry_enabled", "Enable the telemetry module", False)) opts.Add(BoolVariable("tracy_enabled", "Enable tracy profiler", False)) opts.Add(BoolVariable("force_enabled_dd3d", "Keep the rendering code in the release build", False)) @@ -55,12 +57,17 @@ def setup_options(env: SConsEnvironment, arguments): # Additional compilation flags -def setup_defines_and_flags(env: SConsEnvironment, src_out): +def setup_defines_and_flags(env: SConsEnvironment, src_out: list): # Add more sources to `src_out` if needed if "release" in env["target"] and not env["force_enabled_dd3d"]: env.Append(CPPDEFINES=["DISABLE_DEBUG_RENDERING"]) + if env["native_api_enabled"]: + env.Append(CPPDEFINES=["NATIVE_API_ENABLED"]) + if not COMMAND_LINE_TARGETS: + gen_apis(None, None, env, src_out) + if env["telemetry_enabled"]: tele_src = "editor/my_telemetry_modules/GDExtension/usage_time_reporter.cpp" if os.path.exists(os.path.join(src_folder, tele_src)): @@ -103,7 +110,7 @@ def setup_defines_and_flags(env: SConsEnvironment, src_out): print() -def generate_sources_for_resources(env, src_out): +def generate_sources_for_resources(env: SConsEnvironment, src_out: list): # Array of (path, is_text) editor_files = [ ("images/icon_3d_32.png", False), @@ -132,6 +139,10 @@ def apply_patches(target, source, env: SConsEnvironment): return lib_utils_external.apply_git_patches(env, patches_to_apply, "godot-cpp") +def gen_apis(target, source, env: SConsEnvironment, src_out: list = []): + return lib_utils_gen_dd3d_api.gen_apis(env, "src/native_api/templates/c_api.cpp", os.path.join(os.path.dirname(env["addon_output_dir"]), "native_api"), src_out) + + def get_android_toolchain() -> str: sys.path.insert(0, "godot-cpp/tools") import android @@ -170,10 +181,35 @@ extra_tags = "" if "release" in env["target"] and env["force_enabled_dd3d"]: extra_tags += ".enabled" -lib_utils.get_library_object( - env, project_name, lib_name, extra_tags, env["addon_output_dir"], src_folder, additional_src -) +if not env["build_cpp_api_tests"]: + lib_utils.get_library_object( + env, project_name, lib_name, extra_tags, env["addon_output_dir"], src_folder, additional_src + ) # Register console commands env.Command("apply_patches", [], apply_patches) +env.Command("gen_apis", [], gen_apis) # env.Command("build_cmake", [], build_cmake) + + +## CPP API TESTS + +if env["build_cpp_api_tests"]: + tests_src_folder = "tests_native_api/cpp" + env.Append(CPPPATH=src_folder) + env["SHOBJPREFIX"] = "#obj/" + additional_tags = "" + if env["platform"] == "web" and env.get("threads", True): + additional_tags = ".threads" + + lib_filename = "lib{}.{}.{}.{}{}".format( + "dd3d_cpp_api", env["platform"], env["target"], env["arch"], additional_tags + ) + lib_filename = os.path.join(tests_src_folder, "addon_cpp_api_test", "libs", lib_filename + env["SHLIBSUFFIX"]) + + shbin = env.Default( + env.SharedLibrary( + target=env.File(lib_filename), + source=lib_utils.get_sources(env.Glob(os.path.join(tests_src_folder, "*.cpp")), "", "test_c_api"), + ) + ) diff --git a/examples_dd3d/DebugDrawDemoScene.gd b/examples_dd3d/DebugDrawDemoScene.gd index 6d560aec..6bbb962f 100644 --- a/examples_dd3d/DebugDrawDemoScene.gd +++ b/examples_dd3d/DebugDrawDemoScene.gd @@ -461,7 +461,7 @@ func _draw_array_of_boxes(): cfg.set_thickness(randf_range(0, 0.1)) #var size = Vector3(randf_range(0.1, 100),randf_range(0.1, 100),randf_range(0.1, 100)) DebugDraw3D.draw_box(Vector3(x * mul, (-4-z) * mul, y * mul), Quaternion.IDENTITY, size, DebugDraw3D.empty_color, false, cubes_max_time) - #print("Draw Cubes: %fms" % ((Time.get_ticks_usec() - _start_time) / 1000.0)) + print("Draw Cubes: %fms" % ((Time.get_ticks_usec() - _start_time) / 1000.0)) timer_cubes = cubes_max_time diff --git a/lib_utils.py b/lib_utils.py index 8b42a9a6..4a60d55a 100644 --- a/lib_utils.py +++ b/lib_utils.py @@ -91,6 +91,7 @@ def read_all_text(file_path: str) -> str | None: def write_all_text(file_path: str, text: str) -> bool: + os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, "w+", encoding="utf-8") as file: file.write(text) return True diff --git a/lib_utils_gen_dd3d_api.py b/lib_utils_gen_dd3d_api.py new file mode 100644 index 00000000..62293646 --- /dev/null +++ b/lib_utils_gen_dd3d_api.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 + +from SCons.Script.SConscript import SConsEnvironment +from patches import unity_tools + +import SCons +import os, shutil, json, re, hashlib +import lib_utils + +def insert_lines_at_mark(lines: list, mark: str, insert_lines: list): + insert_mark = mark + insert_index = -1 + for idx, line in enumerate(lines): + if line.endswith(mark): + insert_index = idx + break + lines.pop(insert_index) + lines[insert_index:insert_index] = insert_lines + + +def get_api_functions(headers: list) -> dict: + classes = {} + + for header in headers: + print(header) + text_data = lib_utils.read_all_text(header) + lines = text_data.replace("\r\n", "\n").replace("\r", "\n").split("\n") + lines = [line.strip() for line in lines if len(line.strip())] + + functions = {} + current_class = "" + + is_singleton = False + is_refcounted = False + + for idx, line in enumerate(lines): + docs = [] + if lines[idx - 1] == "*/": + doc_idx = idx - 1 + while lines[doc_idx] != "/**": + doc_idx -= 1 + docs = [line.replace("*", "", 1).strip() if line.startswith("*") else line for line in lines[doc_idx+1:idx-1]] + + class_prefixes = ["NAPI_CLASS ", "NAPI_CLASS_SINGLETON ", "NAPI_CLASS_REF "] + class_prefix = "" + for p in class_prefixes: + if line.startswith(p): + class_prefix = p + break + if len(class_prefix): + is_singleton = line.startswith("NAPI_CLASS_SINGLETON ") + is_refcounted = line.startswith("NAPI_CLASS_REF ") + current_class = re.search(r"\w+", line[len(class_prefix):])[0].strip() + + functions = {} + classes[current_class] = {"functions": functions, "singleton": is_singleton, "refcounted": is_refcounted, "docs": docs} + continue + + if line.startswith("NAPI "): + func_name_match = re.search(r'\b(\w+)\s*\(', line) + fun_name = func_name_match.group(1).strip() + ret_type = line[:line.index(fun_name)].replace("NAPI ","").strip() + + is_self_return = False + if is_refcounted: + if ret_type == f"Ref<{current_class}>": + ret_type = "void" + is_self_return = True + + args_str = line[line.find("(") + 1 : line.rfind(")")] + args = [] + + if not is_singleton: + args.append("void *inst") + + if len(args_str): + #args = [a.strip() for a in args] + tmp_args_str = args_str + while len(tmp_args_str): + found_comma = False + nesting = 0 + for idx, c in enumerate(tmp_args_str): + if c in ["(", "[", "{", "<"]: + nesting += 1 + if c in [")", "]", "}", ">"]: + nesting -= 1 + if nesting == 0 and c == ",": + args.append(tmp_args_str[:idx].strip()) + tmp_args_str = tmp_args_str[idx + 1:] + found_comma = True + break + if nesting < 0: + raise Exception("There are more closing brackets than opening ones:\n" + tmp_args_str) + if nesting > 0: + raise Exception("There are more opening brackets than closing ones:\n" + tmp_args_str) + + if not found_comma: + args.append(tmp_args_str) + break + + args_dict = [] + for a in args: + arg_name_match = re.search(r'\b(\w+)\s*=', a) + if arg_name_match: + new_dict = { + "name": arg_name_match.group(1).strip(), + "type": a[:a.index(arg_name_match.group(1))].strip(), + "default": a[a.find("=") + 1:].strip() + } + new_dict["c_type"] = new_dict["type"].replace("&", "").strip() + args_dict.append(new_dict) + else: + arg_name_match = re.search(r'\b(\w+)$', a) + new_dict = { + "name": arg_name_match.group(1).strip(), + "type": a[:a.index(arg_name_match.group(1))].strip() + } + new_dict["c_type"] = new_dict["type"].replace("&", "").strip() + args_dict.append(new_dict) + + fun_dict = {"return": ret_type, "self_return": is_self_return, "args": args_dict, "name": fun_name, "docs": docs} + functions[f"{current_class}_{fun_name}"] = fun_dict + return classes + + +def generate_native_api(c_api_template: str, out_folder: str, src_out: list) -> dict: + classes = get_api_functions([ + "src/3d/config_scope_3d.h", + "src/3d/debug_draw_3d.h" + ]) + + new_funcs = [] + new_func_regs = [] + for cls in classes: + is_singleton = classes[cls]["singleton"] + is_refcounted = classes[cls]["refcounted"] + functions = classes[cls]["functions"] + for func_name in functions: + func = functions[func_name] + func_orig_name = func["name"] + ret_type = func["return"] + + if is_refcounted: + if ret_type == "self": + ret_type = "void" + args = func["args"] + + new_funcs.append(f"{ret_type} {func_name}({", ".join([f"{a["type"]} {a["name"]}" for a in args])}) {{") + if is_singleton: + if ret_type != "void": + new_funcs.append(f"\treturn {cls}::get_singleton()->{func_orig_name}({", ".join([a["name"] for a in args])});") + else: + new_funcs.append(f"\t{cls}::get_singleton()->{func_orig_name}({", ".join([a["name"] for a in args])});") + new_funcs.append(f"}}") + new_funcs.append("") + + new_func_regs.append(f"\t\tADD_FUNC({func_name});") + + c_api_temp_data = lib_utils.read_all_text(c_api_template) + c_api_lines = c_api_temp_data.replace("\r\n", "\n").replace("\r", "\n").split("\n") + + insert_lines_at_mark(c_api_lines, "// GENERATOR_DD3D_FUNCTIONS_DEFINES", new_funcs) + insert_lines_at_mark(c_api_lines, "// GENERATOR_DD3D_FUNCTIONS_REGISTERS", new_func_regs) + c_api_file_name, c_api_file_ext = os.path.splitext(os.path.basename(c_api_template)) + + c_api_gen_path = os.path.join("gen", f"{c_api_file_name}.gen{c_api_file_ext}") + lib_utils.write_all_text(os.path.join("src", c_api_gen_path), "\n".join(c_api_lines)) + src_out.append(c_api_gen_path) + + api_dict = {"hash": hashlib.sha1(json.dumps(classes, sort_keys=True).encode()).hexdigest(), "classes": classes} + lib_utils.write_all_text(os.path.join(out_folder, "api.json"), json.dumps(api_dict, indent=" ")) + + return api_dict + + +def gen_cpp_api(env: SConsEnvironment, api: dict, out_folder: str, additional_include_classes: list = []) -> bool: + os.makedirs(out_folder, exist_ok=True) + classes = api["classes"] + namespaces = {} + + for cls in classes: + functions = classes[cls]["functions"] + + for func_name in functions: + func: dict = functions[func_name] + func_orig_name = func["name"] + ret = func["return"] + args = func["args"] + + if cls not in namespaces: + namespaces[cls] = [] + + def get_default_ret_val(ret_type: str): + if ret_type.endswith("*") or ret_type.startswith("Ref<"): + return "nullptr" + + return "{}" + + docs = func["docs"] + if len(docs): + docs = [" * " + line for line in docs] + docs.insert(0, "/**") + docs.insert(len(docs), " */") + + new_lines = docs + new_lines.append(f"static {ret} {func_orig_name}({", ".join([f"{a["type"]} {a["name"]}" for a in args])}) {{") + new_lines.append(f"\tstatic {ret}(*{func_name})({", ".join([a["type"] for a in args])}) = nullptr;") + def_ret_val = get_default_ret_val(ret) + call_args = ", ".join([a["name"] for a in args]) + if ret != "void": + new_lines.append(f"\tLOAD_AND_CALL_FUNC_POINTER_RET({func_name}, {def_ret_val}{", " if len(args) else ""}{call_args});") + else: + new_lines.append(f"\tLOAD_AND_CALL_FUNC_POINTER({func_name}, {call_args});") + new_lines.append(f"}}") + new_lines.append("") + + namespaces[cls] += new_lines + + + shutil.copyfile("src/native_api/c_api_shared.hpp", os.path.join(out_folder, "c_api_shared.hpp")) + text_data = lib_utils.read_all_text("src/native_api/templates/cpp/dd3d_cpp_api.hpp") + lines = text_data.replace("\r\n", "\n").replace("\r", "\n").split("\n") + + result_arr = [""] + for key in namespaces: + docs:list = classes[key]["docs"] + if len(docs): + docs = [" * " + line for line in docs] + docs.insert(0, "/**") + docs.insert(len(docs), " */") + result_arr = result_arr + docs + result_arr.append(f"namespace {key} {{") + result_arr += namespaces[key] + result_arr.append(f"}} // namespace {key}") + result_arr.append("") + + insert_lines_at_mark(lines, "// GENERATOR_DD3D_API_INCLUDES", [f"#include " for i in additional_include_classes]) + insert_lines_at_mark(lines, "// GENERATOR_DD3D_API_FUNCTIONS", result_arr) + return lib_utils.write_all_text(os.path.join(out_folder, "dd3d_cpp_api.hpp"), "\n".join(lines)) + + +def gen_apis(env: SConsEnvironment, c_api_template: str, out_folder: str, src_out: list): + print("Generating native API!") + os.makedirs(out_folder, exist_ok=True) + + api = generate_native_api(c_api_template, out_folder, src_out) + if api == None: + print("Couldn't get the Native API") + return 110 + + if not gen_cpp_api(env, api, os.path.join(out_folder, "cpp"), [ + "camera3d" + ]): + return 111 + return 0 diff --git a/src/3d/config_scope_3d.h b/src/3d/config_scope_3d.h index 7e80ed73..9707d271 100644 --- a/src/3d/config_scope_3d.h +++ b/src/3d/config_scope_3d.h @@ -1,6 +1,7 @@ #pragma once #include "utils/compiler.h" +#include "utils/native_api_hooks.h" #include #include @@ -55,7 +56,7 @@ using namespace godot; * DebugDraw3D.DrawCameraFrustum(dCamera, Colors.DarkOrange); * ``` */ -class DebugDraw3DScopeConfig : public RefCounted { +NAPI_CLASS_REF DebugDraw3DScopeConfig : public RefCounted { GDCLASS(DebugDraw3DScopeConfig, RefCounted) protected: @@ -103,40 +104,42 @@ class DebugDraw3DScopeConfig : public RefCounted { std::shared_ptr data = nullptr; /// @private - // It can be used for example in C# - void _manual_unregister(); + /** + * It can be used for example in C# + */ + NAPI void _manual_unregister(); /** * Set the thickness of the volumetric lines. If the value is 0, the standard wireframe rendering will be used. * * ![](docs/images/classes/LineThickness.webp) */ - Ref set_thickness(real_t _value) const; - real_t get_thickness() const; + NAPI Ref set_thickness(real_t _value) const; + NAPI real_t get_thickness() const; /** * Set the brightness of the central part of the volumetric lines. * * ![](docs/images/classes/LineCenterBrightness.webp) */ - Ref set_center_brightness(real_t _value) const; - real_t get_center_brightness() const; + NAPI Ref set_center_brightness(real_t _value) const; + NAPI real_t get_center_brightness() const; /** * Set the mesh density of the sphere * * ![](docs/images/classes/SphereDensity.webp) */ - Ref set_hd_sphere(bool _value) const; - bool is_hd_sphere() const; + NAPI Ref set_hd_sphere(bool _value) const; + NAPI bool is_hd_sphere() const; /** * Set the size of the `Plane` in DebugDraw3D.draw_plane. If set to `INF`, the `Far` parameter of the current camera will be used. * * ![](docs/images/classes/PlaneSize.webp) */ - Ref set_plane_size(real_t _value) const; - real_t get_plane_size() const; + NAPI Ref set_plane_size(real_t _value) const; + NAPI real_t get_plane_size() const; /** * Set which Viewport will be used to get World3D. @@ -148,8 +151,8 @@ class DebugDraw3DScopeConfig : public RefCounted { * @note * Objects created for a specific Viewport will use only one camera related to that Viewport for culling. */ - Ref set_viewport(Viewport *_value) const; - Viewport *get_viewport() const; + NAPI Ref set_viewport(godot::Viewport * _value) const; + NAPI godot::Viewport *get_viewport() const; /** * Set whether the `depth_test_disabled` flag is added or not in the shaders of the debug shapes. @@ -159,13 +162,12 @@ class DebugDraw3DScopeConfig : public RefCounted { * * ![](docs/images/classes/NoDepthTest.webp) */ - Ref set_no_depth_test(bool _value) const; - bool is_no_depth_test() const; + NAPI Ref set_no_depth_test(bool _value) const; + NAPI bool is_no_depth_test() const; /// @private DebugDraw3DScopeConfig(); - // `DDScopedConfig3D` is passed as Ref to avoid a random unreference /// @private DebugDraw3DScopeConfig(const uint64_t &p_thread_id, const uint64_t &p_guard_id, const std::shared_ptr &p_parent, const unregister_func p_unreg); ~DebugDraw3DScopeConfig(); diff --git a/src/3d/debug_draw_3d.h b/src/3d/debug_draw_3d.h index c22af4f9..8b7e255c 100644 --- a/src/3d/debug_draw_3d.h +++ b/src/3d/debug_draw_3d.h @@ -4,6 +4,7 @@ #include "common/i_scope_storage.h" #include "config_scope_3d.h" #include "render_instances_enums.h" +#include "utils/native_api_hooks.h" #include "utils/profiler.h" #include @@ -113,7 +114,7 @@ class _DD3D_WorldWatcher : public Node3D { * Due to the way Godot registers this addon, it is not possible to use the `draw_` methods * in the first few frames immediately after the project is launched. */ -class DebugDraw3D : public Object, public IScopeStorage { +NAPI_CLASS_SINGLETON DebugDraw3D : public Object, public IScopeStorage { GDCLASS(DebugDraw3D, Object) friend DebugDrawManager; @@ -273,7 +274,7 @@ class DebugDraw3D : public Object, public IScopeStorage get_render_stats_for_world(Viewport *viewport); + Ref get_render_stats_for_world(godot::Viewport *viewport); #ifndef DISABLE_DEBUG_RENDERING #define FAKE_FUNC_IMPL @@ -315,17 +316,17 @@ class DebugDraw3D : public Object, public IScopeStorage &lines, const Color &color = Colors::empty_color, const real_t &duration = 0) FAKE_FUNC_IMPL; + void draw_lines_c(const std::vector &lines, const godot::Color &color = Colors::empty_color, const real_t &duration = 0) FAKE_FUNC_IMPL; /** * Draw a single line * @@ -495,7 +496,7 @@ class DebugDraw3D : public Object, public IScopeStorage &planes, const Color &color = Colors::empty_color, const real_t &duration = 0) FAKE_FUNC_IMPL; + void draw_camera_frustum_planes_c(const std::array &planes, const godot::Color &color = Colors::empty_color, const real_t &duration = 0) FAKE_FUNC_IMPL; /** * Draw camera frustum area. * @@ -714,7 +715,7 @@ class DebugDraw3D : public Object, public IScopeStorage false + false @@ -384,6 +385,9 @@ false + + + false diff --git a/src/dev_debug_draw_3d_Library.vcxproj.filters b/src/dev_debug_draw_3d_Library.vcxproj.filters index b860016e..d176d25d 100644 --- a/src/dev_debug_draw_3d_Library.vcxproj.filters +++ b/src/dev_debug_draw_3d_Library.vcxproj.filters @@ -3,27 +3,12 @@ + + api + 3d - - thirdparty\tracy\public - - - gen - - - gen - - - editor - - - editor - - - editor - 3d @@ -42,6 +27,24 @@ 3d + + thirdparty\tracy\public + + + gen + + + gen + + + editor + + + editor + + + editor + utils @@ -70,26 +73,17 @@ - - 3d - - - thirdparty\tracy\public\tracy - - - gen + + api - - gen - - - editor + + api\include - - editor + + api\include - - editor + + 3d 3d @@ -100,6 +94,9 @@ 3d + + 3d + 3d @@ -109,6 +106,24 @@ 3d + + thirdparty\tracy\public\tracy + + + gen + + + gen + + + editor + + + editor + + + editor + utils @@ -145,50 +160,53 @@ common - - 3d - - - - + 3d - + + + + + {fc8e1629-cc43-44d0-9828-57702d1afb86} + + + {5441e933-52ce-4a5c-8f74-72fcc17db7fd} + - {41bf4d2b-e2a7-4109-bad0-948fa34741d5} + {64b8422b-a464-492a-9a4f-a3a00e12a41e} - {d2dca650-da5d-42d5-a533-a4cb3529c4e2} + {ab3330c0-d218-4d96-b47c-b23ad29c0f40} - {efda6ef3-f051-4442-bc2f-6c63cca454d2} + {8a91bbde-c91a-4589-9c0f-19c5d30ee129} - {bc54e771-22f9-483b-a35c-5a48ee64eb21} + {b575f914-0b96-4aab-8e4b-d5631943982a} - {36333054-a1bd-4649-9207-8d8bce0b984a} + {eee08138-02d8-4f5d-a6de-8f25a969eedf} - {aea0059b-e5e1-4da7-aeb4-ac472710074d} + {046bb700-bbcd-491b-9b2a-4e2129bbab9d} - {10105c02-b1e0-40f4-9047-8ae4a2fa42f6} + {4e275462-a3fc-4565-8eb0-001bd43c7a9c} - {3404b05e-f966-44bc-af61-f57bc56b8a05} + {e295810f-9318-48a2-b397-4344a55b7117} - {965d83d6-bdd9-46a6-8730-dff1da56df4b} + {e89f1dc9-ff1b-4dd3-86d5-d01cb50dff04} - {7d8a069a-6586-404e-94cb-6d3870d25eb5} + {15bb6dee-b17d-4d39-baf2-53c46dd04154} \ No newline at end of file diff --git a/src/native_api/c_api.h b/src/native_api/c_api.h new file mode 100644 index 00000000..6cdf35d8 --- /dev/null +++ b/src/native_api/c_api.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef NATIVE_API_ENABLED + +#include "utils/compiler.h" + +GODOT_WARNING_DISABLE() +#include +GODOT_WARNING_RESTORE() + +namespace NATIVE_API { +godot::Dictionary get_functions(); +} + +#endif \ No newline at end of file diff --git a/src/native_api/c_api_shared.hpp b/src/native_api/c_api_shared.hpp new file mode 100644 index 00000000..01377625 --- /dev/null +++ b/src/native_api/c_api_shared.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +#if _MSC_VER +__pragma(warning(disable : 4244 26451 26495)); +#endif +#include +#if _MSC_VER +__pragma(warning(default : 4244 26451 26495)); +#endif + +namespace DD3DShared { + +template +struct FunctionSignature; + +template +struct FunctionSignature { + static godot::String get() { + std::ostringstream oss; + oss << typeid(R).name() << " ("; + ((oss << getTypeName() << ", "), ...); + return godot::String(oss.str().c_str()).trim_suffix(", ") + ")"; + } + +private: + template + static std::string getTypeName() { + std::ostringstream oss; + if constexpr (std::is_const::value) { + oss << "const "; + } + if constexpr (std::is_reference::value) { + oss << getTypeName >() << "&"; + } else { + oss << typeid(U).name(); + } + return oss.str(); + } +}; + +template +godot::String get_function_signature(Func func) { + return FunctionSignature::get(); +} +} // namespace DD3DShared diff --git a/src/native_api/templates/c_api.cpp b/src/native_api/templates/c_api.cpp new file mode 100644 index 00000000..1fd0c4bf --- /dev/null +++ b/src/native_api/templates/c_api.cpp @@ -0,0 +1,42 @@ +#ifdef NATIVE_API_ENABLED + +#include "native_api/c_api.h" + +#include "native_api/c_api_shared.hpp" +#include "utils/utils.h" + +#include "2d/debug_draw_2d.h" +#include "3d/config_scope_3d.h" +#include "3d/debug_draw_3d.h" +#include "debug_draw_manager.h" + +using namespace godot; + +namespace NATIVE_API { + +void *test_api_func(const int &i, const Vector2 &v, const float *f, float const *ff = nullptr) { + UtilityFunctions::print("WTF WOW ", i, v); + return nullptr; +} + +// GENERATOR_DD3D_FUNCTIONS_DEFINES + +Dictionary get_functions() { + static Dictionary result; + if (result.is_empty()) { + Dictionary functions; +#define ADD_FUNC(_name) functions[#_name] = Utils::make_dict("ptr", (int64_t) & _name, "signature", DD3DShared::get_function_signature(&_name)) + + ADD_FUNC(test_api_func); + // GENERATOR_DD3D_FUNCTIONS_REGISTERS + +#undef ADD_FUNC + + result["hash"] = functions.hash(); + result["functions"] = functions; + } + return result; +} +} // namespace NATIVE_API + +#endif \ No newline at end of file diff --git a/src/native_api/templates/cpp/dd3d_cpp_api.hpp b/src/native_api/templates/cpp/dd3d_cpp_api.hpp new file mode 100644 index 00000000..fe0fe304 --- /dev/null +++ b/src/native_api/templates/cpp/dd3d_cpp_api.hpp @@ -0,0 +1,111 @@ +#pragma once + +// This file is generated! +// +// TODO reverse: +// Define DD3D_DISABLE_MISMATCH_CHECKS to disable signature mismatch checking and disable method existence checking. + +#include "c_api_shared.hpp" + +#if _MSC_VER +__pragma(warning(disable : 4244 26451 26495)); +#endif +// GENERATOR_DD3D_API_INCLUDES +#include +#include +#include +#include +#if _MSC_VER +__pragma(warning(default : 4244 26451 26495)); +#endif + +struct _DD3D_Loader_ { + static constexpr const char *log_prefix = "[DD3D] "; + static constexpr const char *get_funcs_hash_name = "_get_native_functions_hash"; + static constexpr const char *get_funcs_name = "_get_native_functions"; + + static godot::Object *&dd3d_cached() { + static godot::Object *dd3d = nullptr; + return dd3d; + } + + static godot::Object *get_dd3d() { + godot::Object *&dd3d_c = dd3d_cached(); + if (!dd3d_c && godot::Engine::get_singleton()->has_singleton("DebugDrawManager")) { + godot::Object *dd3d = godot::Engine::get_singleton()->get_singleton("DebugDrawManager"); + +#ifndef DD3D_DISABLE_MISMATCH_CHECKS + if (!dd3d->has_method(get_funcs_hash_name)) { + godot::UtilityFunctions::printerr(log_prefix, get_funcs_hash_name, " not found!"); + return nullptr; + } +#endif + +#ifndef DD3D_DISABLE_MISMATCH_CHECKS + if (!dd3d->has_method(get_funcs_name)) { + godot::UtilityFunctions::printerr(log_prefix, get_funcs_name, " not found!"); + return nullptr; + } +#endif + dd3d_c = dd3d; + } + return dd3d_c; + } + + template + static bool load_function(func &val, const char *name) { + if (Object *dd3d = get_dd3d(); dd3d) { + int64_t api_hash = dd3d->call(get_funcs_hash_name); + + // TODO add an actual comparison with the previous hash. It is useful in case of library reloading, but is it really useful for users?.. + if (api_hash != 0) { + Dictionary api = dd3d->call(get_funcs_name); + if (api.has(name)) { + Dictionary func_dict = api[name]; + +#ifndef DD3D_DISABLE_MISMATCH_CHECKS + String sign1 = func_dict["signature"]; + String sign2 = DD3DShared::get_function_signature(val); + if (sign1 != sign2) { + UtilityFunctions::printerr(log_prefix, "!!! FUNCTION SIGNATURE MISMATCH !!! function name: ", name, ", DD3D Signature '", sign1, "' doesn't match client's Signature '", sign2, "'"); + return false; + } +#endif + val = (func)(int64_t)func_dict["ptr"]; + return true; + } else { + UtilityFunctions::printerr(log_prefix, "!!! FUNCTION NOT FOUND !!! function name: ", name); + return false; + } + } + } + return false; + } +}; + +#define LOAD_AND_CALL_FUNC_POINTER(_name, ...) \ + if (!_name) { \ + if (_DD3D_Loader_::load_function(_name, #_name)) { \ + return; \ + } \ + } \ + if (_name) { \ + _name(__VA_ARGS__); \ + } + +#define LOAD_AND_CALL_FUNC_POINTER_RET(_name, _def_ret_val, ...) \ + if (!_name) { \ + if (_DD3D_Loader_::load_function(_name, #_name)) { \ + return _def_ret_val; \ + } \ + } \ + if (_name) { \ + return _name(__VA_ARGS__); \ + } \ + return _def_ret_val + +// Start of the generated API +// GENERATOR_DD3D_API_FUNCTIONS +// End of the generated API + +#undef LOAD_FUNC_POINTER \ No newline at end of file diff --git a/src/utils/native_api_hooks.h b/src/utils/native_api_hooks.h new file mode 100644 index 00000000..f727709e --- /dev/null +++ b/src/utils/native_api_hooks.h @@ -0,0 +1,6 @@ +#pragma once + +#define NAPI_CLASS class +#define NAPI_CLASS_SINGLETON class +#define NAPI_CLASS_REF class +#define NAPI \ No newline at end of file diff --git a/tests_native_api/cpp/addon_cpp_api_test/dd3d_test_cpp_api.gdextension b/tests_native_api/cpp/addon_cpp_api_test/dd3d_test_cpp_api.gdextension new file mode 100644 index 00000000..33faa772 --- /dev/null +++ b/tests_native_api/cpp/addon_cpp_api_test/dd3d_test_cpp_api.gdextension @@ -0,0 +1,41 @@ +[configuration] + +entry_symbol = "dd3d_test_cpp_api_init" +compatibility_minimum = "4.1.4" +reloadable = false + +[libraries] + +; ------------------------------------- +; debug + +macos = "libs/libdd3d_cpp_api.macos.editor.universal.framework" +windows.x86_64 = "libs/libdd3d_cpp_api.windows.editor.x86_64.dll" +linux.x86_64 = "libs/libdd3d_cpp_api.linux.editor.x86_64.so" + +web.wasm32.nothreads = "libs/libdd3d_cpp_api.web.template_debug.wasm32.wasm" +web.wasm32 = "libs/libdd3d_cpp_api.web.template_debug.wasm32.threads.wasm" + +android.arm32 = "libs/libdd3d_cpp_api.android.template_debug.arm32.so" +android.arm64 = "libs/libdd3d_cpp_api.android.template_debug.arm64.so" +android.x86_32 = "libs/libdd3d_cpp_api.android.template_debug.x86_32.so" +android.x86_64 = "libs/libdd3d_cpp_api.android.template_debug.x86_64.so" + +ios = "libs/libdd3d_cpp_api.ios.template_debug.universal.dylib" + +; ------------------------------------- +; release + +macos.template_release = "libs/libdd3d_cpp_api.macos.template_release.universal.framework" +windows.template_release.x86_64 = "libs/libdd3d_cpp_api.windows.template_release.x86_64.dll" +linux.template_release.x86_64 = "libs/libdd3d_cpp_api.linux.template_release.x86_64.so" + +web.template_release.wasm32.nothreads = "libs/libdd3d_cpp_api.web.template_release.wasm32.wasm" +web.template_release.wasm32 = "libs/libdd3d_cpp_api.web.template_release.wasm32.threads.wasm" + +android.template_release.arm32 = "libs/libdd3d_cpp_api.android.template_release.arm32.so" +android.template_release.arm64 = "libs/libdd3d_cpp_api.android.template_release.arm64.so" +android.template_release.x86_32 = "libs/libdd3d_cpp_api.android.template_release.x86_32.so" +android.template_release.x86_64 = "libs/libdd3d_cpp_api.android.template_release.x86_64.so" + +ios.template_release = "libs/libdd3d_cpp_api.ios.template_release.universal.dylib" diff --git a/tests_native_api/cpp/addon_cpp_api_test/dd3d_test_cpp_api_node.tscn b/tests_native_api/cpp/addon_cpp_api_test/dd3d_test_cpp_api_node.tscn new file mode 100644 index 00000000..7716e12c --- /dev/null +++ b/tests_native_api/cpp/addon_cpp_api_test/dd3d_test_cpp_api_node.tscn @@ -0,0 +1,3 @@ +[gd_scene format=3 uid="uid://b1t231cdom61b"] + +[node name="DD3DTestCppApiNode" type="DD3DTestCppApiNode"] diff --git a/tests_native_api/cpp/register_types.cpp b/tests_native_api/cpp/register_types.cpp new file mode 100644 index 00000000..2096db92 --- /dev/null +++ b/tests_native_api/cpp/register_types.cpp @@ -0,0 +1,36 @@ +/* register_types.cpp */ + +#include "test_api_node.h" + +#include "../src/utils/compiler.h" + +GODOT_WARNING_DISABLE() +//#include +GODOT_WARNING_RESTORE() +using namespace godot; + +/** GDExtension Initialize **/ +void initialize_dd3d_test_cpp_api_module(ModuleInitializationLevel p_level) { + if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { + ClassDB::register_class(); + } +} + +/** GDExtension Uninitialize **/ +void uninitialize_dd3d_test_cpp_api_module(ModuleInitializationLevel p_level) { + if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { + } +} + +/** GDExtension Initialize **/ +extern "C" { +GDExtensionBool GDE_EXPORT dd3d_test_cpp_api_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { + godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); + + init_obj.register_initializer(initialize_dd3d_test_cpp_api_module); + init_obj.register_terminator(uninitialize_dd3d_test_cpp_api_module); + init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); + + return init_obj.init(); +} +} diff --git a/tests_native_api/cpp/test_api_node.cpp b/tests_native_api/cpp/test_api_node.cpp new file mode 100644 index 00000000..7ffec4a3 --- /dev/null +++ b/tests_native_api/cpp/test_api_node.cpp @@ -0,0 +1,60 @@ +#include "test_api_node.h" + +#include "../addons/debug_draw_3d/native_api/cpp/dd3d_cpp_api.hpp" +#include "../src/utils/compiler.h" + +GODOT_WARNING_DISABLE() +#include +#include +#include +#include +GODOT_WARNING_RESTORE() +using namespace godot; + +void DD3DTestCppApiNode::_bind_methods() { +} + +DD3DTestCppApiNode::DD3DTestCppApiNode() { +} + +void DD3DTestCppApiNode::_ready() { +} + +float timer_cubes = 0; +void DD3DTestCppApiNode::_process(double p_delta) { + DebugDraw3D::draw_sphere(Vector3(1, 1, 1), 2, Color(1, 1, 0), 0); + + // Lots of boxes to check performance.. + int x_size = 50; + int y_size = 50; + int z_size = 3; + float mul = 1; + float cubes_max_time = 1.25; + // auto cfg = DebugDraw3D::new_scoped_config(); + + if (true) { + x_size = 100; + y_size = 100; + z_size = 100; + mul = 4; + cubes_max_time = 60; + } + + if (timer_cubes < 0) { + uint64_t _start_time = Time::get_singleton()->get_ticks_usec(); + Ref rng; + rng.instantiate(); + for (int x = 0; x < x_size; x++) + for (int y = 0; y < y_size; y++) + for (int z = 0; z < z_size; z++) { + Vector3 size = Vector3(1, 1, 1); + // cfg.set_thickness(rng->randf_range(0, 0.1)); + // var size = Vector3(randf_range(0.1, 100), randf_range(0.1, 100), randf_range(0.1, 100)) + DebugDraw3D::draw_box(Vector3(x * mul, (-4 - z) * mul, y * mul), Quaternion(), size, Color(0, 0, 0, 0), false, cubes_max_time); + } + UtilityFunctions::print("Draw Cubes C++: ", (Time::get_singleton()->get_ticks_usec() - _start_time) / 1000.0, "ms"); + timer_cubes = cubes_max_time; + } + + timer_cubes -= p_delta; +} diff --git a/tests_native_api/cpp/test_api_node.h b/tests_native_api/cpp/test_api_node.h new file mode 100644 index 00000000..df0b1212 --- /dev/null +++ b/tests_native_api/cpp/test_api_node.h @@ -0,0 +1,19 @@ +#pragma once + +#include "utils/compiler.h" + +GODOT_WARNING_DISABLE() +#include +GODOT_WARNING_RESTORE() +using namespace godot; + +class DD3DTestCppApiNode : public Node { + GDCLASS(DD3DTestCppApiNode, Node) +protected: + static void _bind_methods(); + +public: + DD3DTestCppApiNode(); + virtual void _ready() override; + virtual void _process(double p_delta) override; +};