Skip to content

Commit b8f423c

Browse files
committed
feat: add EnumDefineBuilder to support enum define
1 parent a11899c commit b8f423c

File tree

2 files changed

+343
-0
lines changed

2 files changed

+343
-0
lines changed

ApiDesign.md

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# JsEngine Api design
2+
3+
**JsEngine Api** 设计,除了 `endstone` 本身提供的 `Api`,借助 **ast-dumper** 的能力,可以往 `JsEngine` 添加一些标识类型的 `Api`,方便调试。
4+
5+
所有引擎提供的 Api 都以 `$` 开头,避免和 `endstone``Api` 冲突。
6+
7+
## enum
8+
9+
由于 **js** 没有枚举,所以借助 `class` 的能力来模拟枚举,所有 `endstone` 中的枚举都定义为 `static property`,并且添加一些 **engine helper api** 辅助调试。
10+
11+
`nickName`**engine** 侧的枚举名,`c_type` 是 C++ 侧的枚举名。
12+
引入 `nickName` 是为了避免枚举冲突。
13+
14+
### C++ 侧:
15+
16+
```cpp
17+
enum class TestEnum {
18+
value1,
19+
value2,
20+
value3,
21+
};
22+
```
23+
24+
### Js 侧:
25+
26+
```js
27+
// T -> enum
28+
class T {
29+
static get value1() {
30+
/* native code */
31+
}
32+
33+
//! engine helper api
34+
/**
35+
* 枚举的所有值
36+
*/
37+
static get $values() {
38+
/* native code */
39+
}
40+
41+
/**
42+
* 枚举的所有键名
43+
*/
44+
static get $keys() {
45+
/* native code */
46+
}
47+
48+
/**
49+
* 枚举值对应的键名
50+
*/
51+
static $enum_name(T) {
52+
/* native code */
53+
}
54+
55+
/**
56+
* C++ 侧的原始枚举名
57+
*/
58+
static get $c_type() {
59+
/* native code */
60+
}
61+
62+
/**
63+
* 注册到 engine 的枚举名
64+
*/
65+
static get $nick_name() {
66+
/* native code */
67+
}
68+
}
69+
```
70+
71+
## class
72+
73+
这个和 C++ 侧的 `class` 是一样的,只是多了一些 **engine helper api**
74+
不过由于 js 不支持多继承,所以 C++ 侧的 `class` 是采用全量绑定,在 js 侧就是一个个完全独立的 `class。`
75+
76+
### C++ 侧:
77+
78+
```cpp
79+
80+
namespace endstone {
81+
82+
class Actor {
83+
public:
84+
virtual void move() = 0;
85+
};
86+
87+
class Mob : public Actor {
88+
public:
89+
virtual void attack() = 0;
90+
};
91+
92+
class Player : public Mob {
93+
public:
94+
virtual void jump() = 0;
95+
};
96+
97+
} // namespace endstone
98+
99+
100+
namespace jse {
101+
102+
class ActorWrapper : public ScriptClass {
103+
endstone::Actor* actor;
104+
105+
public:
106+
Local<Value> move(Arguments const& args) {
107+
actor->move();
108+
}
109+
};
110+
111+
class MobWrapper : public ScriptClass {
112+
endstone::Mob* mob;
113+
114+
public:
115+
Local<Value> attack(Arguments const& args) {
116+
mob->attack();
117+
}
118+
};
119+
120+
class PlayerWrapper : public ScriptClass {
121+
endstone::Player* player;
122+
123+
public:
124+
Local<Value> jump(Arguments const& args) {
125+
player->jump();
126+
}
127+
};
128+
129+
ClassDefine<ActorWrapper> ActorBuilder = defineClass("Actor")
130+
.constructor(nullptr)
131+
.instanceFunction("move", &ActorWrapper::move)
132+
.build();
133+
134+
ClassDefine<MobWrapper> MobBuilder = defineClass("Mob")
135+
.constructor(nullptr)
136+
.instanceFunction("attack", &MobWrapper::attack)
137+
// Actor
138+
.instanceFunction("move", &ActorWrapper::move)
139+
.build();
140+
141+
ClassDefine<PlayerWrapper> PlayerBuilder = defineClass("Player")
142+
.constructor(nullptr)
143+
// Actor
144+
.instanceFunction("move", &ActorWrapper::move)
145+
// Mob
146+
.instanceFunction("attack", &MobWrapper::attack)
147+
// Player
148+
.instanceFunction("jump", &PlayerWrapper::jump)
149+
.build();
150+
151+
} // namespace jse
152+
153+
```
154+
155+
### Js 侧:
156+
157+
```js
158+
class Actor {
159+
// C++ 侧没有注册 constructor,所以这里没有 constructor
160+
161+
move() {
162+
/* native code */
163+
}
164+
165+
//! engine helper api
166+
static get $c_type() {
167+
/* native code */
168+
}
169+
170+
static get $nick_name() {
171+
/* native code */
172+
}
173+
174+
/**
175+
* 引用类型
176+
* C++ 侧分为原始指针、独占指针、共享指针,通过此方法得知当前的 native 资源持有方式
177+
*/
178+
get $ref_type() {
179+
/* native code */
180+
}
181+
182+
/**
183+
* 引用计数
184+
* 只有当 $ref_type 为 shared_ptr 时才有效
185+
*/
186+
get $ref_count() {
187+
/* native code */
188+
}
189+
190+
/**
191+
* 对两个对象进行相等性比较
192+
* 由于 js 侧的比较无法准确判断两个对象是否相等,所以引擎提供了此方法,此方法会调用 C++ 侧的 operator==
193+
*/
194+
static $equals(a, b) {
195+
/* native code */
196+
}
197+
}
198+
199+
class Mob {
200+
move() {
201+
/* native code */
202+
}
203+
204+
attack() {
205+
/* native code */
206+
}
207+
208+
//引擎API同Actor
209+
}
210+
211+
// Player 同理
212+
```
213+
214+
## function
215+
216+
这里没啥好说的,除了 js function 不支持重载,其它都一样。
217+
218+
对于多重载方法,C++ 侧只注册一个方法,通过参数类型决定调用哪个方法。
219+
如果参数类型不匹配,则抛出 `Wrong argument type, please check the documentation` 异常。

