summaryrefslogtreecommitdiff
path: root/platform/javascript/javascript_singleton.cpp
blob: 67908a18da6a7d7dd3f1c8a9c6ab090c2c89003c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
/*************************************************************************/
/*  javascript_singleton.cpp                                             */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/

#ifdef JAVASCRIPT_EVAL_ENABLED

#include "api/javascript_singleton.h"
#include "emscripten.h"

extern "C" {
typedef union {
	int64_t i;
	double r;
	void *p;
} godot_js_wrapper_ex;

typedef int (*GodotJSWrapperVariant2JSCallback)(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock);
typedef void (*GodotJSWrapperFreeLockCallback)(void **p_lock, int p_type);
extern int godot_js_wrapper_interface_get(const char *p_name);
extern int godot_js_wrapper_object_call(int p_id, const char *p_method, void **p_args, int p_argc, GodotJSWrapperVariant2JSCallback p_variant2js_callback, godot_js_wrapper_ex *p_cb_rval, void **p_lock, GodotJSWrapperFreeLockCallback p_lock_callback);
extern int godot_js_wrapper_object_get(int p_id, godot_js_wrapper_ex *p_val, const char *p_prop);
extern int godot_js_wrapper_object_getvar(int p_id, int p_type, godot_js_wrapper_ex *p_val);
extern int godot_js_wrapper_object_setvar(int p_id, int p_key_type, godot_js_wrapper_ex *p_key_ex, int p_val_type, godot_js_wrapper_ex *p_val_ex);
extern void godot_js_wrapper_object_set(int p_id, const char *p_name, int p_type, godot_js_wrapper_ex *p_val);
extern void godot_js_wrapper_object_unref(int p_id);
extern int godot_js_wrapper_create_cb(void *p_ref, void (*p_callback)(void *p_ref, int p_arg_id, int p_argc));
extern int godot_js_wrapper_create_object(const char *p_method, void **p_args, int p_argc, GodotJSWrapperVariant2JSCallback p_variant2js_callback, godot_js_wrapper_ex *p_cb_rval, void **p_lock, GodotJSWrapperFreeLockCallback p_lock_callback);
};

class JavaScriptObjectImpl : public JavaScriptObject {
private:
	friend class JavaScript;

	int _js_id = 0;
	Callable _callable;

	static int _variant2js(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock);
	static void _free_lock(void **p_lock, int p_type);
	static Variant _js2variant(int p_type, godot_js_wrapper_ex *p_val);
	static void *_alloc_variants(int p_size);
	static void _callback(void *p_ref, int p_arg_id, int p_argc);

protected:
	bool _set(const StringName &p_name, const Variant &p_value) override;
	bool _get(const StringName &p_name, Variant &r_ret) const override;
	void _get_property_list(List<PropertyInfo> *p_list) const override;

public:
	Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override;
	void setvar(const Variant &p_key, const Variant &p_value, bool *r_valid = nullptr) override;
	Variant call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) override;
	JavaScriptObjectImpl() {}
	JavaScriptObjectImpl(int p_id) { _js_id = p_id; }
	~JavaScriptObjectImpl() {
		if (_js_id) {
			godot_js_wrapper_object_unref(_js_id);
		}
	}
};

bool JavaScriptObjectImpl::_set(const StringName &p_name, const Variant &p_value) {
	ERR_FAIL_COND_V_MSG(!_js_id, false, "Invalid JS instance");
	const String name = p_name;
	godot_js_wrapper_ex exchange;
	void *lock = nullptr;
	const Variant *v = &p_value;
	int type = _variant2js((const void **)&v, 0, &exchange, &lock);
	godot_js_wrapper_object_set(_js_id, name.utf8().get_data(), type, &exchange);
	if (lock) {
		_free_lock(&lock, type);
	}
	return true;
}

bool JavaScriptObjectImpl::_get(const StringName &p_name, Variant &r_ret) const {
	ERR_FAIL_COND_V_MSG(!_js_id, false, "Invalid JS instance");
	const String name = p_name;
	godot_js_wrapper_ex exchange;
	int type = godot_js_wrapper_object_get(_js_id, &exchange, name.utf8().get_data());
	r_ret = _js2variant(type, &exchange);
	return true;
}

