Skip to content

Commit 64f5ee8

Browse files
feat: Godot String methods now exposed as static methods on String
This doesn't change anything about how strings are used within JavaScript i.e. we're still using JS native string type, not Godot's String. However, Godot's String class has many utility functions, some of them static and some of them as instance methods. These are now all available for consumption in JavaScript. Godot String instance methods are mapped to static methods that take a `target: string` as their first parameter. In general, if there's an equivalent native JS string method, you should always use it instead since it will be much more performant.
1 parent 0514427 commit 64f5ee8

File tree

5 files changed

+312
-15
lines changed

5 files changed

+312
-15
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"@godot-js/editor": patch
3+
---
4+
5+
**Feature:** Godot String methods now exposed as static methods on `String`.
6+
7+
This doesn't change anything about how strings are used within
8+
JavaScript i.e. we're still using JS native string type, not
9+
Godot's String. However, Godot's String class has many utility
10+
functions, some of them static and some of them as instance
11+
methods. These are now all available for consumption in JavaScript.
12+
Godot String instance methods are mapped to static methods that
13+
take a `target: string` as their first parameter.
14+
15+
In general, if there's an equivalent native JS string method, you
16+
should always use it instead since it will be much more performant.

bridge/jsb_editor_utility_funcs.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,121 @@ namespace jsb
693693
return class_info_obj;
694694
}
695695

696+
697+
template<typename T>
698+
static v8::Local<v8::Value> generate_primitive_type_utilities(v8::Isolate* isolate, const v8::Local<v8::Context>& context)
699+
{
700+
constexpr static Variant::Type TYPE = GetTypeInfo<T>::VARIANT_TYPE;
701+
v8::Local<v8::Object> class_info_obj = v8::Object::New(isolate);
702+
String class_name = internal::NamingUtil::get_class_name(Variant::get_type_name(TYPE));
703+
set_field(isolate, context, class_info_obj, "name", class_name);
704+
set_field(isolate, context, class_info_obj, "type", TYPE);
705+
if (Variant::has_indexing(TYPE))
706+
{
707+
set_field(isolate, context, class_info_obj, "element_type", Variant::get_indexed_element_type(TYPE));
708+
}
709+
set_field(isolate, context, class_info_obj, "is_keyed", Variant::is_keyed(TYPE));
710+
711+
// methods
712+
{
713+
JSB_HANDLE_SCOPE(isolate);
714+
715+
List<StringName> methods;
716+
Variant::get_builtin_method_list(TYPE, &methods);
717+
v8::Local<v8::Array> methods_obj = v8::Array::New(isolate, (int) methods.size());
718+
set_field(isolate, context, class_info_obj, "methods", methods_obj);
719+
int index = 0;
720+
for (const StringName& name : methods)
721+
{
722+
JSB_HANDLE_SCOPE(isolate);
723+
MethodInfo method_info;
724+
method_info.name = name;
725+
method_info.flags = METHOD_FLAG_STATIC;
726+
if (Variant::is_builtin_method_vararg(TYPE, name)) method_info.flags |= METHOD_FLAG_VARARG;
727+
method_info.return_val.type = Variant::get_builtin_method_return_type(TYPE, name);
728+
729+
if (!Variant::is_builtin_method_static(TYPE, name))
730+
{
731+
PropertyInfo prop_info;
732+
prop_info.name = "target";
733+
prop_info.type = TYPE;
734+
method_info.arguments.push_back(prop_info);
735+
}
736+
737+
for (int i = 0, n = Variant::get_builtin_method_argument_count(TYPE, name); i < n; ++i)
738+
{
739+
PropertyInfo prop_info;
740+
prop_info.name = Variant::get_builtin_method_argument_name(TYPE, name, i);
741+
prop_info.type = Variant::get_builtin_method_argument_type(TYPE, name, i);
742+
method_info.arguments.push_back(prop_info);
743+
}
744+
method_info.default_arguments = Variant::get_builtin_method_default_arguments(TYPE, name);
745+
if (Variant::is_builtin_method_const(TYPE, name)) method_info.flags |= METHOD_FLAG_CONST;
746+
if (Variant::is_builtin_method_vararg(TYPE, name)) method_info.flags |= METHOD_FLAG_VARARG;
747+
const bool has_return_value = Variant::has_builtin_method_return_value(TYPE, name);
748+
v8::Local<v8::Object> method_info_obj = v8::Object::New(isolate);
749+
build_method_info(isolate, context, method_info, has_return_value, method_info_obj);
750+
methods_obj->Set(context, index++, method_info_obj).Check();
751+
}
752+
}
753+
754+
// enums
755+
{
756+
JSB_HANDLE_SCOPE(isolate);
757+
758+
List<StringName> enums;
759+
Variant::get_enums_for_type(TYPE, &enums);
760+
v8::Local<v8::Array> enums_obj = v8::Array::New(isolate, (int) enums.size());
761+
set_field(isolate, context, class_info_obj, "enums", enums_obj);
762+
int index = 0;
763+
for (const StringName& enum_name : enums)
764+
{
765+
JSB_HANDLE_SCOPE(isolate);
766+
List<StringName> enumerations;
767+
Variant::get_enumerations_for_enum(TYPE, enum_name, &enumerations);
768+
ClassDB::ClassInfo::EnumInfo enum_info;
769+
for (const StringName& enumeration : enumerations)
770+
{
771+
enum_info.constants.push_back(enumeration);
772+
}
773+
v8::Local<v8::Object> enum_info_obj = v8::Object::New(isolate);
774+
set_field(isolate, context, enum_info_obj, "name", enum_name);
775+
build_enum_info(isolate, context, TYPE, enum_name, enum_info, enum_info_obj);
776+
enums_obj->Set(context, index++, enum_info_obj).Check();
777+
}
778+
}
779+
780+
// constants
781+
{
782+
JSB_HANDLE_SCOPE(isolate);
783+
784+
List<StringName> constants;
785+
Variant::get_constants_for_type(TYPE, &constants);
786+
v8::Local<v8::Array> constants_obj = v8::Array::New(isolate, (int) constants.size());
787+
set_field(isolate, context, class_info_obj, "constants", constants_obj);
788+
int index = 0;
789+
for (const StringName& constant : constants)
790+
{
791+
JSB_HANDLE_SCOPE(isolate);
792+
v8::Local<v8::Object> constant_info_obj = v8::Object::New(isolate);
793+
const Variant constant_value = Variant::get_constant_value(TYPE, constant);
794+
795+
set_field(isolate, context, constant_info_obj, "name", constant);
796+
set_field(isolate, context, constant_info_obj, "type", constant_value.get_type());
797+
switch (constant_value.get_type())
798+
{
799+
case Variant::BOOL: set_field(isolate, context, constant_info_obj, "value", (bool) constant_value); break;
800+
case Variant::INT: set_field(isolate, context, constant_info_obj, "value", (int64_t) constant_value); break;
801+
case Variant::FLOAT: set_field(isolate, context, constant_info_obj, "value", (double) constant_value); break;
802+
default: break;
803+
}
804+
constants_obj->Set(context, index++, constant_info_obj).Check();
805+
}
806+
}
807+
808+
return class_info_obj;
809+
}
810+
696811
static void _get_class_doc(const v8::FunctionCallbackInfo<v8::Value>& info)
697812
{
698813
v8::Isolate* isolate = info.GetIsolate();
@@ -853,6 +968,11 @@ namespace jsb
853968
# include "jsb_primitive_types.def.h"
854969
#pragma pop_macro("DEF")
855970

971+
{
972+
JSB_HANDLE_SCOPE(isolate);
973+
array->Set(context, index++, generate_primitive_type_utilities<String>(isolate, context)).Check();
974+
}
975+
856976
info.GetReturnValue().Set(array);
857977
}
858978