src/helper/EnumHelper.h

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#pragma once
2+
#include "helper/Using.h"
3+
#include "magic_enum/magic_enum.hpp"
4+
#include <exception>
5+
#include <map>
6+
#include <string>
7+
#include <string_view>
8+
#include <unordered_map>
9+
10+
11+
namespace jse {
12+
13+
14+
// range: [-128, 128)
15+
template <typename T>
16+
struct EnumDefineBuilder {
17+
std::string mNickName;
18+
std::string mOriginName;
19+
20+
// 补充的枚举值(处理超出 magic_enum 范围的枚举)
21+
std::unordered_map<std::string, int> mSupplementEnumKV;
22+
std::unordered_map<int, std::string> mSupplementEnumVK;
23+
inline EnumDefineBuilder<T>& supplementEnum(std::string_view name, T val) {
24+
mSupplementEnumKV[std::string(name)] = static_cast<int>(val);
25+
mSupplementEnumVK[static_cast<int>(val)] = name;
26+
return *this;
27+
}
28+
29+
explicit EnumDefineBuilder<T>(std::string originName, std::string nickName = "") {
30+
mOriginName = std::move(originName);
31+
mNickName = nickName.empty() ? originName : std::move(nickName);
32+
}
33+
34+
inline void registerHelperFunc(script::ClassDefineBuilder<void>& builder) {
35+
builder.property("$c_type", [=]() -> Local<Value> {
36+
try {
37+
return String::newString(mOriginName);
38+
} catch (script::Exception const& e) {
39+
} catch (std::exception const& e) {}
40+
});
41+
42+
builder.property("$nick_name", [=]() -> Local<Value> {
43+
try {
44+
return String::newString(mNickName);
45+
} catch (script::Exception const& e) {
46+
} catch (std::exception const& e) {}
47+
});
48+
49+
builder.property("$keys", [=]() -> Local<Value> {
50+
try {
51+
auto keys = magic_enum::enum_names<T>();
52+
auto array = Array::newArray(keys.size());
53+
for (size_t i = 0; i < keys.size(); ++i) {
54+
array.set(i, String::newString(std::string(keys[i])));
55+
}
56+
if (!mSupplementEnumKV.empty()) {
57+
for (const auto& [name, value] : mSupplementEnumKV) {
58+
array.add(String::newString(name));
59+
}
60+
}
61+
return array;
62+
} catch (script::Exception const& e) {
63+
} catch (std::exception const& e) {}
64+
});
65+
66+
builder.property("$values", [=]() -> Local<Value> {
67+
try {
68+
auto values = magic_enum::enum_values<T>();
69+
auto array = Array::newArray(values.size());
70+
for (size_t i = 0; i < values.size(); ++i) {
71+
array.set(i, Number::newNumber(static_cast<int>(values[i])));
72+
}
73+
if (!mSupplementEnumKV.empty()) {
74+
for (const auto& [name, value] : mSupplementEnumKV) {
75+
array.add(Number::newNumber(value));
76+
}
77+
}
78+
return array;
79+
} catch (script::Exception const& e) {
80+
} catch (std::exception const& e) {}
81+
});
82+
83+
builder.function("$enum_name", [=](Arguments const& args) -> Local<Value> {
84+
try {
85+
if (args.size() != 1 || !args[0].isNumber()) {
86+
return {};
87+
}
88+
auto val = args[0].asNumber().toInt32();
89+
if (val < -128 || val > 128) {
90+
if (mSupplementEnumVK.find(val) != mSupplementEnumVK.end()) {
91+
return String::newString(mSupplementEnumVK[val]);
92+
}
93+
}
94+
return String::newString(magic_enum::enum_name(static_cast<T>(val)));
95+
} catch (script::Exception const& e) {
96+
} catch (std::exception const& e) {}
97+
});
98+
}
99+
100+
[[nodiscard]] inline ClassDefine<void> build(std::string nickName) {
101+
script::ClassDefineBuilder<void> builder = defineClass(std::move(nickName));
102+
103+
// 注册枚举
104+
for (auto& [val, name] : magic_enum::enum_entries<T>()) {
105+
auto _val = val;
106+
builder.property(std::string(name), [=]() -> Local<Value> {
107+
try {
108+
return Number::newNumber(static_cast<int>(_val));
109+
} catch (script::Exception const& e) {
110+
} catch (std::exception const& e) {}
111+
});
112+
}
113+
for (const auto& [name, value] : mSupplementEnumKV) {
114+
builder.property(name, [=]() -> Local<Value> { return Number::newNumber(value); });
115+
}
116+
117+
registerHelperFunc(builder);
118+
119+
return builder.build();
120+
}
121+
};
122+
123+
124+
} // namespace jse

0 commit comments

Comments
 (0)