3 Commits

Author SHA1 Message Date
John Alanbrook
ad4f3d3f58 correct deletion
Some checks failed
Build and Deploy / build-linux (push) Failing after 39s
Build and Deploy / build-windows (CLANG64) (push) Failing after 8m19s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped
2025-02-26 10:07:32 -06:00
John Alanbrook
b23dca6934 attempt at handling automatic removal on destroy
Some checks failed
Build and Deploy / build-linux (push) Failing after 1m17s
Build and Deploy / build-windows (CLANG64) (push) Successful in 9m50s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped
2025-02-25 15:49:32 -06:00
John Alanbrook
8fba19c820 WIP: Chipmunk integration
Some checks failed
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-02-24 18:24:45 -06:00
17 changed files with 2177 additions and 484 deletions

View File

@@ -153,6 +153,7 @@ jobs:
deploy-itch:
needs: [package-dist]
if: ${{ false }}
runs-on: ubuntu-latest
steps:
- name: Check Out Code

View File

@@ -1,39 +0,0 @@
# dmon
### watch() <sub>function</sub>
Start watching the root directory, recursively.
This function begins monitoring the specified directory and its subdirectories recursively for events such as file creation, deletion, modification, or movement. Events are queued and can be retrieved by calling poll.
:throws: An error if dmon is already watching.
**Returns**: None
### unwatch() <sub>function</sub>
Stop watching the currently monitored directory.
This function halts filesystem monitoring for the directory previously set by watch. It clears the watch state, allowing a new watch to be started.
:throws: An error if no directory is currently being watched.
**Returns**: None
### poll(callback) <sub>function</sub>
Retrieve and process queued filesystem events.
This function dequeues all pending filesystem events and invokes the provided callback for each one. The callback receives an event object with properties: 'action' (string: "create", "delete", "modify", or "move"), 'root' (string: watched directory), 'file' (string: affected file path), and 'old' (string: previous file path for move events, empty if not applicable).
**callback**: A function to call for each event, receiving an event object as its argument.
**Returns**: None

View File

@@ -1,45 +0,0 @@
# enet
### initialize() <sub>function</sub>
Initialize the ENet library. Must be called before using any ENet functionality.
Throws an error if initialization fails.
**Returns**: None
### deinitialize() <sub>function</sub>
Deinitialize the ENet library, cleaning up all resources. Call this when you no longer
need any ENet functionality.
**Returns**: None
### create_host(address) <sub>function</sub>
Create an ENet host for either a client-like unbound host or a server bound to a specific
address and port:
- If no argument is provided, creates an unbound "client-like" host with default settings
(maximum 32 peers, 2 channels, unlimited bandwidth).
- If you pass an "ip:port" string (e.g. "127.0.0.1:7777"), it creates a server bound to
that address. The server supports up to 32 peers, 2 channels, and unlimited bandwidth.
Throws an error if host creation fails for any reason.
omit to create an unbound client-like host.
**address**: (optional) A string in 'ip:port' format to bind the host (server), or
**Returns**: An ENetHost object.

View File

@@ -1,30 +0,0 @@
# nota
### encode(value) <sub>function</sub>
Convert a JavaScript value into a NOTA-encoded ArrayBuffer.
This function serializes JavaScript values (such as numbers, strings, booleans, arrays, objects, or ArrayBuffers) into the NOTA binary format. The resulting ArrayBuffer can be stored or transmitted and later decoded back into a JavaScript value.
:throws: An error if no argument is provided.
**value**: The JavaScript value to encode (e.g., number, string, boolean, array, object, or ArrayBuffer).
**Returns**: An ArrayBuffer containing the NOTA-encoded data.
### decode(buffer) <sub>function</sub>
Decode a NOTA-encoded ArrayBuffer into a JavaScript value.
This function deserializes a NOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns undefined.
**buffer**: An ArrayBuffer containing NOTA-encoded data to decode.
**Returns**: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or undefined if no argument is provided.

View File

@@ -1,66 +0,0 @@
# enet_host
### service(callback, timeout) <sub>function</sub>
Poll for and process any available network events (connect, receive, disconnect, or none)
from this host, calling the provided callback for each event. This function loops until
no more events are available in the current timeframe.
Event object properties:
- type: String, one of "connect", "receive", "disconnect", or "none".
- peer: (present if type = "connect") The ENetPeer object for the new connection.
- channelID: (present if type = "receive") The channel on which the data was received.
- data: (present if type = "receive") The received data as a *plain JavaScript object*.
If the JSON parse fails or the data isn't an object, a JavaScript error is thrown.
object as its single argument.
**callback**: A function called once for each available event, receiving an event
**timeout**: (optional) Timeout in milliseconds. Defaults to 0 (non-blocking).
**Returns**: None
### connect(host, port) <sub>function</sub>
Initiate a connection from this host to a remote server. Throws an error if the
connection cannot be started.
**host**: The hostname or IP address of the remote server (e.g. "example.com" or "127.0.0.1").
**port**: The port number to connect to.
**Returns**: An ENetPeer object representing the connection.
### flush() <sub>function</sub>
Flush all pending outgoing packets for this host immediately.
**Returns**: None
### broadcast(data) <sub>function</sub>
Broadcast a JavaScript object to all connected peers on channel 0. The object is
serialized to JSON, and the packet is sent reliably. Throws an error if serialization fails.
**data**: A JavaScript object to broadcast to all peers.
**Returns**: None

View File

