Skip to content

Commit fbc40b6

Browse files
committed
Implement script workflow
Godot call javascript is working [ci skip]
1 parent 7c68bbe commit fbc40b6

File tree

9 files changed

+91
-68
lines changed

9 files changed

+91
-68
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export default class MySprite extends godot.Sprite {
4747
}
4848
}
4949
```
50-
2. Save the script with extension `.jsx`
50+
2. Save the script with extension `.msx`
5151
3. Attach the script file to the node or resource object like you do with GDScript
5252

5353
##### How to export signals
@@ -179,17 +179,17 @@ Label.Align.ALIGN_LEFT | godot.Label.Align.ALIGN_LEFT
179179
}
180180
```
181181

182-
### TypeScript support and JSX code completion
182+
### TypeScript support
183183
- Run the menu command `Project > Tools > JavaScript > Generate TypeScript Project` from the godot editor to generate a TypeScript project
184184
- Run `tsc -w -p .` under your project folder in the terminal to compile scripts
185185

186186
#### Code completion
187-
- Code completion in TSX will automatically work once the TypeScript project is generated by the above steps.
188-
- Code completion in JSX in VS Code is achieved by the property `"types": "./godot.d.ts"` in the generated package.json file of the TypeScript project. The `godot.d.ts` file can be generated alone via the `Project > Tools > ECMAScript > Generate TypeScript Declaration File` editor menu option and added to a `package.json` file manually to achieve this without a full TypeScript project.
187+
- Code completion in TS will automatically work once the TypeScript project is generated by the above steps.
188+
- Code completion in VSCode is achieved by the property `"types": "./godot.d.ts"` in the generated package.json file of the TypeScript project. The `godot.d.ts` file can be generated alone via the `Project > Tools > ECMAScript > Generate TypeScript Declaration File` editor menu option and added to a `package.json` file manually to achieve this without a full TypeScript project.
189189

190190
#### Example TypeScript Usage
191191

192-
Make sure the file with extension '.tsx' so it can be compiled to a `.jsx` file then we can attach it to a node in godot editor.
192+
Compile your `ts` script to a `.mjs` file then we can attach it to a node in godot editor.
193193

194194
Most of the `register` functions are available as various decorators as seen below.
195195

