Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af0996f6ab | ||
|
|
6c390aeae3 | ||
|
|
e9519484cc | ||
|
|
b7da920f31 | ||
|
|
35647a5c5b | ||
|
|
8ea8f7fec7 | ||
|
|
5254b84704 | ||
|
|
f728a217c9 | ||
|
|
c27817b73a | ||
|
|
7ea79c8ced | ||
|
|
fb10c63882 | ||
|
|
96ef8ccba3 | ||
|
|
6148f18340 | ||
|
|
867a18e788 | ||
|
|
387c4364b5 | ||
|
|
60dce4a08f | ||
|
|
d2325c20bd | ||
|
|
29295607df |
239
.github/workflows/build.yml
vendored
239
.github/workflows/build.yml
vendored
@@ -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,112 @@ 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]
|
||||
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 }}
|
||||
@@ -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).
|
||||
|
||||
46
docs/api/modules/camera.md
Normal file
46
docs/api/modules/camera.md
Normal 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
76
docs/api/modules/debug.md
Normal 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.
|
||||
|
||||
39
docs/api/modules/dmon.md
Normal file
39
docs/api/modules/dmon.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# dmon
|
||||
|
||||
### watch() <sub>function</sub>
|
||||
|
||||
Start watching the root directory, recursively.
|
||||
|
||||
This function begins monitoring the specified directory and its subdirectories recursively for events such as file creation, deletion, modification, or movement. Events are queued and can be retrieved by calling poll.
|
||||
|
||||
:throws: An error if dmon is already watching.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### unwatch() <sub>function</sub>
|
||||
|
||||
Stop watching the currently monitored directory.
|
||||
|
||||
This function halts filesystem monitoring for the directory previously set by watch. It clears the watch state, allowing a new watch to be started.
|
||||
|
||||
:throws: An error if no directory is currently being watched.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### poll(callback) <sub>function</sub>
|
||||
|
||||
Retrieve and process queued filesystem events.
|
||||
|
||||
This function dequeues all pending filesystem events and invokes the provided callback for each one. The callback receives an event object with properties: 'action' (string: "create", "delete", "modify", or "move"), 'root' (string: watched directory), 'file' (string: affected file path), and 'old' (string: previous file path for move events, empty if not applicable).
|
||||
|
||||
|
||||
|
||||
**callback**: A function to call for each event, receiving an event object as its argument.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
45
docs/api/modules/enet.md
Normal file
45
docs/api/modules/enet.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# enet
|
||||
|
||||
### initialize() <sub>function</sub>
|
||||
|
||||
|
||||
Initialize the ENet library. Must be called before using any ENet functionality.
|
||||
Throws an error if initialization fails.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### deinitialize() <sub>function</sub>
|
||||
|
||||
|
||||
Deinitialize the ENet library, cleaning up all resources. Call this when you no longer
|
||||
need any ENet functionality.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### create_host(address) <sub>function</sub>
|
||||
|
||||
|
||||
Create an ENet host for either a client-like unbound host or a server bound to a specific
|
||||
address and port:
|
||||
|
||||
- If no argument is provided, creates an unbound "client-like" host with default settings
|
||||
(maximum 32 peers, 2 channels, unlimited bandwidth).
|
||||
- If you pass an "ip:port" string (e.g. "127.0.0.1:7777"), it creates a server bound to
|
||||
that address. The server supports up to 32 peers, 2 channels, and unlimited bandwidth.
|
||||
|
||||
Throws an error if host creation fails for any reason.
|
||||
|
||||
omit to create an unbound client-like host.
|
||||
|
||||
|
||||
**address**: (optional) A string in 'ip:port' format to bind the host (server), or
|
||||
|
||||
|
||||
**Returns**: An ENetHost object.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
30
docs/api/modules/nota.md
Normal file
30
docs/api/modules/nota.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# nota
|
||||
|
||||
### encode(value) <sub>function</sub>
|
||||
|
||||
Convert a JavaScript value into a NOTA-encoded ArrayBuffer.
|
||||
|
||||
This function serializes JavaScript values (such as numbers, strings, booleans, arrays, objects, or ArrayBuffers) into the NOTA binary format. The resulting ArrayBuffer can be stored or transmitted and later decoded back into a JavaScript value.
|
||||
|
||||
:throws: An error if no argument is provided.
|
||||
|
||||
|
||||
**value**: The JavaScript value to encode (e.g., number, string, boolean, array, object, or ArrayBuffer).
|
||||
|
||||
|
||||
**Returns**: An ArrayBuffer containing the NOTA-encoded data.
|
||||
|
||||
|
||||
### decode(buffer) <sub>function</sub>
|
||||
|
||||
Decode a NOTA-encoded ArrayBuffer into a JavaScript value.
|
||||
|
||||
This function deserializes a NOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns undefined.
|
||||
|
||||
|
||||
|
||||
**buffer**: An ArrayBuffer containing NOTA-encoded data to decode.
|
||||
|
||||
|
||||
**Returns**: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or undefined if no argument is provided.
|
||||
|
||||
@@ -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>
|
||||
|
||||
66
docs/api/types/enet_host.md
Normal file
66
docs/api/types/enet_host.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# enet_host
|
||||
|
||||
### service(callback, timeout) <sub>function</sub>
|
||||
|
||||
|
||||
Poll for and process any available network events (connect, receive, disconnect, or none)
|
||||
from this host, calling the provided callback for each event. This function loops until
|
||||
no more events are available in the current timeframe.
|
||||
|
||||
Event object properties:
|
||||
- type: String, one of "connect", "receive", "disconnect", or "none".
|
||||
- peer: (present if type = "connect") The ENetPeer object for the new connection.
|
||||
- channelID: (present if type = "receive") The channel on which the data was received.
|
||||
- data: (present if type = "receive") The received data as a *plain JavaScript object*.
|
||||
If the JSON parse fails or the data isn't an object, a JavaScript error is thrown.
|
||||
|
||||
object as its single argument.
|
||||
|
||||
|
||||
**callback**: A function called once for each available event, receiving an event
|
||||
|
||||
**timeout**: (optional) Timeout in milliseconds. Defaults to 0 (non-blocking).
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### connect(host, port) <sub>function</sub>
|
||||
|
||||
|
||||
Initiate a connection from this host to a remote server. Throws an error if the
|
||||
connection cannot be started.
|
||||
|
||||
|
||||
|
||||
**host**: The hostname or IP address of the remote server (e.g. "example.com" or "127.0.0.1").
|
||||
|
||||
**port**: The port number to connect to.
|
||||
|
||||
|
||||
**Returns**: An ENetPeer object representing the connection.
|
||||
|
||||
|
||||
### flush() <sub>function</sub>
|
||||
|
||||
|
||||
Flush all pending outgoing packets for this host immediately.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### broadcast(data) <sub>function</sub>
|
||||
|
||||
|
||||
Broadcast a JavaScript object to all connected peers on channel 0. The object is
|
||||
serialized to JSON, and the packet is sent reliably. Throws an error if serialization fails.
|
||||
|
||||
|
||||
|
||||
**data**: A JavaScript object to broadcast to all peers.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
102
docs/api/types/enet_peer.md
Normal file
102
docs/api/types/enet_peer.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# enet_peer
|
||||
|
||||
### send(data) <sub>function</sub>
|
||||
|
||||
|
||||
Send a JavaScript object to this peer on channel 0. The object is serialized to JSON and
|
||||
sent reliably. Throws an error if serialization fails.
|
||||
|
||||
|
||||
|
||||
**data**: A JavaScript object to send.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### disconnect() <sub>function</sub>
|
||||
|
||||
|
||||
Request a graceful disconnection from this peer. The connection will close after
|
||||
pending data is sent.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### disconnect_now() <sub>function</sub>
|
||||
|
||||
|
||||
Immediately terminate the connection to this peer, discarding any pending data.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### disconnect_later() <sub>function</sub>
|
||||
|
||||
|
||||
Request a disconnection from this peer after all queued packets are sent.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### reset() <sub>function</sub>
|
||||
|
||||
|
||||
Reset this peer's connection, immediately dropping it and clearing its internal state.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### ping() <sub>function</sub>
|
||||
|
||||
|
||||
Send a ping request to this peer to measure latency.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### throttle_configure(interval, acceleration, deceleration) <sub>function</sub>
|
||||
|
||||
|
||||
Configure the throttling behavior for this peer, controlling how ENet adjusts its sending
|
||||
rate based on packet loss or congestion.
|
||||
|
||||
|
||||
|
||||
**interval**: The interval (ms) between throttle adjustments.
|
||||
|
||||
**acceleration**: The factor to increase sending speed when conditions improve.
|
||||
|
||||
**deceleration**: The factor to decrease sending speed when conditions worsen.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### timeout(timeout_limit, timeout_min, timeout_max) <sub>function</sub>
|
||||
|
||||
|
||||
Set timeout parameters for this peer, determining how long ENet waits before considering
|
||||
the connection lost.
|
||||
|
||||
|
||||
|
||||
**timeout_limit**: The total time (ms) before the peer is disconnected.
|
||||
|
||||
**timeout_min**: The minimum timeout (ms) used for each timeout attempt.
|
||||
|
||||
**timeout_max**: The maximum timeout (ms) used for each timeout attempt.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
661
docs/dull/Array.md
Normal file
661
docs/dull/Array.md
Normal 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.
|
||||
|
||||
88
meson.build
88
meson.build
@@ -1,13 +1,14 @@
|
||||
project('prosperon', ['c', 'cpp'], default_options : [ 'cpp_std=c++11'])
|
||||
project('prosperon', ['c', 'cpp'],
|
||||
version: '0.9.3',
|
||||
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,40 @@ 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'
|
||||
})
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
deps += dependency('sdl3', required:true)
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
deps += dependency('appleframeworks', modules: 'accelerate')
|
||||
@@ -56,22 +83,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 +123,22 @@ 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', static:true)
|
||||
deps += dependency('enet', static:true)
|
||||
deps += dependency('soloud', static:true)
|
||||
|
||||
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('qjs-chipmunk', static:false)
|
||||
|
||||
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']
|
||||
|
||||
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 +157,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 +178,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 +207,7 @@ prosperon = custom_target('prosperon',
|
||||
'@INPUT1@',
|
||||
'@OUTPUT@'
|
||||
],
|
||||
build_always: true,
|
||||
build_always_stale: true,
|
||||
build_by_default: true
|
||||
)
|
||||
|
||||
@@ -194,16 +220,18 @@ 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'
|
||||
]
|
||||
|
||||
foreach file : tests
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
site_name: Prosperon Documentation
|
||||
edit_uri: edit/master/doc/docs
|
||||
|
||||
plugins:
|
||||
- search
|
||||
|
||||
26
scripts/modules/camera.js
Normal file
26
scripts/modules/camera.js
Normal 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;
|
||||
@@ -1,7 +1,6 @@
|
||||
var io = use('io')
|
||||
var util = use('util')
|
||||
|
||||
|
||||
var dumpfolder = ".prosperon";
|
||||
|
||||
io.mkdir(dumpfolder)
|
||||
@@ -27,46 +26,41 @@ 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'
|
||||
for (var g of gs)
|
||||
io.slurpwrite(`.src/docs/api/${g}.md`, doc.writeDocFile(globalThis[g], g))
|
||||
|
||||
console.log(Object.getOwnPropertyDescriptor(prosperon.c_types.transform,'pos')[prosperon.DOC])
|
||||
var coredocs = io.enumerate("scripts/modules", 0)
|
||||
coredocs = coredocs.filter(x => io.match("**/*.js", x)).map(x => x.name())
|
||||
|
||||
for (var g of gs)
|
||||
io.slurpwrite(`.src/docs/api/${g}.md`, doc.writeDocFile(globalThis[g], g))
|
||||
var APIPATH = '.src/docs/api/modules/'
|
||||
|
||||
var coredocs = io.enumerate("scripts/modules", 0)
|
||||
coredocs = coredocs.filter(x => io.match("**/*.js", x)).map(x => x.name())
|
||||
for (var m of coredocs) {
|
||||
var u = use(m)
|
||||
var path = `${APIPATH}${m}.md`
|
||||
io.slurpwrite(path, doc.writeDocFile(u, m))
|
||||
}
|
||||
|
||||
var TYPEPATH = '.src/docs/api/types/'
|
||||
for (var c in prosperon.c_types) {
|
||||
io.slurpwrite(`${TYPEPATH}${c}.md`, doc.writeDocFile(prosperon.c_types[c], c))
|
||||
}
|
||||
var 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 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))
|
||||
}
|
||||
|
||||
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 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 +193,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
42
scripts/modules/debug.js
Normal 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
27
scripts/modules/dmon.js
Normal 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
|
||||
@@ -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
151
scripts/modules/enet.js
Normal 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;
|
||||
@@ -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 pie’s center.
|
||||
:param y: The y position of the pie’s 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
|
||||
|
||||
@@ -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.
|
||||
`
|
||||
|
||||
|
||||
@@ -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
20
scripts/modules/nota.js
Normal 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
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
1748
source/dmon.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -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
|
||||
271
source/jsffi.c
271
source/jsffi.c
@@ -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,15 @@ 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"
|
||||
|
||||
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
|
||||
|
||||
@@ -7691,11 +7602,14 @@ 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));
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
arrput(module_registry, MISTLINE(tracy));
|
||||
@@ -7714,25 +7628,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 +7786,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
123
source/kim.h
Executable 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
|
||||
@@ -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) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "HandmadeMath.h"
|
||||
#include "transform.h"
|
||||
#include "gameobject.h"
|
||||
#include "anim.h"
|
||||
#include "cgltf.h"
|
||||
|
||||
|
||||
632
source/nota.h
Executable file
632
source/nota.h
Executable file
@@ -0,0 +1,632 @@
|
||||
#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 ¬a[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(¬a, &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_str(NotaBuffer *nb, const char *str)
|
||||
{
|
||||
/* -------------------------------------------
|
||||
1) Parse sign
|
||||
------------------------------------------- */
|
||||
int negative = 0;
|
||||
if (*str == '+') {
|
||||
str++;
|
||||
}
|
||||
else if (*str == '-') {
|
||||
negative = 1;
|
||||
str++;
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
2) Parse integer part
|
||||
------------------------------------------- */
|
||||
long long coefficient = 0;
|
||||
int got_digits = 0;
|
||||
|
||||
while (*str >= '0' && *str <= '9') {
|
||||
got_digits = 1;
|
||||
int d = (*str - '0');
|
||||
str++;
|
||||
|
||||
// Basic overflow check (very naive):
|
||||
if (coefficient <= (LLONG_MAX - d) / 10) {
|
||||
coefficient = coefficient * 10 + d;
|
||||
} else {
|
||||
// If you want to handle overflow by switching to float, do that here.
|
||||
// For simplicity, let's just keep wrapping. In production, be careful!
|
||||
coefficient = coefficient * 10 + d;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
3) Check for decimal part
|
||||
------------------------------------------- */
|
||||
int has_decimal_point = 0;
|
||||
int fraction_digits = 0;
|
||||
|
||||
if (*str == '.') {
|
||||
has_decimal_point = 1;
|
||||
str++;
|
||||
while (*str >= '0' && *str <= '9') {
|
||||
got_digits = 1;
|
||||
int d = (*str - '0');
|
||||
str++;
|
||||
fraction_digits++;
|
||||
if (coefficient <= (LLONG_MAX - d) / 10) {
|
||||
coefficient = coefficient * 10 + d;
|
||||
} else {
|
||||
// Same naive overflow comment
|
||||
coefficient = coefficient * 10 + d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
4) Check for exponent part
|
||||
------------------------------------------- */
|
||||
int exponent_negative = 0;
|
||||
long long exponent_val = 0;
|
||||
|
||||
if (*str == 'e' || *str == 'E') {
|
||||
str++;
|
||||
if (*str == '+') {
|
||||
str++;
|
||||
}
|
||||
else if (*str == '-') {
|
||||
exponent_negative = 1;
|
||||
str++;
|
||||
}
|
||||
while (*str >= '0' && *str <= '9') {
|
||||
int d = (*str - '0');
|
||||
str++;
|
||||
if (exponent_val <= (LLONG_MAX - d) / 10) {
|
||||
exponent_val = exponent_val * 10 + d;
|
||||
} else {
|
||||
// Again, naive overflow handling
|
||||
exponent_val = exponent_val * 10 + d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
5) If there were no valid digits at all,
|
||||
store 0 and return. (simple fallback)
|
||||
------------------------------------------- */
|
||||
if (!got_digits) {
|
||||
nota_write_int_buf(nb, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
6) Combine fraction digits into exponent
|
||||
final_exponent = exponent_val - fraction_digits
|
||||
(apply exponent sign if any)
|
||||
------------------------------------------- */
|
||||
if (exponent_negative) {
|
||||
exponent_val = -exponent_val;
|
||||
}
|
||||
long long final_exponent = exponent_val - fraction_digits;
|
||||
|
||||
/* -------------------------------------------
|
||||
7) Decide if we are storing an integer
|
||||
or a float in Nota format.
|
||||
-------------------------------------------
|
||||
Rule used here:
|
||||
- If there's no decimal point AND final_exponent == 0,
|
||||
=> integer
|
||||
- If we do have a decimal point, but fraction_digits == 0
|
||||
and exponent_val == 0, then the user typed something
|
||||
like "123." or "100.0". That is effectively an integer,
|
||||
so store it as an integer if you want a purely numeric approach.
|
||||
- Otherwise store as float.
|
||||
------------------------------------------- */
|
||||
|
||||
// If "no decimal" => definitely integer:
|
||||
// or decimal present but fraction_digits=0 & exponent_val=0 => integer
|
||||
int treat_as_integer = 0;
|
||||
if (!has_decimal_point && final_exponent == 0) {
|
||||
treat_as_integer = 1;
|
||||
}
|
||||
else if (has_decimal_point && fraction_digits == 0 && exponent_val == 0) {
|
||||
// Means "123." or "123.0"
|
||||
treat_as_integer = 1;
|
||||
}
|
||||
|
||||
if (treat_as_integer) {
|
||||
// If negative => flip the sign in the stored value
|
||||
if (negative) {
|
||||
coefficient = -coefficient;
|
||||
}
|
||||
// Write the integer in Nota format (varint with sign bit)
|
||||
nota_write_int_buf(nb, coefficient);
|
||||
return;
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
8) Write as float in Nota format
|
||||
We do basically the same approach as
|
||||
nota_write_float_buf does:
|
||||
- NOTA_FLOAT nibble
|
||||
- sign bit if negative
|
||||
- exponent sign bit if final_exponent < 0
|
||||
- varint of |final_exponent|
|
||||
- varint of |coefficient|
|
||||
------------------------------------------- */
|
||||
{
|
||||
char *p = nota_buffer_alloc(nb, 21); // Up to ~21 bytes worst-case
|
||||
p[0] = NOTA_FLOAT;
|
||||
if (negative) {
|
||||
p[0] |= (1 << 3); // Mantissa sign bit
|
||||
}
|
||||
if (final_exponent < 0) {
|
||||
p[0] |= (1 << 4); // Exponent sign bit
|
||||
final_exponent = -final_exponent;
|
||||
}
|
||||
// Write exponent as varint (with 3 bits used in the first byte)
|
||||
char *c = nota_continue_num(final_exponent, p, 3);
|
||||
// Write the absolute coefficient (7 bits used in the first byte)
|
||||
char *end = nota_continue_num(coefficient, c, 7);
|
||||
|
||||
// Adjust the buffer size to the actual used length
|
||||
size_t used = (size_t)(end - p);
|
||||
nb->size -= (21 - used);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void nota_write_number(NotaBuffer *nb, double n)
|
||||
{
|
||||
nota_write_int_or_float_buf(nb, n);
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
@@ -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);
|
||||
|
||||
150
source/qjs_dmon.c
Normal file
150
source/qjs_dmon.c
Normal 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
8
source/qjs_dmon.h
Normal 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
500
source/qjs_enet.c
Normal 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
8
source/qjs_enet.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef QJS_ENET_H
|
||||
#define QJS_ENET_H
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
JSValue js_enet_use(JSContext*);
|
||||
|
||||
#endif
|
||||
@@ -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),
|
||||
|
||||
@@ -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]))
|
||||
|
||||
|
||||
|
||||
311
source/qjs_nota.c
Executable file
311
source/qjs_nota.c
Executable file
@@ -0,0 +1,311 @@
|
||||
#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: {
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, val);
|
||||
nota_write_number(&enc->nb, d);
|
||||
return;
|
||||
}
|
||||
case JS_TAG_BIG_INT:
|
||||
case JS_TAG_FLOAT64:
|
||||
case JS_TAG_BIG_DECIMAL:
|
||||
case JS_TAG_BIG_FLOAT: {
|
||||
const char *str = JS_ToCString(ctx, val);
|
||||
nota_write_number_str(&enc->nb, str);
|
||||
JS_FreeCString(ctx, str);
|
||||
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
8
source/qjs_nota.h
Normal 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
229
source/qjs_soloud.c
Normal 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
8
source/qjs_soloud.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef QJS_SOLOUD_H
|
||||
#define QJS_SOLOUD_H
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
JSValue js_soloud_use(JSContext*);
|
||||
|
||||
#endif
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
|
||||
535
source/spline.c
535
source/spline.c
@@ -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
|
||||
/* Catmull–Rom (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 catmull–rom 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 Catmull–Rom 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 Catmull–Rom 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;
|
||||
}
|
||||
|
||||
@@ -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 Catmull–Rom 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);
|
||||
// Catmull–Rom “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 Catmull–Rom spline:
|
||||
float catmull_rom_len(HMM_Vec2 *cp);
|
||||
|
||||
/* Returns closest point on a curve given a point p */
|
||||
// Returns closest point on a 2D Catmull–Rom 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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
3
source/thirdparty/stb/stb_c_lexer.h
vendored
3
source/thirdparty/stb/stb_c_lexer.h
vendored
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
9
source/thirdparty/stb/stb_image.h
vendored
9
source/thirdparty/stb/stb_image.h
vendored
@@ -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;
|
||||
|
||||
506
source/thirdparty/stb/stb_image_resize2.h
vendored
506
source/thirdparty/stb/stb_image_resize2.h
vendored
File diff suppressed because it is too large
Load Diff
14
source/thirdparty/stb/stb_truetype.h
vendored
14
source/thirdparty/stb/stb_truetype.h
vendored
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/johnalanbrook/qjs-dmon.git
|
||||
revision = head
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
qjs-dmon = qjs_dmon_dep
|
||||
@@ -1,7 +0,0 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/johnalanbrook/qjs-enet.git
|
||||
revision = head
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
qjs-enet = qjs_enet_dep
|
||||
@@ -1,7 +0,0 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/johnalanbrook/cnota.git
|
||||
revision = head
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
qjs-nota = qjs_nota_dep
|
||||
@@ -1,7 +0,0 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/johnalanbrook/qjs-soloud.git
|
||||
revision = head
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
qjs-soloud = qjs_soloud_dep
|
||||
@@ -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
4
subprojects/sdl3.wrap
Normal file
@@ -0,0 +1,4 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/libsdl-org/SDL.git
|
||||
revision = release-3.2.4
|
||||
depth = 1
|
||||
191
tests/enet.js
Normal file
191
tests/enet.js
Normal 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
262
tests/nota.js
Normal 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user