diff --git a/bridge/jsb_class_info.cpp b/bridge/jsb_class_info.cpp index 5b240ee0..4daa4671 100644 --- a/bridge/jsb_class_info.cpp +++ b/bridge/jsb_class_info.cpp @@ -209,7 +209,9 @@ namespace jsb // signals (@signal_) { v8::Local val_test; - if (prototype->Get(p_context, jsb_symbol(environment, ClassSignals)).ToLocal(&val_test) && val_test->IsArray()) + const v8::Local class_signals_symbol = jsb_symbol(environment, ClassSignals); + if (prototype->HasOwnProperty(p_context, class_signals_symbol).ToChecked() + && prototype->Get(p_context, class_signals_symbol).ToLocal(&val_test) && val_test->IsArray()) { v8::Local collection = val_test.As(); const uint32_t len = collection->Length(); @@ -238,7 +240,9 @@ namespace jsb // detect all exported properties (which annotated with @export_) { v8::Local val_test; - if (prototype->Get(p_context, jsb_symbol(environment, ClassProperties)).ToLocal(&val_test) && val_test->IsArray()) + const v8::Local class_properties_symbol = jsb_symbol(environment, ClassProperties); + if (prototype->HasOwnProperty(p_context, class_properties_symbol).ToChecked() + && prototype->Get(p_context, class_properties_symbol).ToLocal(&val_test) && val_test->IsArray()) { const v8::Local collection = val_test.As(); const uint32_t len = collection->Length(); diff --git a/bridge/jsb_module_resolver.cpp b/bridge/jsb_module_resolver.cpp index e0ecfdb4..d3f6a587 100644 --- a/bridge/jsb_module_resolver.cpp +++ b/bridge/jsb_module_resolver.cpp @@ -447,7 +447,7 @@ namespace jsb return true; } r_source_info = {}; - JSB_LOG(Warning, "failed to check out module (absolute) %s", p_module_id); + JSB_LOG(Verbose, "module candidate not found (absolute probe): %s", p_module_id); return false; } @@ -511,7 +511,7 @@ namespace jsb return true; } - JSB_LOG(Verbose, "failed to check out module (search_path: %s) %s", p_search_path, p_module_id); + JSB_LOG(Verbose, "module candidate not found (search_path: %s) %s", p_search_path, p_module_id); return false; } diff --git a/bridge/jsb_worker.cpp b/bridge/jsb_worker.cpp index baf1d255..20d63d86 100644 --- a/bridge/jsb_worker.cpp +++ b/bridge/jsb_worker.cpp @@ -652,7 +652,7 @@ namespace jsb v8::HandleScope handle_scope(isolate); v8::Isolate::Scope isolate_scope(isolate); const WorkerID worker_id = (WorkerID) info.Data().As()->Value(); - Worker::terminate(worker_id); + Worker::request_termination(worker_id); } // worker -> master (run in worker env) @@ -1068,7 +1068,7 @@ namespace jsb return (bool) o_worker_impl; } - bool Worker::terminate(WorkerID p_id) + bool Worker::request_termination(WorkerID p_id) { bool res = false; lock_.lock(); @@ -1082,6 +1082,41 @@ namespace jsb return res; } + bool Worker::terminate(WorkerID p_id) + { + WorkerImplPtr impl; + lock_.lock(); + const bool found = worker_list_.try_get_value(p_id, impl); + if (found) + { + impl->finish(); + } + lock_.unlock(); + + if (!found) + { + return false; + } + + const Thread::ID thread_id = impl->get_thread_id(); + jsb_check(thread_id != Thread::get_caller_id()); + impl->join(); + + lock_.lock(); + const WorkerID* mapped_id = workers_.getptr(thread_id); + if (mapped_id && *mapped_id == p_id) + { + workers_.erase(thread_id); + } + if (worker_list_.is_valid_index(p_id)) + { + worker_list_.remove_at(p_id); + } + lock_.unlock(); + + return true; + } + void Worker::finish() { bool has_remaining_workers = true; @@ -1309,11 +1344,15 @@ namespace jsb jsb_throw(isolate, "bad this"); return; } - const Worker* worker = (Worker*) self->GetAlignedPointerFromInternalField(IF_Pointer); + Worker* worker = (Worker*) self->GetAlignedPointerFromInternalField(IF_Pointer); if (!Worker::terminate(worker->id_)) { JSB_WORKER_LOG(Warning, "can not terminate a dead worker"); } + else + { + worker->id_ = {}; + } } void Worker::register_(const v8::Local& p_context, const v8::Local& p_self) diff --git a/bridge/jsb_worker.h b/bridge/jsb_worker.h index 5303c134..007a2b41 100644 --- a/bridge/jsb_worker.h +++ b/bridge/jsb_worker.h @@ -115,7 +115,10 @@ namespace jsb static bool try_get_worker(WorkerID p_id, WorkerImplPtr& o_worker_impl); - // terminate a worker + // request worker termination without joining/removing it + static bool request_termination(WorkerID p_id); + + // terminate a worker from an external thread and remove it from the registry static bool terminate(WorkerID p_id); static bool parse_transfer_list( diff --git a/tests/project/tests/extend/child.ts b/tests/project/tests/extend/child.ts deleted file mode 100644 index 2917d146..00000000 --- a/tests/project/tests/extend/child.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Node } from "godot"; - -export default class Child extends Node { - _ready() { - console.log("Child ready"); - } - - childFn() { - return true; - } -} diff --git a/tests/project/tests/extend/parent.ts b/tests/project/tests/extend/parent.ts new file mode 100644 index 00000000..e795cc49 --- /dev/null +++ b/tests/project/tests/extend/parent.ts @@ -0,0 +1,18 @@ +import { Node, Variant } from "godot"; +import { createClassBinder } from "godot.annotations"; + +const bind = createClassBinder(); + +@bind() +export default class Parent extends Node { + @bind.export(Variant.Type.TYPE_INT) + accessor parentOnlyExport: number = 11; + + _ready() { + console.log("Parent ready"); + } + + parentFn() { + return true; + } +} diff --git a/tests/project/tests/extend/child.ts.uid b/tests/project/tests/extend/parent.ts.uid similarity index 100% rename from tests/project/tests/extend/child.ts.uid rename to tests/project/tests/extend/parent.ts.uid diff --git a/tests/project/tests/extend/test-extend.ts b/tests/project/tests/extend/test-extend.ts index 843bd3b7..0d3a9938 100644 --- a/tests/project/tests/extend/test-extend.ts +++ b/tests/project/tests/extend/test-extend.ts @@ -1,9 +1,72 @@ -import Child from "./child"; +import { PropertyUsageFlags, ResourceLoader, Script } from "godot"; +import Parent from "./parent"; +import { reportTestFailure } from "../test-status"; -export default class TestExtend extends Child { +const ChildCategoryName = "test-extend.ts"; +const ParentOnlyExportName = "parentOnlyExport"; + +function countScriptPropertiesInCategory(script: Script, categoryName: string, propertyName: string): number { + let count = 0; + let currentCategory = ""; + + for (const property of script.get_script_property_list()) { + const usage = property.get("usage"); + const name = property.get("name"); + if (typeof usage === "number" && (usage & PropertyUsageFlags.PROPERTY_USAGE_CATEGORY) !== 0) { + currentCategory = typeof name === "string" ? name : ""; + continue; + } + + if (currentCategory === categoryName && name === propertyName) { + count += 1; + } + } + + return count; +} + +function countScriptProperties(script: Script, propertyName: string): number { + let count = 0; + + for (const property of script.get_script_property_list()) { + const usage = property.get("usage"); + const name = property.get("name"); + if (typeof usage === "number" && (usage & PropertyUsageFlags.PROPERTY_USAGE_CATEGORY) !== 0) { + continue; + } + + if (name === propertyName) { + count += 1; + } + } + + return count; +} + +export default class TestExtend extends Parent { _ready() { - super._ready(); - console.log("TestExtend ready"); - console.assert(this.childFn()); + try { + super._ready(); + console.log("TestExtend ready"); + console.assert(this.parentFn()); + + const childScript = ResourceLoader.load("res://tests/extend/test-extend.ts"); + + if (!(childScript instanceof Script)) { + throw new Error("failed to load child TestExtend script"); + } + + const totalExportCount = countScriptProperties(childScript, ParentOnlyExportName); + const childExportCount = countScriptPropertiesInCategory(childScript, ChildCategoryName, ParentOnlyExportName); + + if (totalExportCount !== 1) { + throw new Error(`${ParentOnlyExportName} total property count mismatch: ${totalExportCount}`); + } + if (childExportCount !== 0) { + throw new Error(`${ChildCategoryName} category leaked inherited property ${ParentOnlyExportName}: ${childExportCount}`); + } + } catch (error) { + reportTestFailure("extend", error); + } } } diff --git a/weaver-editor/jsb_editor_plugin.cpp b/weaver-editor/jsb_editor_plugin.cpp index 0e43c99d..b148bbd9 100644 --- a/weaver-editor/jsb_editor_plugin.cpp +++ b/weaver-editor/jsb_editor_plugin.cpp @@ -2,6 +2,9 @@ #include "jsb_docked_panel.h" #include "jsb_export_plugin.h" +#include "core/string/char_utils.h" +#include "core/templates/hash_map.h" + #if GODOT_4_7_OR_NEWER #include "core/object/callable_mp.h" #endif @@ -183,6 +186,15 @@ String GodotJSEditorPlugin::mutate_types(const String& p_content) // Internal utility types are double underscore prefixed return p_identifier.begins_with("__"); }; + auto should_ignore_function_identifier = [&](const String& p_identifier) -> bool + { + if (should_ignore_identifier(p_identifier)) + { + return true; + } + + return !p_identifier.is_empty() && is_ascii_upper_case(p_identifier[0]); + }; // Regex obviously isn't the best tool for the job and this regex will, for example, match some generic parameter // names. However, for now, it does the job. @@ -247,6 +259,7 @@ String GodotJSEditorPlugin::mutate_types(const String& p_content) RegEx function_regex("(?m)\\b(?!(?:if|for|while|switch|catch|return|new|super|this)\\b)([a-zA-Z_]\\w*)\\s*(?:<[^>]+>)?\\s*\\("); RegEx parameter_regex("(?m)\\b([a-zA-Z_]\\w*)\\s*(?:\\?|)\\s*:"); TypedArray func_matches = function_regex.search_all(result); + HashMap function_replacements; for (int match_index = func_matches.size() - 1; match_index >= 0; match_index--) { Ref func_match = func_matches[match_index]; @@ -297,16 +310,39 @@ String GodotJSEditorPlugin::mutate_types(const String& p_content) } } - if (!should_ignore_identifier(func_identifier)) + if (!should_ignore_function_identifier(func_identifier)) { String func_replacement = jsb::internal::NamingUtil::get_member_name(func_identifier); if (func_replacement != func_identifier) { + function_replacements.insert(func_identifier, func_replacement); result = result.substr(0, func_name_start) + func_replacement + result.substr(func_name_end); } } } + RegEx typeof_value_regex("(?m)\\btypeof\\s+([a-zA-Z_]\\w*)"); + TypedArray typeof_value_matches = typeof_value_regex.search_all(result); + for (int match_index = typeof_value_matches.size() - 1; match_index >= 0; match_index--) + { + Ref match = typeof_value_matches[match_index]; + + const int start = match->get_start(1); + const int end = match->get_end(1); + const String identifier = result.substr(start, end - start); + + if (should_ignore_function_identifier(identifier) || !function_replacements.has(identifier)) + { + continue; + } + + const String replacement = function_replacements[identifier]; + if (replacement != identifier) + { + result = result.substr(0, start) + replacement + result.substr(end); + } + } + // Remove references RegEx reference_regex("(?m)^///\\s* reference_matches = reference_regex.search_all(result); diff --git a/weaver-editor/jsb_export_plugin.cpp b/weaver-editor/jsb_export_plugin.cpp index 6a75d76b..7c71061d 100644 --- a/weaver-editor/jsb_export_plugin.cpp +++ b/weaver-editor/jsb_export_plugin.cpp @@ -72,7 +72,9 @@ void GodotJSExportPlugin::get_script_resources(const String &p_dir, Vector