bridge/jsb_primitive_bindings_reflect.cpp

Lines changed: 161 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,10 @@ namespace jsb
448448

449449
template<bool HasReturnValueT>
450450
static void call_builtin_function(Variant* self, const internal::FBuiltinMethodInfo& method_info,
451-
const v8::FunctionCallbackInfo<v8::Value>& info, v8::Isolate* isolate, const v8::Local<v8::Context>& context)
451+
const v8::FunctionCallbackInfo<v8::Value>& info, v8::Isolate* isolate, const v8::Local<v8::Context>& context,
452+
const bool utility = false)
452453
{
453-
const int argc = info.Length();
454+
const int argc = utility ? info.Length() - 1 : info.Length();
454455
if (!method_info.check_argc(argc))
455456
{
456457
jsb_throw(isolate, "num of arguments does not meet the requirement");
@@ -470,7 +471,7 @@ namespace jsb
470471
{
471472
if (index < argc)
472473
{
473-
if (TypeConvert::js_to_gd_var(isolate, context, info[index], method_info.argument_types[index], args[index]))
474+
if (TypeConvert::js_to_gd_var(isolate, context, info[utility ? index + 1 : index], method_info.argument_types[index], args[index]))
474475
{
475476
continue;
476477
}
@@ -488,14 +489,14 @@ namespace jsb
488489
}
489490
else
490491
{
491-
if (TypeConvert::js_to_gd_var(isolate, context, info[index], args[index]))
492+
if (TypeConvert::js_to_gd_var(isolate, context, info[utility ? index + 1 : index], args[index]))
492493
{
493494
continue;
494495
}
495496
}
496497

497498
// revert all constructors
498-
const String error_message = jsb_errorf("bad argument: %d", index);
499+
const String error_message = jsb_errorf("bad argument: %d", utility ? index + 1 : index);
499500
while (index >= 0) { args[index--].~Variant(); }
500501
impl::Helper::throw_error(isolate, error_message);
501502
return;
@@ -563,6 +564,30 @@ namespace jsb
563564
call_builtin_function<HasReturnValueT>(nullptr, method_info, info, isolate, context);
564565
}
565566

567+
template<Variant::Type VariantT, bool HasReturnValueT>
568+
static void _utility_method(const v8::FunctionCallbackInfo<v8::Value>& info)
569+
{
570+
v8::Isolate* isolate = info.GetIsolate();
571+
572+
if (info.Length() < 1 || info[0]->IsObject() || info[0]->IsNull())
573+
{
574+
jsb_throw(isolate, "utility methods' first argument must be a JS primitive");
575+
return;
576+
}
577+
578+
const v8::Local<v8::Context> context = isolate->GetCurrentContext();
579+
const internal::FBuiltinMethodInfo& method_info = GetVariantInfoCollection(Environment::wrap(context)).methods[info.Data().As<v8::Int32>()->Value()];
580+
581+
Variant variant;
582+
if (!TypeConvert::js_to_gd_var(isolate, context, info[0], VariantT, variant))
583+
{
584+
jsb_throw(isolate, "Failed to convert utility function JS primitive to the required Godot variant type");
585+
return;
586+
}
587+
588+
call_builtin_function<HasReturnValueT>(&variant, method_info, info, isolate, context, true);
589+
}
590+
566591
static impl::ClassBuilder get_class_builder(const ClassRegister& p_env, const NativeClassID p_class_id, const StringName& p_class_name)
567592
{
568593
JSB_DEFINE_FAST_CONSTRUCTOR(Vector2, p_class_id, p_class_name);
@@ -892,6 +917,136 @@ namespace jsb
892917
return class_info;
893918
}
894919
}
920+
921+
static void utility_noop_constructor(const v8::FunctionCallbackInfo<v8::Value>& _info)
922+
{
923+
}
924+
925+
// Expose primitive instance methods as static utility functions for variant types not exposed to JS e.g. String
926+
static NativeClassInfoPtr reflect_bind_utilities(const ClassRegister& p_env, NativeClassID* r_class_id = nullptr)
927+
{
928+
const StringName& class_name = internal::NamingUtil::get_class_name(p_env.type_name);
929+
930+
if (class_name != p_env.type_name)
931+
{
932+
internal::StringNames::get_singleton().add_replacement(p_env.type_name, class_name);
933+
}
934+
935+
const NativeClassID class_id = p_env->add_native_class(NativeClassType::GodotPrimitive, class_name);
936+
937+
v8::Isolate* isolate = p_env->get_isolate();
938+
NativeClassInfoPtr class_info = p_env->get_native_class(class_id);
939+
impl::ClassBuilder class_builder = impl::ClassBuilder::New<0>(isolate, class_info->name, &utility_noop_constructor, *class_id);
940+
941+
auto static_builder = class_builder.Static();
942+
943+
// methods
944+
{
945+
List<StringName> methods;
946+
Variant::get_builtin_method_list(TYPE, &methods);
947+
948+
for (const StringName& name : methods)
949+
{
950+
const int argument_count = Variant::get_builtin_method_argument_count(TYPE, name);
951+
const bool has_return_value = Variant::has_builtin_method_return_value(TYPE, name);
952+
const Variant::Type return_type = Variant::get_builtin_method_return_type(TYPE, name);
953+
String member_name = internal::NamingUtil::get_member_name(name);
954+
955+
if (member_name == "length")
956+
{
957+
// We can't bind a property named .length to a function in JS because it clashes with a built-in
958+
// property.
959+
member_name = "length_";
960+
}
961+
962+
// convert method info, and store
963+
const int collection_index = (int) GetVariantInfoCollection(p_env.env).methods.size();
964+
GetVariantInfoCollection(p_env.env).methods.append({});
965+
internal::FBuiltinMethodInfo& method_info = GetVariantInfoCollection(p_env.env).methods.write[collection_index];
966+
method_info.set_debug_name(member_name);
967+
method_info.builtin_func = Variant::get_validated_builtin_method(TYPE, name);
968+
method_info.return_type = return_type;
969+
method_info.default_arguments = Variant::get_builtin_method_default_arguments(TYPE, name);
970+
#if GODOT_4_5_OR_NEWER
971+
method_info.argument_types.resize_initialized(argument_count);
972+
#else
973+
method_info.argument_types.resize_zeroed(argument_count);
974+
#endif
975+
method_info.is_vararg = Variant::is_builtin_method_vararg(TYPE, name);
976+
for (int argument_index = 0; argument_index < argument_count; ++argument_index)
977+
{
978+
const Variant::Type type = Variant::get_builtin_method_argument_type(TYPE, name, argument_index);
979+
method_info.argument_types.write[argument_index] = type;
980+
}
981+
982+
// function wrapper
983+
if (has_return_value)
984+
{
985+
if (Variant::is_builtin_method_static(TYPE, name))
986+
{
987+
static_builder.Method(member_name, _static_method<true>, collection_index);
988+
}
989+
else
990+
{
991+
static_builder.Method(member_name, _utility_method<TYPE, true>, collection_index);
992+
}
993+
}
994+
else if (Variant::is_builtin_method_static(TYPE, name))
995+
{
996+
static_builder.Method(member_name, _static_method<false>, collection_index);
997+
}
998+
else
999+
{
1000+
static_builder.Method(member_name, _utility_method<TYPE, false>, collection_index);
1001+
}
1002+
}
1003+
1004+
ReflectAdditionalMethodRegister<T>::register_(class_builder);
1005+
}
1006+
1007+
// enums
1008+
HashSet<StringName> enum_constants;
1009+
{
1010+
List<StringName> enums;
1011+
Variant::get_enums_for_type(TYPE, &enums);
1012+
for (const StringName& enum_name : enums)
1013+
{
1014+
String exposed_enum_name = internal::NamingUtil::get_enum_name(enum_name);
1015+
auto enum_decl = static_builder.Enum(exposed_enum_name);
1016+
List<StringName> enumerations;
1017+
Variant::get_enumerations_for_enum(TYPE, enum_name, &enumerations);
1018+
for (const StringName& enumeration : enumerations)
1019+
{
1020+
bool r_valid;
1021+
const int enum_value = Variant::get_enum_value(TYPE, enum_name, enumeration, &r_valid);
1022+
jsb_check(r_valid);
1023+
enum_decl.Value(internal::NamingUtil::get_enum_value_name(enumeration), enum_value);
1024+
enum_constants.insert(enumeration);
1025+
}
1026+
}
1027+
}
1028+
1029+
// constants
1030+
{
1031+
List<StringName> constants;
1032+
Variant::get_constants_for_type(TYPE, &constants);
1033+
for (const StringName& constant : constants)
1034+
{
1035+
// exclude all enum constants
1036+
if (enum_constants.has(constant)) continue;
1037+
static_builder.LazyProperty(internal::NamingUtil::get_constant_name(constant), _get_constant_value_lazy);
1038+
}
1039+
}
1040+
1041+
{
1042+
if (r_class_id) *r_class_id = class_id;
1043+
1044+
NativeClassInfoPtr class_info = p_env.env->get_native_class(class_id);
1045+
class_info->clazz = class_builder.Build();
1046+
jsb_check(!class_info->clazz.IsEmpty());
1047+
return class_info;
1048+
}
1049+
}
8951050
};
8961051

