diff --git a/3rd/box2d/include/box2d_bindings.hpp b/3rd/box2d/include/box2d_bindings.hpp index a9deafd8..2eb1c585 100644 --- a/3rd/box2d/include/box2d_bindings.hpp +++ b/3rd/box2d/include/box2d_bindings.hpp @@ -3,28 +3,123 @@ #include "box2d/b2_world.h" #include "box2d/box2d.h" #include "pocketpy/pocketpy.h" -#include namespace pkpy{ + template<> + inline b2Vec2 py_cast(VM* vm, PyObject* obj){ + Vec2 v = py_cast(vm, obj); + return b2Vec2(v.x, v.y); + } -template<> -inline b2Vec2 py_cast(VM* vm, PyObject* obj){ - Vec2 v = py_cast(vm, obj); - return b2Vec2(v.x, v.y); + template<> + inline b2Vec2 _py_cast(VM* vm, PyObject* obj){ + Vec2 v = _py_cast(vm, obj); + return b2Vec2(v.x, v.y); + } + + inline PyObject* py_var(VM* vm, b2Vec2 v){ + return py_var(vm, Vec2(v.x, v.y)); + } } -template<> -inline b2Vec2 _py_cast(VM* vm, PyObject* obj){ - Vec2 v = _py_cast(vm, obj); - return b2Vec2(v.x, v.y); -} - -inline PyObject* py_var(VM* vm, b2Vec2 v){ - return py_var(vm, Vec2(v.x, v.y)); -} +using namespace pkpy; namespace imbox2d{ +// maybe we will use this class later +struct PyDebugDraw: b2Draw{ + PK_ALWAYS_PASS_BY_POINTER(PyDebugDraw) + + VM* vm; + PyObject* draw_like; + + PyDebugDraw(VM* vm): vm(vm){} + + void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) override{ + } + + void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) override{ + } + + void DrawCircle(const b2Vec2& center, float radius, const b2Color& color) override{ + } + + void DrawSolidCircle(const b2Vec2& center, float radius, const b2Vec2& axis, const b2Color& color) override{ + } + + void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color) override{ + } + + void DrawTransform(const b2Transform& xf) override{ + } + + void DrawPoint(const b2Vec2& p, float size, const b2Color& color) override{ + } +}; + +struct PyContactListener: b2ContactListener{ + PK_ALWAYS_PASS_BY_POINTER(PyContactListener) + VM* vm; + PyContactListener(VM* vm): vm(vm){} + + void _contact_f(b2Contact* contact, StrName name){ + auto a = contact->GetFixtureA()->GetBody()->GetUserData().pointer; + auto b = contact->GetFixtureB()->GetBody()->GetUserData().pointer; + Body* bodyA = reinterpret_cast(a); + Body* bodyB = reinterpret_cast(b); + PyObject* self; + PyObject* f; + f = vm->get_unbound_method(bodyA->obj, name, &self, false); + if(f != nullptr) vm->call_method(self, f, VAR_T(PyBody, bodyB)); + f = vm->get_unbound_method(bodyB->obj, name, &self, false); + if(f != nullptr) vm->call_method(self, f, VAR_T(PyBody, bodyA)); + } + + void BeginContact(b2Contact* contact) override { + DEF_SNAME(on_contact_begin); + _contact_f(contact, on_contact_begin); + } + + void EndContact(b2Contact* contact) override { + DEF_SNAME(on_contact_end); + _contact_f(contact, on_contact_end); + } +}; + +struct PyBody{ + PY_CLASS(PyBody, box2d, Body) + PK_ALWAYS_PASS_BY_POINTER(PyBody) + + b2Body* body; + b2Fixture* fixture; + PyObject* node_like; + + PyBody() = default; + + void _gc_mark() { + PK_OBJ_MARK(node_like); + } + + static void _register(VM* vm, PyObject* mod, PyObject* type); +}; + +struct PyWorld { + PY_CLASS(PyWorld, box2d, World) + PK_ALWAYS_PASS_BY_POINTER(PyWorld) + + b2World world; + PyContactListener _contact_listener; + PyDebugDraw _debug_draw; + + PyWorld(VM* vm); + + void _gc_mark(){ + PK_OBJ_MARK(_debug_draw.draw_like); + } + + static void _register(VM* vm, PyObject* mod, PyObject* type); +}; + struct Body final{ b2Body* body; @@ -128,17 +223,18 @@ struct Body final{ } }; -struct PyBody: OpaquePointer{ - PY_CLASS(PyBody, box2d, Body) - - using OpaquePointer::OpaquePointer; - static void _register(VM* vm, PyObject* mod, PyObject* type); -}; - +inline PyObject* get_body_object(b2Body* p){ + auto userdata = p->GetUserData().pointer; + return reinterpret_cast(userdata); +} } // namespace imbox2d -void add_module_box2d(VM* vm); - -} // namespace pkpy \ No newline at end of file +namespace pkpy{ + inline void add_module_box2d(VM* vm){ + PyObject* mod = vm->new_module("box2d"); + imbox2d::PyBody::register_class(vm, mod); + imbox2d::PyWorld::register_class(vm, mod); + } +} \ No newline at end of file diff --git a/3rd/box2d/src/box2d_Body.cpp b/3rd/box2d/src/box2d_Body.cpp index e69de29b..62c6490d 100644 --- a/3rd/box2d/src/box2d_Body.cpp +++ b/3rd/box2d/src/box2d_Body.cpp @@ -0,0 +1,28 @@ +#include "box2d/b2_world.h" +#include "box2d/b2_world_callbacks.h" +#include "box2d_bindings.hpp" + +using namespace pkpy; + +namespace imbox2d{ + + +void PyBody::_register(VM* vm, PyObject* mod, PyObject* type){ + vm->bind(type, "__new__(cls, world: World, node: _NodeLike = None)", + [](VM* vm, ArgsView args){ + PyWorld& world = CAST(PyWorld&, args[1]); + PyObject* node = args[2]; + PyObject* obj = VAR_T(PyBody, PyBody()); + PyBody& body = _CAST(PyBody&, obj); + b2BodyDef def; + def.type = b2_dynamicBody; + // a weak reference to this object + def.userData.pointer = reinterpret_cast(obj); + body.body = world.world.CreateBody(&def); + body.fixture = nullptr; + body.node_like = node; + return obj; + }); +} + +} // namespace imbox2d \ No newline at end of file diff --git a/3rd/box2d/src/box2d_World.cpp b/3rd/box2d/src/box2d_World.cpp index feea3320..5bcd6daf 100644 --- a/3rd/box2d/src/box2d_World.cpp +++ b/3rd/box2d/src/box2d_World.cpp @@ -2,122 +2,47 @@ #include "box2d/b2_world_callbacks.h" #include "box2d_bindings.hpp" -namespace pkpy{ - namespace imbox2d{ +using namespace pkpy; + +namespace imbox2d{ + +struct MyRayCastCallback: b2RayCastCallback{ + PK_ALWAYS_PASS_BY_POINTER(MyRayCastCallback) -// This class captures the closest hit shape. -class MyRayCastCallback : public b2RayCastCallback -{ VM* vm; -public: List result; MyRayCastCallback(VM* vm): vm(vm) {} - float ReportFixture(b2Fixture* fixture, const b2Vec2& point, - const b2Vec2& normal, float fraction) - { - auto userdata = fixture->GetBody()->GetUserData().pointer; - Body* body = reinterpret_cast(userdata); - result.push_back(VAR_T(PyBody, body)); + float ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float fraction){ + result.push_back(get_body_object(fixture->GetBody())); // if(only_one) return 0; return fraction; } }; -class MyBoxCastCallback: public b2QueryCallback{ +struct MyBoxCastCallback: b2QueryCallback{ + PK_ALWAYS_PASS_BY_POINTER(MyBoxCastCallback) + VM* vm; -public: List result; MyBoxCastCallback(VM* vm): vm(vm) {} bool ReportFixture(b2Fixture* fixture) override{ - auto userdata = fixture->GetBody()->GetUserData().pointer; - Body* body = reinterpret_cast(userdata); - result.push_back(VAR_T(PyBody, body)); + result.push_back(get_body_object(fixture->GetBody())); return true; } }; -// maybe we will use this class later -class PyDebugDraw: public b2Draw{ - VM* vm; -public: - PyDebugDraw(VM* vm): vm(vm){} +/****************** PyWorld ******************/ +PyWorld::PyWorld(VM* vm): world(b2Vec2(0, 0)), _contact_listener(vm), _debug_draw(vm){ + _debug_draw.draw_like = vm->None; + world.SetAllowSleeping(true); + world.SetAutoClearForces(true); + world.SetContactListener(&_contact_listener); + world.SetDebugDraw(&_debug_draw); +} - void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) override{ - } - - void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) override{ - } - - void DrawCircle(const b2Vec2& center, float radius, const b2Color& color) override{ - } - - void DrawSolidCircle(const b2Vec2& center, float radius, const b2Vec2& axis, const b2Color& color) override{ - } - - void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color) override{ - } - - void DrawTransform(const b2Transform& xf) override{ - } - - void DrawPoint(const b2Vec2& p, float size, const b2Color& color) override{ - } -}; - -class PyContactListener : public b2ContactListener{ - VM* vm; -public: - PyContactListener(VM* vm): vm(vm){} - - void _contact_f(b2Contact* contact, StrName name){ - auto a = contact->GetFixtureA()->GetBody()->GetUserData().pointer; - auto b = contact->GetFixtureB()->GetBody()->GetUserData().pointer; - Body* bodyA = reinterpret_cast(a); - Body* bodyB = reinterpret_cast(b); - PyObject* self; - PyObject* f; - f = vm->get_unbound_method(bodyA->obj, name, &self, false); - if(f != nullptr) vm->call_method(self, f, VAR_T(PyBody, bodyB)); - f = vm->get_unbound_method(bodyB->obj, name, &self, false); - if(f != nullptr) vm->call_method(self, f, VAR_T(PyBody, bodyA)); - } - - void BeginContact(b2Contact* contact) override { - DEF_SNAME(on_contact_begin); - _contact_f(contact, on_contact_begin); - } - - void EndContact(b2Contact* contact) override { - DEF_SNAME(on_contact_end); - _contact_f(contact, on_contact_end); - } -}; - -// implement placement VAR_T...!!!! -struct PyWorld { - PY_CLASS(PyWorld, box2d, World) - - // this object is too large, so we use unique_ptr - std::unique_ptr world; - std::unique_ptr _contact_listener; - std::unique_ptr _debug_draw; - - PyWorld(VM* vm): - world(new b2World(b2Vec2(0, 0))), - _contact_listener(new PyContactListener(vm)), - _debug_draw(new PyDebugDraw(vm)){ - world->SetAllowSleeping(true); - world->SetAutoClearForces(true); - world->SetContactListener(_contact_listener.get()); - world->SetDebugDraw(_debug_draw.get()); - } - - PyWorld(const PyWorld&) = delete; - PyWorld& operator=(const PyWorld&) = delete; - - static void _register(VM* vm, PyObject* mod, PyObject* type){ +void PyWorld::_register(VM* vm, PyObject* mod, PyObject* type){ vm->bind(type, "__new__(cls)", [](VM* vm, ArgsView args){ return VAR_T(PyWorld, PyWorld(vm)); }); @@ -125,20 +50,19 @@ struct PyWorld { // gravity vm->bind_property(type, "gravity", "vec2", [](VM* vm, ArgsView args){ PyWorld& self = _CAST(PyWorld&, args[0]); - return VAR(self.world->GetGravity()); + return VAR(self.world.GetGravity()); }, [](VM* vm, ArgsView args){ PyWorld& self = _CAST(PyWorld&, args[0]); - self.world->SetGravity(CAST(b2Vec2, args[1])); + self.world.SetGravity(CAST(b2Vec2, args[1])); return vm->None; }); vm->bind(type, "get_bodies(self) -> list[Body]", [](VM* vm, ArgsView args){ PyWorld& self = _CAST(PyWorld&, args[0]); List list; - b2Body* p = self.world->GetBodyList(); + b2Body* p = self.world.GetBodyList(); while(p != nullptr){ - Body* body = (Body*)p->GetUserData().pointer; - list.push_back(VAR_T(PyBody, body)); + list.push_back(get_body_object(p)); p = p->GetNext(); } return VAR(std::move(list)); @@ -150,7 +74,7 @@ struct PyWorld { b2Vec2 start = CAST(b2Vec2, args[1]); b2Vec2 end = CAST(b2Vec2, args[2]); MyRayCastCallback callback(vm); - self.world->RayCast(&callback, start, end); + self.world.RayCast(&callback, start, end); return VAR(std::move(callback.result)); }); @@ -161,7 +85,7 @@ struct PyWorld { aabb.lowerBound = CAST(b2Vec2, args[1]); aabb.upperBound = CAST(b2Vec2, args[2]); MyBoxCastCallback callback(vm); - self.world->QueryAABB(&callback, aabb); + self.world.QueryAABB(&callback, aabb); return VAR(std::move(callback.result)); }); @@ -174,21 +98,36 @@ struct PyWorld { auto f = [](VM* vm, b2Body* p, StrName name){ while(p != nullptr){ - Body* body = (Body*)p->GetUserData().pointer; - vm->call_method(body->obj, name); + PyObject* body_obj = get_body_object(p); + PyBody& body = _CAST(PyBody&, body_obj); + if(body.node_like != vm->None){ + vm->call_method(body.node_like, name); + } p = p->GetNext(); } }; DEF_SNAME(on_box2d_pre_step); DEF_SNAME(on_box2d_post_step); - f(vm, self.world->GetBodyList(), on_box2d_pre_step); - self.world->Step(dt, velocity_iterations, position_iterations); - f(vm, self.world->GetBodyList(), on_box2d_post_step); + f(vm, self.world.GetBodyList(), on_box2d_pre_step); + self.world.Step(dt, velocity_iterations, position_iterations); + f(vm, self.world.GetBodyList(), on_box2d_post_step); return vm->None; }); - } -}; - } // namespace imbox2d -} // namespace pkpy \ No newline at end of file + vm->bind(type, "debug_draw(self, flags: int)", [](VM* vm, ArgsView args){ + PyWorld& self = _CAST(PyWorld&, args[0]); + int flags = CAST(int, args[1]); + self._debug_draw.SetFlags(flags); + self.world.DebugDraw(); + return vm->None; + }); + + vm->bind(type, "set_debug_draw(self, draw: _DrawLike)", [](VM* vm, ArgsView args){ + PyWorld& self = _CAST(PyWorld&, args[0]); + self._debug_draw.draw_like = args[1]; + return vm->None; + }); +} + +} // namespace imbox2d \ No newline at end of file diff --git a/3rd/box2d/src/box2d_bindings.cpp b/3rd/box2d/src/box2d_bindings.cpp index d8b74d5c..d70fa187 100644 --- a/3rd/box2d/src/box2d_bindings.cpp +++ b/3rd/box2d/src/box2d_bindings.cpp @@ -2,12 +2,6 @@ namespace pkpy{ -void add_module_box2d(VM *vm){ - PyObject* mod = vm->new_module("box2d"); - imbox2d::PyBody::register_class(vm, mod); - imbox2d::PyWorld::register_class(vm, mod); -} - namespace imbox2d{ diff --git a/include/pocketpy/common.h b/include/pocketpy/common.h index 63be2e3e..cd962414 100644 --- a/include/pocketpy/common.h +++ b/include/pocketpy/common.h @@ -164,4 +164,10 @@ struct is_pod { static constexpr bool value = std::is_trivially_copyable_v && std::is_standard_layout_v; }; +#define PK_ALWAYS_PASS_BY_POINTER(T) \ + T(const T&) = delete; \ + T& operator=(const T&) = delete; \ + T(T&&) = delete; \ + T& operator=(T&&) = delete; + } // namespace pkpy diff --git a/include/pocketpy/vm.h b/include/pocketpy/vm.h index b786a440..1b2457e0 100644 --- a/include/pocketpy/vm.h +++ b/include/pocketpy/vm.h @@ -107,6 +107,8 @@ struct FrameId{ typedef void(*PrintFunc)(VM*, const Str&); class VM { + PK_ALWAYS_PASS_BY_POINTER(VM) + VM* vm; // self reference for simplify code public: ManagedHeap heap;