19 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
John Alanbrook
e9519484cc fix enet; add enet testing
Some checks failed
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
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-linux (push) Has been cancelled
2025-02-24 13:43:01 -06:00
John Alanbrook
b7da920f31 add qjs_soloud to source tree 2025-02-24 13:37:02 -06:00
John Alanbrook
35647a5c5b Minor nota speed improvement; use nota growable array internally so no more fixed size
All checks were successful
Build and Deploy / build-linux (push) Successful in 1m15s
Build and Deploy / build-windows (CLANG64) (push) Successful in 14m57s
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-24 11:25:12 -06:00
John Alanbrook
8ea8f7fec7 Add qjs_enet into prosperon source 2025-02-24 11:15:22 -06:00
John Alanbrook
5254b84704 fixed nota encoding/decoding bug with arrays longer than 126 elements 2025-02-23 17:16:00 -06:00
John Alanbrook
f728a217c9 add dmon doc; dmon now dispatches via prosperon.on 2025-02-23 17:15:35 -06:00
John Alanbrook
c27817b73a dmon now only watches top level directory; user must call dmon.watch and supply a dmon watch function
Some checks failed
Build and Deploy / build-linux (push) Successful in 1m7s
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
2025-02-23 16:07:09 -06:00
John Alanbrook
7ea79c8ced Fix nota implementation; add nota test suite 2025-02-23 16:06:40 -06:00
John Alanbrook
fb10c63882 add dmon and nota into source tree, out of subprojects; build on macos
All checks were successful
Build and Deploy / build-linux (push) Successful in 1m9s
Build and Deploy / build-windows (CLANG64) (push) Successful in 33m33s
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-23 08:49:49 -06:00
John Alanbrook
96ef8ccba3 Add camera and debug modules
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-linux (push) Has been cancelled
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
2025-02-21 17:15:58 -06:00
John Alanbrook
6148f18340 CLI version ends with a newline 2025-02-21 16:55:45 -06:00
John Alanbrook
867a18e788 Project cleanup. Update stb libraries. Remove unused files. Enable pedantic warning flag and fix all warnings. Fill out spline file. 2025-02-21 16:38:59 -06:00
John Alanbrook
387c4364b5 SDL3 built as a static library as part of build process
All checks were successful
Build and Deploy / build-linux (push) Successful in 1m12s
Build and Deploy / build-windows (CLANG64) (push) Successful in 14m52s
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-21 11:48:28 -06:00
John Alanbrook
60dce4a08f imgui is always compiled in, and developer selects to enable or disable its drawing; fix bug with rendering lines that caused prosperon to crash
All checks were successful
Build and Deploy / build-linux (push) Successful in 34s
Build and Deploy / build-windows (CLANG64) (push) Successful in 9m15s
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-20 17:28:27 -06:00
John Alanbrook
d2325c20bd Add MSYS2 CI
All checks were successful
Build and Deploy / build-linux (push) Successful in 36s
Build and Deploy / build-windows (CLANG64) (push) Successful in 6m12s
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-20 14:31:28 -06:00
John Alanbrook
29295607df Update build action for more useful distribution
All checks were successful
Build and Deploy / build-linux (push) Successful in 41s
Build and Deploy / build-windows (push) Successful in 47s
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-19 16:50:25 -06:00
73 changed files with 9716 additions and 1593 deletions

View File

@@ -1,11 +1,10 @@
name: Build
name: Build and Deploy
on:
push:
branches: [ "*" ]
tags: [ "v*" ]
pull_request:
release:
types: [published]
jobs:
build-linux:
@@ -15,38 +14,12 @@ jobs:
steps:
- name: Check Out Code
uses: actions/checkout@v3
- name: Cache SDL3 (Linux)
uses: actions/cache@v3
uses: actions/checkout@v4
with:
path: |
sdl3
sdl3-build
key: sdl3-linux-${{ hashFiles('sdl3/CMakeLists.txt') }}
- name: Build SDL3 (Linux)
run: |
if [ ! -d "sdl3/.git" ]; then
echo "Cloning SDL3 repository..."
git clone --depth 1 --branch main https://github.com/libsdl-org/SDL.git sdl3
else
echo "SDL3 source is already present (possibly from cache)."
fi
mkdir -p sdl3-build
cd sdl3-build
cmake ../sdl3 -GNinja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/installed_sdl3" \
-DSDL_SHARED=ON \
-DSDL_STATIC=OFF
ninja
ninja install
fetch-depth: 0
- name: Build Prosperon (Linux)
run: |
export PKG_CONFIG_PATH="${PWD}/sdl3-build/installed_sdl3/lib/pkgconfig:$PKG_CONFIG_PATH"
meson setup build -Dbuildtype=release -Db_lto=true -Db_ndebug=true
meson compile -C build
@@ -55,6 +28,7 @@ jobs:
TRACY_NO_INVARIANT_CHECK: 1
run: |
meson test --print-errorlogs -C build
strip build/prosperon
- name: Upload Test Log (Linux)
if: ${{ always() }}
@@ -63,102 +37,84 @@ jobs:
name: testlog-linux
path: build/meson-logs/testlog.txt
- name: Create artifact folder (Linux)
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
run: |
mkdir _pack
cp build/prosperon _pack/
cp sdl3-build/installed_sdl3/lib/libSDL3.so _pack/
- name: Upload Artifact (Linux)
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v3
with:
name: prosperon-artifacts-linux
path: _pack
path: build/prosperon
build-windows:
runs-on: ubuntu-latest
container:
image: gitea.pockle.world/john/prosperon/linux:latest
runs-on: win-native
strategy:
matrix:
msystem: [CLANG64]
steps:
- name: Check Out Code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Cache SDL3 (Windows cross)
uses: actions/cache@v3
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
path: |
sdl3-win
sdl3-build-win
key: sdl3-win-${{ hashFiles('sdl3-win/CMakeLists.txt') }}
msystem: ${{ matrix.msystem }}
update: true
cache: true
install: |
git
zip
gzip
tar
base-devel
pacboy: |
meson
cmake
toolchain
- name: Build SDL3 (Windows cross)
- name: Build Prosperon
shell: msys2 {0}
run: |
if [ ! -d "sdl3-win/.git" ]; then
echo "Cloning SDL3 for Windows cross..."
git clone --depth 1 --branch main https://github.com/libsdl-org/SDL.git sdl3-win
else
echo "SDL3-win source already present (possibly from cache)."
fi
mkdir -p sdl3-build-win
cd sdl3-build-win
cmake ../sdl3-win -GNinja \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ \
-DCMAKE_RC_COMPILER=x86_64-w64-mingw32-windres \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/installed_sdl3_win" \
-DSDL_SHARED=ON \
-DSDL_STATIC=OFF
ninja
ninja install
- name: Configure PKG_CONFIG_PATH (Windows cross)
run: |
echo "PKG_CONFIG_PATH=${GITHUB_WORKSPACE}/sdl3-build-win/installed_sdl3_win/lib/pkgconfig:$PKG_CONFIG_PATH" >> $GITHUB_ENV
- name: Build Prosperon (Windows cross)
run: |
meson setup build -Dbuildtype=release -Db_lto=true -Db_ndebug=true --cross-file mingw32.cross
meson setup build -Dbuildtype=release -Db_lto=true -Db_ndebug=true -Dtracy:only_localhost=true -Dtracy:no_broadcast=true
meson compile -C build
- name: Test Prosperon
shell: msys2 {0}
continue-on-error: true
env:
TRACY_NO_INVARIANT_CHECK: 1
run: |
meson test --print-errorlogs -C build
strip build/prosperon.exe
- name: Upload Test Log
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: testlog-linux
name: testlog-windows
path: build/meson-logs/testlog.txt
- name: Create package folder
run: |
mkdir _pack
cp build/prosperon.exe _pack/
cp sdl3-build-win/installed_sdl3_win/bin/SDL3.dll _pack/
- name: Upload Artifact (Windows cross)
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
- name: Upload Artifact (Windows)
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v3
with:
name: prosperon-artifacts-windows
path: _pack
path: build/prosperon.exe
package-dist:
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
needs: [build-linux, build-windows]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Latest Tag
id: get_tag
run: |
TAG=$(git describe --tags --abbrev=0)
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Download Linux Artifacts
uses: actions/download-artifact@v3
@@ -175,21 +131,113 @@ jobs:
- name: Create the Dist Folder
run: |
mkdir dist
cp README.md dist/
cp license.txt dist/
cp -r examples dist/
# Make subdirectories for each platform
mkdir dist/linux
mkdir dist/win
# Copy artifacts in
cp linux_artifacts/* dist/linux/
cp windows_artifacts/* dist/win/
- name: Package Final Dist
run: |
TAG=${{ steps.get_tag.outputs.tag }}
zip -r "prosperon-${TAG}.zip" dist
echo "Created prosperon-${TAG}.zip"
- name: Upload Final Dist
uses: actions/upload-artifact@v3
with:
name: prosperon
name: "prosperon-${{ steps.get_tag.outputs.tag }}"
path: "prosperon-${{ steps.get_tag.outputs.tag }}.zip"
deploy-itch:
needs: [package-dist]
if: ${{ false }}
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Latest Tag
id: get_tag
run: |
TAG=$(git describe --tags --abbrev=0)
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Download Final Distribution
uses: actions/download-artifact@v3
with:
name: "prosperon-${{ steps.get_tag.outputs.tag }}"
path: dist
- name: Set up Butler
uses: jdno/setup-butler@v1
- name: Push to itch.io
run: |
butler push "dist/prosperon-${{ steps.get_tag.outputs.tag }}.zip" ${{ secrets.ITCHIO_USERNAME }}/prosperon:win-linux --userversion ${{ steps.get_tag.outputs.tag }}
env:
BUTLER_API_KEY: ${{ secrets.ITCHIO_API_KEY }}
deploy-gitea:
needs: [package-dist]
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Latest Tag and Commit Message
id: get_tag
run: |
TAG=$(git describe --tags --abbrev=0)
COMMIT_MSG=$(git log -1 --pretty=%B "${TAG}")
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "commit_msg=$COMMIT_MSG" >> $GITHUB_OUTPUT
- name: Download Final Distribution
uses: actions/download-artifact@v3
with:
name: "prosperon-${{ steps.get_tag.outputs.tag }}"
path: dist
- name: Create or Update Gitea Release
run: |
TAG=${{ steps.get_tag.outputs.tag }}
ZIP_FILE="dist/prosperon-${TAG}.zip"
COMMIT_MSG=$(echo "${{ steps.get_tag.outputs.commit_msg }}" | jq -R -s '.')
# Check if release exists
RELEASE_ID=$(curl -s -H "Authorization: token ${{ secrets.TOKEN_GITEA }}" \
"https://gitea.pockle.world/api/v1/repos/john/prosperon/releases/tags/${TAG}" | jq -r '.id')
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" == "null" ]; then
# Create a new release if it doesn't exist
echo "Creating new release for tag ${TAG}"
RELEASE_ID=$(curl -X POST \
-H "Authorization: token ${{ secrets.TOKEN_GITEA }}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${TAG}\",\"target_commitish\":\"${{ github.sha }}\",\"name\":\"${TAG}\",\"body\":${COMMIT_MSG},\"draft\":false,\"prerelease\":false}" \
"https://gitea.pockle.world/api/v1/repos/john/prosperon/releases" | jq -r '.id')
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" == "null" ]; then
echo "Failed to create release for tag ${TAG}"
exit 1
fi
echo "Created release with ID: ${RELEASE_ID}"
else
echo "Release already exists with ID: ${RELEASE_ID}"
fi
# Upload the zip file as an asset
curl -X POST \
-H "Authorization: token ${{ secrets.TOKEN_GITEA }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"${ZIP_FILE}" \
"https://gitea.pockle.world/api/v1/repos/john/prosperon/releases/${RELEASE_ID}/assets?name=prosperon-${TAG}.zip"
env:
TOKEN_GITEA: ${{ secrets.TOKEN_GITEA }}

View File

@@ -1,9 +1,7 @@
Thank you for using Prosperon!
Provided are prosperon builds for all available platforms, including SDL3 for each respective one. SDL3 must be present in the same folder as prosperon to run!
Provided are prosperon builds for all available platforms. Simply run prosperon for your platform in a game folder to play!
To get started, take a dive into the provided example games in the examples folder. Just copy the prosperon executable for your platform, along with its SDL3 library, into any provided example folder, then run it!
NOTE: For MacOS, SDL3 must first be installed with homebrew. After installing homebrew, run `brew install sdl3`. This will be fixed in a future release!
To get started, take a dive into the provided example games in the examples folder. Just copy the prosperon executable for your platform, into any provided example folder, then run it!
You can take a look through the docs folder for the prosperon manual to learn all about it. The manual is available on the web at [docs.prosperon.dev](https://docs.prosperon.dev).

View File

@@ -0,0 +1,46 @@
# camera
### list() <sub>function</sub>
Return an array of available camera device IDs.
**Returns**: An array of camera IDs, or undefined if no cameras are available.
### open(id) <sub>function</sub>
Open a camera device with the given ID.
**id**: The camera ID to open.
**Returns**: A camera object on success, or throws an error if the camera cannot be opened.
### name(id) <sub>function</sub>
Return the name of the camera with the given ID.
**id**: The camera ID to query.
**Returns**: A string with the camera's name, or throws an error if the name cannot be retrieved.
### position(id) <sub>function</sub>
Return the physical position of the camera with the given ID.
**id**: The camera ID to query.
**Returns**: A string indicating the camera position ("unknown", "front", or "back").

76
docs/api/modules/debug.md Normal file
View File

@@ -0,0 +1,76 @@
# debug
### stack_depth() <sub>function</sub>
Return the current stack depth.
**Returns**: A number representing the stack depth.
### build_backtrace() <sub>function</sub>
Build and return a backtrace of the current call stack.
**Returns**: An object representing the call stack backtrace.
### closure_vars(fn) <sub>function</sub>
Return the closure variables for a given function.
**fn**: The function object to inspect.
**Returns**: An object containing the closure variables.
### local_vars(depth) <sub>function</sub>
Return the local variables for a specific stack frame.
**depth**: The stack frame depth to inspect.
**Returns**: An object containing the local variables at the specified depth.
### fn_info(fn) <sub>function</sub>
Return metadata about a given function.
**fn**: The function object to inspect.
**Returns**: An object with metadata about the function.
### backtrace_fns() <sub>function</sub>
Return an array of functions in the current backtrace.
**Returns**: An array of function objects from the call stack.
### dump_obj(obj) <sub>function</sub>
Return a string representation of a given object.
**obj**: The object to dump.
**Returns**: A string describing the object's contents.

File diff suppressed because it is too large Load Diff

View File

@@ -66,23 +66,3 @@ and booleans for pressed buttons (left, middle, right, x1, x2).
**Returns**: Object { x, y, left, middle, right, x1, x2 }
### mouse <sub>object</sub>
### keyboard <sub>object</sub>
### print_pawn_kbm(pawn) <sub>function</sub>
### procdown() <sub>function</sub>
### print_md_kbm(pawn) <sub>function</sub>
### has_bind(pawn, bind) <sub>function</sub>
### action <sub>object</sub>
### tabcomplete(val, list) <sub>function</sub>
### do_uncontrol(pawn) <sub>function</sub>
### player <sub>object</sub>

View File

@@ -174,12 +174,17 @@ Return the application's base directory (where the executable is located).
**Returns**: A string with the base directory path.
### userdir() <sub>function</sub>
### prefdir(org, app) <sub>function</sub>
Return the user's directory, often used for saving data.
Get the user-and-app-specific path where files can be written.
**org**: The name of your organization.
**app**: The name of your application.
**Returns**: A string with the user's directory path.

View File

@@ -33,71 +33,3 @@
### imgui(...args) <sub>function</sub>
### app(...args) <sub>function</sub>
### date <sub>string</sub>
### camera <sub>object</sub>
### debug <sub>boolean</sub>
### semver <sub>object</sub>
### title <sub>string</sub>
### width <sub>number</sub>
### height <sub>number</sub>
### size <sub>object</sub>
### icon <sub>object</sub>
### high_dpi <sub>number</sub>
### alpha <sub>number</sub>
### fullscreen <sub>number</sub>
### sample_count <sub>number</sub>
### enable_clipboard <sub>boolean</sub>
### enable_dragndrop <sub>boolean</sub>
### max_dropped_files <sub>number</sub>
### swap_interval <sub>number</sub>
### name <sub>string</sub>
### identifier <sub>string</sub>
### creator <sub>string</sub>
### copyright <sub>string</sub>
### type <sub>string</sub>
### url <sub>string</sub>
### postvals <sub>object</sub>
### hudcam <sub>object</sub>
### appcam <sub>object</sub>
### screencolor <sub>object</sub>
### window <sub>object</sub>
An application window, created via prosperon.engine_start or SDL calls. Freed on GC.
### font <sub>object</sub>
### gpu <sub>object</sub>
A handle for low-level GPU operations via SDL GPU. Freed on GC.
### exit() <sub>function</sub>

661
docs/dull/Array.md Normal file
View File

@@ -0,0 +1,661 @@
# Array
### length <sub>number</sub>
### at(index) <sub>function</sub>
Return the item at index 'index', supporting negative indices to count from
the end. If 'index' is out of range, returns undefined.
**index**: The index of the element to return (can be negative).
**Returns**: The element at the given index, or undefined.
### with(index, value) <sub>function</sub>
Return a shallow copy of the array, but with the element at 'index' replaced
by 'value'. If 'index' is negative, it counts from the end. Throws if out of range.
**index**: The zero-based index (can be negative) to replace.
**value**: The new value for the specified position.
**Returns**: A new array with the updated element.
### concat(items) <sub>function</sub>
Return a new array that is the result of concatenating this array with
any additional arrays or values provided.
**items**: One or more arrays or values to concatenate.
**Returns**: A new array with the items appended.
### every(callback, thisArg) <sub>function</sub>
Return true if the provided callback function returns a truthy value for
every element in the array; otherwise false.
**callback**: A function(element, index, array) => boolean.
**thisArg**: Optional. A value to use as 'this' within the callback.
**Returns**: True if all elements pass the test, otherwise false.
### some(callback, thisArg) <sub>function</sub>
Return true if the provided callback function returns a truthy value for at
least one element in the array; otherwise false.
**callback**: A function(element, index, array) => boolean.
**thisArg**: Optional. A value to use as 'this' within the callback.
**Returns**: True if at least one element passes the test, otherwise false.
### forEach(callback, thisArg) <sub>function</sub>
Call the provided callback function once for each element in the array.
Does not produce a return value.
**callback**: A function(element, index, array).
**thisArg**: Optional. A value to use as 'this' within the callback.
**Returns**: None
### map(callback, thisArg) <sub>function</sub>
Create a new array with the results of calling a provided callback function
on every element in this array.
**callback**: A function(element, index, array) => newElement.
**thisArg**: Optional. A value to use as 'this' within the callback.
**Returns**: A new array of transformed elements.
### filter(callback, thisArg) <sub>function</sub>
Create a new array containing all elements for which the provided callback
function returns a truthy value.
**callback**: A function(element, index, array) => boolean.
**thisArg**: Optional. A value to use as 'this' within the callback.
**Returns**: A new array of elements that passed the test.
### reduce(callback, initialValue) <sub>function</sub>
Apply a callback function against an accumulator and each element in the
array (from left to right) to reduce it to a single value.
**callback**: A function(accumulator, element, index, array) => newAccumulator.
**initialValue**: Optional. The initial value for the accumulator.
**Returns**: The single resulting value.
### reduceRight(callback, initialValue) <sub>function</sub>
Similar to reduce(), except it processes elements from right to left.
**callback**: A function(accumulator, element, index, array) => newAccumulator.
**initialValue**: Optional. The initial value for the accumulator.
**Returns**: The single resulting value.
### fill(value, start, end) <sub>function</sub>
Fill the array with a static value from 'start' index up to (but not including)
'end' index. Modifies the original array.
**value**: The value to fill.
**start**: The starting index (default 0).
**end**: The ending index (default array.length).
**Returns**: The modified array (with filled values).
### find(callback, thisArg) <sub>function</sub>
Return the first element in the array that satisfies the provided callback
function. If none is found, return undefined.
**callback**: A function(element, index, array) => boolean.
**thisArg**: Optional. A value to use as 'this' within the callback.
**Returns**: The first matching element, or undefined if not found.
### findIndex(callback, thisArg) <sub>function</sub>
Return the index of the first element in the array that satisfies the
provided callback function. If none is found, return -1.
**callback**: A function(element, index, array) => boolean.
**thisArg**: Optional. A value to use as 'this' within the callback.
**Returns**: The index of the first matching element, or -1 if not found.
### findLast(callback, thisArg) <sub>function</sub>
Return the last element in the array that satisfies the provided callback
function, searching from right to left. If none is found, return undefined.
**callback**: A function(element, index, array) => boolean.
**thisArg**: Optional. A value to use as 'this' within the callback.
**Returns**: The last matching element, or undefined if not found.
### findLastIndex(callback, thisArg) <sub>function</sub>
Return the index of the last element in the array that satisfies the
provided callback function, searching from right to left. If none is found,
return -1.
**callback**: A function(element, index, array) => boolean.
**thisArg**: Optional. A value to use as 'this' within the callback.
**Returns**: The index of the last matching element, or -1 if not found.
### indexOf(searchElement, fromIndex) <sub>function</sub>
Return the first index at which a given element can be found. If not present,
return -1.
**searchElement**: The item to locate in the array.
**fromIndex**: The index at which to start searching (default 0).
**Returns**: The index of the found element, or -1 if not found.
### lastIndexOf(searchElement, fromIndex) <sub>function</sub>
Return the last index at which a given element can be found, or -1 if not
present. Searches backward from 'fromIndex'.
**searchElement**: The item to locate in the array.
**fromIndex**: The index at which to start searching backward (default array.length - 1).
**Returns**: The last index of the found element, or -1 if not found.
### includes(searchElement, fromIndex) <sub>function</sub>
Return a boolean indicating whether the array contains the given element,
comparing elements using the SameValueZero algorithm.
**searchElement**: The value to search for.
**fromIndex**: The position in the array to start searching (default 0).
**Returns**: True if the element is found, otherwise false.
### join(separator) <sub>function</sub>
Join all elements of the array into a string, separated by 'separator'.
**separator**: The delimiter string to separate elements (default ',').
**Returns**: A string of array elements joined by the separator.
### toString() <sub>function</sub>
Return a string representing the elements of the array, separated by commas.
Overrides Object.prototype.toString.
**Returns**: A comma-separated string of array elements.
### toLocaleString() <sub>function</sub>
Return a localized string representing the array and its elements, calling
each element's toLocaleString if available.
**Returns**: A locale-sensitive, comma-separated string of array elements.
### pop() <sub>function</sub>
Remove the last element from the array and return it. This changes the length
of the array.
**Returns**: The removed element, or undefined if the array is empty.
### push(items) <sub>function</sub>
Append one or more elements to the end of the array and return the new length
of the array.
**items**: One or more items to append.
**Returns**: The new length of the array.
### shift() <sub>function</sub>
Remove the first element from the array and return it. This changes the length
of the array.
**Returns**: The removed element, or undefined if the array is empty.
### unshift(items) <sub>function</sub>
Insert one or more elements at the start of the array and return the new
length of the array.
**items**: One or more elements to insert at the start.
**Returns**: The new length of the array.
### reverse() <sub>function</sub>
Reverse the elements of the array in place and return the modified array.
**Returns**: The reversed array.
### toReversed() <sub>function</sub>
Return a shallow copy of the array in reverse order, without modifying the original array.
**Returns**: A new reversed array.
### sort(compareFunction) <sub>function</sub>
Sort the array in place, returning it. By default, sorts elements as strings in ascending order. An optional compareFunction may be used for custom sorting.
**compareFunction**: A function(a, b) => number, defining sort order.
**Returns**: The sorted array.
### toSorted(compareFunction) <sub>function</sub>
Return a shallow copy of the array, sorted according to the optional compare
function, without modifying the original array.
**compareFunction**: A function(a, b) => number, defining sort order.
**Returns**: A new sorted array.
### slice(start, end) <sub>function</sub>
Return a shallow copy of a portion of the array into a new array object, selected from 'start' to 'end' (end not included).
**start**: The beginning index (0-based). Negative values count from the end.
**end**: The end index (exclusive). Negative values count from the end.
**Returns**: A new array containing the extracted elements.
### splice(start, deleteCount, items) <sub>function</sub>
Change the contents of the array by removing or replacing existing elements and/or adding new elements in place. Returns an array of removed elements.
**start**: The index at which to start changing the array.
**deleteCount**: Number of elements to remove.
**items**: Elements to add in place of the removed elements.
**Returns**: An array containing the removed elements (if any).
### toSpliced(start, deleteCount, items) <sub>function</sub>
Return a shallow copy of the array, with a splice-like operation applied at 'start' removing 'deleteCount' elements and adding 'items'. Does not mutate the original array.
**start**: The index at which to start the splice operation.
**deleteCount**: Number of elements to remove.
**items**: Elements to add in place of the removed elements.
**Returns**: A new array with the splice applied.
### copyWithin(target, start, end) <sub>function</sub>
Copy a sequence of array elements within the array, overwriting existing values. This operation is performed in place and returns the modified array.
**target**: The index at which to copy the sequence to.
**start**: The beginning index of the sequence to copy.
**end**: The end index (exclusive) of the sequence to copy (default array.length).
**Returns**: The modified array.
### flatMap(callback, thisArg) <sub>function</sub>
Return a new array formed by applying a callback function to each element and then flattening the result by one level.
**callback**: A function(element, index, array) => array or value.
**thisArg**: Optional. A value to use as 'this' within the callback.
**Returns**: A new array with the flattened results.
### flat(depth) <sub>function</sub>
Return a new array with all sub-array elements concatenated into it recursively up to the specified depth.
**depth**: The maximum depth to flatten (default 1).
**Returns**: A new flattened array.
### values() <sub>function</sub>
Return a new Array Iterator object that contains the values for each index in the array.
**Returns**: An iterator over the array's elements.
### keys() <sub>function</sub>
Return a new Array Iterator object that contains the keys (indexes) for each index in the array.
**Returns**: An iterator over the array's indices.
### entries() <sub>function</sub>
Return a new Array Iterator object that contains key/value pairs for each index in the array. Each entry is [index, value].
**Returns**: An iterator over [index, value] pairs.
### add(other) <sub>function</sub>
Non-standard. Add corresponding elements of the current array and 'other' element-wise, returning a new array. Behavior depends on data types.
**other**: Another array or scalar to add to each element.
**Returns**: A new array with element-wise sum results.
### sub(other) <sub>function</sub>
Non-standard. Subtract corresponding elements of 'other' from the current array element-wise, returning a new array. Behavior depends on data types.
**other**: Another array or scalar to subtract from each element.
**Returns**: A new array with element-wise difference results.
### div(other) <sub>function</sub>
Non-standard. Divide each element of the current array by the corresponding element of 'other' (or a scalar) element-wise, returning a new array. Behavior depends on data types.
**other**: Another array or scalar for the division.
**Returns**: A new array with element-wise division results.
### scale() <sub>function</sub>
### lerp() <sub>function</sub>
### x <sub>accessor</sub>
### y <sub>accessor</sub>
### xy <sub>accessor</sub>
### r <sub>accessor</sub>
### g <sub>accessor</sub>
### b <sub>accessor</sub>
### a <sub>accessor</sub>
### rgb <sub>accessor</sub>
### rgba <sub>accessor</sub>
### filter!(fn) <sub>function</sub>
Perform an in-place filter of this array using the provided callback 'fn'.
Any element for which 'fn' returns a falsy value is removed. The array is modified
and then returned.
**fn**: A callback function(element, index, array) => boolean.
**Returns**: The filtered (modified) array.
### delete(item) <sub>function</sub>
Remove the first occurrence of 'item' from the array, if it exists.
Returns undefined.
**item**: The item to remove.
**Returns**: undefined
### copy() <sub>function</sub>
Return a deep copy of this array by applying 'deep_copy' to each element.
The resulting array is entirely new.
**Returns**: A new array that is a deep copy of the original.
### equal(b) <sub>function</sub>
Check if this array and array 'b' have the same elements in the same order.
If they are of different lengths, return false. Otherwise compare them via JSON.
**b**: Another array to compare against.
**Returns**: True if they match, false otherwise.
### last() <sub>function</sub>
Return the last element of this array. If the array is empty, returns undefined.
**Returns**: The last element of the array, or undefined if empty.
### wrapped(x) <sub>function</sub>
Return a copy of the array with the first 'x' elements appended to the end.
Does not modify the original array.
**x**: The number of leading elements to re-append.
**Returns**: A new array with the leading elements wrapped to the end.
### wrap_idx(x) <sub>function</sub>
Wrap the integer 'x' around this array's length, ensuring the resulting index
lies within [0, this.length - 1].
**x**: The index to wrap.
**Returns**: A wrapped index within this array's bounds.
### mirrored(x) <sub>function</sub>
Return a new array that appends a reversed copy (excluding the last element)
of itself to the end. For example, [1,2,3] -> [1,2,3,2,1]. If the array has length
<= 1, a copy of it is returned directly.
**Returns**: A new "mirrored" array.

View File

@@ -1,13 +1,14 @@
project('prosperon', ['c', 'cpp'], default_options : [ 'cpp_std=c++11'])
project('prosperon', ['c', 'cpp'],
version: '0.9.2',
meson_version: '>=1.4',
default_options : [ 'cpp_std=c++11'])
libtype = get_option('default_library')
link = []
src = []
if not get_option('editor')
add_project_arguments('-DNEDITOR', language:'c')
endif
add_project_arguments('-pedantic', language: ['c'])
git_tag_cmd = run_command('git', 'describe', '--tags', '--abbrev=0', check: false)
prosperon_version = 'unknown'
@@ -38,14 +39,52 @@ deps = []
if host_machine.system() == 'darwin'
add_project_arguments('-x', 'objective-c', language: 'c')
fworks = ['foundation', 'metal', 'audiotoolbox', 'metalkit', 'avfoundation', 'quartzcore', 'cocoa']
fworks = [
'foundation',
'metal',
'audiotoolbox',
'metalkit',
'avfoundation',
'quartzcore',
'cocoa',
'coreaudio',
'coremedia',
'gamecontroller',
'forcefeedback',
'iokit',
'corefoundation',
'corehaptics',
'carbon',
'uniformtypeidentifiers'
]
foreach fkit : fworks
deps += dependency('appleframeworks', modules: fkit)
endforeach
endif
cmake = import('cmake')
sdl3_opts = cmake.subproject_options()
sdl3_opts.add_cmake_defines({
'SDL_STATIC': 'ON',
'SDL_SHARED': 'OFF',
'SDL_TEST': 'OFF',
'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')
deps += dependency('sdl3', required:true)
if host_machine.system() == 'darwin'
deps += dependency('appleframeworks', modules: 'accelerate')
@@ -56,22 +95,29 @@ endif
if host_machine.system() == 'linux'
deps += cc.find_library('asound', required:true)
deps += [dependency('x11'), dependency('xi'), dependency('xcursor'), dependency('egl'), dependency('gl')]
# link += '-fuse-ld=mold' # use mold, which is very fast, for debug builds
endif
if host_machine.system() == 'windows'
deps += cc.find_library('d3d11')
# these are for tracy
deps += cc.find_library('ws2_32', required:true)
deps += cc.find_library('dbghelp')
#end
link += '-static' # Required to pack in mingw dlls on cross compilation
deps += cc.find_library('winmm')
deps += cc.find_library('setupapi')
deps += cc.find_library('imm32')
deps += cc.find_library('version')
deps += cc.find_library('cfgmgr32')
sdl3_opts.add_cmake_defines({'HAVE_ISINF': '1'}) # TODO: A hack to get this to compile on MSYS2; otherwise it doesn't link correctly
link += '-static'
endif
if host_machine.system() == 'emscripten'
link += '-sUSE_WEBGPU'
endif
sdl3_proj = cmake.subproject('sdl3', options: sdl3_opts)
deps += sdl3_proj.dependency('SDL3-static')
tracy_opts = ['fibers=true', 'on_demand=true']
quickjs_opts = []
@@ -89,28 +135,19 @@ if storefront == 'steam'
endif
deps += dependency('qjs-layout',static:true)
deps += dependency('qjs-nota',static:true)
deps += dependency('qjs-miniz',static:true)
deps += dependency('qjs-soloud',static:true)
deps += dependency('physfs', static:true)
#deps += dependency('opencv4')
#deps += cc.find_library('opencv')
deps += dependency('threads')
deps += dependency('chipmunk')
if get_option('chipmunk')
# deps += dependency('qjs-chipmunk', static:false)
endif
if get_option('enet')
#deps += dependency('qjs-enet', static:false)
endif
deps += dependency('enet', static:true)
deps += dependency('soloud', static:true)
sources = []
src += ['anim.c', 'config.c', 'datastream.c','font.c','gameobject.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', 'quadtree.c', 'aabb.c', 'rtree.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']
@@ -129,8 +166,6 @@ if get_option('editor')
foreach imgui : imsrc
sources += tp / 'imgui' / imgui
endforeach
# sub_dmon = subproject('qjs-dmon')
# dmon_dep = sub_dmon.get_variable('qjs_dmon_dep')
endif
includers = []
@@ -152,7 +187,7 @@ core = custom_target('core.zip',
' && echo "Rebuilding core.zip" && rm -f ' + meson.current_build_dir() + '/core.zip && ' +
'zip -r ' + meson.current_build_dir() + '/core.zip scripts fonts icons shaders'
],
build_always: true,
build_always_stale: true,
build_by_default: true
)
@@ -181,7 +216,7 @@ prosperon = custom_target('prosperon',
'@INPUT1@',
'@OUTPUT@'
],
build_always: true,
build_always_stale: true,
build_by_default: true
)
@@ -194,16 +229,19 @@ copy_tests = custom_target(
output: 'tests',
command: [
'cp', '-rf',
join_paths(meson.source_root(), 'tests'),
meson.build_root()
join_paths(meson.project_source_root(), 'tests'),
meson.project_build_root()
],
build_always: true,
build_always_stale: true,
build_by_default: true
)
tests = [
'spawn_actor',
'empty'
'empty',
'nota',
'enet',
'chipmunk2d'
]
foreach file : tests

26
scripts/modules/camera.js Normal file
View File

@@ -0,0 +1,26 @@
var camera = this
camera.list[prosperon.DOC] = `Return an array of available camera device IDs.
:return: An array of camera IDs, or undefined if no cameras are available.
`
camera.open[prosperon.DOC] = `Open a camera device with the given ID.
:param id: The camera ID to open.
:return: A camera object on success, or throws an error if the camera cannot be opened.
`
camera.name[prosperon.DOC] = `Return the name of the camera with the given ID.
:param id: The camera ID to query.
:return: A string with the camera's name, or throws an error if the name cannot be retrieved.
`
camera.position[prosperon.DOC] = `Return the physical position of the camera with the given ID.
:param id: The camera ID to query.
:return: A string indicating the camera position ("unknown", "front", or "back").
`
return this;

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

@@ -27,46 +27,45 @@ Cmdline.register_order = function (order, fn, doc, usage = "") {
Cmdline.register_order(
"makedoc",
function() {
var doc = use('doc')
var doc = use('doc')
var gs = ['console', 'prosperon', 'actor', 'use']
var gs = ['console', 'prosperon', 'actor', 'use']
Object.getOwnPropertyDescriptor(prosperon.c_types.transform, 'pos')[prosperon.DOC] = 'TEST DOC'
Object.getOwnPropertyDescriptor(prosperon.c_types.transform, 'pos')[prosperon.DOC] = 'TEST DOC'
console.log(Object.getOwnPropertyDescriptor(prosperon.c_types.transform,'pos')[prosperon.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))
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 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 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/'
var APIPATH = '.src/docs/api/modules/'
for (var m of coredocs) {
var u = use(m)
var path = `${APIPATH}${m}.md`
io.slurpwrite(path, doc.writeDocFile(u, m))
}
for (var m of coredocs) {
var u = use(m)
var path = `${APIPATH}${m}.md`
io.slurpwrite(path, doc.writeDocFile(u, m))
}
var DULLPATH = '.src/docs/dull/'
var mixins = ['Object', 'String', 'Array', 'Map', 'WeakMap', 'Symbol','Set', 'WeakSet', 'ArrayBuffer', 'Function']
for (var m of mixins) {
var path = `${DULLPATH}${m}.md`
io.slurpwrite(path, doc.writeDocFile(globalThis[m].prototype, m))
}
var DULLPATH = '.src/docs/dull/'
var mixins = ['Object', 'String', 'Array', 'Map', 'WeakMap', 'Symbol','Set', 'WeakSet', 'ArrayBuffer', 'Function']
for (var m of mixins) {
var path = `${DULLPATH}${m}.md`
io.slurpwrite(path, doc.writeDocFile(globalThis[m].prototype, m))
}
var dullgpath = '.src/docs/dull/globals/'
var globals = ['Object', 'String', 'Array', 'Symbol', 'Number', 'Error','Function', 'Math']
for (var m of globals) {
var path = `${dullgpath}${m}.md`
io.slurpwrite(path, doc.writeDocFile(globalThis[m], m))
}
var dullgpath = '.src/docs/dull/globals/'
var globals = ['Object', 'String', 'Array', 'Symbol', 'Number', 'Error','Function', 'Math']
for (var m of globals) {
var path = `${dullgpath}${m}.md`
io.slurpwrite(path, doc.writeDocFile(globalThis[m], m))
}
"Make documentation."
})
@@ -199,7 +198,7 @@ Cmdline.register_order(
Cmdline.register_order(
"version",
function () {
console.print(`Prosperon version ${prosperon.version} [${prosperon.revision}]`);
console.print(`Prosperon version ${prosperon.version} [${prosperon.revision}]` + "\n");
},
"Display Prosperon info.",
);

42
scripts/modules/debug.js Normal file
View File

@@ -0,0 +1,42 @@
var debug = this
debug.stack_depth[prosperon.DOC] = `Return the current stack depth.
:return: A number representing the stack depth.
`
debug.build_backtrace[prosperon.DOC] = `Build and return a backtrace of the current call stack.
:return: An object representing the call stack backtrace.
`
debug.closure_vars[prosperon.DOC] = `Return the closure variables for a given function.
:param fn: The function object to inspect.
:return: An object containing the closure variables.
`
debug.local_vars[prosperon.DOC] = `Return the local variables for a specific stack frame.
:param depth: The stack frame depth to inspect.
:return: An object containing the local variables at the specified depth.
`
debug.fn_info[prosperon.DOC] = `Return metadata about a given function.
:param fn: The function object to inspect.
:return: An object with metadata about the function.
`
debug.backtrace_fns[prosperon.DOC] = `Return an array of functions in the current backtrace.
:return: An array of function objects from the call stack.
`
debug.dump_obj[prosperon.DOC] = `Return a string representation of a given object.
:param obj: The object to dump.
:return: A string describing the object's contents.
`
return this

27
scripts/modules/dmon.js Normal file
View File

@@ -0,0 +1,27 @@
var dmon = this
dmon.watch[prosperon.DOC] = `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.
:return: None
:throws: An error if dmon is already watching.
`
dmon.unwatch[prosperon.DOC] = `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.
:return: None
:throws: An error if no directory is currently being watched.
`
dmon.poll[prosperon.DOC] = `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).
:param callback: A function to call for each event, receiving an event object as its argument.
:return: None
`
return dmon

View File

@@ -32,7 +32,8 @@ draw.line = function render_line(points, color = Color.white, thickness = 1, pip
mesh,
pipeline,
first_index: 0,
num_indices: mesh.num_indices
num_indices: mesh.num_indices,
image:whiteimage
})
}
draw.line[prosperon.DOC] = `

151
scripts/modules/enet.js Normal file
View File

@@ -0,0 +1,151 @@
// enet.js doc (updated)
var enet = this;
//------------------------------------------------
// Top-level ENet functions
//------------------------------------------------
enet.initialize[prosperon.DOC] = `
Initialize the ENet library. Must be called before using any ENet functionality.
Throws an error if initialization fails.
:return: None
`;
enet.deinitialize[prosperon.DOC] = `
Deinitialize the ENet library, cleaning up all resources. Call this when you no longer
need any ENet functionality.
:return: None
`;
enet.create_host[prosperon.DOC] = `
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.
:param address: (optional) A string in 'ip:port' format to bind the host (server), or
omit to create an unbound client-like host.
:return: An ENetHost object.
`;
//------------------------------------------------
// ENetHost methods
//------------------------------------------------
var enet_host = prosperon.c_types.enet_host;
enet_host.service[prosperon.DOC] = `
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.
:param callback: A function called once for each available event, receiving an event
object as its single argument.
:param timeout: (optional) Timeout in milliseconds. Defaults to 0 (non-blocking).
:return: None
`;
enet_host.connect[prosperon.DOC] = `
Initiate a connection from this host to a remote server. Throws an error if the
connection cannot be started.
:param host: The hostname or IP address of the remote server (e.g. "example.com" or "127.0.0.1").
:param port: The port number to connect to.
:return: An ENetPeer object representing the connection.
`;
enet_host.flush[prosperon.DOC] = `
Flush all pending outgoing packets for this host immediately.
:return: None
`;
enet_host.broadcast[prosperon.DOC] = `
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.
:param data: A JavaScript object to broadcast to all peers.
:return: None
`;
//------------------------------------------------
// ENetPeer methods
//------------------------------------------------
var enet_peer = prosperon.c_types.enet_peer;
enet_peer.send[prosperon.DOC] = `
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.
:param data: A JavaScript object to send.
:return: None
`;
enet_peer.disconnect[prosperon.DOC] = `
Request a graceful disconnection from this peer. The connection will close after
pending data is sent.
:return: None
`;
enet_peer.disconnect_now[prosperon.DOC] = `
Immediately terminate the connection to this peer, discarding any pending data.
:return: None
`;
enet_peer.disconnect_later[prosperon.DOC] = `
Request a disconnection from this peer after all queued packets are sent.
:return: None
`;
enet_peer.reset[prosperon.DOC] = `
Reset this peer's connection, immediately dropping it and clearing its internal state.
:return: None
`;
enet_peer.ping[prosperon.DOC] = `
Send a ping request to this peer to measure latency.
:return: None
`;
enet_peer.throttle_configure[prosperon.DOC] = `
Configure the throttling behavior for this peer, controlling how ENet adjusts its sending
rate based on packet loss or congestion.
:param interval: The interval (ms) between throttle adjustments.
:param acceleration: The factor to increase sending speed when conditions improve.
:param deceleration: The factor to decrease sending speed when conditions worsen.
:return: None
`;
enet_peer.timeout[prosperon.DOC] = `
Set timeout parameters for this peer, determining how long ENet waits before considering
the connection lost.
:param timeout_limit: The total time (ms) before the peer is disconnected.
:param timeout_min: The minimum timeout (ms) used for each timeout attempt.
:param timeout_max: The maximum timeout (ms) used for each timeout attempt.
:return: None
`;
// Return the enet object.
return enet;

View File

@@ -2,6 +2,15 @@ var imgui = this;
var debug = {}
var imdebug = function imdebug() {
imtoggle("Physics", debug, "draw_phys");
imtoggle("Bouning boxes", debug, "draw_bb");
imtoggle("Names", debug, "draw_names");
imtoggle("Sprite nums", debug, "sprite_nums");
imtoggle("Debug overlay", debug, "show");
imtoggle("Show ur names", debug, "urnames");
};
function imtoggle(name, obj, field) {
var changed = false;
var old = obj[field];
@@ -10,9 +19,14 @@ function imtoggle(name, obj, field) {
return false;
};
var render_menu = true;
imgui.render_menu = function(render) { render_menu = render; }
imgui.prosperon_menu = function prosperon_menu() {
imgui.newframe();
if (render_menu) {
if (debug.console)
debug.console = imgui.window("console", _ => {
imgui.text(console.transcript);
@@ -45,10 +59,10 @@ imgui.prosperon_menu = function prosperon_menu() {
imgui.sokol_gfx();
imgui.menu("Graphics", _ => {
imtoggle("Draw sprites", render, "draw_sprites");
imtoggle("Draw particles", render, "draw_particles");
imtoggle("Draw HUD", render, "draw_hud");
imtoggle("Draw GUI", render, "draw_gui");
// imtoggle("Draw sprites", render, "draw_sprites");
// imtoggle("Draw particles", render, "draw_particles");
// imtoggle("Draw HUD", render, "draw_hud");
// imtoggle("Draw GUI", render, "draw_gui");
imgui.menu("Window", _ => {
prosperon.fullscreen = imgui.checkbox("fullscreen", prosperon.fullscreen);
@@ -81,9 +95,8 @@ imgui.prosperon_menu = function prosperon_menu() {
}
});
*/
// prosperon.imgui();
imgui.endframe(render._main);
}
prosperon.imgui();
};
imgui.windowpos[prosperon.DOC] = `Return the position of the current ImGui window as an ImVec2.
@@ -309,16 +322,6 @@ imgui.barplot[prosperon.DOC] = `Plot a bar chart in the current ImPlot.
:return: None
`;
imgui.pieplot[prosperon.DOC] = `Plot a pie chart in the current ImPlot.
:param labels: An array of label strings for each slice.
:param values: An array of numeric values corresponding to each slice.
:param x: The x position of the pies center.
:param y: The y position of the pies center.
:param radius: The radius of the pie chart.
:return: None
`;
imgui.textplot[prosperon.DOC] = `Render text at the specified coordinates in plot space.
:param text: The string to render.
@@ -725,4 +728,4 @@ imgui.init[prosperon.DOC] = `Initialize ImGui with SDL and SDL_gpu, creating the
:return: None
`;
return false
return this

