Skip to content

Commit 1381e69

Browse files
author
mingqian.gui
committed
add some const binding support
1 parent 44ae982 commit 1381e69

File tree

6 files changed

+111
-36
lines changed

6 files changed

+111
-36
lines changed

engine/engine/nickel/script/internal/binding.hpp

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,17 @@ requires std::is_class_v<T>
5757
class JSValueWrapper<T> {
5858
public:
5959
JSValue Wrap(JSContext* ctx, T& value) {
60-
JSValue obj = JS_NewObject(ctx);
61-
QJS_CALL(JS_SetOpaque(obj, &value));
60+
JSClassID id = 0;
61+
if constexpr (std::is_const_v<T>) {
62+
id = QJSClass<std::remove_const_t<T>>::GetConstTypeID();
63+
} else {
64+
id = QJSClass<T>::GetID();
65+
}
66+
JSValue obj = JS_NewObjectClass(ctx, id);
67+
if (JS_IsException(obj)) {
68+
LogJSException(ctx);
69+
}
70+
QJS_CALL(JS_SetOpaque(obj, (void*)&value));
6271
return obj;
6372
}
6473

@@ -73,7 +82,10 @@ template <typename T>
7382
class JSValueWrapper<T*> {
7483
public:
7584
JSValue Wrap(JSContext* ctx, T* value) {
76-
JSValue obj = JS_NewObject(ctx);
85+
JSValue obj = JS_NewObjectClass(ctx, QJSClass<T>::GetID());
86+
if (JS_IsException(obj)) {
87+
LogJSException(ctx);
88+
}
7789
QJS_CALL(JS_SetOpaque(obj, value));
7890
return obj;
7991
}
@@ -308,14 +320,16 @@ class QJSClassBase {
308320
virtual JSValue GetConstructor() const = 0;
309321
virtual ~QJSClassBase() = default;
310322
virtual const std::string& GetName() const = 0;
323+
virtual const std::string& GetConstName() const = 0;
311324
};
312325

313326
class QJSContext {
314327
public:
315328
explicit QJSContext(QJSRuntime& runtime);
316329
~QJSContext();
317330

318-
JSValue Eval(std::span<const char> content, const Path& filename) const;
331+
JSValue Eval(std::span<const char> content, const Path& filename,
332+
bool strict_mode) const;
319333

320334
QJSModule& NewModule(const std::string& name);
321335

@@ -504,8 +518,11 @@ struct JSMemberVariableTraits {
504518
static_assert(traits::is_member);
505519

506520
static JSValue Getter(JSContext* ctx, JSValue this_val, int magic) {
507-
clazz* p = static_cast<clazz*>(
508-
JS_GetOpaque2(ctx, this_val, QJSClass<clazz>::GetID()));
521+
JSClassID id = JS_GetClassID(this_val);
522+
NICKEL_ASSERT(id == QJSClass<clazz>::GetID() ||
523+
id == QJSClass<clazz>::GetConstTypeID());
524+
const clazz* p = static_cast<const clazz*>(
525+
JS_GetOpaque2(ctx, this_val, id));
509526
if (!p) {
510527
LOGE("access class variable from nullptr");
511528
return JS_EXCEPTION;
@@ -535,22 +552,26 @@ template <typename T>
535552
class QJSClass : public QJSClassBase {
536553
public:
537554
QJSClass(QJSRuntime& runtime, const std::string& name)
538-
: m_context{runtime.GetContext()}, m_name{name}, m_ctor{JS_UNDEFINED} {
555+
: m_context{runtime.GetContext()},
556+
m_name{name},
557+
m_const_name{"const " + name},
558+
m_ctor{JS_UNDEFINED} {
539559
if (id == 0) {
540560
id = JS_NewClassID(runtime, &id);
541561
}
542562
if (const_id == 0) {
543-
id = JS_NewClassID(runtime, &id);
563+
const_id = JS_NewClassID(runtime, &const_id);
544564
}
545565
m_def.class_name = m_name.c_str();
546566
m_def.finalizer = jsFinalizer;
547567

548-
m_const_type_def.class_name = ("const " + m_name).c_str();
568+
m_const_type_def.class_name = m_const_name.c_str();
549569

550570
QJS_CALL(JS_NewClass(runtime, id, &m_def));
551571
QJS_CALL(JS_NewClass(runtime, const_id, &m_const_type_def));
552572

553573
m_proto = JS_NewObject(runtime.GetContext());
574+
m_const_type_proto = JS_NewObject(runtime.GetContext());
554575
}
555576

556577
template <typename... Args>
@@ -581,6 +602,15 @@ class QJSClass : public QJSClassBase {
581602
refl::list_size_v<typename traits::args>, JS_CFUNC_generic, 0);
582603
QJS_CALL(JS_DefinePropertyValueStr(m_context, m_proto, name.c_str(),
583604
fn, 0));
605+
606+
if constexpr (traits::is_const) {
607+
JSValue const_fn = JS_NewCFunction2(
608+
m_context, JSMemberFnTraits<F>::Fn, name.c_str(),
609+
refl::list_size_v<typename traits::args>, JS_CFUNC_generic,
610+
0);
611+
QJS_CALL(JS_DefinePropertyValueStr(
612+
m_context, m_const_type_proto, name.c_str(), const_fn, 0));
613+
}
584614
} else {
585615
JSValue fn = JS_NewCFunction2(
586616
m_context, JSFnTraits<F>::Fn, name.c_str(),
@@ -595,18 +625,36 @@ class QJSClass : public QJSClassBase {
595625
using traits = refl::variable_pointer_traits<F>;
596626
static_assert(traits::is_member);
597627
using js_traits = JSMemberVariableTraits<F>;
598-
JSCFunctionListEntry entry[] = {
599-
JS_CGETSET_MAGIC_DEF(name.c_str(), js_traits::Getter,
600-
js_traits::Setter, 0),
628+
629+
if constexpr (std::is_const_v<typename traits::type>) {
630+
JSCFunctionListEntry entry[] = {
631+
JS_CGETSET_MAGIC_DEF(name.c_str(), js_traits::Getter, nullptr,
632+
0),
633+
};
634+
JS_SetPropertyFunctionList(m_context, m_proto, entry,
635+
std::size(entry));
636+
} else {
637+
JSCFunctionListEntry entry[] = {
638+
JS_CGETSET_MAGIC_DEF(name.c_str(), js_traits::Getter,
639+
js_traits::Setter, 0),
640+
};
641+
JS_SetPropertyFunctionList(m_context, m_proto, entry,
642+
std::size(entry));
643+
}
644+
645+
JSCFunctionListEntry const_entry[] = {
646+
JS_CGETSET_MAGIC_DEF(name.c_str(), js_traits::Getter, nullptr, 0),
601647
};
602-
JS_SetPropertyFunctionList(m_context, m_proto, entry, std::size(entry));
648+
JS_SetPropertyFunctionList(m_context, m_const_type_proto, const_entry,
649+
std::size(const_entry));
603650
return *this;
604651
}
605652

606653
template <typename U>
607654
QJSClass& AddStaticField(const std::string& name, U* ptr) {
608655
JS_SetPropertyStr(m_context, m_ctor, name.c_str(),
609656
JSValueWrapper<U>{}.Wrap(m_context, *ptr));
657+
// TODO: handle const type
610658
return *this;
611659
}
612660

@@ -619,6 +667,8 @@ class QJSClass : public QJSClassBase {
619667

620668
const std::string& GetName() const override { return m_name; }
621669

670+
const std::string& GetConstName() const override { return m_const_name; }
671+
622672
static JSClassID GetID() { return id; }
623673

624674
static JSClassID GetConstTypeID() { return const_id; }
@@ -631,6 +681,7 @@ class QJSClass : public QJSClassBase {
631681
JSClassDef m_def{};
632682
JSClassDef m_const_type_def{};
633683
std::string m_name;
684+
std::string m_const_name;
634685
JSValue m_proto;
635686
JSValue m_const_type_proto;
636687
JSValue m_ctor;

engine/engine/nickel/script/internal/qjs_script_impl.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ class QuickJSScriptImpl : public RefCountable {
1313
void OnUpdate();
1414
void DecRefcount() override;
1515

16+
JSValue GetJSValue() const { return m_value; }
17+
1618
private:
1719
QJSContext& m_ctx;
1820
JSValue m_value;

engine/engine/src/script/binding.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,27 @@ QJSContext::~QJSContext() {
1919
JS_FreeContext(m_context);
2020
}
2121

22-
JSValue QJSContext::Eval(std::span<const char> content,
23-
const Path& filename) const {
22+
JSValue QJSContext::Eval(std::span<const char> content, const Path& filename,
23+
bool strict_mode) const {
2424
// FIXME: there must be a better way to auto import modules
2525
std::string module_import_code;
2626
for (auto& module : m_modules) {
2727
auto& name = module->GetName();
2828
module_import_code += "import * as " + name + " from '" + name + "'\n" +
29-
"globalThis." + name + " = " + name + "\n";
29+
"globalThis." + name + " = " + name + "\n";
3030
}
31-
31+
3232
{
3333
JSValue result =
34-
JS_Eval(m_context, module_import_code.data(), module_import_code.size(),
35-
"<import>", JS_EVAL_TYPE_MODULE);
34+
JS_Eval(m_context, module_import_code.data(),
35+
module_import_code.size(), "<import>", JS_EVAL_TYPE_MODULE);
3636
JS_VALUE_CHECK_RETURN_UNDEFINED(m_context, result);
3737
JS_FreeValue(m_context, result);
3838
}
3939

4040
JSValue value = JS_Eval(m_context, content.data(), content.size(),
41-
filename.ToString().c_str(), 0);
41+
filename.ToString().c_str(),
42+
strict_mode ? JS_EVAL_FLAG_STRICT : 0);
4243
JS_VALUE_CHECK_RETURN_UNDEFINED(m_context, value);
4344
return value;
4445
}
@@ -102,6 +103,8 @@ QJSContext& QJSModule::EndModule() const {
102103
for (auto& clazz : m_classes) {
103104
QJS_CALL(JS_AddModuleExport(m_context, m_module,
104105
clazz->GetName().c_str()));
106+
QJS_CALL(JS_AddModuleExport(m_context, m_module,
107+
clazz->GetConstName().c_str()));
105108
}
106109
}
107110
return m_context;

engine/engine/src/script/script_impl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ QuickJSScript ScriptManagerImpl::Load(const Path& filename) {
1414
auto& ctx = QJSRuntime::GetInst().GetContext();
1515
// FIXME: remove new
1616
return QuickJSScript{
17-
new QuickJSScriptImpl(ctx, ctx.Eval(content, filename))};
17+
new QuickJSScriptImpl(ctx, ctx.Eval(content, filename, true))};
1818
}
1919

2020
QuickJSScript ScriptManagerImpl::Load(std::span<const char> content) {
2121
auto& ctx = QJSRuntime::GetInst().GetContext();
2222
// FIXME: remove new
2323
return QuickJSScript{
24-
new QuickJSScriptImpl{ctx, ctx.Eval(content, "")}
24+
new QuickJSScriptImpl{ctx, ctx.Eval(content, "", true)}
2525
};
2626
}
2727

engine/tests/script/qjs/qjs_test.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct Person {
1010
int age = 12;
1111
float height = 180;
1212
std::string name;
13+
const int const_value = 996;
1314

1415
Person(std::string name): name{name} {}
1516

@@ -58,6 +59,7 @@ TEST_CASE("test") {
5859
.AddField<&Person::age>("age")
5960
.AddField<&Person::height>("height")
6061
.AddField<&Person::name>("name")
62+
.AddField<&Person::const_value>("const_value")
6163
.AddFunction<&Person::Introduce>("Introduce")
6264
.AddFunction<&Person::SayHello>("SayHello")
6365
.AddStaticField("static_elem", &Person::static_elem)
@@ -74,5 +76,14 @@ TEST_CASE("test") {
7476
sstream << file.rdbuf();
7577

7678
std::string str = sstream.str();
77-
mgr.Load(std::span{str});
79+
auto script = mgr.Load(std::span{str});
80+
JSContext* ctx = script::QJSRuntime::GetInst().GetContext();
81+
JSValue global = JS_GetGlobalObject(ctx);
82+
JSValue fn = JS_GetPropertyStr(ctx, global, "test_const_var");
83+
Person* person = new Person{"John"};
84+
JSValue param = script::JSValueWrapper<const Person>{}.Wrap(ctx, *person);
85+
JS_Call(ctx, fn, JS_UNDEFINED, 1, &param);
86+
JS_FreeValue(ctx, global);
87+
JS_FreeValue(ctx, param);
88+
JS_FreeValue(ctx, fn);
7889
}

engine/tests/script/qjs/test.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
Object.keys(test_module).forEach(key => {
2+
console.log(`Key: ${key}, Value: ${test_module[key]}`);
3+
});
4+
15
console.log(test_module.int_elem)
26
console.log(test_module.uint_elem)
37
console.log(test_module.char_elem)
@@ -11,18 +15,22 @@ console.log(test_module.string_view)
1115
console.log(test_module.str_literal)
1216
console.log(test_module.string_elem)
1317

14-
function main() {
15-
let person = new test_module.Person("John")
16-
test_module.Person.SayHello()
17-
person.Introduce()
18+
let person = new test_module.Person("John")
19+
test_module.Person.SayHello()
20+
person.Introduce()
21+
console.log(person.age)
22+
person.age = 123
23+
console.log(person.age)
24+
console.log(person.height)
25+
console.log(person.name)
26+
person.name = "VisualGMQ"
27+
console.log(person.name)
28+
console.log(test_module.Person.static_elem)
29+
console.log(person.const_value)
30+
31+
function test_const_var(person) {
32+
console.log("in test const var")
1833
console.log(person.age)
19-
person.age = 123
34+
person.age = 33
2035
console.log(person.age)
21-
console.log(person.height)
22-
console.log(person.name)
23-
person.name = "VisualGMQ"
24-
console.log(person.name)
25-
console.log(test_module.Person.static_elem)
26-
}
27-
28-
main()
36+
}

0 commit comments

Comments
 (0)