Skip to content

Commit cd1bc60

Browse files
committed
Make JavaScript file as ECMAScriptModule resources to avoid godot editor auto load and valid script errors
Make ECMAScript as jsx files only jsx files can be attacked to godot object Add decorators for typescript project Fix mermory leak for import statement resource preload Update README documentations
1 parent 86ef38a commit cd1bc60

File tree

10 files changed

+307
-90
lines changed

10 files changed

+307
-90
lines changed

README.md

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ It is also possible to replace the ECMAScript engine like V8 or SpiderMonkey but
1818
* Clone this module and put it into `godot/modules/` and make sure the folder name of this module is `ECMAScript`
1919
* [Recompile godot engine](https://docs.godotengine.org/en/3.2/development/compiling/index.html) <b>(Only MinGW is supported on Windows for now!)</b>
2020

21+
2122
### Usage
2223

2324
##### How to export script class to godot
@@ -40,8 +41,8 @@ export default class MySprite extends godot.Sprite {
4041
}
4142
}
4243
```
43-
44-
2. Attach the script file to the node or resource object like you did with GDScript
44+
2. Save the script with extension `.jsx`
45+
3. Attach the script file to the node or resource object like you did with GDScript
4546

4647
##### How to export signals
4748

@@ -102,8 +103,10 @@ Label.Align.ALIGN_LEFT | godot.Label.Align.ALIGN_LEFT
102103
- `godot.set_script_tooled(cls, tooled)` to set `tooled` of the class
103104
- `godot.set_script_icon(cls, path)` to set icon of the class
104105
- `godot.get_type(val)` Returns the internal type of the given `Variant` object, using the `godot.TYPE_*`
106+
- `godot.yield(target, signal)` Returns a Promise which will be resolved when the signal emitted
105107
- `requestAnimationFrame(callback)` to add a callback function to be called every frame
106108
- `cancelAnimationFrame(request_id)` to cancel an frame request previously scheduled
109+
- `require(module_id)` to load a CommonJS module or load a resource file
107110
- Using signals in the ECMAScript way
108111
- Allow passing functions for `godot.Object.connect`, `godot.Object.disconnect` and `godot.Object.is_connected`
109112
```js
@@ -120,7 +123,7 @@ Label.Align.ALIGN_LEFT | godot.Label.Align.ALIGN_LEFT
120123
```js
121124
import ICON from 'res://icon.png';
122125
```
123-
- Multi-threading with minimal [Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Worker)
126+
- Multi-threading with minimal [Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Worker) (**This is an expiremental feature**)
124127
- Start a new thread with Worker
125128
```js
126129
const worker = new Worker('worker.js'); // Run worker.js in a new thread context
@@ -143,10 +146,75 @@ Label.Align.ALIGN_LEFT | godot.Label.Align.ALIGN_LEFT
143146
}
144147
```
145148

146-
- TypeScript support
147-
- Run the menu command `Project > Tools > ECMAScript > Generate TypeScript Declaration` from godot editor to dump the api declearations
148-
- Run the menu command `Project > Tools > ECMAScript > Generate TypeScript Project` from godot editor to generate a TypeScript project
149+
### TypeScript support
150+
- Run the menu command `Project > Tools > ECMAScript > Generate TypeScript Project` from godot editor to generate a TypeScript project
151+
- Run `tsc -w -p .` under your project folder in the terminal to compile scripts
152+
153+
#### Here is an example for using TypesSript
154+
Make sure the file with extension '.tsx' so it can be compiled to a `.jsx` file so we can attach it to a node in godot eidot.
155+
156+
```ts
157+
import { signal, property, tool } from "./decorators";
158+
159+
export const Signal = {
160+
OnTextChanged: 'OnTextChanged'
161+
};
162+
163+
@tool // make the script runnable in godot editor
164+
@signal(Signal.OnTextChanged) // register signal to class InputLine
165+
export default class InputLine extends godot.HBoxContainer {
166+
167+
static readonly Signal = Signal;
149168

169+
// register properties for godot editor inspector
170+
@property("Title")
171+
get title() { return this._title; }
172+
set title(v: string) {
173+
this._title = v;
174+
if (this._label) {
175+
this._label.text = v;
176+
}
177+
}
178+
private _title: string;
179+
180+
@property("Input text here")
181+
get hint() { return this._hint; }
182+
set hint(v: string) {
183+
this._hint = v;
184+
if (this._edit) {
185+
this._edit.hint_tooltip = v;
186+
this._edit.placeholder_text = v;
187+
}
188+
}
189+
private _hint: string;
190+
191+
192+
get label(): godot.Label { return this.label; }
193+
protected _label: godot.Label;
194+
195+
get edit(): godot.LineEdit { return this._edit; }
196+
protected _edit: godot.LineEdit;
197+
198+
get text(): string {
199+
return this._edit?.text;
200+
}
201+
202+
_ready() {
203+
// get child node in the traditionnal way
204+
this._edit = this.get_node("LineEdit") as godot.LineEdit;
205+
// get first child with the
206+
this._label = this.get_node(Label);
207+
208+
// there is no onready keywords so we have to do this mannually :(
209+
this.title = this.title;
210+
this.hint = this.hint;
211+
212+
this._edit.connect(godot.LineEdit.text_changed, (text: string)=>{
213+
this.emit_signal(Signal.OnTextChanged, text);
214+
});
215+
}
216+
}
217+
```
150218

