diff --git a/.github/workflows/dylib.yml b/.github/workflows/dylib.yml index 0ad45bb0..cdfd401b 100644 --- a/.github/workflows/dylib.yml +++ b/.github/workflows/dylib.yml @@ -1,5 +1,7 @@ name: build dylib -on: [push, pull_request] +on: + push: + branches: [ main ] jobs: build_win: diff --git a/3rd/box2d/CMakeLists.txt b/3rd/box2d/CMakeLists.txt index 0599ee8e..eddff030 100644 --- a/3rd/box2d/CMakeLists.txt +++ b/3rd/box2d/CMakeLists.txt @@ -12,6 +12,8 @@ aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src/common BOX2D_SRC_1) aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src/dynamics BOX2D_SRC_2) aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src/rope BOX2D_SRC_3) +aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src BOX2D_BINDINGS_SRC) + set(CMAKE_CXX_FLAGS_RELEASE "-O2") set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -20,5 +22,5 @@ add_library( box2d STATIC ${BOX2D_SRC_0} ${BOX2D_SRC_1} ${BOX2D_SRC_2} ${BOX2D_SRC_3} - ${CMAKE_CURRENT_LIST_DIR}/src/box2d_bindings.cpp + ${BOX2D_BINDINGS_SRC} ) \ No newline at end of file diff --git a/3rd/box2d/include/box2d_bindings.hpp b/3rd/box2d/include/box2d_bindings.hpp index cb7cb12d..d1c29e06 100644 --- a/3rd/box2d/include/box2d_bindings.hpp +++ b/3rd/box2d/include/box2d_bindings.hpp @@ -1,8 +1,9 @@ #pragma once +#include "box2d/b2_world.h" #include "box2d/box2d.h" +#include "pocketpy/common.h" #include "pocketpy/pocketpy.h" -#include namespace pkpy{ @@ -22,126 +23,134 @@ inline PyObject* py_var(VM* vm, b2Vec2 v){ return py_var(vm, Vec2(v.x, v.y)); } -namespace imbox2d{ +inline PyObject* get_body_object(b2Body* p){ + auto userdata = p->GetUserData().pointer; + return reinterpret_cast(userdata); +} + +// 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){ + b2Body* bodyA = contact->GetFixtureA()->GetBody(); + b2Body* bodyB = contact->GetFixtureB()->GetBody(); + PyObject* a = get_body_object(bodyA); + PyObject* b = get_body_object(bodyB); + PyObject* self; + PyObject* f; + f = vm->get_unbound_method(a, name, &self, false); + if(f != nullptr) vm->call_method(self, f, b); + f = vm->get_unbound_method(b, name, &self, false); + if(f != nullptr) vm->call_method(self, f, a); + } + + 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) -struct Body final{ b2Body* body; b2Fixture* fixture; - PyObject* obj; - Vec4 debug_color; + PyObject* node_like; - Body(b2World* world, PyObject* obj){ - b2BodyDef def; - def.type = b2_dynamicBody; - // a weak reference to the object, no need to mark it - def.userData.pointer = reinterpret_cast(this); - body = world->CreateBody(&def); - fixture = nullptr; - this->obj = obj; - this->debug_color = Vec4(std::rand() / float(RAND_MAX), std::rand() / float(RAND_MAX), std::rand() / float(RAND_MAX), 1.0f); + PyBody(): body(nullptr), fixture(nullptr), node_like(nullptr){} + + void _gc_mark() { + PK_OBJ_MARK(node_like); } - void _update_fixture(b2Shape* shape){ - body->DestroyFixture(fixture); // this takes care of NULL case - fixture = body->CreateFixture(shape, 1.0f); - } + PyBody& _() { return *this; } + b2Body& _b2Body() { return *body; } + b2Fixture& _b2Fixture() { return *fixture; } - Vec4 get_debug_color() const{ return debug_color; } - - b2Vec2 get_position() const{ return body->GetPosition(); } - void set_position(b2Vec2 v){ body->SetTransform(v, get_rotation()); } - - void set_rotation(float angle){ body->SetTransform(get_position(), angle); } - float get_rotation() const{ return body->GetAngle(); } - - void set_velocity(b2Vec2 v){ body->SetLinearVelocity(v); } - b2Vec2 get_velocity() const{ return body->GetLinearVelocity(); } - - void set_angular_velocity(float omega){ body->SetAngularVelocity(omega); } - float get_angular_velocity() const{ return body->GetAngularVelocity(); } - - void set_damping(float damping){ body->SetLinearDamping(damping); } - float get_damping(){ return body->GetLinearDamping(); } - - void set_angular_damping(float damping){ body->SetAngularDamping(damping); } - float get_angular_damping() const{ return body->GetAngularDamping(); } - - void set_gravity_scale(float scale){ body->SetGravityScale(scale); } - float get_gravity_scale() const{ return body->GetGravityScale(); } - - void set_type(int type){ body->SetType(static_cast(type)); } - int get_type() const{ return static_cast(body->GetType()); } - - float get_mass() const{ return body->GetMass(); } - float get_inertia() const{ return body->GetInertia(); } - - bool get_fixed_rotation() const{ return body->IsFixedRotation(); } - void set_fixed_rotation(bool fixed){ body->SetFixedRotation(fixed); } - - // fixture settings - float get_density() const{ return fixture->GetDensity(); } - void set_density(float density){ fixture->SetDensity(density); } - - float get_friction() const{ return fixture->GetFriction(); } - void set_friction(float friction){ fixture->SetFriction(friction); } - - float get_restitution() const{ return fixture->GetRestitution(); } - void set_restitution(float restitution){ fixture->SetRestitution(restitution); } - - float get_restitution_threshold() const{ return fixture->GetRestitutionThreshold(); } - void set_restitution_threshold(float threshold){ fixture->SetRestitutionThreshold(threshold); } - - bool get_is_trigger() const{ return fixture->IsSensor(); } - void set_is_trigger(bool trigger){ fixture->SetSensor(trigger); } + static void _register(VM* vm, PyObject* mod, PyObject* type); // methods - void apply_force(b2Vec2 force, b2Vec2 point){ - body->ApplyForce(force, point, true); - } + b2Vec2 get_position() const { return body->GetPosition(); } + void set_position(b2Vec2 v){ body->SetTransform(v, body->GetAngle()); } + float get_rotation() const { return body->GetAngle(); } + void set_rotation(float v){ body->SetTransform(body->GetPosition(), v); } + b2Vec2 get_velocity() const { return body->GetLinearVelocity(); } + void set_velocity(b2Vec2 v){ body->SetLinearVelocity(v); } - void apply_force_to_center(b2Vec2 force){ - body->ApplyForceToCenter(force, true); - } - - void apply_torque(float torque){ - body->ApplyTorque(torque, true); - } - - void apply_linear_impulse(b2Vec2 impulse, b2Vec2 point){ + void apply_force(b2Vec2 force, b2Vec2 point){ body->ApplyForce(force, point, true); } + void apply_force_to_center(b2Vec2 force){ body->ApplyForceToCenter(force, true); } + void apply_torque(float torque){ body->ApplyTorque(torque, true); } + void apply_impulse(b2Vec2 impulse, b2Vec2 point){ body->ApplyLinearImpulse(impulse, point, true); } - - void apply_linear_impulse_to_center(b2Vec2 impulse){ + void apply_impulse_to_center(b2Vec2 impulse){ body->ApplyLinearImpulseToCenter(impulse, true); } - void apply_angular_impulse(float impulse){ body->ApplyAngularImpulse(impulse, true); } - - void destroy(){ - if(body == nullptr) return; - body->GetWorld()->DestroyBody(body); - body = nullptr; - } }; -struct PyBody: OpaquePointer{ - PY_CLASS(PyBody, box2d, Body) - - using OpaquePointer::OpaquePointer; - static void _register(VM* vm, PyObject* mod, PyObject* type); -}; - -struct PyWorld: OpaquePointer{ +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); + } - using OpaquePointer::OpaquePointer; static void _register(VM* vm, PyObject* mod, PyObject* type); }; -} // namespace imbox2d - -void add_module_box2d(VM* vm); +inline void add_module_box2d(VM* vm){ + PyObject* mod = vm->new_module("box2d"); + PyBody::register_class(vm, mod); + PyWorld::register_class(vm, mod); +} } // namespace pkpy \ No newline at end of file diff --git a/3rd/box2d/src/box2d_Body.cpp b/3rd/box2d/src/box2d_Body.cpp new file mode 100644 index 00000000..29d69c52 --- /dev/null +++ b/3rd/box2d/src/box2d_Body.cpp @@ -0,0 +1,86 @@ +#include "box2d/b2_world.h" +#include "box2d/b2_world_callbacks.h" +#include "box2d_bindings.hpp" +#include "pocketpy/bindings.h" + +namespace pkpy{ + +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 = vm->heap.gcnew(PyBody::_type(vm)); + 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; + }); + + PK_REGISTER_PROPERTY(PyBody, "type: int", _b2Body, GetType, SetType) + PK_REGISTER_PROPERTY(PyBody, "gravity_scale: float", _b2Body, GetGravityScale, SetGravityScale) + PK_REGISTER_PROPERTY(PyBody, "fixed_rotation: bool", _b2Body, IsFixedRotation, SetFixedRotation) + PK_REGISTER_PROPERTY(PyBody, "enabled: bool", _b2Body, IsEnabled, SetEnabled) + PK_REGISTER_PROPERTY(PyBody, "bullet: bool", _b2Body, IsBullet, SetBullet) + + PK_REGISTER_READONLY_PROPERTY(PyBody, "mass: float", _b2Body, GetMass) + PK_REGISTER_READONLY_PROPERTY(PyBody, "inertia: float", _b2Body, GetInertia) + + PK_REGISTER_PROPERTY(PyBody, "position: vec2", _, get_position, set_position) + PK_REGISTER_PROPERTY(PyBody, "rotation: float", _, get_rotation, set_rotation) + PK_REGISTER_PROPERTY(PyBody, "velocity: vec2", _, get_velocity, set_velocity) + PK_REGISTER_PROPERTY(PyBody, "angular_velocity: float", _b2Body, GetAngularVelocity, SetAngularVelocity) + PK_REGISTER_PROPERTY(PyBody, "damping: float", _b2Body, GetLinearDamping, SetLinearDamping) + PK_REGISTER_PROPERTY(PyBody, "angular_damping: float", _b2Body, GetAngularDamping, SetAngularDamping) + + PK_REGISTER_PROPERTY(PyBody, "density: float", _b2Fixture, GetDensity, SetDensity) + PK_REGISTER_PROPERTY(PyBody, "friction: float", _b2Fixture, GetFriction, SetFriction) + PK_REGISTER_PROPERTY(PyBody, "restitution: float", _b2Fixture, GetRestitution, SetRestitution) + PK_REGISTER_PROPERTY(PyBody, "restitution_threshold: float", _b2Fixture, GetRestitutionThreshold, SetRestitutionThreshold) + PK_REGISTER_PROPERTY(PyBody, "is_trigger: bool", _b2Fixture, IsSensor, SetSensor) + + // methods + _bind(vm, type, "apply_force(self, force: vec2, point: vec2)", &PyBody::apply_force); + _bind(vm, type, "apply_force_to_center(self, force: vec2)", &PyBody::apply_force_to_center); + _bind(vm, type, "apply_torque(self, torque: float)", &PyBody::apply_torque); + _bind(vm, type, "apply_impulse(self, impulse: vec2, point: vec2)", &PyBody::apply_impulse); + _bind(vm, type, "apply_impulse_to_center(self, impulse: vec2)", &PyBody::apply_impulse_to_center); + _bind(vm, type, "apply_angular_impulse(self, impulse: float)", &PyBody::apply_angular_impulse); + + // get_node + vm->bind(type, "get_node(self)", [](VM* vm, ArgsView args){ + PyBody& body = CAST(PyBody&, args[1]); + return body.node_like; + }); + + // get_contacts + vm->bind(type, "get_contacts(self) -> list[Body]", [](VM* vm, ArgsView args){ + PyBody& self = _CAST(PyBody&, args[0]); + b2ContactEdge* edge = self.body->GetContactList(); + List list; + while(edge){ + b2Fixture* fixtureB = edge->contact->GetFixtureB(); + b2Body* bodyB = fixtureB->GetBody(); + list.push_back(get_body_object(bodyB)); + edge = edge->next; + } + return VAR(std::move(list)); + }); + + // destroy + vm->bind(type, "destroy(self)", [](VM* vm, ArgsView args){ + PyBody& body = CAST(PyBody&, args[1]); + body.body->GetWorld()->DestroyBody(body.body); + body.body = nullptr; + body.fixture = nullptr; + body.node_like = nullptr; + return vm->None; + }); +} + +} // namespace pkpy \ No newline at end of file diff --git a/3rd/box2d/src/box2d_World.cpp b/3rd/box2d/src/box2d_World.cpp new file mode 100644 index 00000000..c9c757aa --- /dev/null +++ b/3rd/box2d/src/box2d_World.cpp @@ -0,0 +1,130 @@ +#include "box2d/b2_world.h" +#include "box2d/b2_world_callbacks.h" +#include "box2d_bindings.hpp" + +namespace pkpy{ + +struct MyRayCastCallback: b2RayCastCallback{ + PK_ALWAYS_PASS_BY_POINTER(MyRayCastCallback) + + VM* vm; + List result; + MyRayCastCallback(VM* vm): vm(vm) {} + + 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; + } +}; + +struct MyBoxCastCallback: b2QueryCallback{ + PK_ALWAYS_PASS_BY_POINTER(MyBoxCastCallback) + + VM* vm; + List result; + MyBoxCastCallback(VM* vm): vm(vm) {} + + bool ReportFixture(b2Fixture* fixture) override{ + result.push_back(get_body_object(fixture->GetBody())); + return true; + } +}; + +/****************** 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 PyWorld::_register(VM* vm, PyObject* mod, PyObject* type){ + vm->bind(type, "__new__(cls)", [](VM* vm, ArgsView args){ + return vm->heap.gcnew(PyWorld::_type(vm), vm); + }); + + // gravity + vm->bind_property(type, "gravity: vec2", [](VM* vm, ArgsView args){ + PyWorld& self = _CAST(PyWorld&, args[0]); + return VAR(self.world.GetGravity()); + }, [](VM* vm, ArgsView args){ + PyWorld& self = _CAST(PyWorld&, args[0]); + 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(); + while(p != nullptr){ + list.push_back(get_body_object(p)); + p = p->GetNext(); + } + return VAR(std::move(list)); + }); + + vm->bind(type, "ray_cast(self, start: vec2, end: vec2) -> list[Body]", [](VM* vm, ArgsView args){ + auto _lock = vm->heap.gc_scope_lock(); + PyWorld& self = _CAST(PyWorld&, args[0]); + b2Vec2 start = CAST(b2Vec2, args[1]); + b2Vec2 end = CAST(b2Vec2, args[2]); + MyRayCastCallback callback(vm); + self.world.RayCast(&callback, start, end); + return VAR(std::move(callback.result)); + }); + + vm->bind(type, "box_cast(self, lower: vec2, upper: vec2) -> list[Body]", [](VM* vm, ArgsView args){ + auto _lock = vm->heap.gc_scope_lock(); + PyWorld& self = _CAST(PyWorld&, args[0]); + b2AABB aabb; + aabb.lowerBound = CAST(b2Vec2, args[1]); + aabb.upperBound = CAST(b2Vec2, args[2]); + MyBoxCastCallback callback(vm); + self.world.QueryAABB(&callback, aabb); + return VAR(std::move(callback.result)); + }); + + vm->bind(type, "step(self, dt: float, velocity_iterations: int, position_iterations: int)", + [](VM* vm, ArgsView args){ + PyWorld& self = _CAST(PyWorld&, args[0]); + float dt = CAST(float, args[1]); + int velocity_iterations = CAST(int, args[2]); + int position_iterations = CAST(int, args[3]); + + auto f = [](VM* vm, b2Body* p, StrName name){ + while(p != nullptr){ + 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); + return vm->None; + }); + + 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 pkpy \ No newline at end of file diff --git a/3rd/box2d/src/box2d_bindings.cpp b/3rd/box2d/src/box2d_bindings.cpp deleted file mode 100644 index 6b646fdd..00000000 --- a/3rd/box2d/src/box2d_bindings.cpp +++ /dev/null @@ -1,321 +0,0 @@ -#include "box2d_bindings.hpp" - -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{ - -// maybe we will use this class later -class PyDebugDraw: public b2Draw{ - VM* vm; -public: - 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{ - } -}; - -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); - } -}; - - void PyBody::_register(VM* vm, PyObject* mod, PyObject* type){ - vm->bind_notimplemented_constructor(type); - PK_REGISTER_READONLY_PROPERTY(PyBody, debug_color, "vec4"); - - PK_REGISTER_PROPERTY(PyBody, position, "vec2"); - PK_REGISTER_PROPERTY(PyBody, rotation, "float"); - PK_REGISTER_PROPERTY(PyBody, velocity, "vec2"); - PK_REGISTER_PROPERTY(PyBody, angular_velocity, "float"); - PK_REGISTER_PROPERTY(PyBody, damping, "float"); - PK_REGISTER_PROPERTY(PyBody, angular_damping, "float"); - PK_REGISTER_PROPERTY(PyBody, gravity_scale, "float"); - PK_REGISTER_PROPERTY(PyBody, type, "int"); - PK_REGISTER_READONLY_PROPERTY(PyBody, mass, "float"); - PK_REGISTER_READONLY_PROPERTY(PyBody, inertia, "float"); - - // fixture settings - PK_REGISTER_PROPERTY(PyBody, density, "float"); - PK_REGISTER_PROPERTY(PyBody, friction, "float"); - PK_REGISTER_PROPERTY(PyBody, restitution, "float"); - PK_REGISTER_PROPERTY(PyBody, restitution_threshold, "float"); - PK_REGISTER_PROPERTY(PyBody, is_trigger, "bool"); - - // methods - _bind_opaque(vm, type, "apply_force(self, force: vec2, point: vec2)", &Body::apply_force); - _bind_opaque(vm, type, "apply_force_to_center(self, force: vec2)", &Body::apply_force_to_center); - _bind_opaque(vm, type, "apply_torque(self, torque: float)", &Body::apply_torque); - _bind_opaque(vm, type, "apply_linear_impulse(self, impulse: vec2, point: vec2)", &Body::apply_linear_impulse); - _bind_opaque(vm, type, "apply_linear_impulse_to_center(self, impulse: vec2)", &Body::apply_linear_impulse_to_center); - _bind_opaque(vm, type, "apply_angular_impulse(self, impulse: float)", &Body::apply_angular_impulse); - - vm->bind__eq__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* lhs, PyObject* rhs){ - PyBody& self = _CAST(PyBody&, lhs); - if(is_non_tagged_type(rhs, PyBody::_type(vm))) return vm->NotImplemented; - PyBody& other = _CAST(PyBody&, rhs); - return VAR(self->body == other->body); - }); - - vm->bind__repr__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* obj){ - PyBody& self = _CAST(PyBody&, obj); - return VAR(fmt("body, ">")); - }); - - // destroy - _bind_opaque(vm, type, "destroy(self)", &Body::destroy); - - // contacts - vm->bind(type, "get_contacts(self) -> list", [](VM* vm, ArgsView args){ - PyBody& self = _CAST(PyBody&, args[0]); - b2ContactEdge* edge = self->body->GetContactList(); - List list; - while(edge){ - b2Fixture* fixtureB = edge->contact->GetFixtureB(); - b2Body* bodyB = fixtureB->GetBody(); - PyObject* objB = reinterpret_cast(bodyB->GetUserData().pointer)->obj; - list.push_back(objB); - edge = edge->next; - } - return VAR(std::move(list)); - }); - - // userdata - vm->bind(type, "get_node(self)", [](VM* vm, ArgsView args){ - PyBody& self = _CAST(PyBody&, args[0]); - return self->obj; - }); - - // shape - vm->bind(type, "set_box_shape(self, hx: float, hy: float)", [](VM* vm, ArgsView args){ - PyBody& self = _CAST(PyBody&, args[0]); - float hx = CAST(float, args[1]); - float hy = CAST(float, args[2]); - b2PolygonShape shape; - shape.SetAsBox(hx, hy); - self->_update_fixture(&shape); - return vm->None; - }); - - vm->bind(type, "set_circle_shape(self, radius: float)", [](VM* vm, ArgsView args){ - PyBody& self = _CAST(PyBody&, args[0]); - float radius = CAST(float, args[1]); - b2CircleShape shape; - shape.m_radius = radius; - self->_update_fixture(&shape); - return vm->None; - }); - - vm->bind(type, "set_polygon_shape(self, points: list[vec2])", [](VM* vm, ArgsView args){ - PyBody& self = _CAST(PyBody&, args[0]); - List& points = CAST(List&, args[1]); - if(points.size() > b2_maxPolygonVertices || points.size() < 3){ - vm->ValueError(fmt("invalid polygon vertices count: ", points.size())); - return vm->None; - } - std::vector vertices(points.size()); - for(int i = 0; i < points.size(); ++i){ - vertices[i] = CAST(b2Vec2, points[i]); - } - b2PolygonShape shape; - shape.Set(vertices.data(), vertices.size()); - self->_update_fixture(&shape); - return vm->None; - }); - - vm->bind(type, "set_chain_shape(self, points: list[vec2])", [](VM* vm, ArgsView args){ - PyBody& self = _CAST(PyBody&, args[0]); - List& points = CAST(List&, args[1]); - std::vector vertices(points.size()); - for(int i = 0; i < points.size(); ++i){ - vertices[i] = CAST(b2Vec2, points[i]); - } - b2ChainShape shape; - shape.CreateLoop(vertices.data(), vertices.size()); - self->_update_fixture(&shape); - return vm->None; - }); - - vm->bind(type, "get_shape_info(self) -> tuple", [](VM* vm, ArgsView args){ - PyBody& self = _CAST(PyBody&, args[0]); - b2Shape* shape = self->fixture->GetShape(); - switch(shape->GetType()){ - case b2Shape::e_polygon:{ - b2PolygonShape* poly = static_cast(shape); - Tuple points(poly->m_count + 1); - for(int i = 0; i < poly->m_count; ++i){ - points[i] = VAR(poly->m_vertices[i]); - } - points[poly->m_count] = points[0]; - return VAR(Tuple({ - VAR("polygon"), VAR(std::move(points)) - })); - } - case b2Shape::e_circle:{ - b2CircleShape* circle = static_cast(shape); - return VAR(Tuple({ - VAR("circle"), VAR(circle->m_radius) - })); - } - case b2Shape::e_chain:{ - b2ChainShape* chain = static_cast(shape); - Tuple points(chain->m_count); - for(int i = 0; i < chain->m_count; ++i){ - points[i] = VAR(chain->m_vertices[i]); - } - return VAR(Tuple({ - VAR("chain"), VAR(std::move(points)) - })); - } - default: - vm->ValueError("unsupported shape type"); - return vm->None; - } - }); - } - -// 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)); - // if(only_one) return 0; - return fraction; - } -}; - - - void PyWorld::_register(VM* vm, PyObject* mod, PyObject* type){ - vm->bind(type, "__new__(cls)", [](VM* vm, ArgsView args){ - b2World* w = new b2World(b2Vec2(0, 0)); - w->SetAllowSleeping(true); - w->SetAutoClearForces(true); - // the contact listener will leak memory after the world is destroyed - // but it's not a big deal since the world is only destroyed when the game exits - w->SetContactListener(new PyContactListener(vm)); - w->SetDebugDraw(new PyDebugDraw(vm)); - return VAR_T(PyWorld, w); - }); - - // gravity - vm->bind_property(type, "gravity", "vec2", [](VM* vm, ArgsView args){ - PyWorld& self = _CAST(PyWorld&, args[0]); - return VAR(self->GetGravity()); - }, [](VM* vm, ArgsView args){ - PyWorld& self = _CAST(PyWorld&, args[0]); - self->SetGravity(CAST(b2Vec2, args[1])); - return vm->None; - }); - - // body - vm->bind(type, "create_body(self, obj) -> Body", [](VM* vm, ArgsView args){ - PyWorld& self = _CAST(PyWorld&, args[0]); - return VAR_T(PyBody, new Body(self.ptr, args[1])); - }); - vm->bind(type, "get_bodies(self) -> list[Body]", [](VM* vm, ArgsView args){ - PyWorld& self = _CAST(PyWorld&, args[0]); - List list; - b2Body* p = self->GetBodyList(); - while(p != nullptr){ - Body* body = (Body*)p->GetUserData().pointer; - list.push_back(VAR_T(PyBody, body)); - p = p->GetNext(); - } - return VAR(std::move(list)); - }); - - // step - vm->bind(type, "step(self, dt: float, velocity_iterations: int, position_iterations: int)", - [](VM* vm, ArgsView args){ - PyWorld& self = _CAST(PyWorld&, args[0]); - float dt = CAST(float, args[1]); - int velocity_iterations = CAST(int, args[2]); - int position_iterations = CAST(int, args[3]); - - auto f = [](VM* vm, b2Body* p, StrName name){ - while(p != nullptr){ - Body* body = (Body*)p->GetUserData().pointer; - vm->call_method(body->obj, name); - p = p->GetNext(); - } - }; - - DEF_SNAME(on_box2d_pre_step); - DEF_SNAME(on_box2d_post_step); - f(vm, self->GetBodyList(), on_box2d_pre_step); - self->Step(dt, velocity_iterations, position_iterations); - f(vm, self->GetBodyList(), on_box2d_post_step); - return vm->None; - }); - - // raycast - vm->bind(type, "raycast(self, start: vec2, end: vec2) -> list[Body]", [](VM* vm, ArgsView args){ - auto _lock = vm->heap.gc_scope_lock(); - PyWorld& self = _CAST(PyWorld&, args[0]); - b2Vec2 start = CAST(b2Vec2, args[1]); - b2Vec2 end = CAST(b2Vec2, args[2]); - MyRayCastCallback callback(vm); - self->RayCast(&callback, start, end); - return VAR(std::move(callback.result)); - }); - } - -} - -} // namespace pkpy \ No newline at end of file diff --git a/docs/modules/box2d.md b/docs/modules/box2d.md index 78ce6eac..b4cfa410 100644 --- a/docs/modules/box2d.md +++ b/docs/modules/box2d.md @@ -68,15 +68,12 @@ class World: def ray_cast(self, start: vec2, end: vec2) -> list['Body']: """raycast from start to end""" - def box_cast(self, p0: vec2, p1: vec2) -> list['Body']: + def box_cast(self, lower: vec2, upper: vec2) -> list['Body']: """query bodies in the AABB region.""" def step(self, dt: float, velocity_iterations: int, position_iterations: int) -> None: """step the simulation, e.g. world.step(1/60, 8, 3)""" - def destroy(self): - """destroy this world.""" - # enum # { # e_shapeBit = 0x0001, ///< draw shapes @@ -93,13 +90,16 @@ class World: class Body: type: int # 0-static, 1-kinematic, 2-dynamic, by default 2 - mass: float - inertia: float gravity_scale: float fixed_rotation: bool enabled: bool bullet: bool # whether to use continuous collision detection + @property + def mass(self) -> float: ... + @property + def inertia(self) -> float: ... + position: vec2 rotation: float # in radians (counter-clockwise) velocity: vec2 # linear velocity @@ -125,8 +125,8 @@ class Body: def apply_force(self, force: vec2, point: vec2): ... def apply_force_to_center(self, force: vec2): ... def apply_torque(self, torque: float): ... - def apply_linear_impulse(self, impulse: vec2, point: vec2): ... - def apply_linear_impulse_to_center(self, impulse: vec2): ... + def apply_impulse(self, impulse: vec2, point: vec2): ... + def apply_impulse_to_center(self, impulse: vec2): ... def apply_angular_impulse(self, impulse: float): ... def get_node(self) -> _NodeLike: diff --git a/docs/quick-start/bind.md b/docs/quick-start/bind.md index f0ddc743..6108e5f6 100644 --- a/docs/quick-start/bind.md +++ b/docs/quick-start/bind.md @@ -67,7 +67,7 @@ a property is a python's `property` that attached to a type instance with a gett You can use `@property` to create python property or use `vm->property` to create native property. -You can also use `vm->bind_property()`, the new style property binding function. +Use `vm->bind_property()`, the new style property binding function. ```cpp struct Point { @@ -86,15 +86,16 @@ struct Point { }); // getter and setter of property `x` - type->attr().set("x", vm->property([](VM* vm, ArgsView args){ - Point& self = CAST(Point&, args[0]); - return VAR(self.x); - }, - [](VM* vm, ArgsView args){ - Point& self = CAST(Point&, args[0]); - self.x = CAST(int, args[1]); - return vm->None; - })); + vm->bind_property(type, "x: int", + [](VM* vm, ArgsView args){ + Point& self = CAST(Point&, args[0]); + return VAR(self.x); + }, + [](VM* vm, ArgsView args){ + Point& self = CAST(Point&, args[0]); + self.x = CAST(int, args[1]); + return vm->None; + }); } }; ``` diff --git a/include/pocketpy/bindings.h b/include/pocketpy/bindings.h index 1d6905d5..aa1ef5d9 100644 --- a/include/pocketpy/bindings.h +++ b/include/pocketpy/bindings.h @@ -65,33 +65,6 @@ struct NativeProxyMethodC final: NativeProxyFuncCBase { } }; -template -struct NativeProxyOpaqueMethodC final: NativeProxyFuncCBase { - static_assert(std::is_base_of_v, _OpaqueT>); - static constexpr int N = sizeof...(Params); - using _Fp = Ret(T::*)(Params...); - _Fp func; - NativeProxyOpaqueMethodC(_Fp func) : func(func) {} - - PyObject* operator()(VM* vm, ArgsView args) override { - PK_ASSERT(args.size() == N+1); - return call(vm, args, std::make_index_sequence()); - } - - template - PyObject* call(VM* vm, ArgsView args, std::index_sequence){ - OpaquePointer& _opa_self = py_cast<_OpaqueT&>(vm, args[0]); - T& self = *_opa_self.ptr; - if constexpr(std::is_void_v<__Ret>){ - (self.*func)(py_cast(vm, args[Is+1])...); - return vm->None; - }else{ - __Ret ret = (self.*func)(py_cast(vm, args[Is+1])...); - return VAR(std::move(ret)); - } - } -}; - inline PyObject* proxy_wrapper(VM* vm, ArgsView args){ NativeProxyFuncCBase* pf = lambda_get_userdata(args.begin()); return (*pf)(vm, args); @@ -108,56 +81,44 @@ void _bind(VM* vm, PyObject* obj, const char* sig, Ret(T::*func)(Params...)){ auto proxy = new NativeProxyMethodC(func); vm->bind(obj, sig, proxy_wrapper, proxy); } - -template -void _bind_opaque(VM* vm, PyObject* obj, const char* sig, Ret(T::*func)(Params...)){ - auto proxy = new NativeProxyOpaqueMethodC<_OpaqueT, Ret, T, Params...>(func); - vm->bind(obj, sig, proxy_wrapper, proxy); -} /*****************************************************************/ -#define PK_REGISTER_FIELD(T, NAME) \ - type->attr().set(#NAME, vm->property( \ - [](VM* vm, ArgsView args){ \ - T& self = _CAST(T&, args[0]); \ - return VAR(self->NAME); \ - }, \ - [](VM* vm, ArgsView args){ \ - T& self = _CAST(T&, args[0]); \ - self->NAME = CAST(decltype(self->NAME), args[1]); \ - return vm->None; \ - })); +#define PK_REGISTER_FIELD(T, NAME, REF, EXPR) \ + vm->bind_property(type, NAME, \ + [](VM* vm, ArgsView args){ \ + T& self = _CAST(T&, args[0]); \ + return VAR(self.REF().EXPR); \ + }, \ + [](VM* vm, ArgsView args){ \ + T& self = _CAST(T&, args[0]); \ + self.REF().EXPR = CAST(decltype(self.REF().EXPR), args[1]); \ + return vm->None; \ + }); -#define PK_REGISTER_READONLY_FIELD(T, NAME) \ - type->attr().set(#NAME, vm->property( \ - [](VM* vm, ArgsView args){ \ - T& self = _CAST(T&, args[0]); \ - return VAR(self->NAME); \ - })); +#define PK_REGISTER_READONLY_FIELD(T, NAME, REF, EXPR) \ + vm->bind_property(type, NAME, \ + [](VM* vm, ArgsView args){ \ + T& self = _CAST(T&, args[0]); \ + return VAR(self.REF().EXPR); \ + }); -#define PK_REGISTER_PROPERTY(T, NAME, __tp) \ - type->attr().set(#NAME, vm->property( \ - [](VM* vm, ArgsView args){ \ - T& self = _CAST(T&, args[0]); \ - return VAR(self->get_##NAME()); \ - }, \ - [](VM* vm, ArgsView args){ \ - T& self = _CAST(T&, args[0]); \ - using __NT = decltype(self->get_##NAME()); \ - self->set_##NAME(CAST(__NT, args[1])); \ - return vm->None; \ - }, __tp)); +#define PK_REGISTER_PROPERTY(T, NAME, REF, FGET, FSET) \ + vm->bind_property(type, NAME, \ + [](VM* vm, ArgsView args){ \ + T& self = _CAST(T&, args[0]); \ + return VAR(self.REF().FGET()); \ + }, \ + [](VM* vm, ArgsView args){ \ + T& self = _CAST(T&, args[0]); \ + using __NT = decltype(self.REF().FGET()); \ + self.REF().FSET(CAST(__NT, args[1])); \ + return vm->None; \ + }); -#define PK_REGISTER_READONLY_PROPERTY(T, NAME, __tp) \ - type->attr().set(#NAME, vm->property( \ - [](VM* vm, ArgsView args){ \ - T& self = _CAST(T&, args[0]); \ - return VAR(self->get_##NAME()); \ - }, nullptr, __tp)); - -#define PK_REGISTER_CONSTRUCTOR(T, T0) \ - vm->bind_constructor<2>(type, [](VM* vm, ArgsView args){ \ - void* p = CAST(void*, args[0]); \ - return VAR_T(T, (T0*)p); \ - }); +#define PK_REGISTER_READONLY_PROPERTY(T, NAME, REF, FGET) \ + vm->bind_property(type, NAME, \ + [](VM* vm, ArgsView args){ \ + T& self = _CAST(T&, args[0]); \ + return VAR(self.REF().FGET()); \ + }); } // namespace pkpy \ No newline at end of file diff --git a/include/pocketpy/cffi.h b/include/pocketpy/cffi.h index da76a9d8..f28583ec 100644 --- a/include/pocketpy/cffi.h +++ b/include/pocketpy/cffi.h @@ -27,7 +27,7 @@ namespace pkpy { return type; \ } -#define VAR_T(T, ...) vm->heap.gcnew(T::_type(vm), T(__VA_ARGS__)) +#define VAR_T(T, ...) vm->heap.gcnew(T::_type(vm), __VA_ARGS__) int c99_sizeof(VM*, const Str&); diff --git a/include/pocketpy/codeobject.h b/include/pocketpy/codeobject.h index 9d1dd11c..5422a58b 100644 --- a/include/pocketpy/codeobject.h +++ b/include/pocketpy/codeobject.h @@ -129,7 +129,7 @@ struct FuncDecl { }; struct UserData{ - char data[16]; + char data[15]; bool empty; UserData(): empty(true) {} @@ -186,7 +186,8 @@ struct Function{ template<> struct Py_ final: PyObject { Function _value; - Py_(Type type, Function val): PyObject(type), _value(val) { + template + Py_(Type type, Args&&... args): PyObject(type), _value(std::forward(args)...) { enable_instance_dict(); } void _obj_gc_mark() override { @@ -199,7 +200,8 @@ struct Py_ final: PyObject { template<> struct Py_ final: PyObject { NativeFunc _value; - Py_(Type type, NativeFunc val): PyObject(type), _value(val) { + template + Py_(Type type, Args&&... args): PyObject(type), _value(std::forward(args)...) { enable_instance_dict(); } void _obj_gc_mark() override { diff --git a/include/pocketpy/common.h b/include/pocketpy/common.h index 63be2e3e..374eb169 100644 --- a/include/pocketpy/common.h +++ b/include/pocketpy/common.h @@ -21,7 +21,7 @@ #include #include -#define PK_VERSION "1.1.2" +#define PK_VERSION "1.1.3" #include "config.h" #include "export.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/gc.h b/include/pocketpy/gc.h index bb50662e..ad79f8ca 100644 --- a/include/pocketpy/gc.h +++ b/include/pocketpy/gc.h @@ -37,29 +37,21 @@ struct ManagedHeap{ } /********************/ - template - PyObject* gcnew(Type type, T&& val){ + template + PyObject* gcnew(Type type, Args&&... args){ using __T = Py_>; -#if _WIN32 // https://github.com/blueloveTH/pocketpy/issues/94#issuecomment-1594784476 - PyObject* obj = new(pool64.alloc<__T>()) Py_>(type, std::forward(val)); -#else - PyObject* obj = new(pool64.alloc<__T>()) __T(type, std::forward(val)); -#endif + PyObject* obj = new(pool64.alloc<__T>()) Py_>(type, std::forward(args)...); gen.push_back(obj); gc_counter++; return obj; } - template - PyObject* _new(Type type, T&& val){ + template + PyObject* _new(Type type, Args&&... args){ using __T = Py_>; -#if _WIN32 // https://github.com/blueloveTH/pocketpy/issues/94#issuecomment-1594784476 - PyObject* obj = new(pool64.alloc<__T>()) Py_>(type, std::forward(val)); -#else - PyObject* obj = new(pool64.alloc<__T>()) __T(type, std::forward(val)); -#endif + PyObject* obj = new(pool64.alloc<__T>()) Py_>(type, std::forward(args)...); obj->gc.enabled = false; _no_gc.push_back(obj); return obj; diff --git a/include/pocketpy/obj.h b/include/pocketpy/obj.h index 12cdf9a7..b59871f5 100644 --- a/include/pocketpy/obj.h +++ b/include/pocketpy/obj.h @@ -31,8 +31,8 @@ struct BoundMethod { struct Property{ PyObject* getter; PyObject* setter; - const char* type_hint; - Property(PyObject* getter, PyObject* setter, const char* type_hint) : getter(getter), setter(setter), type_hint(type_hint) {} + Str type_hint; + Property(PyObject* getter, PyObject* setter, Str type_hint) : getter(getter), setter(setter), type_hint(type_hint) {} }; struct Range { @@ -134,8 +134,9 @@ struct Py_ final: PyObject { _value._gc_mark(); } } - Py_(Type type, const T& value) : PyObject(type), _value(value) {} - Py_(Type type, T&& value) : PyObject(type), _value(std::move(value)) {} + + template + Py_(Type type, Args&&... args) : PyObject(type), _value(std::forward(args)...) { } }; struct MappingProxy{ @@ -320,7 +321,8 @@ struct Py_ final: PyObject { template<> struct Py_ final: PyObject { Super _value; - Py_(Type type, Super val): PyObject(type), _value(val) {} + template + Py_(Type type, Args&&... args): PyObject(type), _value(std::forward(args)...) {} void _obj_gc_mark() override { PK_OBJ_MARK(_value.first); } @@ -328,8 +330,7 @@ struct Py_ final: PyObject { template<> struct Py_ final: PyObject { - Py_(Type type, DummyInstance val): PyObject(type) { - PK_UNUSED(val); + Py_(Type type): PyObject(type) { enable_instance_dict(); } void _obj_gc_mark() override {} @@ -346,8 +347,7 @@ struct Py_ final: PyObject { template<> struct Py_ final: PyObject { - Py_(Type type, DummyModule val): PyObject(type) { - PK_UNUSED(val); + Py_(Type type): PyObject(type) { enable_instance_dict(kTypeAttrLoadFactor); } void _obj_gc_mark() override {} diff --git a/include/pocketpy/str.h b/include/pocketpy/str.h index 4d0a6c82..a13c97c3 100644 --- a/include/pocketpy/str.h +++ b/include/pocketpy/str.h @@ -3,6 +3,7 @@ #include "common.h" #include "memory.h" #include "vector.h" +#include namespace pkpy { @@ -22,6 +23,7 @@ struct Str{ Str(int size, bool is_ascii); Str(const std::string& s); Str(std::string_view s); + Str(std::nullptr_t) { FATAL_ERROR(); } Str(const char* s); Str(const char* s, int len); Str(const Str& other); diff --git a/include/pocketpy/vm.h b/include/pocketpy/vm.h index adde17e3..3fd67e05 100644 --- a/include/pocketpy/vm.h +++ b/include/pocketpy/vm.h @@ -42,8 +42,8 @@ namespace pkpy{ PK_UNUSED(vm); \ return PK_OBJ_GET(ctype, obj); \ } \ - inline PyObject* py_var(VM* vm, const ctype& value) { return vm->heap.gcnew(vm->ptype, value);} \ - inline PyObject* py_var(VM* vm, ctype&& value) { return vm->heap.gcnew(vm->ptype, std::move(value));} + inline PyObject* py_var(VM* vm, const ctype& value) { return vm->heap.gcnew(vm->ptype, value);} \ + inline PyObject* py_var(VM* vm, ctype&& value) { return vm->heap.gcnew(vm->ptype, std::move(value));} typedef PyObject* (*BinaryFuncC)(VM*, PyObject*, PyObject*); @@ -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; @@ -201,7 +203,6 @@ public: return call_method(self, callable, args...); } - PyObject* property(NativeFuncC fget, NativeFuncC fset=nullptr, const char* type_hint=nullptr); PyObject* new_type_object(PyObject* mod, StrName name, Type base, bool subclass_enabled=true); Type _new_type_object(StrName name, Type base=0); PyObject* _find_type_object(const Str& type); @@ -460,7 +461,7 @@ public: // new style binding api PyObject* bind(PyObject*, const char*, const char*, NativeFuncC, UserData userdata={}); PyObject* bind(PyObject*, const char*, NativeFuncC, UserData userdata={}); - PyObject* bind_property(PyObject*, StrName name, const char* type_hint, NativeFuncC fget, NativeFuncC fset=nullptr); + PyObject* bind_property(PyObject*, Str, NativeFuncC fget, NativeFuncC fset=nullptr); }; DEF_NATIVE_2(Str, tp_str) diff --git a/src/io.cpp b/src/io.cpp index e02f8d43..9b2a59d7 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -99,7 +99,7 @@ void add_module_io(VM* vm){ void add_module_os(VM* vm){ #if PK_ENABLE_OS PyObject* mod = vm->new_module("os"); - PyObject* path_obj = vm->heap.gcnew(vm->tp_object, {}); + PyObject* path_obj = vm->heap.gcnew(vm->tp_object); mod->attr().set("path", path_obj); // Working directory is shared by all VMs!! diff --git a/src/linalg.cpp b/src/linalg.cpp index 272c222e..911ba07d 100644 --- a/src/linalg.cpp +++ b/src/linalg.cpp @@ -36,14 +36,15 @@ namespace pkpy{ }); #define BIND_VEC_FIELD(D, name) \ - type->attr().set(#name, vm->property([](VM* vm, ArgsView args){ \ + vm->bind_property(type, #name, \ + [](VM* vm, ArgsView args){ \ PyVec##D& self = _CAST(PyVec##D&, args[0]); \ return VAR(self.name); \ }, [](VM* vm, ArgsView args){ \ PyVec##D& self = _CAST(PyVec##D&, args[0]); \ self.name = CAST(f64, args[1]); \ return vm->None; \ - })); + }); void PyVec2::_register(VM* vm, PyObject* mod, PyObject* type){ @@ -284,14 +285,15 @@ namespace pkpy{ }); #define PROPERTY_FIELD(field) \ - type->attr().set(#field, vm->property([](VM* vm, ArgsView args){ \ + vm->bind_property(type, #field ": float", \ + [](VM* vm, ArgsView args){ \ PyMat3x3& self = _CAST(PyMat3x3&, args[0]); \ return VAR(self.field); \ }, [](VM* vm, ArgsView args){ \ PyMat3x3& self = _CAST(PyMat3x3&, args[0]); \ self.field = CAST(f64, args[1]); \ return vm->None; \ - })); + }); PROPERTY_FIELD(_11) PROPERTY_FIELD(_12) diff --git a/src/pocketpy.cpp b/src/pocketpy.cpp index b198118f..9f2be850 100644 --- a/src/pocketpy.cpp +++ b/src/pocketpy.cpp @@ -113,7 +113,7 @@ void init_builtins(VM* _vm) { vm->TypeError("super(): " + _0.escape() + " is not an instance of " + _1.escape()); } Type base = vm->_all_types[type].base; - return vm->heap.gcnew(vm->tp_super, Super(args[1], base)); + return vm->heap.gcnew(vm->tp_super, args[1], base); }); _vm->bind_builtin_func<2>("isinstance", [](VM* vm, ArgsView args) { @@ -149,36 +149,6 @@ void init_builtins(VM* _vm) { } }); - _vm->bind_builtin_func<3>("pow", [](VM* vm, ArgsView args) { - i64 lhs = CAST(i64, args[0]); // assume lhs>=0 - i64 rhs = CAST(i64, args[1]); // assume rhs>=0 - i64 mod = CAST(i64, args[2]); // assume mod>0, mod*mod should not overflow - - if(rhs <= 0){ - vm->ValueError("pow(): rhs should be positive"); - } - - PK_LOCAL_STATIC const auto _mul = [](i64 a, i64 b, i64 c){ - if(c < 16384) return (a%c) * (b%c) % c; - i64 res = 0; - while(b > 0){ - if(b & 1) res = (res + a) % c; - a = (a << 1) % c; - b >>= 1; - } - return res; - }; - - i64 res = 1; - lhs %= mod; - while(rhs){ - if(rhs & 1) res = _mul(res, lhs, mod); - lhs = _mul(lhs, lhs, mod); - rhs >>= 1; - } - return VAR(res); - }); - _vm->bind_builtin_func<1>("id", [](VM* vm, ArgsView args) { PyObject* obj = args[0]; if(is_tagged(obj)) return vm->None; @@ -340,7 +310,7 @@ void init_builtins(VM* _vm) { _vm->cached_object__new__ = _vm->bind_constructor<1>("object", [](VM* vm, ArgsView args) { vm->check_non_tagged_type(args[0], vm->tp_type); Type t = PK_OBJ_GET(Type, args[0]); - return vm->heap.gcnew(t, {}); + return vm->heap.gcnew(t); }); _vm->bind_constructor<2>("type", PK_LAMBDA(vm->_t(args[1]))); @@ -1018,6 +988,11 @@ void init_builtins(VM* _vm) { return (i64)_CAST(MappingProxy&, obj).attr().size(); }); + _vm->bind__hash__(_vm->tp_mappingproxy, [](VM* vm, PyObject* obj) { + vm->TypeError("unhashable type: 'mappingproxy'"); + return (i64)0; + }); + _vm->bind__getitem__(_vm->tp_mappingproxy, [](VM* vm, PyObject* obj, PyObject* index) { MappingProxy& self = _CAST(MappingProxy&, obj); StrName key = CAST(Str&, index); @@ -1073,6 +1048,11 @@ void init_builtins(VM* _vm) { _vm->bind__len__(_vm->tp_dict, [](VM* vm, PyObject* obj) { return (i64)_CAST(Dict&, obj).size(); }); + + _vm->bind__hash__(_vm->tp_dict, [](VM* vm, PyObject* obj) { + vm->TypeError("unhashable type: 'dict'"); + return (i64)0; + }); _vm->bind__getitem__(_vm->tp_dict, [](VM* vm, PyObject* obj, PyObject* index) { Dict& self = _CAST(Dict&, obj); @@ -1216,41 +1196,41 @@ void init_builtins(VM* _vm) { /************ property ************/ _vm->bind_constructor<-1>("property", [](VM* vm, ArgsView args) { if(args.size() == 1+1){ - return VAR(Property(args[1], vm->None, nullptr)); + return VAR(Property(args[1], vm->None, "")); }else if(args.size() == 1+2){ - return VAR(Property(args[1], args[2], nullptr)); + return VAR(Property(args[1], args[2], "")); } vm->TypeError("property() takes at most 2 arguments"); return vm->None; }); - _vm->bind_property(_vm->_t(_vm->tp_property), "type_hint", "str", [](VM* vm, ArgsView args){ + _vm->bind_property(_vm->_t(_vm->tp_property), "type_hint: str", [](VM* vm, ArgsView args){ Property& self = _CAST(Property&, args[0]); - if(self.type_hint == nullptr) return vm->None; return VAR(self.type_hint); }); - _vm->_t(_vm->tp_function)->attr().set("__doc__", _vm->property([](VM* vm, ArgsView args) { + + _vm->bind_property(_vm->_t(_vm->tp_function), "__doc__", [](VM* vm, ArgsView args) { Function& func = _CAST(Function&, args[0]); return VAR(func.decl->docstring); - })); + }); - _vm->_t(_vm->tp_native_func)->attr().set("__doc__", _vm->property([](VM* vm, ArgsView args) { + _vm->bind_property(_vm->_t(_vm->tp_native_func), "__doc__", [](VM* vm, ArgsView args) { NativeFunc& func = _CAST(NativeFunc&, args[0]); if(func.decl != nullptr) return VAR(func.decl->docstring); return VAR(""); - })); + }); - _vm->_t(_vm->tp_function)->attr().set("__signature__", _vm->property([](VM* vm, ArgsView args) { + _vm->bind_property(_vm->_t(_vm->tp_function), "__signature__", [](VM* vm, ArgsView args) { Function& func = _CAST(Function&, args[0]); return VAR(func.decl->signature); - })); + }); - _vm->_t(_vm->tp_native_func)->attr().set("__signature__", _vm->property([](VM* vm, ArgsView args) { + _vm->bind_property(_vm->_t(_vm->tp_native_func), "__signature__", [](VM* vm, ArgsView args) { NativeFunc& func = _CAST(NativeFunc&, args[0]); if(func.decl != nullptr) return VAR(func.decl->signature); return VAR("unknown(*args, **kwargs)"); - })); + }); RangeIter::register_class(_vm, _vm->builtins); ArrayIter::register_class(_vm, _vm->builtins); @@ -1364,8 +1344,8 @@ void add_module_sys(VM* vm){ vm->setattr(mod, "version", VAR(PK_VERSION)); vm->setattr(mod, "platform", VAR(PK_SYS_PLATFORM)); - PyObject* stdout_ = vm->heap.gcnew(vm->tp_object, {}); - PyObject* stderr_ = vm->heap.gcnew(vm->tp_object, {}); + PyObject* stdout_ = vm->heap.gcnew(vm->tp_object); + PyObject* stderr_ = vm->heap.gcnew(vm->tp_object); vm->setattr(mod, "stdout", stdout_); vm->setattr(mod, "stderr", stderr_); @@ -1520,41 +1500,41 @@ void add_module_gc(VM* vm){ void VM::post_init(){ init_builtins(this); - _t(tp_object)->attr().set("__class__", property(PK_LAMBDA(vm->_t(args[0])))); - _t(tp_type)->attr().set("__base__", property([](VM* vm, ArgsView args){ + bind_property(_t(tp_object), "__class__", PK_LAMBDA(VAR(vm->_t(args[0])))); + bind_property(_t(tp_type), "__base__", [](VM* vm, ArgsView args){ const PyTypeInfo& info = vm->_all_types[PK_OBJ_GET(Type, args[0])]; return info.base.index == -1 ? vm->None : vm->_all_types[info.base].obj; - })); - _t(tp_type)->attr().set("__name__", property([](VM* vm, ArgsView args){ + }); + bind_property(_t(tp_type), "__name__", [](VM* vm, ArgsView args){ const PyTypeInfo& info = vm->_all_types[PK_OBJ_GET(Type, args[0])]; return VAR(info.name); - })); - - _t(tp_bound_method)->attr().set("__self__", property([](VM* vm, ArgsView args){ + }); + bind_property(_t(tp_bound_method), "__self__", [](VM* vm, ArgsView args){ return CAST(BoundMethod&, args[0]).self; - })); - _t(tp_bound_method)->attr().set("__func__", property([](VM* vm, ArgsView args){ + }); + bind_property(_t(tp_bound_method), "__func__", [](VM* vm, ArgsView args){ return CAST(BoundMethod&, args[0]).func; - })); + }); bind__eq__(tp_bound_method, [](VM* vm, PyObject* lhs, PyObject* rhs){ if(!is_non_tagged_type(rhs, vm->tp_bound_method)) return vm->NotImplemented; return VAR(_CAST(BoundMethod&, lhs) == _CAST(BoundMethod&, rhs)); }); - _t(tp_slice)->attr().set("start", property([](VM* vm, ArgsView args){ - return CAST(Slice&, args[0]).start; - })); - _t(tp_slice)->attr().set("stop", property([](VM* vm, ArgsView args){ - return CAST(Slice&, args[0]).stop; - })); - _t(tp_slice)->attr().set("step", property([](VM* vm, ArgsView args){ - return CAST(Slice&, args[0]).step; - })); - _t(tp_object)->attr().set("__dict__", property([](VM* vm, ArgsView args){ + bind_property(_t(tp_slice), "start", [](VM* vm, ArgsView args){ + return CAST(Slice&, args[0]).start; + }); + bind_property(_t(tp_slice), "stop", [](VM* vm, ArgsView args){ + return CAST(Slice&, args[0]).stop; + }); + bind_property(_t(tp_slice), "step", [](VM* vm, ArgsView args){ + return CAST(Slice&, args[0]).step; + }); + + bind_property(_t(tp_object), "__dict__", [](VM* vm, ArgsView args){ if(is_tagged(args[0]) || !args[0]->is_attr_valid()) return vm->None; return VAR(MappingProxy(args[0])); - })); + }); #if !PK_DEBUG_NO_BUILTINS add_module_sys(this); diff --git a/src/vm.cpp b/src/vm.cpp index 8381dc1a..062d4cce 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -113,13 +113,6 @@ namespace pkpy{ return nullptr; } - PyObject* VM::property(NativeFuncC fget, NativeFuncC fset, const char* type_hint){ - PyObject* _0 = heap.gcnew(tp_native_func, NativeFunc(fget, 1, false)); - PyObject* _1 = vm->None; - if(fset != nullptr) _1 = heap.gcnew(tp_native_func, NativeFunc(fset, 2, false)); - return VAR(Property(_0, _1, type_hint)); - } - PyObject* VM::new_type_object(PyObject* mod, StrName name, Type base, bool subclass_enabled){ PyObject* obj = heap._new(tp_type, _all_types.size()); const PyTypeInfo& base_info = _all_types[base]; @@ -434,7 +427,7 @@ PyObject* VM::format(Str spec, PyObject* obj){ } PyObject* VM::new_module(StrName name) { - PyObject* obj = heap._new(tp_module, DummyModule()); + PyObject* obj = heap._new(tp_module); obj->attr().set("__name__", VAR(name.sv())); // we do not allow override in order to avoid memory leak // it is because Module objects are not garbage collected @@ -592,12 +585,12 @@ void VM::init_builtin_types(){ tp_property = _new_type_object("property"); tp_star_wrapper = _new_type_object("_star_wrapper"); - this->None = heap._new(_new_type_object("NoneType"), {}); - this->NotImplemented = heap._new(_new_type_object("NotImplementedType"), {}); - this->Ellipsis = heap._new(_new_type_object("ellipsis"), {}); - this->True = heap._new(tp_bool, {}); - this->False = heap._new(tp_bool, {}); - this->StopIteration = heap._new(_new_type_object("StopIterationType"), {}); + this->None = heap._new(_new_type_object("NoneType")); + this->NotImplemented = heap._new(_new_type_object("NotImplementedType")); + this->Ellipsis = heap._new(_new_type_object("ellipsis")); + this->True = heap._new(tp_bool); + this->False = heap._new(tp_bool); + this->StopIteration = heap._new(_new_type_object("StopIterationType")); this->builtins = new_module("builtins"); @@ -804,7 +797,7 @@ PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){ if(new_f == cached_object__new__) { // fast path for object.__new__ Type t = PK_OBJ_GET(Type, callable); - obj= vm->heap.gcnew(t, {}); + obj= vm->heap.gcnew(t); }else{ PUSH(new_f); PUSH(PY_NULL); @@ -979,8 +972,17 @@ PyObject* VM::bind(PyObject* obj, const char* sig, const char* docstring, Native return f_obj; } -PyObject* VM::bind_property(PyObject* obj, StrName name, const char* type_hint, NativeFuncC fget, NativeFuncC fset){ - PyObject* prop = property(fget, fset, type_hint); +PyObject* VM::bind_property(PyObject* obj, Str name, NativeFuncC fget, NativeFuncC fset){ + PyObject* _0 = heap.gcnew(tp_native_func, fget, 1, false); + PyObject* _1 = vm->None; + if(fset != nullptr) _1 = heap.gcnew(tp_native_func, fset, 2, false); + Str type_hint; + int pos = name.index(":"); + if(pos > 0){ + type_hint = name.substr(pos + 1).strip(); + name = name.substr(0, pos).strip(); + } + PyObject* prop = VAR(Property(_0, _1, type_hint)); obj->attr().set(name, prop); return prop; } diff --git a/tests/99_bugs.py b/tests/99_bugs.py index f69d967d..9e259a8b 100644 --- a/tests/99_bugs.py +++ b/tests/99_bugs.py @@ -31,7 +31,7 @@ if inq is not 1: if inq is not 0: assert False -assert pow(2,5000,2**59-1) == 17592186044416 +# assert pow(2,5000,2**59-1) == 17592186044416 def g(x): return x