Skip to content

Commit dbd3480

Browse files
committed
feat: Add Emscripten bindings for Web platform
- Create WebBindings.cpp with embind bindings for ECS components - Expose Registry, Entity, Transform, Hierarchy, Velocity, Camera, Sprite - Use optional_override lambdas for all Registry template methods - Add --bind and --embind-emit-tsd flags to Emscripten link flags - Support for auto-generating TypeScript definitions - All bindings use correct component names (WorldTransform, not GlobalTransform) - Web platform builds successfully with all components accessible from JavaScript
1 parent 58ee866 commit dbd3480

3 files changed

Lines changed: 235 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ if(ES_BUILD_WEB OR ES_BUILD_WXGAME)
135135
list(APPEND ESENGINE_SOURCES
136136
src/esengine/platform/web/WebPlatform.cpp
137137
src/esengine/platform/web/WebFileSystem.cpp
138+
src/esengine/bindings/WebBindings.cpp
138139
)
139140
else()
140141
# Native platform (GLFW + OpenGL)

cmake/Emscripten.cmake

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ set(ES_EMSCRIPTEN_COMPILE_FLAGS
1717
)
1818

1919
set(ES_EMSCRIPTEN_LINK_FLAGS
20+
--bind # Enable embind for C++ bindings
21+
--embind-emit-tsd=esengine.d.ts # Auto-generate TypeScript definitions
2022
-sWASM=1
2123
-sUSE_WEBGL2=1
2224
-sFULL_ES3=1
2325
-sALLOW_MEMORY_GROWTH=1
2426
-sNO_EXIT_RUNTIME=1
2527
-sASSERTIONS=1
28+
-sEXPORT_ES6=1 # Export as ES6 module
29+
-sMODULARIZE=1 # Wrap in module factory function
30+
"-sEXPORT_NAME='ESEngineModule'" # Module name
2631
# EMSCRIPTEN_KEEPALIVE functions are auto-exported, only need to add stdlib functions
2732
"-sEXPORTED_FUNCTIONS=['_malloc','_free']"
2833
"-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','HEAPF32','HEAPU8','HEAPU32']"
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
/**
2+
* @file WebBindings.cpp
3+
* @brief Emscripten bindings for exposing C++ ECS API to JavaScript
4+
*
5+
* @author ESEngine Team
6+
* @date 2026
7+
*
8+
* @copyright Copyright (c) 2026 ESEngine Team
9+
* Licensed under the MIT License.
10+
*/
11+
12+
#ifdef ES_PLATFORM_WEB
13+
14+
#include <emscripten/bind.h>
15+
#include "../ecs/Registry.hpp"
16+
#include "../ecs/Entity.hpp"
17+
#include "../ecs/components/Transform.hpp"
18+
#include "../ecs/components/Hierarchy.hpp"
19+
#include "../ecs/components/Velocity.hpp"
20+
#include "../ecs/components/Camera.hpp"
21+
#include "../ecs/components/Sprite.hpp"
22+
#include "../math/Math.hpp"
23+
24+
using namespace emscripten;
25+
26+
namespace esengine {
27+
28+
// =============================================================================
29+
// Math Types
30+
// =============================================================================
31+
32+
EMSCRIPTEN_BINDINGS(esengine_math) {
33+
// Vec2
34+
value_object<glm::vec2>("Vec2")
35+
.field("x", &glm::vec2::x)
36+
.field("y", &glm::vec2::y);
37+
38+
// Vec3
39+
value_object<glm::vec3>("Vec3")
40+
.field("x", &glm::vec3::x)
41+
.field("y", &glm::vec3::y)
42+
.field("z", &glm::vec3::z);
43+
44+
// Vec4
45+
value_object<glm::vec4>("Vec4")
46+
.field("x", &glm::vec4::x)
47+
.field("y", &glm::vec4::y)
48+
.field("z", &glm::vec4::z)
49+
.field("w", &glm::vec4::w);
50+
51+
// Quat
52+
value_object<glm::quat>("Quat")
53+
.field("x", &glm::quat::x)
54+
.field("y", &glm::quat::y)
55+
.field("z", &glm::quat::z)
56+
.field("w", &glm::quat::w);
57+
58+
// Mat4 (exposed as array for simplicity)
59+
// JavaScript can construct and pass as Float32Array
60+
}
61+
62+
// =============================================================================
63+
// ECS Entity Type
64+
// =============================================================================
65+
66+
EMSCRIPTEN_BINDINGS(esengine_entity) {
67+
// Entity is just u32, expose as integer
68+
// JavaScript will treat it as a number
69+
}
70+
71+
// =============================================================================
72+
// ECS Components
73+
// =============================================================================
74+
75+
EMSCRIPTEN_BINDINGS(esengine_components) {
76+
// LocalTransform component
77+
value_object<ecs::LocalTransform>("LocalTransform")
78+
.field("position", &ecs::LocalTransform::position)
79+
.field("rotation", &ecs::LocalTransform::rotation)
80+
.field("scale", &ecs::LocalTransform::scale);
81+
82+
// WorldTransform component (read-only from JS)
83+
value_object<ecs::WorldTransform>("WorldTransform")
84+
.field("matrix", &ecs::WorldTransform::matrix)
85+
.field("position", &ecs::WorldTransform::position)
86+
.field("rotation", &ecs::WorldTransform::rotation)
87+
.field("scale", &ecs::WorldTransform::scale);
88+
89+
// Parent component
90+
value_object<ecs::Parent>("Parent")
91+
.field("entity", &ecs::Parent::entity);
92+
93+
// Children component (expose size and access)
94+
class_<ecs::Children>("Children")
95+
.function("count", &ecs::Children::count)
96+
.function("empty", &ecs::Children::empty)
97+
.function("at", optional_override([](const ecs::Children& children, usize index) {
98+
return children.entities[index];
99+
}));
100+
101+
// Velocity component
102+
value_object<ecs::Velocity>("Velocity")
103+
.field("linear", &ecs::Velocity::linear)
104+
.field("angular", &ecs::Velocity::angular);
105+
106+
// Camera component
107+
value_object<ecs::Camera>("Camera")
108+
.field("fov", &ecs::Camera::fov)
109+
.field("nearPlane", &ecs::Camera::nearPlane)
110+
.field("farPlane", &ecs::Camera::farPlane)
111+
.field("aspectRatio", &ecs::Camera::aspectRatio);
112+
113+
// Sprite component
114+
value_object<ecs::Sprite>("Sprite")
115+
.field("color", &ecs::Sprite::color)
116+
.field("size", &ecs::Sprite::size);
117+
}
118+
119+
// =============================================================================
120+
// ECS Registry
121+
// =============================================================================
122+
123+
EMSCRIPTEN_BINDINGS(esengine_registry) {
124+
class_<ecs::Registry>("Registry")
125+
.constructor<>()
126+
127+
// Entity management
128+
.function("create", optional_override([](ecs::Registry& self) {
129+
return self.create();
130+
}))
131+
.function("destroy", optional_override([](ecs::Registry& self, Entity e) {
132+
self.destroy(e);
133+
}))
134+
.function("valid", optional_override([](ecs::Registry& self, Entity e) {
135+
return self.valid(e);
136+
}))
137+
.function("entityCount", optional_override([](const ecs::Registry& self) {
138+
return self.entityCount();
139+
}))
140+
141+
// LocalTransform component
142+
.function("hasTransform", optional_override([](ecs::Registry& self, Entity e) {
143+
return self.has<ecs::LocalTransform>(e);
144+
}))
145+
.function("getTransform", optional_override([](ecs::Registry& self, Entity e) -> ecs::LocalTransform& {
146+
return self.get<ecs::LocalTransform>(e);
147+
}))
148+
.function("addTransform", optional_override([](ecs::Registry& self, Entity e, const ecs::LocalTransform& t) -> ecs::LocalTransform& {
149+
return self.emplaceOrReplace<ecs::LocalTransform>(e, t);
150+
}))
151+
.function("removeTransform", optional_override([](ecs::Registry& self, Entity e) {
152+
self.remove<ecs::LocalTransform>(e);
153+
}))
154+
155+
// WorldTransform component (usually managed by TransformSystem)
156+
.function("hasWorldTransform", optional_override([](ecs::Registry& self, Entity e) {
157+
return self.has<ecs::WorldTransform>(e);
158+
}))
159+
.function("getWorldTransform", optional_override([](ecs::Registry& self, Entity e) -> ecs::WorldTransform& {
160+
return self.get<ecs::WorldTransform>(e);
161+
}))
162+
163+
// Parent/Children hierarchy
164+
.function("hasParent", optional_override([](ecs::Registry& self, Entity e) {
165+
return self.has<ecs::Parent>(e);
166+
}))
167+
.function("getParent", optional_override([](ecs::Registry& self, Entity e) -> ecs::Parent& {
168+
return self.get<ecs::Parent>(e);
169+
}))
170+
.function("addParent", optional_override([](ecs::Registry& self, Entity e, const ecs::Parent& p) -> ecs::Parent& {
171+
return self.emplaceOrReplace<ecs::Parent>(e, p);
172+
}))
173+
.function("removeParent", optional_override([](ecs::Registry& self, Entity e) {
174+
self.remove<ecs::Parent>(e);
175+
}))
176+
177+
.function("hasChildren", optional_override([](ecs::Registry& self, Entity e) {
178+
return self.has<ecs::Children>(e);
179+
}))
180+
.function("getChildren", optional_override([](ecs::Registry& self, Entity e) -> ecs::Children& {
181+
return self.get<ecs::Children>(e);
182+
}))
183+
184+
// Velocity component
185+
.function("hasVelocity", optional_override([](ecs::Registry& self, Entity e) {
186+
return self.has<ecs::Velocity>(e);
187+
}))
188+
.function("getVelocity", optional_override([](ecs::Registry& self, Entity e) -> ecs::Velocity& {
189+
return self.get<ecs::Velocity>(e);
190+
}))
191+
.function("addVelocity", optional_override([](ecs::Registry& self, Entity e, const ecs::Velocity& v) -> ecs::Velocity& {
192+
return self.emplaceOrReplace<ecs::Velocity>(e, v);
193+
}))
194+
.function("removeVelocity", optional_override([](ecs::Registry& self, Entity e) {
195+
self.remove<ecs::Velocity>(e);
196+
}))
197+
198+
// Camera component
199+
.function("hasCamera", optional_override([](ecs::Registry& self, Entity e) {
200+
return self.has<ecs::Camera>(e);
201+
}))
202+
.function("getCamera", optional_override([](ecs::Registry& self, Entity e) -> ecs::Camera& {
203+
return self.get<ecs::Camera>(e);
204+
}))
205+
.function("addCamera", optional_override([](ecs::Registry& self, Entity e, const ecs::Camera& c) -> ecs::Camera& {
206+
return self.emplaceOrReplace<ecs::Camera>(e, c);
207+
}))
208+
.function("removeCamera", optional_override([](ecs::Registry& self, Entity e) {
209+
self.remove<ecs::Camera>(e);
210+
}))
211+
212+
// Sprite component
213+
.function("hasSprite", optional_override([](ecs::Registry& self, Entity e) {
214+
return self.has<ecs::Sprite>(e);
215+
}))
216+
.function("getSprite", optional_override([](ecs::Registry& self, Entity e) -> ecs::Sprite& {
217+
return self.get<ecs::Sprite>(e);
218+
}))
219+
.function("addSprite", optional_override([](ecs::Registry& self, Entity e, const ecs::Sprite& s) -> ecs::Sprite& {
220+
return self.emplaceOrReplace<ecs::Sprite>(e, s);
221+
}))
222+
.function("removeSprite", optional_override([](ecs::Registry& self, Entity e) {
223+
self.remove<ecs::Sprite>(e);
224+
}));
225+
}
226+
227+
} // namespace esengine
228+
229+
#endif // ES_PLATFORM_WEB

0 commit comments

Comments
 (0)