javascript.cpp

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -231,18 +231,20 @@ bool JavaScript::is_valid() const {
231231
void JavaScript::_bind_methods() {
232232
}
233233

234-
Ref<Resource> ResourceFormatLoaderJavaScript::load(const String &p_path, const String &p_original_path, Error *r_error) {
234+
Ref<Resource> ResourceFormatLoaderJavaScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
235235
Error err = OK;
236236
Ref<JavaScriptModule> module = ResourceFormatLoaderJavaScriptModule::load_static(p_path, p_original_path, &err);
237-
if (r_error) *r_error = err;
237+
if (r_error)
238+
*r_error = err;
238239
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load script file '" + p_path + "'.");
239240
Ref<JavaScript> script;
240241
script.instantiate();
241242
script->set_script_path(p_path);
242243
script->bytecode = module->get_bytecode();
243244
script->set_source_code(module->get_source_code());
244245
err = script->reload();
245-
if (r_error) *r_error = err;
246+
if (r_error)
247+
*r_error = err;
246248
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Parse source code from file '" + p_path + "' failed.");
247249
#ifdef TOOLS_ENABLED
248250
JavaScriptLanguage::get_singleton()->get_scripts().insert(script);
@@ -252,8 +254,6 @@ Ref<Resource> ResourceFormatLoaderJavaScript::load(const String &p_path, const S
252254

253255
void ResourceFormatLoaderJavaScript::get_recognized_extensions(List<String> *p_extensions) const {
254256
p_extensions->push_front(EXT_JSCLASS);
255-
p_extensions->push_front(EXT_JSCLASS_BYTECODE);
256-
p_extensions->push_front(EXT_JSCLASS_ENCRYPTED);
257257
}
258258

259259
void ResourceFormatLoaderJavaScript::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
@@ -266,11 +266,16 @@ bool ResourceFormatLoaderJavaScript::handles_type(const String &p_type) const {
266266

267267
String ResourceFormatLoaderJavaScript::get_resource_type(const String &p_path) const {
268268
String el = p_path.get_extension().to_lower();
269-
if (el == EXT_JSCLASS || el == EXT_JSCLASS_BYTECODE || el == EXT_JSCLASS_ENCRYPTED) return JavaScript::get_class_static();
269+
if (el == EXT_JSCLASS)
270+
return JavaScript::get_class_static();
270271
return "";
271272
}
272273

273-
Error ResourceFormatSaverJavaScript::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) {
274+
bool ResourceFormatLoaderJavaScript::recognize_path(const String &p_path, const String &p_for_type) const {
275+
return p_path.get_extension() == EXT_JSCLASS;
276+
}
277+
278+
Error ResourceFormatSaverJavaScript::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
274279
Ref<JavaScript> script = p_resource;
275280
ERR_FAIL_COND_V(script.is_null(), ERR_INVALID_PARAMETER);
276281

@@ -321,8 +326,6 @@ Ref<Resource> ResourceFormatLoaderJavaScriptModule::load(const String &p_path, c
321326

322327
void ResourceFormatLoaderJavaScriptModule::get_recognized_extensions(List<String> *p_extensions) const {
323328
p_extensions->push_front(EXT_JSMODULE);
324-
p_extensions->push_front(EXT_JSMODULE_BYTECODE);
325-
p_extensions->push_front(EXT_JSMODULE_ENCRYPTED);
326329
}
327330

328331
void ResourceFormatLoaderJavaScriptModule::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
@@ -335,7 +338,7 @@ bool ResourceFormatLoaderJavaScriptModule::handles_type(const String &p_type) co
335338

336339
String ResourceFormatLoaderJavaScriptModule::get_resource_type(const String &p_path) const {
337340
String el = p_path.get_extension().to_lower();
338-
if (el == EXT_JSMODULE || el == EXT_JSMODULE_BYTECODE || el == EXT_JSMODULE_ENCRYPTED)
341+
if (el == EXT_JSMODULE)
339342
return JavaScriptModule::get_class_static();
340343
return "";
341344
}
@@ -350,7 +353,10 @@ Ref<Resource> ResourceFormatLoaderJavaScriptModule::load_static(const String &p_
350353
if (r_error) *r_error = err;
351354
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load source code from file '" + p_path + "'.");
352355
module->set_source_code(code);
353-
} else if (p_path.ends_with("." EXT_JSMODULE_BYTECODE) || p_path.ends_with("." EXT_JSCLASS_BYTECODE)) {
356+
}
357+
358+
#if 0
359+
else if (p_path.ends_with("." EXT_JSMODULE_BYTECODE) || p_path.ends_with("." EXT_JSCLASS_BYTECODE)) {
354360
module->set_bytecode(FileAccess::get_file_as_array(p_path, &err));
355361
if (r_error) *r_error = err;
356362
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load bytecode from file '" + p_path + "'.");
@@ -380,6 +386,8 @@ Ref<Resource> ResourceFormatLoaderJavaScriptModule::load_static(const String &p_
380386
err = ERR_CANT_OPEN;
381387
}
382388
}
389+
#endif
390+
383391
if (r_error) *r_error = err;
384392
ERR_FAIL_COND_V(err != OK, Ref<Resource>());
385393
return module;

javascript.h

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@
77
#include "javascript_binder.h"
88
#include "scene/resources/text_file.h"
99

10-
#define EXT_JSCLASS "jsx"
11-
#define EXT_JSCLASS_BYTECODE EXT_JSCLASS "b"
12-
#define EXT_JSCLASS_ENCRYPTED EXT_JSCLASS "e"
10+
#define EXT_JSCLASS "mjs"
1311
#define EXT_JSMODULE "js"
14-
#define EXT_JSMODULE_BYTECODE EXT_JSMODULE "b"
15-
#define EXT_JSMODULE_ENCRYPTED EXT_JSMODULE "e"
1612
#define EXT_JSON "json"
1713

1814
class JavaScript : public Script {
@@ -90,17 +86,19 @@ class JavaScript : public Script {
9086
class ResourceFormatLoaderJavaScript : public ResourceFormatLoader {
9187
GDCLASS(ResourceFormatLoaderJavaScript, ResourceFormatLoader)
9288
public:
93-
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL);
89+
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
9490
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
95-
virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override;
9691
virtual bool handles_type(const String &p_type) const override;
92+
virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override;
9793
virtual String get_resource_type(const String &p_path) const override;
94+
95+
virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const override;
9896
};
9997

10098
class ResourceFormatSaverJavaScript : public ResourceFormatSaver {
10199
GDCLASS(ResourceFormatSaverJavaScript, ResourceFormatSaver)
102100
public:
103-
virtual Error save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags = 0);
101+
virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override;
104102
virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override;
105103
virtual bool recognize(const Ref<Resource> &p_resource) const override;
106104
};

javascript_instance.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ Variant JavaScriptInstance::callp(const StringName &p_method, const Variant **p_
5656
r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
5757
ERR_FAIL_V(Variant());
5858
}
59-
return binder->call_method(javascript_object, p_method, p_args, p_argcount, r_error);
59+
if (binder->has_method(javascript_object, p_method)) {
60+
return binder->call_method(javascript_object, p_method, p_args, p_argcount, r_error);
61+
}
62+
return Variant();
6063
}
6164

6265
ScriptLanguage *JavaScriptInstance::get_language() {

javascript_language.cpp

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ void JavaScriptLanguage::get_reserved_words(List<String> *p_words) const {
105105
"const",
106106
"enum",
107107
"export",
108+
"default",
108109
"extends",
109110
"import",
110111
"super",
@@ -204,7 +205,6 @@ bool JavaScriptLanguage::is_control_flow_keyword(String p_keyword) const {
204205
p_keyword == "continue" ||
205206
p_keyword == "switch" ||
206207
p_keyword == "case" ||
207-
p_keyword == "default" ||
208208
p_keyword == "throw" ||
209209
p_keyword == "try" ||
210210
p_keyword == "catch" ||
@@ -222,46 +222,64 @@ void JavaScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const
222222
p_delimiters->push_back("` `");
223223
}
224224

225-
Ref<Script> JavaScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const {
226-
String script_template = "export default class %CLASS% extends " GODOT_OBJECT_NAME ".%BASE% {\n"
227-
" \n"
228-
" // Declare member variables here. Examples:\n"
229-
" a = 2;\n"
230-
" b = \"text\";\n"
231-
" \n"
232-
" constructor() {\n"
233-
" super();\n"
234-
" }\n"
235-
" \n"
236-
" // Called when the node enters the scene tree for the first time.\n"
237-
" _ready() {\n"
238-
" \n"
239-
" }\n"
240-
" \n"
241-
" // Called every frame. 'delta' is the elapsed time since the previous frame.\n"
242-
" _process(delta) {\n"
243-
" \n"
244-
" }\n"
245-
"}\n";
246-
script_template = script_template.replace("%BASE%", p_base_class_name).replace("%CLASS%", p_class_name);
247-
248-
Ref<JavaScript> script;
249-
script.instantiate();
250-
script->set_source_code(script_template);
251-
script->set_name(p_class_name);
252-
script->set_script_path(p_class_name);
253-
return script;
254-
}
255-
256225
Ref<Script> JavaScriptLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const {
257226
Ref<JavaScript> script;
258227
script.instantiate();
259-
String src = script->get_source_code();
260-
src = src.replace("%BASE%", p_base_class_name).replace("%CLASS%", p_class_name);
228+
String src = p_template.replace("%BASE%", p_base_class_name).replace("%CLASS%", p_class_name);
261229
script->set_source_code(src);
262230
return script;
263231
}
264232

233+
Vector<ScriptLanguage::ScriptTemplate> JavaScriptLanguage::get_built_in_templates(StringName p_object) {
234+
Vector<ScriptLanguage::ScriptTemplate> templates;
235+
#ifdef TOOLS_ENABLED
236+
constexpr size_t len = 2;
237+
static const struct ScriptLanguage::ScriptTemplate TEMPLATES[len] = {
238+
{ "Node",
239+
"Default",
240+
"Base template for Node with default Godot cycle methods",
241+
"export default class %CLASS% extends " GODOT_OBJECT_NAME ".%BASE% {\n"
242+
" \n"
243+
" constructor() {\n"
244+
" super();\n"
245+
" }\n"
246+
" \n"
247+
" // Called when the node enters the scene tree for the first time.\n"
248+
" _ready() {\n"
249+
" \n"
250+
" }\n"
251+
" \n"
252+
" // Called every frame. 'delta' is the elapsed time since the previous frame.\n"
253+
" _process(delta) {\n"
254+
" \n"
255+
" }\n"
256+
"}\n" },
257+
{ "Object",
258+
"Empty",
259+
"Empty template suitable for all Objects",
260+
"export default class %CLASS% extends " GODOT_OBJECT_NAME ".%BASE% {\n"
261+
" \n"
262+
" // Declare member variables here. Examples:\n"
263+
" a = 2;\n"
264+
" b = \"text\";\n"
265+
" \n"
266+
" constructor() {\n"
267+
" super();\n"
268+
" }\n"
269+
" \n"
270+
"}\n" },
271+
};
272+
273+
for (int i = 0; i < len; i++) {
274+
if (TEMPLATES[i].inherit == p_object) {
275+
templates.append(TEMPLATES[i]);
276+
}
277+
}
278+
#endif
279+
280+
return templates;
281+
}
282+
265283
bool JavaScriptLanguage::validate(const String &p_script, const String &p_path, List<String> *r_functions, List<ScriptError> *r_errors, List<Warning> *r_warnings, HashSet<int> *r_safe_lines) const {
266284
JavaScriptError script_error;
267285
bool ret = main_binder->validate(p_script, p_path, &script_error);
@@ -303,10 +321,6 @@ void JavaScriptLanguage::get_recognized_extensions(List<String> *p_extensions) c
303321
p_extensions->push_back(EXT_JSMODULE);
304322
p_extensions->push_back(EXT_JSCLASS);
305323
p_extensions->push_back(EXT_JSON);
306-
p_extensions->push_back(EXT_JSMODULE_ENCRYPTED);
307-
p_extensions->push_back(EXT_JSMODULE_BYTECODE);
308-
p_extensions->push_back(EXT_JSCLASS_ENCRYPTED);
309-
p_extensions->push_back(EXT_JSCLASS_BYTECODE);
310324
}
311325

312326
void JavaScriptLanguage::frame() {

javascript_language.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ class JavaScriptLanguage : public ScriptLanguage {
6161
virtual void get_comment_delimiters(List<String> *p_delimiters) const override;
6262
virtual void get_string_delimiters(List<String> *p_delimiters) const override;
6363

64-
Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
6564
virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override;
65+
virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override;
66+
virtual bool is_using_templates() override { return true; }
6667

6768
virtual Script *create_script() const override;
6869
_FORCE_INLINE_ virtual bool has_named_classes() const override { return true; }
6970
_FORCE_INLINE_ virtual bool supports_builtin_mode() const override { return false; }
70-
_FORCE_INLINE_ virtual bool is_using_templates() override { return true; }
7171

7272
virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptError> *r_errors = nullptr, List<Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const override;
7373

quickjs/quickjs_binder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ JSValue QuickJSBinder::console_functions(JSContext *ctx, JSValue this_val, int a
9797
String message = "";
9898
for (int i = 0; i < argc; ++i) {
9999
message += args[i];
100-
if (i > 0 && i < argc - 1) {
100+
if (i < argc - 1) {
101101
message += " ";
102102
}
103103
}

quickjs/quickjs_callable.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ bool QuickJSCallable::compare_less(const CallableCustom *p_a, const CallableCust
1717
}
1818

1919
QuickJSCallable::QuickJSCallable(JSContext *ctx, const JSValue &p_value) {
20-
ERR_FAIL_COND(JS_IsFunction(ctx, p_value));
20+
ERR_FAIL_COND(!JS_IsFunction(ctx, p_value));
2121
JSValue v = JS_DupValue(ctx, p_value);
2222
js_function.context = ctx;
2323
js_function.javascript_object = JS_VALUE_GET_PTR(v);

tests/UnitTest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ test('Object.prototype.connect', () => {
3232
console.log(`signal 'script_changed' emited with:`, ...args);
3333
ok = true;
3434
});
35-
obj.emit_signal('script_changed');
35+
obj.emit_signal('script_changed', 123, 'hello');
3636
obj.free();
3737
return ok;
3838
}, 'core')

0 commit comments

Comments
 (0)