Variant JavaScriptObjectImpl::getvar(const Variant &p_key, bool *r_valid) const {
	if (r_valid) {
		*r_valid = false;
	}
	godot_js_wrapper_ex exchange;
	void *lock = nullptr;
	const Variant *v = &p_key;
	int prop_type = _variant2js((const void **)&v, 0, &exchange, &lock);
	int type = godot_js_wrapper_object_getvar(_js_id, prop_type, &exchange);
	if (lock) {
		_free_lock(&lock, prop_type);
	}
	if (type < 0) {
		return Variant();
	}
	if (r_valid) {
		*r_valid = true;
	}
	return _js2variant(type, &exchange);
}

void JavaScriptObjectImpl::setvar(const Variant &p_key, const Variant &p_value, bool *r_valid) {
	if (r_valid) {
		*r_valid = false;
	}
	godot_js_wrapper_ex kex, vex;
	void *klock = nullptr;
	void *vlock = nullptr;
	const Variant *kv = &p_key;
	const Variant *vv = &p_value;
	int ktype = _variant2js((const void **)&kv, 0, &kex, &klock);
	int vtype = _variant2js((const void **)&vv, 0, &vex, &vlock);
	int ret = godot_js_wrapper_object_setvar(_js_id, ktype, &kex, vtype, &vex);
	if (klock) {
		_free_lock(&klock, ktype);
	}
	if (vlock) {
		_free_lock(&vlock, vtype);
	}
	if (ret == 0 && r_valid) {
		*r_valid = true;
	}
}

void JavaScriptObjectImpl::_get_property_list(List<PropertyInfo> *p_list) const {
}

void JavaScriptObjectImpl::_free_lock(void **p_lock, int p_type) {
	ERR_FAIL_COND_MSG(*p_lock == nullptr, "No lock to free!");
	const Variant::Type type = (Variant::Type)p_type;
	switch (type) {
		case Variant::STRING: {
			CharString *cs = (CharString *)(*p_lock);
			memdelete(cs);
			*p_lock = nullptr;
		} break;
		default:
			ERR_FAIL_MSG("Unknown lock type to free. Likely a bug.");
	}
}

Variant JavaScriptObjectImpl::_js2variant(int p_type, godot_js_wrapper_ex *p_val) {
	Variant::Type type = (Variant::Type)p_type;
	switch (type) {
		case Variant::BOOL:
			return Variant((bool)p_val->i);
		case Variant::INT:
			return p_val->i;
		case Variant::FLOAT:
			return p_val->r;
		case Variant::STRING: {
			String out((const char *)p_val->p);
			free(p_val->p);
			return out;
		}
		case Variant::OBJECT: {
			return memnew(JavaScriptObjectImpl(p_val->i));
		}
		default:
			return Variant();
	}
}

int JavaScriptObjectImpl::_variant2js(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock) {
	const Variant **args = (const Variant **)p_args;
	const Variant *v = args[p_pos];
	Variant::Type type = v->get_type();
	switch (type) {
		case Variant::BOOL:
			r_val->i = v->operator bool() ? 1 : 0;
			break;
		case Variant::INT: {
			const int64_t tmp = v->operator int64_t();
			if (tmp >= 1 << 31) {
				r_val->r = (double)tmp;
				return Variant::FLOAT;
			}
			r_val->i = v->operator int64_t();
		} break;
		case Variant::FLOAT:
			r_val->r = v->operator real_t();
			break;
		case Variant::STRING: {
			CharString *cs = memnew(CharString(v->operator String().utf8()));
			r_val->p = (void *)cs->get_data();
			*p_lock = (void *)cs;
		} break;
		case Variant::OBJECT: {
			JavaScriptObject *js_obj = Object::cast_to<JavaScriptObject>(v->operator Object *());
			r_val->i = js_obj != nullptr ? ((JavaScriptObjectImpl *)js_obj)->_js_id : 0;
		} break;
		default:
			break;
	}
	return type;
}

