Skip to content

Commit

Permalink
Add builtin operators binding in gdapi.pxd, add virtual dispatch supp…
Browse files Browse the repository at this point in the history
…ort in godot extension
  • Loading branch information
touilleMan committed Dec 27, 2023
1 parent fcf9f5e commit a2e27f4
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 103 deletions.
51 changes: 42 additions & 9 deletions scripts/gdextension_cython_preprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class MethodDef:
method_name: str
is_staticmethod: bool
is_const: bool
is_virtual: bool
return_type: str
parameters: Dict[str, str]

Expand All @@ -35,11 +36,11 @@ class ClassDef:
inject_code_at_line: int


def generate_injected_code_method(spec: MethodDef, class_spec: ClassDef) -> str:
def generate_injected_code_method(spec: MethodDef, class_spec: ClassDef, virtual_flavor: bool = False) -> str:
code = f"""
@staticmethod
cdef void __godot_extension_class_meth_{spec.method_name}(
void *method_userdata,
cdef void __godot_extension_class_{'virtual_' if virtual_flavor else ''}meth_{spec.method_name}(
{'void *method_userdata,' if not virtual_flavor else ''}
GDExtensionClassInstancePtr p_instance,
const GDExtensionConstTypePtr *p_args,
GDExtensionTypePtr r_ret,
Expand All @@ -59,6 +60,10 @@ def generate_injected_code_method(spec: MethodDef, class_spec: ClassDef) -> str:
code += f" (<{param_type}*>p_args[{i}])[0],\n"
code += " )\n"

if spec.is_virtual and not virtual_flavor:
code += "\n"
code += generate_injected_code_method(spec, class_spec, virtual_flavor=True)

return code


Expand All @@ -69,22 +74,23 @@ def __godot_extension_unregister_class():
unregister_extension_class(b"{spec.class_name}")
@staticmethod
cdef GDExtensionClassInstancePtr __godot_extension_new(void* p_userdata) noexcept with gil:
cdef GDExtensionClassInstancePtr __godot_extension_create_instance(void* p_userdata) noexcept with gil:
cdef {spec.class_name} obj = {spec.class_name}()
Py_INCREF(obj)
return <PyObject*>obj
@staticmethod
cdef void __godot_extension_free(void* p_userdata, GDExtensionClassInstancePtr p_instance) noexcept with gil:
cdef void __godot_extension_free_instance(void* p_userdata, GDExtensionClassInstancePtr p_instance) noexcept with gil:
Py_DECREF(<{spec.class_name}>p_instance)
@staticmethod
def __godot_extension_register_class():
register_extension_class_creation(
b"{spec.class_name}",
b"{spec.parent_class_name}",
&{spec.class_name}.__godot_extension_new,
&{spec.class_name}.__godot_extension_free,
&{spec.class_name}.__godot_extension_create_instance,
&{spec.class_name}.__godot_extension_free_instance,
&{spec.class_name}.__godot_extension_get_virtual,
)
"""
for method in spec.methods:
Expand All @@ -109,6 +115,28 @@ def __godot_extension_register_class():
prefix=" ",
)

# TODO: cache virtual methods name to avoid pystr to gd_string_name_t conversions
code += """
@staticmethod
cdef GDExtensionClassCallVirtual __godot_extension_get_virtual(void *p_class_userdata, GDExtensionConstStringNamePtr p_name) noexcept with gil:
cdef gd_string_t gd_candidate_name
"""

for method in spec.methods:
if not method.is_virtual:
continue
code += f"""
gd_candidate_name = gd_string_from_pybytes(b"{method.method_name}")
if gd_string_name_op_equal_string(<const gd_string_name_t *>p_name, &gd_candidate_name):
gd_string_del(&gd_candidate_name)
return &{spec.class_name}.__godot_extension_class_virtual_meth_{method.method_name}
gd_string_del(&gd_candidate_name)
"""

code += """
return NULL
"""

return code


Expand Down Expand Up @@ -166,16 +194,20 @@ def extract_classes_from_code(code_lines: List[str]) -> List[ClassDef]:

else:

def _method(const: bool = False) -> MethodDef:
def _method(const: bool = False, virtual: bool = False) -> MethodDef:
if current_class is None:
raise RuntimeError(
f"`# godot_extension: method(...)` must be within a `# godot_extension: class(...)` pragma"
)

if not isinstance(const, bool):
raise RuntimeError("`const` parameter must be a boolean")

is_const = const

if not isinstance(virtual, bool):
raise RuntimeError("`virtual` parameter must be a boolean")
is_virtual = virtual

try:
_, line = next(code_lines)
is_staticmethod = line.strip() == "@staticmethod"
Expand Down Expand Up @@ -235,6 +267,7 @@ def _method(const: bool = False) -> MethodDef:
method_name=match.group("method_name"),
is_staticmethod=is_staticmethod,
is_const=is_const,
is_virtual=is_virtual,
return_type=handle_pointer_type(match.group("return_type")),
parameters=params,
)
Expand Down
2 changes: 1 addition & 1 deletion src/_pythonscript.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ cdef api void _pythonscript_deinitialize(int p_level) noexcept with gil:
&ret,
)
if ret != 0: # TODO: use `Error.Ok` here
print("Failed to unregister Python from Godot: `Engine::unregister_script_language` returned error {ret}", flush=True)
print(f"Failed to unregister Python from Godot: `Engine::unregister_script_language` returned error {ret}", flush=True)
return

_pythons_script_language = None
Expand Down
Loading

0 comments on commit a2e27f4

Please sign in to comment.