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