Skip to content

Commit a114d67

Browse files
authored
Merge pull request Redot-Engine#18 from Spartan322/merge/714c9e2
Merge commit godotengine/godot-cpp@714c9e2
2 parents 307d4ea + eb2aa8e commit a114d67

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+13324
-1698
lines changed

.github/workflows/ci.yml

+5-5
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ jobs:
178178
./run-tests.sh
179179
180180
- name: Upload artifact
181-
uses: actions/upload-artifact@v3
181+
uses: actions/upload-artifact@v4
182182
with:
183183
name: ${{ matrix.artifact-name }}
184184
path: ${{ matrix.artifact-path }}
@@ -202,8 +202,8 @@ jobs:
202202
run: |
203203
mkdir cmake-build
204204
cd cmake-build
205-
cmake ../ -DTEST_TARGET=template_release
206-
cmake --build . --verbose -j $(nproc) -t godot-cpp-test --config Release
205+
cmake ../ -DGODOTCPP_ENABLE_TESTING=YES
206+
cmake --build . --verbose -j $(nproc) -t godot-cpp.test.template_release --config Release
207207
208208
windows-msvc-cmake:
209209
name: 🏁 Build (Windows, MSVC, CMake)
@@ -218,5 +218,5 @@ jobs:
218218
run: |
219219
mkdir cmake-build
220220
cd cmake-build
221-
cmake ../ -DTEST_TARGET=template_release
222-
cmake --build . --verbose -t godot-cpp-test --config Release
221+
cmake ../ -DGODOTCPP_ENABLE_TESTING=YES
222+
cmake --build . --verbose -t godot-cpp.test.template_release --config Release

.github/workflows/static_checks.yml

+4
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@ jobs:
3232
uses: pre-commit/action@v3.0.1
3333
with:
3434
extra_args: --verbose --hook-stage manual --files ${{ env.CHANGED_FILES }}
35+
36+
- name: Check generated files consistency
37+
run:
38+
python misc/scripts/check_get_file_list.py

CMakeLists.txt

+10-5
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,8 @@ The CMake equivalent is below.
3838
]=======================================================================]
3939

4040
include( cmake/godotcpp.cmake )
41-
godotcpp_options()
4241

43-
#[[ Python is required for code generation ]]
44-
find_package(Python3 3.4 REQUIRED) # pathlib should be present
42+
godotcpp_options()
4543

