Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate compatibility layer #1415

Open
wants to merge 1 commit into
base: 4.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,64 @@ generic reusable template.

Or checkout the code for the [Summator example](https://github.com/paddy-exe/GDExtensionSummator)
as shown in the [official documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/gdextension/gdextension_cpp_example.html).

## Godot and Godot Cpp Compatibility

If you intend to target both building as a GDExtension and as a module using godot repo, you can generate compatibility includes that will target either GDExtension or module, based on the GODOT_MODULE define.

If you want such headers, when running the build command, `scons`, pass the `godot_repo` param with the path to the godot repository. Eg. if you have the godot repository cloned at path `../godot`, then do:

```sh
scons godot_repo="../godot"
```

This will generate something like this:
```
gen/include/godot_cpp/..
gen/include/godot_compat/..
```

Now, all you need to do is when writing your addon/module, replace includes like these:

```cpp
#include <godot_cpp/classes/a_star_grid2d.hpp>
```

with

```cpp
#include <godot_compat/classes/a_star_grid2d.hpp>
```

Inside, this file will have code for both godot and godot-cpp:

```cpp
#ifdef GODOT_MODULE
#include <core/math/a_star_grid_2d.h>
#else
#include <godot_cpp/classes/a_star_grid2d.hpp>
#endif
```

### Manually generate mapping files

The mappings can be manually generated by running the `compat_generator.py` script.

Example of how to run `compat_generator.py`:

```sh
git clone godotengine/godot
python compat_generator.py godot
```

The first argument of `compat_generator.py` is the folder where the repo is (can be godot or godot-cpp repo). If this folder is not given, the current directory is assumed. The output of this is either `output_header_mapping_godot.json` or `output_header_mapping_godot_cpp.json`

### Manually match the mapping files

If you want to manually match the godot mapping file with the godot-cpp one, you can do that by running:

```sh
python header_matcher.py
```

This will generate the `header_matches.json` file with matches from godot and godot_cpp repo.
54 changes: 53 additions & 1 deletion binding_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import shutil
from pathlib import Path

from compat_generator import map_header_files
from header_matcher import match_headers


def generate_mod_version(argcount, const=False, returns=False):
s = """
Expand Down Expand Up @@ -378,11 +381,14 @@ def scons_generate_bindings(target, source, env):
"32" if "32" in env["arch"] else "64",
env["precision"],
env["godot_cpp_gen_dir"],
env["godot_repo"],
)
return None


def generate_bindings(api_filepath, use_template_get_node, bits="64", precision="single", output_dir="."):
def generate_bindings(
api_filepath, use_template_get_node, bits="64", precision="single", output_dir=".", godot_repo=""
):
api = None

target_dir = Path(output_dir) / "gen"
Expand All @@ -402,6 +408,8 @@ def generate_bindings(api_filepath, use_template_get_node, bits="64", precision=
generate_builtin_bindings(api, target_dir, real_t + "_" + bits)
generate_engine_classes_bindings(api, target_dir, use_template_get_node)
generate_utility_functions(api, target_dir)
if godot_repo != "":
generate_compat_includes(godot_repo, target_dir)


CLASS_ALIASES = {
Expand Down Expand Up @@ -1545,6 +1553,50 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
header_file.write("\n".join(result))


def generate_compat_includes(godot_repo: Path, target_dir: Path):
file_types_mapping_godot_cpp_gen = map_header_files(target_dir)
file_types_mapping_godot = map_header_files(godot_repo)
# Match the headers
file_types_mapping = match_headers(file_types_mapping_godot_cpp_gen, file_types_mapping_godot)
for file_godot_cpp_name, file_godot_names in file_types_mapping.items():
result = []
with (Path(target_dir) / Path(file_godot_cpp_name)).open("r", encoding="utf-8") as header_file:
content = header_file.readlines()
# Find the last line (#endif guard)
last_endif_idx = len(content) - 1
# Look for the marker for the header guard (usually #define)
marker_pos = next((i for i, line in enumerate(content) if line.startswith("#define")), -1)

if marker_pos != -1:
# Add content before the last #endif
before_marker = content[: marker_pos + 1] # Include up to the marker line
after_marker = content[marker_pos + 1 : last_endif_idx] # Content excluding the final #endif

result.extend(before_marker) # Append original content up to marker
result.append("\n") # Blank line for separation

# Insert the #ifdef block
result.append("#ifdef GODOT_MODULE\n")
if len(file_godot_names) == 0:
print("No header found for", file_godot_cpp_name)
for file_godot_name in file_godot_names:
result.append(f'#include "{Path(file_godot_name).as_posix()}"\n')
result.append("#else\n")

result.extend(after_marker)

# Add the namespace and endif
result.append("#endif\n")

# Finally, append the original final #endif line
result.append(content[last_endif_idx].strip())

else:
print(f"Marker not found in {file_godot_cpp_name}")
with (Path(target_dir) / Path(file_godot_cpp_name)).open("w+", encoding="utf-8") as header_file:
header_file.write("".join(result))


def generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node):
global singletons
result = []
Expand Down
75 changes: 75 additions & 0 deletions compat_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env python

import json
import os
import re
import sys


def parse_header_file(file_path):
types = {"classes": [], "structs": [], "defines": [], "enums": []}

with open(file_path, "r", encoding="utf-8") as file:
content = file.read()

# Regular expressions to match different types
struct_pattern = r"struct.*\s(\S+)\s*\{"
type_pattern = r"typedef.*\s(\S+)\s*\;"
enum_pattern = r"enum.*\s(\S+)\s*\{"
class_pattern = r"class\s+[\w\s]*?([a-zA-Z_]\w*)\s*[:{]"
define_pattern = r"#define\s+([a-zA-Z_]\w*)"

# Extract classes
types["classes"] += re.findall(class_pattern, content)

# Extract structs
struct_names = re.findall(struct_pattern, content)
types["structs"].extend(struct_names)
type_names = re.findall(type_pattern, content)
types["structs"].extend(type_names)
enum_names = re.findall(enum_pattern, content)
types["enums"].extend(enum_names)

# Extract defines
define_matches = re.findall(define_pattern, content)
types["defines"] += define_matches

# Debug the case where no classes or structs are found
# if len(types["classes"]) == 0 and len(types["structs"]) == 0 and len(types["defines"]) == 0:
# print(f"{file_path} missing things")
return types


def map_header_files(directory):
file_types_mapping = {}
for root, dirs, files in os.walk(directory):
if "thirdparty" in dirs:
dirs.remove("thirdparty")
if "tests" in dirs:
dirs.remove("tests")
if "test" in dirs:
dirs.remove("test")
if "misc" in dirs:
dirs.remove("misc")
if "gdextension" in dirs:
dirs.remove("gdextension")
for file in files:
if file.endswith(".h") or file.endswith(".hpp"):
relative_path = os.path.relpath(root, directory)
file_path = os.path.join(root, file)
file_types_mapping[f"{relative_path}/{file}"] = parse_header_file(file_path)

return file_types_mapping


if __name__ == "__main__":
# Get current directory for godot-cpp
current_directory = os.path.join(os.getcwd(), "")
mapping_name = ""
if len(sys.argv) > 1:
mapping_name = "_godot"
current_directory = os.path.join(os.getcwd(), sys.argv[1])

file_types_mapping = map_header_files(current_directory)
with open(f"output_header_mapping{mapping_name}.json", "w") as json_file:
json.dump(file_types_mapping, json_file, indent=4)
33 changes: 33 additions & 0 deletions header_matcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import json
import os

from compat_generator import map_header_files


def match_headers(mapping1, mapping2):
matches = {}
for header_file, data1 in mapping1.items():
for header_file2, data2 in mapping2.items():
# Check if classes/defines/structs in header_file1 are present in header_file2
if header_file not in matches:
matches[header_file] = []
if (
any(class_name in data2["classes"] for class_name in data1["classes"])
or any(define_name in data2["defines"] for define_name in data1["defines"])
or any(define_name in data2["structs"] for define_name in data1["structs"])
): # or
# any(define_name in data2["enums"] for define_name in data1["enums"])):
matches[header_file].append(header_file2)
return matches


if __name__ == "__main__":
# Load the two header mappings
with open("output_header_mapping_godot.json", "r") as file:
mapping_godot = json.load(file)
file_types_mapping_godot_cpp_gen = map_header_files(os.path.join(os.getcwd(), "gen", "include"))
matches = match_headers(file_types_mapping_godot_cpp_gen, mapping_godot)

# Optionally, you can save the matches to a file
with open("header_matches.json", "w") as outfile:
json.dump(matches, outfile, indent=4)
5 changes: 5 additions & 0 deletions include/godot_cpp/classes/editor_plugin_registration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
#ifndef GODOT_EDITOR_PLUGIN_REGISTRATION_HPP
#define GODOT_EDITOR_PLUGIN_REGISTRATION_HPP

#ifdef GODOT_MODULE
#include "editor/plugins/editor_plugin.h"
#else

#include <godot_cpp/templates/vector.hpp>

namespace godot {
Expand Down Expand Up @@ -59,4 +63,5 @@ class EditorPlugins {

} // namespace godot

#endif
#endif // GODOT_EDITOR_PLUGIN_REGISTRATION_HPP
5 changes: 5 additions & 0 deletions include/godot_cpp/classes/ref.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
#ifndef GODOT_REF_HPP
#define GODOT_REF_HPP

#ifdef GODOT_MODULE
#include "core/object/ref_counted.h"
#else

#include <godot_cpp/core/defs.hpp>

#include <godot_cpp/classes/object.hpp>
Expand Down Expand Up @@ -281,4 +285,5 @@ struct GetTypeInfo<const Ref<T> &, typename EnableIf<TypeInherits<RefCounted, T>

} // namespace godot

#endif
#endif // GODOT_REF_HPP
5 changes: 5 additions & 0 deletions include/godot_cpp/classes/wrapped.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
#ifndef GODOT_WRAPPED_HPP
#define GODOT_WRAPPED_HPP

#ifdef GODOT_MODULE
#include "core/object/object.h"
#else

#include <godot_cpp/core/memory.hpp>

#include <godot_cpp/core/property_info.hpp>
Expand Down Expand Up @@ -506,4 +510,5 @@ public:
#define GDVIRTUAL_IS_OVERRIDDEN(m_name) _gdvirtual_##m_name##_overridden()
#define GDVIRTUAL_IS_OVERRIDDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overridden()

#endif
#endif // GODOT_WRAPPED_HPP
5 changes: 5 additions & 0 deletions include/godot_cpp/core/binder_common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
#ifndef GODOT_BINDER_COMMON_HPP
#define GODOT_BINDER_COMMON_HPP

#ifdef GODOT_MODULE
#include "core/variant/binder_common.h"
#else

#include <gdextension_interface.h>

#include <godot_cpp/core/method_ptrcall.hpp>
Expand Down Expand Up @@ -693,4 +697,5 @@ void call_with_ptr_args_static_method_ret(R (*p_method)(P...), const GDExtension
#include <godot_cpp/classes/global_constants_binds.hpp>
#include <godot_cpp/variant/builtin_binds.hpp>

#endif
#endif // GODOT_BINDER_COMMON_HPP
4 changes: 4 additions & 0 deletions include/godot_cpp/core/builtin_ptrcall.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
#ifndef GODOT_BUILTIN_PTRCALL_HPP
#define GODOT_BUILTIN_PTRCALL_HPP

#ifdef GODOT_MODULE
#else

#include <gdextension_interface.h>
#include <godot_cpp/core/object.hpp>

Expand Down Expand Up @@ -89,4 +92,5 @@ T _call_builtin_ptr_getter(const GDExtensionPtrGetter getter, GDExtensionConstTy

} // namespace godot

#endif
#endif // GODOT_BUILTIN_PTRCALL_HPP
6 changes: 6 additions & 0 deletions include/godot_cpp/core/class_db.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
#ifndef GODOT_CLASS_DB_HPP
#define GODOT_CLASS_DB_HPP

#ifdef GODOT_MODULE
#include "core/core_bind.h"
#include "core/object/class_db.h"
#else

#include <gdextension_interface.h>

#include <godot_cpp/core/defs.hpp>
Expand Down Expand Up @@ -358,4 +363,5 @@ MethodBind *ClassDB::bind_vararg_method(uint32_t p_flags, StringName p_name, M p

CLASSDB_SINGLETON_VARIANT_CAST;

#endif
#endif // GODOT_CLASS_DB_HPP
6 changes: 6 additions & 0 deletions include/godot_cpp/core/defs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
#ifndef GODOT_DEFS_HPP
#define GODOT_DEFS_HPP

#ifdef GODOT_MODULE
#include "core/math/math_defs.h"
#include "core/typedefs.h"
#else

#include <cstddef>
#include <cstdint>
#include <cstring>
Expand Down Expand Up @@ -127,4 +132,5 @@ struct BuildIndexSequence : BuildIndexSequence<N - 1, N - 1, Is...> {};
template <size_t... Is>
struct BuildIndexSequence<0, Is...> : IndexSequence<Is...> {};

#endif
#endif // GODOT_DEFS_HPP
4 changes: 4 additions & 0 deletions include/godot_cpp/core/engine_ptrcall.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
#ifndef GODOT_ENGINE_PTRCALL_HPP
#define GODOT_ENGINE_PTRCALL_HPP

#ifdef GODOT_MODULE
#else

#include <gdextension_interface.h>

#include <godot_cpp/core/binder_common.hpp>
Expand Down Expand Up @@ -94,4 +97,5 @@ void _call_utility_no_ret(const GDExtensionPtrUtilityFunction func, const Args &

} // namespace godot

#endif
#endif // GODOT_ENGINE_PTRCALL_HPP
Loading