Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/extension/gdextension_manager.cpp
20897 views
1
/**************************************************************************/
2
/* gdextension_manager.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "gdextension_manager.h"
32
33
#include "core/extension/gdextension_function_loader.h"
34
#include "core/extension/gdextension_library_loader.h"
35
#include "core/extension/gdextension_special_compat_hashes.h"
36
#include "core/io/dir_access.h"
37
#include "core/io/file_access.h"
38
#include "core/object/script_language.h"
39
40
GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load) {
41
if (level >= 0) { // Already initialized up to some level.
42
int32_t minimum_level = 0;
43
if (!p_first_load) {
44
minimum_level = p_extension->get_minimum_library_initialization_level();
45
if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {
46
return LOAD_STATUS_NEEDS_RESTART;
47
}
48
}
49
// Initialize up to current level.
50
for (int32_t i = minimum_level; i <= level; i++) {
51
p_extension->initialize_library(GDExtension::InitializationLevel(i));
52
}
53
}
54
55
for (const KeyValue<String, String> &kv : p_extension->class_icon_paths) {
56
gdextension_class_icon_paths[kv.key] = kv.value;
57
}
58
59
return LOAD_STATUS_OK;
60
}
61
62
void GDExtensionManager::_finish_load_extension(const Ref<GDExtension> &p_extension) {
63
#ifdef TOOLS_ENABLED
64
// Signals that a new extension is loaded so GDScript can register new class names.
65
emit_signal("extension_loaded", p_extension);
66
#endif
67
68
if (startup_callback_called) {
69
// Extension is loading after the startup callback has already been called,
70
// so we call it now for this extension to make sure it doesn't miss it.
71
if (p_extension->startup_callback) {
72
p_extension->startup_callback();
73
}
74
}
75
}
76
77
GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref<GDExtension> &p_extension) {
78
#ifdef TOOLS_ENABLED
79
// Signals that a new extension is unloading so GDScript can unregister class names.
80
emit_signal("extension_unloading", p_extension);
81
#endif
82
83
if (!shutdown_callback_called) {
84
// Extension is unloading before the shutdown callback has been called,
85
// which means the engine hasn't shutdown yet but we want to make sure
86
// to call the shutdown callback so it doesn't miss it.
87
if (p_extension->shutdown_callback) {
88
p_extension->shutdown_callback();
89
}
90
}
91
92
if (level >= 0) { // Already initialized up to some level.
93
// Deinitialize down from current level.
94
for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) {
95
p_extension->deinitialize_library(GDExtension::InitializationLevel(i));
96
}
97
}
98
99
for (const KeyValue<String, String> &kv : p_extension->class_icon_paths) {
100
gdextension_class_icon_paths.erase(kv.key);
101
}
102
103
// Clear main loop callbacks.
104
p_extension->startup_callback = nullptr;
105
p_extension->shutdown_callback = nullptr;
106
p_extension->frame_callback = nullptr;
107
108
return LOAD_STATUS_OK;
109
}
110
111
GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) {
112
if (Engine::get_singleton()->is_recovery_mode_hint()) {
113
return LOAD_STATUS_FAILED;
114
}
115
116
Ref<GDExtensionLibraryLoader> loader;
117
loader.instantiate();
118
return load_extension_with_loader(p_path, loader);
119
}
120
121
GDExtensionManager::LoadStatus GDExtensionManager::load_extension_from_function(const String &p_path, GDExtensionConstPtr<const GDExtensionInitializationFunction> p_init_func) {
122
Ref<GDExtensionFunctionLoader> func_loader;
123
func_loader.instantiate();
124
func_loader->set_initialization_function((GDExtensionInitializationFunction)*p_init_func.data);
125
return load_extension_with_loader(p_path, func_loader);
126
}
127
128
GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(const String &p_path, const Ref<GDExtensionLoader> &p_loader) {
129
DEV_ASSERT(p_loader.is_valid());
130
131
if (gdextension_map.has(p_path)) {
132
return LOAD_STATUS_ALREADY_LOADED;
133
}
134
135
Ref<GDExtension> extension;
136
extension.instantiate();
137
Error err = extension->open_library(p_path, p_loader);
138
if (err != OK) {
139
return LOAD_STATUS_FAILED;
140
}
141
142
LoadStatus status = _load_extension_internal(extension, true);
143
if (status != LOAD_STATUS_OK) {
144
return status;
145
}
146
147
_finish_load_extension(extension);
148
149
extension->set_path(p_path);
150
gdextension_map[p_path] = extension;
151
return LOAD_STATUS_OK;
152
}
153
154
GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String &p_path) {
155
#ifndef TOOLS_ENABLED
156
ERR_FAIL_V_MSG(LOAD_STATUS_FAILED, "GDExtensions can only be reloaded in an editor build.");
157
#else
158
ERR_FAIL_COND_V_MSG(!Engine::get_singleton()->is_extension_reloading_enabled(), LOAD_STATUS_FAILED, "GDExtension reloading is disabled.");
159
160
if (Engine::get_singleton()->is_recovery_mode_hint()) {
161
return LOAD_STATUS_FAILED;
162
}
163
164
if (!gdextension_map.has(p_path)) {
165
return LOAD_STATUS_NOT_LOADED;
166
}
167
168
Ref<GDExtension> extension = gdextension_map[p_path];
169
ERR_FAIL_COND_V_MSG(!extension->is_reloadable(), LOAD_STATUS_FAILED, vformat("This GDExtension is not marked as 'reloadable' or doesn't support reloading: %s.", p_path));
170
171
LoadStatus status;
172
173
extension->prepare_reload();
174
175
// Unload library if it's open. It may not be open if the developer made a
176
// change that broke loading in a previous hot-reload attempt.
177
if (extension->is_library_open()) {
178
status = _unload_extension_internal(extension);
179
if (status != LOAD_STATUS_OK) {
180
// We need to clear these no matter what.
181
extension->clear_instance_bindings();
182
return status;
183
}
184
185
extension->clear_instance_bindings();
186
extension->close_library();
187
}
188
189
Error err = extension->open_library(p_path, extension->loader);
190
if (err != OK) {
191
return LOAD_STATUS_FAILED;
192
}
193
194
status = _load_extension_internal(extension, false);
195
if (status != LOAD_STATUS_OK) {
196
return status;
197
}
198
199
extension->finish_reload();
200
201
// Needs to come after reload is fully finished, so all objects using
202
// extension classes are in a consistent state.
203
_finish_load_extension(extension);
204
205
return LOAD_STATUS_OK;
206
#endif
207
}
208
209
GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String &p_path) {
210
if (Engine::get_singleton()->is_recovery_mode_hint()) {
211
return LOAD_STATUS_FAILED;
212
}
213
214
if (!gdextension_map.has(p_path)) {
215
return LOAD_STATUS_NOT_LOADED;
216
}
217
218
Ref<GDExtension> extension = gdextension_map[p_path];
219
220
LoadStatus status = _unload_extension_internal(extension);
221
if (status != LOAD_STATUS_OK) {
222
return status;
223
}
224
225
gdextension_map.erase(p_path);
226
return LOAD_STATUS_OK;
227
}
228
229
bool GDExtensionManager::is_extension_loaded(const String &p_path) const {
230
return gdextension_map.has(p_path);
231
}
232
233
Vector<String> GDExtensionManager::get_loaded_extensions() const {
234
Vector<String> ret;
235
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
236
ret.push_back(E.key);
237
}
238
return ret;
239
}
240
Ref<GDExtension> GDExtensionManager::get_extension(const String &p_path) {
241
HashMap<String, Ref<GDExtension>>::Iterator E = gdextension_map.find(p_path);
242
ERR_FAIL_COND_V(!E, Ref<GDExtension>());
243
return E->value;
244
}
245
246
bool GDExtensionManager::class_has_icon_path(const String &p_class) const {
247
// TODO: Check that the icon belongs to a registered class somehow.
248
return gdextension_class_icon_paths.has(p_class);
249
}
250
251
String GDExtensionManager::class_get_icon_path(const String &p_class) const {
252
// TODO: Check that the icon belongs to a registered class somehow.
253
if (gdextension_class_icon_paths.has(p_class)) {
254
return gdextension_class_icon_paths[p_class];
255
}
256
return "";
257
}
258
259
void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) {
260
if (Engine::get_singleton()->is_recovery_mode_hint()) {
261
return;
262
}
263
264
ERR_FAIL_COND(int32_t(p_level) - 1 != level);
265
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
266
E.value->initialize_library(p_level);
267
268
if (p_level == GDExtension::INITIALIZATION_LEVEL_EDITOR) {
269
for (const KeyValue<String, String> &kv : E.value->class_icon_paths) {
270
gdextension_class_icon_paths[kv.key] = kv.value;
271
}
272
}
273
}
274
level = p_level;
275
}
276
277
void GDExtensionManager::deinitialize_extensions(GDExtension::InitializationLevel p_level) {
278
if (Engine::get_singleton()->is_recovery_mode_hint()) {
279
return;
280
}
281
282
ERR_FAIL_COND(int32_t(p_level) != level);
283
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
284
E.value->deinitialize_library(p_level);
285
}
286
level = int32_t(p_level) - 1;
287
}
288
289
#ifdef TOOLS_ENABLED
290
void GDExtensionManager::track_instance_binding(void *p_token, Object *p_object) {
291
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
292
if (E.value.ptr() == p_token) {
293
if (E.value->is_reloadable()) {
294
E.value->track_instance_binding(p_object);
295
return;
296
}
297
}
298
}
299
}
300
301
void GDExtensionManager::untrack_instance_binding(void *p_token, Object *p_object) {
302
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
303
if (E.value.ptr() == p_token) {
304
if (E.value->is_reloadable()) {
305
E.value->untrack_instance_binding(p_object);
306
return;
307
}
308
}
309
}
310
}
311
312
void GDExtensionManager::_reload_all_scripts() {
313
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
314
ScriptServer::get_language(i)->reload_all_scripts();
315
}
316
}
317
#endif // TOOLS_ENABLED
318
319
void GDExtensionManager::load_extensions() {
320
if (Engine::get_singleton()->is_recovery_mode_hint()) {
321
return;
322
}
323
324
Ref<FileAccess> f = FileAccess::open(GDExtension::get_extension_list_config_file(), FileAccess::READ);
325
while (f.is_valid() && !f->eof_reached()) {
326
String s = f->get_line().strip_edges();
327
if (!s.is_empty()) {
328
LoadStatus err = load_extension(s);
329
ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, vformat("Error loading extension: '%s'.", s));
330
}
331
}
332
333
OS::get_singleton()->load_platform_gdextensions();
334
}
335
336
void GDExtensionManager::reload_extensions() {
337
#ifdef TOOLS_ENABLED
338
if (Engine::get_singleton()->is_recovery_mode_hint()) {
339
return;
340
}
341
bool reloaded = false;
342
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
343
if (!E.value->is_reloadable()) {
344
continue;
345
}
346
347
if (E.value->has_library_changed()) {
348
reloaded = true;
349
reload_extension(E.value->get_path());
350
}
351
}
352
353
if (reloaded) {
354
emit_signal("extensions_reloaded");
355
356
// Reload all scripts to clear out old references.
357
callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();
358
}
359
#endif
360
}
361
362
bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_extensions) {
363
Vector<String> extensions_added;
364
Vector<String> extensions_removed;
365
366
for (const String &E : p_extensions) {
367
if (!is_extension_loaded(E)) {
368
extensions_added.push_back(E);
369
}
370
}
371
372
Vector<String> loaded_extensions = get_loaded_extensions();
373
for (const String &loaded_extension : loaded_extensions) {
374
if (!p_extensions.has(loaded_extension)) {
375
// The extension may not have a .gdextension file.
376
const Ref<GDExtension> extension = GDExtensionManager::get_singleton()->get_extension(loaded_extension);
377
if (!extension->get_loader()->library_exists()) {
378
extensions_removed.push_back(loaded_extension);
379
}
380
}
381
}
382
383
String extension_list_config_file = GDExtension::get_extension_list_config_file();
384
if (p_extensions.size()) {
385
if (extensions_added.size() || extensions_removed.size()) {
386
// Extensions were added or removed.
387
Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);
388
for (const String &E : p_extensions) {
389
f->store_line(E);
390
}
391
}
392
} else {
393
if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) {
394
// Extensions were removed.
395
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
396
da->remove(extension_list_config_file);
397
}
398
}
399
400
bool needs_restart = false;
401
for (const String &extension : extensions_added) {
402
GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extension);
403
if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
404
needs_restart = true;
405
}
406
}
407
408
for (const String &extension : extensions_removed) {
409
GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extension);
410
if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
411
needs_restart = true;
412
}
413
}
414
415
#ifdef TOOLS_ENABLED
416
if (extensions_added.size() || extensions_removed.size()) {
417
// Emitting extensions_reloaded so EditorNode can reload Inspector and regenerate documentation.
418
emit_signal("extensions_reloaded");
419
420
// Reload all scripts to clear out old references.
421
callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();
422
}
423
#endif
424
425
return needs_restart;
426
}
427
428
void GDExtensionManager::startup() {
429
startup_callback_called = true;
430
431
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
432
const Ref<GDExtension> &extension = E.value;
433
if (extension->startup_callback) {
434
extension->startup_callback();
435
}
436
}
437
}
438
439
void GDExtensionManager::shutdown() {
440
shutdown_callback_called = true;
441
442
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
443
const Ref<GDExtension> &extension = E.value;
444
if (extension->shutdown_callback) {
445
extension->shutdown_callback();
446
}
447
}
448
}
449
450
void GDExtensionManager::frame() {
451
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
452
const Ref<GDExtension> &extension = E.value;
453
if (extension->frame_callback) {
454
extension->frame_callback();
455
}
456
}
457
}
458
459
GDExtensionManager *GDExtensionManager::get_singleton() {
460
return singleton;
461
}
462
463
void GDExtensionManager::_bind_methods() {
464
ClassDB::bind_method(D_METHOD("load_extension", "path"), &GDExtensionManager::load_extension);
465
ClassDB::bind_method(D_METHOD("load_extension_from_function", "path", "init_func"), &GDExtensionManager::load_extension_from_function);
466
ClassDB::bind_method(D_METHOD("reload_extension", "path"), &GDExtensionManager::reload_extension);
467
ClassDB::bind_method(D_METHOD("unload_extension", "path"), &GDExtensionManager::unload_extension);
468
ClassDB::bind_method(D_METHOD("is_extension_loaded", "path"), &GDExtensionManager::is_extension_loaded);
469
470
ClassDB::bind_method(D_METHOD("get_loaded_extensions"), &GDExtensionManager::get_loaded_extensions);
471
ClassDB::bind_method(D_METHOD("get_extension", "path"), &GDExtensionManager::get_extension);
472
473
BIND_ENUM_CONSTANT(LOAD_STATUS_OK);
474
BIND_ENUM_CONSTANT(LOAD_STATUS_FAILED);
475
BIND_ENUM_CONSTANT(LOAD_STATUS_ALREADY_LOADED);
476
BIND_ENUM_CONSTANT(LOAD_STATUS_NOT_LOADED);
477
BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART);
478
479
ADD_SIGNAL(MethodInfo("extensions_reloaded"));
480
ADD_SIGNAL(MethodInfo("extension_loaded", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, GDExtension::get_class_static())));
481
ADD_SIGNAL(MethodInfo("extension_unloading", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, GDExtension::get_class_static())));
482
}
483
484
GDExtensionManager::GDExtensionManager() {
485
ERR_FAIL_COND(singleton != nullptr);
486
singleton = this;
487
488
#ifndef DISABLE_DEPRECATED
489
GDExtensionSpecialCompatHashes::initialize();
490
#endif
491
}
492
493
GDExtensionManager::~GDExtensionManager() {
494
if (singleton == this) {
495
singleton = nullptr;
496
}
497
#ifndef DISABLE_DEPRECATED
498
GDExtensionSpecialCompatHashes::finalize();
499
#endif
500
}
501
502