@@ -1,102 +0,0 @@
# enet_peer
### send(data) <sub>function</sub>
Send a JavaScript object to this peer on channel 0. The object is serialized to JSON and
sent reliably. Throws an error if serialization fails.
**data**: A JavaScript object to send.
**Returns**: None
### disconnect() <sub>function</sub>
Request a graceful disconnection from this peer. The connection will close after
pending data is sent.
**Returns**: None
### disconnect_now() <sub>function</sub>
Immediately terminate the connection to this peer, discarding any pending data.
**Returns**: None
### disconnect_later() <sub>function</sub>
Request a disconnection from this peer after all queued packets are sent.
**Returns**: None
### reset() <sub>function</sub>
Reset this peer's connection, immediately dropping it and clearing its internal state.
**Returns**: None
### ping() <sub>function</sub>
Send a ping request to this peer to measure latency.
**Returns**: None
### throttle_configure(interval, acceleration, deceleration) <sub>function</sub>
Configure the throttling behavior for this peer, controlling how ENet adjusts its sending
rate based on packet loss or congestion.
**interval**: The interval (ms) between throttle adjustments.
**acceleration**: The factor to increase sending speed when conditions improve.
**deceleration**: The factor to decrease sending speed when conditions worsen.
**Returns**: None
### timeout(timeout_limit, timeout_min, timeout_max) <sub>function</sub>
Set timeout parameters for this peer, determining how long ENet waits before considering
the connection lost.
**timeout_limit**: The total time (ms) before the peer is disconnected.
**timeout_min**: The minimum timeout (ms) used for each timeout attempt.
**timeout_max**: The maximum timeout (ms) used for each timeout attempt.
**Returns**: None

View File

@@ -1,5 +1,5 @@
project('prosperon', ['c', 'cpp'],
version: '0.9.3',
version: '0.9.2',
meson_version: '>=1.4',
default_options : [ 'cpp_std=c++11'])
@@ -72,6 +72,18 @@ sdl3_opts.add_cmake_defines({
'CMAKE_BUILD_TYPE': 'Release'
})
chipmunk_opts = cmake.subproject_options()
chipmunk_opts.add_cmake_defines({
'BUILD_DEMOS': 'OFF',
'BUILD_SHARED': 'OFF',
'BUILD_STATIC': 'ON',
'CMAKE_BUILD_TYPE': 'Release',
# uncomment to use floats instead of doubles
# 'CP_USE_DOUBLES': 'OFF',
})
chipmunk_proj = cmake.subproject('chipmunk', options: chipmunk_opts)
deps += chipmunk_proj.dependency('chipmunk_static')
cc = meson.get_compiler('c')
if host_machine.system() == 'darwin'
@@ -131,14 +143,11 @@ deps += dependency('physfs', static:true)
#deps += cc.find_library('opencv')
deps += dependency('threads')
deps += dependency('chipmunk', static:true)
deps += dependency('enet', static:true)
deps += dependency('soloud', static:true)
#deps += dependency('qjs-chipmunk', static:false)
sources = []
src += ['anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','prosperon.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_dmon.c', 'qjs_nota.c', 'qjs_enet.c', 'qjs_soloud.c']
src += ['anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','prosperon.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_dmon.c', 'qjs_nota.c', 'qjs_enet.c', 'qjs_soloud.c', 'qjs_chipmunk.c']
imsrc = ['GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp','imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp','implot_items.cpp','implot.cpp', 'imgui_impl_sdlrenderer3.cpp', 'imgui_impl_sdl3.cpp', 'imgui_impl_sdlgpu3.cpp']
@@ -231,7 +240,8 @@ tests = [
'spawn_actor',
'empty',
'nota',
'enet'
'enet',
'chipmunk2d'
]
foreach file : tests

View File

@@ -1,4 +1,5 @@
site_name: Prosperon Documentation
edit_uri: edit/master/doc/docs
plugins:
- search

View File