4644
# Define our project.
4745
project( godot-cpp
@@ -53,5 +51,12 @@ project( godot-cpp
5351
compiler_detection()
5452
godotcpp_generate()
5553

56-
# Test Example
57-
add_subdirectory( test )
54+
# Conditionally enable the godot-cpp.test.<target> integration testing targets
55+
if( GODOTCPP_ENABLE_TESTING )
56+
add_subdirectory( test )
57+
endif()
58+
59+
# If this is the top level CMakeLists.txt, Generators which honor the
60+
# USE_FOLDERS flag will organize godot-cpp targets under the subfolder
61+
# 'godot-cpp'. This is enable by default from CMake version 3.26
62+
set_property(GLOBAL PROPERTY USE_FOLDERS ON)

README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ wish to help out, ensure you have an account on GitHub and create a "fork" of
6161
this repository. See [Pull request workflow](https://docs.redotengine.org/en/stable/community/contributing/pr_workflow.html)
6262
for instructions.
6363

64-
Please install clang-format and copy the files in `misc/hooks` into `.git/hooks`
65-
so formatting is done before your changes are submitted.
64+
Please install clang-format and the [pre-commit](https://pre-commit.com/) Python framework so formatting is done before your changes are submitted. See the [code style guidelines](https://docs.godotengine.org/en/latest/contributing/development/code_style_guidelines.html#pre-commit-hook) for instructions.
6665

6766
## Getting started
6867

binding_generator.py

+41-145
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,9 @@ def generate_wrappers(target):
7070
f.write(txt)
7171

7272

73-
def generate_virtual_version(argcount, const=False, returns=False):
73+
def generate_virtual_version(argcount, const=False, returns=False, required=False):
7474
s = """#define GDVIRTUAL$VER($RET m_name $ARG)\\
7575
::godot::StringName _gdvirtual_##m_name##_sn = #m_name;\\
76-
template <bool required>\\
7776
_FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST {\\
7877
if (::godot::internal::gdextension_interface_object_has_script_method(_owner, &_gdvirtual_##m_name##_sn)) { \\
7978
GDExtensionCallError ce;\\
@@ -85,10 +84,8 @@ def generate_virtual_version(argcount, const=False, returns=False):
8584
return true;\\
8685
}\\
8786
}\\
88-
if (required) {\\
89-
ERR_PRINT_ONCE("Required virtual method " + get_class() + "::" + #m_name + " must be overridden before calling.");\\
90-
$RVOID\\
91-
}\\
87+
$REQCHECK\\
88+
$RVOID\\
9289
return false;\\
9390
}\\
9491
_FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const {\\
@@ -106,6 +103,7 @@ def generate_virtual_version(argcount, const=False, returns=False):
106103

107104
sproto = str(argcount)
108105
method_info = ""
106+
method_flags = "METHOD_FLAG_VIRTUAL"
109107
if returns:
110108
sproto += "R"
111109
s = s.replace("$RET", "m_ret,")
@@ -114,16 +112,26 @@ def generate_virtual_version(argcount, const=False, returns=False):
114112
method_info += "\t\tmethod_info.return_val_metadata = ::godot::GetTypeInfo<m_ret>::METADATA;"
115113
else:
116114
s = s.replace("$RET ", "")
117-
s = s.replace("\t\t\t$RVOID\\\n", "")
115+
s = s.replace("\t\t$RVOID\\\n", "")
118116

119117
if const:
120118
sproto += "C"
119+
method_flags += " | METHOD_FLAG_CONST"
121120
s = s.replace("$CONST", "const")
122-
s = s.replace("$METHOD_FLAGS", "::godot::METHOD_FLAG_VIRTUAL | ::godot::METHOD_FLAG_CONST")
123121
else:
124122
s = s.replace("$CONST ", "")
125-
s = s.replace("$METHOD_FLAGS", "::godot::METHOD_FLAG_VIRTUAL")
126123

124+
if required:
125+
sproto += "_REQUIRED"
126+
method_flags += " | METHOD_FLAG_VIRTUAL_REQUIRED"
127+
s = s.replace(
128+
"$REQCHECK",
129+
'ERR_PRINT_ONCE("Required virtual method " + get_class() + "::" + #m_name + " must be overridden before calling.");',
130+
)
131+
else:
132+
s = s.replace("\t\t$REQCHECK\\\n", "")
133+
134+
s = s.replace("$METHOD_FLAGS", method_flags)
127135
s = s.replace("$VER", sproto)
128136
argtext = ""
129137
callargtext = ""
@@ -190,20 +198,27 @@ def generate_virtuals(target):
190198
txt += generate_virtual_version(i, False, True)
191199
txt += generate_virtual_version(i, True, False)
192200
txt += generate_virtual_version(i, True, True)
201+
txt += generate_virtual_version(i, False, False, True)
202+
txt += generate_virtual_version(i, False, True, True)
203+
txt += generate_virtual_version(i, True, False, True)
204+
txt += generate_virtual_version(i, True, True, True)
193205

194206
txt += "#endif // GDEXTENSION_GDVIRTUAL_GEN_H\n"
195207

196208
with open(target, "w", encoding="utf-8") as f:
197209
f.write(txt)
198210

199211

200-
def get_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""):
212+
def get_file_list(api_filepath, output_dir, headers=False, sources=False):
201213
api = {}
202-
files = []
203214
with open(api_filepath, encoding="utf-8") as api_file:
204215
api = json.load(api_file)
205216

206-
build_profile = parse_build_profile(profile_filepath, api)
217+
return _get_file_list(api, output_dir, headers, sources)
218+
219+
220+
def _get_file_list(api, output_dir, headers=False, sources=False):
221+
files = []
207222

208223
core_gen_folder = Path(output_dir) / "gen" / "include" / "godot_cpp" / "core"
209224
include_gen_folder = Path(output_dir) / "gen" / "include" / "godot_cpp"
@@ -235,7 +250,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False, profil
235250
source_filename = source_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".cpp")
236251
if headers:
237252
files.append(str(header_filename.as_posix()))
238-
if sources and is_class_included(engine_class["name"], build_profile):
253+
if sources:
239254
files.append(str(source_filename.as_posix()))
240255

241256
for native_struct in api["native_structures"]:
@@ -267,128 +282,19 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False, profil
267282
return files
268283

269284

270-
def print_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""):
271-
print(*get_file_list(api_filepath, output_dir, headers, sources, profile_filepath), sep=";", end=None)
272-
273-
274-
def parse_build_profile(profile_filepath, api):
275-
if profile_filepath == "":
276-
return {}
277-
print("Using feature build profile: " + profile_filepath)
278-
279-
with open(profile_filepath, encoding="utf-8") as profile_file:
280-
profile = json.load(profile_file)
281-
282-
api_dict = {}
283-
parents = {}
284-
children = {}
285-
for engine_class in api["classes"]:
286-
api_dict[engine_class["name"]] = engine_class
287-
parent = engine_class.get("inherits", "")
288-
child = engine_class["name"]
289-
parents[child] = parent
290-
if parent == "":
291-
continue
292-
children[parent] = children.get(parent, [])
293-
children[parent].append(child)
294-
295-
# Parse methods dependencies
296-
deps = {}
297-
reverse_deps = {}
298-
for name, engine_class in api_dict.items():
299-
ref_cls = set()
300-
for method in engine_class.get("methods", []):
301-
rtype = method.get("return_value", {}).get("type", "")
302-
args = [a["type"] for a in method.get("arguments", [])]
303-
if rtype in api_dict:
304-
ref_cls.add(rtype)
305-
elif is_enum(rtype) and get_enum_class(rtype) in api_dict:
306-
ref_cls.add(get_enum_class(rtype))
307-
for arg in args:
308-
if arg in api_dict:
309-
ref_cls.add(arg)
310-
elif is_enum(arg) and get_enum_class(arg) in api_dict:
311-
ref_cls.add(get_enum_class(arg))
312-
deps[engine_class["name"]] = set(filter(lambda x: x != name, ref_cls))
313-
for acls in ref_cls:
314-
if acls == name:
315-
continue
316-
reverse_deps[acls] = reverse_deps.get(acls, set())
317-
reverse_deps[acls].add(name)
318-
319-
included = []
320-
front = list(profile.get("enabled_classes", []))
321-
if front:
322-
# These must always be included
323-
front.append("WorkerThreadPool")
324-
front.append("ClassDB")
325-
front.append("ClassDBSingleton")
326-
while front:
327-
cls = front.pop()
328-
if cls in included:
329-
continue
330-
included.append(cls)
331-
parent = parents.get(cls, "")
332-
if parent:
333-
front.append(parent)
334-
for rcls in deps.get(cls, set()):
335-
if rcls in included or rcls in front:
336-
continue
337-
front.append(rcls)
338-
339-
excluded = []
340-
front = list(profile.get("disabled_classes", []))
341-
while front:
342-
cls = front.pop()
343-
if cls in excluded:
344-
continue
345-
excluded.append(cls)
346-
front += children.get(cls, [])
347-
for rcls in reverse_deps.get(cls, set()):
348-
if rcls in excluded or rcls in front:
349-
continue
350-
front.append(rcls)
351-
352-
if included and excluded:
353-
print(
354-
"WARNING: Cannot specify both 'enabled_classes' and 'disabled_classes' in build profile. 'disabled_classes' will be ignored."
355-
)
356-
357-
return {
358-
"enabled_classes": included,
359-
"disabled_classes": excluded,
360-
}
361-
362-
363-
def scons_emit_files(target, source, env):
364-
profile_filepath = env.get("build_profile", "")
365-
if profile_filepath and not Path(profile_filepath).is_absolute():
366-
profile_filepath = str((Path(env.Dir("#").abspath) / profile_filepath).as_posix())
367-
368-
files = [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True, profile_filepath)]
369-
env.Clean(target, files)
370-
env["godot_cpp_gen_dir"] = target[0].abspath
371-
return files, source
372-
373-
374-
def scons_generate_bindings(target, source, env):
375-
generate_bindings(
376-
str(source[0]),
377-
env["generate_template_get_node"],
378-
"32" if "32" in env["arch"] else "64",
379-
env["precision"],
380-
env["godot_cpp_gen_dir"],
381-
)
382-
return None
285+
def print_file_list(api_filepath, output_dir, headers=False, sources=False):
286+
print(*get_file_list(api_filepath, output_dir, headers, sources), sep=";", end=None)
383287

384288

385289
def generate_bindings(api_filepath, use_template_get_node, bits="64", precision="single", output_dir="."):
386-
api = None
387-
388-
target_dir = Path(output_dir) / "gen"
389-
290+
api = {}
390291
with open(api_filepath, encoding="utf-8") as api_file:
391292
api = json.load(api_file)
293+
_generate_bindings(api, use_template_get_node, bits, precision, output_dir)
294+
295+
296+
def _generate_bindings(api, use_template_get_node, bits="64", precision="single", output_dir="."):
297+
target_dir = Path(output_dir) / "gen"
392298

393299
shutil.rmtree(target_dir, ignore_errors=True)
394300
target_dir.mkdir(parents=True)
@@ -1788,7 +1694,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
17881694
# condition returns false (in such cases it can't compile due to ambiguity).
17891695
f"\t\tif constexpr (!std::is_same_v<decltype(&B::{method_name}), decltype(&T::{method_name})>) {{"
17901696
)
1791-
result.append(f"\t\t\tBIND_VIRTUAL_METHOD(T, {method_name});")
1697+
result.append(f"\t\t\tBIND_VIRTUAL_METHOD(T, {method_name}, {method['hash']});")
17921698
result.append("\t\t}")
17931699

17941700
result.append("\t}")
@@ -2457,6 +2363,10 @@ def get_encoded_arg(arg_name, type_name, type_meta):
24572363
result.append(f"\t{get_gdextension_type(arg_type)} {name}_encoded;")
24582364
result.append(f"\tPtrToArg<{correct_type(type_name)}>::encode({name}, &{name}_encoded);")
24592365
name = f"&{name}_encoded"
2366+
elif is_enum(type_name) and not is_bitfield(type_name):
2367+
result.append(f"\tint64_t {name}_encoded;")
2368+
result.append(f"\tPtrToArg<int64_t>::encode({name}, &{name}_encoded);")
2369+
name = f"&{name}_encoded"
24602370
elif is_engine_class(type_name):
24612371
# `{name}` is a C++ wrapper, it contains a field which is the object's pointer Godot expects.
24622372
# We have to check `nullptr` because when the caller sends `nullptr`, the wrapper itself will be null.
@@ -2767,20 +2677,6 @@ def is_refcounted(type_name):
27672677
return type_name in engine_classes and engine_classes[type_name]
27682678

27692679

2770-
def is_class_included(class_name, build_profile):
2771-
"""
2772-
Check if an engine class should be included.
2773-
This removes classes according to a build profile of enabled or disabled classes.
2774-
"""
2775-
included = build_profile.get("enabled_classes", [])
2776-
excluded = build_profile.get("disabled_classes", [])
2777-
if included:
2778-
return class_name in included
2779-
if excluded:
2780-
return class_name not in excluded
2781-
return True
2782-
2783-
27842680
def is_included(type_name, current_type):
27852681
"""
27862682
Check if a builtin type should be included.

0 commit comments

Comments
 (0)