Variant JavaScriptObjectImpl::call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) {
	godot_js_wrapper_ex exchange;
	const String method = p_method;
	void *lock = nullptr;
	const int type = godot_js_wrapper_object_call(_js_id, method.utf8().get_data(), (void **)p_args, p_argc, &_variant2js, &exchange, &lock, &_free_lock);
	r_error.error = Callable::CallError::CALL_OK;
	if (type < 0) {
		r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
		return Variant();
	}
	return _js2variant(type, &exchange);
}

void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) {
	const JavaScriptObjectImpl *obj = (JavaScriptObjectImpl *)p_ref;
	ERR_FAIL_COND_MSG(obj->_callable.is_null(), "JavaScript callback failed.");
	Vector<const Variant *> argp;
	Array arg_arr;
	for (int i = 0; i < p_argc; i++) {
		godot_js_wrapper_ex exchange;
		exchange.i = i;
		int type = godot_js_wrapper_object_getvar(p_args_id, Variant::INT, &exchange);
		arg_arr.push_back(_js2variant(type, &exchange));
	}
	Variant arg = arg_arr;
	const Variant *argv[1] = { &arg };
	Callable::CallError err;
	Variant ret;
	obj->_callable.call(argv, 1, ret, err);
}

Ref<JavaScriptObject> JavaScript::create_callback(const Callable &p_callable) {
	Ref<JavaScriptObjectImpl> out = memnew(JavaScriptObjectImpl);
	out->_callable = p_callable;
	out->_js_id = godot_js_wrapper_create_cb(out.ptr(), JavaScriptObjectImpl::_callback);
	return out;
}

Ref<JavaScriptObject> JavaScript::get_interface(const String &p_interface) {
	int js_id = godot_js_wrapper_interface_get(p_interface.utf8().get_data());
	ERR_FAIL_COND_V_MSG(!js_id, Ref<JavaScriptObject>(), "No interface '" + p_interface + "' registered.");
	return Ref<JavaScriptObject>(memnew(JavaScriptObjectImpl(js_id)));
}

Variant JavaScript::_create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
	if (p_argcount < 1) {
		r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
		r_error.argument = 0;
		return Ref<JavaScriptObject>();
	}
	if (p_args[0]->get_type() != Variant::STRING) {
		r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
		r_error.argument = 0;
		r_error.expected = Variant::STRING;
		return Ref<JavaScriptObject>();
	}
	godot_js_wrapper_ex exchange;
	const String object = *p_args[0];
	void *lock = nullptr;
	const Variant **args = p_argcount > 1 ? &p_args[1] : nullptr;
	const int type = godot_js_wrapper_create_object(object.utf8().get_data(), (void **)args, p_argcount - 1, &JavaScriptObjectImpl::_variant2js, &exchange, &lock, &JavaScriptObjectImpl::_free_lock);
	r_error.error = Callable::CallError::CALL_OK;
	if (type < 0) {
		r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
		return Ref<JavaScriptObject>();
	}
	return JavaScriptObjectImpl::_js2variant(type, &exchange);
}

extern "C" {
union js_eval_ret {
	uint32_t b;
	double d;
	char *s;
};

extern int godot_js_eval(const char *p_js, int p_use_global_ctx, union js_eval_ret *p_union_ptr, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len));
}

void *resize_PackedByteArray_and_open_write(void *p_arr, void *r_write, int p_len) {
	PackedByteArray *arr = (PackedByteArray *)p_arr;
	VectorWriteProxy<uint8_t> *write = (VectorWriteProxy<uint8_t> *)r_write;
	arr->resize(p_len);
	*write = arr->write;
	return arr->ptrw();
}

Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
	union js_eval_ret js_data;
	PackedByteArray arr;
	VectorWriteProxy<uint8_t> arr_write;

	Variant::Type return_type = static_cast<Variant::Type>(godot_js_eval(p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write, resize_PackedByteArray_and_open_write));

	switch (return_type) {
		case Variant::BOOL:
			return js_data.b;
		case Variant::FLOAT:
			return js_data.d;
		case Variant::STRING: {
			String str = String::utf8(js_data.s);
			free(js_data.s); // Must free the string allocated in JS.
			return str;
		}
		case Variant::PACKED_BYTE_ARRAY:
			arr_write = VectorWriteProxy<uint8_t>();
			return arr;
		default:
			return Variant();
	}
}
#endif // JAVASCRIPT_EVAL_ENABLED