@@ -0,0 +1,564 @@
var chipmunk = this;
return chipmunk
//------------------------------------------------
// Top-level Chipmunk2D functions
//------------------------------------------------
chipmunk.make_space[prosperon.DOC] = `
Create and return a new Chipmunk2D cpSpace instance. By default, this space has
no bodies or shapes. You can add bodies via space.add_body() and step the simulation
with space.step().
:return: A newly created Space object.
`;
//------------------------------------------------
// Space methods and properties
//------------------------------------------------
var cpSpace = prosperon.c_types.cpSpace;
cpSpace.step[prosperon.DOC] = `
Advance the physics simulation by the specified timestep.
:param dt: A number representing the time step to simulate (e.g., 1/60 for 60 FPS).
:return: None
`;
cpSpace.add_body[prosperon.DOC] = `
Create and add a new dynamic body to this space. Returns the newly created cpBody
object, which you can then configure (mass, moment, etc.) or attach shapes to.
:return: A cpBody object representing the new body.
`;
cpSpace.eachBody[prosperon.DOC] = `
Iterate over every cpBody in this space, calling the provided callback for each one.
Useful for enumerating or modifying all bodies.
:param callback: A function(body) called for each cpBody in this space.
:return: None
`;
cpSpace.eachShape[prosperon.DOC] = `
Iterate over every cpShape in this space, calling the provided callback for each one.
:param callback: A function(shape) called for each cpShape in this space.
:return: None
`;
cpSpace.eachConstraint[prosperon.DOC] = `
Iterate over every cpConstraint in this space, calling the provided callback for each.
:param callback: A function(constraint) called for each cpConstraint in this space.
:return: None
`;
cpSpace.gravity[prosperon.DOC] = `
The gravity vector for this space, typically something like { x: 0, y: -9.8 } for
Earth-like gravity in 2D. You can read or write this property to change gravity.
:return: An object { x, y } for read. Assign a similar object to set.
`;
cpSpace.iterations[prosperon.DOC] = `
The number of solver iterations that Chipmunk2D performs each time step. Higher values
improve stability at a performance cost. You can read or write this property.
:return: Number of iterations.
`;
cpSpace.idle_speed[prosperon.DOC] = `
Bodies with a speed (velocity magnitude) lower than idle_speed for a certain amount of
time can enter sleep mode, which saves CPU. Read or set this threshold.
:return: A number indicating the idle speed threshold.
`;
cpSpace.sleep_time[prosperon.DOC] = `
A duration threshold (in seconds) for which a body must remain below idle_speed before it
can sleep. Read or set this property.
:return: Number of seconds for the sleep threshold.
`;
cpSpace.collision_slop[prosperon.DOC] = `
Extra distance allowed to mitigate floating-point issues, preventing shapes from
interpenetrating excessively. Can be read or set.
:return: A number representing the collision slop distance.
`;
cpSpace.collision_bias[prosperon.DOC] = `
Rate at which interpenetration errors are corrected each step. Usually a number close to
1.0 (a bit less). Can be read or set.
:return: A number controlling bias for shape collision resolution.
`;
cpSpace.collision_persistence[prosperon.DOC] = `
How many steps a contact persists if the two shapes are still overlapping. Read or set.
:return: An integer count of persistence steps.
`;
//------------------------------------------------
// Space joint-creation methods
//------------------------------------------------
cpSpace.pin[prosperon.DOC] = `
Create a cpPinJoint between two bodies (argv[0] and argv[1]) and add it to this space.
If called without arguments, returns the PinJoint prototype object.
:return: A cpPinJoint constraint object if arguments are provided, or the prototype if none.
`;
cpSpace.pivot[prosperon.DOC] = `
Create a cpPivotJoint between two bodies at a given pivot point. If called without
arguments, returns the PivotJoint prototype object.
:param bodyA: The first cpBody.
:param bodyB: The second cpBody.
:param pivotPoint: An object { x, y } for the pivot location in world coordinates.
:return: A cpPivotJoint constraint object or the prototype if no arguments.
`;
cpSpace.gear[prosperon.DOC] = `
Create a cpGearJoint between two bodies, controlling their relative angular motion.
If called without arguments, returns the GearJoint prototype object.
:param bodyA: The first cpBody.
:param bodyB: The second cpBody.
:param phase: Initial angular offset in radians.
:param ratio: Gear ratio linking rotations of bodyA and bodyB.
:return: A cpGearJoint constraint object or prototype if no arguments.
`;
cpSpace.rotary[prosperon.DOC] = `
Create a cpRotaryLimitJoint that constrains rotation between two bodies to a min and max
angle. Without arguments, returns the RotaryLimitJoint prototype.
:param bodyA: The first cpBody.
:param bodyB: The second cpBody.
:param minAngle: Minimum relative angle in radians.
:param maxAngle: Maximum relative angle in radians.
:return: A cpRotaryLimitJoint constraint object or prototype if no arguments.
`;
cpSpace.damped_rotary[prosperon.DOC] = `
Create a cpDampedRotarySpring that uses a spring-damper system to keep two bodies at a
relative rest angle. Without arguments, returns the prototype.
:param bodyA: The first cpBody.
:param bodyB: The second cpBody.
:param restAngle: Rest angle between the bodies.
:param stiffness: Spring stiffness.
:param damping: Damping factor.
:return: A cpDampedRotarySpring object or prototype if no arguments.
`;
cpSpace.damped_spring[prosperon.DOC] = `
Create a cpDampedSpring between two bodies, each with an anchor point and a rest length.
Without arguments, returns the prototype.
:param bodyA: The first cpBody.
:param bodyB: The second cpBody.
:param anchorA: { x, y } anchor relative to bodyA.
:param anchorB: { x, y } anchor relative to bodyB.
:param restLength: The spring's natural rest length.
:param stiffness: The spring stiffness.
:param damping: The damping factor.
:return: A cpDampedSpring constraint or prototype if no arguments.
`;
cpSpace.groove[prosperon.DOC] = `
Create a cpGrooveJoint, which allows one bodys anchor to slide along a groove defined
in the other body. Without arguments, returns the prototype.
:param bodyA: The first cpBody.
:param bodyB: The second cpBody.
:param grooveA: { x, y } start of the groove in bodyA.
:param grooveB: { x, y } end of the groove in bodyA.
:param anchorB: { x, y } anchor point on bodyB.
:return: A cpGrooveJoint object or prototype if no arguments.
`;
cpSpace.slide[prosperon.DOC] = `
Create a cpSlideJoint that limits the distance between two anchor points on two bodies
to a min and max. Without arguments, returns the prototype.
:param bodyA: The first cpBody.
:param bodyB: The second cpBody.
:param anchorA: { x, y } anchor relative to bodyA.
:param anchorB: { x, y } anchor relative to bodyB.
:param minDistance: Minimum distance allowed.
:param maxDistance: Maximum distance allowed.
:return: A cpSlideJoint object or prototype if no arguments.
`;
cpSpace.ratchet[prosperon.DOC] = `
Create a cpRatchetJoint, allowing relative rotation in steps (like a ratchet). Without
arguments, returns the prototype.
:param bodyA: The first cpBody.
:param bodyB: The second cpBody.
:param phase: Initial angular offset.
:param ratchet: The angle increment for each "click" of the ratchet.
:return: A cpRatchetJoint object or prototype if no arguments.
`;
cpSpace.motor[prosperon.DOC] = `
Create a cpSimpleMotor that enforces a relative angular velocity between two bodies.
Without arguments, returns the prototype.
:param bodyA: The first cpBody.
:param bodyB: The second cpBody.
:param rate: The desired relative angular speed (radians per second).
:return: A cpSimpleMotor object or prototype if no arguments.
`;
//------------------------------------------------
// Body methods and properties
//------------------------------------------------
var cpBody = prosperon.c_types.cpBody;
cpBody.position[prosperon.DOC] = `
The current position (x, y) of this body in world coordinates. Read or assign { x, y }.
`;
cpBody.angle[prosperon.DOC] = `
The body's rotation in radians. 0 is "no rotation". Read or set this property.
`;
cpBody.velocity[prosperon.DOC] = `
The linear velocity of this body (x, y). Typically updated each time step, but you
can also set it directly.
`;
cpBody.angularVelocity[prosperon.DOC] = `
The body's angular velocity (radians per second).
`;
cpBody.moment[prosperon.DOC] = `
The moment of inertia (rotational inertia) for this body. Must not be changed if
the body is static or kinematic.
`;
cpBody.torque[prosperon.DOC] = `
Accumulated torque on this body. Setting this directly allows you to apply a net
torque each frame.
`;
cpBody.mass[prosperon.DOC] = `
Mass of the body. Must not be changed if the body is static or kinematic.
`;
cpBody.centerOfGravity[prosperon.DOC] = `
Offset of the center of gravity (relative to the body's local origin). Typically
set before shapes are attached.
`;
cpBody.force[prosperon.DOC] = `
Accumulated force on this body. Setting this is another way to apply a direct force
each frame.
`;
cpBody.type[prosperon.DOC] = `
The body's type. 0 = CP_BODY_TYPE_DYNAMIC, 1 = CP_BODY_TYPE_KINEMATIC, 2 = CP_BODY_TYPE_STATIC.
Changing the type for a body can move it in or out of the simulation.
`;
cpBody.isSleeping[prosperon.DOC] = `
Returns true if this body is currently sleeping. Sleep is managed automatically.
`;
cpBody.activate[prosperon.DOC] = `
Wake up this body if it is sleeping, making it active again. Typically needed if
you manually move a sleeping body.
`;
cpBody.sleep[prosperon.DOC] = `
Force this body to sleep immediately. Useful if you know it won't move for a while.
`;
cpBody.activateStatic[prosperon.DOC] = `
Wake up any dynamic bodies touching this static/kinematic body. Optionally pass a shape
to wake only bodies that touch that shape.
:param shape: (optional) A cpShape that is part of this body.
:return: None
`;
cpBody.sleepWithGroup[prosperon.DOC] = `
Put this body to sleep as though it were in the same group as another sleeping body.
Allows linking bodies for group sleep.
:param otherBody: Another body that is already sleeping.
:return: None
`;
cpBody.applyForceAtWorldPoint[prosperon.DOC] = `
Apply a force to this body at a specific world space point. This can move or rotate the
body depending on the offset from its center of gravity.
:param force: { x, y } force vector in world coordinates.
:param point: { x, y } point in world coordinates.
:return: None
`;
cpBody.applyForceAtLocalPoint[prosperon.DOC] = `
Apply a force at a local point on the body (local coords). The bodys transform is used
to find its world effect.
:param force: { x, y } force vector in body local coordinates.
:param point: { x, y } local point relative to the body's origin.
:return: None
`;
cpBody.applyImpulseAtWorldPoint[prosperon.DOC] = `
Apply an instantaneous impulse (like a collision) at a point in world coordinates.
:param impulse: { x, y } impulse vector in world coordinates.
:param point: { x, y } where to apply the impulse, in world coordinates.
:return: None
`;
cpBody.applyImpulseAtLocalPoint[prosperon.DOC] = `
Apply an instantaneous impulse at a local point. Similar to applyForceAtLocalPoint, but
impulse is used instead of a continuous force.
:param impulse: { x, y } impulse vector in local coordinates.
:param point: { x, y } local point on the body.
:return: None
`;
cpBody.eachShape[prosperon.DOC] = `
Iterate over all cpShape objects attached to this body, calling the provided callback.
:param callback: A function(shape) that is called once per shape.
:return: None
`;
cpBody.eachConstraint[prosperon.DOC] = `
Iterate over all cpConstraint objects attached to this body, calling the provided
callback.
:param callback: A function(constraint) that is called once per constraint.
:return: None
`;
cpBody.eachArbiter[prosperon.DOC] = `
Iterate over all cpArbiters (contact pairs) for this body. Not currently implemented
for JavaScript, so its a no-op in this binding.
:return: None
`;
cpBody.add_circle_shape[prosperon.DOC] = `
Attach a circle shape to this body. Does not automatically add the shape to the space.
You may need to space.addShape(...) or store it.
:param radius: The radius of the circle shape.
:param offset: { x, y } local offset of the circle center from the bodys origin.
:return: A cpShape representing the circle.
`;
cpBody.add_segment_shape[prosperon.DOC] = `
Attach a line segment shape to this body. Does not automatically add it to the space.
Useful for edges or walls.
:param a: { x, y } start point of the segment in local coords.
:param b: { x, y } end point of the segment in local coords.
:param radius: Thickness radius for the segment.
:return: A cpShape representing the segment.
`;
cpBody.add_poly_shape[prosperon.DOC] = `
Attach a polygon shape to this body. Currently a stub that uses a placeholder for verts.
Does not automatically add the shape to the space.
:param count: Number of vertices. (Actual vertex data is not fully implemented in the stub.)
:return: A cpShape representing the polygon.
`;
//------------------------------------------------
// Shape methods and properties
//------------------------------------------------
var cpShape = prosperon.c_types.cpShape;
cpShape.getBB[prosperon.DOC] = `
Retrieve the bounding box of this shape. Returns an object with x, y, width, and height
corresponding to the bounding box in world coordinates.
:return: An object { x, y, width, height }.
`;
cpShape.collisionType[prosperon.DOC] = `
An integer used to identify the collision type of this shape. Used with collision handlers
if you have custom collision code. Assign any int to set.
:return: The collision type integer.
`;
cpShape.sensor[prosperon.DOC] = `
If true, the shape is a sensor that detects collisions without generating contact forces.
Set to false for normal collisions.
:return: Boolean indicating sensor status.
`;
cpShape.elasticity[prosperon.DOC] = `
Coefficient of restitution (bounciness). Ranges from 0 (inelastic) to 1 (fully elastic),
though values above 1 are possible (super bouncy).
:return: A number for shape elasticity.
`;
cpShape.friction[prosperon.DOC] = `
Coefficient of friction for this shape. Typically 0 for no friction to 1 or more for
high friction.
:return: A number for friction.
`;
cpShape.surfaceVelocity[prosperon.DOC] = `
Relative velocity of the shapes surface, useful for conveyors. Typically { x:0, y:0 }
for normal shapes.
:return: { x, y } velocity vector.
`;
cpShape.filter[prosperon.DOC] = `
Collision filtering parameters. An object { categories, mask, group } controlling which
objects this shape collides with. E.g., shape.filter = { categories: 1, mask: 0xFFFFFFFF, group: 0 }
:return: Object with fields categories, mask, and group.
`;
//------------------------------------------------
// Circle shape (subtype of cpShape)
//------------------------------------------------
var cpCircleShape = prosperon.c_types.cpCircleShape;
cpCircleShape.radius[prosperon.DOC] = `
The radius of this circle shape.
:return: A number representing circle radius.
`;
cpCircleShape.offset[prosperon.DOC] = `
A local offset of the circle center relative to the body's origin. Typically { x: 0, y: 0 }.
:return: { x, y } local offset.
`;
//------------------------------------------------
// Segment shape (subtype of cpShape)
//------------------------------------------------
var cpSegmentShape = prosperon.c_types.cpSegmentShape;
cpSegmentShape.setEndpoints[prosperon.DOC] = `
Change the endpoints of this line segment. Each endpoint is specified as an { x, y }
vector in the body's local coordinates.
:param startPoint: { x, y } local coordinates for the segment start.
:param endPoint: { x, y } local coordinates for the segment end.
:return: None
`;
cpSegmentShape.radius[prosperon.DOC] = `
Thickness radius of this segment shape.
:return: Number representing the segment's thickness radius.
`;
//------------------------------------------------
// Poly shape (subtype of cpShape)
//------------------------------------------------
var cpPolyShape = prosperon.c_types.cpPolyShape;
cpPolyShape.setVerts[prosperon.DOC] = `
Set the vertices of this polygon shape. Currently a stub: in reality youd pass an array
of { x, y } points to define the polygon outline.
:param count: Number of vertices (integer).
:param verts: (stub) Array of { x, y } points in local coords.
:return: None
`;
cpPolyShape.radius[prosperon.DOC] = `
Radius used to give the polygon a beveled edge. 0 for a sharp polygon, >0 for smoothing
corners.
:return: Number representing the bevel radius.
`;
//------------------------------------------------
// Constraint methods and properties
//------------------------------------------------
var cpConstraint = prosperon.c_types.cpConstraint;
cpConstraint.bodyA[prosperon.DOC] = `
Return the first body attached to this constraint.
:return: The cpBody object for body A.
`;
cpConstraint.bodyB[prosperon.DOC] = `
Return the second body attached to this constraint.
:return: The cpBody object for body B.
`;
cpConstraint.max_force[prosperon.DOC] = `
The maximum force the constraint can apply. If the constraint would need more than this
force to maintain its constraints, it won't hold them fully.
:return: Number for max force.
`;
cpConstraint.max_bias[prosperon.DOC] = `
Limits how quickly the constraint can correct errors each step. Setting a maxBias too low
can cause "soft" constraints.
:return: Number controlling maximum correction speed.
`;
cpConstraint.error_bias[prosperon.DOC] = `
Bias factor controlling how quickly overlap/penetration is corrected. Typically close to 1.0.
:return: Number (0..1 range).
`;
cpConstraint.collide_bodies[prosperon.DOC] = `
If true, the connected bodies can still collide with each other. If false, the bodies
won't collide. Usually false for "connected" objects.
:return: Boolean indicating if bodies collide.
`;
cpConstraint.broken[prosperon.DOC] = `
Check if the constraint is still in the space. Returns true if the space still contains
this constraint, false otherwise.
:return: Boolean indicating whether the constraint remains in the space.
`;
cpConstraint.break[prosperon.DOC] = `
Remove this constraint from the space immediately, effectively "breaking" it.
:return: None
`;
//------------------------------------------------
// Return the chipmunk object
//------------------------------------------------
return chipmunk;