View File

@@ -103,8 +103,10 @@ io.basedir[prosperon.DOC] = `Return the application's base directory (where the
:return: A string with the base directory path.
`
io.userdir[prosperon.DOC] = `Return the user's directory, often used for saving data.
io.prefdir[prosperon.DOC] = `Get the user-and-app-specific path where files can be written.
:param org: The name of your organization.
:param app: The name of your application.
:return: A string with the user's directory path.
`

View File

@@ -6,7 +6,6 @@ var emitter = use('emitter')
var os = use('os')
var event = use('event')
var imgui = use('imgui')
var tracy = use('tracy')
var waittime = 1/240
@@ -15,7 +14,14 @@ var frame_t = 0
var fpses = []
var timescale = 1
var dmon = use('dmon')
function dmon_cb(e) { prosperon.dispatch('dmon', e) }
function step() {
if (dmon)
dmon.poll(dmon_cb)
var now = os.now()
var dt = now - last_frame_time
if (dt < waittime) os.sleep(waittime - dt)

20
scripts/modules/nota.js Normal file
View File

@@ -0,0 +1,20 @@
var nota = this
nota.encode[prosperon.DOC] = `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.
:param value: The JavaScript value to encode (e.g., number, string, boolean, array, object, or ArrayBuffer).
:return: An ArrayBuffer containing the NOTA-encoded data.
:throws: An error if no argument is provided.
`
nota.decode[prosperon.DOC] = `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.
:param buffer: An ArrayBuffer containing NOTA-encoded data to decode.
:return: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or undefined if no argument is provided.
`
return nota

View File

@@ -24,14 +24,14 @@ var default_conf = {
identifier: "world.pockle.prosperon",
creator: "Pockle World LLC",
copyright: "Copyright Pockle World 2025",
type: "application",
url: "https://github.com/johnbrethauer/prosperon"
type: "game",
url: "https://prosperon.dev"
}
var config = use('config.js')
config.__proto__ = default_conf
prosperon.camera = use('camera').make()
prosperon.camera = use('ext/camera').make()
prosperon.camera.size = [config.width,config.height]
var base_pipeline = {
@@ -105,7 +105,6 @@ sprite_pipeline.target = {
depth: "d32 float s8"
};
var appy = {};
appy.inputs = {};
if (os.platform() === "macos") {
@@ -116,9 +115,6 @@ appy.inputs["M-f4"] = os.exit;
controller.player[0].control(appy);
prosperon.window = prosperon.engine_start(config);
var driver = "vulkan"
@@ -141,6 +137,14 @@ render._main.window = prosperon.window
render._main.claim_window(prosperon.window)
render._main.set_swapchain('sdr', 'vsync')
var whiteimage = {}
whiteimage.surface = graphics.make_surface([1,1])
whiteimage.surface.rect({x:0,y:0,width:1,height:1}, [1,1,1,1])
whiteimage.texture = render._main.load_texture(whiteimage.surface)
var imgui = use('imgui')
if (imgui) imgui.init(render._main, prosperon.window)
var unit_transform = os.make_transform();
var cur = {};
@@ -357,7 +361,6 @@ function make_shader(sh_file) {
num_uniform_buffers: refl.ubos ? refl.ubos.length : 0,
entrypoint: shader_type === "msl" ? "main0" : "main"
}
console.log(`making shader ${sh_file} of format ${shader_type}`)
shader.gpu = render._main.make_shader(shader)
shader.reflection = refl;
@@ -653,9 +656,6 @@ render_camera[prosperon.DOC] = `Render a scene using the provided camera, drawin
:return: None
`
var imgui = use('imgui')
if (imgui) imgui.init(render._main, prosperon.window);
var swaps = [];
function gpupresent() {
os.clean_transforms();
@@ -675,7 +675,7 @@ function gpupresent() {
load: "clear"
});
if (imgui) {
if (imgui) { // draws any imgui commands present
cmds.push_debug_group("imgui")
imgui.prepend(cmds);
var pass = cmds.render_pass({
@@ -790,20 +790,6 @@ render.scissor[prosperon.DOC] = `Set the GPU scissor region to the specified rec
:return: None
`
var imdebug = function imdebug() {
imtoggle("Physics", debug, "draw_phys");
imtoggle("Bouning boxes", debug, "draw_bb");
imtoggle("Names", debug, "draw_names");
imtoggle("Sprite nums", debug, "sprite_nums");
imtoggle("Debug overlay", debug, "show");
imtoggle("Show ur names", debug, "urnames");
};
var observed_tex = undefined;
var debug = {}
debug.console = false
// Some initialization
shader_type = render._main.shader_format()[0];

View File

@@ -1,34 +0,0 @@
#include <stdlib.h>
#include <math.h>
#include "aabb.h"
aabb*
aabb_new(float x, float y, float hW, float hH) {
aabb* a = malloc(sizeof(aabb));
a->center.x = x;
a->center.y = y;
a->dims.w = hW;
a->dims.h = hH;
return a;
}
void
aabb_free(aabb *a) {
free(a);
}
int
aabb_contains(aabb *a, float x, float y) {
return (x >= a->center.x-a->dims.w &&
x <= a->center.x+a->dims.w) &&
(y >= a->center.y-a->dims.h &&
y <= a->center.y+a->dims.h);
}
int
aabb_intersects(aabb *a, aabb *b) {
return (abs(a->center.x - b->center.x) < (a->dims.w + b->dims.w)) &&
(abs(a->center.y - b->center.y) < (a->dims.h + b->dims.h));
}

View File

@@ -1,48 +0,0 @@
/*
aabb.h
2014 JSK (kutani@projectkutani.com)
Simple (2D) axis-aligned bounding box implementation. Part of the Panic
Panic project.
Released to the public domain. See LICENSE for details.
*/
#ifndef _AABB_H
#define _AABB_H
/** \brief axis-aligned bounding box
Simple struct of four floats, divided into two sub-structs.
center {x, y} - The center point of the bounding box
dims {w, h} - The half-width and half-height of the box
*/
typedef struct aabb {
struct {
float x;
float y;
} center;
struct {
float w;
float h;
} dims;
} aabb;
/// Malloc's a new aabb struct
/*!
Mallocs a new aabb struct and sets center and dims to the passed
x, y, hW, and hH values.
*/
aabb* aabb_new(float x, float y, float hW, float hH);
/// Frees the passed aabb.
void aabb_free(aabb *a);
/// Checks if the point x,y lies within the passed aabb
int aabb_contains(aabb *a, float x, float y);
/// Checks if the two passed aabb's intersect
int aabb_intersects(aabb *a, aabb *b);
#endif

View File

@@ -16,7 +16,10 @@ void animation_run(struct animation *anim, float now)
HMM_Vec4 sample_cubicspline(sampler *sampler, float t, int prev, int next)
{
return (HMM_Vec4)HMM_SLerp(HMM_QV4(sampler->data[prev]), t, HMM_QV4(sampler->data[next]));
HMM_Vec4 ret;
HMM_Quat qv = HMM_SLerp(HMM_QV4(sampler->data[prev]), t, HMM_QV4(sampler->data[next]));
memcpy(ret.e, qv.e, sizeof(ret.e));
return ret;
}
HMM_Vec4 sample_sampler(sampler *sampler, float time)
@@ -37,6 +40,9 @@ HMM_Vec4 sample_sampler(sampler *sampler, float time)
float td = sampler->times[next_time]-sampler->times[previous_time];
float t = (time - sampler->times[previous_time])/td;
HMM_Vec4 ret;
HMM_Quat qv;
switch(sampler->type) {
case LINEAR:
return HMM_LerpV4(sampler->data[previous_time],time,sampler->data[next_time]);
@@ -48,7 +54,9 @@ HMM_Vec4 sample_sampler(sampler *sampler, float time)
return sample_cubicspline(sampler,t, previous_time, next_time);
break;
case SLERP:
return (HMM_Vec4)HMM_SLerp(sampler->data[previous_time].quat, time, sampler->data[next_time].quat);
qv = HMM_SLerp(sampler->data[previous_time].quat, time, sampler->data[next_time].quat);
memcpy(ret.e,qv.e,sizeof(ret.e));
return ret;
break;
}
return sample_cubicspline(sampler,t, previous_time, next_time);

View File

@@ -24,11 +24,12 @@
#define STBI_NO_STDIO
#include "stb_image.h"
#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_BOX
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_BOX
#include "stb_image_write.h"
#define PL_MPEG_IMPLEMENTATION

View File

@@ -306,24 +306,7 @@ struct ase_t
void* mem_ctx;
};
#define ASEPRITE_ERROR_MAX 256
static char aseprite_error[ASEPRITE_ERROR_MAX] = {0};
static const char *aseprite_GetError() {
return aseprite_error;
}
static void aseprite_clear_error() {
aseprite_error[0] = 0;
}
static void aseprite_set_error(const char *msg) {
if (msg) {
strncpy(aseprite_error, msg, ASEPRITE_ERROR_MAX-1);
aseprite_error[ASEPRITE_ERROR_MAX-1] = 0;
} else
aseprite_error[0] = 0;
}
const char *aseprite_GetError();
#endif // CUTE_ASEPRITE_H
@@ -331,6 +314,24 @@ static void aseprite_set_error(const char *msg) {
#ifndef CUTE_ASEPRITE_IMPLEMENTATION_ONCE
#define CUTE_ASEPRITE_IMPLEMENTATION_ONCE
#define ASEPRITE_ERROR_MAX 256
char aseprite_error[ASEPRITE_ERROR_MAX] = {0};
const char *aseprite_GetError() {
return aseprite_error;
}
void aseprite_clear_error() {
aseprite_error[0] = 0;
}
void aseprite_set_error(const char *msg) {
if (msg) {
strncpy(aseprite_error, msg, ASEPRITE_ERROR_MAX-1);
aseprite_error[ASEPRITE_ERROR_MAX-1] = 0;
} else
aseprite_error[0] = 0;
}
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS

View File

@@ -15,11 +15,6 @@ void datastream_free(JSRuntime *rt,datastream *ds)
free(ds);
}
static void render_audio(plm_t *mpeg, plm_samples_t *samples, struct datastream *ds) {
// for (int i = 0; i < samples->count * CHANNELS; i++)
// ringpush(ds->ring, samples->interleaved[i]);
}
struct datastream *ds_openvideo(void *raw, size_t rawlen)
{
struct datastream *ds = malloc(sizeof(*ds));

1748
source/dmon.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -21,31 +21,6 @@ void font_free(JSRuntime *rt, font *f)
free(f);
}
struct sFont *MakeSDFFont(const char *fontfile, int height)
{
int packsize = 1024;
struct sFont *newfont = calloc(1, sizeof(struct sFont));
newfont->height = height;
char fontpath[256];
snprintf(fontpath, 256, "fonts/%s", fontfile);
// unsigned char *ttf_buffer = slurp_file(fontpath, NULL);
unsigned char *bitmap = malloc(packsize * packsize);
stbtt_fontinfo fontinfo;
// if (!stbtt_InitFont(&fontinfo, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer, 0))) {
// YughError("Failed to make font %s", fontfile);
// }
for (int i = 32; i < 95; i++) {
int w, h, xoff, yoff;
// unsigned char *stbtt_GetGlyphSDF(&fontinfo, height, i, 1, 0, 1, &w, &h, &xoff, &yoff);
}
return newfont;
}
struct sFont *MakeFont(void *ttf_buffer, size_t len, int height) {
if (!ttf_buffer)
return NULL;
@@ -111,29 +86,6 @@ struct sFont *MakeFont(void *ttf_buffer, size_t len, int height) {
return newfont;
}
int text_flush() {
/* if (arrlen(text_buffer) == 0) return 0;
sg_range verts;
verts.ptr = text_buffer;
verts.size = sizeof(struct text_vert) * arrlen(text_buffer);
if (sg_query_buffer_will_overflow(*buf, verts.size)) {
sg_destroy_buffer(*buf);
*buf = sg_make_buffer(&(sg_buffer_desc){
.size = verts.size,
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.usage = SG_USAGE_STREAM,
.label = "text buffer"
});
}
sg_append_buffer(*buf, &verts);
int n = arrlen(text_buffer);
arrsetlen(text_buffer, 0);
return n;
*/
}
void sdrawCharacter(struct text_vert **buffer, stbtt_packedchar c, HMM_Vec2 cursor, float scale, struct rgba color) {
struct text_vert vert;
@@ -185,7 +137,7 @@ const char *esc_color(const char *c, struct rgba *color, struct rgba defc)
{
struct rgba d;
if (!color) color = &d;
if (*c != '\e') return c;
if (*c != '\033') return c;
c++;
if (*c != '[') return c;
c++;
@@ -235,7 +187,7 @@ HMM_Vec2 measure_text(const char *text, font *f, float size, float letterSpacing
continue;
}
float charWidth = f->Characters[*c].advance + letterSpacing;
float charWidth = f->Characters[(unsigned char)*c].advance + letterSpacing;
// Handle wrapping
if (wrap > 0 && lineWidth + charWidth > wrap) {
@@ -283,15 +235,13 @@ HMM_Vec2 measure_text(const char *text, font *f, float size, float letterSpacing
}
/* pos given in screen coordinates */
struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, float scale, colorf color, float wrap) {
int wrapAtWord = 1;
text_vert *buffer = NULL;
int len = strlen(text);
HMM_Vec2 cursor = pos;
float lineHeight = f->ascent - f->descent;
float lineWidth = 0;
for (char *c = text; *c != 0; c++) {
for (const char *c = text; *c != 0; c++) {
if (*c == '\n') {
cursor.x = pos.x;
cursor.y -= lineHeight + f->linegap;
@@ -299,7 +249,7 @@ struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, float scal
continue;
}
struct character chara = f->Characters[*c];
struct character chara = f->Characters[(unsigned char)*c];
if (wrap > 0 && lineWidth + chara.advance > wrap) {
cursor.x = pos.x;

View File

@@ -60,7 +60,4 @@ struct sFont *MakeFont(void *data, size_t len, int height);
struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, float scale, colorf color, float wrap);
HMM_Vec2 measure_text(const char *text, font *f, float scale, float letterSpacing, float wrap);
// Flushes all letters from renderText calls into the provided buffer
int text_flush();
#endif

View File

@@ -1,33 +0,0 @@
#include "gameobject.h"
#include "math.h"
#include <chipmunk/chipmunk.h>
#include "stb_ds.h"
static void velocityFn(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt)
{
/* gameobject *go = body2go(body);
gameobject_apply(go);
cpVect pos = cpBodyGetPosition(body);
HMM_Vec2 g = warp_force((HMM_Vec3){pos.x, pos.y, 0}, go->warp_mask).xy;
if (!go) {
cpBodyUpdateVelocity(body,g.cp,damping,dt);
return;
}
// cpFloat d = isfinite(go->damping) ? go->damping : damping;
cpFloat d = damping;
cpBodyUpdateVelocity(body,g.cp,d,dt*go->timescale);
if (isfinite(go->maxvelocity))
cpBodySetVelocity(body, cpvclamp(cpBodyGetVelocity(body), go->maxvelocity));
if (isfinite(go->maxangularvelocity)) {
float av = cpBodyGetAngularVelocity(body);
if (fabs(av) > go->maxangularvelocity)
cpBodySetAngularVelocity(body, copysignf(go->maxangularvelocity, av));
}
*/
}

View File

@@ -1,49 +0,0 @@
#ifndef GAMEOBJECT_H
#define GAMEOBJECT_H
#define dag_rm(p,c) do{\
for (int i = arrlen(p->children)-1; i--; i >=0) {\
if (p->children[i] == c) { \
arrdelswap(p->children,i);\
c->parent=NULL;\
break;\
}}}while(0)
#define dag_set(p,c) do{\
arrpush(p->children,c);\
if(c->parent) dag_rm(c->parent,c);\
c->parent=p;\
}while(0)
#define dag_clip(p) do{\
if (p->parent)\
dag_rm(p->parent,p);\
}while(0)
struct gameobject {
float damping;
float timescale;
float maxvelocity;
float maxangularvelocity;
unsigned int layer;
unsigned int warp_mask;
};
/*
Friction uses coulomb model. When shapes collide, their friction is multiplied. Some example values:
Steel on steel: 0.0005
Wood on steel: 0.0012
Wood on wood: 0.0015
=> steel = 0.025
=> wood = 0.04
=> hardrubber = 0.31
=> concrete = 0.05
=> rubber = 0.5
Hardrubber on steel: 0.0077
Hardrubber on concrete: 0.015
Rubber on concrete: 0.025
*/
typedef struct gameobject gameobject;
#endif

View File

@@ -62,8 +62,6 @@ typedef struct rtree rtree;
//#include <cblas.h>
#endif
#define STATE_VECTOR_LENGTH 624
#define STATE_VECTOR_M 397 /* changes to STATE_VECTOR_LENGTH also require changes to this */
@@ -419,11 +417,6 @@ static BufferCheckResult get_or_extend_buffer(
#include <sys/resource.h>
#endif
#if (defined(_WIN32) || defined(__WIN32__))
#include <direct.h>
#define mkdir(x,y) _mkdir(x)
#endif
struct lrtb {
float l;
float r;
@@ -1038,7 +1031,7 @@ static const char *vals_SDL_GPUTextureFormat[] = {
"astc 12x12 float"
};
JS2ENUM(SDL_GPUTextureFormat, rets_SDL_GPUTextureFormat, vals_SDL_GPUTextureFormat);
JS2ENUM(SDL_GPUTextureFormat, rets_SDL_GPUTextureFormat, vals_SDL_GPUTextureFormat)
SDL_GPUColorTargetBlendState js2SDL_GPUColorTargetBlendState(JSContext *js, JSValue v)
{
@@ -1606,7 +1599,7 @@ int point2segindex(HMM_Vec2 p, HMM_Vec2 *segs, double slop) {
return best;
}
static JSValue idx_buffer = JS_UNDEFINED;
static JSValue idx_buffer;
static int idx_count = 0;
JSValue make_quad_indices_buffer(JSContext *js, int quads)
@@ -1828,7 +1821,7 @@ JSValue js_math_dot(JSContext *js, JSValue self, int argc, JSValue *argv) {
free(a);
free(b);
return number2js(js,dot);
};
}
JSValue js_math_project(JSContext *js, JSValue self, int argc, JSValue *argv) {
size_t alen, blen;
@@ -2572,10 +2565,10 @@ JSC_CCALL(os_engine_start,
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_URL_STRING, url)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_TYPE_STRING, type)
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_CAMERA) < 0)
return JS_ThrowReferenceError(js, "Couldn't initialize SDL: %s\n", SDL_GetError());
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_CAMERA))
return JS_ThrowReferenceError(js, "Couldn't initialize SDL: %s\n", SDL_GetError());
char *title;
const char *title;
JS_GETPROP(js,title,argv[0],title,cstring)
SDL_Window *new = SDL_CreateWindow(title, js2number(js, js_getproperty(js,argv[0], width_atom)), js2number(js,js_getproperty(js,argv[0], height_atom)), SDL_WINDOW_RESIZABLE);
@@ -3533,7 +3526,6 @@ JSC_CCALL(renderer_make_sprite_mesh,
for (int i = 0; i < quads; i++) {
JSValue sub = JS_GetPropertyUint32(js,sprites,i);
JSValue jstransform = JS_GetProperty(js,sub,transform_atom);
transform *tr = js2transform(js,jstransform);
JSValue jssrc = JS_GetProperty(js,sub,src_atom);
JSValue jscolor = JS_GetProperty(js,sub,color_atom);
@@ -3553,23 +3545,11 @@ JSC_CCALL(renderer_make_sprite_mesh,
// Calculate the base index for the current quad
size_t base = i * 4;
// HMM_Mat3 trmat = transform2mat3_global(tr);
HMM_Vec3 base_quad[4] = {
{0.0,0.0,1.0},
{1.0,0.0,1.0},
{0.0,1.0,1.0},
{1.0,1.0,1.0}
};
// for (int j = 0; j < 4; j++)
// posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy;
// Define the UV coordinates based on the source rectangle
uvdata[base + 0] = (HMM_Vec2){ src.x, src.y + src.h };
uvdata[base + 1] = (HMM_Vec2){ src.x + src.w, src.y + src.h };
uvdata[base + 2] = (HMM_Vec2){ src.x, src.y };
uvdata[base + 3] = (HMM_Vec2){ src.x + src.w, src.y };
uvdata[base + 0] = (HMM_Vec2){ src.x, src.y + src.h };
uvdata[base + 1] = (HMM_Vec2){ src.x + src.w, src.y + src.h };
uvdata[base + 2] = (HMM_Vec2){ src.x, src.y };
uvdata[base + 3] = (HMM_Vec2){ src.x + src.w, src.y };
colordata[base] = color;
colordata[base+1] = color;
@@ -4156,18 +4136,12 @@ JSC_CCALL(gpu_texture,
return jstex;
)
static HMM_Vec3 base_quad[4] = {
{0.0,0.0,1.0},
{1.0,0.0,1.0},
{0.0,1.0,1.0},
{1.0,1.0,1.0}
};
static HMM_Vec4 base_quad_4[4] = {
{ 0.0,0.0, 1.0f, 1.0f },
{ 1,0,0.0, 1.0f, 1.0f },
{ 0.0,1.0, 1.0f, 1.0f },
{ 1.0,1.0, 1.0f, 1.0f }
};
static HMM_Vec3 base_quad[4] = {
{0.0,0.0,1.0},
{1.0,0.0,1.0},
{0.0,1.0,1.0},
{1.0,1.0,1.0}
};
static inline void add_quad(text_vert **verts, rect *restrict src, rect *restrict dst)
{
@@ -4644,7 +4618,6 @@ JSC_CCALL(gpu_acquire_cmd_buffer,
2: an optional transfer buffer to use; if undefined a temporary one is used
*/
JSC_CCALL(gpu_upload,
Uint64 ss = SDL_GetTicksNS();
JSValue js_cmd = argv[0];
JSValue js_buffers = argv[1];
JSValue js_transfer = argv[2];
@@ -4724,16 +4697,14 @@ JSC_CCALL(gpu_upload,
return JS_ThrowReferenceError(js, "Failed to map transfer buffer: %s", SDL_GetError());
}
Uint64 sy = SDL_GetTicksNS();
// Copy all data into the mapped transfer buffer
size_t current_offset = 0;
for (size_t i = 0; i < len; i++) {
memcpy(mapped_data + current_offset, items[i].data, items[i].size);
memcpy((char*)mapped_data + current_offset, items[i].data, items[i].size);
current_offset += items[i].size;
}
SDL_UnmapGPUTransferBuffer(gpu, transfer);
// Issue uploads for each item
current_offset = 0;
for (size_t i = 0; i < len; i++) {
@@ -5120,14 +5091,6 @@ JSC_CCALL(gpu_tile,
arrfree(verts);
)
static const JSCFunctionListEntry js_SDL_GPUCopyPass_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUFence_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUTransferBuffer_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUShader_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUSampler_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUGraphicsPipeline_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUComputePipeline_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = {
MIST_FUNC_DEF(gpu, claim_window, 1),
MIST_FUNC_DEF(gpu, make_pipeline, 1), // loads pipeline state into an object
@@ -5707,8 +5670,6 @@ static const JSCFunctionListEntry js_SDL_Camera_funcs[] =
MIST_FUNC_DEF(camera, release_frame, 1),
};
static const JSCFunctionListEntry js_SDL_Cursor_funcs[] = {};
JSC_CCALL(texture_mode,
SDL_Texture *tex = js2SDL_Texture(js,self);
SDL_SetTextureScaleMode(tex,js2number(js,argv[0]));
@@ -5832,42 +5793,14 @@ static const JSCFunctionListEntry js_console_funcs[] = {
MIST_FUNC_DEF(console,print,1),
};
JSC_CCALL(profile_gather_rate,
JS_SetInterruptRate(js2number(js,argv[0]));
)
JSC_CCALL(profile_gather_stop,
JS_SetInterruptHandler(JS_GetRuntime(js),NULL,NULL);
)
JSC_CCALL(profile_best_t,
char* result[50];
double seconds = js2number(js,argv[0]);
if (seconds < 1e-6)
snprintf(result, 50, "%.2f ns", seconds * 1e9);
else if (seconds < 1e-3)
snprintf(result, 50, "%.2f µs", seconds * 1e6);
else if (seconds < 1)
snprintf(result, 50, "%.2f ms", seconds * 1e3);
else
snprintf(result, 50, "%.2f s", seconds);
return JS_NewString(js,result);
)
static const JSCFunctionListEntry js_profile_funcs[] = {
MIST_FUNC_DEF(profile,best_t, 1),
MIST_FUNC_DEF(profile,gather_rate,1),
MIST_FUNC_DEF(profile,gather_stop,0),
};
JSC_CCALL(debug_stack_depth, return number2js(js,js_debugger_stack_depth(js)))
JSC_CCALL(debug_build_backtrace, return js_debugger_build_backtrace(js,NULL))
JSC_CCALL(debug_closure_vars, return js_debugger_closure_variables(js,argv[0]))
JSC_CCALL(debug_local_vars, return js_debugger_local_variables(js, js2number(js,argv[0])))
JSC_CCALL(debug_fn_info, return js_debugger_fn_info(js, argv[0]));
JSC_CCALL(debug_backtrace_fns, return js_debugger_backtrace_fns(js,NULL));
JSC_CCALL(debug_dump_obj, return js_dump_value(js, argv[0]));
JSC_CCALL(debug_fn_info, return js_debugger_fn_info(js, argv[0]))
JSC_CCALL(debug_backtrace_fns, return js_debugger_backtrace_fns(js,NULL))
JSC_CCALL(debug_dump_obj, return js_dump_value(js, argv[0]))
static const JSCFunctionListEntry js_debug_funcs[] = {
MIST_FUNC_DEF(debug, stack_depth, 0),
MIST_FUNC_DEF(debug, build_backtrace, 0),
@@ -5932,22 +5865,33 @@ JSC_SCALL(io_slurp,
END:
)
size_t js_physfs_write(JSContext *js, PHYSFS_File *f, JSValue val)
{
size_t len;
size_t wrote;
if (JS_IsString(val)) {
const char *data = JS_ToCStringLen(js,&len,val);
wrote = PHYSFS_writeBytes(f,data,len);
JS_FreeCString(js,data);
} else {
unsigned char *data = JS_GetArrayBuffer(js,&len,val);
wrote = PHYSFS_writeBytes(f,data,len);
}
if (wrote < len) wrote = -1;
return wrote;
}
JSC_SCALL(io_slurpwrite,
PHYSFS_File *f = PHYSFS_openWrite(str);
if (!f) {
ret = JS_ThrowReferenceError(js,"could not write to %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
goto END;
}
size_t len;
unsigned char *data;
if (JS_IsString(argv[1]))
data = JS_ToCStringLen(js,&len,argv[1]);
else
data = JS_GetArrayBuffer(js,&len, argv[1]);
size_t wrote = js_physfs_write(js,f,argv[1]);
size_t wrote = PHYSFS_writeBytes(f,data, len);
PHYSFS_close(f);
if (wrote == -1 || wrote < len)
if (wrote == -1)
ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
END:
@@ -5989,10 +5933,11 @@ int globfs_cb(struct globdata *data, char *dir, char *file)
}
char **glob = data->globs;
while (*glob != NULL) {
if (wildmatch(*glob, path, WM_WILDSTAR) == WM_MATCH)
goto END;
*glob++;
glob++;
}
PHYSFS_Stat stat;
@@ -6023,18 +5968,18 @@ JSC_CCALL(io_globfs,
data.arr = ret;
data.idx = 0;
int globs_len = js_arrlen(js,argv[0]);
char *globs[globs_len+1];
const char *globs[globs_len+1];
for (int i = 0; i < globs_len; i++) {
JSValue g = JS_GetPropertyUint32(js,argv[0],i);
globs[i] = JS_ToCString(js,g);
JS_FreeValue(js,g);
}
globs[globs_len] = NULL;
data.globs = globs;
const char *path = NULL;
if (!JS_IsUndefined(argv[1])) path = JS_ToCString(js,argv[1]);
printf("LOOKING INTO %s\n", path);
PHYSFS_enumerate(path, globfs_cb, &data);
for (int i = 0; i < globs_len; i++)
@@ -6100,7 +6045,7 @@ JSC_SCALL(io_enumerate,
)
JSC_CCALL(io_basedir, return JS_NewString(js,PHYSFS_getBaseDir()))
JSC_CCALL(io_userdir, return JS_NewString(js,PHYSFS_getUserDir()))
JSC_SSCALL(io_prefdir, return JS_NewString(js,PHYSFS_getPrefDir(str, str2)))
JSC_SCALL(io_open,
PHYSFS_File *f = PHYSFS_openWrite(str);
@@ -6158,7 +6103,7 @@ static const JSCFunctionListEntry js_io_funcs[] = {
MIST_FUNC_DEF(io,slurpwrite,2),
MIST_FUNC_DEF(io,writepath, 1),
MIST_FUNC_DEF(io,basedir, 0),
MIST_FUNC_DEF(io, userdir, 0),
MIST_FUNC_DEF(io, prefdir, 2),
MIST_FUNC_DEF(io, realdir, 1),
MIST_FUNC_DEF(io, open, 1),
MIST_FUNC_DEF(io, searchpath, 0),
@@ -6174,15 +6119,8 @@ JSC_CCALL(file_close,
JSC_CCALL(file_write,
PHYSFS_File *f = js2PHYSFS_File(js,self);
size_t len;
unsigned char *data;
if (JS_IsString(argv[0]))
data = JS_ToCStringLen(js,&len,argv[0]);
else
data = JS_GetArrayBuffer(js,&len, argv[0]);
size_t wrote = PHYSFS_writeBytes(f,data,len);
if (wrote == -1 || wrote < len)
size_t wrote = js_physfs_write(js,f,argv[0]);
if (wrote == -1)
return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
@@ -6309,6 +6247,7 @@ static JSValue js_transform_set_change_hook(JSContext *js, JSValueConst self, JS
if (!JS_IsUndefined(v) && !JS_IsFunction(js,v)) return JS_ThrowReferenceError(js, "Hook must be a function.");
JS_FreeValue(js,t->change_hook);
t->change_hook = JS_DupValue(js,v);
return JS_UNDEFINED;
}
static JSValue js_transform_get_parent(JSContext *js, JSValueConst self)
@@ -6443,33 +6382,6 @@ static const JSCFunctionListEntry js_font_funcs[] = {
MIST_FUNC_DEF(font, text_size, 3),
};
const char *STRTEST = "TEST STRING";
JSC_CCALL(performance_barecall,)
JSC_CCALL(performance_unpack_array,
void *v = js2cpvec2arr(js,argv[0]);
arrfree(v);
)
JSC_CCALL(performance_pack_num, return number2js(js,1.0))
JSC_CCALL(performance_pack_string, return JS_NewStringLen(js, STRTEST, sizeof(*STRTEST)))
JSC_CCALL(performance_unpack_string, JS_ToCString(js, argv[0]))
JSC_CCALL(performance_call_fn_n,
for (int i = 0; i < js2number(js,argv[1]); i++) {
JSValue r = JS_Call(js, argv[0], JS_UNDEFINED, 0, NULL);
uncaught_exception(js,r);
}
uncaught_exception(js,JS_Call(js,argv[2], JS_UNDEFINED, 0, NULL));
)
static const JSCFunctionListEntry js_performance_funcs[] = {
MIST_FUNC_DEF(performance, barecall,0),
MIST_FUNC_DEF(performance, unpack_array, 1),
MIST_FUNC_DEF(performance, pack_num, 0),
MIST_FUNC_DEF(performance, pack_string, 0),
MIST_FUNC_DEF(performance, unpack_string, 1),
MIST_FUNC_DEF(performance, call_fn_n, 3)
};
JSC_CCALL(geometry_rect_intersection,
rect a = js2rect(js,argv[0]);
rect b = js2rect(js,argv[1]);
@@ -6590,17 +6502,6 @@ JSC_CCALL(os_gc_threshold, JS_SetGCThreshold(JS_GetRuntime(js), js2number(js,arg
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(JS_GetRuntime(js), js2number(js,argv[0])))
JSC_CCALL(os_rt_info, return JS_GetRTInfo(JS_GetRuntime(js),js))
static JSValue tmp2js(JSContext *js,FILE *tmp)
{
size_t size = ftell(tmp);
rewind(tmp);
char *buffer = calloc(size+1, sizeof(char));
fread(buffer, sizeof(char),size, tmp);
JSValue ret = JS_NewString(js,buffer);
free(buffer);
return ret;
}
JSC_CCALL(os_dump_atoms,
return js_dump_atoms(js);
)
@@ -6641,8 +6542,8 @@ JSC_CCALL(os_mallinfo,
JSJMEMRET(keepcost);*/
)
JSC_CCALL(os_rusage,
ret = JS_NewObject(js);
JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) {
JSValue ret = JS_NewObject(js);
#ifndef _WIN32
struct rusage jsmem;
@@ -6662,7 +6563,9 @@ JSC_CCALL(os_rusage,
JSJMEMRET(ru_nvcsw);
JSJMEMRET(ru_nivcsw);
#endif
)
return ret;
}
JSC_CCALL(os_mem, return js_get_memory_usage(js))
JSC_CCALL(os_value_id,
@@ -7135,7 +7038,7 @@ JSC_CCALL(os_hostname,
return JS_NewString(js,"");
)
JSC_CCALL(os_freemem,
JSValue js_os_freemem(JSContext *js, JSValue self, int argc, JSValue *argv) {
#ifdef _WIN32
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
@@ -7158,9 +7061,9 @@ JSC_CCALL(os_freemem,
// Fallback: unknown
return JS_NewInt64(js,0);
#endif
)
}
JSC_CCALL(os_arch,
JSValue js_os_arch(JSContext *js, JSValue self, int argc, JSValue *argv) {
#if defined(__x86_64__) || defined(_M_X64)
return JS_NewString(js,"x64");
#elif defined(__aarch64__) || defined(_M_ARM64)
@@ -7184,9 +7087,9 @@ JSC_CCALL(os_arch,
#else
return JS_NewString(js,"unknown");
#endif
)
}
JSC_CCALL(os_version,
JSValue js_os_version(JSContext *js, JSValue self, int argc, JSValue *argv) {
#ifdef _WIN32
typedef LONG (WINAPI *RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
HMODULE h = GetModuleHandleA("ntdll.dll");
@@ -7225,7 +7128,9 @@ JSC_CCALL(os_version,
if (!uname(&info)) return JS_NewString(js, info.release);
return JS_NewString(js, "");
#endif
)
return JS_UNDEFINED;
}
static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, make_transform, 0),
@@ -7269,7 +7174,7 @@ JSC_CCALL(js_dump_class, return js_get_object_class_distribution(js))
JSC_CCALL(js_dump_type_overheads, return js_get_object_type_overheads(js))
JSC_CCALL(js_dump_objects, return js_dump_objects(js))
JSValue cycle_fn = JS_UNDEFINED;
static JSValue cycle_fn;
void cycle_hook_call(JSContext *js, JSValue v)
{
@@ -7336,7 +7241,7 @@ static const JSCFunctionListEntry js_video_funcs[] = {
void gui_input(SDL_Event *e);
// Polls and handles all input events
JSC_CCALL(os_engine_input,
JSValue js_os_engine_input(JSContext *js, JSValue self, int argc, JSValue *argv) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
#ifndef NEDITOR
@@ -7346,7 +7251,8 @@ JSC_CCALL(os_engine_input,
JSValue ret = JS_Call(js,argv[0], JS_UNDEFINED, 1, &e);
uncaught_exception(js,ret);
}
)
return JS_UNDEFINED;
}
JSC_CCALL(os_push_event,
SDL_UserEvent e;
@@ -7620,7 +7526,6 @@ JS_SetPropertyFunctionList(js, js_##NAME, js_##NAME##_funcs, countof(js_##NAME##
JS_SetPrototype(js, js_##NAME, PARENT); \
JSValue js_layout_use(JSContext *js);
JSValue js_soloud_use(JSContext *js);
JSValue js_miniz_use(JSContext *js);
#ifdef TRACY_ENABLE
@@ -7671,9 +7576,16 @@ MISTUSE(graphics)
MISTUSE(util)
MISTUSE(video)
MISTUSE(event)
#ifndef NEDITOR
MISTUSE(camera)
MISTUSE(debug)
JSValue js_imgui_use(JSContext *js);
#endif
#include "qjs_dmon.h"
#include "qjs_nota.h"
#include "qjs_enet.h"
#include "qjs_soloud.h"
#include "qjs_chipmunk.h"
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
@@ -7691,11 +7603,15 @@ void ffi_load(JSContext *js, int argc, char **argv) {
arrput(module_registry, MISTLINE(video));
arrput(module_registry, MISTLINE(event));
arrput(module_registry, MISTLINE(soloud));
arrput(module_registry,MISTLINE(layout));
arrput(module_registry, MISTLINE(layout));
arrput(module_registry, MISTLINE(miniz));
#ifndef NEDITOR
arrput(module_registry, MISTLINE(imgui));
#endif
arrput(module_registry, MISTLINE(camera));
arrput(module_registry, MISTLINE(debug));
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));
@@ -7714,25 +7630,23 @@ void ffi_load(JSContext *js, int argc, char **argv) {
QJSCLASSPREP_FUNCS(SDL_Texture)
QJSCLASSPREP_FUNCS(SDL_Renderer)
QJSCLASSPREP_FUNCS(SDL_Camera)
QJSCLASSPREP_FUNCS(SDL_Cursor)
QJSCLASSPREP_FUNCS(SDL_GPUDevice)
QJSCLASSPREP_FUNCS(SDL_GPUTexture)
QJSCLASSPREP_FUNCS(SDL_GPUCommandBuffer)
QJSCLASSPREP_FUNCS(SDL_GPURenderPass)
QJSCLASSPREP_FUNCS(SDL_GPUComputePass)
QJSCLASSPREP_FUNCS(SDL_GPUCopyPass)
QJSCLASSPREP_FUNCS(SDL_GPUFence)
QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer)
QJSCLASSPREP_FUNCS(SDL_GPUShader)
QJSCLASSPREP_FUNCS(SDL_GPUSampler)
QJSCLASSPREP_FUNCS(SDL_GPUGraphicsPipeline)
QJSCLASSPREP_FUNCS(SDL_GPUComputePipeline)
QJSCLASSPREP_NO_FUNCS(SDL_Cursor)
QJSCLASSPREP_NO_FUNCS(SDL_GPUCopyPass)
QJSCLASSPREP_NO_FUNCS(SDL_GPUFence)
QJSCLASSPREP_NO_FUNCS(SDL_GPUTransferBuffer)
QJSCLASSPREP_NO_FUNCS(SDL_GPUShader)
QJSCLASSPREP_NO_FUNCS(SDL_GPUSampler)
QJSCLASSPREP_NO_FUNCS(SDL_GPUGraphicsPipeline)
QJSCLASSPREP_NO_FUNCS(SDL_GPUComputePipeline)
QJSCLASSPREP_FUNCS(sprite)
// QJSCLASSPREP_FUNCS(SDL_GPUGraphicsPipeline)
// QJSCLASSPREP_FUNCS(SDL_GPUSampler)
// QJSCLASSPREP_FUNCS(SDL_GPUShader)
QJSCLASSPREP_FUNCS(SDL_GPUBuffer)
// QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer)
QJSCLASSPREP_FUNCS(PHYSFS_File)
QJSCLASSPREP_FUNCS(transform);
QJSCLASSPREP_FUNCS(font);
@@ -7874,5 +7788,8 @@ void ffi_load(JSContext *js, int argc, char **argv) {
JS_SetPropertyStr(js,globalThis,"prosperon", prosp);
idx_buffer = JS_UNDEFINED;
cycle_fn = JS_UNDEFINED;
JS_FreeValue(js,globalThis);
}

123
source/kim.h Executable file
View File

@@ -0,0 +1,123 @@
#ifndef KIM_H
#define KIM_H
// write number of runes from a kim stream int a utf8 stream
void utf8_to_kim(const char **utf, char **kim, long long *runeout);
// write number of runes from a kim stream int a utf8 stream
void kim_to_utf8(char **kim, char **utf, int runes);
// Return the number of runes in a utf8 string
int utf8_count(const char *utf8);
#ifdef KIM_IMPLEMENTATION
#define KIM_CONT 0x80
#define KIM_DATA 0x7f
#define CONTINUE(CHAR) (CHAR>>7)
int decode_utf8(char **s);
void encode_utf8(char **s, int code);
static void encode_kim(char **s, int code);
int decode_kim(char **s);
static inline int utf8_bytes(char c)
{
int bytes = __builtin_clz(~(c));
if (!bytes) return 1;
return bytes-24;
}
int utf8_count(const char *utf8)
{
int count = 0;
while(*utf8) {
count++;
utf8 += utf8_bytes(*utf8);
}
return count;
}
// decode and advance s, returning the character rune
int decode_utf8(char **s) {
int k = **s ? __builtin_clz(~(**s << 24)) : 0; // Count # of leading 1 bits.
int mask = (1 << (8 - k)) - 1; // All 1's with k leading 0's.
int value = **s & mask;
for (++(*s), --k; k > 0 && **s; --k, ++(*s)) { // Note that k = #total bytes, or 0.
value <<= 6;
value += (**s & 0x3F);
}
return value;
}
// Write and advance s with rune in utf-8
void encode_utf8(char **s, int rune) {
char val[4];
int lead_byte_max = 0x7F;
int val_index = 0;
while (rune > lead_byte_max) {
val[val_index++] = (rune & 0x3F) | 0x80;
rune >>= 6;
lead_byte_max >>= (val_index == 1 ? 2 : 1);
}
val[val_index++] = (rune & lead_byte_max) | (~lead_byte_max << 1);
while (val_index--) {
**s = val[val_index];
(*s)++;
}
}
// write and advance s with rune in kim
static inline void encode_kim(char **s, int rune)
{
if (rune < KIM_CONT) {
**s = 0 | (KIM_DATA & rune);
(*s)++;
return;
}
int bits = ((32 - __builtin_clz(rune) + 6) / 7) * 7;
while (bits > 7) {
bits -= 7;
**s = KIM_CONT | (KIM_DATA & (rune >> bits));
(*s)++;
}
**s = KIM_DATA & rune;
(*s)++;
}
// decode and advance s, returning the character rune
int decode_kim(char **s)
{
int rune = **s & KIM_DATA;
while (CONTINUE(**s)) {
rune <<= 7;
(*s)++;
rune |= **s & KIM_DATA;
}
(*s)++;
return rune;
}
void utf8_to_kim(const char **utf, char **kim, long long *runeout)
{
const char * str = *utf;
long long runes = 0;
while (*str) {
runes++;
encode_kim(kim, decode_utf8(&str));
}
if (runeout) *runeout = runes;
}
void kim_to_utf8(char **kim, char **utf, int runes)
{
for (int i = 0; i < runes; i++)
encode_utf8(utf, decode_kim(kim));
}
#endif
#endif

View File

@@ -1,7 +1,6 @@
#include "model.h"
#include "stb_ds.h"
#include "gameobject.h"
#include "render.h"
@@ -18,59 +17,19 @@
#include "jsffi.h"
unsigned short pack_short_tex(float c) { return c * USHRT_MAX; }
SDL_GPUBuffer *texcoord_floats(float *f, int n)
{
unsigned short packed[n];
for (int i = 0; i < n; i++) {
float v = f[i];
if (v < 0) v = 0;
if (v > 1) v = 1;
packed[i] = pack_short_tex(v);
}
/* return sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(packed),
.label = "tex coord vert buffer",
});*/
return NULL;
}
SDL_GPUBuffer *par_idx_buffer(uint32_t *p, int v)
{
uint16_t idx[v];
for (int i = 0; i < v; i++) idx[i] = p[i];
/* return sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(idx),
.type = SG_BUFFERTYPE_INDEXBUFFER
});*/
return NULL;
}
SDL_GPUBuffer *float_buffer(float *f, int v)
{
return NULL;
/* return sg_make_buffer(&(sg_buffer_desc){
.data = (sg_range){
.ptr = f,
.size = sizeof(*f)*v
}
});*/
}
SDL_GPUBuffer *index_buffer(float *f, int verts)
{
return NULL;
/* uint16_t idxs[verts];
for (int i = 0; i < verts; i++)
idxs[i] = f[i];
return sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(idxs),
.type = SG_BUFFERTYPE_INDEXBUFFER,
});*/
}
uint32_t pack_int10_n2(float *norm)
@@ -87,71 +46,21 @@ uint32_t pack_int10_n2(float *norm)
SDL_GPUBuffer *normal_floats(float *f, int n)
{
return float_buffer(f, n);
/* uint32_t packed_norms[n/3];
for (int v = 0, i = 0; v < n/3; v++, i+= 3)
packed_norms[v] = pack_int10_n2(f+i);
return sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(packed_norms),
.label = "normal vert buffer",
});*/
}
SDL_GPUBuffer *ubyten_buffer(float *f, int v)
{
return NULL;
/* unsigned char b[v];
for (int i = 0; i < (v); i++)
b[i] = f[i]*255;
return sg_make_buffer(&(sg_buffer_desc){.data=SG_RANGE(b)});*/
}
SDL_GPUBuffer *ubyte_buffer(float *f, int v)
{
return NULL;
/* unsigned char b[v];
for (int i = 0; i < (v); i++)
b[i] = f[i];
return sg_make_buffer(&(sg_buffer_desc){.data=SG_RANGE(b)});
*/
}
SDL_GPUBuffer *accessor2buffer(cgltf_accessor *a, int type)
{
return NULL;
/* int n = cgltf_accessor_unpack_floats(a, NULL, 0);
float vs[n];
cgltf_accessor_unpack_floats(a, vs, n);
switch(type) {
case MAT_POS:
return sg_make_buffer(&(sg_buffer_desc){
.data.ptr = vs,
.data.size = sizeof(float)*n
});
case MAT_NORM:
return normal_floats(vs,n);
case MAT_TAN:
return normal_floats(vs,n); // TODO: MAKE A TANGENT READER
case MAT_COLOR:
return ubyten_buffer(vs,n);
case MAT_WEIGHT:
return ubyten_buffer(vs,n);
case MAT_BONE:
return ubyte_buffer(vs,n);
case MAT_UV:
return texcoord_floats(vs,n);
case MAT_INDEX:
return index_buffer(vs,n);
}
return sg_make_buffer(&(sg_buffer_desc) {
.data.size = 4,
.usage = SG_USAGE_STREAM
});
*/
}
void packFloats(float *src, float *dest, int srcLength) {

View File

@@ -3,7 +3,6 @@
#include "HandmadeMath.h"
#include "transform.h"
#include "gameobject.h"
#include "anim.h"
#include "cgltf.h"

461
source/nota.h Executable file
View File

@@ -0,0 +1,461 @@
#ifndef NOTA_H
#define NOTA_H
#include <stddef.h>
#include <stdint.h>
/* Nota type nibble values */
#define NOTA_BLOB 0x00
#define NOTA_TEXT 0x10
#define NOTA_ARR 0x20
#define NOTA_REC 0x30
#define NOTA_FLOAT 0x40
#define NOTA_INT 0x60
#define NOTA_SYM 0x70
#define NOTA_NULL 0x00
#define NOTA_FALSE 0x02
#define NOTA_TRUE 0x03
#define NOTA_INF 0x03
#define NOTA_PRIVATE 0x08
#define NOTA_SYSTEM 0x09
/* Some internal constants/macros (used in varint logic, etc.) */
#define NOTA_CONT 0x80
#define NOTA_DATA 0x7f
#define NOTA_INT_DATA 0x07
#define NOTA_INT_SIGN(CHAR) (CHAR & (1<<3))
#define NOTA_SIG_SIGN(CHAR) (CHAR & (1<<3))
#define NOTA_EXP_SIGN(CHAR) (CHAR & (1<<4))
#define NOTA_TYPE 0x70
#define NOTA_HEAD_DATA 0x0f
#define CONTINUE(CHAR) ((CHAR)>>7)
#define UTF8_DATA 0x3f
/* A helper to get the high-level Nota type nibble from a byte */
static inline int nota_type(const char *nota) { return (*nota) & 0x70; }
char *nota_read_blob(long long *len, char **blob, char *nota);
char *nota_read_text(char **text, char *nota);
char *nota_read_array(long long *len, char *nota);
char *nota_read_record(long long *len, char *nota);
char *nota_read_float(double *d, char *nota);
char *nota_read_int(long long *n, char *nota);
char *nota_read_sym(int *sym, char *nota);
typedef struct NotaBuffer {
char *data;
size_t size; /* number of bytes used */
size_t capacity; /* allocated size of data */
} NotaBuffer;
/* Initialize a NotaBuffer with a given initial capacity. */
void nota_buffer_init(NotaBuffer *nb, size_t initial_capacity);
/* Free the buffer's internal memory. (Does NOT free nb itself.) */
void nota_buffer_free(NotaBuffer *nb);
void nota_write_blob (NotaBuffer *nb, unsigned long long nbits, const char *data);
void nota_write_text (NotaBuffer *nb, const char *s);
void nota_write_array (NotaBuffer *nb, unsigned long long count);
void nota_write_record(NotaBuffer *nb, unsigned long long count);
void nota_write_number(NotaBuffer *nb, double n);
void nota_write_sym (NotaBuffer *nb, int sym);
#ifdef NOTA_IMPLEMENTATION
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>
#include "kim.h"
/* -------------------------------------------------------
HELPER: skip a varint
------------------------------------------------------- */
static inline char *nota_skip(char *nota)
{
while (CONTINUE(*nota)) {
nota++;
}
return nota + 1;
}
/* -------------------------------------------------------
HELPER: read a varint
------------------------------------------------------- */
char *nota_read_num(long long *n, char *nota)
{
if (!n) {
return nota_skip(nota);
}
unsigned char b = (unsigned char)*nota;
long long result = b & NOTA_HEAD_DATA;
nota++;
while (b & NOTA_CONT) {
b = (unsigned char)*nota++;
result = (result << 7) | (b & NOTA_DATA);
}
*n = result;
return nota;
}
/* Count how many bits of varint we need to encode n,
with sb “special bits” in the first byte. */
static inline int nota_bits(long long n, int sb)
{
if (n == 0) return sb;
int bits = (sizeof(n)*CHAR_BIT) - __builtin_clzll(n);
bits -= sb;
int needed = ((bits + 6) / 7)*7 + sb;
return needed;
}
/* Write a varint into *nota, with sb bits in the first char (which is already set). */
static inline char *nota_continue_num(long long n, char *nota, int sb)
{
int bits = nota_bits(n, sb);
bits -= sb;
if (bits > 0)
nota[0] |= NOTA_CONT;
else
nota[0] &= ~NOTA_CONT;
int shex = (~0) << sb;
nota[0] &= shex; /* clear sb bits */
nota[0] |= (~shex) & ((unsigned long long)n >> bits);
int i = 1;
while (bits > 0) {
bits -= 7;
int head = (bits == 0) ? 0 : NOTA_CONT;
nota[i] = head | (NOTA_DATA & (n >> bits));
i++;
}
return &nota[i];
}
char *nota_read_blob(long long *len, char **blob, char *nota)
{
if (!len) return nota;
nota = nota_read_num(len, nota);
int bytes = (int)floor((*len + 7) / 8.0);
*len = bytes;
*blob = (char *)malloc(bytes);
memcpy(*blob, nota, bytes);
return nota + bytes;
}
char *nota_read_text(char **text, char *nota)
{
long long chars;
nota = nota_read_num(&chars, nota);
char utf[chars*4 + 1]; /* enough for wide chars + null */
char *pp = utf;
kim_to_utf8(&nota, &pp, chars);
*pp = 0;
*text = strdup(utf);
return nota;
}
char *nota_read_array(long long *len, char *nota)
{
if (!len) return nota;
return nota_read_num(len, nota);
}
char *nota_read_record(long long *len, char *nota)
{
if (!len) return nota;
return nota_read_num(len, nota);
}
char *nota_read_float(double *d, char *nota)
{
if (!d) {
return nota_skip(nota);
}
int neg = NOTA_SIG_SIGN(*nota);
int esign = NOTA_EXP_SIGN(*nota);
long long e = (*nota) & NOTA_INT_DATA;
while (CONTINUE(*nota)) {
nota++;
e = (e << 7) | ((*nota) & NOTA_DATA);
}
nota++;
long long sig = (*nota) & NOTA_DATA;
while (CONTINUE(*nota)) {
nota++;
sig = (sig << 7) | ((*nota) & NOTA_DATA);
}
nota++;
if (neg) sig = -sig;
if (esign) e = -e;
*d = (double)sig * pow(10.0, (double)e);
return nota;
}
char *nota_read_int(long long *n, char *nota)
{
if (!n) return nota_skip(nota);
*n = 0;
char *c = nota;
*n |= (*c) & NOTA_INT_DATA;
while (CONTINUE(*(c++))) {
*n = (*n << 7) | (*c & NOTA_DATA);
}
/* if sign bit is set in the first byte, negative. */
if (NOTA_INT_SIGN(*nota)) *n = -*n;
return c;
}
char *nota_read_sym(int *sym, char *nota)
{
if (sym) *sym = ((*nota) & 0x0f);
return nota + 1;
}
static void nota_buffer_grow(NotaBuffer *nb, size_t min_add)
{
size_t needed = nb->size + min_add;
if (needed <= nb->capacity) return;
size_t new_cap = (nb->capacity == 0 ? 64 : nb->capacity * 2);
while (new_cap < needed) {
new_cap *= 2;
}
char *new_data = (char *)realloc(nb->data, new_cap);
if (!new_data) {
fprintf(stderr, "realloc failed in nota_buffer_grow\n");
abort();
}
nb->data = new_data;
nb->capacity = new_cap;
}
void nota_buffer_init(NotaBuffer *nb, size_t initial_capacity)
{
nb->data = NULL;
nb->size = 0;
nb->capacity = 0;
if (initial_capacity > 0) {
nb->data = (char *)malloc(initial_capacity);
if (!nb->data) {
fprintf(stderr, "malloc failed in nota_buffer_init\n");
abort();
}
nb->capacity = initial_capacity;
}
}
void nota_buffer_free(NotaBuffer *nb)
{
if (nb->data) free(nb->data);
nb->data = NULL;
nb->size = 0;
nb->capacity = 0;
}
/* Allocate 'len' bytes in the buffer and return a pointer to them. */
static char *nota_buffer_alloc(NotaBuffer *nb, size_t len)
{
nota_buffer_grow(nb, len);
char *p = nb->data + nb->size;
nb->size += len;
return p;
}
static void nota_write_int_buf(NotaBuffer *nb, long long n);
static void nota_write_float_buf(NotaBuffer *nb, double d);
static void nota_write_int_or_float_buf(NotaBuffer *nb, double n)
{
if (n < (double)INT64_MIN || n > (double)INT64_MAX) {
nota_write_float_buf(nb, n);
return;
}
double ip;
double frac = modf(n, &ip);
if (fabs(frac) < 1e-14)
nota_write_int_buf(nb, (long long)ip);
else
nota_write_float_buf(nb, n);
}
void nota_write_sym(NotaBuffer *nb, int sym)
{
char *p = nota_buffer_alloc(nb, 1);
*p = NOTA_SYM | (sym & 0x0f);
}
void nota_write_blob(NotaBuffer *nb, unsigned long long nbits, const char *data)
{
unsigned long long bytes_len = (nbits + 7ULL) >> 3;
char *p = nota_buffer_alloc(nb, 1 + 10 + bytes_len);
p[0] = NOTA_BLOB;
char *end = nota_continue_num(nbits, p, 4);
size_t varint_used = (size_t)(end - p - 1);
memcpy(end, data, (size_t)bytes_len);
size_t total_used = 1 + varint_used + bytes_len;
size_t allocated = 1 + 10 + bytes_len;
nb->size -= (allocated - total_used);
}
void nota_write_text(NotaBuffer *nb, const char *s)
{
long long runes = utf8_count(s);
size_t max_kim = (size_t)(runes * 5);
char *p = nota_buffer_alloc(nb, 1 + 10 + max_kim);
p[0] = NOTA_TEXT;
char *end = nota_continue_num(runes, p, 4);
char *kim_out = end;
const char *utf_in = s;
while (*utf_in) {
int codepoint = decode_utf8((char **)&utf_in);
encode_kim(&kim_out, codepoint);
}
size_t used = (size_t)(kim_out - p);
size_t allocated = 1 + 10 + max_kim;
nb->size -= (allocated - used);
}
void nota_write_array(NotaBuffer *nb, unsigned long long count)
{
char *p = nota_buffer_alloc(nb, 10);
p[0] = NOTA_ARR;
char *end = nota_continue_num(count, p, 4);
size_t used = (size_t)(end - p);
nb->size -= (10 - used);
}
void nota_write_record(NotaBuffer *nb, unsigned long long count)
{
char *p = nota_buffer_alloc(nb, 10);
p[0] = NOTA_REC;
char *end = nota_continue_num(count, p, 4);
size_t used = (size_t)(end - p);
nb->size -= (10 - used);
}
void nota_write_number(NotaBuffer *nb, double n)
{
nota_write_int_or_float_buf(nb, n);
}
/* Write an integer in varint form (with sign bit) */
static void nota_write_int_buf(NotaBuffer *nb, long long n)
{
/* up to ~10 bytes for varint */
char *p = nota_buffer_alloc(nb, 10);
char sign = 0;
if (n < 0) {
sign = 0x08; /* sign bit in the nibble */
n = -n;
}
p[0] = NOTA_INT | sign;
char *end = nota_continue_num(n, p, 3);
size_t used = (size_t)(end - p);
nb->size -= (10 - used);
}
static void extract_mantissa_coefficient(double num, long *coefficient, long *exponent)
{
if (num == 0.0) {
*coefficient = 0;
*exponent = 0;
return;
}
/* Round to 12 decimal places to avoid floating artifacts. */
double rounded = floor(fabs(num) * 1e12 + 0.5) / 1e12;
if (num < 0) {
rounded = -rounded;
}
char buf[64];
snprintf(buf, sizeof(buf), "%.14g", rounded);
char *exp_pos = strpbrk(buf, "eE");
long exp_from_sci = 0;
if (exp_pos) {
exp_from_sci = atol(exp_pos + 1);
*exp_pos = '\0';
}
char *dec_point = strchr(buf, '.');
int digits_after_decimal = 0;
if (dec_point) {
digits_after_decimal = (int)strlen(dec_point + 1);
memmove(dec_point, dec_point + 1, strlen(dec_point));
}
long long coeff_ll = atoll(buf);
*coefficient = (long)coeff_ll;
*exponent = exp_from_sci - digits_after_decimal;
}
static void nota_write_float_buf(NotaBuffer *nb, double d)
{
if (d == 0.0) {
nota_write_int_buf(nb, 0);
return;
}
long coef, exp;
extract_mantissa_coefficient(d, &coef, &exp);
if (coef == 0) {
nota_write_int_buf(nb, 0);
return;
}
int neg = (d < 0.0);
if (exp == 0) {
nota_write_int_buf(nb, neg ? -coef : coef);
return;
}
char *p = nota_buffer_alloc(nb, 21);
p[0] = NOTA_FLOAT;
if (neg) p[0] |= (1 << 3);
if (exp < 0) {
p[0] |= (1 << 4);
exp = -exp;
}
char *c = nota_continue_num(exp, p, 3);
char *end = nota_continue_num(labs(coef), c, 7);
size_t used = (size_t)(end - p);
nb->size -= (21 - used);
}
#endif /* NOTA_IMPLEMENTATION */
#endif /* NOTA_H */

View File

@@ -96,7 +96,7 @@ int main(int argc, char **argv) {
prosperon = argv[0];
PHYSFS_init(argv[0]);
char *base = PHYSFS_getBaseDir();
const char *base = PHYSFS_getBaseDir();
PHYSFS_setWriteDir(base);
PHYSFS_mount(base, "/", 0);

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

150
source/qjs_dmon.c Normal file
View File

@@ -0,0 +1,150 @@
#include "quickjs.h"
#define DMON_IMPL
#include "dmon.h"
// Define the file event structure and completion queue
typedef struct {
dmon_action action;
char rootdir[256];
char filepath[256];
char oldfilepath[256];
} FileEvent;
typedef struct EventNode {
FileEvent event;
struct EventNode *next;
} EventNode;
typedef struct {
EventNode *head;
EventNode *tail;
} CompletionQueue;
CompletionQueue completionQueue = { NULL, NULL };
// Helper functions for the completion queue
void enqueue_event(FileEvent event) {
EventNode *node = malloc(sizeof(EventNode));
node->event = event;
node->next = NULL;
if (completionQueue.tail) {
completionQueue.tail->next = node;
} else {
completionQueue.head = node;
}
completionQueue.tail = node;
}
int dequeue_event(FileEvent *event) {
if (!completionQueue.head) {
return 0; // No event
}
EventNode *node = completionQueue.head;
*event = node->event;
completionQueue.head = node->next;
if (!completionQueue.head) {
completionQueue.tail = NULL;
}
free(node);
return 1;
}
void watch_cb(dmon_watch_id id, dmon_action action, const char *rootdir, const char *filepath, const char *oldfilepath, void *user)
{
FileEvent event;
event.action = action;
strncpy(event.rootdir, rootdir, sizeof(event.rootdir) - 1);
strncpy(event.filepath, filepath, sizeof(event.filepath) - 1);
if (oldfilepath) {
strncpy(event.oldfilepath, oldfilepath, sizeof(event.oldfilepath) - 1);
} else {
event.oldfilepath[0] = '\0';
}
enqueue_event(event); // Add event to completion queue
}
static dmon_watch_id watched = {0};
JSValue js_dmon_watch(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (watched.id)
return JS_ThrowReferenceError(js, "Already watching a directory.");
watched = dmon_watch(".", watch_cb, DMON_WATCHFLAGS_RECURSIVE, NULL);
return JS_UNDEFINED;
}
JSValue js_dmon_unwatch(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (!watched.id)
return JS_ThrowReferenceError(js, "Not watching a directory.");
dmon_unwatch(watched);
watched.id = 0;
return JS_UNDEFINED;
}
JSValue js_dmon_poll(JSContext *js, JSValueConst this_val, int argc, JSValueConst *argv) {
FileEvent event;
while (dequeue_event(&event)) {
if (!JS_IsFunction(js, argv[0])) continue;
JSValue jsevent = JS_NewObject(js);
JSValue action;
switch(event.action) {
case DMON_ACTION_CREATE:
action = JS_NewAtomString(js, "create");
break;
case DMON_ACTION_DELETE:
action = JS_NewAtomString(js, "delete");
break;
case DMON_ACTION_MODIFY:
action = JS_NewAtomString(js, "modify");
break;
case DMON_ACTION_MOVE:
action = JS_NewAtomString(js, "move");
break;
}
JS_SetPropertyStr(js, jsevent, "action", action);
JS_SetPropertyStr(js, jsevent, "root", JS_NewString(js, event.rootdir));
JS_SetPropertyStr(js, jsevent, "file", JS_NewString(js, event.filepath));
JS_SetPropertyStr(js, jsevent, "old", JS_NewString(js, event.oldfilepath));
JS_Call(js, argv[0], JS_UNDEFINED, 1, &jsevent);
}
return JS_UNDEFINED;
}
static const JSCFunctionListEntry js_dmon_funcs[] = {
JS_CFUNC_DEF("watch", 0, js_dmon_watch),
JS_CFUNC_DEF("unwatch", 0, js_dmon_unwatch),
JS_CFUNC_DEF("poll", 1, js_dmon_poll)
};
JSValue js_dmon_use(JSContext *js)
{
JSValue export = JS_NewObject(js);
JS_SetPropertyFunctionList(js, export, js_dmon_funcs, sizeof(js_dmon_funcs)/sizeof(JSCFunctionListEntry));
dmon_init();
return export;
}
static int js_dmon_init(JSContext *js, JSModuleDef *m) {
JS_SetModuleExport(js, m, "default",js_dmon_use(js));
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_dmon
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *js, const char *module_name) {
JSModuleDef *m = JS_NewCModule(js, module_name, js_dmon_init);
if (!m) return NULL;
JS_AddModuleExport(js, m, "default");
return m;
}

8
source/qjs_dmon.h Normal file
View File

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

500
source/qjs_enet.c Normal file
View File

@@ -0,0 +1,500 @@
// qjs_enet.c
#include "quickjs.h"
#include <enet/enet.h>
#include <stdio.h>
#include <string.h>
#define countof(a) (sizeof(a)/sizeof(*(a)))
static JSClassID enet_host_id;
static JSClassID enet_peer_class_id;
/* Finalizers */
static void js_enet_host_finalizer(JSRuntime *rt, JSValue val) {
ENetHost *host = JS_GetOpaque(val, enet_host_id);
if (host) {
enet_host_destroy(host);
}
}
static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val) {
ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
// No explicit cleanup needed for ENetPeer itself
(void)peer;
}
/* ENet init/deinit */
static JSValue js_enet_initialize(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (enet_initialize() != 0) {
return JS_ThrowInternalError(ctx, "Error initializing ENet.");
}
return JS_UNDEFINED;
}
static JSValue js_enet_deinitialize(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
enet_deinitialize();
return JS_UNDEFINED;
}
/* Host creation */
static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetHost *host;
ENetAddress address;
JSValue obj;
if (argc < 1) {
// Create client-like host, unbound
host = enet_host_create(NULL, 32, 2, 0, 0);
if (!host) {
return JS_ThrowInternalError(ctx, "Failed to create ENet host (null address).");
}
goto RET;
}
// If arg is provided, interpret as "ip:port" for server
const char *address_str = JS_ToCString(ctx, argv[0]);
if (!address_str) {
return JS_EXCEPTION; // memory or conversion error
}
char ip[64];
int port;
if (sscanf(address_str, "%63[^:]:%d", ip, &port) != 2) {
JS_FreeCString(ctx, address_str);
return JS_ThrowTypeError(ctx, "Invalid address format. Expected 'ip:port'.");
}
JS_FreeCString(ctx, address_str);
int err = enet_address_set_host_ip(&address, ip);
if (err != 0) {
return JS_ThrowInternalError(ctx, "Failed to set host IP from %s. Error %d.", ip, err);
}
address.port = port;
// Create server host with max 32 clients, 2 channels
host = enet_host_create(&address, 32, 2, 0, 0);
if (!host) {
return JS_ThrowInternalError(ctx, "Failed to create ENet host.");
}
RET:
obj = JS_NewObjectClass(ctx, enet_host_id);
if (JS_IsException(obj)) {
enet_host_destroy(host);
return obj;
}
JS_SetOpaque(obj, host);
return obj;
}
/* Host service: poll for events */
static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
if (!host) {
return JS_EXCEPTION;
}
// Expect a callback function
if (argc < 1 || !JS_IsFunction(ctx, argv[0])) {
return JS_ThrowTypeError(ctx, "Expected a callback function as first argument.");
}
JSValue callback = argv[0];
JS_DupValue(ctx, callback);
// Optional timeout
int timeout = 0;
if (argc > 1) {
JS_ToInt32(ctx, &timeout, argv[1]);
}
ENetEvent event;
while (enet_host_service(host, &event, timeout) > 0) {
JSValue event_obj = JS_NewObject(ctx);
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT: {
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "connect"));
JSValue peer_obj = JS_NewObjectClass(ctx, enet_peer_class_id);
if (JS_IsException(peer_obj)) {
JS_FreeValue(ctx, event_obj);
JS_FreeValue(ctx, callback);
return peer_obj;
}
JS_SetOpaque(peer_obj, event.peer);
JS_SetPropertyStr(ctx, event_obj, "peer", peer_obj);
break;
}
case ENET_EVENT_TYPE_RECEIVE: {
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "receive"));
JS_SetPropertyStr(ctx, event_obj, "channelID", JS_NewInt32(ctx, event.channelID));
char *tmp = js_mallocz(ctx, event.packet->dataLength+1);
memcpy(tmp, event.packet->data, event.packet->dataLength);
tmp[event.packet->dataLength] = '\0';
// We expect strictly a JSON object
JSValue packet_data = JS_ParseJSON(ctx,
tmp,
event.packet->dataLength,
"<enet-packet>");
js_free(ctx,tmp);
if (JS_IsException(packet_data)) {
// Malformed JSON -> throw error, abort
printf("INVALID JSON!\n");
enet_packet_destroy(event.packet);
JS_FreeValue(ctx, event_obj);
JS_FreeValue(ctx, callback);
return JS_ThrowTypeError(ctx, "Received invalid JSON (parse error).");
}
if (!JS_IsObject(packet_data)) {
// It might be a string/number/array/... -> we want only a plain object
JS_FreeValue(ctx, event_obj);
JS_FreeValue(ctx, callback);
JS_FreeValue(ctx, packet_data);
enet_packet_destroy(event.packet);
return JS_ThrowTypeError(ctx,
"Received data is not an object (must send a plain object).");
}
JS_SetPropertyStr(ctx, event_obj, "data", packet_data);
enet_packet_destroy(event.packet);
break;
}
case ENET_EVENT_TYPE_DISCONNECT:
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "disconnect"));
break;
case ENET_EVENT_TYPE_NONE:
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "none"));
break;
}
// Invoke callback
JS_Call(ctx, callback, JS_UNDEFINED, 1, &event_obj);
JS_FreeValue(ctx, event_obj);
}
JS_FreeValue(ctx, callback);
return JS_UNDEFINED;
}
/* Host connect: client -> connect to server */
static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
if (!host) {
return JS_EXCEPTION;
}
if (argc < 2) {
return JS_ThrowTypeError(ctx, "Expected 2 arguments: hostname, port.");
}
const char *hostname = JS_ToCString(ctx, argv[0]);
if (!hostname) {
return JS_EXCEPTION; // out of memory or conversion error
}
int port;
JS_ToInt32(ctx, &port, argv[1]);
ENetAddress address;
enet_address_set_host(&address, hostname);
JS_FreeCString(ctx, hostname);
address.port = port;
ENetPeer *peer = enet_host_connect(host, &address, 2, 0);
if (!peer) {
return JS_ThrowInternalError(ctx, "Failed to initiate connection.");
}
JSValue peer_obj = JS_NewObjectClass(ctx, enet_peer_class_id);
if (JS_IsException(peer_obj)) {
return peer_obj;
}
JS_SetOpaque(peer_obj, peer);
return peer_obj;
}
/* Flush queued packets */
static JSValue js_enet_host_flush(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
if (!host) {
return JS_EXCEPTION;
}
enet_host_flush(host);
return JS_UNDEFINED;
}
/* Broadcast a plain object */
static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
if (!host) {
return JS_EXCEPTION;
}
if (argc < 1) {
return JS_ThrowTypeError(ctx, "Expected an object to broadcast.");
}
// Must be a JavaScript object
if (!JS_IsObject(argv[0])) {
return JS_ThrowTypeError(ctx,
"broadcast() only accepts a plain JS object, not strings/numbers.");
}
// JSON.stringify the object
JSValue json_data = JS_JSONStringify(ctx, argv[0], JS_NULL, JS_NULL);
if (JS_IsException(json_data)) {
return JS_ThrowTypeError(ctx,
"Failed to stringify object (circular ref or non-serializable).");
}
size_t data_len;
const char *data_str = JS_ToCStringLen(ctx, &data_len, json_data);
JS_FreeValue(ctx, json_data);
if (!data_str) {
return JS_EXCEPTION; // out of memory
}
ENetPacket *packet = enet_packet_create(data_str, data_len, ENET_PACKET_FLAG_RELIABLE);
JS_FreeCString(ctx, data_str);
if (!packet) {
return JS_ThrowInternalError(ctx, "Failed to create ENet packet.");
}
enet_host_broadcast(host, 0, packet);
return JS_UNDEFINED;
}
/* Peer-level operations */
static JSValue js_enet_peer_disconnect(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
enet_peer_disconnect(peer, 0);
return JS_UNDEFINED;
}
/* Peer send must only accept an object */
static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
if (argc < 1) {
return JS_ThrowTypeError(ctx, "Expected an object to send.");
}
if (!JS_IsObject(argv[0])) {
return JS_ThrowTypeError(ctx,
"peer.send() only accepts a plain JS object, not strings/numbers.");
}
JSValue json_data = JS_JSONStringify(ctx, argv[0], JS_NULL, JS_NULL);
if (JS_IsException(json_data)) {
return JS_ThrowTypeError(ctx,
"Failed to stringify object (circular ref or non-serializable).");
}
size_t data_len;
const char *data_str = JS_ToCStringLen(ctx, &data_len, json_data);
JS_FreeValue(ctx, json_data);
if (!data_str) {
return JS_EXCEPTION;
}
// Create packet
ENetPacket *packet = enet_packet_create(data_str, data_len, ENET_PACKET_FLAG_RELIABLE);
JS_FreeCString(ctx, data_str);
if (!packet) {
return JS_ThrowInternalError(ctx, "Failed to create ENet packet.");
}
if (enet_peer_send(peer, 0, packet) < 0) {
return JS_ThrowInternalError(ctx, "enet_peer_send returned error.");
}
return JS_UNDEFINED;
}
static JSValue js_enet_peer_disconnect_now(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
enet_peer_disconnect_now(peer, 0);
return JS_UNDEFINED;
}
static JSValue js_enet_peer_disconnect_later(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
enet_peer_disconnect_later(peer, 0);
return JS_UNDEFINED;
}
static JSValue js_enet_peer_reset(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
enet_peer_reset(peer);
return JS_UNDEFINED;
}
static JSValue js_enet_peer_ping(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
enet_peer_ping(peer);
return JS_UNDEFINED;
}
static JSValue js_enet_peer_throttle_configure(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
int interval, acceleration, deceleration;
if (argc < 3 ||
JS_ToInt32(ctx, &interval, argv[0]) ||
JS_ToInt32(ctx, &acceleration, argv[1]) ||
JS_ToInt32(ctx, &deceleration, argv[2])) {
return JS_ThrowTypeError(ctx,
"Expected 3 int arguments: interval, acceleration, deceleration");
}
enet_peer_throttle_configure(peer, interval, acceleration, deceleration);
return JS_UNDEFINED;
}
static JSValue js_enet_peer_timeout(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
int timeout_limit, timeout_min, timeout_max;
if (argc < 3 ||
JS_ToInt32(ctx, &timeout_limit, argv[0]) ||
JS_ToInt32(ctx, &timeout_min, argv[1]) ||
JS_ToInt32(ctx, &timeout_max, argv[2])) {
return JS_ThrowTypeError(ctx,
"Expected 3 integer arguments: timeout_limit, timeout_min, timeout_max");
}
enet_peer_timeout(peer, timeout_limit, timeout_min, timeout_max);
return JS_UNDEFINED;
}
/* Class definitions */
static JSClassDef enet_host = {
"ENetHost",
.finalizer = js_enet_host_finalizer,
};
static JSClassDef enet_peer_class = {
"ENetPeer",
.finalizer = js_enet_peer_finalizer,
};
/* Function lists */
static const JSCFunctionListEntry js_enet_funcs[] = {
JS_CFUNC_DEF("initialize", 0, js_enet_initialize),
JS_CFUNC_DEF("deinitialize", 0, js_enet_deinitialize),
JS_CFUNC_DEF("create_host", 1, js_enet_host_create),
};
static const JSCFunctionListEntry js_enet_host_funcs[] = {
JS_CFUNC_DEF("service", 2, js_enet_host_service),
JS_CFUNC_DEF("connect", 2, js_enet_host_connect),
JS_CFUNC_DEF("flush", 0, js_enet_host_flush),
JS_CFUNC_DEF("broadcast", 1, js_enet_host_broadcast),
};
static const JSCFunctionListEntry js_enet_peer_funcs[] = {
JS_CFUNC_DEF("send", 1, js_enet_peer_send),
JS_CFUNC_DEF("disconnect", 0, js_enet_peer_disconnect),
JS_CFUNC_DEF("disconnect_now", 0, js_enet_peer_disconnect_now),
JS_CFUNC_DEF("disconnect_later", 0, js_enet_peer_disconnect_later),
JS_CFUNC_DEF("reset", 0, js_enet_peer_reset),
JS_CFUNC_DEF("ping", 0, js_enet_peer_ping),
JS_CFUNC_DEF("throttle_configure",3, js_enet_peer_throttle_configure),
JS_CFUNC_DEF("timeout", 3, js_enet_peer_timeout),
};
/* Module entry point */
static int js_enet_init(JSContext *ctx, JSModuleDef *m);
/* This function returns the default export object */
JSValue js_enet_use(JSContext *ctx) {
// Register ENetHost class
JS_NewClassID(&enet_host_id);
JS_NewClass(JS_GetRuntime(ctx), enet_host_id, &enet_host);
JSValue host_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto);
// Register ENetPeer class
JS_NewClassID(&enet_peer_class_id);
JS_NewClass(JS_GetRuntime(ctx), enet_peer_class_id, &enet_peer_class);
JSValue peer_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto);
// Optional: store references in a "prosperon.c_types" for your environment
JSValue global = JS_GetGlobalObject(ctx);
JSValue prosp = JS_GetPropertyStr(ctx, global, "prosperon");
JSValue c_types = JS_GetPropertyStr(ctx, prosp, "c_types");
JS_SetPropertyStr(ctx, c_types, "enet_host", JS_DupValue(ctx, host_proto));
JS_SetPropertyStr(ctx, c_types, "enet_peer", JS_DupValue(ctx, peer_proto));
JS_FreeValue(ctx, c_types);
JS_FreeValue(ctx, prosp);
JS_FreeValue(ctx, global);
// Create the default export object with top-level ENet functions
JSValue export_obj = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs));
return export_obj;
}
static int js_enet_init(JSContext *ctx, JSModuleDef *m) {
return JS_SetModuleExport(ctx, m, "default", js_enet_use(ctx));
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_enet
#endif
/* Module definition */
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) {
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_enet_init);
if (!m) {
return NULL;
}
JS_AddModuleExport(ctx, m, "default");
return m;
}

8
source/qjs_enet.h Normal file
View File

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

View File

@@ -3,6 +3,8 @@
#include "imnodes.h"
#include "quickjs.h"
#include <stb_ds.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>
#include "imgui_impl_sdl3.h"
@@ -163,11 +165,10 @@ JSC_SCALL(imgui_plot,
fill_plotdata(js, argv[1], argv[3]); \
bool shaded = JS_ToBool(js,argv[2]);\
int flag = 0; \
if (shaded) flag = SHADED; \
ImPlot::FN(str, &plotdata[0].x, &plotdata[0].y, arrlen(plotdata), ADD flag, 0, sizeof(ImVec2)); \
) \
//if (shaded) flag = SHADED;
// ImPlot::FN(str, &plotdata[0].x, &plotdata[0].y, arrlen(plotdata), ADD flag, 0, sizeof(ImVec2));
static ImVec2 *plotdata = NULL;
void fill_plotdata(JSContext *js, JSValue v, JSValue last)
@@ -204,40 +205,19 @@ PLOT_FN(digitalplot, PlotDigital,,0)
JSC_SCALL(imgui_barplot,
fill_plotdata(js, argv[1], JS_UNDEFINED);
// ImPlot::PlotBars(str, &plotdata[0].x, &plotdata[0].y, js_arrlen(js, argv[1]), js2number(js, argv[2]), 0, 0, sizeof(ImVec2));
ImPlot::PlotBars(str, &plotdata[0].x, &plotdata[0].y, js_arrlen(js, argv[1]), js2number(js, argv[2]), 0, 0, sizeof(ImVec2));
)
JSC_SCALL(imgui_histogramplot,
size_t offset, len, per_e;
JSValue typed = JS_GetTypedArrayBuffer(js, argv[1], &offset, &len, &per_e);
// ImPlot::PlotHistogram(str, JS_GetArrayBuffer(js, NULL, typed), js_arrlen(js, argv[1]));
ImPlot::PlotHistogram(str, JS_GetArrayBuffer(js, NULL, typed), js_arrlen(js, argv[1]));
JS_FreeValue(js, typed);
)
JSC_SCALL(imgui_heatplot,
int rows = js2number(js, argv[2]);
int cols = js2number(js, argv[3]);
// if (rows*cols == (int)js_arrlen(js, argv[1]))
// ImPlot::PlotHeatmap(str, histodata, rows, cols);
)
JSC_CCALL(imgui_pieplot,
/* if (js_arrlen(js, argv[0]) != js_arrlen(js, argv[1])) return JS_UNDEFINED;
const char *labels[js_arrlen(js, argv[0])];
for (int i = 0; i < js_arrlen(js, argv[0]); i++)
labels[i] = JS_ToCString(js, js_getpropidx(argv[0], i));
fill_histodata(argv[1]);
ImPlot::PlotPieChart(labels, histodata, js_arrlen(js, argv[1]), js2number(js, argv[2]), js2number(js, argv[3]), js2number(js, argv[4]));
for (int i = 0; i < js_arrlen(js, argv[0]); i++)
JS_FreeCString(js,labels[i]);*/
)
JSC_SCALL(imgui_textplot,
ImVec2 c = js2vec2(js, argv[1]);
// ImPlot::PlotText(str, c.x, c.y);
ImPlot::PlotText(str, c.x, c.y);
)
JSC_CCALL(imgui_inplot,
@@ -275,7 +255,7 @@ JSC_SSCALL(imgui_textinput,
if (JS_IsUndefined(argv[1]))
buffer[0] = 0;
else
strncpy(buffer, str2, 512);
strncpy(buffer, str2, sizeof(buffer)-1);
ImGui::InputText(str, buffer, sizeof(buffer));
if (strcmp(buffer, str2))
@@ -289,7 +269,7 @@ JSC_SSCALL(imgui_textbox,
if (JS_IsUndefined(argv[1]))
buffer[0] = 0;
else
strncpy(buffer, str2, 512);
strncpy(buffer, str2, sizeof(buffer)-1);
ImGui::InputTextMultiline(str, buffer, sizeof(buffer));
if (strcmp(buffer, str2))
@@ -834,7 +814,6 @@ const JSCFunctionListEntry js_imgui_funcs[] = {
MIST_FUNC_DEF(imgui, stairplot, 4),
MIST_FUNC_DEF(imgui, digitalplot, 4),
MIST_FUNC_DEF(imgui, barplot, 3),
MIST_FUNC_DEF(imgui, pieplot, 5),
MIST_FUNC_DEF(imgui, textplot, 2),
MIST_FUNC_DEF(imgui, histogramplot, 2),
MIST_FUNC_DEF(imgui, plotaxes, 2),

View File

@@ -134,10 +134,13 @@ JS_SetPropertyStr(js, globalThis, #NAME, NAME); \
/* Defines a class and uses its function list as its prototype */
#define QJSCLASSPREP_FUNCS(TYPE) \
QJSCLASSPREP_NO_FUNCS(TYPE) \
JS_SetPropertyFunctionList(js, TYPE##_proto, js_##TYPE##_funcs, countof(js_##TYPE##_funcs)); \
#define QJSCLASSPREP_NO_FUNCS(TYPE) \
JS_NewClassID(&js_##TYPE##_id);\
JS_NewClass(JS_GetRuntime(js), js_##TYPE##_id, &js_##TYPE##_class);\
JSValue TYPE##_proto = JS_NewObject(js); \
JS_SetPropertyFunctionList(js, TYPE##_proto, js_##TYPE##_funcs, countof(js_##TYPE##_funcs)); \
JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \
JS_SetPropertyStr(js, c_types, #TYPE, JS_DupValue(js,TYPE##_proto)); \
@@ -147,8 +150,6 @@ JSValue js_##NAME##_use(JSContext *js) { \
JS_SetPropertyFunctionList(js,mod,js_##NAME##_funcs,countof(js_##NAME##_funcs)); \
return mod; } \
#define MISTLINE(NAME) (ModuleEntry){ #NAME, js_##NAME##_funcs, countof(js_##NAME##_funcs) }
#define countof(x) (sizeof(x)/sizeof((x)[0]))

306
source/qjs_nota.c Executable file
View File

@@ -0,0 +1,306 @@
#include "quickjs.h"
#define KIM_IMPLEMENTATION
#define NOTA_IMPLEMENTATION
#include "nota.h"
typedef struct NotaEncodeContext {
JSContext *ctx;
JSValue visitedStack;
NotaBuffer nb; // use the dynamic NotaBuffer
int cycle;
} NotaEncodeContext;
static void nota_stack_push(NotaEncodeContext *enc, JSValueConst val)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visitedStack);
JS_SetPropertyInt64(ctx, enc->visitedStack, len, JS_DupValue(ctx, val));
}
static void nota_stack_pop(NotaEncodeContext *enc)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visitedStack);
JS_SetPropertyStr(ctx, enc->visitedStack, "length", JS_NewUint32(ctx, len - 1));
}
static int nota_stack_has(NotaEncodeContext *enc, JSValueConst val)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visitedStack);
for (int i = 0; i < len; i++) {
JSValue elem = JS_GetPropertyUint32(ctx, enc->visitedStack, i);
if (JS_IsObject(elem) && JS_IsObject(val)) {
if (JS_VALUE_GET_OBJ(elem) == JS_VALUE_GET_OBJ(val)) {
JS_FreeValue(ctx, elem);
return 1;
}
}
JS_FreeValue(ctx, elem);
}
return 0;
}
JSValue number;
char *js_do_nota_decode(JSContext *js, JSValue *tmp, char *nota)
{
int type = nota_type(nota);
JSValue ret2;
long long n;
double d;
int b;
char *str;
uint8_t *blob;
switch(type) {
case NOTA_BLOB:
nota = nota_read_blob(&n, (char**)&blob, nota);
*tmp = JS_NewArrayBufferCopy(js, blob, n);
free(blob);
break;
case NOTA_TEXT:
nota = nota_read_text(&str, nota);
*tmp = JS_NewString(js, str);
free(str);
break;
case NOTA_ARR:
nota = nota_read_array(&n, nota);
*tmp = JS_NewArray(js);
for (int i = 0; i < n; i++) {
nota = js_do_nota_decode(js, &ret2, nota);
JS_SetPropertyInt64(js, *tmp, i, ret2);
}
break;
case NOTA_REC:
nota = nota_read_record(&n, nota);
*tmp = JS_NewObject(js);
for (int i = 0; i < n; i++) {
nota = nota_read_text(&str, nota);
nota = js_do_nota_decode(js, &ret2, nota);
JS_SetPropertyStr(js, *tmp, str, ret2);
free(str);
}
break;
case NOTA_INT:
nota = nota_read_int(&n, nota);
*tmp = JS_NewInt64(js,n);
break;
case NOTA_SYM:
nota = nota_read_sym(&b, nota);
switch(b) {
case NOTA_NULL:
*tmp = JS_UNDEFINED;
break;
case NOTA_FALSE:
*tmp = JS_NewBool(js,0);
break;
case NOTA_TRUE:
*tmp = JS_NewBool(js,1);
break;
}
break;
default:
case NOTA_FLOAT:
nota = nota_read_float(&d, nota);
*tmp = JS_NewFloat64(js,d);
break;
}
return nota;
}
static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val);
static void encode_object_properties(NotaEncodeContext *enc, JSValueConst val)
{
JSContext *ctx = enc->ctx;
JSPropertyEnum *ptab;
uint32_t plen;
if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, val, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK) < 0) {
nota_write_sym(&enc->nb, NOTA_NULL);
return;
}
nota_write_record(&enc->nb, plen);
for (uint32_t i = 0; i < plen; i++) {
// property name
const char *propName = JS_AtomToCString(ctx, ptab[i].atom);
nota_write_text(&enc->nb, propName);
JS_FreeCString(ctx, propName);
// property value
JSValue propVal = JS_GetProperty(ctx, val, ptab[i].atom);
nota_encode_value(enc, propVal);
JS_FreeValue(ctx, propVal);
// free the atom
JS_FreeAtom(ctx, ptab[i].atom);
}
js_free(ctx, ptab);
}
static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val)
{
JSContext *ctx = enc->ctx;
int tag = JS_VALUE_GET_TAG(val);
switch (tag) {
case JS_TAG_INT:
case JS_TAG_BIG_INT:
case JS_TAG_FLOAT64:
case JS_TAG_BIG_DECIMAL:
case JS_TAG_BIG_FLOAT: {
double d;
JS_ToFloat64(ctx, &d, val);
nota_write_number(&enc->nb, d);
return;
}
case JS_TAG_STRING: {
const char *str = JS_ToCString(ctx, val);
nota_write_text(&enc->nb, str);
JS_FreeCString(ctx, str);
return;
}
case JS_TAG_BOOL: {
if (JS_VALUE_GET_BOOL(val))
nota_write_sym(&enc->nb, NOTA_TRUE);
else
nota_write_sym(&enc->nb, NOTA_FALSE);
return;
}
case JS_TAG_NULL:
case JS_TAG_UNDEFINED:
nota_write_sym(&enc->nb, NOTA_NULL);
return;
case JS_TAG_OBJECT: {
size_t bufLen;
void *bufData = JS_GetArrayBuffer(ctx, &bufLen, val);
if (bufData) {
/* Write as a blob of bits (bufLen * 8). */
nota_write_blob(&enc->nb, (unsigned long long)bufLen * 8, (const char*)bufData);
return;
}
if (JS_IsArray(ctx, val)) {
if (nota_stack_has(enc, val)) {
enc->cycle = 1;
return; // bail out
}
nota_stack_push(enc, val);
int arrLen = JS_ArrayLength(ctx, val);
nota_write_array(&enc->nb, arrLen);
for (int i = 0; i < arrLen; i++) {
JSValue elemVal = JS_GetPropertyUint32(ctx, val, i);
nota_encode_value(enc, elemVal);
JS_FreeValue(ctx, elemVal);
}
nota_stack_pop(enc);
return;
}
if (nota_stack_has(enc, val)) {
enc->cycle = 1;
return; // bail out
}
nota_stack_push(enc, val);
encode_object_properties(enc, val);
nota_stack_pop(enc);
return;
}
default:
nota_write_sym(&enc->nb, NOTA_NULL);
return;
}
}
static JSValue js_nota_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (argc < 1)
return JS_ThrowTypeError(ctx, "nota.encode requires 1 argument");
NotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visitedStack = JS_NewArray(ctx); // empty array initially
enc->cycle = 0;
nota_buffer_init(&enc->nb, 128);
nota_encode_value(enc, argv[0]);
if (enc->cycle) {
JS_FreeValue(ctx, enc->visitedStack);
nota_buffer_free(&enc->nb);
return JS_ThrowReferenceError(ctx, "Tried to encode something to nota with a cycle.");
}
JS_FreeValue(ctx, enc->visitedStack);
size_t totalLen = enc->nb.size; // how many bytes used
void* dataPtr = enc->nb.data; // pointer to the raw data
JSValue ret = JS_NewArrayBufferCopy(ctx, (uint8_t*)dataPtr, totalLen);
nota_buffer_free(&enc->nb);
return ret;
}
JSValue js_nota_decode(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (argc < 1) return JS_UNDEFINED;
size_t len;
unsigned char *nota = JS_GetArrayBuffer(js, &len, argv[0]);
if (!nota) return JS_UNDEFINED;
JSValue ret;
js_do_nota_decode(js, &ret, (char*)nota);
return ret;
}
static const JSCFunctionListEntry js_nota_funcs[] = {
JS_CFUNC_DEF("encode", 1, js_nota_encode),
JS_CFUNC_DEF("decode", 1, js_nota_decode),
};
static int js_nota_init(JSContext *ctx, JSModuleDef *m) {
JS_SetModuleExportList(ctx, m, js_nota_funcs,
sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
return 0;
}
JSValue js_nota_use(JSContext *js)
{
JSValue export = JS_NewObject(js);
JS_SetPropertyFunctionList(js, export,
js_nota_funcs,
sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
number = JS_GetPropertyStr(js, JS_GetGlobalObject(js), "Number");
return export;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_nota
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) {
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_nota_init);
if (!m) return NULL;
JS_AddModuleExportList(ctx, m, js_nota_funcs,
sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
return m;
}

8
source/qjs_nota.h Normal file
View File

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

229
source/qjs_soloud.c Normal file
View File

@@ -0,0 +1,229 @@
#define STB_HEXWAVE_IMPLEMENTATION
#include "soloud_c.h"
#include "quickjs.h"
#include <stdlib.h>
#define countof(x) (sizeof(x)/sizeof((x)[0]))
#define FNSIG (JSContext *js, JSValueConst this_val, int argc, JSValue *argv)
#define GETSIG (JSContext *js, JSValueConst this_val)
#define SETSIG (JSContext *js, JSValueConst this_val, JSValue val)
#define JSCLASS(TYPE, FINALIZER) \
static JSClassID js_##TYPE##_class_id; \
static inline TYPE *js2##TYPE(JSContext *js, JSValue v) { \
return JS_GetOpaque(v, js_##TYPE##_class_id); \
} \
static inline JSValue TYPE##2js(JSContext *js, TYPE *data) { \
JSValue obj = JS_NewObjectClass(js, js_##TYPE##_class_id); \
JS_SetOpaque(obj, data); \
return obj; \
} \
static inline void js_##TYPE##_finalizer(JSRuntime *rt, JSValue val) { \
FINALIZER(JS_GetOpaque(val, js_##TYPE##_class_id)); \
} \
static JSClassDef js_##TYPE##_class = { \
#TYPE, \
.finalizer = js_##TYPE##_finalizer \
}; \
#define JS_GETPROP(C, PROP, VAL, TYPE) \
{ \
JSValue tmp = JS_GetPropertyStr(js, VAL, #PROP); \
JS_To##TYPE(js, &C, tmp); \
JS_FreeValue(js, tmp); \
} \
static double js2number(JSContext *js, JSValue v)
{
double ret;
JS_ToFloat64(js, &ret, v);
return ret;
}
static JSValue number2js(JSContext *js, double num)
{
return JS_NewFloat64(js, num);
}
static int js2bool(JSContext *js, JSValue v)
{
int b;
JS_ToInt32(js, &b, v);
return b;
}
static JSValue bool2js(JSContext *js, int b)
{
return JS_NewBool(js,b);
}
typedef unsigned int voice;
static Soloud *soloud;
JSCLASS(Wav, Wav_destroy)
JSCLASS(voice, free)
JSCLASS(Bus, Bus_destroy)
static JSValue js_soloud_make(JSContext *js, JSValue self, int argc, JSValue *argv)
{
soloud = Soloud_create();
Soloud_initEx(soloud, SOLOUD_CLIP_ROUNDOFF, SOLOUD_AUTO, SOLOUD_AUTO, SOLOUD_AUTO, SOLOUD_AUTO);
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "channels", JS_NewFloat64(js, Soloud_getBackendChannels(soloud)));
JS_SetPropertyStr(js, obj, "samplerate", JS_NewFloat64(js, Soloud_getBackendSamplerate(soloud)));
return obj;
}
static JSValue js_soloud_play(JSContext *js, JSValue self, int argc, JSValue *argv)
{
Wav *wav = js2Wav(js,argv[0]);
unsigned int *ret = malloc(sizeof(*ret));
*ret = Soloud_play(soloud, wav);
JSValue voice = voice2js(js,ret);
return voice;
}
static JSValue js_soloud_mix(JSContext *js, JSValue self, int argc, JSValue *argv)
{
size_t len;
void *data = JS_GetArrayBuffer(js, &len, argv[0]);
Soloud_mix(soloud, data, js2number(js,argv[1]));
return JS_UNDEFINED;
}
// Create a voice from a WAV file
static JSValue js_load_wav_mem(JSContext *js, JSValue self, int argc, JSValue *argv)
{
size_t len;
void *data = JS_GetArrayBuffer(js, &len, argv[0]);
Wav *wav = Wav_create();
if (Wav_loadMemEx(wav, data, len, 1, 1)) {
Wav_destroy(wav);
return JS_ThrowReferenceError(js, "buffer data not wav data");
}
return Wav2js(js, wav);
}
// Create a voice from pure PWM data
static JSValue js_load_pwm(JSContext *js, JSValue self, int argc, JSValue *argv)
{
size_t len;
void *data = JS_GetArrayBuffer(js, &len, argv[0]);
Wav *wav = Wav_create();
Wav_loadRawWaveEx(wav, data, len, js2number(js,argv[1]), js2number(js,argv[2]), 1, 1);
return Wav2js(js, wav);
}
static JSValue js_soloud_profile(JSContext *js, JSValue self, int argc, JSValue *argv)
{
JSValue prof = JS_NewObject(js);
JS_SetPropertyStr(js, prof, "active_voices", JS_NewFloat64(js, Soloud_getActiveVoiceCount(soloud)));
JS_SetPropertyStr(js, prof, "voices", JS_NewFloat64(js, Soloud_getVoiceCount(soloud)));
}
static const JSCFunctionListEntry js_soloud_funcs[] = {
JS_CFUNC_DEF("init", 3, js_soloud_make),
JS_CFUNC_DEF("mix", 1, js_soloud_mix),
JS_CFUNC_DEF("load_pwm", 3, js_load_pwm),
JS_CFUNC_DEF("load_wav_mem", 1, js_load_wav_mem),
JS_CFUNC_DEF("play", 1, js_soloud_play),
JS_CFUNC_DEF("profile", 0, js_soloud_profile),
};
static const JSCFunctionListEntry *js_Wav_funcs;
#define SOLOUD_GETSET(ENTRY, TYPE) \
static JSValue js_voice_set_##ENTRY (JSContext *js, JSValueConst self, JSValue val) { \
unsigned int voice = *js2voice(js, self); \
Soloud_set##ENTRY(soloud, voice, js2##TYPE(js, val)); \
return JS_UNDEFINED; \
} \
static JSValue js_voice_get_##ENTRY (JSContext *js, JSValueConst self) { \
unsigned int voice = *js2voice(js,self); \
return TYPE##2js(js, Soloud_get##ENTRY(soloud, voice)); \
} \
static JSValue js_voice_seek(JSContext *js, JSValue self, int argc, JSValue *argv)
{
unsigned int voice = *js2voice(js, self);
Soloud_seek(soloud, voice, js2number(js, argv[0]));
return JS_UNDEFINED;
}
static JSValue js_voice_stop(JSContext *js, JSValue self, int argc, JSValue *argv)
{
unsigned int voice = *js2voice(js, self);
Soloud_stop(soloud, voice);
return JS_UNDEFINED;
}
static JSValue js_voice_setInaudibleBehavior(JSContext *js, JSValueConst self, int argc, JSValue *argv)
{
unsigned int voice = *js2voice(js, self);
int mustTick = js2bool(js, argv[0]);
int kill = js2bool(js, argv[1]);
Soloud_setInaudibleBehavior(soloud, voice, mustTick, kill);
return JS_UNDEFINED;
}
SOLOUD_GETSET(Volume, number);
SOLOUD_GETSET(Pan, number)
SOLOUD_GETSET(Samplerate, number)
SOLOUD_GETSET(RelativePlaySpeed, number)
SOLOUD_GETSET(LoopPoint, number)
SOLOUD_GETSET(Looping, bool)
SOLOUD_GETSET(AutoStop, bool)
SOLOUD_GETSET(ProtectVoice, bool)
static const JSCFunctionListEntry js_voice_funcs[] = {
JS_CFUNC_DEF("seek", 1, js_voice_seek),
JS_CFUNC_DEF("stop", 0, js_voice_stop),
JS_CGETSET_DEF("volume", js_voice_get_Volume, js_voice_set_Volume),
JS_CGETSET_DEF("pan", js_voice_get_Pan, js_voice_set_Pan),
JS_CGETSET_DEF("samplerate", js_voice_get_Samplerate, js_voice_set_Samplerate),
JS_CGETSET_DEF("relativePlaySpeed", js_voice_get_RelativePlaySpeed, js_voice_set_RelativePlaySpeed),
JS_CGETSET_DEF("loopPoint", js_voice_get_LoopPoint, js_voice_set_LoopPoint),
JS_CGETSET_DEF("loop", js_voice_get_Looping, js_voice_set_Looping),
JS_CGETSET_DEF("autoStop", js_voice_get_AutoStop, js_voice_set_AutoStop),
JS_CGETSET_DEF("protect", js_voice_get_ProtectVoice, js_voice_set_ProtectVoice),
};
static const JSCFunctionListEntry *js_Bus_funcs;
#define INITCLASS(TYPE) \
JS_NewClassID(&js_##TYPE##_class_id); \
JS_NewClass(JS_GetRuntime(js), js_##TYPE##_class_id, &js_##TYPE##_class); \
JSValue TYPE##_proto = JS_NewObject(js); \
JS_SetPropertyFunctionList(js, TYPE##_proto, js_##TYPE##_funcs, countof(js_##TYPE##_funcs)); \
JS_SetClassProto(js, js_##TYPE##_class_id, TYPE##_proto); \
JSValue js_soloud_use(JSContext *js)
{
INITCLASS(Wav)
INITCLASS(voice)
INITCLASS(Bus)
JSValue export = JS_NewObject(js);
JS_SetPropertyFunctionList(js, export, js_soloud_funcs, sizeof(js_soloud_funcs)/sizeof(JSCFunctionListEntry));
return export;
}
static int js_soloud_init(JSContext *js, JSModuleDef *m) {
js_soloud_use(js);
JS_SetModuleExportList(js, m, js_soloud_funcs, sizeof(js_soloud_funcs)/sizeof(JSCFunctionListEntry));
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_soloud
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) {
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_soloud_init);
if (!m) return NULL;
JS_AddModuleExportList(ctx, m, js_soloud_funcs, sizeof(js_soloud_funcs)/sizeof(JSCFunctionListEntry));
return m;
}

8
source/qjs_soloud.h Normal file
View File

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

View File

@@ -37,6 +37,7 @@ static JSValue js_tracy_fiber_leave(JSContext *js, JSValue self, int argc, JSVal
const char *str = JS_AtomToCString(js, atom);
TracyCFiberLeave(str);
JS_FreeAtom(js,atom);
return JS_UNDEFINED;
}
static JSValue js_tracy_plot(JSContext *js, JSValue self, int argc, JSValue *argv)
@@ -61,7 +62,15 @@ static JSValue js_tracy_plot_config(JSContext *js, JSValue self, int argc, JSVal
return JS_UNDEFINED;
#endif
// TracyCPlotConfig(str, js2number(js,argv[1]), JS_ToBool(js,argv[2]), JS_ToBool(js,argv[3]), js2number(js,argv[4]))
const char *str = JS_ToCString(js,argv[0]);
uint32_t type, color;
JS_ToUint32(js,&type, argv[1]);
JS_ToUint32(js,&color,argv[4]);
TracyCPlotConfig(str, type, JS_ToBool(js,argv[2]), JS_ToBool(js,argv[3]), color);
JS_FreeCString(js,str);
return JS_UNDEFINED;
}
@@ -73,6 +82,8 @@ static JSValue js_tracy_frame_mark(JSContext *js, JSValue self, int argc, JSValu
#endif
TracyCFrameMark
return JS_UNDEFINED;
}
static JSValue js_tracy_message(JSContext *js, JSValue self, int argc, JSValue *argv)
@@ -86,6 +97,7 @@ static JSValue js_tracy_message(JSContext *js, JSValue self, int argc, JSValue *
const char *str = JS_ToCStringLen(js, &len, argv[0]);
TracyCMessage(str,len);
JS_FreeCString(js,str);
return JS_UNDEFINED;
}
static JSValue js_tracy_thread_name(JSContext *js, JSValue self, int argc, JSValue *argv)
@@ -98,6 +110,7 @@ static JSValue js_tracy_thread_name(JSContext *js, JSValue self, int argc, JSVal
const char *str = JS_ToCString(js, argv[0]);
TracyCSetThreadName(str);
JS_FreeCString(js,str);
return JS_UNDEFINED;
}
static JSValue js_tracy_zone_begin(JSContext *js, JSValue self, int argc, JSValue *argv)
@@ -121,6 +134,7 @@ static JSValue js_tracy_zone_begin(JSContext *js, JSValue self, int argc, JSValu
JS_Call(js, argv[0], JS_UNDEFINED, 0, NULL);
TracyCZoneEnd(TCTX);
return JS_UNDEFINED;
}
#ifdef SOKOL_GLCORE
@@ -190,7 +204,6 @@ static JSValue js_tracy_gpu_zone_begin(JSContext *js, JSValue self, int argc, JS
___tracy_emit_gpu_zone_end(enddata);
qhead = (qhead+1)%query_count;
return ret;
}
@@ -477,8 +490,8 @@ static JSValue js_tracy_image(JSContext *js, JSValue self, int argc, JSValue *ar
/* SDL_Surface *img = js2SDL_Surface(js,argv[0]);
SDL_Surface *scaled = SDL_ScaleSurface(img, 320,180,SDL_SCALEMODE_LINEAR);
___tracy_emit_frame_image(scaled->pixels, scaled->w,scaled->h, 0,0);
SDL_DestroySurface(scaled);
return JS_UNDEFINED;*/
SDL_DestroySurface(scaled);*/
return JS_UNDEFINED;
}
#endif
@@ -513,6 +526,8 @@ JSValue js_tracy_level(JSContext *js, JSValue selff, int argc, JSValue *argv)
js_debug_sethook(js, tracy_call_hook, JS_HOOK_CALL);
js_debug_sethook(js, tracy_end_hook, JS_HOOK_RET);
}
return JS_UNDEFINED;
}
static const JSCFunctionListEntry js_tracy_funcs[] = {
@@ -523,11 +538,13 @@ static const JSCFunctionListEntry js_tracy_funcs[] = {
JS_CFUNC_DEF("gpu_init", 0, js_tracy_gpu_init),
JS_CFUNC_DEF("gpu_sync", 0, js_tracy_gpu_sync),
JS_CFUNC_DEF("end_frame", 0, js_tracy_frame_mark),
JS_CFUNC_DEF("thread_name", 1, js_tracy_thread_name),
JS_CFUNC_DEF("zone", 1, js_tracy_zone_begin),
JS_CFUNC_DEF("message", 1, js_tracy_message),
JS_CFUNC_DEF("plot", 2, js_tracy_plot),
JS_CFUNC_DEF("image", 3, js_tracy_image),
JS_CFUNC_DEF("level", 1, js_tracy_level),
JS_CFUNC_DEF("plot_config", 5, js_tracy_plot_config),
};
JSValue js_tracy_use(JSContext *js)

View File

@@ -1,246 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "aabb.h"
/// Default node size cap
#define QTREE_STDCAP 4
/// A function pointer def for determining if an element exists in a range
typedef int (*qtree_fnc)(void *ptr, aabb *range);
typedef int (*qtree_rm)(void *ptr, void *cmp);
/// Quadtree node
typedef struct qnode {
uint16_t cnt; ///< Number of elements in this node
aabb bound; ///< Area this node covers
void **elist; ///< List of element pointers
struct qnode *nw; ///< NW quadrant of this node
struct qnode *ne; ///< NE quadrant of this node
struct qnode *sw; ///< SW quadrant of this node
struct qnode *se; ///< SE quadrant of this node
} qnode;
/// Quadtree container
typedef struct _qtree {
uint16_t maxnodecap; ///< Maximum element count per node
qnode *root; ///< Root node
qtree_fnc cmpfnc; ///< Element range compare function pointer
qtree_rm rmfnc;
} _qtree;
typedef struct _qtree* qtree;
/// Simple container for returning found elements
typedef struct retlist {
uint32_t cnt; ///< Number of elements found
aabb range; ///< Range to use for searching
void **list; ///< Array of pointers to found elements
} retlist;
static void retlist_add(retlist *r, void *p) {
r->list = realloc(r->list, sizeof(void*)*(r->cnt+1));
r->list[r->cnt] = p;
r->cnt++;
}
static uint16_t qtree_getMaxNodeCnt(qtree q) {
uint16_t r;
r = q->maxnodecap;
return r;
}
static qnode* qnode_new(qtree p, float x, float y, float hW, float hH) {
qnode *q = malloc(sizeof(qnode));
memset(q, 0, sizeof(qnode));
q->bound.center.x = x;
q->bound.center.y = y;
q->bound.dims.w = hW;
q->bound.dims.h = hH;
return q;
}
static void qnode_free(qtree q, qnode *qn) {
if(qn->cnt)
free(qn->elist);
qn->cnt = 0;
if(qn->nw) {
qnode_free(q, qn->nw);
qnode_free(q, qn->ne);
qnode_free(q, qn->sw);
qnode_free(q, qn->se);
}
free(qn);
}
static void add(qnode *q, void *p) {
q->elist = realloc(q->elist, sizeof(void*)*(q->cnt+1));
q->elist[q->cnt] = p;
q->cnt++;
}
static void drop(qnode *q, uint16_t idx) {
void **narry = malloc(sizeof(void*)*(q->cnt-1));
// This is a little (lot) ugly; a pair of memcpy's would be
// better, but I had some problems with it
for(uint16_t i=0,skip=0; i<q->cnt; i++) {
if(i == idx) { skip++; continue; }
narry[i-skip] = q->elist[i];
}
void **old = q->elist;
q->elist = narry;
free(old);
q->cnt--;
}
static void subdivide(qtree p, qnode *q) {
float cx = q->bound.center.x;
float cy = q->bound.center.y;
float hw = q->bound.dims.w/2;
float hh = q->bound.dims.h/2;
q->nw = qnode_new(p, cx-hw, cy-hh, hw, hh);
q->ne = qnode_new(p, cx+hw, cy-hh, hw, hh);
q->sw = qnode_new(p, cx-hw, cy+hh, hw, hh);
q->se = qnode_new(p, cx+hw, cy+hh, hw, hh);
}
static int qnode_insert(qtree q, qnode *qn, void *ptr) {
int ret = 0;
if(! (q->cmpfnc)(ptr, &qn->bound)) return 0;
if(qn->cnt < qtree_getMaxNodeCnt(q)) {
add(qn, ptr);
return 1;
}
if(! qn->nw)
subdivide(q, qn);
if(qnode_insert(q,qn->nw,ptr))
return 1;
else if(qnode_insert(q,qn->ne,ptr))
return 1;
else if(qnode_insert(q,qn->sw,ptr))
return 1;
else if(qnode_insert(q,qn->se,ptr))
return 1;
}
static void* qnode_remove(qtree q, qnode *qn, void *ptr) {
if(qn->cnt) {
for(uint16_t i=0; i<qn->cnt; i++) {
if(q->rmfnc(qn->elist[i], ptr)) {
drop(qn, i);
ptr = NULL;
goto QN_REM_EXIT;
}
}
}
if(! qn->nw)
return NULL;
if(qnode_remove(q, qn->nw, ptr)) return ptr;
if(qnode_remove(q, qn->ne, ptr)) return ptr;
if(qnode_remove(q, qn->sw, ptr)) return ptr;
if(qnode_remove(q, qn->se, ptr)) return ptr;
return NULL;
QN_REM_EXIT:
return ptr;
}
static void qnode_getInRange(qtree q, qnode *qn, retlist *r) {
if(qn->cnt) {
if(! aabb_intersects(&qn->bound, &r->range))
goto QN_GET_EXIT;
for(uint16_t i=0; i<qn->cnt; i++)
if((q->cmpfnc)(qn->elist[i], &r->range))
retlist_add(r, qn->elist[i]);
}
if(! qn->nw)
goto QN_GET_EXIT;
qnode_getInRange(q, qn->nw, r);
qnode_getInRange(q, qn->ne, r);
qnode_getInRange(q, qn->sw, r);
qnode_getInRange(q, qn->se, r);
QN_GET_EXIT:
return;
}
qtree qtree_new(float x, float y, float w, float h, qtree_fnc fnc, qtree_rm rm) {
qtree q = malloc(sizeof(_qtree));
memset(q, 0, sizeof(_qtree));
q->maxnodecap = QTREE_STDCAP;
q->cmpfnc = fnc;
q->rmfnc = rm;
q->root = qnode_new(q, x+(w/2),y+(h/2),w/2,h/2);
return q;
}
void qtree_destroy(qtree q) {
void *m;
if(q->root) qnode_free(q, q->root);
memset(q, 0, sizeof(_qtree));
free(q);
}
void qtree_insert(qtree q, void *ptr) {
qnode_insert(q, q->root, ptr);
}
void qtree_remove(qtree q, void *ptr) {
qnode_remove(q, q->root, ptr);
}
void qtree_setMaxNodeCnt(qtree q, uint16_t cnt) {
q->maxnodecap = cnt || 1;
}
void qtree_clear(qtree q) {
float x = q->root->bound.center.x;
float y = q->root->bound.center.y;
float w = q->root->bound.dims.w;
float h = q->root->bound.dims.h;
qnode *qn = q->root;
q->root = qnode_new(q, x, y, w, h);
qnode_free(q, qn);
}
void** qtree_findInArea(qtree q, float x, float y, float w, float h, uint32_t *cnt) {
float hw = w/2;
float hh = h/2;
retlist ret;
memset(&ret, 0, sizeof(retlist));
ret.range.center.x = x+hw;
ret.range.center.y = y+hh;
ret.range.dims.w = hw;
ret.range.dims.h = hh;
qnode_getInRange(q, q->root, &ret);
*cnt = ret.cnt;
return ret.list;
}

View File

@@ -1,78 +0,0 @@
/*
quadtree.h
2014 JSK (kutani@projectkutani.com)
Part of the Panic Panic project.
Released to the public domain. See LICENSE for details.
*/
#ifndef _QUADTREE_H
#define _QUADTREE_H
#ifndef _AABB_H
#include "aabb.h"
#endif
/// Opaque pointer to a quadtree data structure
typedef struct _qtree* qtree;
/// A function pointer def for determining if an element exists in a range
typedef int (*qtree_fnc)(void *ptr, aabb *range);
typedef int (*qtree_rm)(void *ptr, void *cmp);
/// Create a new qtree
/*!
Creates a new qtree with a bound of w,h size, centered at x,y.
Uses the passed function pointer fnc to test elements against nodes
for insertion, and finding.
Returns a new qtree pointer.
*/
qtree qtree_new(float x, float y, float w, float h, qtree_fnc fnc, qtree_rm rm);
void qtree_destroy(qtree q);
/// Insert an element
/*!
Inserts the passed element into quadtree q.
Uses the function passed to qtree_new() to determine where the
element should go.
*/
void qtree_insert(qtree q, void *ptr);
/// Removes an element from the quadtree
/*!
Performs a selective removal of the passed element.
Performs a naive pointer comparison and a depth-first search of the
tree, so this isn't very fast.
*/
void qtree_remove(qtree q, void *ptr);
/// Set the maximum number of elements per node
/*!
Sets the maximum elements per quadtree node.
The default is 4.
*/
void qtree_setMaxNodeCnt(qtree q, uint16_t cnt);
/// Resets a quadtree
/*!
Clears all nodes held by the quadtree and creates a fresh root node
with no elements assigned.
*/
void qtree_clear(qtree q);
/// Find all elements within a rectangular bound
/*!
Performs a search for any elements within the given x,y + w,h
bound. Returns an array of pointers to any elements (which should be
freed by the user), and places the number of elements in cnt.
*/
void** qtree_findInArea(qtree q, float x, float y, float w, float h, uint32_t *cnt);
#endif

View File

@@ -27,7 +27,7 @@ static JSRuntime *rt = NULL;
JSContext *global_js = NULL;
JSValue on_exception = JS_UNDEFINED;
JSValue on_exception;
#define ENGINE "scripts/core/engine.js"
@@ -157,6 +157,9 @@ void script_startup(int argc, char **argv) {
JS_AddIntrinsicBigFloat(js);
JS_AddIntrinsicBigDecimal(js);
JS_AddIntrinsicOperators(js);
JS_EnableBignumExt(js, 1);
on_exception = JS_UNDEFINED;
ffi_load(js, argc, argv);

View File

@@ -3,318 +3,447 @@
#include "transform.h"
#include "math.h"
/* -------------------------------------------------------------------------
Cubic Spline Basis Matrices
------------------------------------------------------------------------- */
static const HMM_Mat4 cubic_hermite_m = {
2, -2, 1, 1,
-3, 3, -2, -1,
0, 0, 1, 0,
1, 0, 0, 0
2, -2, 1, 1,
-3, 3, -2, -1,
0, 0, 1, 0,
1, 0, 0, 0
};
static const HMM_Mat4 cubic_hermite_dm = {
0, 0, 0, 0,
6, -6, 3, 3,
-6, 6, -4, -2,
0, 0, 1, 0
0, 0, 0, 0,
6, -6, 3, 3,
-6, 6, -4, -2,
0, 0, 1, 0
};
static const HMM_Mat4 cubic_hermite_ddm = {
0, 0, 0, 0,
0, 0, 0, 0,
12, -12, 6, 6,
-6, 6, -4, -2
0, 0, 0, 0,
0, 0, 0, 0,
12, -12, 6, 6,
-6, 6, -4, -2
};
static const HMM_Mat4 cubic_hermite_dddm = {
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
12, -12, 6, 6
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
12, -12, 6, 6
};
static const HMM_Mat4 b_spline_m = {
-1/6, 3/6, -3/6, 1,
3/6, -6/6, 3/6, 0,
-3/6, 0, 3/6, 0,
1/6, 4/6, 1/6, 0
-1.0f/6, 3.0f/6, -3.0f/6, 1.0f,
3.0f/6, -6.0f/6, 3.0f/6, 0.0f,
-3.0f/6, 0.0f, 3.0f/6, 0.0f,
1.0f/6, 4.0f/6, 1.0f/6, 0.0f
};
static const HMM_Mat4 b_spline_dm = {
0, 0, 0, 0,
-3/6, 9/6, -9/6, 3,
6/6, -12/6, 6/6, 0,
-3/6, 0, 3/6, 0
0, 0, 0, 0,
-3.0f/6, 9.0f/6, -9.0f/6, 3.0f,
6.0f/6, -12.0f/6, 6.0f/6, 0.0f,
-3.0f/6, 0.0f, 3.0f/6, 0.0f
};
static const HMM_Mat4 b_spline_ddm = {
0, 0, 0, 0,
0, 0, 0, 0,
-6/6, 18/6, -18/6, 6,
6/6, -12/6, 6/6, 0
0, 0, 0, 0,
0, 0, 0, 0,
-6.0f/6, 18.0f/6, -18.0f/6, 6.0f,
6.0f/6, -12.0f/6, 6.0f/6, 0.0f
};
static const HMM_Mat4 b_spline_dddm = {
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
-6/6, 18/6, -18/6, 6
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
-6.0f/6, 18.0f/6, -18.0f/6, 6.0f
};
static const HMM_Mat4 bezier_m = {
-1, 3, -3, 1,
3, -6, 3, 0,
-3, 3, 0, 0,
1, 0, 0, 0
-1, 3, -3, 1,
3, -6, 3, 0,
-3, 3, 0, 0,
1, 0, 0, 0
};
static const HMM_Mat4 bezier_dm = {
0, 0, 0, 0,
-3, 9, -9, 3,
6, -12, 6, 0,
-3, 3, 0, 0,
0, 0, 0, 0,
-3, 9, -9, 3,
6, -12, 6, 0,
-3, 3, 0, 0
};
static const HMM_Mat4 bezier_ddm = {
0, 0, 0, 0,
0, 0, 0, 0,
-6, 18, -18, 6,
6, -12, 6, 0
0, 0, 0, 0,
0, 0, 0, 0,
-6, 18, -18, 6,
6, -12, 6, 0
};
static const HMM_Mat4 bezier_dddm = {
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
-6, 18, -18, 6
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
-6, 18, -18, 6
};
#define CAT_S 0.5
/* CatmullRom (with tension = 0.5 by default) */
#define CAT_S 0.5f
/* Position */
static const HMM_Mat4 catmull_rom_m = {
-CAT_S, 2-CAT_S, CAT_S-2, CAT_S,
2*CAT_S, CAT_S-3, 3-2*CAT_S, -CAT_S,
-CAT_S, 0, CAT_S, 0,
0, 1, 0, 0
-CAT_S, 2-CAT_S, CAT_S-2, CAT_S,
2*CAT_S, CAT_S-3, 3-2*CAT_S, -CAT_S,
-CAT_S, 0, CAT_S, 0,
0, 1, 0, 0
};
/* Tangent */
static const HMM_Mat4 catmull_rom_dm = {
0, 0, 0, 0,
-3*CAT_S, 9*CAT_S, -9*CAT_S, 3*CAT_S,
4*CAT_S, -10*CAT_S, 8*CAT_S, -2*CAT_S,
-CAT_S, 0, CAT_S, 0,
0, 0, 0, 0,
-3*CAT_S, 9*CAT_S, -9*CAT_S, 3*CAT_S,
4*CAT_S, -10*CAT_S, 8*CAT_S, -2*CAT_S,
-CAT_S, 0, CAT_S, 0
};
/* Curvature */
static const HMM_Mat4 catmull_rom_ddm = {
0, 0, 0, 0,
0, 0, 0, 0,
-9*CAT_S, 18*CAT_S, -18*CAT_S, 6*CAT_S,
4*CAT_S, -10*CAT_S, 8*CAT_S, -2*CAT_S
0, 0, 0, 0,
0, 0, 0, 0,
-9*CAT_S, 18*CAT_S, -18*CAT_S, 6*CAT_S,
4*CAT_S, -10*CAT_S, 8*CAT_S, -2*CAT_S
};
/* Wiggle */
static const HMM_Mat4 catmull_rom_dddm = {
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
-9*CAT_S, 18*CAT_S, -18*CAT_S, 6*CAT_S
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
-9*CAT_S, 18*CAT_S, -18*CAT_S, 6*CAT_S
};
/*
[t3 t2 t1 1] B [p1
p2 that is, point 1, tangent at point 1, point 2, tan and point 2
t1
t2]
*/
/* -------------------------------------------------------------------------
Core “C·T” multiplication: [ t^3, t^2, t, 1 ] * C
------------------------------------------------------------------------- */
HMM_Vec4 spline_CT(HMM_Mat4 *C, float t)
{
float t2 = t*t;
float t3 = t2*t;
HMM_Vec4 T = {t3, t2, t, 1};
return HMM_MulM4V4(*C, T);
float t2 = t * t;
float t3 = t2 * t;
HMM_Vec4 T = { t3, t2, t, 1.0f };
return HMM_MulM4V4(*C, T);
}
/* Construct the “geometry matrix” G from four 2D points, then multiply by B */
HMM_Mat4 make_C(const HMM_Vec2 *p, const HMM_Mat4 *B)
{
HMM_Mat4 G;
G.Columns[0].xy = p[0];
G.Columns[1].xy = p[1];
G.Columns[2].xy = p[2];
G.Columns[3].xy = p[3];
return HMM_MulM4(G, *B);
HMM_Mat4 G = HMM_M4(); // Zeroed out
// Only fill XY of each column; if you are storing 3D in HMM_Vec4, adapt as needed
G.Columns[0].XY = p[0];
G.Columns[1].XY = p[1];
G.Columns[2].XY = p[2];
G.Columns[3].XY = p[3];
return HMM_MulM4(G, *B);
}
/* Evaluate a single-segment cubic spline at parameter d in [0,1].
p must be 4 control points, m is the cubic basis matrix. */
HMM_Vec2 cubic_spline_d(HMM_Vec2 *p, HMM_Mat4 *m, float d)
{
HMM_Mat4 C = make_C(p, m);
return spline_CT(&C, d).xy;
HMM_Mat4 C = make_C(p, m);
HMM_Vec4 v4 = spline_CT(&C, d);
return v4.XY;
}
/* -------------------------------------------------------------------------
Convenience single-segment functions for each basis
(pos / tan / curv / wig all require the appropriate matrix)
Typically you pass p[0..3] as the 4 relevant control points.
------------------------------------------------------------------------- */
HMM_Vec2 cubic_hermite_pos(HMM_Vec2 *p, float d) { return cubic_spline_d(p, (HMM_Mat4 *)&cubic_hermite_m, d); }
HMM_Vec2 cubic_hermite_tan(HMM_Vec2 *p, float d) { return cubic_spline_d(p, (HMM_Mat4 *)&cubic_hermite_dm, d); }
HMM_Vec2 cubic_hermite_curv(HMM_Vec2 *p, float d){ return cubic_spline_d(p, (HMM_Mat4 *)&cubic_hermite_ddm, d); }
HMM_Vec2 cubic_hermite_wig(HMM_Vec2 *p, float d) { return cubic_spline_d(p, (HMM_Mat4 *)&cubic_hermite_dddm, d); }
HMM_Vec2 b_spline_pos(HMM_Vec2 *p, float d) { return cubic_spline_d(p, (HMM_Mat4 *)&b_spline_m, d); }
HMM_Vec2 b_spline_tan(HMM_Vec2 *p, float d) { return cubic_spline_d(p, (HMM_Mat4 *)&b_spline_dm, d); }
HMM_Vec2 b_spline_curv(HMM_Vec2 *p, float d){ return cubic_spline_d(p, (HMM_Mat4 *)&b_spline_ddm, d); }
HMM_Vec2 b_spline_wig(HMM_Vec2 *p, float d) { return cubic_spline_d(p, (HMM_Mat4 *)&b_spline_dddm, d); }
HMM_Vec2 bezier_pos(HMM_Vec2 *p, float d) { return cubic_spline_d(p, (HMM_Mat4 *)&bezier_m, d); }
HMM_Vec2 bezier_tan(HMM_Vec2 *p, float d) { return cubic_spline_d(p, (HMM_Mat4 *)&bezier_dm, d); }
HMM_Vec2 bezier_curv(HMM_Vec2 *p, float d){ return cubic_spline_d(p, (HMM_Mat4 *)&bezier_ddm, d); }
HMM_Vec2 bezier_wig(HMM_Vec2 *p, float d) { return cubic_spline_d(p, (HMM_Mat4 *)&bezier_dddm, d); }
/* -------------------------------------------------------------------------
Multi-segment sampling (“spline_v2”) for uniform division
------------------------------------------------------------------------- */
HMM_Vec2 *spline_v2(HMM_Vec2 *p, HMM_Mat4 *m, int segs)
{
HMM_Vec2 *ret = NULL;
if (segs < 2) return NULL;
// For a single 4-point segment, produce 'segs' points along [0..1).
// If you want the final 1.0 also, you can do <= 1.0 in the loop, etc.
HMM_Vec2 *ret = NULL;
if (segs < 2) return NULL;
HMM_Mat4 C = make_C(p, m);
float s = (float)1/segs;
for (float t = 0; t < 1; t += s)
arrput(ret, spline_CT(&C, t).xy);
return ret;
HMM_Mat4 C = make_C(p, m);
float s = 1.0f / (float)segs;
for (int i = 0; i <= segs; i++)
{
float t = s * i;
arrput(ret, spline_CT(&C, t).XY);
}
return ret;
}
/* -------------------------------------------------------------------------
Adaptive subdivision by min segment length
(spline2d_min_seg) for a single 4-point segment
------------------------------------------------------------------------- */
HMM_Vec2 *spline2d_min_seg(float u0, float u1, float min_seg, HMM_Mat4 *C, HMM_Vec2 *ret)
{
HMM_Vec2 a = spline_CT(C, u0).xy;
HMM_Vec2 b = spline_CT(C, u1).xy;
if (HMM_DistV2(a,b) > min_seg) {
float umid = (u0+u1)/2;
spline2d_min_seg(u0, umid, min_seg, C, ret);
spline2d_min_seg(umid, u1, min_seg, C, ret);
}
else
arrput(ret, b);
return ret;
HMM_Vec2 a = spline_CT(C, u0).XY;
HMM_Vec2 b = spline_CT(C, u1).XY;
if (HMM_DistV2(a, b) > min_seg)
{
float umid = 0.5f * (u0 + u1);
spline2d_min_seg(u0, umid, min_seg, C, ret);
spline2d_min_seg(umid, u1, min_seg, C, ret);
}
else
{
// We push 'b' so that we don't double-push 'a'
arrput(ret, b);
}
return ret;
}
/* Example: catmull_rom_min_seg -> subdiv for catmullrom over one segment
You would decide how to pick the 4 points from a,b,c,d, then run. */
HMM_Vec2 *catmull_rom_min_seg(HMM_Vec2 *a, HMM_Vec2 *b, HMM_Vec2 *c, HMM_Vec2 *d, float min_seg)
{
HMM_Vec2 *ret = NULL;
arrsetcap(ret, 1000);
arrput(ret, *b);
// spline2d_min_seg(0, 1, min_seg, &C, ret);
return ret;
HMM_Vec2 *ret = NULL;
arrsetcap(ret, 1000);
// Build the matrix for these four points
HMM_Vec2 p[4] = { *a, *b, *c, *d };
HMM_Mat4 C = make_C(p, &catmull_rom_m);
// Always push the starting point (b in your original code was the second ctrl point, etc.)
// But usually we want the first actual point in the segment:
arrput(ret, cubic_spline_d(p, (HMM_Mat4*)&catmull_rom_m, 0.0f));
// Actually subdiv
spline2d_min_seg(0.0f, 1.0f, min_seg, &C, ret);
return ret;
}
/* -------------------------------------------------------------------------
Adaptive subdivision by “max angle” proxy
(spline2d_min_angle_2) for single 4-point segment
------------------------------------------------------------------------- */
HMM_Vec2 *spline2d_min_angle_2(float u0, float u1, float max_angle, HMM_Mat4 *C, HMM_Vec2 *arr)
{
float ustep = (u1-u0)/4;
float um0 = u0+ustep;
float um1 = u0+(ustep*2);
float um2 = u0+(ustep*3);
// Heuristic approach: sample midpoints, check “chord vs polyline” difference
float ustep = (u1 - u0) / 4.0f;
float um0 = u0 + ustep;
float um1 = u0 + 2.0f * ustep;
float um2 = u0 + 3.0f * ustep;
HMM_Vec2 m0 = spline_CT(C, um0)._2;
HMM_Vec2 m1 = spline_CT(C, um1)._2;
HMM_Vec2 m2 = spline_CT(C,um2)._2;
HMM_Vec2 m0 = spline_CT(C, um0).XY;
HMM_Vec2 m1 = spline_CT(C, um1).XY;
HMM_Vec2 m2 = spline_CT(C, um2).XY;
HMM_Vec2 a = spline_CT(C,u0)._2;
HMM_Vec2 b = spline_CT(C,u1)._2;
HMM_Vec2 a = spline_CT(C, u0).XY;
HMM_Vec2 b = spline_CT(C, u1).XY;
float ab = HMM_DistV2(a,b);
float cdist = HMM_DistV2(a,m0) + HMM_DistV2(m0,m1) + HMM_DistV2(m1,m2) + HMM_DistV2(m2,b);
// Chord = distance from a to b
float chord = HMM_DistV2(a, b);
// Polyline = a->m0->m1->m2->b
float cdist = HMM_DistV2(a, m0)
+ HMM_DistV2(m0, m1)
+ HMM_DistV2(m1, m2)
+ HMM_DistV2(m2, b);
if (cdist-ab > max_angle) {
arr = spline2d_min_angle_2(u0,um1,max_angle,C,arr);
arr = spline2d_min_angle_2(um1,u1,max_angle,C,arr);
} else
arrput(arr,b);
return arr;
// If the difference is bigger than some threshold (max_angle),
// subdivide. Otherwise, keep it.
if ((cdist - chord) > max_angle)
{
arr = spline2d_min_angle_2(u0, um1, max_angle, C, arr);
arr = spline2d_min_angle_2(um1, u1, max_angle, C, arr);
}
else
{
// We accept “b” as a new point
arrput(arr, b);
}
return arr;
}
HMM_Vec2 *spline_min_angle(HMM_Vec2 *p, const HMM_Mat4 *B, float min_angle, HMM_Vec2 *arr)
{
HMM_Mat4 C = make_C(p, B);
arr = spline2d_min_angle_2(0,1,min_angle, &C, arr);
return arr;
HMM_Mat4 C = make_C(p, B);
// Subdivide from 0..1
float u0 = 0.0f, u1 = 1.0f;
// Usually we want to ensure the start point is in arr:
HMM_Vec2 startPt = spline_CT(&C, u0).XY;
if (arrlen(arr) == 0) {
arrput(arr, startPt);
}
// Now subdiv for angle
arr = spline2d_min_angle_2(u0, u1, min_angle, &C, arr);
return arr;
}
/* Example: catmull_rom_ma_v2 uses “min_angle” over multiple segments
Each 4 consecutive points is one segment. We do this for all segments. */
HMM_Vec2 *catmull_rom_ma_v2(HMM_Vec2 *cp, float ma)
{
if (arrlen(cp) < 4) return NULL;
HMM_Vec2 *ret = NULL;
int n = arrlen(cp);
if (n < 4) return NULL;
int segments = arrlen(cp)-3;
arrsetcap(ret,segments*(ma>=2 ? 3 : 7));
arrput(ret, cp[1]);
for (int i = 0; i < arrlen(cp)-3; i++)
ret = spline_min_angle(&cp[i], &catmull_rom_m, ma, ret);
HMM_Vec2 *ret = NULL;
// Pre-allocate some capacity
arrsetcap(ret, (n-3) * 8);
return ret;
// For convenience, let's always ensure we push the very first point:
arrput(ret, cp[0]);
// For each segment [i, i+1, i+2, i+3], adaptively sample
// Then move i by 1 each time if you want CatmullRom in “overlapped” fashion
for (int i = 0; i < n - 3; i++)
{
// p[i..i+3]
ret = spline_min_angle(&cp[i], &catmull_rom_m, ma, ret);
}
return ret;
}
/* Example: do the same with Bezier in “cubic-bezier” style (control points in groups of 3 “handles”) */
HMM_Vec2 *bezier_cb_ma_v2(HMM_Vec2 *cp, float ma)
{
if (arrlen(cp) < 4) return NULL;
HMM_Vec2 *ret = NULL;
int segments = arrlen(cp)-3;
arrsetcap(ret,segments*(ma>=2?3:7));
arrput(ret,cp[0]);
for (int i = 0; i < arrlen(cp)-3; i+=3)
ret = spline_min_angle(&cp[i], &bezier_m, ma, ret);
int n = arrlen(cp);
// Typically a Bezier “chain” would use control points in multiples of 3 + 1, etc.
// E.g. p[0] is start, p[1..3] are control handles for first segment, then p[3..6] for second, etc.
// Adjust logic to your liking.
if (n < 4) return NULL;
return ret;
HMM_Vec2 *ret = NULL;
arrsetcap(ret, (n/3) * 8);
// First point
arrput(ret, cp[0]);
// For each cubic Bezier segment: i += 3
for (int i = 0; i < n - 3; i += 3)
{
ret = spline_min_angle(&cp[i], &bezier_m, ma, ret);
}
return ret;
}
HMM_Vec2 catmull_rom_query(HMM_Vec2 *cp, float d, const HMM_Mat4 *G)
static HMM_Vec2 catmull_rom_query_internal(HMM_Vec2 *cp, float d, const HMM_Mat4 *M)
{
if (arrlen(cp) < 4 || d < 0 || d > 1) return HMM_V2(0,0);
int n = arrlen(cp);
if (n < 4) return HMM_V2(0,0);
int segs = arrlen(cp)-3;
float d_per_seg = (float)1/segs;
float maxi = d_per_seg;
int p1 = 2;
while (maxi < d) {
maxi += d_per_seg;
p1++;
}
// Number of segments:
int seg_count = n - 3;
// Scale d in [0..1] -> which segment?
float segf = d * seg_count;
int seg_idx = (int) floorf(segf);
if (seg_idx >= seg_count) seg_idx = seg_count - 1;
if (seg_idx < 0) seg_idx = 0;
return cp[0];
// return cubic_spline_d(p0, cp[p1], cp[p1+1], p3, G, d);
// Local parameter in [0..1]
float u = segf - seg_idx;
// The control points for that segment are cp[ seg_idx .. seg_idx+3 ]
return cubic_spline_d(cp + seg_idx, (HMM_Mat4 *)M, u);
}
float spline_seglen(float t0, float t1, float max_angle, HMM_Mat4 *Cd, HMM_Mat4 *C)
HMM_Vec2 catmull_rom_pos(HMM_Vec2 *cp, float d)
{
float total = 0;
float step = 0.1;
for (float i = t0; i < t1; i += step)
total += HMM_LenV2(spline_CT(Cd, i).xy) * step;
return total;
/* Estimation via max angle */
/* float total = 0.0;
float tmid = (t0+t1)/2;
HMM_Vec2 a = spline_CT(C, t0).xy;
HMM_Vec2 b = spline_CT(C, t1).xy;
HMM_Vec2 m = spline_CT(C, tmid).xy;
if (HMM_AngleV2(m,b) > max_angle) {
total += spline_seglen(t0, tmid, max_angle, Cd, C);
total += spline_seglen(tmid, t1, max_angle, Cd, C);
} else
return HMM_LenV2(spline_CT(Cd, t0).xy)*(t1-t0);
return total;
*/
return catmull_rom_query_internal(cp, d, &catmull_rom_m);
}
HMM_Vec2 catmull_rom_tan(HMM_Vec2 *cp, float d)
{
return catmull_rom_query_internal(cp, d, &catmull_rom_dm);
}
HMM_Vec2 catmull_rom_curv(HMM_Vec2 *cp, float d)
{
return catmull_rom_query_internal(cp, d, &catmull_rom_ddm);
}
HMM_Vec2 catmull_rom_wig(HMM_Vec2 *cp, float d)
{
return catmull_rom_query_internal(cp, d, &catmull_rom_dddm);
}
/* -------------------------------------------------------------------------
Approximate length of a single 4-point cubic spline segment by
numeric integration (or sampling) of the velocity magnitude.
“spline_seglen” below does a quick sampling approach.
------------------------------------------------------------------------- */
float spline_seglen(float t0, float t1, int steps, HMM_Mat4 *Cd, HMM_Mat4 *C)
{
// Simple uniform sampling of the tangent magnitude
float total = 0.0f;
float dt = (t1 - t0) / (float) steps;
for (int i = 0; i < steps; i++)
{
float t = t0 + (i + 0.5f) * dt; // midpoint rule
// derivative at t
HMM_Vec2 vel = spline_CT(Cd, t).XY;
float speed = HMM_LenV2(vel);
total += speed * dt;
}
return total;
}
/* Summation of lengths across all CatmullRom segments. */
float catmull_rom_len(HMM_Vec2 *cp)
{
float len = 0.0;
int segs = arrlen(cp)-3;
float d_per_seg = (float)1/segs;
float maxi = d_per_seg;
for (int i = 0; i < arrlen(cp)-3; i++) {
HMM_Mat4 C = make_C(&cp[i], &catmull_rom_m);
HMM_Mat4 Cd = make_C(&cp[i], &catmull_rom_dm);
len += spline_seglen(0, 1, 0.1, &Cd, &C);
}
return len;
}
int stepsPerSegment = 64;
float len = 0.0f;
int n = arrlen(cp);
if (n < 4) return 0.0f;
/* d is from 0 to 1 for the entire spline */
HMM_Vec2 catmull_rom_pos(HMM_Vec2 *cp, float d) { return catmull_rom_query(cp,d,&catmull_rom_m); }
HMM_Vec2 catmull_rom_tan(HMM_Vec2 *cp, float d) { return catmull_rom_query(cp,d,&catmull_rom_dm); }
HMM_Vec2 catmull_rom_curv(HMM_Vec2 *cp, float d) { return catmull_rom_query(cp,d,&catmull_rom_ddm); }
HMM_Vec2 catmull_rom_wig(HMM_Vec2 *cp, float d) { return catmull_rom_query(cp,d,&catmull_rom_dddm); }
for (int i = 0; i < n - 3; i++)
{
// Build the position matrix & derivative matrix for this segment
HMM_Mat4 C = make_C(&cp[i], &catmull_rom_m);
HMM_Mat4 Cd = make_C(&cp[i], &catmull_rom_dm);
// integrate from 0..1
len += spline_seglen(0.0f, 1.0f, stepsPerSegment, &Cd, &C);
}
return len;
}
HMM_Vec2 catmull_rom_closest(HMM_Vec2 *cp, HMM_Vec2 p)
{
return p;
int n = arrlen(cp);
if (n < 4) return p;
float bestDist = FLT_MAX;
HMM_Vec2 bestPt = p;
int steps = 64; // more steps => more accurate
for (int seg = 0; seg < n - 3; seg++)
{
// Build a single-segment matrix
HMM_Vec2 segCP[4] = { cp[seg], cp[seg+1], cp[seg+2], cp[seg+3] };
HMM_Mat4 C = make_C(segCP, &catmull_rom_m);
for (int i = 0; i <= steps; i++)
{
float t = (float)i / steps;
HMM_Vec2 pt = spline_CT(&C, t).XY;
float dist = HMM_DistV2(p, pt);
if (dist < bestDist)
{
bestDist = dist;
bestPt = pt;
}
}
}
return bestPt;
}

View File

@@ -3,21 +3,83 @@
#include "HandmadeMath.h"
HMM_Vec2 *catmull_rom_ma_v2(HMM_Vec2 *cp, float ma);
HMM_Vec3 *catmull_rom_ma_v3(HMM_Vec3 *cp, float ma);
HMM_Vec4 *catmull_rom_ma_v4(HMM_Vec4 *cp, float ma);
#ifdef __cplusplus
extern "C" {
#endif
/*
These were already in your original header:
*/
// Adaptive CatmullRom in 2D / 3D / 4D (by minimum angle):
HMM_Vec2 *catmull_rom_ma_v2(HMM_Vec2 *cp, float ma);
HMM_Vec3 *catmull_rom_ma_v3(HMM_Vec3 *cp, float ma); /* not yet implemented in .c, placeholder */
HMM_Vec4 *catmull_rom_ma_v4(HMM_Vec4 *cp, float ma); /* not yet implemented in .c, placeholder */
// Adaptive Bezier in 2D (by minimum angle):
HMM_Vec2 *bezier_cb_ma_v2(HMM_Vec2 *cp, float ma);
// Generic “single-segment” query for 2D control points + basis matrix:
HMM_Vec2 spline_query(HMM_Vec2 *cp, float d, HMM_Mat4 *basis);
HMM_Vec2 catmull_rom_pos(HMM_Vec2 *cp, float d);
HMM_Vec2 catmull_rom_tan(HMM_Vec2 *cp, float d);
HMM_Vec2 catmull_rom_curv(HMM_Vec2 *cp, float d);
HMM_Vec2 catmull_rom_wig(HMM_Vec2 *cp, float d);
// CatmullRom “entire spline” queries:
HMM_Vec2 catmull_rom_pos(HMM_Vec2 *cp, float d); // position
HMM_Vec2 catmull_rom_tan(HMM_Vec2 *cp, float d); // tangent
HMM_Vec2 catmull_rom_curv(HMM_Vec2 *cp, float d); // curvature
HMM_Vec2 catmull_rom_wig(HMM_Vec2 *cp, float d); // 3rd derivative (“wiggle”)
// Computes approximate length of a 2D CatmullRom spline:
float catmull_rom_len(HMM_Vec2 *cp);
/* Returns closest point on a curve given a point p */
// Returns closest point on a 2D CatmullRom curve given an external 2D point `p`:
HMM_Vec2 catmull_rom_closest(HMM_Vec2 *cp, HMM_Vec2 p);
/*
Additional convenience functions for *single-segment* cubic splines:
Each of these expects exactly 4 control points in `p[0..3]`,
and a parameter t in [0..1]. They pick the appropriate matrix internally.
*/
// Hermite:
HMM_Vec2 cubic_hermite_pos(HMM_Vec2 *p, float d);
HMM_Vec2 cubic_hermite_tan(HMM_Vec2 *p, float d);
HMM_Vec2 cubic_hermite_curv(HMM_Vec2 *p, float d);
HMM_Vec2 cubic_hermite_wig(HMM_Vec2 *p, float d);
// B-spline:
HMM_Vec2 b_spline_pos(HMM_Vec2 *p, float d);
HMM_Vec2 b_spline_tan(HMM_Vec2 *p, float d);
HMM_Vec2 b_spline_curv(HMM_Vec2 *p, float d);
HMM_Vec2 b_spline_wig(HMM_Vec2 *p, float d);
// Bezier:
HMM_Vec2 bezier_pos(HMM_Vec2 *p, float d);
HMM_Vec2 bezier_tan(HMM_Vec2 *p, float d);
HMM_Vec2 bezier_curv(HMM_Vec2 *p, float d);
HMM_Vec2 bezier_wig(HMM_Vec2 *p, float d);
/*
Uniform sampling of a *single* 4-point segment in 2D:
Returns an array of points (stb_ds dynamic array).
*/
HMM_Vec2 *spline_v2(HMM_Vec2 *p, HMM_Mat4 *m, int segs);
/*
Adaptive subdivision routines (single-segment) in 2D:
- Subdivide by min segment length
- Subdivide by “max angle” proxy
*/
HMM_Vec2 *spline2d_min_seg(float u0, float u1, float min_seg, HMM_Mat4 *C, HMM_Vec2 *ret);
HMM_Vec2 *catmull_rom_min_seg(HMM_Vec2 *a, HMM_Vec2 *b, HMM_Vec2 *c, HMM_Vec2 *d, float min_seg);
HMM_Vec2 *spline2d_min_angle_2(float u0, float u1, float max_angle, HMM_Mat4 *C, HMM_Vec2 *arr);
HMM_Vec2 *spline_min_angle(HMM_Vec2 *p, const HMM_Mat4 *B, float min_angle, HMM_Vec2 *arr);
#ifdef __cplusplus
}
#endif
#endif /* SPLINE_H */

View File

@@ -1,17 +1,18 @@
#include "sprite.h"
static sprite model = {
.affine = {x:0,y:0,w:0,h:0},
.image = JS_UNDEFINED,
.affine = {.x = 0, .y = 0, .w = 0, .h = 0},
.tex = NULL,
.uv = {x:0,y:0,w:1,h:1},
.uv = {.x = 0, .y = 0, .w = 1, .h = 1},
.layer = 0,
.color = {1,1,1,1}
.color = {1, 1, 1, 1}
};
sprite *make_sprite()
{
sprite *sprite = malloc(sizeof(*sprite));
*sprite = model;
sprite->image = JS_UNDEFINED;
return sprite;
}

View File

@@ -38,6 +38,7 @@
// Contributors:
// Arpad Goretity (bugfix)
// Alan Hickman (hex floats)
// github:mundusnine (bugfix)
//
// LICENSE
//
@@ -562,7 +563,6 @@ int stb_c_lexer_get_token(stb_lexer *lexer)
{
int n = 0;
lexer->string = lexer->string_storage;
lexer->string_len = n;
do {
if (n+1 >= lexer->string_storage_len)
return stb__clex_token(lexer, CLEX_parse_error, p, p+n);
@@ -576,6 +576,7 @@ int stb_c_lexer_get_token(stb_lexer *lexer)
STB_C_LEX_DOLLAR_IDENTIFIER( || p[n] == '$' )
);
lexer->string[n] = 0;
lexer->string_len = n;
return stb__clex_token(lexer, CLEX_id, p, p+n-1);
}

View File

@@ -1,4 +1,4 @@
/* stb_image - v2.29 - public domain image loader - http://nothings.org/stb
/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb
no warranty implied; use at your own risk
Do this:
@@ -48,6 +48,7 @@ LICENSE
RECENT REVISION HISTORY:
2.30 (2024-05-31) avoid erroneous gcc warning
2.29 (2023-05-xx) optimizations
2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
@@ -5159,9 +5160,11 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
// non-paletted with tRNS = constant alpha. if header-scanning, we can stop now.
if (scan == STBI__SCAN_header) { ++s->img_n; return 1; }
if (z->depth == 16) {
for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning
tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
} else {
for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
for (k = 0; k < s->img_n && k < 3; ++k)
tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
}
}
break;

File diff suppressed because it is too large Load Diff

View File

@@ -54,7 +54,7 @@
// Hou Qiming Derek Vinyard
// Rob Loach Cort Stratton
// Kenney Phillis Jr. Brian Costabile
// Ken Voskuil (kaesve)
// Ken Voskuil (kaesve) Yakov Galka
//
// VERSION HISTORY
//
@@ -4604,6 +4604,8 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
scale_y = -scale_y;
{
// distance from singular values (in the same units as the pixel grid)
const float eps = 1./1024, eps2 = eps*eps;
int x,y,i,j;
float *precompute;
stbtt_vertex *verts;
@@ -4616,15 +4618,15 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y;
float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y;
float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0));
precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist;
precompute[i] = (dist < eps) ? 0.0f : 1.0f / dist;
} else if (verts[i].type == STBTT_vcurve) {
float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y;
float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y;
float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y;
float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2;
float len2 = bx*bx + by*by;
if (len2 != 0.0f)
precompute[i] = 1.0f / (bx*bx + by*by);
if (len2 >= eps2)
precompute[i] = 1.0f / len2;
else
precompute[i] = 0.0f;
} else
@@ -4689,8 +4691,8 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
float a = 3*(ax*bx + ay*by);
float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by);
float c = mx*ax+my*ay;
if (a == 0.0) { // if a is 0, it's linear
if (b != 0.0) {
if (STBTT_fabs(a) < eps2) { // if a is 0, it's linear
if (STBTT_fabs(b) >= eps2) {
res[num++] = -c/b;
}
} else {

View File

@@ -13,14 +13,14 @@ static transform model = {
.cache = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
.gcache = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
.dirty = 0,
.jsparent = JS_UNDEFINED,
.change_hook = JS_UNDEFINED
};
transform *make_transform()
{
transform *t = malloc(sizeof(transform));
*t = model;
t->jsparent = JS_UNDEFINED;
t->change_hook = JS_UNDEFINED;
return t;
}

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

View File

@@ -1,7 +0,0 @@
[wrap-git]
url = https://github.com/johnalanbrook/qjs-dmon.git
revision = head
depth = 1
[provide]
qjs-dmon = qjs_dmon_dep

View File

@@ -1,7 +0,0 @@
[wrap-git]
url = https://github.com/johnalanbrook/qjs-enet.git
revision = head
depth = 1
[provide]
qjs-enet = qjs_enet_dep

View File

@@ -1,7 +0,0 @@
[wrap-git]
url = https://github.com/johnalanbrook/cnota.git
revision = head
depth = 1
[provide]
qjs-nota = qjs_nota_dep

View File

@@ -1,7 +0,0 @@
[wrap-git]
url = https://github.com/johnalanbrook/qjs-soloud.git
revision = head
depth = 1
[provide]
qjs-soloud = qjs_soloud_dep

View File

@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/johnalanbrook/quickjs.git
url = https://gitea.pockle.world/john/dull.git
revision = head
depth = 1

4
subprojects/sdl3.wrap Normal file
View File

@@ -0,0 +1,4 @@
[wrap-git]
url = https://github.com/libsdl-org/SDL.git
revision = release-3.2.4
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);
}

191
tests/enet.js Normal file
View File

@@ -0,0 +1,191 @@
var os = use('os');
var enet = use('enet');
var json = use('json'); // Some QuickJS environments need this import for JSON
////////////////////////////////////////////////////////////////////////////////
// 1. Initialization
////////////////////////////////////////////////////////////////////////////////
// Make sure ENet is initialized before we do anything else
enet.initialize();
////////////////////////////////////////////////////////////////////////////////
// 2. "Framework": test runner & polling helper
////////////////////////////////////////////////////////////////////////////////
let results = [];
/**
* Simple test runner. Each test is given a name and a function.
* If the function throws an Error, the test is marked failed.
*/
function runTest(testName, testFunc) {
console.log(`=== Running Test: ${testName} ===`);
try {
testFunc();
results.push({ testName, passed: true });
} catch (err) {
console.log(`Test "${testName}" failed: ${err}`);
results.push({ testName, passed: false, error: err });
}
}
/**
* runSteps polls both client and server for `steps` iterations,
* each iteration calling service() with a small timeout. Any events
* are captured and returned in arrays for further inspection.
*
* @param {Object} client - the client ENet host
* @param {Object} server - the server ENet host
* @param {number} steps - how many iterations to process
* @param {boolean} printEvents - whether to log events to console
* @param {number} timeout - milliseconds to pass to service()
* @returns { clientEvents, serverEvents } arrays of all events captured
*/
function runSteps(client, server, steps = 5, printEvents = false, timeout = 10) {
let clientEvents = [];
let serverEvents = [];
for (let i = 0; i < steps; i++) {
// Poll the client
client.service((evt) => {
if (evt) {
clientEvents.push(evt);
if (printEvents) {
console.log("client:" + json.encode(evt));
}
}
}, timeout);
// Poll the server
server.service((evt) => {
if (evt) {
serverEvents.push(evt);
if (printEvents) {
console.log("server:" + json.encode(evt));
}
}
}, timeout);
}
return { clientEvents, serverEvents };
}
////////////////////////////////////////////////////////////////////////////////
// 3. Actual Tests
////////////////////////////////////////////////////////////////////////////////
let serverHost = null;
let clientHost = null;
let clientPeer = null;
runTest("Create Server", () => {
// Bind on 127.0.0.1:12345
serverHost = enet.create_host("127.0.0.1:12345");
if (!serverHost) {
throw new Error("Failed to create server host");
}
});
runTest("Create Client", () => {
clientHost = enet.create_host();
if (!clientHost) {
throw new Error("Failed to create client host");
}
});
runTest("Connect Client to Server", () => {
clientPeer = clientHost.connect("127.0.0.1", 12345);
if (!clientPeer) {
throw new Error("Failed to create client->server peer");
}
// Poll both sides for a few steps so the connection can succeed
const { clientEvents, serverEvents } = runSteps(clientHost, serverHost, 5, true);
// Verify the server got a 'connect' event
let connectEvent = serverEvents.find(evt => evt.type === "connect");
if (!connectEvent) {
throw new Error("Server did not receive a connect event");
}
});
runTest("Send Data from Client to Server", () => {
// Send some JSON object from client -> server
clientPeer.send({ hello: "HELLO" });
// Process for a few steps so the data actually arrives
const { clientEvents, serverEvents } = runSteps(clientHost, serverHost, 5, true);
// The server should get a 'receive' event
let receiveEvent = serverEvents.find(evt => evt.type === "receive");
if (!receiveEvent) {
throw new Error("Server did not receive data from the client");
}
// Check the payload
if (!receiveEvent.data || receiveEvent.data.hello !== "HELLO") {
throw new Error(`Server got unexpected data: ${JSON.stringify(receiveEvent.data)}`);
}
});
runTest("Broadcast from Server to Client", () => {
// The server broadcasts a JSON string
serverHost.broadcast({ broadcast: "HelloAll" });
// Let data flow
const { clientEvents, serverEvents } = runSteps(clientHost, serverHost, 5, true);
// Client should get a 'receive' event with broadcast data
let broadcastEvent = clientEvents.find(evt => evt.type === "receive");
if (!broadcastEvent) {
throw new Error("Client did not receive broadcast data");
}
// The broadcastEvent.data should be an object with broadcast="HelloAll"
if (!broadcastEvent.data || broadcastEvent.data.broadcast !== "HelloAll") {
throw new Error(`Client received unexpected broadcast: ${JSON.stringify(broadcastEvent.data)}`);
}
});
runTest("Disconnect Client", () => {
// Disconnect from the client side
clientPeer.disconnect();
// Let both sides see the disconnect
const { clientEvents, serverEvents } = runSteps(clientHost, serverHost, 5, true);
// The server should eventually get a "disconnect"
let disconnectEvent = serverEvents.find(evt => evt.type === "disconnect");
if (!disconnectEvent) {
throw new Error("Server never received a disconnect event");
}
});
runTest("Deinitialize ENet", () => {
enet.deinitialize();
});
////////////////////////////////////////////////////////////////////////////////
// 4. Print Summary & Exit
////////////////////////////////////////////////////////////////////////////////
console.log("\n=== Test Summary ===");
let passedCount = 0;
results.forEach(({ testName, passed, error }, idx) => {
console.log(`Test ${idx + 1}: ${testName} - ${passed ? "PASSED" : "FAILED"}`);
if (!passed && error) {
console.log(" Reason:", error);
}
if (passed) passedCount++;
});
console.log(`\nResult: ${passedCount}/${results.length} tests passed`);
if (passedCount < results.length) {
console.log("Overall: FAILED");
os.exit(1);
} else {
console.log("Overall: PASSED");
os.exit(0);
}

262
tests/nota.js Normal file
View File

@@ -0,0 +1,262 @@
var nota = use('nota');
var os = use('os');
// Helper function to convert hex string to ArrayBuffer
function hexToBuffer(hex) {
let bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
}
return bytes.buffer;
}
// Helper function to convert ArrayBuffer to hex string
function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
.toLowerCase();
}
var EPSILON = 1e-12
// Deep comparison function for objects and arrays
function deepCompare(expected, actual, path = '') {
if (expected === actual) return { passed: true, messages: [] };
// If both are numbers, compare with tolerance
if (typeof expected === 'number' && typeof actual === 'number') {
// e.g. handle NaN specially if you like:
if (isNaN(expected) && isNaN(actual)) {
return { passed: true, messages: [] };
}
const diff = Math.abs(expected - actual);
// Pass the test if difference is within EPSILON
if (diff <= EPSILON) {
return { passed: true, messages: [] };
}
return {
passed: false,
messages: [
`Value mismatch at ${path}: expected ${expected}, got ${actual}`,
`Difference of ${diff} is larger than tolerance ${EPSILON}`
]
};
}
if (expected instanceof ArrayBuffer && actual instanceof ArrayBuffer) {
const expArray = Array.from(new Uint8Array(expected));
const actArray = Array.from(new Uint8Array(actual));
return deepCompare(expArray, actArray, path);
}
if (actual instanceof ArrayBuffer) {
actual = Array.from(new Uint8Array(actual));
}
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 };
}
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)}`]
};
}
// Extended test cases covering all Nota types from documentation
var testarr = []
var hex = "a374"
for (var i = 0; i < 500; i++) {
testarr.push(1)
hex += "61"
}
var testCases = [
// Integer tests
{ input: 0, expectedHex: "60" },
{ input: 2023, expectedHex: "e08f67" },
{ input: -1, expectedHex: "69" },
{ input: 7, expectedHex: "67" },
{ input: -7, expectedHex: "6f" },
{ input: 1023, expectedHex: "e07f" },
{ input: -1023, expectedHex: "ef7f" },
// Symbol tests
{ input: undefined, expectedHex: "70" },
{ input: false, expectedHex: "72" },
{ input: true, expectedHex: "73" },
// Note: private (78) and system (79) require following records, tested below
// Floating Point tests
{ input: -1.01, expectedHex: "5a65" },
{ input: 98.6, expectedHex: "51875a" },
{ input: -0.5772156649, expectedHex: "d80a95c0b0bd69" },
{ input: -1.00000000000001, expectedHex: "d80e96deb183e98001" },
{ input: -10000000000000, expectedHex: "c80d01" },
// Text tests
{ input: "", expectedHex: "10" },
{ input: "cat", expectedHex: "13636174" },
{ input: "U+1F4A9 「うんち絵文字」 «💩»",
expectedHex: "9014552b314634413920e00ce046e113e06181fa7581cb0781b657e00d20812b87e929813b" },
// Blob tests
{ input: new Uint8Array([0xFF, 0xAA]).buffer, expectedHex: "8010ffaa" },
{ input: new Uint8Array([0b11110000, 0b11100011, 0b00100000, 0b10000000]).buffer,
expectedHex: "8019f0e32080" }, // 25 bits example padded to 32 bits
{ input: testarr, expectedHex: hex },
// Array tests
{ input: [], expectedHex: "20" },
{ input: [1, 2, 3], expectedHex: "23616263" },
{ input: [-1, 0, 1.5], expectedHex: "2369605043" },
// Record tests
{ input: {}, expectedHex: "30" },
{ input: { a: 1, b: 2 }, expectedHex: "32116161116262" },
// Complex nested structures
{ input: {
num: 42,
arr: [1, -1, 2.5],
str: "test",
obj: { x: true }
},
expectedHex: "34216e756d622a2173747214746573742161727223616965235840216f626a21117873" },
// Private prefix test (requires record)
{ input: { private: { address: "test" } },
expectedHex: "317821616464726573731474657374" },
// System prefix test (requires record)
{ input: { system: { msg: "hello" } },
expectedHex: "3179216d73671568656c6c6f" },
{ input: [ { system: {msg: "hello" } }, {
num: 42,
arr: [1, -1, 2.5],
str: "test",
obj: { x: true }
} ], expectedHex: "223179216d73671568656c6c6f34216e756d622a2173747214746573742161727223616965235840216f626a21117873" },
// Additional edge cases
{ input: new Uint8Array([]).buffer, expectedHex: "00" }, // Empty blob
{ input: [[]], expectedHex: "2120" }, // Nested empty array
{ input: { "": "" }, expectedHex: "311010" }, // Empty string key and value
{ input: 1e-10, expectedHex: "d00a01" }, // Small floating point
];
// Run tests and collect results
let results = [];
let testCount = 0;
for (let test of testCases) {
testCount++;
let testName = `Test ${testCount}: ${JSON.stringify(test.input)}`;
let passed = true;
let messages = [];
// Test encoding
let encoded = nota.encode(test.input);
if (!(encoded instanceof ArrayBuffer)) {
passed = false;
messages.push("Encode should return ArrayBuffer");
} else {
let encodedHex = bufferToHex(encoded);
if (encodedHex !== test.expectedHex.toLowerCase()) {
messages.push(
`Hex encoding differs (informational):
Expected: ${test.expectedHex}
Got: ${encodedHex}`
);
}
// Test decoding
let decoded = nota.decode(encoded);
let expected = test.input;
// Normalize ArrayBuffer and special cases for comparison
if (expected instanceof ArrayBuffer) {
expected = Array.from(new Uint8Array(expected));
}
if (decoded instanceof ArrayBuffer) {
decoded = Array.from(new Uint8Array(decoded));
}
// Handle private/system prefix objects
if (expected && (expected.private || expected.system)) {
const key = expected.private ? 'private' : 'system';
expected = { [key]: expected[key] };
}
const compareResult = deepCompare(expected, decoded);
if (!compareResult.passed) {
passed = false;
messages.push("Decoding failed:");
messages.push(...compareResult.messages);
}
}
// Record result
results.push({ testName, passed, messages });
// Print detailed results on first failure
if (!passed) {
console.log(`\nDetailed Failure Report for ${testName}:`);
console.log(`Input: ${JSON.stringify(test.input)}`);
console.log(messages.join("\n"));
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)
});
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);
}