@@ -70,6 +70,136 @@ def generate_wrappers(target):
7070 f .write (txt )
7171
7272
73+ def generate_virtual_version (argcount , const = False , returns = False ):
74+ s = """#define GDVIRTUAL$VER($RET m_name $ARG)\\
75+ StringName _gdvirtual_##m_name##_sn = #m_name;\\
76+ template <bool required>\\
77+ _FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST {\\
78+ if (::godot::internal::gdextension_interface_object_has_script_method(_owner, &_gdvirtual_##m_name##_sn)) { \\
79+ GDExtensionCallError ce;\\
80+ $CALLSIARGS\\
81+ $CALLSIBEGIN::godot::internal::gdextension_interface_object_call_script_method(_owner, &_gdvirtual_##m_name##_sn, $CALLSIARGPASS, $CALLSIRETPASS, &ce);\\
82+ if (ce.error == GDEXTENSION_CALL_OK) {\\
83+ $CALLSIRET\\
84+ return true;\\
85+ }\\
86+ }\\
87+ if (required) {\\
88+ ERR_PRINT_ONCE("Required virtual method " + get_class() + "::" + #m_name + " must be overridden before calling.");\\
89+ $RVOID\\
90+ }\\
91+ return false;\\
92+ }\\
93+ _FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const {\\
94+ return godot::internal::gdextension_interface_object_has_script_method(_owner, &_gdvirtual_##m_name##_sn); \\
95+ }\\
96+ _FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() {\\
97+ MethodInfo method_info;\\
98+ method_info.name = #m_name;\\
99+ method_info.flags = $METHOD_FLAGS;\\
100+ $FILL_METHOD_INFO\\
101+ return method_info;\\
102+ }
103+
104+ """
105+
106+ sproto = str (argcount )
107+ method_info = ""
108+ if returns :
109+ sproto += "R"
110+ s = s .replace ("$RET" , "m_ret," )
111+ s = s .replace ("$RVOID" , "(void)r_ret;" ) # If required, may lead to uninitialized errors
112+ method_info += "method_info.return_val = GetTypeInfo<m_ret>::get_class_info();\\ \n "
113+ method_info += "\t \t method_info.return_val_metadata = GetTypeInfo<m_ret>::METADATA;"
114+ else :
115+ s = s .replace ("$RET " , "" )
116+ s = s .replace ("\t \t \t $RVOID\\ \n " , "" )
117+
118+ if const :
119+ sproto += "C"
120+ s = s .replace ("$CONST" , "const" )
121+ s = s .replace ("$METHOD_FLAGS" , "METHOD_FLAG_VIRTUAL | METHOD_FLAG_CONST" )
122+ else :
123+ s = s .replace ("$CONST " , "" )
124+ s = s .replace ("$METHOD_FLAGS" , "METHOD_FLAG_VIRTUAL" )
125+
126+ s = s .replace ("$VER" , sproto )
127+ argtext = ""
128+ callargtext = ""
129+ callsiargs = ""
130+ callsiargptrs = ""
131+ if argcount > 0 :
132+ argtext += ", "
133+ callsiargs = f"Variant vargs[{ argcount } ] = {{ "
134+ callsiargptrs = f"\t \t \t const Variant *vargptrs[{ argcount } ] = {{ "
135+ for i in range (argcount ):
136+ if i > 0 :
137+ argtext += ", "
138+ callargtext += ", "
139+ callsiargs += ", "
140+ callsiargptrs += ", "
141+ argtext += f"m_type{ i + 1 } "
142+ callargtext += f"m_type{ i + 1 } arg{ i + 1 } "
143+ callsiargs += f"Variant(arg{ i + 1 } )"
144+ callsiargptrs += f"&vargs[{ i } ]"
145+ if method_info :
146+ method_info += "\\ \n \t \t "
147+ method_info += f"method_info.arguments.push_back(GetTypeInfo<m_type{ i + 1 } >::get_class_info());\\ \n "
148+ method_info += f"\t \t method_info.arguments_metadata.push_back(GetTypeInfo<m_type{ i + 1 } >::METADATA);"
149+
150+ if argcount :
151+ callsiargs += " };\\ \n "
152+ callsiargptrs += " };"
153+ s = s .replace ("$CALLSIARGS" , callsiargs + callsiargptrs )
154+ s = s .replace ("$CALLSIARGPASS" , f"(const GDExtensionConstVariantPtr *)vargptrs, { argcount } " )
155+ else :
156+ s = s .replace ("\t \t \t $CALLSIARGS\\ \n " , "" )
157+ s = s .replace ("$CALLSIARGPASS" , "nullptr, 0" )
158+
159+ if returns :
160+ if argcount > 0 :
161+ callargtext += ", "
162+ callargtext += "m_ret &r_ret"
163+ s = s .replace ("$CALLSIBEGIN" , "Variant ret;\\ \n \t \t \t " )
164+ s = s .replace ("$CALLSIRETPASS" , "&ret" )
165+ s = s .replace ("$CALLSIRET" , "r_ret = VariantCaster<m_ret>::cast(ret);" )
166+ else :
167+ s = s .replace ("$CALLSIBEGIN" , "" )
168+ s = s .replace ("$CALLSIRETPASS" , "nullptr" )
169+ s = s .replace ("\t \t \t \t $CALLSIRET\\ \n " , "" )
170+
171+ s = s .replace (" $ARG" , argtext )
172+ s = s .replace ("$CALLARGS" , callargtext )
173+ if method_info :
174+ s = s .replace ("$FILL_METHOD_INFO" , method_info )
175+ else :
176+ s = s .replace ("\t \t $FILL_METHOD_INFO\\ \n " , method_info )
177+
178+ return s
179+
180+
181+ def generate_virtuals (target ):
182+ max_versions = 12
183+
184+ txt = """/* THIS FILE IS GENERATED DO NOT EDIT */
185+ #ifndef GDEXTENSION_GDVIRTUAL_GEN_H
186+ #define GDEXTENSION_GDVIRTUAL_GEN_H
187+
188+ """
189+
190+ for i in range (max_versions + 1 ):
191+ txt += f"/* { i } Arguments */\n \n "
192+ txt += generate_virtual_version (i , False , False )
193+ txt += generate_virtual_version (i , False , True )
194+ txt += generate_virtual_version (i , True , False )
195+ txt += generate_virtual_version (i , True , True )
196+
197+ txt += "#endif // GDEXTENSION_GDVIRTUAL_GEN_H\n "
198+
199+ with open (target , "w" , encoding = "utf-8" ) as f :
200+ f .write (txt )
201+
202+
73203def get_file_list (api_filepath , output_dir , headers = False , sources = False ):
74204 api = {}
75205 files = []
@@ -81,6 +211,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
81211 source_gen_folder = Path (output_dir ) / "gen" / "src"
82212
83213 files .append (str ((core_gen_folder / "ext_wrappers.gen.inc" ).as_posix ()))
214+ files .append (str ((core_gen_folder / "gdvirtual.gen.inc" ).as_posix ()))
84215
85216 for builtin_class in api ["builtin_classes" ]:
86217 if is_pod_type (builtin_class ["name" ]):
@@ -204,6 +335,7 @@ def generate_builtin_bindings(api, output_dir, build_config):
204335 source_gen_folder .mkdir (parents = True , exist_ok = True )
205336
206337 generate_wrappers (core_gen_folder / "ext_wrappers.gen.inc" )
338+ generate_virtuals (core_gen_folder / "gdvirtual.gen.inc" )
207339
208340 # Store types beforehand.
209341 for builtin_api in api ["builtin_classes" ]:
0 commit comments