View File

@@ -1,6 +1,7 @@
var io = use('io')
var util = use('util')
var dumpfolder = ".prosperon";
io.mkdir(dumpfolder)
@@ -30,12 +31,20 @@ Cmdline.register_order(
var gs = ['console', 'prosperon', 'actor', 'use']
Object.getOwnPropertyDescriptor(prosperon.c_types.transform, 'pos')[prosperon.DOC] = 'TEST DOC'
console.log(Object.getOwnPropertyDescriptor(prosperon.c_types.transform,'pos')[prosperon.DOC])
for (var g of gs)
io.slurpwrite(`.src/docs/api/${g}.md`, doc.writeDocFile(globalThis[g], g))
var coredocs = io.enumerate("scripts/modules", 0)
coredocs = coredocs.filter(x => io.match("**/*.js", x)).map(x => x.name())
var TYPEPATH = '.src/docs/api/types/'
for (var c in prosperon.c_types)
io.slurpwrite(`${TYPEPATH}${c}.md`, doc.writeDocFile(prosperon.c_types[c], c))
var APIPATH = '.src/docs/api/modules/'
for (var m of coredocs) {
@@ -43,10 +52,6 @@ Cmdline.register_order(
var path = `${APIPATH}${m}.md`
io.slurpwrite(path, doc.writeDocFile(u, m))
}
var TYPEPATH = '.src/docs/api/types/'
for (var c in prosperon.c_types)
io.slurpwrite(`${TYPEPATH}${c}.md`, doc.writeDocFile(prosperon.c_types[c], c))
var DULLPATH = '.src/docs/dull/'
var mixins = ['Object', 'String', 'Array', 'Map', 'WeakMap', 'Symbol','Set', 'WeakSet', 'ArrayBuffer', 'Function']

View File

@@ -7585,6 +7585,7 @@ JSValue js_imgui_use(JSContext *js);
#include "qjs_nota.h"
#include "qjs_enet.h"
#include "qjs_soloud.h"
#include "qjs_chipmunk.h"
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
@@ -7610,6 +7611,7 @@ void ffi_load(JSContext *js, int argc, char **argv) {
arrput(module_registry, MISTLINE(dmon));
arrput(module_registry, MISTLINE(nota));
arrput(module_registry, MISTLINE(enet));
arrput(module_registry, MISTLINE(chipmunk2d));
#ifdef TRACY_ENABLE
arrput(module_registry, MISTLINE(tracy));

View File

@@ -362,177 +362,6 @@ void nota_write_record(NotaBuffer *nb, unsigned long long count)
nb->size -= (10 - used);
}
void nota_write_number_str(NotaBuffer *nb, const char *str)
{
/* -------------------------------------------
1) Parse sign
------------------------------------------- */
int negative = 0;
if (*str == '+') {
str++;
}
else if (*str == '-') {
negative = 1;
str++;
}
/* -------------------------------------------
2) Parse integer part
------------------------------------------- */
long long coefficient = 0;
int got_digits = 0;
while (*str >= '0' && *str <= '9') {
got_digits = 1;
int d = (*str - '0');
str++;
// Basic overflow check (very naive):
if (coefficient <= (LLONG_MAX - d) / 10) {
coefficient = coefficient * 10 + d;
} else {
// If you want to handle overflow by switching to float, do that here.
// For simplicity, let's just keep wrapping. In production, be careful!
coefficient = coefficient * 10 + d;
}
}
/* -------------------------------------------
3) Check for decimal part
------------------------------------------- */
int has_decimal_point = 0;
int fraction_digits = 0;
if (*str == '.') {
has_decimal_point = 1;
str++;
while (*str >= '0' && *str <= '9') {
got_digits = 1;
int d = (*str - '0');
str++;
fraction_digits++;
if (coefficient <= (LLONG_MAX - d) / 10) {
coefficient = coefficient * 10 + d;
} else {
// Same naive overflow comment
coefficient = coefficient * 10 + d;
}
}
}
/* -------------------------------------------
4) Check for exponent part
------------------------------------------- */
int exponent_negative = 0;
long long exponent_val = 0;
if (*str == 'e' || *str == 'E') {
str++;
if (*str == '+') {
str++;
}
else if (*str == '-') {
exponent_negative = 1;
str++;
}
while (*str >= '0' && *str <= '9') {
int d = (*str - '0');
str++;
if (exponent_val <= (LLONG_MAX - d) / 10) {
exponent_val = exponent_val * 10 + d;
} else {
// Again, naive overflow handling
exponent_val = exponent_val * 10 + d;
}
}
}
/* -------------------------------------------
5) If there were no valid digits at all,
store 0 and return. (simple fallback)
------------------------------------------- */
if (!got_digits) {
nota_write_int_buf(nb, 0);
return;
}
/* -------------------------------------------
6) Combine fraction digits into exponent
final_exponent = exponent_val - fraction_digits
(apply exponent sign if any)
------------------------------------------- */
if (exponent_negative) {
exponent_val = -exponent_val;
}
long long final_exponent = exponent_val - fraction_digits;
/* -------------------------------------------
7) Decide if we are storing an integer
or a float in Nota format.
-------------------------------------------
Rule used here:
- If there's no decimal point AND final_exponent == 0,
=> integer
- If we do have a decimal point, but fraction_digits == 0
and exponent_val == 0, then the user typed something
like "123." or "100.0". That is effectively an integer,
so store it as an integer if you want a purely numeric approach.
- Otherwise store as float.
------------------------------------------- */
// If "no decimal" => definitely integer:
// or decimal present but fraction_digits=0 & exponent_val=0 => integer
int treat_as_integer = 0;
if (!has_decimal_point && final_exponent == 0) {
treat_as_integer = 1;
}
else if (has_decimal_point && fraction_digits == 0 && exponent_val == 0) {
// Means "123." or "123.0"
treat_as_integer = 1;
}
if (treat_as_integer) {
// If negative => flip the sign in the stored value
if (negative) {
coefficient = -coefficient;
}
// Write the integer in Nota format (varint with sign bit)
nota_write_int_buf(nb, coefficient);
return;
}
/* -------------------------------------------
8) Write as float in Nota format
We do basically the same approach as
nota_write_float_buf does:
- NOTA_FLOAT nibble
- sign bit if negative
- exponent sign bit if final_exponent < 0
- varint of |final_exponent|
- varint of |coefficient|
------------------------------------------- */
{
char *p = nota_buffer_alloc(nb, 21); // Up to ~21 bytes worst-case
p[0] = NOTA_FLOAT;
if (negative) {
p[0] |= (1 << 3); // Mantissa sign bit
}
if (final_exponent < 0) {
p[0] |= (1 << 4); // Exponent sign bit
final_exponent = -final_exponent;
}
// Write exponent as varint (with 3 bits used in the first byte)
char *c = nota_continue_num(final_exponent, p, 3);
// Write the absolute coefficient (7 bits used in the first byte)
char *end = nota_continue_num(coefficient, c, 7);
// Adjust the buffer size to the actual used length
size_t used = (size_t)(end - p);
nb->size -= (21 - used);
}
}
void nota_write_number(NotaBuffer *nb, double n)
{
nota_write_int_or_float_buf(nb, n);

1278
source/qjs_chipmunk.c Normal file

File diff suppressed because it is too large Load Diff

8
source/qjs_chipmunk.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_CHIPMUNK_H
#define QJS_CHIPMUNK_H
#include "quickjs.h"
JSValue js_chipmunk2d_use(JSContext*);
#endif

View File

@@ -150,19 +150,14 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val)
int tag = JS_VALUE_GET_TAG(val);
switch (tag) {
case JS_TAG_INT: {
double d;
JS_ToFloat64(ctx, &d, val);
nota_write_number(&enc->nb, d);
return;
}
case JS_TAG_INT:
case JS_TAG_BIG_INT:
case JS_TAG_FLOAT64:
case JS_TAG_BIG_DECIMAL:
case JS_TAG_BIG_FLOAT: {
const char *str = JS_ToCString(ctx, val);
nota_write_number_str(&enc->nb, str);
JS_FreeCString(ctx, str);
double d;
JS_ToFloat64(ctx, &d, val);
nota_write_number(&enc->nb, d);
return;
}

View File

@@ -1,13 +1,5 @@
[wrap-file]
directory = Chipmunk2D-Chipmunk-7.0.3
source_url = https://github.com/slembcke/Chipmunk2D/archive/Chipmunk-7.0.3.tar.gz
source_filename = Chipmunk2D-Chipmunk-7.0.3.tar.gz
source_hash = 1e6f093812d6130e45bdf4cb80280cb3c93d1e1833d8cf989d554d7963b7899a
patch_filename = chipmunk_7.0.3-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/chipmunk_7.0.3-1/get_patch
patch_hash = 90e5e1be7712812cd303974910c37e7c4f2d2c8060312521b3a7995daa54f66a
wrapdb_version = 7.0.3-1
[provide]
chipmunk = chipmunk_dep
[wrap-git]
url = https://github.com/slembcke/Chipmunk2D.git
revision = Chipmunk-7.0.3
depth = 1

290
tests/chipmunk2d.js Normal file
View File

@@ -0,0 +1,290 @@
// chipmunk_test.js
var chipmunk = use('chipmunk2d');
var os = use('os');
// Constants
var EPSILON = 1e-12; // Tolerance for floating-point comparisons
// Helper function to create a vector
function vec(x, y) {
return { x: x, y: y };
}
// Deep comparison function for objects and physics properties
function deepCompare(expected, actual, path = '') {
if (expected === actual) return { passed: true, messages: [] };
// Handle vector comparison
if (expected && expected.x !== undefined && expected.y !== undefined &&
actual && actual.x !== undefined && actual.y !== undefined) {
const dx = Math.abs(expected.x - actual.x);
const dy = Math.abs(expected.y - actual.y);
if (dx <= EPSILON && dy <= EPSILON) {
return { passed: true, messages: [] };
}
return {
passed: false,
messages: [
`Vector mismatch at ${path}: expected (${expected.x}, ${expected.y}), got (${actual.x}, ${actual.y})`,
`Differences: x=${dx}, y=${dy} exceed tolerance ${EPSILON}`
]
};
}
// Number comparison with tolerance
if (typeof expected === 'number' && typeof actual === 'number') {
const diff = Math.abs(expected - actual);
if (diff <= EPSILON) {
return { passed: true, messages: [] };
}
return {
passed: false,
messages: [
`Number mismatch at ${path}: expected ${expected}, got ${actual}`,
`Difference ${diff} exceeds tolerance ${EPSILON}`
]
};
}
// Array comparison
if (Array.isArray(expected) && Array.isArray(actual)) {
if (expected.length !== actual.length) {
return {
passed: false,
messages: [`Array length mismatch at ${path}: expected ${expected.length}, got ${actual.length}`]
};
}
let messages = [];
for (let i = 0; i < expected.length; i++) {
const result = deepCompare(expected[i], actual[i], `${path}[${i}]`);
if (!result.passed) messages.push(...result.messages);
}
return { passed: messages.length === 0, messages };
}
// Object comparison
if (typeof expected === 'object' && expected !== null &&
typeof actual === 'object' && actual !== null) {
const expKeys = Object.keys(expected).sort();
const actKeys = Object.keys(actual).sort();
if (JSON.stringify(expKeys) !== JSON.stringify(actKeys)) {
return {
passed: false,
messages: [`Object keys mismatch at ${path}: expected ${expKeys}, got ${actKeys}`]
};
}
let messages = [];
for (let key of expKeys) {
const result = deepCompare(expected[key], actual[key], `${path}.${key}`);
if (!result.passed) messages.push(...result.messages);
}
return { passed: messages.length === 0, messages };
}
return {
passed: false,
messages: [`Value mismatch at ${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`]
};
}
// Test cases for Chipmunk functionality
var testCases = [
// Space tests
{
name: "Space creation and gravity",
run: () => {
const space = chipmunk.make_space();
space.gravity = vec(0, -9.81);
return deepCompare(vec(0, -9.81), space.gravity);
}
},
{
name: "Space iterations",
run: () => {
const space = chipmunk.make_space();
space.iterations = 5;
return deepCompare(5, space.iterations);
}
},
// Body tests
{
name: "Body creation and position",
run: () => {
const space = chipmunk.make_space();
const body = space.body();
body.position = vec(10, 20);
return deepCompare(vec(10, 20), body.position);
}
},
{
name: "Body mass and moment",
run: () => {
const space = chipmunk.make_space();
const body = space.body();
body.type = 0
body.mass = 10;
body.moment = 100;
return {
passed: deepCompare(10, body.mass).passed && deepCompare(100, body.moment).passed,
messages: [
...deepCompare(10, body.mass).messages,
...deepCompare(100, body.moment).messages
]
};
}
},
{
name: "Body velocity",
run: () => {
const space = chipmunk.make_space();
const body = space.body();
body.velocity = vec(5, -5);
return deepCompare(vec(5, -5), body.velocity);
}
},
{
name: "Body force application",
run: () => {
const space = chipmunk.make_space();
const body = space.body();
body.type = 0
body.mass = 1;
body.moment = 1
body.applyForceAtWorldPoint(vec(10, 0), vec(0, 0));
space.step(1/60); // One frame at 60 FPS
const expectedVelocity = vec(10/60, 0); // F = ma, v = at
return deepCompare(expectedVelocity, body.velocity);
}
},
// Shape tests
{
name: "Circle shape creation",
run: () => {
const space = chipmunk.make_space();
const body = space.body();
const shape = body.circle({radius:5, offset:vec(0, 0)});
const radiusResult = deepCompare(5, shape.radius);
const offsetResult = deepCompare(vec(0, 0), shape.offset);
return {
passed: radiusResult.passed && offsetResult.passed,
messages: [...radiusResult.messages, ...offsetResult.messages]
};
}
},
{
name: "Segment shape creation",
run: () => {
const space = chipmunk.make_space();
const body = space.body();
const shape = body.add_segment_shape(vec(0, 0), vec(10, 10), 2);
for (var i in shape) console.log(i)
shape.setEndpoints(vec(1, 1), vec(11, 11));
const radiusResult = deepCompare(2, shape.radius);
// Note: Chipmunk doesn't provide endpoint getters, so we test indirectly
return radiusResult;
}
},
// Constraint tests
{
name: "Pin joint",
run: () => {
const space = chipmunk.make_space();
const body1 = space.body();
const body2 = space.body();
body1.position = vec(0, 0);
body2.position = vec(10, 0);
const joint = space.pin(body1, body2);
joint.distance = 10;
return deepCompare(10, joint.distance);
}
},
{
name: "Pivot joint",
run: () => {
const space = chipmunk.make_space();
const body1 = space.body();
const body2 = space.body();
const joint = space.pivot(body1, body2, vec(5, 5));
const anchorAResult = deepCompare(vec(5, 5), joint.anchor_a);
const anchorBResult = deepCompare(vec(5, 5), joint.anchor_b);
return {
passed: anchorAResult.passed && anchorBResult.passed,
messages: [...anchorAResult.messages, ...anchorBResult.messages]
};
}
},
// Simulation test
{
name: "Basic gravity simulation",
run: () => {
const space = chipmunk.make_space();
console.log(space.gravity)
space.gravity = vec(0, -9.81);
console.log(space.gravity)
const body = space.body();
body.circle(5);
body.mass = 1;
body.moment = 1;
body.position = vec(0, 100);
for (var i = 0; i < 61; i++) space.step(1/60)
const expectedPos = vec(0, 100 - (9.81/2)); // s = ut + (1/2)at^2
return deepCompare(expectedPos, body.position);
}
}
];
// Run tests and collect results
let results = [];
let testCount = 0;
for (let test of testCases) {
testCount++;
let testName = `Test ${testCount}: ${test.name}`;
try {
const result = test.run();
results.push({
testName,
passed: result.passed,
messages: result.messages
});
if (!result.passed) {
console.log(`\nDetailed Failure Report for ${testName}:`);
console.log(result.messages.join("\n"));
console.log("");
}
} catch (e) {
results.push({
testName,
passed: false,
messages: [`Test threw exception: ${e.message}`]
});
console.log(`\nException in ${testName}:`);
console.log(e.stack || e.message);
console.log("");
}
}
// Summary
console.log("\nTest Summary:");
results.forEach(result => {
console.log(`${result.testName} - ${result.passed ? "Passed" : "Failed"}`);
if (!result.passed) {
console.log(result.messages.map(m => " " + m).join("\n"));
}
});
let passedCount = results.filter(r => r.passed).length;
console.log(`\nResult: ${passedCount}/${testCount} tests passed`);
if (passedCount < testCount) {
console.log("Overall: FAILED");
os.exit(1);
} else {
console.log("Overall: PASSED");
os.exit(0);
}