1 | /**************************************************************************/ |
2 | /* gdscript_cache.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 "gdscript_cache.h" |
32 | |
33 | #include "gdscript.h" |
34 | #include "gdscript_analyzer.h" |
35 | #include "gdscript_compiler.h" |
36 | #include "gdscript_parser.h" |
37 | |
38 | #include "core/io/file_access.h" |
39 | #include "core/templates/vector.h" |
40 | #include "scene/resources/packed_scene.h" |
41 | |
42 | bool GDScriptParserRef::is_valid() const { |
43 | return parser != nullptr; |
44 | } |
45 | |
46 | GDScriptParserRef::Status GDScriptParserRef::get_status() const { |
47 | return status; |
48 | } |
49 | |
50 | GDScriptParser *GDScriptParserRef::get_parser() const { |
51 | return parser; |
52 | } |
53 | |
54 | GDScriptAnalyzer *GDScriptParserRef::get_analyzer() { |
55 | if (analyzer == nullptr) { |
56 | analyzer = memnew(GDScriptAnalyzer(parser)); |
57 | } |
58 | return analyzer; |
59 | } |
60 | |
61 | Error GDScriptParserRef::raise_status(Status p_new_status) { |
62 | ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA); |
63 | |
64 | if (result != OK) { |
65 | return result; |
66 | } |
67 | |
68 | while (p_new_status > status) { |
69 | switch (status) { |
70 | case EMPTY: |
71 | status = PARSED; |
72 | result = parser->parse(GDScriptCache::get_source_code(path), path, false); |
73 | break; |
74 | case PARSED: { |
75 | status = INHERITANCE_SOLVED; |
76 | Error inheritance_result = get_analyzer()->resolve_inheritance(); |
77 | if (result == OK) { |
78 | result = inheritance_result; |
79 | } |
80 | } break; |
81 | case INHERITANCE_SOLVED: { |
82 | status = INTERFACE_SOLVED; |
83 | Error interface_result = get_analyzer()->resolve_interface(); |
84 | if (result == OK) { |
85 | result = interface_result; |
86 | } |
87 | } break; |
88 | case INTERFACE_SOLVED: { |
89 | status = FULLY_SOLVED; |
90 | Error body_result = get_analyzer()->resolve_body(); |
91 | if (result == OK) { |
92 | result = body_result; |
93 | } |
94 | } break; |
95 | case FULLY_SOLVED: { |
96 | return result; |
97 | } |
98 | } |
99 | if (result != OK) { |
100 | return result; |
101 | } |
102 | } |
103 | |
104 | return result; |
105 | } |
106 | |
107 | void GDScriptParserRef::clear() { |
108 | if (cleared) { |
109 | return; |
110 | } |
111 | cleared = true; |
112 | |
113 | if (parser != nullptr) { |
114 | memdelete(parser); |
115 | } |
116 | |
117 | if (analyzer != nullptr) { |
118 | memdelete(analyzer); |
119 | } |
120 | } |
121 | |
122 | GDScriptParserRef::~GDScriptParserRef() { |
123 | clear(); |
124 | |
125 | MutexLock lock(GDScriptCache::singleton->mutex); |
126 | GDScriptCache::singleton->parser_map.erase(path); |
127 | } |
128 | |
129 | GDScriptCache *GDScriptCache::singleton = nullptr; |
130 | |
131 | void GDScriptCache::move_script(const String &p_from, const String &p_to) { |
132 | if (singleton == nullptr || p_from == p_to) { |
133 | return; |
134 | } |
135 | |
136 | MutexLock lock(singleton->mutex); |
137 | |
138 | if (singleton->cleared) { |
139 | return; |
140 | } |
141 | |
142 | for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { |
143 | if (E.value.has(p_from)) { |
144 | E.value.insert(p_to); |
145 | E.value.erase(p_from); |
146 | } |
147 | } |
148 | |
149 | if (singleton->parser_map.has(p_from) && !p_from.is_empty()) { |
150 | singleton->parser_map[p_to] = singleton->parser_map[p_from]; |
151 | } |
152 | singleton->parser_map.erase(p_from); |
153 | |
154 | if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) { |
155 | singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from]; |
156 | } |
157 | singleton->shallow_gdscript_cache.erase(p_from); |
158 | |
159 | if (singleton->full_gdscript_cache.has(p_from) && !p_from.is_empty()) { |
160 | singleton->full_gdscript_cache[p_to] = singleton->full_gdscript_cache[p_from]; |
161 | } |
162 | singleton->full_gdscript_cache.erase(p_from); |
163 | } |
164 | |
165 | void GDScriptCache::remove_script(const String &p_path) { |
166 | if (singleton == nullptr) { |
167 | return; |
168 | } |
169 | |
170 | MutexLock lock(singleton->mutex); |
171 | |
172 | if (singleton->cleared) { |
173 | return; |
174 | } |
175 | |
176 | for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { |
177 | if (!E.value.has(p_path)) { |
178 | continue; |
179 | } |
180 | E.value.erase(p_path); |
181 | } |
182 | |
183 | GDScriptCache::clear_unreferenced_packed_scenes(); |
184 | |
185 | if (singleton->parser_map.has(p_path)) { |
186 | singleton->parser_map[p_path]->clear(); |
187 | singleton->parser_map.erase(p_path); |
188 | } |
189 | |
190 | singleton->dependencies.erase(p_path); |
191 | singleton->shallow_gdscript_cache.erase(p_path); |
192 | singleton->full_gdscript_cache.erase(p_path); |
193 | } |
194 | |
195 | Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) { |
196 | MutexLock lock(singleton->mutex); |
197 | Ref<GDScriptParserRef> ref; |
198 | if (!p_owner.is_empty()) { |
199 | singleton->dependencies[p_owner].insert(p_path); |
200 | } |
201 | if (singleton->parser_map.has(p_path)) { |
202 | ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]); |
203 | if (ref.is_null()) { |
204 | r_error = ERR_INVALID_DATA; |
205 | return ref; |
206 | } |
207 | } else { |
208 | if (!FileAccess::exists(p_path)) { |
209 | r_error = ERR_FILE_NOT_FOUND; |
210 | return ref; |
211 | } |
212 | GDScriptParser *parser = memnew(GDScriptParser); |
213 | ref.instantiate(); |
214 | ref->parser = parser; |
215 | ref->path = p_path; |
216 | singleton->parser_map[p_path] = ref.ptr(); |
217 | } |
218 | r_error = ref->raise_status(p_status); |
219 | |
220 | return ref; |
221 | } |
222 | |
223 | String GDScriptCache::get_source_code(const String &p_path) { |
224 | Vector<uint8_t> source_file; |
225 | Error err; |
226 | Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); |
227 | ERR_FAIL_COND_V(err, "" ); |
228 | |
229 | uint64_t len = f->get_length(); |
230 | source_file.resize(len + 1); |
231 | uint64_t r = f->get_buffer(source_file.ptrw(), len); |
232 | ERR_FAIL_COND_V(r != len, "" ); |
233 | source_file.write[len] = 0; |
234 | |
235 | String source; |
236 | if (source.parse_utf8((const char *)source_file.ptr()) != OK) { |
237 | ERR_FAIL_V_MSG("" , "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode." ); |
238 | } |
239 | return source; |
240 | } |
241 | |
242 | Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) { |
243 | MutexLock lock(singleton->mutex); |
244 | if (!p_owner.is_empty()) { |
245 | singleton->dependencies[p_owner].insert(p_path); |
246 | } |
247 | if (singleton->full_gdscript_cache.has(p_path)) { |
248 | return singleton->full_gdscript_cache[p_path]; |
249 | } |
250 | if (singleton->shallow_gdscript_cache.has(p_path)) { |
251 | return singleton->shallow_gdscript_cache[p_path]; |
252 | } |
253 | |
254 | Ref<GDScript> script; |
255 | script.instantiate(); |
256 | script->set_path(p_path, true); |
257 | r_error = script->load_source_code(p_path); |
258 | |
259 | if (r_error) { |
260 | return Ref<GDScript>(); // Returns null and does not cache when the script fails to load. |
261 | } |
262 | |
263 | Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error); |
264 | if (r_error == OK) { |
265 | GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true); |
266 | } |
267 | |
268 | singleton->shallow_gdscript_cache[p_path] = script; |
269 | return script; |
270 | } |
271 | |
272 | Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner, bool p_update_from_disk) { |
273 | MutexLock lock(singleton->mutex); |
274 | |
275 | if (!p_owner.is_empty()) { |
276 | singleton->dependencies[p_owner].insert(p_path); |
277 | } |
278 | |
279 | Ref<GDScript> script; |
280 | r_error = OK; |
281 | if (singleton->full_gdscript_cache.has(p_path)) { |
282 | script = singleton->full_gdscript_cache[p_path]; |
283 | if (!p_update_from_disk) { |
284 | return script; |
285 | } |
286 | } |
287 | |
288 | if (script.is_null()) { |
289 | script = get_shallow_script(p_path, r_error); |
290 | if (r_error) { |
291 | return script; |
292 | } |
293 | } |
294 | |
295 | if (p_update_from_disk) { |
296 | r_error = script->load_source_code(p_path); |
297 | if (r_error) { |
298 | return script; |
299 | } |
300 | } |
301 | |
302 | r_error = script->reload(true); |
303 | if (r_error) { |
304 | return script; |
305 | } |
306 | |
307 | singleton->full_gdscript_cache[p_path] = script; |
308 | singleton->shallow_gdscript_cache.erase(p_path); |
309 | |
310 | return script; |
311 | } |
312 | |
313 | Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) { |
314 | MutexLock lock(singleton->mutex); |
315 | |
316 | if (singleton->full_gdscript_cache.has(p_path)) { |
317 | return singleton->full_gdscript_cache[p_path]; |
318 | } |
319 | |
320 | if (singleton->shallow_gdscript_cache.has(p_path)) { |
321 | return singleton->shallow_gdscript_cache[p_path]; |
322 | } |
323 | |
324 | return Ref<GDScript>(); |
325 | } |
326 | |
327 | Error GDScriptCache::finish_compiling(const String &p_owner) { |
328 | MutexLock lock(singleton->mutex); |
329 | |
330 | // Mark this as compiled. |
331 | Ref<GDScript> script = get_cached_script(p_owner); |
332 | singleton->full_gdscript_cache[p_owner] = script; |
333 | singleton->shallow_gdscript_cache.erase(p_owner); |
334 | |
335 | HashSet<String> depends = singleton->dependencies[p_owner]; |
336 | |
337 | Error err = OK; |
338 | for (const String &E : depends) { |
339 | Error this_err = OK; |
340 | // No need to save the script. We assume it's already referenced in the owner. |
341 | get_full_script(E, this_err); |
342 | |
343 | if (this_err != OK) { |
344 | err = this_err; |
345 | } |
346 | } |
347 | |
348 | singleton->dependencies.erase(p_owner); |
349 | |
350 | return err; |
351 | } |
352 | |
353 | void GDScriptCache::add_static_script(Ref<GDScript> p_script) { |
354 | ERR_FAIL_COND_MSG(p_script.is_null(), "Trying to cache empty script as static." ); |
355 | ERR_FAIL_COND_MSG(!p_script->is_valid(), "Trying to cache non-compiled script as static." ); |
356 | singleton->static_gdscript_cache[p_script->get_fully_qualified_name()] = p_script; |
357 | } |
358 | |
359 | void GDScriptCache::remove_static_script(const String &p_fqcn) { |
360 | singleton->static_gdscript_cache.erase(p_fqcn); |
361 | } |
362 | |
363 | Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) { |
364 | MutexLock lock(singleton->mutex); |
365 | |
366 | if (singleton->packed_scene_cache.has(p_path)) { |
367 | singleton->packed_scene_dependencies[p_path].insert(p_owner); |
368 | return singleton->packed_scene_cache[p_path]; |
369 | } |
370 | |
371 | Ref<PackedScene> scene = ResourceCache::get_ref(p_path); |
372 | if (scene.is_valid()) { |
373 | singleton->packed_scene_cache[p_path] = scene; |
374 | singleton->packed_scene_dependencies[p_path].insert(p_owner); |
375 | return scene; |
376 | } |
377 | scene.instantiate(); |
378 | |
379 | r_error = OK; |
380 | if (p_path.is_empty()) { |
381 | r_error = ERR_FILE_BAD_PATH; |
382 | return scene; |
383 | } |
384 | |
385 | scene->set_path(p_path); |
386 | singleton->packed_scene_cache[p_path] = scene; |
387 | singleton->packed_scene_dependencies[p_path].insert(p_owner); |
388 | |
389 | scene->reload_from_file(); |
390 | return scene; |
391 | } |
392 | |
393 | void GDScriptCache::clear_unreferenced_packed_scenes() { |
394 | if (singleton == nullptr) { |
395 | return; |
396 | } |
397 | |
398 | MutexLock lock(singleton->mutex); |
399 | |
400 | if (singleton->cleared) { |
401 | return; |
402 | } |
403 | |
404 | for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { |
405 | if (E.value.size() > 0 || !ResourceLoader::is_imported(E.key)) { |
406 | continue; |
407 | } |
408 | |
409 | singleton->packed_scene_dependencies.erase(E.key); |
410 | singleton->packed_scene_cache.erase(E.key); |
411 | } |
412 | } |
413 | |
414 | void GDScriptCache::clear() { |
415 | if (singleton == nullptr) { |
416 | return; |
417 | } |
418 | |
419 | MutexLock lock(singleton->mutex); |
420 | |
421 | if (singleton->cleared) { |
422 | return; |
423 | } |
424 | singleton->cleared = true; |
425 | |
426 | RBSet<Ref<GDScriptParserRef>> parser_map_refs; |
427 | for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) { |
428 | parser_map_refs.insert(E.value); |
429 | } |
430 | |
431 | for (Ref<GDScriptParserRef> &E : parser_map_refs) { |
432 | if (E.is_valid()) |
433 | E->clear(); |
434 | } |
435 | |
436 | singleton->packed_scene_dependencies.clear(); |
437 | singleton->packed_scene_cache.clear(); |
438 | |
439 | parser_map_refs.clear(); |
440 | singleton->parser_map.clear(); |
441 | singleton->shallow_gdscript_cache.clear(); |
442 | singleton->full_gdscript_cache.clear(); |
443 | |
444 | singleton->packed_scene_cache.clear(); |
445 | singleton->packed_scene_dependencies.clear(); |
446 | } |
447 | |
448 | GDScriptCache::GDScriptCache() { |
449 | singleton = this; |
450 | } |
451 | |
452 | GDScriptCache::~GDScriptCache() { |
453 | if (!cleared) { |
454 | clear(); |
455 | } |
456 | singleton = nullptr; |
457 | } |
458 | |