Skip to content

Commit dd2a7ee

Browse files
committed
POC code for working with libgodot
1 parent c9834a5 commit dd2a7ee

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed

src/_gdpy_libgodot.pyx

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
from libc.stdlib cimport malloc, free
2+
from threading import Lock
3+
from contextlib import contextmanager
4+
5+
from godot.hazmat.gdextension_interface cimport *
6+
from godot.classes cimport BaseGDObject
7+
8+
9+
# LibGodot API
10+
cdef extern from "*":
11+
ctypedef struct LibGodotExtensionParameter:
12+
const char* key
13+
void* val
14+
15+
GDExtensionObjectPtr libgodot_create_godot_instance(int p_argc, char *p_argv[], GDExtensionInitializationFunction p_init_func, LibGodotExtensionParameter *p_params)
16+
void libgodot_destroy_godot_instance(GDExtensionObjectPtr p_godot_instance)
17+
18+
19+
# Godot uses global variables, hence only a single instance can be created
20+
cdef object _libgodot_instance = None
21+
cdef object _libgodot_init_lock = Lock()
22+
23+
24+
@contextmanager
25+
def create_godot_instance(argv: list[str]):
26+
global _libgodot_instance
27+
28+
_libgodot_init_lock.acquire()
29+
30+
if _libgodot_instance is not None:
31+
raise RuntimeError(f"Only a single instance of Godot can exist at once ! ({_libgodot_instance!r} already exist)")
32+
33+
cdef GDExtensionObjectPtr gd_instance = _create_godot_instance(argv)
34+
if gd_instance == NULL:
35+
raise RuntimeError("Failed to create Godot instance")
36+
37+
try:
38+
from godot.classes import GodotInstance
39+
cdef BaseGDObject instance = GodotInstance.__new__(GodotInstance)
40+
_libgodot_instance = instance
41+
42+
_libgodot_init_lock.release()
43+
44+
yield instance
45+
46+
finally:
47+
with _libgodot_init_lock:
48+
libgodot_destroy_godot_instance(instance._gd_instance)
49+
_libgodot_instance = None
50+
51+
52+
cdef GDExtensionObjectPtr _create_godot_instance(argv: list[str]):
53+
# Convert argv Python `list[str]` into C `char*[]`
54+
pybytes_argv = [
55+
a.encode("utf8") for a in argv
56+
]
57+
cdef int length = len(argv)
58+
cdef char **c_argv = <char **>malloc(length * sizeof(char*))
59+
assert c_argv != NULL
60+
try:
61+
for i in range(length):
62+
c_argv[i] = <const char*>pybytes_argv
63+
64+
# `c_argv` is ready, now we can initialize Godot instance !
65+
return libgodot_create_godot_instance(
66+
len(argv),
67+
c_argv,
68+
pythonscript_init,
69+
NULL
70+
)
71+
72+
finally:
73+
free(c_argv)
74+
75+
76+
#
77+
# Callbacks used by Godot on instance creation
78+
#
79+
80+
81+
# Those symbols are defined in `pythonscript_gdextension_ptrs.c` which is
82+
# going to be compiled together with this file into a single shared library.
83+
cdef extern from "*":
84+
const GDExtensionInterfaceGetProcAddress pythonscript_gdptr_get_proc_address
85+
const GDExtensionClassLibraryPtr pythonscript_gdptr_library
86+
void init_pythonscript_gdextension()
87+
88+
89+
cdef public GDExtensionBool pythonscript_init(
90+
const GDExtensionInterfaceGetProcAddress p_get_proc_address,
91+
const GDExtensionClassLibraryPtr p_library,
92+
GDExtensionInitialization *r_initialization
93+
):
94+
print('[libgodot] pythonscript_init()', flush=True)
95+
96+
# `pythonscript_gdptr_*` must be set as early as possible given it is never
97+
# null-pointer checked, especially in the Cython modules.
98+
# Note we start by setting only `get_proc_address`&`library` since it is the
99+
# minimum we need to check Godot compatibility, and only after that we proceed
100+
# with the rest of the pointers.
101+
pythonscript_gdptr_get_proc_address = p_get_proc_address
102+
pythonscript_gdptr_library = p_library
103+
104+
# Initialize the rest of the `pythonscript_gdptr_*` pointers
105+
init_pythonscript_gdextension()
106+
107+
# Initialize as early as possible, this way we can have 3rd party plugins written
108+
# in Python/Cython that can do things at this level
109+
r_initialization.minimum_initialization_level = GDEXTENSION_INITIALIZATION_CORE
110+
r_initialization.userdata = NULL
111+
r_initialization.initialize = _initialize
112+
r_initialization.deinitialize = _deinitialize
113+
114+
115+
cdef void _initialize(void *userdata, GDExtensionInitializationLevel p_level):
116+
print(f'[libgodot] _initialize({p_level})', flush=True)
117+
118+
import godot._lang
119+
pythonscript_initialize = <void(*)(int)>godot._lang.pythonscript_initialize_function_ptr
120+
pythonscript_initialize(p_level);
121+
122+
123+
cdef void _deinitialize(void *userdata, GDExtensionInitializationLevel p_level):
124+
print(f'[libgodot] _deinitialize({p_level})', flush=True)
125+
126+
import godot._lang
127+
pythonscript_deinitialize = <void(*)(int)>godot._lang.pythonscript_deinitialize_function_ptr
128+
pythonscript_deinitialize(p_level);

0 commit comments

Comments
 (0)