151219
## Demo
152220
You can try demos in the [ECMAScriptDemos](https://github.com/Geequlim/ECMAScriptDemos)

SCsub

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ if env['tools']:
6464
with open("tools/tsconfig.json.gen.cpp", "w", encoding="utf-8") as f:
6565
text = '/* THIS FILE IS GENERATED DO NOT EDIT */\n#include "editor_tools.h"\nString ECMAScriptPlugin::TSCONFIG_CONTENT = \n${source};'
6666
f.write(text.replace('${source}', dump_text_file_to_cpp("misc/tsconfig.json")))
67+
with open("tools/decorators.ts.gen.cpp", "w") as f:
68+
text = '/* THIS FILE IS GENERATED DO NOT EDIT */\n#include "editor_tools.h"\nString ECMAScriptPlugin::TS_DECORATORS_CONTENT = \n${source};'
69+
f.write(text.replace('${source}', dump_text_file_to_cpp("misc/decorators.ts")))
6770
env_module.add_source_files(env.modules_sources, 'tools/*.cpp')
6871

6972
env_module.Append(CPPPATH=["#modules/ECMAScript"])

ecmascript.cpp

Lines changed: 128 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -207,22 +207,125 @@ void ECMAScript::_bind_methods() {
207207
}
208208

209209
RES ResourceFormatLoaderECMAScript::load(const String &p_path, const String &p_original_path, Error *r_error) {
210-
Error err;
211-
212-
if (r_error)
213-
*r_error = ERR_FILE_CANT_OPEN;
214-
210+
Error err = OK;
211+
Ref<ECMAScriptModule> module = ResourceFormatLoaderECMAScriptModule::load_static(p_path, p_original_path, &err);
212+
if (r_error) *r_error = err;
213+
ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load script file '" + p_path + "'.");
215214
Ref<ECMAScript> script;
216215
script.instance();
217216
script->set_script_path(p_path);
217+
script->bytecode = module->get_bytecode();
218+
script->set_source_code(module->get_source_code());
219+
err = script->reload();
220+
if (r_error) *r_error = err;
221+
ERR_FAIL_COND_V_MSG(err != OK, RES(), "Parse source code from file '" + p_path + "' failed.");
222+
return script;
223+
}
224+
225+
void ResourceFormatLoaderECMAScript::get_recognized_extensions(List<String> *p_extensions) const {
226+
p_extensions->push_front(EXT_JSCLASS);
227+
p_extensions->push_front(EXT_JSCLASS_BYTECODE);
228+
p_extensions->push_front(EXT_JSCLASS_ENCRYPTED);
229+
}
230+
231+
void ResourceFormatLoaderECMAScript::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
232+
get_recognized_extensions(p_extensions);
233+
}
234+
235+
bool ResourceFormatLoaderECMAScript::handles_type(const String &p_type) const {
236+
return p_type == ECMAScript::get_class_static();
237+
}
238+
239+
String ResourceFormatLoaderECMAScript::get_resource_type(const String &p_path) const {
240+
String el = p_path.get_extension().to_lower();
241+
if (el == EXT_JSCLASS || el == EXT_JSCLASS_BYTECODE || el == EXT_JSCLASS_ENCRYPTED) return ECMAScript::get_class_static();
242+
return "";
243+
}
244+
245+
Error ResourceFormatSaverECMAScript::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
246+
247+
Ref<ECMAScript> script = p_resource;
248+
ERR_FAIL_COND_V(script.is_null(), ERR_INVALID_PARAMETER);
249+
250+
String source = script->get_source_code();
251+
252+
Error err;
253+
FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE, &err);
254+
ERR_FAIL_COND_V_MSG(err, err, "Cannot save file '" + p_path + "'.");
255+
file->store_string(source);
256+
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
257+
return ERR_CANT_CREATE;
258+
}
259+
file->close();
260+
261+
if (ScriptServer::is_reload_scripts_on_save_enabled()) {
262+
script->reload();
263+
}
264+
265+
return OK;
266+
}
267+
268+
void ResourceFormatSaverECMAScript::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const {
269+
if (Object::cast_to<ECMAScript>(*p_resource)) {
270+
p_extensions->push_back(EXT_JSCLASS);
271+
}
272+
}
273+
274+
bool ResourceFormatSaverECMAScript::recognize(const RES &p_resource) const {
275+
return Object::cast_to<ECMAScript>(*p_resource) != NULL;
276+
}
277+
278+
void ECMAScriptModule::_bind_methods() {
279+
ClassDB::bind_method(D_METHOD("set_script_path", "script_path"), &ECMAScriptModule::set_script_path);
280+
ClassDB::bind_method(D_METHOD("get_script_path"), &ECMAScriptModule::get_script_path);
281+
ClassDB::bind_method(D_METHOD("set_source_code", "source_code"), &ECMAScriptModule::set_source_code);
282+
ClassDB::bind_method(D_METHOD("get_source_code"), &ECMAScriptModule::get_source_code);
283+
ClassDB::bind_method(D_METHOD("set_bytecode", "bytecode"), &ECMAScriptModule::set_bytecode);
284+
ClassDB::bind_method(D_METHOD("get_bytecode"), &ECMAScriptModule::get_bytecode);
285+
ADD_PROPERTY(PropertyInfo(Variant::STRING, "script_path"), "set_script_path", "get_script_path");
286+
}
218287