8971052
void register_primitive_bindings_reflect(Environment* p_env)
@@ -902,6 +1057,7 @@ namespace jsb
9021057
# include "jsb_primitive_types.def.h"
9031058
#pragma pop_macro("DEF")
9041059

1060+
p_env->add_class_register(GetTypeInfo<String>::VARIANT_TYPE, &VariantBind<String>::reflect_bind_utilities);
9051061
}
9061062
}
9071063

scripts/jsb.editor/src/jsb.editor.codegen.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3317,18 +3317,23 @@ export class TSDCodeGen {
33173317
}
33183318
}
33193319
}
3320-
for (let constructor_info of cls.constructors) {
3321-
class_cg.constructor_(constructor_info);
3320+
if (cls.constructors) {
3321+
for (let constructor_info of cls.constructors) {
3322+
class_cg.constructor_(constructor_info);
3323+
}
33223324
}
3323-
33243325
for (let method_info of cls.methods) {
33253326
class_cg.ordinary_method_(method_info);
33263327
}
3327-
for (let operator_info of cls.operators) {
3328-
class_cg.operator_(operator_info);
3328+
if (cls.operators) {
3329+
for (let operator_info of cls.operators) {
3330+
class_cg.operator_(operator_info);
3331+
}
33293332
}
3330-
for (let property_info of cls.properties) {
3331-
class_cg.primitive_property_(property_info);
3333+
if (cls.properties) {
3334+
for (let property_info of cls.properties) {
3335+
class_cg.primitive_property_(property_info);
3336+
}
33323337
}
33333338
class_cg.finish();
33343339
}

0 commit comments

Comments
 (0)