gdnative_python/generation/bindings_templates/class.tmpl.pyx

281 lines
8.7 KiB
Cython

{% from 'method.tmpl.pyx' import render_method, get_method_bind_register_name %}
{% macro render_class_gdapi_ptrs_init(cls) %}
{% if not cls.singleton %}
global __{{ cls.name }}_constructor
__{{ cls.name }}_constructor = gdapi10.pandemonium_get_class_constructor("{{ cls.name }}")
{% endif %}
{% for method in cls.methods %}
global {{ get_method_bind_register_name(cls, method) }}
{{ get_method_bind_register_name(cls, method) }} = gdapi10.pandemonium_method_bind_get_method("{{ cls.bind_register_name }}", "{{ method.name }}")
{% endfor %}
{% endmacro %}
{# TODO: Handle signals #}
{% macro render_class(cls) %}
{% if not cls.base_class %}
from cpython.object cimport PyObject_GenericGetAttr, PyObject_GenericSetAttr
{% endif %}
{% if not cls.singleton %}
cdef pandemonium_class_constructor __{{ cls.name }}_constructor = NULL
{% endif %}
{% for method in cls.methods %}
cdef pandemonium_method_bind *{{ get_method_bind_register_name(cls, method) }} = NULL
{% endfor %}
cdef class {{ cls.name }}({{ cls.base_class }}):
{% if not cls.base_class %}
# free is virtual but this is not marked in api.json :'(
def free(self):
with nogil:
gdapi10.pandemonium_object_destroy(self._gd_ptr)
def __init__(self):
raise RuntimeError(
f"Use `new()` method to instantiate non-refcounted Pandemonium object (and don't forget to free it !)"
)
def __repr__(self):
return f"<{type(self).__name__} wrapper on 0x{<size_t>self._gd_ptr:x}>"
@staticmethod
cdef inline Object cast_from_variant(const pandemonium_variant *p_gdvar):
cdef pandemonium_object *ptr = gdapi10.pandemonium_variant_as_object(p_gdvar)
# Retreive class
cdef GDString classname = GDString.__new__(GDString)
with nogil:
gdapi10.pandemonium_method_bind_ptrcall(
__methbind__Object__get_class,
ptr,
NULL,
&classname._gd_data
)
return globals()[str(classname)]._from_ptr(<size_t>ptr)
@staticmethod
cdef inline Object cast_from_ptr(pandemonium_object *ptr):
# Retreive class
cdef GDString classname = GDString.__new__(GDString)
with nogil:
gdapi10.pandemonium_method_bind_ptrcall(
__methbind__Object__get_class,
ptr,
NULL,
&classname._gd_data
)
return globals()[str(classname)]._from_ptr(<size_t>ptr)
def __eq__(self, other):
try:
return self._gd_ptr == (<{{ cls.name }}>other)._gd_ptr
except TypeError:
return False
def __ne__(self, other):
try:
return self._gd_ptr != (<{{ cls.name }}>other)._gd_ptr
except TypeError:
return True
def __getattr__(self, name):
cdef GDString gdname = GDString(name)
cdef GDString gdnamefield = GDString("name")
# If a script is attached to the object, we expose here it methods
if not hasattr(type(self), '__exposed_python_class'):
if self.has_method(name):
def _call(*args):
return {{ cls.name }}.callv(self, gdname, Array(args))
return _call
# from functools import partial
# return partial(self.call, gdname)
elif any(x for x in self.get_property_list() if x[gdnamefield] == gdname):
# TODO: Pandemonium currently lacks a `has_property` method
return self.get(gdname)
raise AttributeError(
f"`{type(self).__name__}` object has no attribute `{name}`"
)
def __setattr__(self, name, value):
cdef GDString gdname = GDString(name)
cdef GDString gdnamefield = GDString("name")
if hasattr(type(self), '__exposed_python_class'):
PyObject_GenericSetAttr(self, name, value)
return
# Could retrieve the item inside the Pandemonium class, try to look into
# the attached script if it has one
else:
if any(x for x in self.get_property_list() if x[gdnamefield] == gdname):
# TODO: Pandemonium currently lacks a `has_property` method
self.set(name, value)
return
raise AttributeError(
f"`{type(self).__name__}` object has no attribute `{name}`"
)
def call(self, name, *args):
return self.callv(name, Array(args))
{% endif %}
{% if not cls.singleton and cls.instantiable %}
{% if cls.is_reference %}
def __init__(self):
if __{{ cls.name }}_constructor == NULL:
raise NotImplementedError(__ERR_MSG_BINDING_NOT_AVAILABLE)
cdef pandemonium_bool __ret
with nogil:
self._gd_ptr = __{{ cls["name"] }}_constructor()
if self._gd_ptr is NULL:
raise MemoryError
gdapi10.pandemonium_method_bind_ptrcall(
__methbind__Reference__init_ref,
self._gd_ptr,
NULL,
&__ret
)
{% else %}
@staticmethod
def new():
if __{{ cls.name }}_constructor == NULL:
raise NotImplementedError(__ERR_MSG_BINDING_NOT_AVAILABLE)
# Call to __new__ bypasses __init__ constructor
cdef {{ cls.name }} wrapper = {{ cls.name }}.__new__({{ cls.name }})
with nogil:
wrapper._gd_ptr = __{{ cls.name }}_constructor()
if wrapper._gd_ptr is NULL:
raise MemoryError
return wrapper
{% endif %}
{% if cls.name == "Reference" %}
@classmethod
def new(cls):
raise RuntimeError(f"Refcounted Pandemonium object must be created with `{ cls.__name__ }()`")
def __dealloc__(self):
cdef pandemonium_bool __ret
if self._gd_ptr == NULL:
return
with nogil:
gdapi10.pandemonium_method_bind_ptrcall(
__methbind__Reference__unreference,
self._gd_ptr,
NULL,
&__ret
)
if __ret:
gdapi10.pandemonium_object_destroy(self._gd_ptr)
{% endif %}
{% endif %}
@staticmethod
cdef {{ cls.name }} from_ptr(pandemonium_object *_ptr):
# Call to __new__ bypasses __init__ constructor
cdef {{ cls.name }} wrapper = {{ cls.name }}.__new__({{ cls.name }})
wrapper._gd_ptr = _ptr
{% if cls.is_reference %}
# Note we steal the reference from the caller given we
# don't call `Reference.reference` here
{% endif %}
return wrapper
{% if not cls.singleton and cls.instantiable %}
@classmethod
def _new(cls):
cdef pandemonium_object* ptr = __{{ cls.name }}_constructor()
if ptr is NULL:
raise MemoryError
return <size_t>ptr
{% endif %}
@staticmethod
def _from_ptr(ptr):
# Call to __new__ bypasses __init__ constructor
cdef {{ cls.name }} wrapper = {{ cls.name }}.__new__({{ cls.name }})
# /!\ doing `<pandemonium_object*>ptr` would return the address of
# the PyObject instead of casting it value !
wrapper._gd_ptr = <pandemonium_object *><size_t>ptr
{% if cls.is_reference %}
# Note we steal the reference from the caller given we
# don't call `Reference.reference` here
{% endif %}
return wrapper
{% if cls.constants | length %}
# Constants
{% endif %}
{% for key, value in cls.constants.items() %}
{{ key }} = {{ value }}
{% endfor %}
{% if cls.enums | length %}
# Enums
{% endif %}
{% for enum in cls.enums %}
{{ enum.name }} = IntFlag("{{ enum.name }}", {
{% for key, value in enum.values.items() %}
"{{ key }}": {{ value }},
{% endfor %}
})
{% endfor %}
{% if cls.methods | length %}
# Methods
{% endif %}
{# TODO: Use typing for params&return #}
{% for method in cls.methods %}
{% if method.name != "free" %}
{{ render_method(cls, method) | indent }}
{% endif %}
{% endfor %}
{% if cls.properties | length %}
# Properties
{% endif %}
{#
TODO: some properties has / in there name
TODO: some properties pass a parameter to the setter/getter
TODO: see PinJoint.params/bias for a good example
#}
{% for prop in cls.properties %}
@property
def {{ prop.name }}(self):
{% if prop.is_supported %}
return self.{{ prop.getter }}({% if prop.index is not none %}{{ prop.index }}{% endif %})
{% else %}
raise NotImplementedError("{{prop.unsupported_reason}}")
{% endif %}
{% if prop.setter %}
@{{ prop.name }}.setter
def {{ prop.name }}(self, val):
{% if prop.is_supported %}
self.{{ prop.setter }}({% if prop.index is not none %}{{ prop.index }},{% endif %}val)
{% else %}
raise NotImplementedError("{{prop.unsupported_reason}}")
{% endif %}
{% endif %}
{% endfor %}
{% endmacro %}