219-
if (p_path.ends_with("." EXT_JSCLASS)) {
288+
ECMAScriptModule::ECMAScriptModule() {
289+
set_source_code("module.exports = {};" ENDL);
290+
}
291+
292+
RES ResourceFormatLoaderECMAScriptModule::load(const String &p_path, const String &p_original_path, Error *r_error) {
293+
return load_static(p_path, p_original_path, r_error);
294+
}
295+
296+
void ResourceFormatLoaderECMAScriptModule::get_recognized_extensions(List<String> *p_extensions) const {
297+
p_extensions->push_front(EXT_JSMODULE);
298+
p_extensions->push_front(EXT_JSMODULE_BYTECODE);
299+
p_extensions->push_front(EXT_JSMODULE_ENCRYPTED);
300+
}
301+
302+
void ResourceFormatLoaderECMAScriptModule::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
303+
get_recognized_extensions(p_extensions);
304+
}
305+
306+
bool ResourceFormatLoaderECMAScriptModule::handles_type(const String &p_type) const {
307+
return p_type == ECMAScriptModule::get_class_static();
308+
}
309+
310+
String ResourceFormatLoaderECMAScriptModule::get_resource_type(const String &p_path) const {
311+
String el = p_path.get_extension().to_lower();
312+
if (el == EXT_JSMODULE || el == EXT_JSMODULE_BYTECODE || el == EXT_JSMODULE_ENCRYPTED) return ECMAScriptModule::get_class_static();
313+
return "";
314+
}
315+
316+
RES ResourceFormatLoaderECMAScriptModule::load_static(const String &p_path, const String &p_original_path, Error *r_error) {
317+
Error err = ERR_FILE_CANT_OPEN;
318+
Ref<ECMAScriptModule> module;
319+
module.instance();
320+
module->set_script_path(p_path);
321+
if (p_path.ends_with("." EXT_JSMODULE) || p_path.ends_with("." EXT_JSCLASS)) {
220322
String code = FileAccess::get_file_as_string(p_path, &err);
323+
if (r_error) *r_error = err;
221324
ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load source code from file '" + p_path + "'.");
222-
script->set_source_code(code);
223-
} else if (p_path.ends_with("." EXT_JSCLASS_BYTECODE)) {
224-
Error err;
225-
script->bytecode = FileAccess::get_file_as_array(p_path, &err);
325+
module->set_source_code(code);
326+
} else if (p_path.ends_with("." EXT_JSMODULE_BYTECODE) || p_path.ends_with("." EXT_JSCLASS_BYTECODE)) {
327+
module->set_bytecode(FileAccess::get_file_as_array(p_path, &err));
328+
if (r_error) *r_error = err;
226329
ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load bytecode from file '" + p_path + "'.");
227330
} else if (p_path.ends_with("." EXT_JSCLASS_ENCRYPTED)) {
228331
FileAccess *fa = FileAccess::open(p_path, FileAccess::READ);
@@ -243,7 +346,7 @@ RES ResourceFormatLoaderECMAScript::load(const String &p_path, const String &p_o
243346
if (code.parse_utf8((const char *)encrypted_code.ptr(), encrypted_code.size())) {
244347
err = ERR_PARSE_ERROR;
245348
} else {
246-
script->set_source_code(code);
349+
module->set_source_code(code);
247350
}
248351
fa->close();
249352
fae->close();
@@ -258,71 +361,32 @@ RES ResourceFormatLoaderECMAScript::load(const String &p_path, const String &p_o
258361
err = ERR_CANT_OPEN;
259362
}
260363
}
261-
262-
err = script->reload();
263-
if (OK != err) {
264-
ERR_PRINTS("Cannot parse source code from file '" + p_path + "'.");
265-
}
266-
if (r_error)
267-
*r_error = err;
268-
269-
return script;
270-
}
271-
272-
void ResourceFormatLoaderECMAScript::get_recognized_extensions(List<String> *p_extensions) const {
273-
p_extensions->push_front(EXT_JSCLASS);
274-
p_extensions->push_front(EXT_JSCLASS_BYTECODE);
275-
p_extensions->push_front(EXT_JSCLASS_ENCRYPTED);
364+
if (r_error) *r_error = err;
365+
ERR_FAIL_COND_V(err != OK, RES());
366+
return module;
276367
}
277368

278-
void ResourceFormatLoaderECMAScript::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
279-
get_recognized_extensions(p_extensions);
280-
}
281-
282-
bool ResourceFormatLoaderECMAScript::handles_type(const String &p_type) const {
283-
return p_type == "ECMAScript";
284-
}
285-
286-
String ResourceFormatLoaderECMAScript::get_resource_type(const String &p_path) const {
287-
String el = p_path.get_extension().to_lower();
288-
if (el == EXT_JSCLASS || el == EXT_JSCLASS_BYTECODE || el == EXT_JSCLASS_ENCRYPTED) return "ECMAScript";
289-
return "";
290-
}
291-
292-
Error ResourceFormatSaverECMAScript::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
293-
294-
Ref<ECMAScript> script = p_resource;
295-
ERR_FAIL_COND_V(script.is_null(), ERR_INVALID_PARAMETER);
296-
297-
String source = script->get_source_code();
298-
369+
Error ResourceFormatSaverECMAScriptModule::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
370+
Ref<ECMAScriptModule> module = p_resource;
371+
ERR_FAIL_COND_V(module.is_null(), ERR_INVALID_PARAMETER);
372+
String source = module->get_source_code();
299373
Error err;
300-
FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err);
301-
302-
ERR_FAIL_COND_V_MSG(err, err, "Cannot save ECMAScript file '" + p_path + "'.");
303-
374+
FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE, &err);
375+
ERR_FAIL_COND_V_MSG(err, err, "Cannot save file '" + p_path + "'.");
304376
file->store_string(source);
305377
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
306-
memdelete(file);
307378
return ERR_CANT_CREATE;
308379
}
309380
file->close();
310-
memdelete(file);
311-
312-
if (ScriptServer::is_reload_scripts_on_save_enabled()) {
313-
script->reload();
314-
}
315-
316381
return OK;
317382
}
318383

319-
void ResourceFormatSaverECMAScript::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const {
320-
321-
if (Object::cast_to<ECMAScript>(*p_resource)) {
322-
p_extensions->push_back("jsx");
384+
void ResourceFormatSaverECMAScriptModule::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const {
385+
if (Object::cast_to<ECMAScriptModule>(*p_resource)) {
386+
p_extensions->push_back(EXT_JSMODULE);
323387
}
324388
}
325389

326-
bool ResourceFormatSaverECMAScript::recognize(const RES &p_resource) const {
327-
return Object::cast_to<ECMAScript>(*p_resource) != NULL;
390+
bool ResourceFormatSaverECMAScriptModule::recognize(const RES &p_resource) const {
391+
return Object::cast_to<ECMAScriptModule>(*p_resource) != NULL;
328392
}

0 commit comments

Comments
 (0)