Repository: cloudwu/soluna
Branch: master
Commit: 59f5d0dc69a7
Files: 218
Total size: 673.5 KB
Directory structure:
gitextract_o983gujm/
├── .editorconfig
├── .github/
│ ├── actions/
│ │ ├── sample/
│ │ │ └── action.yml
│ │ └── soluna/
│ │ └── action.yml
│ └── workflows/
│ ├── nightly.yml
│ └── pages.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── Makefile
├── README.md
├── asset/
│ ├── sounds.dl
│ └── sprites.dl
├── clibs/
│ ├── datalist/
│ │ └── make.lua
│ ├── ltask/
│ │ └── make.lua
│ ├── lua/
│ │ └── make.lua
│ ├── sample/
│ │ └── make.lua
│ ├── soluna/
│ │ ├── compile_lua.lua
│ │ ├── compile_shader.lua
│ │ ├── make.lua
│ │ ├── runlua.lua
│ │ └── shader2c.lua
│ ├── yoga/
│ │ └── make.lua
│ └── zip/
│ └── make.lua
├── docs/
│ ├── app.lua
│ ├── args.lua
│ ├── callback.lua
│ ├── coroutine.lua
│ ├── crypt.lua
│ ├── datalist.lua
│ ├── file.lua
│ ├── font.lua
│ ├── font_system.lua
│ ├── image.lua
│ ├── layout.lua
│ ├── lfs.lua
│ ├── material_mask.lua
│ ├── material_quad.lua
│ ├── material_text.lua
│ ├── soluna.lua
│ ├── text.lua
│ ├── url.lua
│ └── zip.lua
├── extlua/
│ ├── extlua.c
│ ├── extlua.temp.c
│ ├── extlua_impl.c
│ ├── extlua_impl.temp.c
│ ├── extlua_sample.c
│ ├── gen.lua
│ ├── gen_sokol.lua
│ ├── gen_soluna.lua
│ ├── perspective_quad.glsl
│ ├── sokolapi.c
│ ├── sokolapi.temp.c
│ ├── sokolapi_impl.c
│ ├── sokolapi_impl.temp.c
│ ├── solunaapi.c
│ ├── solunaapi.h
│ ├── solunaapi.h.temp
│ ├── solunaapi.temp.c
│ ├── solunaapi_impl.c
│ └── solunaapi_impl.temp.c
├── make.lua
├── script/
│ ├── act.lua
│ ├── act_targets.lua
│ ├── compile_commands.lua
│ ├── datalist2c.lua
│ ├── hashversion.lua
│ └── lua2c.lua
├── src/
│ ├── appevent.h
│ ├── audio.c
│ ├── batch.c
│ ├── batch.h
│ ├── blit.glsl
│ ├── colorquad.glsl
│ ├── data/
│ │ └── settingdefault.dl
│ ├── drawmgr.c
│ ├── embedlua.c
│ ├── entry.c
│ ├── extapi.c
│ ├── extapi_types.h
│ ├── extapi_types.temp.h
│ ├── external.c
│ ├── file.c
│ ├── font.c
│ ├── font_define.h
│ ├── font_manager.c
│ ├── font_manager.h
│ ├── font_system.c
│ ├── gamepad.c
│ ├── image.c
│ ├── ime_char_filter.h
│ ├── ime_state.h
│ ├── lcrypt.c
│ ├── lfs.c
│ ├── loginfo.h
│ ├── lsha1.c
│ ├── luabuffer.h
│ ├── lualib/
│ │ ├── coroutine.lua
│ │ ├── fontmgr.lua
│ │ ├── icon.lua
│ │ ├── initsetting.lua
│ │ ├── layout.lua
│ │ ├── main.lua
│ │ ├── packageloader.lua
│ │ ├── print_r.lua
│ │ ├── soluna.lua
│ │ ├── spritebundle.lua
│ │ ├── text.lua
│ │ └── util.lua
│ ├── luamods.c
│ ├── luayoga.c
│ ├── luazip.c
│ ├── maskquad.glsl
│ ├── material/
│ │ ├── matdefault.lua
│ │ ├── matmask.lua
│ │ ├── matquad.lua
│ │ └── mattext.lua
│ ├── material_blit.c
│ ├── material_default.c
│ ├── material_mask.c
│ ├── material_quad.c
│ ├── material_text.c
│ ├── material_util.c
│ ├── material_util.h
│ ├── mutex.h
│ ├── openlibs.c
│ ├── openurl.c
│ ├── platform/
│ │ ├── linux/
│ │ │ ├── soluna_linux_ime.c
│ │ │ └── soluna_linux_ime.h
│ │ ├── macos/
│ │ │ ├── soluna_macos_ime.h
│ │ │ └── soluna_macos_ime.m
│ │ ├── wasm/
│ │ │ ├── soluna_ime.js
│ │ │ ├── soluna_openurl.js
│ │ │ └── soluna_wasm_ime.h
│ │ └── windows/
│ │ ├── soluna_windows_ime.c
│ │ └── soluna_windows_ime.h
│ ├── render.c
│ ├── render_bindings.c
│ ├── render_bindings.h
│ ├── render_uniform.c
│ ├── sdfimage.c
│ ├── sdftext.glsl
│ ├── service/
│ │ ├── audio.lua
│ │ ├── gamepad.lua
│ │ ├── loader.lua
│ │ ├── log.lua
│ │ ├── render.lua
│ │ ├── settings.lua
│ │ └── start.lua
│ ├── sprite_submit.h
│ ├── spritemgr.c
│ ├── spritemgr.h
│ ├── srbuffer.c
│ ├── srbuffer.h
│ ├── texquad.glsl
│ ├── tmpbuffer.h
│ ├── transform.c
│ ├── transform.h
│ ├── truetype.c
│ ├── truetype.h
│ ├── version.h
│ ├── winfile.c
│ ├── writelog.c
│ ├── yogaone.cpp
│ └── zipreader.h
├── test/
│ ├── audio.game
│ ├── audio.lua
│ ├── bundle.lua
│ ├── extlua/
│ │ └── material/
│ │ └── perspective_quad.lua
│ ├── extlua.game
│ ├── extlua.lua
│ ├── file.lua
│ ├── hello.game
│ ├── hello.lua
│ ├── icon.lua
│ ├── image.lua
│ ├── ime.lua
│ ├── intersect.lua
│ ├── layout.lua
│ ├── mask.lua
│ ├── mtex.game
│ ├── mtex.lua
│ ├── setting.lua
│ ├── sprite.lua
│ ├── spritepack.lua
│ ├── test.lua
│ ├── text.lua
│ ├── version.lua
│ ├── window.game
│ └── window.lua
└── website/
├── README.md
├── README.zh-CN.md
├── astro.config.mjs
├── eslint.config.mjs
├── package.json
├── packages/
│ └── astro-theme-soluna/
│ ├── package.json
│ └── src/
│ ├── client/
│ │ └── play.ts
│ ├── components/
│ │ ├── Footer.astro
│ │ ├── Hero.astro
│ │ ├── Menubar.astro
│ │ ├── Nav.astro
│ │ ├── PlainList.astro
│ │ ├── Section.astro
│ │ ├── docs/
│ │ │ └── DocsPage.astro
│ │ └── examples/
│ │ ├── ExampleListPage.astro
│ │ └── ExamplePlayPage.astro
│ ├── layouts/
│ │ └── BaseLayout.astro
│ └── styles/
│ └── theme.css
├── pnpm-workspace.yaml
├── scripts/
│ └── prepare-runtime.mjs
├── src/
│ ├── content.config.ts
│ ├── lib/
│ │ ├── content.ts
│ │ └── readme.ts
│ └── pages/
│ ├── docs/
│ │ └── index.astro
│ ├── examples/
│ │ ├── [id].astro
│ │ └── index.astro
│ └── index.astro
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# see https://github.com/CppCXY/EmmyLuaCodeStyle
[*.lua]
max_line_length = 120
end_of_line = lf
indent_style = tab
indent_size = 4
quote_style = double
call_arg_parentheses = remove
auto_collapse_lines = false
================================================
FILE: .github/actions/sample/action.yml
================================================
name: Build Sample WASM
description: Build sample.wasm side module for Soluna web runtime
inputs:
soluna_path:
description: "The path to the Soluna repository. Defaults to ."
required: false
default: "."
luamake_version:
description: "LuaMake commit SHA used to build."
required: false
default: "5bedfce66f075a9f68b1475747738b81b3b41c25"
emsdk_version:
description: "Emscripten SDK version."
required: false
default: "4.0.17"
outputs:
SAMPLE_WASM_PATH:
description: "The path to built sample.wasm side module."
value: ${{ steps.set-output.outputs.SAMPLE_WASM_PATH }}
runs:
using: "composite"
steps:
- name: Get the soluna commit
id: refs
working-directory: ${{ inputs.soluna_path }}
shell: bash
run: |
echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Cache sample wasm build
uses: actions/cache@v4
id: cache
with:
path: |
${{ inputs.soluna_path }}/bin
${{ inputs.soluna_path }}/build
key: ${{ runner.os }}-sample-wasm-${{ steps.refs.outputs.commit }}-${{ inputs.luamake_version }}-${{ inputs.emsdk_version }}
- name: Checkout all submodules
if: steps.cache.outputs.cache-hit != 'true'
working-directory: ${{ inputs.soluna_path }}
shell: bash
run: |
git submodule update --init --recursive
- name: Setup LuaMake
if: steps.cache.outputs.cache-hit != 'true'
uses: yuchanns/actions-luamake@v1
with:
luamake-version: ${{ inputs.luamake_version }}
- name: Setup Emscripten
if: runner.os == 'Linux' && steps.cache.outputs.cache-hit != 'true'
uses: mymindstorm/setup-emsdk@v14
with:
version: ${{ inputs.emsdk_version }}
actions-cache-folder: 'emsdk-cache'
- name: Build sample side module
if: runner.os == 'Linux' && steps.cache.outputs.cache-hit != 'true'
shell: bash
working-directory: ${{ inputs.soluna_path }}
run: |
luamake -compiler emcc sample
- name: Set output
id: set-output
shell: bash
working-directory: ${{ inputs.soluna_path }}
run: |
soluna_root=$(pwd -P)
SAMPLE_WASM_PATH=$(find "$soluna_root/bin" -name "sample.wasm" | head -n 1)
if [ -z "$SAMPLE_WASM_PATH" ]; then
echo "sample.wasm not found" >&2
exit 1
fi
echo "SAMPLE_WASM_PATH=$SAMPLE_WASM_PATH" >> "$GITHUB_OUTPUT"
================================================
FILE: .github/actions/soluna/action.yml
================================================
name: Build Soluna
description: Build Soluna for different operating systems
inputs:
soluna_path:
description: 'The path to the Soluna repository. Defaults to .'
required: false
default: '.'
debug:
description: 'Whether to build Soluna in debug mode. Defaults to false.'
required: false
default: 'false'
outputs:
SOLUNA_BINARY:
description: 'The name of the built Soluna binary.'
value: ${{ steps.set-output.outputs.SOLUNA_BINARY }}
SOLUNA_PATH:
description: 'The path to the built Soluna binary.'
value: ${{ steps.set-output.outputs.SOLUNA_PATH }}
SOLUNA_WASM_PATH:
description: 'The path to the built Soluna WebAssembly binary.'
value: ${{ steps.set-output.outputs.SOLUNA_WASM_PATH }}
SOLUNA_JS_PATH:
description: 'The path to the Soluna JavaScript glue code for WebAssembly.'
value: ${{ steps.set-output.outputs.SOLUNA_JS_PATH }}
SOLUNA_WASM_MAP_PATH:
description: 'The path to the built Soluna WebAssembly map if debug is true.'
value: ${{ steps.set-output.outputs.SOLUNA_WASM_MAP_PATH }}
runs:
using: "composite"
steps:
- name: Get the soluna commit
id: refs
working-directory: ${{ inputs.soluna_path }}
run: |
echo "commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
shell: bash
- name: Cache soluna build
uses: actions/cache@v4
id: cache
with:
path: ${{ inputs.soluna_path }}/bin
key: ${{ runner.os }}-soluna-build-${{ steps.refs.outputs.commit }}-${{ inputs.debug }}
- name: Checkout all submodules
if: steps.cache.outputs.cache-hit != 'true'
working-directory: ${{ inputs.soluna_path }}
run: |
git submodule update --init --recursive
shell: bash
- uses: yuchanns/actions-luamake@v1
if: steps.cache.outputs.cache-hit != 'true'
with:
luamake-version: "5bedfce66f075a9f68b1475747738b81b3b41c25"
- name: Install Dependencies (Linux)
shell: bash
if: runner.os == 'Linux' && steps.cache.outputs.cache-hit != 'true'
run: |
sudo apt-get update
sudo apt-get install -y build-essential \
libgl1-mesa-dev libglu1-mesa-dev libx11-dev \
libxrandr-dev libxi-dev libxxf86vm-dev libxcursor-dev \
libasound2-dev libfontconfig1-dev
- name: Build (Windows)
if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true'
shell: powershell
working-directory: ${{ inputs.soluna_path }}
run: |
if (${{ inputs.debug }} -eq 'true') {
$env:LUAMAKE_BUILD_TYPE = 'debug'
} else {
$env:LUAMAKE_BUILD_TYPE = 'release'
}
luamake -mode $env:LUAMAKE_BUILD_TYPE soluna
- name: Build (Unix)
if: runner.os != 'Windows' && steps.cache.outputs.cache-hit != 'true'
shell: bash
working-directory: ${{ inputs.soluna_path }}
run: |
if [ "${{ inputs.debug }}" == "true" ]; then
export LUAMAKE_BUILD_TYPE=debug
else
export LUAMAKE_BUILD_TYPE=release
fi
luamake -mode $LUAMAKE_BUILD_TYPE soluna
- uses: mymindstorm/setup-emsdk@v14
if: runner.os == 'Linux' && steps.cache.outputs.cache-hit != 'true'
with:
version: 4.0.17
actions-cache-folder: 'emsdk-cache'
- name: Build Emscripten
if: runner.os == 'Linux' && steps.cache.outputs.cache-hit != 'true'
working-directory: ${{ inputs.soluna_path }}
shell: bash
run: |
if [ "${{ inputs.debug }}" == "true" ]; then
export LUAMAKE_BUILD_TYPE=debug
else
export LUAMAKE_BUILD_TYPE=release
fi
luamake -mode $LUAMAKE_BUILD_TYPE -compiler emcc
SOLUNA_JS="soluna.js"
SOLUNA_JS_PATH=$(find bin -name $SOLUNA_JS | head -n 1)
sed -i 's/setBindGroup(groupIndex,group,(growMemViews(),HEAPU32),dynamicOffsetsPtr>>2,dynamicOffsetCount)/setBindGroup(groupIndex,group,(growMemViews(),HEAPU32).subarray(dynamicOffsetsPtr>>2,(dynamicOffsetsPtr>>2)+dynamicOffsetCount))/g' "$SOLUNA_JS_PATH"
sed -i 's/setBindGroup(groupIndex, group, (growMemViews(), HEAPU32), ((dynamicOffsetsPtr) >> 2), dynamicOffsetCount)/setBindGroup(groupIndex, group, (growMemViews(), HEAPU32).subarray(((dynamicOffsetsPtr) >> 2), ((dynamicOffsetsPtr) >> 2) + dynamicOffsetCount))/g' "$SOLUNA_JS_PATH"
perl -0pi -e 's/var group=WebGPU\.getJsObject\(groupPtr\);if\(dynamicOffsetCount==0\)\{pass\.setBindGroup\(groupIndex,group\)\}else\{pass\.setBindGroup\(groupIndex,group,\(growMemViews\(\),HEAPU32\)\.subarray\(dynamicOffsetsPtr>>2,\(dynamicOffsetsPtr>>2\)\+dynamicOffsetCount\)\)\}/var group=WebGPU.getJsObject(groupPtr);if(!group){return}if(dynamicOffsetCount==0){pass.setBindGroup(groupIndex,group)}else{pass.setBindGroup(groupIndex,group,(growMemViews(),HEAPU32).subarray(dynamicOffsetsPtr>>2,(dynamicOffsetsPtr>>2)+dynamicOffsetCount))}/g' "$SOLUNA_JS_PATH"
perl -0pi -e 's/var group = WebGPU\.getJsObject\(groupPtr\);\n if \(dynamicOffsetCount == 0\) \{\n pass\.setBindGroup\(groupIndex, group\);\n \} else \{\n pass\.setBindGroup\(groupIndex, group, \(growMemViews\(\), HEAPU32\)\.subarray\(\(\(dynamicOffsetsPtr\) >> 2\), \(\(dynamicOffsetsPtr\) >> 2\) \+ dynamicOffsetCount\)\);\n \}/var group = WebGPU.getJsObject(groupPtr);\n if (!group) {\n return;\n }\n if (dynamicOffsetCount == 0) {\n pass.setBindGroup(groupIndex, group);\n } else {\n pass.setBindGroup(groupIndex, group, (growMemViews(), HEAPU32).subarray(((dynamicOffsetsPtr) >> 2), ((dynamicOffsetsPtr) >> 2) + dynamicOffsetCount));\n }/g' "$SOLUNA_JS_PATH"
if grep -Fq 'setBindGroup(groupIndex,group,(growMemViews(),HEAPU32),dynamicOffsetsPtr>>2,dynamicOffsetCount)' "$SOLUNA_JS_PATH" || \
grep -Fq 'setBindGroup(groupIndex, group, (growMemViews(), HEAPU32), ((dynamicOffsetsPtr) >> 2), dynamicOffsetCount)' "$SOLUNA_JS_PATH"; then
echo "Unpatched WebGPU setBindGroup glue remains in $SOLUNA_JS_PATH" >&2
exit 1
fi
- name: Set Output Build Path
id: set-output
run: |
soluna_root=$(cd "${{ inputs.soluna_path }}" && pwd -P)
if [ "${{ runner.os }}" == "Windows" ]; then
soluna_root_output=$(cd "${{ inputs.soluna_path }}" && pwd -W | sed 's#\\#/#g')
else
soluna_root_output="$soluna_root"
fi
bin_dir="$soluna_root/bin"
bin_dir_output="$soluna_root_output/bin"
if [ "${{ runner.os }}" == "Windows" ]; then
SOLUNA_BINARY="soluna.exe"
RENAME_BINARY="soluna-windows-amd64.exe"
elif [ "${{ runner.os }}" == "macOS" ]; then
SOLUNA_BINARY="soluna"
RENAME_BINARY="soluna-macos-arm64"
else
SOLUNA_BINARY="soluna"
RENAME_BINARY="soluna-linux-amd64"
fi
SOLUNA_PATH=$(find "$bin_dir" -name "$SOLUNA_BINARY" | head -n 1)
cp "$SOLUNA_PATH" "$bin_dir/$RENAME_BINARY"
echo "SOLUNA_PATH=$bin_dir_output/$RENAME_BINARY" >> $GITHUB_OUTPUT
echo "SOLUNA_BINARY=$RENAME_BINARY" >> $GITHUB_OUTPUT
if [ "${{ runner.os }}" == "Linux" ]; then
SOLUNA_WASM_PATH=$(find "$bin_dir" -name "soluna.wasm" | head -n 1)
SOLUNA_JS_PATH=$(find "$bin_dir" -name "soluna.js" | head -n 1)
echo "SOLUNA_WASM_PATH=$SOLUNA_WASM_PATH" >> $GITHUB_OUTPUT
echo "SOLUNA_JS_PATH=$SOLUNA_JS_PATH" >> $GITHUB_OUTPUT
if [ "${{ inputs.debug }}" == "true" ]; then
SOLUNA_WASM_MAP_PATH=$(find "$bin_dir" -name "soluna.wasm.map" | head -n 1)
echo "SOLUNA_WASM_MAP_PATH=$SOLUNA_WASM_MAP_PATH" >> $GITHUB_OUTPUT
fi
fi
shell: bash
================================================
FILE: .github/workflows/nightly.yml
================================================
name: Nightly Build
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # Runs every day at midnight UTC
jobs:
check:
name: Determine Build Necessity
runs-on: ubuntu-latest
outputs:
proceed: ${{ steps.check.outputs.proceed }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get the last nightly commit
id: last_nightly
uses: actions/github-script@v7
with:
script: |
const releases = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
});
const nightlyRelease = releases.data.find(release => release.tag_name.includes('nightly') || release.name.includes('Nightly'));
if (nightlyRelease) {
const tagName = nightlyRelease.tag_name;
const tag = await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `tags/${tagName}`
});
const commitSha = tag.data.object.sha;
return commitSha;
} else {
return null;
}
- name: Check the proceed condition
id: check
run: |
LAST_NIGHTLY_COMMIT="${{ steps.last_nightly.outputs.result }}"
CURRENT_COMMIT="${GITHUB_SHA}"
echo "Last nightly commit: $LAST_NIGHTLY_COMMIT"
echo "Current commit: $CURRENT_COMMIT"
if [ -z "$LAST_NIGHTLY_COMMIT" ]; then
echo "No previous nightly release found. Proceeding with build."
echo "proceed=true" >> $GITHUB_OUTPUT
elif [ "$LAST_NIGHTLY_COMMIT" != "$CURRENT_COMMIT" ]; then
echo "New commits found since last nightly release. Proceeding with build."
echo "proceed=true" >> $GITHUB_OUTPUT
else
echo "No new commits since last nightly release. Skipping build."
echo "proceed=false" >> $GITHUB_OUTPUT
fi
build:
name: Nightly Build on ${{ matrix.os }}
needs: check
if: needs.check.outputs.proceed == 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: ./.github/actions/soluna
name: Build
id: build
with:
soluna_path: "."
- uses: actions/upload-artifact@v4
name: Upload
if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule'
with:
name: "soluna-${{ runner.os }}-${{ steps.build.outputs.SOLUNA_BINARY }}"
if-no-files-found: "error"
path: "${{ steps.build.outputs.SOLUNA_PATH }}"
overwrite: "true"
- uses: actions/upload-artifact@v4
name: Upload Emscripten
if: (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') && runner.os == 'Linux'
with:
name: "soluna-emscripten"
if-no-files-found: "error"
overwrite: "true"
path: |
${{ steps.build.outputs.SOLUNA_WASM_PATH }}
${{ steps.build.outputs.SOLUNA_JS_PATH }}
release:
name: Create Nightly Release
needs: [check, build]
runs-on: ubuntu-latest
if: (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') && github.actor != 'nektos/act'
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- name: Download all artifacts
uses: actions/download-artifact@v5
with:
path: artifacts
- name: Prepare release assets
run: |
mkdir -p release-assets
find artifacts -type f -name "soluna*" -exec cp {} release-assets/ \;
ls -la release-assets/
- name: Delete existing nightly releases
uses: actions/github-script@v7
with:
script: |
const releases = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
});
for (const release of releases.data) {
if (release.tag_name.includes('nightly') || release.name.includes('Nightly')) {
console.log(`Deleting release: ${release.tag_name}`);
try {
await github.rest.repos.deleteRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.id
});
try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `tags/${release.tag_name}`
});
} catch (error) {
console.log(`Tag ${release.tag_name} might not exist or already deleted`);
}
} catch (error) {
console.log(`Failed to delete release ${release.tag_name}: ${error.message}`);
}
}
}
- name: Create nightly release
id: create-release
uses: actions/github-script@v7
with:
script: |
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
const commitSha = context.sha.substring(0, 7);
const tagName = 'nightly';
const releaseNotes = `🌙 **Nightly Build**
**Build Information:**
- **Commit:** \`${context.sha}\`
- **Branch:** \`${context.ref.replace('refs/heads/', '')}\`
- **Build Time:** \`${new Date().toISOString()}\`
- **Workflow:** [${context.runId}](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})
> ⚠️ **Note:** This is an automated nightly build. It may contain unstable features and bugs.
>
> 📦 **Previous nightly releases are automatically removed to keep the repository clean.**`;
const { data } = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
name: `Nightly Build (${timestamp})`,
body: releaseNotes,
prerelease: true,
make_latest: 'true',
draft: false,
});
console.log(`Created release: ${data.html_url}`);
return data.id;
- name: Upload release assets
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const releaseId = ${{ steps.create-release.outputs.result }};
const assetsDir = 'release-assets';
const files = fs.readdirSync(assetsDir);
for (const file of files) {
const filePath = path.join(assetsDir, file);
const stats = fs.statSync(filePath);
if (stats.isFile()) {
console.log(`Uploading ${file}...`);
const content = fs.readFileSync(filePath);
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId,
name: file,
data: content
});
console.log(`Uploaded ${file}`);
}
}
================================================
FILE: .github/workflows/pages.yml
================================================
name: GitHub Pages
on:
workflow_run:
workflows:
- Nightly Build
types:
- completed
branches:
- master
workflow_dispatch:
inputs:
debug:
description: 'Whether to build in debug mode'
required: false
default: 'false'
jobs:
build:
name: Build Web Site
if: |
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }}
permissions:
contents: read
pages: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
- name: Build Soluna
uses: ./.github/actions/soluna
id: build
with:
soluna_path: "."
debug: ${{ github.event.inputs.debug }}
- name: Build Sample
uses: ./.github/actions/sample
id: sample
with:
soluna_path: "."
- name: Build Astro
uses: withastro/action@v6
env:
SITE_BASE: /soluna/
SOLUNA_JS_PATH: ${{ steps.build.outputs.SOLUNA_JS_PATH }}
SOLUNA_WASM_PATH: ${{ steps.build.outputs.SOLUNA_WASM_PATH }}
SOLUNA_WASM_MAP_PATH: ${{ steps.build.outputs.SOLUNA_WASM_MAP_PATH }}
SAMPLE_WASM_PATH: ${{ steps.sample.outputs.SAMPLE_WASM_PATH }}
with:
path: website
node-version: 24
package-manager: pnpm@10.28.2
build-cmd: pnpm run build
out-dir: dist
deploy:
name: Deploy to GitHub Pages
if: |
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')) &&
github.actor != 'nektos/act'
needs: [build]
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
================================================
FILE: .gitignore
================================================
bin
build
compile_commands.json
.cache
node_modules
.pnpm-store
pnpm-debug.log*
website/node_modules
website/packages/*/node_modules
website/dist
website/.astro
website/public/runtime/
================================================
FILE: .gitmodules
================================================
[submodule "3rd/ltask"]
path = 3rd/ltask
url = https://github.com/cloudwu/ltask.git
[submodule "3rd/sokol"]
path = 3rd/sokol
url = https://github.com/floooh/sokol.git
[submodule "3rd/lua"]
path = 3rd/lua
url = https://github.com/lua/lua.git
[submodule "3rd/stb"]
path = 3rd/stb
url = https://github.com/nothings/stb.git
[submodule "3rd/datalist"]
path = 3rd/datalist
url = https://github.com/cloudwu/datalist.git
[submodule "3rd/yoga"]
path = 3rd/yoga
url = https://github.com/facebook/yoga.git
[submodule "3rd/zlib"]
path = 3rd/zlib
url = https://github.com/madler/zlib.git
[submodule "bin/sokol-tools-bin"]
path = bin/sokol-tools-bin
url = https://github.com/floooh/sokol-tools-bin.git
[submodule "3rd/miniaudio"]
path = 3rd/miniaudio
url = https://github.com/mackron/miniaudio.git
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2025 codingnow.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: Makefile
================================================
.PHONY : all clean shader extlua_sample
BUILD=build
BIN=bin
APPNAME=soluna.exe
CC?=gcc
# msvc support
#CC=cl
LD=$(CC)
LUA_EXE=$(BUILD)/lua.exe
SHDC=$(BIN)/sokol-tools-bin/bin/win32/sokol-shdc.exe
VERSION=$(shell git rev-parse HEAD)
#for msvc
ifeq ($(CC),cl)
CCPP=cl
CFLAGS=-utf-8 -W3 -O2
OUTPUT_O=-c -Fo:
OUTPUT_EXE=-Fe:
STDC=-std:c11 -experimental:c11atomics
STDCPP=-std:c++20
SUBSYSTEM=-LINK -SUBSYSTEM:WINDOWS -ENTRY:"mainCRTStartup"
LDFLAGS=$(SUBSYSTEM) xinput.lib Ws2_32.lib ntdll.lib Imm32.lib
SHARED=-LD
else
CCPP=g++
CFLAGS=-Wall -O2
OUTPUT_O=-c -o
OUTPUT_EXE=-o
STDC=-std=c99 -lm
STDCPP=-std=c++20
SUBSYSTEM=-Wl,-subsystem,windows
LDFLAGS=-lkernel32 -luser32 -lshell32 -lgdi32 -ldxgi -ld3d11 -lwinmm -lws2_32 -lntdll -lxinput -limm32 -lstdc++ $(SUBSYSTEM)
SHARED=--shared
endif
all : $(BIN)/$(APPNAME)
3RDINC=-I3rd
YOGAINC=-I3rd/yoga
MINIAUDIOINC=-I3rd/miniaudio
LUAINC=-I3rd/lua
LUASRC:=$(wildcard 3rd/lua/*.c 3rd/lua/*.h)
WINFILE:=src/winfile.c
$(LUA_EXE) : $(LUASRC) $(WINFILE)
$(CC) $(CFLAGS) -o $@ 3rd/lua/onelua.c $(WINFILE) -DMAKE_LUA -Dfopen=fopen_utf8
COMPILE_C=$(CC) $(CFLAGS) $(STDC) $(OUTPUT_O) $@ $<
COMPILE_LUA=$(LUA_EXE) script/lua2c.lua $< $@
COMPILE_DATALIST=$(LUA_EXE) script/datalist2c.lua $< $@
LUA_O=$(BUILD)/onelua.o
$(LUA_O) : $(LUASRC)
$(CC) $(CFLAGS) $(OUTPUT_O) $@ 3rd/lua/onelua.c -DMAKE_LIB -Dfopen=fopen_utf8
SHADER_SRC=$(wildcard src/*.glsl)
SHADER_O=$(patsubst src/%.glsl,$(BUILD)/%.glsl.h,$(SHADER_SRC))
EXTLUA_SHADER_SRC=$(wildcard extlua/*.glsl)
EXTLUA_SHADER_O=$(patsubst extlua/%.glsl,$(BUILD)/%.glsl.h,$(EXTLUA_SHADER_SRC))
SHADERINC=-I$(BUILD)
$(BUILD)/%.glsl.h : src/%.glsl
$(SHDC) --input $< --output $@ --slang hlsl4 --format sokol
$(BUILD)/%.glsl.h : extlua/%.glsl
$(SHDC) --input $< --output $@ --slang hlsl4 --format sokol
shader : $(SHADER_O) $(EXTLUA_SHADER_O)
MAIN_FULL=$(wildcard src/*.c)
PLATFORM_FULL=$(wildcard src/platform/windows/*.c)
MAIN_C=$(notdir $(MAIN_FULL))
MAIN_O=$(patsubst %.c,$(BUILD)/soluna_%.o,$(MAIN_C))
PLATFORM_C=$(notdir $(PLATFORM_FULL))
PLATFORM_O=$(patsubst %.c,$(BUILD)/platform_%.o,$(PLATFORM_C))
EXTLUA_O=$(BUILD)/extlua_impl.o $(BUILD)/sokolapi_impl.o $(BUILD)/solunaapi_impl.o
$(MAIN_O) : $(SHADER_O)
LTASK_FULL=$(wildcard 3rd/ltask/src/*.c)
LTASK_C=$(notdir $(LTASK_FULL))
LTASK_O=$(patsubst %.c,$(BUILD)/ltask_%.o,$(LTASK_C))
LTASK_LUASRC=\
3rd/ltask/service/root.lua\
3rd/ltask/service/timer.lua\
$(wildcard 3rd/ltask/lualib/*.lua src/lualib/*.lua src/service/*.lua src/material/*.lua)
LTASK_LUACODE=$(patsubst %.lua, $(BUILD)/%.lua.h, $(notdir $(LTASK_LUASRC)))
DATALIST_SRC=$(wildcard src/data/*.dl)
DATALIST_CODE=$(patsubst %.dl, $(BUILD)/%.dl.h, $(notdir $(DATALIST_SRC)))
ZLIBINC=-I3rd/zlib
ZLIB_FULL=$(wildcard 3rd/zlib/*.c)
ZLIB_C = $(notdir $(ZLIB_FULL))
ZLIB_O = $(patsubst %.c,$(BUILD)/zlib_%.o,$(ZLIB_C))
MINIZIP_FULL=\
3rd\zlib\contrib/minizip/ioapi.c\
3rd\zlib\contrib/minizip/unzip.c\
3rd\zlib\contrib/minizip/zip.c\
3rd\zlib\contrib/minizip/iowin32.c
MINIZIP_C = $(notdir $(MINIZIP_FULL))
MINIZIP_O = $(patsubst %.c,$(BUILD)/minizip_%.o,$(MINIZIP_C))
$(LTASK_LUACODE) $(DATALIST_CODE) : | $(LUA_EXE)
$(BUILD)/%.lua.h : 3rd/ltask/service/%.lua
$(COMPILE_LUA)
$(BUILD)/%.lua.h : 3rd/ltask/lualib/%.lua
$(COMPILE_LUA)
$(BUILD)/%.lua.h : src/lualib/%.lua
$(COMPILE_LUA)
$(BUILD)/%.lua.h : src/service/%.lua
$(COMPILE_LUA)
$(BUILD)/%.lua.h : src/material/%.lua
$(COMPILE_LUA)
$(BUILD)/%.dl.h : src/data/%.dl
$(COMPILE_DATALIST)
$(BUILD)/soluna_embedlua.o : src/embedlua.c $(LTASK_LUACODE) $(DATALIST_CODE)
$(COMPILE_C) -I$(BUILD) $(LUAINC)
$(BUILD)/soluna_entry.o : src/entry.c src/version.h
$(COMPILE_C) $(LUAINC) $(3RDINC) -DSOLUNA_HASH_VERSION=\"$(VERSION)\"
$(BUILD)/soluna_%.o : src/%.c
$(COMPILE_C) $(LUAINC) $(3RDINC) $(SHADERINC) $(YOGAINC) $(ZLIBINC) $(MINIAUDIOINC)
$(BUILD)/platform_%.o : src/platform/windows/%.c
$(COMPILE_C) $(LUAINC) $(3RDINC) $(SHADERINC) $(YOGAINC) $(ZLIBINC) -Isrc
$(BUILD)/ltask_%.o : 3rd/ltask/src/%.c
$(COMPILE_C) $(LUAINC) -D_WIN32_WINNT=0x0601 -DLTASK_EXTERNAL_OPENLIBS=soluna_openlibs
DATALIST_O=$(BUILD)/datalist.o
$(DATALIST_O) : 3rd/datalist/datalist.c
$(COMPILE_C) $(LUAINC)
YOGASRC:=$(wildcard 3rd/yoga/yoga/*.cpp $(addsuffix *.cpp,$(wildcard 3rd/yoga/yoga/*/)))
$(BUILD)/yoga.o : src/yogaone.cpp $(YOGASRC)
$(CCPP) $(STDCPP) $(OUTPUT_O) $@ $< $(YOGAINC) $(CFLAGS)
$(BUILD)/zlib_%.o : 3rd/zlib/%.c
$(COMPILE_C) $(ZLIBINC)
$(BUILD)/minizip_%.o : 3rd/zlib/contrib/minizip/%.c
$(COMPILE_C) $(ZLIBINC)
$(BUILD)/extlua_impl.o : extlua/extlua_impl.c
$(COMPILE_C) $(LUAINC)
$(BUILD)/sokolapi_impl.o : extlua/sokolapi_impl.c
$(COMPILE_C) $(3RDINC)
$(BUILD)/solunaapi_impl.o : extlua/solunaapi_impl.c
$(COMPILE_C) $(LUAINC) $(3RDINC)
$(BIN)/$(APPNAME): $(MAIN_O) $(PLATFORM_O) $(EXTLUA_O) $(LTASK_O) $(LUA_O) $(DATALIST_O) $(BUILD)/yoga.o $(ZLIB_O) $(MINIZIP_O)
$(LD) $(OUTPUT_EXE) $@ $^ $(LDFLAGS)
$(BIN)/sample.dll : extlua/extlua.c extlua/sokolapi.c extlua/solunaapi.c extlua/extlua_sample.c | $(EXTLUA_SHADER_O)
$(CC) $(CFLAGS) $(SHARED) $(OUTPUT_EXE) $@ $^ $(LUAINC) $(3RDINC) $(SHADERINC) -Iextlua
extlua_sample: $(BIN)/sample.dll
clean :
rm -f $(BIN)/*.exe $(BIN)/*.dll $(BUILD)/*.o $(BUILD)/*.h
================================================
FILE: README.md
================================================
Sokol + Lua = Soluna
# Soluna
[Live Examples / 在线示例](https://cloudwu.github.io/soluna/)
Soluna is a 2D game framework for Lua. It is built on top of [sokol](https://github.com/floooh/sokol), integrates ltask for multithreading, and runs on Windows, Linux, macOS, and modern browsers through WebAssembly.
Soluna 是一个 Lua 2D 游戏框架。它基于 [sokol](https://github.com/floooh/sokol),整合 ltask 作为多线程框架,可运行在 Windows、Linux、macOS 以及通过 WebAssembly 支持的现代浏览器中。
[](/../../actions/workflows/nightly.yml)
## Documentation / 文档
- [API Reference / API 参考](./docs)
- [Examples / 示例](./test)
- [Wiki](https://github.com/cloudwu/soluna/wiki)
## Precompiled Binaries / 预编译二进制文件
Precompiled binaries for Windows, Linux, macOS, and WebAssembly are available from [Nightly Releases](/../../releases/tag/nightly).
Windows、Linux、macOS 和 WebAssembly 的预编译二进制文件可从 [Nightly Releases](/../../releases/tag/nightly) 下载。
## Building from Source / 从源码构建
Soluna can be built with `make` on Windows and with `luamake` on all supported platforms. The GitHub Action in [`.github/actions/soluna`](./.github/actions/soluna) shows the exact CI build flow.
Soluna 可在 Windows 上通过 `make` 构建,也可在所有支持平台上通过 `luamake` 构建。[`.github/actions/soluna`](./.github/actions/soluna) 展示了 CI 使用的完整构建流程。
### GitHub Actions Integration / GitHub Actions 集成
```yaml
- uses: actions/checkout@v6
with:
repository: cloudwu/soluna
ref:
path: soluna
submodules: recursive
- uses: ./soluna/.github/actions/soluna
id: soluna
with:
soluna_path: soluna
- run: |
echo "Soluna binary is at ${{ steps.soluna.outputs.SOLUNA_PATH }}"
echo "Soluna WASM binary is at ${{ steps.soluna.outputs.SOLUNA_WASM_PATH }}"
echo "Soluna js glue is at ${{ steps.soluna.outputs.SOLUNA_JS_PATH }}"
```
## Local Website Build / 本地构建与运行网站
The website is an Astro app in `website/`. It renders the homepage from this README, generates API pages from `docs/`, and builds live example pages from `test/`.
网站是位于 `website/` 目录的 Astro 应用。它使用本 README 生成首页,从 `docs/` 生成 API 页面,并从 `test/` 生成在线示例页面。
Build the WebAssembly runtime from the repository root first:
先在仓库根目录构建 WebAssembly runtime:
```bash
luamake -compiler emcc
luamake -compiler emcc sample
```
Then install dependencies and start the local dev server:
然后安装依赖并启动本地开发服务器:
```bash
cd website
pnpm install
pnpm run dev
```
## Projects Made with Soluna / 使用 Soluna 制作的项目
- [Deep Future](https://github.com/cloudwu/deepfuture), a digital version of the board game Deep Future. / 电子版桌游《深远未来》。
## License / 许可证
Soluna is licensed under the MIT License. See [LICENSE](./LICENSE) for details.
Soluna 使用 MIT 许可证。详情见 [LICENSE](./LICENSE)。
================================================
FILE: asset/sounds.dl
================================================
--
name : bloop
filename : asset/sounds/bloop_x.wav
--
name : bloop_loop
filename : asset/sounds/bloop_x.wav
group : music
volume : 0.6
pitch : 0.8
loop : true
stream : true
================================================
FILE: asset/sprites.dl
================================================
--
name : avatar
filename : avatar.png
x : -0.5
y : -1
--
name : avatar2
filename : avatar.png
x : -0.5
y : -0.5
================================================
FILE: clibs/datalist/make.lua
================================================
local lm = require "luamake"
lm.rootdir = lm.basedir .. "/3rd/datalist"
lm:source_set "datalist_src" {
sources = {
"datalist.c",
},
includes = {
lm.basedir .. "/3rd/lua",
},
}
================================================
FILE: clibs/ltask/make.lua
================================================
local lm = require "luamake"
lm.rootdir = lm.basedir .. "/3rd/ltask"
lm:source_set "ltask_src" {
sources = {
"src/*.c",
},
includes = {
lm.basedir .. "/3rd/lua",
},
defines = {
"LTASK_EXTERNAL_OPENLIBS=soluna_openlibs",
},
}
================================================
FILE: clibs/lua/make.lua
================================================
local lm = require "luamake"
lm.rootdir = lm.basedir .. "/3rd/lua"
if lm.os == "windows" then
lm:source_set "winfile" {
sources = {
lm.basedir .. "/src/winfile.c",
},
}
end
lm:source_set "lua_src" {
sources = {
"onelua.c",
},
defines = {
"MAKE_LIB",
lm.os == "windows" and "LUA_DL_DLL" or "LUA_USE_DLOPEN",
},
}
lm:exe "lua" {
deps = {
lm.os == "windows" and "winfile",
},
sources = {
"onelua.c",
},
defines = {
"MAKE_LUA",
},
windows = {
defines = {
"fopen=fopen_utf8",
},
},
}
================================================
FILE: clibs/sample/make.lua
================================================
local lm = require "luamake"
local platform = require "bee.platform"
lm.rootdir = lm.basedir
local function shdc_plat()
if lm.os == "windows" then
return "win32"
end
if lm.os == "linux" then
return "linux"
end
if lm.os == "macos" then
return platform.Arch == "arm64" and "osx_arm64" or "osx"
end
return "unknown"
end
local paths = {
windows = "$PATH/$NAME.exe",
macos = "$PATH/$NAME",
linux = "$PATH/$NAME",
}
local shdc = assert(paths[lm.os]):gsub("%$(%u+)", {
PATH = tostring(lm.basedir / "bin/sokol-tools-bin/bin" / shdc_plat()),
NAME = "sokol-shdc",
})
local function shader_lang()
local plat = lm.platform
if plat == "msvc" or plat == "clang-cl" or plat == "mingw" then
return "hlsl4"
end
if plat == "macos" then
return "metal_macos"
end
if plat == "emcc" then
return "wgsl"
end
if plat == "linux" then
return "glsl430"
end
return "unknown"
end
local function compile_shader(src, name)
local dep = name .. "_shader"
local target = lm.builddir .. "/" .. name
lm:runlua(dep) {
script = lm.basedir .. "/clibs/soluna/shader2c.lua",
inputs = lm.basedir .. "/" .. src,
outputs = lm.basedir .. "/" .. target,
args = {
shdc,
"$in",
"$out",
shader_lang(),
},
}
return dep
end
local sample_shader = compile_shader("extlua/perspective_quad.glsl", "perspective_quad.glsl.h")
lm:dll "sample" {
sources = {
"extlua/extlua.c",
"extlua/sokolapi.c",
"extlua/solunaapi.c",
"extlua/extlua_sample.c",
},
objdeps = {
sample_shader,
},
includes = {
"3rd/lua",
"3rd",
"build",
"extlua",
},
}
================================================
FILE: clibs/soluna/compile_lua.lua
================================================
local lm = require "luamake"
local fs = require "bee.filesystem"
local function compile_lua_code(script, src, name)
local dep = name .. "_lua_code"
local target = lm.builddir .. "/" .. name
local bindir = lm.bindir
if lm.platform == "emcc" then
bindir = lm.osbindir
end
lm:runlua(dep) {
script = lm.basedir .. "/clibs/soluna/runlua.lua",
deps = {
"lua",
},
inputs = lm.basedir .. "/" .. src,
outputs = lm.basedir .. "/" .. target,
args = {
bindir,
lm.basedir .. "/" .. script,
"$in",
"$out",
},
}
return dep
end
local lua_code_src = {
"3rd/ltask/service",
"3rd/ltask/lualib",
"src/service",
"src/lualib",
"src/material",
}
return function(objdeps)
for _, dir in ipairs(lua_code_src) do
for path in fs.pairs(lm.basedir .. "/" .. dir) do
if path:extension() == ".lua" then
local base = path:stem():string()
local dep = compile_lua_code("script/lua2c.lua", path:string(), base .. ".lua.h")
objdeps[#objdeps + 1] = dep
end
end
end
for path in fs.pairs "src/data" do
if path:extension() == ".dl" then
local base = path:stem():string()
local dep = compile_lua_code("script/datalist2c.lua", path:string(), base .. ".dl.h")
objdeps[#objdeps + 1] = dep
end
end
end
================================================
FILE: clibs/soluna/compile_shader.lua
================================================
local lm = require "luamake"
local fs = require "bee.filesystem"
local platform = require "bee.platform"
local function shdc_plat()
if lm.os == "windows" then
return "win32"
end
if lm.os == "linux" then
return "linux"
end
if lm.os == "macos" then
return platform.Arch == "arm64" and "osx_arm64" or "osx"
end
return "unknown"
end
local paths = {
windows = "$PATH/$NAME.exe",
macos = "$PATH/$NAME",
linux = "$PATH/$NAME",
}
local shdc = assert(paths[lm.os]):gsub("%$(%u+)", {
PATH = tostring(lm.basedir / "bin/sokol-tools-bin/bin" / shdc_plat()),
NAME = "sokol-shdc",
})
local function compile_shader(src, name, lang)
local dep = name .. "_shader"
local target = lm.builddir .. "/" .. name
lm:runlua(dep) {
script = lm.basedir .. "/clibs/soluna/shader2c.lua",
inputs = lm.basedir .. "/" .. src,
outputs = lm.basedir .. "/" .. target,
args = {
shdc,
"$in",
"$out",
lang,
},
}
return dep
end
local function shader_lang()
local plat = lm.platform
if plat == "msvc" or plat == "clang-cl" or plat == "mingw" then
return "hlsl4"
end
if plat == "macos" then
return "metal_macos"
end
if plat == "emcc" then
return "wgsl"
end
if plat == "linux" then
return "glsl430"
end
return "unknown"
end
return function(objdeps)
for path in fs.pairs "src" do
local lang = shader_lang()
if path:extension() == ".glsl" then
local base = path:stem():string()
local dep = compile_shader(path:string(), base .. ".glsl.h", lang)
objdeps[#objdeps + 1] = dep
end
end
end
================================================
FILE: clibs/soluna/make.lua
================================================
local lm = require "luamake"
local subprocess = require "bee.subprocess"
local compile_lua = require "compile_lua"
local compile_shader = require "compile_shader"
lm.rootdir = lm.basedir
local ok, process, errMsg = pcall(subprocess.spawn, {
lm.os ~= "windows" and "git" or "C:\\Program Files\\Git\\cmd\\git.exe",
"rev-parse",
"HEAD",
stdout = true,
})
local commit
if ok then
if errMsg then
print("Failed to start git process: " .. errMsg)
else
local output = process.stdout:read "a"
commit = output:match "^%s*(.-)%s*$"
process:wait()
end
end
local objdeps = {}
compile_lua(objdeps)
compile_shader(objdeps)
lm:source_set "soluna_src" {
sources = {
"src/*.c",
"extlua/extlua_impl.c",
"extlua/sokolapi_impl.c",
"extlua/solunaapi_impl.c",
},
objdeps = objdeps,
defines = {
commit and string.format('SOLUNA_HASH_VERSION=\\"%s\\"', commit),
},
includes = {
"build",
"src",
"3rd",
"3rd/lua",
"3rd/yoga",
"3rd/zlib",
"3rd/miniaudio",
},
clang = {
sources = lm.os == "macos" and {
"src/platform/macos/*.m",
},
flags = lm.os == "macos" and {
"-x objective-c",
},
frameworks = lm.os == "macos" and {
"AudioToolbox",
"IOKit",
"CoreText",
"CoreFoundation",
"Foundation",
"Cocoa",
"Metal",
"MetalKit",
"QuartzCore",
},
},
windows = {
sources = {
"src/platform/windows/*.c",
},
includes = {
"3rd/zlib/contrib/minizip",
}
},
gcc = {
sources = lm.os == "linux" and {
"src/platform/linux/*.c",
} or nil,
links = lm.os == "linux" and {
"pthread",
"dl",
"GL",
"X11",
"Xrandr",
"Xi",
"Xxf86vm",
"Xcursor",
"GLU",
"asound",
},
},
msvc = {
ldflags = {
"-SUBSYSTEM:WINDOWS",
"xinput.lib",
"Ws2_32.lib",
"ntdll.lib",
"Imm32.lib",
},
},
mingw = {
links = {
"kernel32",
"user32",
"shell32",
"gdi32",
"dxgi",
"d3d11",
"winmm",
"ws2_32",
"ntdll",
"xinput",
"imm32",
},
flags = {
"-Wl,subsystem,windows",
},
},
}
================================================
FILE: clibs/soluna/runlua.lua
================================================
local subprocess = require "bee.subprocess"
local platform = require "bee.platform"
local bindir, script, src, target = ...
local luaexe = platform.os == "windows" and bindir .. "/lua.exe" or bindir .. "/lua"
local process = assert(subprocess.spawn {
luaexe, script, src, target,
})
local code = process:wait()
if code ~= 0 then
os.exit(code, true)
end
================================================
FILE: clibs/soluna/shader2c.lua
================================================
local subprocess = require "bee.subprocess"
local shdcexe, src, target, lang = ...
local process = assert(subprocess.spawn {
shdcexe,
"--input",
src,
"--output",
target,
"--slang",
lang,
"--format",
"sokol",
})
local code = process:wait()
if code ~= 0 then
os.exit(code, true)
end
================================================
FILE: clibs/yoga/make.lua
================================================
local lm = require "luamake"
lm.rootdir = lm.basedir .. "/3rd/yoga"
lm:source_set "yoga_src" {
sources = {
"yoga/*.cpp",
"yoga/*/*.cpp",
},
includes = {
lm.rootdir,
}
}
================================================
FILE: clibs/zip/make.lua
================================================
local lm = require "luamake"
lm.rootdir = lm.basedir .. "/3rd/zlib"
lm:source_set "minizip" {
sources = {
"contrib/minizip/ioapi.c",
"contrib/minizip/unzip.c",
"contrib/minizip/zip.c",
},
windows = {
sources = {
"contrib/minizip/iowin32.c",
},
includes = {
"contrib/minizip",
},
},
includes = {
lm.rootdir,
},
}
lm:source_set "zlib" {
sources = {
"*.c",
"!gz*.c",
},
}
lm:source_set "zip_src" {
deps = {
"minizip",
"zlib",
}
}
================================================
FILE: docs/app.lua
================================================
---@meta soluna.app
---输入法候选窗口矩形
---IME candidate window rectangle.
---@class soluna.app.ImeRect
---@field x number 左上角 X / Left coordinate
---@field y number 左上角 Y / Top coordinate
---@field width number 宽度 / Width
---@field height number 高度 / Height
---@field text_color? integer 文本 ARGB 颜色;alpha 为 0 时补为 0xff / Text color in ARGB; alpha 0 is promoted to 0xff
---应用控制模块
---Application control module.
---@class soluna.app
local app = {}
---请求应用优雅退出
---Requests graceful application quit.
function app.quit()
end
---设置输入法字体
---Sets the IME font face and pixel size.
---@overload fun()
---@overload fun(font_size: number)
---@param font_name? string 字体名;nil 表示平台默认字体 / Font face; nil uses platform default
---@param font_size number 字体像素大小 / Font size in pixels
function app.set_ime_font(font_name, font_size)
end
---设置输入法候选窗口矩形
---Sets the IME candidate window rectangle.
---@param rect? soluna.app.ImeRect nil 会清除矩形 / nil clears the rectangle
function app.set_ime_rect(rect)
end
return app
================================================
FILE: docs/args.lua
================================================
---@meta
---可提交给 batch 的绘制对象
---Drawable object accepted by `Batch:add`.
---@alias soluna.Drawable integer|string|userdata
---绘制批次
---Render batch object.
---@class Batch
local batch = {}
---向批次添加 sprite、material 对象或 packed stream
---Adds a sprite id, material userdata, or packed command stream.
---@param sprite soluna.Drawable sprite ID、material userdata 或 packed string / Sprite id, material userdata, or packed string
---@param x? number X 坐标,默认 0 / X position, default 0
---@param y? number Y 坐标,默认 0 / Y position, default 0
function batch:add(sprite, x, y)
end
---打开或关闭变换层
---Opens or closes a transform layer.
---@overload fun(self: Batch)
---@overload fun(self: Batch, rotation: number)
---@overload fun(self: Batch, x: number, y: number)
---@overload fun(self: Batch, scale: number, x: number, y: number)
---@param scale number 缩放倍率,不能为 0 / Scale factor, cannot be 0
---@param rotation number 旋转弧度 / Rotation in radians
---@param x number X 平移 / X translation
---@param y number Y 平移 / Y translation
function batch:layer(scale, rotation, x, y)
end
---把屏幕点转换到当前 layer 坐标
---Transforms a screen point into the current layer space.
---@param x number 屏幕 X / Screen X
---@param y number 屏幕 Y / Screen Y
---@return number x 转换后的 X / Transformed X
---@return number y 转换后的 Y / Transformed Y
function batch:point(x, y)
end
---入口参数表
---Entry argument table passed to the game script.
---@class Args
---@field width integer 当前窗口宽度 / Current window width
---@field height integer 当前窗口高度 / Current window height
---@field batch Batch 绘制批次 / Render batch
---@field [integer] string 启动参数 / Startup argument
local args = {}
return args
================================================
FILE: docs/callback.lua
================================================
---@meta
---游戏入口返回的 callback 表
---Callback table returned by the game entry script.
---@class Callback
local callback = {}
---每帧调用
---Called once per frame.
---@param count integer frame 计数 / Frame counter
function callback.frame(count)
end
---键盘事件
---Keyboard event.
---@param keycode integer Sokol key code / Sokol key code
---@param state integer 1 为按下,0 为释放 / 1 for key down, 0 for key up
function callback.key(keycode, state)
end
---字符输入事件
---Text input event.
---@param codepoint integer Unicode codepoint / Unicode codepoint
function callback.char(codepoint)
end
---鼠标按钮事件
---Mouse button event.
---@param button integer 0 左键,1 右键,2 中键 / 0 left, 1 right, 2 middle
---@param state integer 1 为按下,0 为释放 / 1 for down, 0 for up
function callback.mouse_button(button, state)
end
---鼠标移动事件
---Mouse move event.
---@param x integer 逻辑像素 X / Logical pixel X
---@param y integer 逻辑像素 Y / Logical pixel Y
function callback.mouse_move(x, y)
end
---鼠标滚轮事件
---Mouse scroll event.
---@param y integer 垂直滚动量 / Vertical scroll delta
---@param x integer 水平滚动量 / Horizontal scroll delta
function callback.mouse_scroll(y, x)
end
---其它鼠标事件
---Other mouse event.
---@param event_type integer Sokol event type / Sokol event type
function callback.mouse(event_type)
end
---触摸开始
---Touch begin event.
---@param x integer 逻辑像素 X / Logical pixel X
---@param y integer 逻辑像素 Y / Logical pixel Y
function callback.touch_begin(x, y)
end
---触摸移动
---Touch move event.
---@param x integer 逻辑像素 X / Logical pixel X
---@param y integer 逻辑像素 Y / Logical pixel Y
function callback.touch_moved(x, y)
end
---触摸结束
---Touch end event.
---@param x integer 逻辑像素 X / Logical pixel X
---@param y integer 逻辑像素 Y / Logical pixel Y
function callback.touch_end(x, y)
end
---触摸取消
---Touch cancelled event.
---@param x integer 逻辑像素 X / Logical pixel X
---@param y integer 逻辑像素 Y / Logical pixel Y
function callback.touch_cancelled(x, y)
end
---窗口尺寸变化
---Window resize event.
---@param width integer 新窗口宽度 / New window width
---@param height integer 新窗口高度 / New window height
function callback.window_resize(width, height)
end
return callback
================================================
FILE: docs/coroutine.lua
================================================
---@meta soluna.coroutine
---ltask 兼容 coroutine 模块
---ltask-compatible coroutine module.
---@class soluna.coroutine
local coroutine = {}
---创建受 ltask 跟踪的 coroutine
---Creates a coroutine tracked by the ltask bridge.
---@param f function coroutine 函数 / Coroutine function
---@return thread co coroutine 线程 / Coroutine thread
function coroutine.create(f)
end
---恢复 coroutine
---Resumes a tracked coroutine.
---@param co thread coroutine 线程 / Coroutine thread
---@param ... any 传入参数 / Arguments
---@return boolean ok 是否成功 / Whether resume succeeded
---@return any ... 返回值或错误 / Return values or error
function coroutine.resume(co, ...)
end
---挂起当前 coroutine
---Yields from the current coroutine.
---@param ... any 返回给 resume 的值 / Values returned to resume
---@return any ... 下次 resume 传入的值 / Values passed by the next resume
function coroutine.yield(...)
end
return coroutine
================================================
FILE: docs/crypt.lua
================================================
---@meta soluna.crypt
---密码辅助模块
---Cryptography helper module.
---@class soluna.crypt
local crypt = {}
---编码为小写十六进制字符串
---Encodes binary data as lower-case hex.
---@param data string 二进制数据 / Binary data
---@return string hex 十六进制字符串 / Hex string
function crypt.hexencode(data)
end
---计算 SHA-1 摘要
---Calculates SHA-1 digest.
---@param data string 输入数据 / Input data
---@return string hash 20 字节摘要 / 20-byte digest
function crypt.sha1(data)
end
return crypt
================================================
FILE: docs/datalist.lua
================================================
---@meta soluna.datalist
---datalist 解析模块
---Datalist parser module.
---@class soluna.datalist
local datalist = {}
---解析 datalist 文本
---Parses datalist text.
---@param data string datalist 文本 / Datalist text
---@return table parsed 解析结果 / Parsed result
function datalist.parse(data)
end
---为 datalist 格式引用字符串
---Quotes a string for datalist syntax.
---@param str string 原始字符串 / Raw string
---@return string quoted quoted 字符串 / Quoted string
function datalist.quote(str)
end
return datalist
================================================
FILE: docs/file.lua
================================================
---@meta soluna.file
---文件属性表
---File attribute table.
---@class soluna.file.Attributes
---@field mode "file"|"directory"|"link"|"socket"|"named pipe"|"char device"|"block device"|"other" 文件类型 / File type
---@field dev integer 设备号 / Device id
---@field ino integer inode / Inode
---@field nlink integer 硬链接数 / Hard link count
---@field uid integer owner user id / Owner user id
---@field gid integer owner group id / Owner group id
---@field rdev integer special file device id / Special file device id
---@field access integer 最后访问时间 / Last access time
---@field modification integer 最后修改时间 / Last modification time
---@field change integer 最后状态变化时间 / Last status change time
---@field size integer 文件大小 / File size
---@field permissions string 权限字符串 / Permission string
---文件加载模块
---File loading module.
---@class soluna.file
local file = {}
---加载文件内容
---Loads file contents.
---@param filename string 文件路径 / File path
---@param mode? string 本地文件打开模式,默认 `"rb"` / Local file open mode, default `"rb"`
---@return string? content 文件内容;失败返回 nil / File contents, nil on failure
function file.load(filename, mode)
end
---获取文件属性
---Gets file attributes.
---@param filename string 文件路径 / File path
---@return soluna.file.Attributes|string? attributes 本地文件返回属性表,zip 文件可返回 `"file"` 或 `"directory"` / Local files return attributes; zip files may return `"file"` or `"directory"`
function file.attributes(filename)
end
---判断文件是否存在
---Checks whether a file exists.
---@param filename string 文件路径 / File path
---@return boolean? exists 存在时为 true,否则为 nil / true when found, nil otherwise
function file.exist(filename)
end
---判断本地文件是否存在
---Checks whether a local file exists.
---@param filename string 文件路径 / File path
---@return boolean? exists 存在时为 true,否则为 nil / true when found, nil otherwise
function file.local_exist(filename)
end
---加载本地文件内容
---Loads local file contents.
---@param filename string 文件路径 / File path
---@param mode? string 打开模式,默认 `"rb"` / Open mode, default `"rb"`
---@return string? content 文件内容;失败返回 nil / File contents, nil on failure
function file.local_load(filename, mode)
end
---遍历目录条目
---Iterates directory entries.
---@param path string 目录路径 / Directory path
---@return fun(): string? iterator 迭代器 / Iterator
---@return userdata? state 本地目录句柄 / Local directory handle
function file.dir(path)
end
return file
================================================
FILE: docs/font.lua
================================================
---@meta soluna.font
---字体模块
---Font module.
---@class soluna.font
local font = {}
---导入 TrueType 字体数据
---Imports TrueType font data.
---@param data string TTF/TTC 字体数据 / TTF/TTC font data
function font.import(data)
end
---按字体族名获取 font id
---Gets a font id by family name.
---@param name string 字体族名 / Font family name
---@return integer? fontid 字体 id;找不到时为 nil / Font id, nil when not found
function font.name(name)
end
---返回字体管理器 C 指针
---Returns the native font manager pointer.
---@return lightuserdata fontcobj 字体管理器指针 / Font manager pointer
function font.cobj()
end
return font
================================================
FILE: docs/font_system.lua
================================================
---@meta soluna.font.system
---系统字体模块
---System font module.
---@class soluna.font.system
local font_system = {}
---按字体族名读取系统 TTF/TTC 数据
---Reads system TTF/TTC data by family name.
---@param name string 字体族名 / Font family name
---@return string? data 字体数据;wasm 或失败时可能为 nil / Font data, nil on wasm or failure
function font_system.ttfdata(name)
end
return font_system
================================================
FILE: docs/image.lua
================================================
---@meta soluna.image
---图片模块
---Image module.
---@class soluna.image
local image = {}
---从 PNG 数据加载 RGBA 图片
---Loads RGBA image data from PNG bytes.
---@param data string PNG 数据 / PNG bytes
---@return string? data RGBA 像素数据,失败时为 nil / RGBA pixels, nil on failure
---@return integer|string width_or_error 成功时为宽度,失败时为错误信息 / Width on success, error message on failure
---@return integer? height 高度 / Height
function image.load(data)
end
---按比例缩放 RGBA 或灰度图片
---Resizes RGBA or grayscale image data by scale factors.
---@param data string RGBA 或灰度像素数据 / RGBA or grayscale pixels
---@param width integer 原始宽度 / Source width
---@param height integer 原始高度 / Source height
---@param scale_x number X 缩放倍率 / X scale factor
---@param scale_y? number Y 缩放倍率,默认等于 `scale_x` / Y scale factor, default is `scale_x`
---@return string data 缩放后的像素数据 / Resized pixels
---@return integer width 新宽度 / New width
---@return integer height 新高度 / New height
function image.resize(data, width, height, scale_x, scale_y)
end
return image
================================================
FILE: docs/layout.lua
================================================
---@meta soluna.layout
---layout 元素对象
---Layout element object.
---@class soluna.layout.Element
local element = {}
---更新元素 Yoga 属性
---Updates Yoga attributes on the element.
---@param attr table 属性表 / Attribute table
function element:update(attr)
end
---读取元素布局结果
---Reads calculated element layout.
---@return number x X 坐标 / X coordinate
---@return number y Y 坐标 / Y coordinate
---@return number w 宽度 / Width
---@return number h 高度 / Height
function element:get()
end
---返回元素属性表
---Returns the element attribute table.
---@return table attrs 属性表 / Attribute table
function element:attribs()
end
---layout 文档对象
---Layout document object.
---@class soluna.layout.Document
---@field [string] soluna.layout.Element 按 id 访问元素 / Element access by id
---layout 绘制条目
---Calculated drawable layout item.
---@class soluna.layout.Item
---@field x number X 坐标 / X coordinate
---@field y number Y 坐标 / Y coordinate
---@field w number 宽度 / Width
---@field h number 高度 / Height
---@field [string] any datalist 属性 / Datalist attributes
---layout 计算结果
---Calculated layout item list.
---@class soluna.layout.Result
---@field [integer] soluna.layout.Item 绘制条目 / Drawable item
---@field width number 根节点宽度 / Root width
---@field height number 根节点高度 / Root height
---Yoga layout 模块
---Yoga layout module.
---@class soluna.layout
local layout = {}
---加载 layout 定义
---Loads a layout definition.
---@param filename_or_list string|table layout 文件路径或已解析 datalist / Layout file path or parsed datalist
---@param scripts? fun(name: string): table children 动态 children resolver / Dynamic children resolver
---@return soluna.layout.Document document layout 文档 / Layout document
function layout.load(filename_or_list, scripts)
end
---计算 layout 并返回绘制条目
---Calculates layout and returns drawable items.
---@param document soluna.layout.Document layout 文档 / Layout document
---@return soluna.layout.Result items 绘制条目列表 / Drawable item list
function layout.calc(document)
end
return layout
================================================
FILE: docs/lfs.lua
================================================
---@meta soluna.lfs
---本地文件属性表
---Local file attribute table.
---@class soluna.lfs.Attributes
---@field mode "file"|"directory"|"link"|"socket"|"named pipe"|"char device"|"block device"|"other" 文件类型 / File type
---@field size integer 文件大小 / File size
---@field access integer 最后访问时间 / Last access time
---@field modification integer 最后修改时间 / Last modification time
---@field change integer 最后状态变化时间 / Last status change time
---@field permissions string 权限字符串 / Permission string
---本地文件系统模块
---Local filesystem module.
---@class soluna.lfs
local lfs = {}
---获取文件属性
---Gets file attributes.
---@param filename string 文件路径 / File path
---@param member? string 可选属性名 / Optional attribute name
---@return soluna.lfs.Attributes|integer|string|nil attributes 属性表或指定属性 / Attribute table or selected attribute
---@return string? err 错误信息 / Error message
---@return integer? errno 系统错误码 / System errno
function lfs.attributes(filename, member)
end
---遍历目录条目
---Iterates directory entries.
---@param path string 目录路径 / Directory path
---@return fun(): string? iterator 迭代器 / Iterator
---@return userdata state 目录句柄 / Directory handle
function lfs.dir(path)
end
return lfs
================================================
FILE: docs/material_mask.lua
================================================
---@meta soluna.material.mask
---mask material 模块
---Mask material module.
---@class soluna.material.mask
local matmask = {}
---创建带颜色遮罩的 sprite command stream
---Creates a colored mask sprite command stream.
---@param sprite integer 1-based sprite id / 1-based sprite id
---@param color integer ARGB 颜色,alpha 为 0 时补为 0xff / ARGB color; alpha 0 is promoted to 0xff
---@return string stream 可传给 `batch:add` 的 packed stream / Packed stream for `batch:add`
function matmask.mask(sprite, color)
end
return matmask
================================================
FILE: docs/material_quad.lua
================================================
---@meta soluna.material.quad
---quad material 模块
---Quad material module.
---@class soluna.material.quad
local matquad = {}
---创建纯色矩形 command stream
---Creates a solid rectangle command stream.
---@param width integer 宽度 / Width
---@param height integer 高度 / Height
---@param color integer ARGB 颜色,alpha 为 0 时补为 0xff / ARGB color; alpha 0 is promoted to 0xff
---@return string stream 可传给 `batch:add` 的 packed stream / Packed stream for `batch:add`
function matquad.quad(width, height, color)
end
return matquad
================================================
FILE: docs/material_text.lua
================================================
---@meta soluna.material.text
---文本块创建函数
---Text block builder function.
---@alias soluna.material.text.Block fun(text: string, width?: integer, height?: integer): string, integer
---光标位置查询函数
---Text cursor query function.
---@alias soluna.material.text.Cursor fun(text: string, position: integer, width?: integer, height?: integer): integer, integer, integer, integer, integer, integer
---text material 模块
---Text material module.
---@class soluna.material.text
local mattext = {}
---创建文本块和光标查询函数
---Creates text block and cursor query functions.
---@param fontcobj lightuserdata `font.cobj()` 返回的字体管理器指针 / Font manager pointer returned by `font.cobj()`
---@param fontid integer `font.name()` 返回的字体 id / Font id returned by `font.name()`
---@param size? integer 字体像素大小,默认 16 / Font pixel size, default 16
---@param color? integer ARGB 颜色,默认 `0xff000000` / ARGB color, default `0xff000000`
---@param alignment? string 对齐代码,如 `"LT"`、`"CV"`、`"RB"` / Alignment code such as `"LT"`, `"CV"`, `"RB"`
---@return soluna.material.text.Block block 创建 packed text stream / Creates packed text stream
---@return soluna.material.text.Cursor cursor 查询光标矩形 / Queries cursor rectangle
function mattext.block(fontcobj, fontid, size, color, alignment)
end
return mattext
================================================
FILE: docs/soluna.lua
================================================
---@meta
---单个 sprite id 或动画帧 id 列表
---Single sprite id or animation frame id list.
---@alias Sprite integer|integer[]
---sprite bundle 名称到 id 的映射
---Sprite bundle name-to-id mapping.
---@alias SpriteBundle table
---窗口图标图片描述
---Window icon image descriptor.
---@class soluna.IconImage
---@field data string|userdata|lightuserdata RGBA 像素数据 / RGBA pixel buffer
---@field w? integer 宽度;也可使用 `width` / Width; `width` is also accepted
---@field h? integer 高度;也可使用 `height` / Height; `height` is also accepted
---@field width? integer 宽度;`w` 的别名 / Width alias for `w`
---@field height? integer 高度;`h` 的别名 / Height alias for `h`
---@field stride? integer 每行字节数,默认 `width * 4` / Row stride in bytes, default `width * 4`
---@field size? integer `lightuserdata` 数据大小 / Buffer size for `lightuserdata`
---运行时预加载 sprite 图片
---Runtime preloaded sprite image.
---@class soluna.PreloadSprite
---@field filename string 虚拟文件名 / Virtual filename
---@field content string RGBA 像素数据 / RGBA pixel data
---@field w integer 宽度 / Width
---@field h integer 高度 / Height
---音频播放选项
---Audio playback options.
---@class soluna.AudioPlayOptions
---@field group? string audio bus 名称 / Audio bus name
---@field volume? number 线性音量倍率 / Linear volume multiplier
---@field pan? number 声像,范围通常为 `[-1.0, 1.0]` / Stereo pan, usually in `[-1.0, 1.0]`
---@field pitch? number pitch 倍率 / Pitch multiplier
---@field loop? boolean 是否循环播放 / Whether playback loops
---@field stream? boolean 是否流式播放 / Whether to stream instead of preloading
---音频播放实例
---Audio playback instance.
---@class soluna.AudioVoice
local AudioVoice = {}
---停止播放
---Stops playback.
---@param fade_seconds? number fade out 秒数 / Fade-out seconds
---@return boolean ok voice 有效且请求成功时为 true / true when the voice is valid and the request succeeds
function AudioVoice:stop(fade_seconds)
end
---返回是否仍在播放
---Returns whether the voice is still playing.
---@return boolean playing 是否播放中 / Whether it is playing
function AudioVoice:playing()
end
---设置 voice 音量
---Sets voice volume.
---@param volume number 线性音量倍率 / Linear volume multiplier
---@return boolean ok voice 有效时为 true / true when the voice is valid
function AudioVoice:set_volume(volume)
end
---设置 voice 声像
---Sets voice pan.
---@param pan number 声像 / Stereo pan
---@return boolean ok voice 有效时为 true / true when the voice is valid
function AudioVoice:set_pan(pan)
end
---设置 voice pitch
---Sets voice pitch.
---@param pitch number pitch 倍率 / Pitch multiplier
---@return boolean ok voice 有效时为 true / true when the voice is valid
function AudioVoice:set_pitch(pitch)
end
---设置 voice 是否循环
---Sets whether the voice loops.
---@param loop boolean 是否循环 / Whether to loop
---@return boolean ok voice 有效时为 true / true when the voice is valid
function AudioVoice:set_loop(loop)
end
---跳转播放位置
---Seeks to a playback position.
---@param seconds number 目标秒数 / Target seconds
---@return boolean ok voice 有效且 seek 成功时为 true / true when valid and seek succeeds
function AudioVoice:seek(seconds)
end
---返回当前播放位置
---Returns the current playback position.
---@return number? seconds 当前秒数 / Current seconds
---@return string? err 错误信息 / Error message
function AudioVoice:tell()
end
---音频 bus 句柄
---Audio bus handle.
---@class soluna.AudioBus
local AudioBus = {}
---设置 bus 音量
---Sets bus volume.
---@param volume number 线性音量倍率 / Linear volume multiplier
---@return boolean ok bus 存在时为 true / true when the bus exists
function AudioBus:set_volume(volume)
end
---Soluna 主模块
---Soluna root module.
---@class soluna
---@field platform "windows"|"macos"|"linux"|"wasm" 当前平台 / Current platform
---@field version string 运行时版本字符串 / Runtime version string
---@field version_api integer API 版本号 / API version number
local soluna = {}
---返回 `.game` 设置表
---Returns the `.game` settings table.
---@return table settings 游戏设置 / Game settings
function soluna.settings()
end
---设置窗口标题
---Sets the window title.
---@param text string 标题文字 / Window title
function soluna.set_window_title(text)
end
---设置窗口图标
---Sets one icon image or an icon image list.
---@param data soluna.IconImage|soluna.IconImage[] 图标数据 / Icon data
function soluna.set_icon(data)
end
---返回并创建游戏数据目录
---Returns and creates the game data directory.
---@param name? string 项目名,默认来自 `settings.project` / Project name, default from `settings.project`
---@return string path 绝对路径,结尾带 `/` / Absolute path ending with `/`
function soluna.gamedir(name)
end
---加载 sprite bundle
---Loads a sprite bundle.
---@param filename string|table `.dl` 文件路径或已解析 bundle 表 / `.dl` path or parsed bundle table
---@return SpriteBundle sprites sprite 名称映射 / Sprite name mapping
function soluna.load_sprites(filename)
end
---预加载运行时生成的 RGBA sprite 图片
---Preloads runtime-generated RGBA sprite images.
---@param sprites soluna.PreloadSprite|soluna.PreloadSprite[] 单个 sprite 或列表 / One sprite or a list
function soluna.preload(sprites)
end
---加载音频定义 bundle
---Loads an audio definition bundle.
---@param filename string `sounds.dl` 文件路径 / `sounds.dl` path
function soluna.load_sounds(filename)
end
---播放音频并返回 voice
---Plays a sound and returns a voice handle.
---@param name string `sounds.dl` 中的音频名 / Sound name from `sounds.dl`
---@param opts? soluna.AudioPlayOptions 播放选项覆盖 / Playback option overrides
---@return soluna.AudioVoice? voice voice 句柄 / Voice handle
---@return string? err 错误信息 / Error message
function soluna.play_sound(name, opts)
end
---返回 audio bus 句柄
---Returns an audio bus handle.
---@param name string bus 名称 / Bus name
---@return soluna.AudioBus? bus bus 句柄 / Bus handle
---@return string? err 错误信息 / Error message
function soluna.audio_bus(name)
end
return soluna
================================================
FILE: docs/text.lua
================================================
---@meta soluna.text
---文本预处理模块
---Text preprocessing module.
---@class soluna.text
---@field convert table 文本转换缓存表 / Text conversion cache table
local text = {}
---初始化内嵌 icon bundle
---Initializes the embedded icon bundle.
---@param bundle_file string icon bundle `.dl` 文件路径 / Icon bundle `.dl` path
function text.init(bundle_file)
end
return text
================================================
FILE: docs/url.lua
================================================
---@meta soluna.url
---URL 模块
---URL module.
---@class soluna.url
local url = {}
---用系统默认浏览器打开 URL
---Opens a URL with the system default browser.
---@param link string URL / URL
function url.open(link)
end
return url
================================================
FILE: docs/zip.lua
================================================
---@meta soluna.zip
---ZIP 模块
---ZIP module.
---@class soluna.zip
local zip = {}
---打开 ZIP 文件
---Opens a ZIP archive.
---@param filename string ZIP 文件路径 / ZIP file path
---@param mode "r"|"w"|"a" 打开模式:读、写、追加 / Open mode: read, write, append
---@return userdata? zipfile ZIP 句柄;失败时为 nil / ZIP handle, nil on failure
function zip.open(filename, mode)
end
return zip
================================================
FILE: extlua/extlua.c
================================================
// AUTO GENERATED by extlua.temp.c, DONT EDIT
#include
#include
#include
#include
#include
struct lua_api {
int version;
lua_State * (*lua_newstate) (lua_Alloc f, void *ud, unsigned seed);
void (*lua_close) (lua_State *L);
lua_State * (*lua_newthread) (lua_State *L);
int (*lua_closethread) (lua_State *L, lua_State *from);
lua_CFunction (*lua_atpanic) (lua_State *L, lua_CFunction panicf);
lua_Number (*lua_version) (lua_State *L);
int (*lua_absindex) (lua_State *L, int idx);
int (*lua_gettop) (lua_State *L);
void (*lua_settop) (lua_State *L, int idx);
void (*lua_pushvalue) (lua_State *L, int idx);
void (*lua_rotate) (lua_State *L, int idx, int n);
void (*lua_copy) (lua_State *L, int fromidx, int toidx);
int (*lua_checkstack) (lua_State *L, int n);
void (*lua_xmove) (lua_State *from, lua_State *to, int n);
int (*lua_isnumber) (lua_State *L, int idx);
int (*lua_isstring) (lua_State *L, int idx);
int (*lua_iscfunction) (lua_State *L, int idx);
int (*lua_isinteger) (lua_State *L, int idx);
int (*lua_isuserdata) (lua_State *L, int idx);
int (*lua_type) (lua_State *L, int idx);
const char * (*lua_typename) (lua_State *L, int tp);
lua_Number (*lua_tonumberx) (lua_State *L, int idx, int *isnum);
lua_Integer (*lua_tointegerx) (lua_State *L, int idx, int *isnum);
int (*lua_toboolean) (lua_State *L, int idx);
const char * (*lua_tolstring) (lua_State *L, int idx, size_t *len);
lua_Unsigned (*lua_rawlen) (lua_State *L, int idx);
lua_CFunction (*lua_tocfunction) (lua_State *L, int idx);
void * (*lua_touserdata) (lua_State *L, int idx);
lua_State * (*lua_tothread) (lua_State *L, int idx);
const void * (*lua_topointer) (lua_State *L, int idx);
void (*lua_arith) (lua_State *L, int op);
int (*lua_rawequal) (lua_State *L, int idx1, int idx2);
int (*lua_compare) (lua_State *L, int idx1, int idx2, int op);
void (*lua_pushnil) (lua_State *L);
void (*lua_pushnumber) (lua_State *L, lua_Number n);
void (*lua_pushinteger) (lua_State *L, lua_Integer n);
const char * (*lua_pushlstring) (lua_State *L, const char *s, size_t len);
const char * (*lua_pushexternalstring) (lua_State *L,
const char *s, size_t len, lua_Alloc falloc, void *ud);
const char * (*lua_pushstring) (lua_State *L, const char *s);
const char * (*lua_pushvfstring) (lua_State *L, const char *fmt,
va_list argp);
void (*lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
void (*lua_pushboolean) (lua_State *L, int b);
void (*lua_pushlightuserdata) (lua_State *L, void *p);
int (*lua_pushthread) (lua_State *L);
int (*lua_getglobal) (lua_State *L, const char *name);
int (*lua_gettable) (lua_State *L, int idx);
int (*lua_getfield) (lua_State *L, int idx, const char *k);
int (*lua_geti) (lua_State *L, int idx, lua_Integer n);
int (*lua_rawget) (lua_State *L, int idx);
int (*lua_rawgeti) (lua_State *L, int idx, lua_Integer n);
int (*lua_rawgetp) (lua_State *L, int idx, const void *p);
void (*lua_createtable) (lua_State *L, int narr, int nrec);
void * (*lua_newuserdatauv) (lua_State *L, size_t sz, int nuvalue);
int (*lua_getmetatable) (lua_State *L, int objindex);
int (*lua_getiuservalue) (lua_State *L, int idx, int n);
void (*lua_setglobal) (lua_State *L, const char *name);
void (*lua_settable) (lua_State *L, int idx);
void (*lua_setfield) (lua_State *L, int idx, const char *k);
void (*lua_seti) (lua_State *L, int idx, lua_Integer n);
void (*lua_rawset) (lua_State *L, int idx);
void (*lua_rawseti) (lua_State *L, int idx, lua_Integer n);
void (*lua_rawsetp) (lua_State *L, int idx, const void *p);
int (*lua_setmetatable) (lua_State *L, int objindex);
int (*lua_setiuservalue) (lua_State *L, int idx, int n);
void (*lua_callk) (lua_State *L, int nargs, int nresults,
lua_KContext ctx, lua_KFunction k);
int (*lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc,
lua_KContext ctx, lua_KFunction k);
int (*lua_load) (lua_State *L, lua_Reader reader, void *dt,
const char *chunkname, const char *mode);
int (*lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip);
int (*lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx,
lua_KFunction k);
int (*lua_resume) (lua_State *L, lua_State *from, int narg,
int *nres);
int (*lua_status) (lua_State *L);
int (*lua_isyieldable) (lua_State *L);
void (*lua_setwarnf) (lua_State *L, lua_WarnFunction f, void *ud);
void (*lua_warning) (lua_State *L, const char *msg, int tocont);
int (*lua_error) (lua_State *L);
int (*lua_next) (lua_State *L, int idx);
void (*lua_concat) (lua_State *L, int n);
void (*lua_len) (lua_State *L, int idx);
unsigned (*lua_numbertocstring) (lua_State *L, int idx, char *buff);
size_t (*lua_stringtonumber) (lua_State *L, const char *s);
lua_Alloc (*lua_getallocf) (lua_State *L, void **ud);
void (*lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
void (*lua_toclose) (lua_State *L, int idx);
void (*lua_closeslot) (lua_State *L, int idx);
int (*lua_getstack) (lua_State *L, int level, lua_Debug *ar);
int (*lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar);
const char * (*lua_getlocal) (lua_State *L, const lua_Debug *ar, int n);
const char * (*lua_setlocal) (lua_State *L, const lua_Debug *ar, int n);
const char * (*lua_getupvalue) (lua_State *L, int funcindex, int n);
const char * (*lua_setupvalue) (lua_State *L, int funcindex, int n);
void * (*lua_upvalueid) (lua_State *L, int fidx, int n);
void (*lua_upvaluejoin) (lua_State *L, int fidx1, int n1,
int fidx2, int n2);
void (*lua_sethook) (lua_State *L, lua_Hook func, int mask, int count);
lua_Hook (*lua_gethook) (lua_State *L);
int (*lua_gethookmask) (lua_State *L);
int (*lua_gethookcount) (lua_State *L);
void (*luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz);
int (*luaL_getmetafield) (lua_State *L, int obj, const char *e);
int (*luaL_callmeta) (lua_State *L, int obj, const char *e);
const char * (*luaL_tolstring) (lua_State *L, int idx, size_t *len);
int (*luaL_argerror) (lua_State *L, int arg, const char *extramsg);
int (*luaL_typeerror) (lua_State *L, int arg, const char *tname);
const char * (*luaL_checklstring) (lua_State *L, int arg,
size_t *l);
const char * (*luaL_optlstring) (lua_State *L, int arg,
const char *def, size_t *l);
lua_Number (*luaL_checknumber) (lua_State *L, int arg);
lua_Number (*luaL_optnumber) (lua_State *L, int arg, lua_Number def);
lua_Integer (*luaL_checkinteger) (lua_State *L, int arg);
lua_Integer (*luaL_optinteger) (lua_State *L, int arg,
lua_Integer def);
void (*luaL_checkstack) (lua_State *L, int sz, const char *msg);
void (*luaL_checktype) (lua_State *L, int arg, int t);
void (*luaL_checkany) (lua_State *L, int arg);
int (*luaL_newmetatable) (lua_State *L, const char *tname);
void (*luaL_setmetatable) (lua_State *L, const char *tname);
void * (*luaL_testudata) (lua_State *L, int ud, const char *tname);
void * (*luaL_checkudata) (lua_State *L, int ud, const char *tname);
void (*luaL_where) (lua_State *L, int lvl);
int (*luaL_checkoption) (lua_State *L, int arg, const char *def,
const char *const lst[]);
int (*luaL_fileresult) (lua_State *L, int stat, const char *fname);
int (*luaL_execresult) (lua_State *L, int stat);
void * (*luaL_alloc) (void *ud, void *ptr, size_t osize,
size_t nsize);
int (*luaL_ref) (lua_State *L, int t);
void (*luaL_unref) (lua_State *L, int t, int ref);
int (*luaL_loadfilex) (lua_State *L, const char *filename,
const char *mode);
int (*luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz,
const char *name, const char *mode);
int (*luaL_loadstring) (lua_State *L, const char *s);
lua_State * (*luaL_newstate) (void);
unsigned (*luaL_makeseed) (lua_State *L);
lua_Integer (*luaL_len) (lua_State *L, int idx);
void (*luaL_addgsub) (luaL_Buffer *b, const char *s,
const char *p, const char *r);
const char * (*luaL_gsub) (lua_State *L, const char *s,
const char *p, const char *r);
void (*luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
int (*luaL_getsubtable) (lua_State *L, int idx, const char *fname);
void (*luaL_traceback) (lua_State *L, lua_State *L1,
const char *msg, int level);
void (*luaL_requiref) (lua_State *L, const char *modname,
lua_CFunction openf, int glb);
void (*luaL_buffinit) (lua_State *L, luaL_Buffer *B);
char * (*luaL_prepbuffsize) (luaL_Buffer *B, size_t sz);
void (*luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);
void (*luaL_addstring) (luaL_Buffer *B, const char *s);
void (*luaL_addvalue) (luaL_Buffer *B);
void (*luaL_pushresult) (luaL_Buffer *B);
void (*luaL_pushresultsize) (luaL_Buffer *B, size_t sz);
char * (*luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz);
int (*lua_gc) (lua_State *L, int what, ...);
};
static struct lua_api API;
LUA_API lua_State *
lua_newstate(lua_Alloc f, void *ud, unsigned seed) {
return API.lua_newstate(f,ud,seed);
}
LUA_API void
lua_close(lua_State *L) {
API.lua_close(L);
}
LUA_API lua_State *
lua_newthread(lua_State *L) {
return API.lua_newthread(L);
}
LUA_API int
lua_closethread(lua_State *L, lua_State *from) {
return API.lua_closethread(L,from);
}
LUA_API lua_CFunction
lua_atpanic(lua_State *L, lua_CFunction panicf) {
return API.lua_atpanic(L,panicf);
}
LUA_API lua_Number
lua_version(lua_State *L) {
return API.lua_version(L);
}
LUA_API int
lua_absindex(lua_State *L, int idx) {
return API.lua_absindex(L,idx);
}
LUA_API int
lua_gettop(lua_State *L) {
return API.lua_gettop(L);
}
LUA_API void
lua_settop(lua_State *L, int idx) {
API.lua_settop(L,idx);
}
LUA_API void
lua_pushvalue(lua_State *L, int idx) {
API.lua_pushvalue(L,idx);
}
LUA_API void
lua_rotate(lua_State *L, int idx, int n) {
API.lua_rotate(L,idx,n);
}
LUA_API void
lua_copy(lua_State *L, int fromidx, int toidx) {
API.lua_copy(L,fromidx,toidx);
}
LUA_API int
lua_checkstack(lua_State *L, int n) {
return API.lua_checkstack(L,n);
}
LUA_API void
lua_xmove(lua_State *from, lua_State *to, int n) {
API.lua_xmove(from,to,n);
}
LUA_API int
lua_isnumber(lua_State *L, int idx) {
return API.lua_isnumber(L,idx);
}
LUA_API int
lua_isstring(lua_State *L, int idx) {
return API.lua_isstring(L,idx);
}
LUA_API int
lua_iscfunction(lua_State *L, int idx) {
return API.lua_iscfunction(L,idx);
}
LUA_API int
lua_isinteger(lua_State *L, int idx) {
return API.lua_isinteger(L,idx);
}
LUA_API int
lua_isuserdata(lua_State *L, int idx) {
return API.lua_isuserdata(L,idx);
}
LUA_API int
lua_type(lua_State *L, int idx) {
return API.lua_type(L,idx);
}
LUA_API const char *
lua_typename(lua_State *L, int tp) {
return API.lua_typename(L,tp);
}
LUA_API lua_Number
lua_tonumberx(lua_State *L, int idx, int *isnum) {
return API.lua_tonumberx(L,idx,isnum);
}
LUA_API lua_Integer
lua_tointegerx(lua_State *L, int idx, int *isnum) {
return API.lua_tointegerx(L,idx,isnum);
}
LUA_API int
lua_toboolean(lua_State *L, int idx) {
return API.lua_toboolean(L,idx);
}
LUA_API const char *
lua_tolstring(lua_State *L, int idx, size_t *len) {
return API.lua_tolstring(L,idx,len);
}
LUA_API lua_Unsigned
lua_rawlen(lua_State *L, int idx) {
return API.lua_rawlen(L,idx);
}
LUA_API lua_CFunction
lua_tocfunction(lua_State *L, int idx) {
return API.lua_tocfunction(L,idx);
}
LUA_API void *
lua_touserdata(lua_State *L, int idx) {
return API.lua_touserdata(L,idx);
}
LUA_API lua_State *
lua_tothread(lua_State *L, int idx) {
return API.lua_tothread(L,idx);
}
LUA_API const void *
lua_topointer(lua_State *L, int idx) {
return API.lua_topointer(L,idx);
}
LUA_API void
lua_arith(lua_State *L, int op) {
API.lua_arith(L,op);
}
LUA_API int
lua_rawequal(lua_State *L, int idx1, int idx2) {
return API.lua_rawequal(L,idx1,idx2);
}
LUA_API int
lua_compare(lua_State *L, int idx1, int idx2, int op) {
return API.lua_compare(L,idx1,idx2,op);
}
LUA_API void
lua_pushnil(lua_State *L) {
API.lua_pushnil(L);
}
LUA_API void
lua_pushnumber(lua_State *L, lua_Number n) {
API.lua_pushnumber(L,n);
}
LUA_API void
lua_pushinteger(lua_State *L, lua_Integer n) {
API.lua_pushinteger(L,n);
}
LUA_API const char *
lua_pushlstring(lua_State *L, const char *s, size_t len) {
return API.lua_pushlstring(L,s,len);
}
LUA_API const char *
lua_pushexternalstring(lua_State *L,
const char *s, size_t len, lua_Alloc falloc, void *ud) {
return API.lua_pushexternalstring(L,s,len,falloc,ud);
}
LUA_API const char *
lua_pushstring(lua_State *L, const char *s) {
return API.lua_pushstring(L,s);
}
LUA_API const char *
lua_pushvfstring(lua_State *L, const char *fmt,
va_list argp) {
return API.lua_pushvfstring(L,fmt,argp);
}
LUA_API void
lua_pushcclosure(lua_State *L, lua_CFunction fn, int n) {
API.lua_pushcclosure(L,fn,n);
}
LUA_API void
lua_pushboolean(lua_State *L, int b) {
API.lua_pushboolean(L,b);
}
LUA_API void
lua_pushlightuserdata(lua_State *L, void *p) {
API.lua_pushlightuserdata(L,p);
}
LUA_API int
lua_pushthread(lua_State *L) {
return API.lua_pushthread(L);
}
LUA_API int
lua_getglobal(lua_State *L, const char *name) {
return API.lua_getglobal(L,name);
}
LUA_API int
lua_gettable(lua_State *L, int idx) {
return API.lua_gettable(L,idx);
}
LUA_API int
lua_getfield(lua_State *L, int idx, const char *k) {
return API.lua_getfield(L,idx,k);
}
LUA_API int
lua_geti(lua_State *L, int idx, lua_Integer n) {
return API.lua_geti(L,idx,n);
}
LUA_API int
lua_rawget(lua_State *L, int idx) {
return API.lua_rawget(L,idx);
}
LUA_API int
lua_rawgeti(lua_State *L, int idx, lua_Integer n) {
return API.lua_rawgeti(L,idx,n);
}
LUA_API int
lua_rawgetp(lua_State *L, int idx, const void *p) {
return API.lua_rawgetp(L,idx,p);
}
LUA_API void
lua_createtable(lua_State *L, int narr, int nrec) {
API.lua_createtable(L,narr,nrec);
}
LUA_API void *
lua_newuserdatauv(lua_State *L, size_t sz, int nuvalue) {
return API.lua_newuserdatauv(L,sz,nuvalue);
}
LUA_API int
lua_getmetatable(lua_State *L, int objindex) {
return API.lua_getmetatable(L,objindex);
}
LUA_API int
lua_getiuservalue(lua_State *L, int idx, int n) {
return API.lua_getiuservalue(L,idx,n);
}
LUA_API void
lua_setglobal(lua_State *L, const char *name) {
API.lua_setglobal(L,name);
}
LUA_API void
lua_settable(lua_State *L, int idx) {
API.lua_settable(L,idx);
}
LUA_API void
lua_setfield(lua_State *L, int idx, const char *k) {
API.lua_setfield(L,idx,k);
}
LUA_API void
lua_seti(lua_State *L, int idx, lua_Integer n) {
API.lua_seti(L,idx,n);
}
LUA_API void
lua_rawset(lua_State *L, int idx) {
API.lua_rawset(L,idx);
}
LUA_API void
lua_rawseti(lua_State *L, int idx, lua_Integer n) {
API.lua_rawseti(L,idx,n);
}
LUA_API void
lua_rawsetp(lua_State *L, int idx, const void *p) {
API.lua_rawsetp(L,idx,p);
}
LUA_API int
lua_setmetatable(lua_State *L, int objindex) {
return API.lua_setmetatable(L,objindex);
}
LUA_API int
lua_setiuservalue(lua_State *L, int idx, int n) {
return API.lua_setiuservalue(L,idx,n);
}
LUA_API void
lua_callk(lua_State *L, int nargs, int nresults,
lua_KContext ctx, lua_KFunction k) {
API.lua_callk(L,nargs,nresults,ctx,k);
}
LUA_API int
lua_pcallk(lua_State *L, int nargs, int nresults, int errfunc,
lua_KContext ctx, lua_KFunction k) {
return API.lua_pcallk(L,nargs,nresults,errfunc,ctx,k);
}
LUA_API int
lua_load(lua_State *L, lua_Reader reader, void *dt,
const char *chunkname, const char *mode) {
return API.lua_load(L,reader,dt,chunkname,mode);
}
LUA_API int
lua_dump(lua_State *L, lua_Writer writer, void *data, int strip) {
return API.lua_dump(L,writer,data,strip);
}
LUA_API int
lua_yieldk(lua_State *L, int nresults, lua_KContext ctx,
lua_KFunction k) {
return API.lua_yieldk(L,nresults,ctx,k);
}
LUA_API int
lua_resume(lua_State *L, lua_State *from, int narg,
int *nres) {
return API.lua_resume(L,from,narg,nres);
}
LUA_API int
lua_status(lua_State *L) {
return API.lua_status(L);
}
LUA_API int
lua_isyieldable(lua_State *L) {
return API.lua_isyieldable(L);
}
LUA_API void
lua_setwarnf(lua_State *L, lua_WarnFunction f, void *ud) {
API.lua_setwarnf(L,f,ud);
}
LUA_API void
lua_warning(lua_State *L, const char *msg, int tocont) {
API.lua_warning(L,msg,tocont);
}
LUA_API int
lua_error(lua_State *L) {
return API.lua_error(L);
}
LUA_API int
lua_next(lua_State *L, int idx) {
return API.lua_next(L,idx);
}
LUA_API void
lua_concat(lua_State *L, int n) {
API.lua_concat(L,n);
}
LUA_API void
lua_len(lua_State *L, int idx) {
API.lua_len(L,idx);
}
LUA_API unsigned
lua_numbertocstring(lua_State *L, int idx, char *buff) {
return API.lua_numbertocstring(L,idx,buff);
}
LUA_API size_t
lua_stringtonumber(lua_State *L, const char *s) {
return API.lua_stringtonumber(L,s);
}
LUA_API lua_Alloc
lua_getallocf(lua_State *L, void **ud) {
return API.lua_getallocf(L,ud);
}
LUA_API void
lua_setallocf(lua_State *L, lua_Alloc f, void *ud) {
API.lua_setallocf(L,f,ud);
}
LUA_API void
lua_toclose(lua_State *L, int idx) {
API.lua_toclose(L,idx);
}
LUA_API void
lua_closeslot(lua_State *L, int idx) {
API.lua_closeslot(L,idx);
}
LUA_API int
lua_getstack(lua_State *L, int level, lua_Debug *ar) {
return API.lua_getstack(L,level,ar);
}
LUA_API int
lua_getinfo(lua_State *L, const char *what, lua_Debug *ar) {
return API.lua_getinfo(L,what,ar);
}
LUA_API const char *
lua_getlocal(lua_State *L, const lua_Debug *ar, int n) {
return API.lua_getlocal(L,ar,n);
}
LUA_API const char *
lua_setlocal(lua_State *L, const lua_Debug *ar, int n) {
return API.lua_setlocal(L,ar,n);
}
LUA_API const char *
lua_getupvalue(lua_State *L, int funcindex, int n) {
return API.lua_getupvalue(L,funcindex,n);
}
LUA_API const char *
lua_setupvalue(lua_State *L, int funcindex, int n) {
return API.lua_setupvalue(L,funcindex,n);
}
LUA_API void *
lua_upvalueid(lua_State *L, int fidx, int n) {
return API.lua_upvalueid(L,fidx,n);
}
LUA_API void
lua_upvaluejoin(lua_State *L, int fidx1, int n1,
int fidx2, int n2) {
API.lua_upvaluejoin(L,fidx1,n1,fidx2,n2);
}
LUA_API void
lua_sethook(lua_State *L, lua_Hook func, int mask, int count) {
API.lua_sethook(L,func,mask,count);
}
LUA_API lua_Hook
lua_gethook(lua_State *L) {
return API.lua_gethook(L);
}
LUA_API int
lua_gethookmask(lua_State *L) {
return API.lua_gethookmask(L);
}
LUA_API int
lua_gethookcount(lua_State *L) {
return API.lua_gethookcount(L);
}
LUA_API void
luaL_checkversion_(lua_State *L, lua_Number ver, size_t sz) {
API.luaL_checkversion_(L,ver,sz);
}
LUA_API int
luaL_getmetafield(lua_State *L, int obj, const char *e) {
return API.luaL_getmetafield(L,obj,e);
}
LUA_API int
luaL_callmeta(lua_State *L, int obj, const char *e) {
return API.luaL_callmeta(L,obj,e);
}
LUA_API const char *
luaL_tolstring(lua_State *L, int idx, size_t *len) {
return API.luaL_tolstring(L,idx,len);
}
LUA_API int
luaL_argerror(lua_State *L, int arg, const char *extramsg) {
return API.luaL_argerror(L,arg,extramsg);
}
LUA_API int
luaL_typeerror(lua_State *L, int arg, const char *tname) {
return API.luaL_typeerror(L,arg,tname);
}
LUA_API const char *
luaL_checklstring(lua_State *L, int arg,
size_t *l) {
return API.luaL_checklstring(L,arg,l);
}
LUA_API const char *
luaL_optlstring(lua_State *L, int arg,
const char *def, size_t *l) {
return API.luaL_optlstring(L,arg,def,l);
}
LUA_API lua_Number
luaL_checknumber(lua_State *L, int arg) {
return API.luaL_checknumber(L,arg);
}
LUA_API lua_Number
luaL_optnumber(lua_State *L, int arg, lua_Number def) {
return API.luaL_optnumber(L,arg,def);
}
LUA_API lua_Integer
luaL_checkinteger(lua_State *L, int arg) {
return API.luaL_checkinteger(L,arg);
}
LUA_API lua_Integer
luaL_optinteger(lua_State *L, int arg,
lua_Integer def) {
return API.luaL_optinteger(L,arg,def);
}
LUA_API void
luaL_checkstack(lua_State *L, int sz, const char *msg) {
API.luaL_checkstack(L,sz,msg);
}
LUA_API void
luaL_checktype(lua_State *L, int arg, int t) {
API.luaL_checktype(L,arg,t);
}
LUA_API void
luaL_checkany(lua_State *L, int arg) {
API.luaL_checkany(L,arg);
}
LUA_API int
luaL_newmetatable(lua_State *L, const char *tname) {
return API.luaL_newmetatable(L,tname);
}
LUA_API void
luaL_setmetatable(lua_State *L, const char *tname) {
API.luaL_setmetatable(L,tname);
}
LUA_API void *
luaL_testudata(lua_State *L, int ud, const char *tname) {
return API.luaL_testudata(L,ud,tname);
}
LUA_API void *
luaL_checkudata(lua_State *L, int ud, const char *tname) {
return API.luaL_checkudata(L,ud,tname);
}
LUA_API void
luaL_where(lua_State *L, int lvl) {
API.luaL_where(L,lvl);
}
LUA_API int
luaL_checkoption(lua_State *L, int arg, const char *def,
const char *const lst[]) {
return API.luaL_checkoption(L,arg,def,lst);
}
LUA_API int
luaL_fileresult(lua_State *L, int stat, const char *fname) {
return API.luaL_fileresult(L,stat,fname);
}
LUA_API int
luaL_execresult(lua_State *L, int stat) {
return API.luaL_execresult(L,stat);
}
LUA_API void *
luaL_alloc(void *ud, void *ptr, size_t osize,
size_t nsize) {
return API.luaL_alloc(ud,ptr,osize,nsize);
}
LUA_API int
luaL_ref(lua_State *L, int t) {
return API.luaL_ref(L,t);
}
LUA_API void
luaL_unref(lua_State *L, int t, int ref) {
API.luaL_unref(L,t,ref);
}
LUA_API int
luaL_loadfilex(lua_State *L, const char *filename,
const char *mode) {
return API.luaL_loadfilex(L,filename,mode);
}
LUA_API int
luaL_loadbufferx(lua_State *L, const char *buff, size_t sz,
const char *name, const char *mode) {
return API.luaL_loadbufferx(L,buff,sz,name,mode);
}
LUA_API int
luaL_loadstring(lua_State *L, const char *s) {
return API.luaL_loadstring(L,s);
}
LUA_API lua_State *
luaL_newstate(void) {
return API.luaL_newstate();
}
LUA_API unsigned
luaL_makeseed(lua_State *L) {
return API.luaL_makeseed(L);
}
LUA_API lua_Integer
luaL_len(lua_State *L, int idx) {
return API.luaL_len(L,idx);
}
LUA_API void
luaL_addgsub(luaL_Buffer *b, const char *s,
const char *p, const char *r) {
API.luaL_addgsub(b,s,p,r);
}
LUA_API const char *
luaL_gsub(lua_State *L, const char *s,
const char *p, const char *r) {
return API.luaL_gsub(L,s,p,r);
}
LUA_API void
luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) {
API.luaL_setfuncs(L,l,nup);
}
LUA_API int
luaL_getsubtable(lua_State *L, int idx, const char *fname) {
return API.luaL_getsubtable(L,idx,fname);
}
LUA_API void
luaL_traceback(lua_State *L, lua_State *L1,
const char *msg, int level) {
API.luaL_traceback(L,L1,msg,level);
}
LUA_API void
luaL_requiref(lua_State *L, const char *modname,
lua_CFunction openf, int glb) {
API.luaL_requiref(L,modname,openf,glb);
}
LUA_API void
luaL_buffinit(lua_State *L, luaL_Buffer *B) {
API.luaL_buffinit(L,B);
}
LUA_API char *
luaL_prepbuffsize(luaL_Buffer *B, size_t sz) {
return API.luaL_prepbuffsize(B,sz);
}
LUA_API void
luaL_addlstring(luaL_Buffer *B, const char *s, size_t l) {
API.luaL_addlstring(B,s,l);
}
LUA_API void
luaL_addstring(luaL_Buffer *B, const char *s) {
API.luaL_addstring(B,s);
}
LUA_API void
luaL_addvalue(luaL_Buffer *B) {
API.luaL_addvalue(B);
}
LUA_API void
luaL_pushresult(luaL_Buffer *B) {
API.luaL_pushresult(B);
}
LUA_API void
luaL_pushresultsize(luaL_Buffer *B, size_t sz) {
API.luaL_pushresultsize(B,sz);
}
LUA_API char *
luaL_buffinitsize(lua_State *L, luaL_Buffer *B, size_t sz) {
return API.luaL_buffinitsize(L,B,sz);
}
LUA_API
const char *lua_pushfstring (lua_State *L, const char *fmt, ...) {
const char *ret;
va_list argp;
va_start(argp, fmt);
ret = API.lua_pushvfstring(L, fmt, argp);
va_end(argp);
return ret;
}
LUA_API int
lua_gc (lua_State *L, int what, ...) {
va_list argp;
va_start(argp, what);
int p1 = va_arg(argp, int);
int p2 = va_arg(argp, int);
int p3 = va_arg(argp, int);
va_end(argp);
return API.lua_gc(L, what, p1, p2, p3);
}
LUA_API int
luaL_error(lua_State *L, const char *fmt, ...) {
va_list argp;
va_start(argp, fmt);
luaL_where(L, 1);
lua_pushvfstring(L, fmt, argp);
va_end(argp);
lua_concat(L, 2);
return lua_error(L);
}
static void
stub_luaL_checkversion_ (lua_State *L, lua_Number ver, size_t sz) {
}
static void stub_lua_createtable (lua_State *L, int narr, int nrec) {
}
static void stub_luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {
}
struct sokol_api;
struct soluna_api;
struct extlua_apis {
struct lua_api * lua;
struct sokol_api * sokol;
struct soluna_api * soluna;
};
LUA_API void
luaapi_init(lua_State *L) {
struct extlua_apis *apis = *(struct extlua_apis **)lua_getextraspace(L);
struct lua_api * api = apis->lua;
if (api->version == LUA_VERSION_NUM) {
API = *api;
return;
}
// stub for luaL_newlib
API.luaL_checkversion_ = stub_luaL_checkversion_;
API.lua_createtable = stub_lua_createtable;
API.luaL_setfuncs = stub_luaL_setfuncs;
}
================================================
FILE: extlua/extlua.temp.c
================================================
#include
#include
#include
#include
#include
struct lua_api {
int version;
$API_DECL$
};
static struct lua_api API;
$API_IMPL$
LUA_API
const char *lua_pushfstring (lua_State *L, const char *fmt, ...) {
const char *ret;
va_list argp;
va_start(argp, fmt);
ret = API.lua_pushvfstring(L, fmt, argp);
va_end(argp);
return ret;
}
LUA_API int
lua_gc (lua_State *L, int what, ...) {
va_list argp;
va_start(argp, what);
int p1 = va_arg(argp, int);
int p2 = va_arg(argp, int);
int p3 = va_arg(argp, int);
va_end(argp);
return API.lua_gc(L, what, p1, p2, p3);
}
LUA_API int
luaL_error(lua_State *L, const char *fmt, ...) {
va_list argp;
va_start(argp, fmt);
luaL_where(L, 1);
lua_pushvfstring(L, fmt, argp);
va_end(argp);
lua_concat(L, 2);
return lua_error(L);
}
static void
stub_luaL_checkversion_ (lua_State *L, lua_Number ver, size_t sz) {
}
static void stub_lua_createtable (lua_State *L, int narr, int nrec) {
}
static void stub_luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {
}
struct sokol_api;
struct soluna_api;
struct extlua_apis {
struct lua_api * lua;
struct sokol_api * sokol;
struct soluna_api * soluna;
};
LUA_API void
luaapi_init(lua_State *L) {
struct extlua_apis *apis = *(struct extlua_apis **)lua_getextraspace(L);
struct lua_api * api = apis->lua;
if (api->version == LUA_VERSION_NUM) {
API = *api;
return;
}
// stub for luaL_newlib
API.luaL_checkversion_ = stub_luaL_checkversion_;
API.lua_createtable = stub_lua_createtable;
API.luaL_setfuncs = stub_luaL_setfuncs;
}
================================================
FILE: extlua/extlua_impl.c
================================================
// AUTO GENERATED by extlua_impl.temp.c, DONT EDIT
#include
#include
struct lua_api {
int version;
lua_State * (*lua_newstate) (lua_Alloc f, void *ud, unsigned seed);
void (*lua_close) (lua_State *L);
lua_State * (*lua_newthread) (lua_State *L);
int (*lua_closethread) (lua_State *L, lua_State *from);
lua_CFunction (*lua_atpanic) (lua_State *L, lua_CFunction panicf);
lua_Number (*lua_version) (lua_State *L);
int (*lua_absindex) (lua_State *L, int idx);
int (*lua_gettop) (lua_State *L);
void (*lua_settop) (lua_State *L, int idx);
void (*lua_pushvalue) (lua_State *L, int idx);
void (*lua_rotate) (lua_State *L, int idx, int n);
void (*lua_copy) (lua_State *L, int fromidx, int toidx);
int (*lua_checkstack) (lua_State *L, int n);
void (*lua_xmove) (lua_State *from, lua_State *to, int n);
int (*lua_isnumber) (lua_State *L, int idx);
int (*lua_isstring) (lua_State *L, int idx);
int (*lua_iscfunction) (lua_State *L, int idx);
int (*lua_isinteger) (lua_State *L, int idx);
int (*lua_isuserdata) (lua_State *L, int idx);
int (*lua_type) (lua_State *L, int idx);
const char * (*lua_typename) (lua_State *L, int tp);
lua_Number (*lua_tonumberx) (lua_State *L, int idx, int *isnum);
lua_Integer (*lua_tointegerx) (lua_State *L, int idx, int *isnum);
int (*lua_toboolean) (lua_State *L, int idx);
const char * (*lua_tolstring) (lua_State *L, int idx, size_t *len);
lua_Unsigned (*lua_rawlen) (lua_State *L, int idx);
lua_CFunction (*lua_tocfunction) (lua_State *L, int idx);
void * (*lua_touserdata) (lua_State *L, int idx);
lua_State * (*lua_tothread) (lua_State *L, int idx);
const void * (*lua_topointer) (lua_State *L, int idx);
void (*lua_arith) (lua_State *L, int op);
int (*lua_rawequal) (lua_State *L, int idx1, int idx2);
int (*lua_compare) (lua_State *L, int idx1, int idx2, int op);
void (*lua_pushnil) (lua_State *L);
void (*lua_pushnumber) (lua_State *L, lua_Number n);
void (*lua_pushinteger) (lua_State *L, lua_Integer n);
const char * (*lua_pushlstring) (lua_State *L, const char *s, size_t len);
const char * (*lua_pushexternalstring) (lua_State *L,
const char *s, size_t len, lua_Alloc falloc, void *ud);
const char * (*lua_pushstring) (lua_State *L, const char *s);
const char * (*lua_pushvfstring) (lua_State *L, const char *fmt,
va_list argp);
void (*lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
void (*lua_pushboolean) (lua_State *L, int b);
void (*lua_pushlightuserdata) (lua_State *L, void *p);
int (*lua_pushthread) (lua_State *L);
int (*lua_getglobal) (lua_State *L, const char *name);
int (*lua_gettable) (lua_State *L, int idx);
int (*lua_getfield) (lua_State *L, int idx, const char *k);
int (*lua_geti) (lua_State *L, int idx, lua_Integer n);
int (*lua_rawget) (lua_State *L, int idx);
int (*lua_rawgeti) (lua_State *L, int idx, lua_Integer n);
int (*lua_rawgetp) (lua_State *L, int idx, const void *p);
void (*lua_createtable) (lua_State *L, int narr, int nrec);
void * (*lua_newuserdatauv) (lua_State *L, size_t sz, int nuvalue);
int (*lua_getmetatable) (lua_State *L, int objindex);
int (*lua_getiuservalue) (lua_State *L, int idx, int n);
void (*lua_setglobal) (lua_State *L, const char *name);
void (*lua_settable) (lua_State *L, int idx);
void (*lua_setfield) (lua_State *L, int idx, const char *k);
void (*lua_seti) (lua_State *L, int idx, lua_Integer n);
void (*lua_rawset) (lua_State *L, int idx);
void (*lua_rawseti) (lua_State *L, int idx, lua_Integer n);
void (*lua_rawsetp) (lua_State *L, int idx, const void *p);
int (*lua_setmetatable) (lua_State *L, int objindex);
int (*lua_setiuservalue) (lua_State *L, int idx, int n);
void (*lua_callk) (lua_State *L, int nargs, int nresults,
lua_KContext ctx, lua_KFunction k);
int (*lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc,
lua_KContext ctx, lua_KFunction k);
int (*lua_load) (lua_State *L, lua_Reader reader, void *dt,
const char *chunkname, const char *mode);
int (*lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip);
int (*lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx,
lua_KFunction k);
int (*lua_resume) (lua_State *L, lua_State *from, int narg,
int *nres);
int (*lua_status) (lua_State *L);
int (*lua_isyieldable) (lua_State *L);
void (*lua_setwarnf) (lua_State *L, lua_WarnFunction f, void *ud);
void (*lua_warning) (lua_State *L, const char *msg, int tocont);
int (*lua_error) (lua_State *L);
int (*lua_next) (lua_State *L, int idx);
void (*lua_concat) (lua_State *L, int n);
void (*lua_len) (lua_State *L, int idx);
unsigned (*lua_numbertocstring) (lua_State *L, int idx, char *buff);
size_t (*lua_stringtonumber) (lua_State *L, const char *s);
lua_Alloc (*lua_getallocf) (lua_State *L, void **ud);
void (*lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
void (*lua_toclose) (lua_State *L, int idx);
void (*lua_closeslot) (lua_State *L, int idx);
int (*lua_getstack) (lua_State *L, int level, lua_Debug *ar);
int (*lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar);
const char * (*lua_getlocal) (lua_State *L, const lua_Debug *ar, int n);
const char * (*lua_setlocal) (lua_State *L, const lua_Debug *ar, int n);
const char * (*lua_getupvalue) (lua_State *L, int funcindex, int n);
const char * (*lua_setupvalue) (lua_State *L, int funcindex, int n);
void * (*lua_upvalueid) (lua_State *L, int fidx, int n);
void (*lua_upvaluejoin) (lua_State *L, int fidx1, int n1,
int fidx2, int n2);
void (*lua_sethook) (lua_State *L, lua_Hook func, int mask, int count);
lua_Hook (*lua_gethook) (lua_State *L);
int (*lua_gethookmask) (lua_State *L);
int (*lua_gethookcount) (lua_State *L);
void (*luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz);
int (*luaL_getmetafield) (lua_State *L, int obj, const char *e);
int (*luaL_callmeta) (lua_State *L, int obj, const char *e);
const char * (*luaL_tolstring) (lua_State *L, int idx, size_t *len);
int (*luaL_argerror) (lua_State *L, int arg, const char *extramsg);
int (*luaL_typeerror) (lua_State *L, int arg, const char *tname);
const char * (*luaL_checklstring) (lua_State *L, int arg,
size_t *l);
const char * (*luaL_optlstring) (lua_State *L, int arg,
const char *def, size_t *l);
lua_Number (*luaL_checknumber) (lua_State *L, int arg);
lua_Number (*luaL_optnumber) (lua_State *L, int arg, lua_Number def);
lua_Integer (*luaL_checkinteger) (lua_State *L, int arg);
lua_Integer (*luaL_optinteger) (lua_State *L, int arg,
lua_Integer def);
void (*luaL_checkstack) (lua_State *L, int sz, const char *msg);
void (*luaL_checktype) (lua_State *L, int arg, int t);
void (*luaL_checkany) (lua_State *L, int arg);
int (*luaL_newmetatable) (lua_State *L, const char *tname);
void (*luaL_setmetatable) (lua_State *L, const char *tname);
void * (*luaL_testudata) (lua_State *L, int ud, const char *tname);
void * (*luaL_checkudata) (lua_State *L, int ud, const char *tname);
void (*luaL_where) (lua_State *L, int lvl);
int (*luaL_checkoption) (lua_State *L, int arg, const char *def,
const char *const lst[]);
int (*luaL_fileresult) (lua_State *L, int stat, const char *fname);
int (*luaL_execresult) (lua_State *L, int stat);
void * (*luaL_alloc) (void *ud, void *ptr, size_t osize,
size_t nsize);
int (*luaL_ref) (lua_State *L, int t);
void (*luaL_unref) (lua_State *L, int t, int ref);
int (*luaL_loadfilex) (lua_State *L, const char *filename,
const char *mode);
int (*luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz,
const char *name, const char *mode);
int (*luaL_loadstring) (lua_State *L, const char *s);
lua_State * (*luaL_newstate) (void);
unsigned (*luaL_makeseed) (lua_State *L);
lua_Integer (*luaL_len) (lua_State *L, int idx);
void (*luaL_addgsub) (luaL_Buffer *b, const char *s,
const char *p, const char *r);
const char * (*luaL_gsub) (lua_State *L, const char *s,
const char *p, const char *r);
void (*luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
int (*luaL_getsubtable) (lua_State *L, int idx, const char *fname);
void (*luaL_traceback) (lua_State *L, lua_State *L1,
const char *msg, int level);
void (*luaL_requiref) (lua_State *L, const char *modname,
lua_CFunction openf, int glb);
void (*luaL_buffinit) (lua_State *L, luaL_Buffer *B);
char * (*luaL_prepbuffsize) (luaL_Buffer *B, size_t sz);
void (*luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);
void (*luaL_addstring) (luaL_Buffer *B, const char *s);
void (*luaL_addvalue) (luaL_Buffer *B);
void (*luaL_pushresult) (luaL_Buffer *B);
void (*luaL_pushresultsize) (luaL_Buffer *B, size_t sz);
char * (*luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz);
int (*lua_gc) (lua_State *L, int what, ...);
};
struct lua_api *
extlua_api() {
static struct lua_api api = {
LUA_VERSION_NUM,
lua_newstate,
lua_close,
lua_newthread,
lua_closethread,
lua_atpanic,
lua_version,
lua_absindex,
lua_gettop,
lua_settop,
lua_pushvalue,
lua_rotate,
lua_copy,
lua_checkstack,
lua_xmove,
lua_isnumber,
lua_isstring,
lua_iscfunction,
lua_isinteger,
lua_isuserdata,
lua_type,
lua_typename,
lua_tonumberx,
lua_tointegerx,
lua_toboolean,
lua_tolstring,
lua_rawlen,
lua_tocfunction,
lua_touserdata,
lua_tothread,
lua_topointer,
lua_arith,
lua_rawequal,
lua_compare,
lua_pushnil,
lua_pushnumber,
lua_pushinteger,
lua_pushlstring,
lua_pushexternalstring,
lua_pushstring,
lua_pushvfstring,
lua_pushcclosure,
lua_pushboolean,
lua_pushlightuserdata,
lua_pushthread,
lua_getglobal,
lua_gettable,
lua_getfield,
lua_geti,
lua_rawget,
lua_rawgeti,
lua_rawgetp,
lua_createtable,
lua_newuserdatauv,
lua_getmetatable,
lua_getiuservalue,
lua_setglobal,
lua_settable,
lua_setfield,
lua_seti,
lua_rawset,
lua_rawseti,
lua_rawsetp,
lua_setmetatable,
lua_setiuservalue,
lua_callk,
lua_pcallk,
lua_load,
lua_dump,
lua_yieldk,
lua_resume,
lua_status,
lua_isyieldable,
lua_setwarnf,
lua_warning,
lua_error,
lua_next,
lua_concat,
lua_len,
lua_numbertocstring,
lua_stringtonumber,
lua_getallocf,
lua_setallocf,
lua_toclose,
lua_closeslot,
lua_getstack,
lua_getinfo,
lua_getlocal,
lua_setlocal,
lua_getupvalue,
lua_setupvalue,
lua_upvalueid,
lua_upvaluejoin,
lua_sethook,
lua_gethook,
lua_gethookmask,
lua_gethookcount,
luaL_checkversion_,
luaL_getmetafield,
luaL_callmeta,
luaL_tolstring,
luaL_argerror,
luaL_typeerror,
luaL_checklstring,
luaL_optlstring,
luaL_checknumber,
luaL_optnumber,
luaL_checkinteger,
luaL_optinteger,
luaL_checkstack,
luaL_checktype,
luaL_checkany,
luaL_newmetatable,
luaL_setmetatable,
luaL_testudata,
luaL_checkudata,
luaL_where,
luaL_checkoption,
luaL_fileresult,
luaL_execresult,
luaL_alloc,
luaL_ref,
luaL_unref,
luaL_loadfilex,
luaL_loadbufferx,
luaL_loadstring,
luaL_newstate,
luaL_makeseed,
luaL_len,
luaL_addgsub,
luaL_gsub,
luaL_setfuncs,
luaL_getsubtable,
luaL_traceback,
luaL_requiref,
luaL_buffinit,
luaL_prepbuffsize,
luaL_addlstring,
luaL_addstring,
luaL_addvalue,
luaL_pushresult,
luaL_pushresultsize,
luaL_buffinitsize,
lua_gc,
};
return &api;
}
================================================
FILE: extlua/extlua_impl.temp.c
================================================
#include
#include
struct lua_api {
int version;
$API_DECL$
};
struct lua_api *
extlua_api() {
static struct lua_api api = {
LUA_VERSION_NUM,
$API_STRUCT$
};
return &api;
}
================================================
FILE: extlua/extlua_sample.c
================================================
#include
#include
#include
#include
#include "sokol/sokol_gfx.h"
#include "perspective_quad.glsl.h"
#include "solunaapi.h"
LUA_API void luaapi_init(lua_State *L);
void sokolapi_init(lua_State *L);
#if defined(_WIN32)
#define EXTLUA_EXPORT __declspec(dllexport)
#else
#define EXTLUA_EXPORT __attribute__((visibility("default")))
#endif
#define PQUAD_CORNER_N 4
#define PQUAD_INFO_CORNER_MASK 0x3u
#define PQUAD_INFO_USE_SPRITE_RECT 0x4u
#define PQUAD_EPSILON 0.000001f
struct color {
unsigned char channel[4];
};
struct pquad_payload {
uint32_t info;
float q;
struct color color;
};
struct pquad_inst {
float pos_h0[3];
float pos_h1[3];
float pos_h2[3];
float uv_rect[4];
float q[4];
struct color color;
};
struct material_perspective_quad {
sg_pipeline pip;
sg_buffer inst;
struct soluna_render_bindings bind;
int base;
vs_params_t *uniform;
struct soluna_sprite_bank bank;
void *tmp_ptr;
size_t tmp_size;
};
struct sprite_rect_basis {
float scale_x;
float scale_y;
float shear_x;
float shear_y;
float tx;
float ty;
};
static int material_id = 0;
static void *
free_material_stream(void *ud, void *ptr, size_t osize, size_t nsize) {
(void)ud;
(void)osize;
if (nsize == 0) {
soluna_material_stream_free(ptr);
}
return NULL;
}
static sg_pipeline
make_pipeline(sg_pipeline_desc *desc) {
sg_shader shd = sg_make_shader(perspective_quad_shader_desc(sg_query_backend()));
desc->shader = shd;
desc->primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP;
desc->label = "extlua-perspective-quad-pipeline";
desc->layout.buffers[0].step_func = SG_VERTEXSTEP_PER_INSTANCE;
desc->colors[0].blend = (sg_blend_state) {
.enabled = true,
.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.src_factor_alpha = SG_BLENDFACTOR_ONE,
.dst_factor_alpha = SG_BLENDFACTOR_ZERO,
};
return sg_make_pipeline(desc);
}
static void
set_position_homography(const float pos[PQUAD_CORNER_N][2], struct pquad_inst *inst) {
const float x0 = pos[0][0], y0 = pos[0][1];
const float x1 = pos[1][0], y1 = pos[1][1];
const float x2 = pos[2][0], y2 = pos[2][1];
const float x3 = pos[3][0], y3 = pos[3][1];
const float sx = x0 - x1 + x3 - x2;
const float sy = y0 - y1 + y3 - y2;
const float dx1 = x1 - x3;
const float dx2 = x2 - x3;
const float dy1 = y1 - y3;
const float dy2 = y2 - y3;
const float det = dx1 * dy2 - dx2 * dy1;
float m31 = 0.0f;
float m32 = 0.0f;
float abs_det = det < 0.0f ? -det : det;
if (abs_det > PQUAD_EPSILON) {
m31 = (sx * dy2 - sy * dx2) / det;
m32 = (sy * dx1 - sx * dy1) / det;
}
const float m11 = x1 - x0 + m31 * x1;
const float m12 = x2 - x0 + m32 * x2;
const float m13 = x0;
const float m21 = y1 - y0 + m31 * y1;
const float m22 = y2 - y0 + m32 * y2;
const float m23 = y0;
inst->pos_h0[0] = m11;
inst->pos_h0[1] = m21;
inst->pos_h0[2] = m31;
inst->pos_h1[0] = m12;
inst->pos_h1[1] = m22;
inst->pos_h1[2] = m32;
inst->pos_h2[0] = m13;
inst->pos_h2[1] = m23;
inst->pos_h2[2] = 1.0f;
}
static inline soluna_material_error
perspective_quad_count(int prim_n, int *out) {
if (prim_n % PQUAD_CORNER_N != 0) {
return "Invalid perspective quad primitive count";
}
*out = prim_n / PQUAD_CORNER_N;
return NULL;
}
static inline void
decode_sprite_rect_basis(struct sprite_rect_basis *basis, uint32_t corner, const struct soluna_material_stream_data *item) {
float x = item->x;
float y = item->y;
switch (corner) {
case 0:
basis->scale_x = x;
basis->scale_y = y;
break;
case 1:
basis->shear_x = x;
basis->shear_y = y;
break;
case 2:
basis->tx = x;
basis->ty = y;
break;
}
}
static inline void
build_quad_from_rect(const struct sprite_rect_basis *basis, const struct soluna_sprite_rect *rect, float pos[PQUAD_CORNER_N][2]) {
float scale_x = basis->scale_x - basis->tx;
float scale_y = basis->scale_y - basis->ty;
float shear_x = basis->shear_x - basis->tx;
float shear_y = basis->shear_y - basis->ty;
int corner;
for (corner=0; cornerw - rect->ox) : -rect->ox;
float y = (corner >> 1) ? (rect->h - rect->oy) : -rect->oy;
pos[corner][0] = x * scale_x + y * shear_x + basis->tx;
pos[corner][1] = x * shear_y + y * scale_y + basis->ty;
}
}
static void
submit(void *m_, struct soluna_material_stream_context ctx, int n) {
struct material_perspective_quad *m = (struct material_perspective_quad *)m_;
struct pquad_inst *tmp = (struct pquad_inst *)m->tmp_ptr;
int out_n;
soluna_material_error err = perspective_quad_count(n, &out_n);
if (err != NULL) {
soluna_material_stream_error(ctx, err);
return;
}
int i;
for (i=0; icolor = payload.color;
} else if (item.sprite != sprite || flags != stream_flags) {
soluna_material_stream_error(ctx, "Invalid perspective quad stream");
return;
}
uint32_t corner = payload.info & PQUAD_INFO_CORNER_MASK;
if (corner >= PQUAD_CORNER_N) {
soluna_material_stream_error(ctx, "Invalid perspective quad corner");
return;
}
if (stream_flags & PQUAD_INFO_USE_SPRITE_RECT) {
decode_sprite_rect_basis(&sprite_rect_basis, corner, &item);
} else {
pos[corner][0] = item.x;
pos[corner][1] = item.y;
}
float q = payload.q;
if (q <= PQUAD_EPSILON) {
q = PQUAD_EPSILON;
}
inst->q[corner] = q;
}
struct soluna_sprite_rect sprite_rect;
if (!soluna_material_sprite_rect(m->bank, sprite, &sprite_rect)) {
soluna_material_stream_error(ctx, "Invalid perspective quad sprite");
return;
}
if (stream_flags & PQUAD_INFO_USE_SPRITE_RECT) {
build_quad_from_rect(&sprite_rect_basis, &sprite_rect, pos);
}
set_position_homography(pos, inst);
inst->uv_rect[0] = sprite_rect.u;
inst->uv_rect[1] = sprite_rect.v;
inst->uv_rect[2] = sprite_rect.w;
inst->uv_rect[3] = sprite_rect.h;
}
sg_append_buffer(m->inst, &(sg_range) { tmp, out_n * sizeof(tmp[0]) });
}
static int
lmaterial_perspective_quad_submit(lua_State *L) {
struct material_perspective_quad *m = (struct material_perspective_quad *)luaL_checkudata(L, 1, "EXTLUA_MATERIAL_PERSPECTIVE_QUAD");
int inst_batch_n = (int)(m->tmp_size / sizeof(struct pquad_inst));
if (inst_batch_n < 1) {
return luaL_error(L, "Perspective quad tmp buffer is too small");
}
const void *stream = lua_touserdata(L, 2);
int prim_n = luaL_checkinteger(L, 3);
soluna_material_error err = soluna_material_submit(stream, prim_n, material_id, inst_batch_n * PQUAD_CORNER_N, m, submit);
if (err != NULL) {
return luaL_error(L, "%s", err);
}
return 0;
}
static int
lmaterial_perspective_quad_draw(lua_State *L) {
struct material_perspective_quad *m = (struct material_perspective_quad *)luaL_checkudata(L, 1, "EXTLUA_MATERIAL_PERSPECTIVE_QUAD");
int prim_n = luaL_checkinteger(L, 3);
if (prim_n <= 0) {
return 0;
}
int quad_n;
soluna_material_error err = perspective_quad_count(prim_n, &quad_n);
if (err != NULL) {
return luaL_error(L, "%s", err);
}
sg_apply_pipeline(m->pip);
sg_apply_uniforms(UB_vs_params, &(sg_range) { m->uniform, sizeof(vs_params_t) });
sg_bindings bindings = soluna_material_bindings(m->bind);
bindings.vertex_buffer_offsets[0] += (size_t)m->base * sizeof(struct pquad_inst);
sg_apply_bindings(&bindings);
sg_draw(0, 4, quad_n);
m->base += quad_n;
return 0;
}
static int
lmaterial_perspective_quad_reset(lua_State *L) {
struct material_perspective_quad *m = (struct material_perspective_quad *)luaL_checkudata(L, 1, "EXTLUA_MATERIAL_PERSPECTIVE_QUAD");
m->base = 0;
return 0;
}
static int
lset_material_id(lua_State *L) {
int id = luaL_checkinteger(L, 1);
if (id <= 0) {
return luaL_error(L, "Invalid perspective quad material id %d", id);
}
material_id = id;
return 0;
}
static void
init_pipeline(struct material_perspective_quad *p) {
sg_pipeline_desc desc = {
.layout.attrs = {
[ATTR_perspective_quad_pos_h0].format = SG_VERTEXFORMAT_FLOAT3,
[ATTR_perspective_quad_pos_h1].format = SG_VERTEXFORMAT_FLOAT3,
[ATTR_perspective_quad_pos_h2].format = SG_VERTEXFORMAT_FLOAT3,
[ATTR_perspective_quad_uv_rect].format = SG_VERTEXFORMAT_FLOAT4,
[ATTR_perspective_quad_q].format = SG_VERTEXFORMAT_FLOAT4,
[ATTR_perspective_quad_color].format = SG_VERTEXFORMAT_UBYTE4N,
},
};
p->pip = make_pipeline(&desc);
}
static int
lnew_material_perspective_quad(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
struct material_perspective_quad *m = (struct material_perspective_quad *)lua_newuserdatauv(L, sizeof(*m), 4);
int material_index = lua_gettop(L);
init_pipeline(m);
m->base = 0;
if (lua_getfield(L, 1, "inst_buffer") != LUA_TUSERDATA) {
return luaL_error(L, "Invalid key .inst_buffer");
}
luaL_checkudata(L, -1, "SOKOL_BUFFER");
lua_pushvalue(L, -1);
lua_setiuservalue(L, material_index, 1);
lua_pushlightuserdata(L, &m->inst);
lua_call(L, 1, 0);
if (lua_getfield(L, 1, "bindings") != LUA_TUSERDATA) {
return luaL_error(L, "Invalid key .bindings");
}
m->bind = (struct soluna_render_bindings) {
.ctx = luaL_checkudata(L, -1, "SOKOL_BINDINGS"),
};
lua_pushvalue(L, -1);
lua_setiuservalue(L, material_index, 2);
lua_pop(L, 1);
if (lua_getfield(L, 1, "uniform") != LUA_TUSERDATA) {
return luaL_error(L, "Invalid key .uniform");
}
m->uniform = (vs_params_t *)luaL_checkudata(L, -1, "SOKOL_UNIFORM");
lua_pushvalue(L, -1);
lua_setiuservalue(L, material_index, 3);
lua_pop(L, 1);
if (lua_getfield(L, 1, "sprite_bank") != LUA_TLIGHTUSERDATA) {
return luaL_error(L, "Invalid key .sprite_bank");
}
m->bank = (struct soluna_sprite_bank) {
.ctx = lua_touserdata(L, -1),
};
lua_pop(L, 1);
if (lua_getfield(L, 1, "tmp_buffer") != LUA_TUSERDATA) {
return luaL_error(L, "Invalid key .tmp_buffer");
}
if (lua_getmetatable(L, -1)) {
return luaL_error(L, "Not an userdata without metatable");
}
m->tmp_ptr = lua_touserdata(L, -1);
m->tmp_size = lua_rawlen(L, -1);
lua_setiuservalue(L, material_index, 4);
if (luaL_newmetatable(L, "EXTLUA_MATERIAL_PERSPECTIVE_QUAD")) {
luaL_Reg l[] = {
{ "__index", NULL },
{ "reset", lmaterial_perspective_quad_reset },
{ "submit", lmaterial_perspective_quad_submit },
{ "draw", lmaterial_perspective_quad_draw },
{ NULL, NULL },
};
luaL_setfuncs(L, l, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
}
lua_setmetatable(L, -2);
return 1;
}
static inline float
get_number_field(lua_State *L, int index, const char *field, float defv) {
float v;
lua_getfield(L, index, field);
v = luaL_optnumber(L, -1, defv);
lua_pop(L, 1);
return v;
}
static int
get_quad(lua_State *L, int index, float quad[8]) {
if (lua_getfield(L, index, "quad") == LUA_TTABLE) {
int i;
for (i=0; i<8; i++) {
lua_geti(L, -1, i + 1);
quad[i] = luaL_checknumber(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
return 0;
}
lua_pop(L, 1);
return 1;
}
static void
get_q(lua_State *L, int index, float q[4]) {
int i;
if (lua_getfield(L, index, "q") != LUA_TTABLE) {
for (i=0; i<4; i++) {
q[i] = 1.0f;
}
lua_pop(L, 1);
return;
}
for (i=0; i<4; i++) {
lua_geti(L, -1, i + 1);
q[i] = luaL_optnumber(L, -1, 1.0f);
lua_pop(L, 1);
}
lua_pop(L, 1);
}
static struct color
get_color(lua_State *L, int index) {
uint32_t color;
struct color c;
lua_getfield(L, index, "color");
color = (uint32_t)luaL_optinteger(L, -1, 0xffffffff);
if (!(color & 0xff000000)) {
color |= 0xff000000;
}
c.channel[0] = (color >> 16) & 0xff;
c.channel[1] = (color >> 8) & 0xff;
c.channel[2] = color & 0xff;
c.channel[3] = (color >> 24) & 0xff;
lua_pop(L, 1);
return c;
}
struct pquad_stream_context {
int sprite;
int use_sprite_rect;
float scale_x;
float scale_y;
float shear_x;
float shear_y;
float quad[8];
float q[4];
struct color color;
struct pquad_payload payload[PQUAD_CORNER_N];
};
static void
write_perspective_quad_stream(void *ud, int index, struct soluna_material_stream_item *item) {
struct pquad_stream_context *ctx = (struct pquad_stream_context *)ud;
item->sprite = ctx->sprite;
if (ctx->use_sprite_rect) {
switch (index) {
case 0:
item->x = ctx->scale_x;
item->y = ctx->scale_y;
break;
case 1:
item->x = ctx->shear_x;
item->y = ctx->shear_y;
break;
default:
item->x = 0.0f;
item->y = 0.0f;
break;
}
} else {
float x = ctx->quad[index * 2];
float y = ctx->quad[index * 2 + 1];
item->x = x * ctx->scale_x + y * ctx->shear_x;
item->y = x * ctx->shear_y + y * ctx->scale_y;
}
struct pquad_payload *payload = &ctx->payload[index];
uint32_t info = (uint32_t)index;
if (ctx->use_sprite_rect) {
info |= PQUAD_INFO_USE_SPRITE_RECT;
}
payload->info = info;
payload->q = ctx->q[index];
payload->color = ctx->color;
item->payload = payload;
}
static int
lperspective_quad_sprite(lua_State *L) {
if (material_id <= 0) {
return luaL_error(L, "Perspective quad material is not registered");
}
luaL_checktype(L, 2, LUA_TTABLE);
struct pquad_stream_context ctx;
ctx.sprite = luaL_checkinteger(L, 1) - 1;
ctx.use_sprite_rect = get_quad(L, 2, ctx.quad);
ctx.scale_x = get_number_field(L, 2, "scale_x", 1.0f);
ctx.scale_y = get_number_field(L, 2, "scale_y", 1.0f);
ctx.shear_x = get_number_field(L, 2, "shear_x", 0.0f);
ctx.shear_y = get_number_field(L, 2, "shear_y", 0.0f);
get_q(L, 2, ctx.q);
ctx.color = get_color(L, 2);
struct soluna_material_stream stream;
soluna_material_error err = soluna_material_push_stream(material_id, PQUAD_CORNER_N, sizeof(struct pquad_payload), write_perspective_quad_stream, &ctx, &stream);
if (err != NULL) {
return luaL_error(L, "%s", err);
}
lua_pushexternalstring(L, stream.data, stream.size, free_material_stream, NULL);
return 1;
}
static int
luaopen_ext_material_perspective_quad(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "set_material_id", lset_material_id },
{ "new", lnew_material_perspective_quad },
{ "sprite", lperspective_quad_sprite },
{ "instance_size", NULL },
{ NULL, NULL },
};
luaL_newlib(L, l);
lua_pushinteger(L, sizeof(struct pquad_inst));
lua_setfield(L, -2, "instance_size");
return 1;
}
static int
lhello(lua_State *L) {
lua_pushstring(L, "Hello World From Sample");
return 1;
}
static int
luaopen_foobar(lua_State *L) {
luaL_Reg l[] = {
{ "hello", lhello },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
EXTLUA_EXPORT int
extlua_init(lua_State *L) {
luaapi_init(L);
sokolapi_init(L);
solunaapi_init(L);
luaL_Reg l[] = {
{ "ext.foobar", luaopen_foobar },
{ "ext.material.perspective_quad", luaopen_ext_material_perspective_quad },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: extlua/gen.lua
================================================
-- extlua.h[/.c] generator
local luapath = "../3rd/lua/"
local function parse_params(params)
local p = {}
if params == "(void)" then
return p
end
local pn = 1
for t, n in params:gmatch ("(.-)([%w_.%[%]]+)[,)]", 2) do
local is_array
if n:sub(-2) == "[]" then
is_array = true
n = n:sub(1, -3)
end
p[pn] = { type = t, name = n, is_array = is_array }
pn = pn + 1
end
return p
end
local function get_apis(result, filename, prefix)
local f = assert(io.open(luapath .. filename))
local src = f:read "*a"
f:close()
local n = #result + 1
for line in src:gmatch( prefix .. "%s*(.-;)%s*\n" ) do
if not line:find("...", 1, true) then
local retv, funcname, params = line:match "([%s%w_*]+)%(([%s%w_]+)%)%s*(%b());$"
if retv == nil then
if line:match "%S" then
error("Invalid function " .. line)
end
end
retv = retv:gsub("%s+$", "")
if retv:match "%s*void%s*$" then
retv = "void"
end
result[n] = {
ret = retv,
name = funcname,
params = parse_params(params),
}
n = n + 1
end
end
end
local function decls(params)
if #params == 0 then
return "void"
end
local r = {}
for idx, p in ipairs(params) do
local d = p.type .. p.name
if p.is_array then
d = d .. "[]"
end
r[idx] = d
end
return table.concat(r, ",")
end
local function gen_decl(apis)
local d = {}
for idx, item in ipairs(apis) do
d[idx] = ("\t%s (*%s) (%s);"):format(item.ret, item.name, decls(item.params))
end
d[#d+1] = "\tint (*lua_gc) (lua_State *L, int what, ...);"
return table.concat(d, "\n")
end
local function gen_struct(apis)
local d = {}
for idx, item in ipairs(apis) do
d[idx] = ("\t\t%s,"):format(item.name)
end
d[#d+1] = "\t\tlua_gc,"
return table.concat(d, "\n")
end
local function args(params)
local args = {}
for idx, p in ipairs(params) do
args[idx] = p.name
end
return table.concat(args, ",")
end
local impl_fmt = [[
LUA_API %s
%s(%s) {
%sAPI.%s(%s);
}
]]
local function gen_impl(apis)
local d = {}
local n = 1
for idx, item in ipairs(apis) do
if item.name ~= "lua_gc" then
local ret
if item.ret == "void" then
ret = ""
else
ret = "return "
end
local impl = impl_fmt:format(item.ret, item.name, decls(item.params),
ret, item.name, args(item.params))
d[n] = impl; n = n + 1
end
end
return table.concat(d)
end
local function readfile(filename)
local f = assert(io.open(filename))
local content = f:read "a"
f:close()
return content
end
local function genfile(filename, temp)
local t = readfile(filename)
local output_filename = filename:gsub("%.temp", "")
local output_f = assert(io.open(output_filename, "w"))
output_f:write("// AUTO GENERATED by " .. filename .. ", DONT EDIT\n\n")
output_f:write((t:gsub("%$([%w_]+)%$", temp)))
output_f:close()
end
local apis = {}
get_apis(apis, "lua.h", "LUA_API")
get_apis(apis, "lauxlib.h", "LUALIB_API")
local convert = {
API_DECL = gen_decl(apis),
API_STRUCT = gen_struct(apis),
API_IMPL = gen_impl(apis),
}
genfile("extlua.temp.c", convert)
genfile("extlua_impl.temp.c", convert)
================================================
FILE: extlua/gen_sokol.lua
================================================
-- sokolapi.c generator
local header = "../3rd/sokol/sokol_gfx.h"
local allowlist = {
sg_make_buffer = true,
sg_destroy_buffer = true,
sg_update_buffer = true,
sg_append_buffer = true,
sg_make_shader = true,
sg_destroy_shader = true,
sg_make_pipeline = true,
sg_destroy_pipeline = true,
sg_apply_pipeline = true,
sg_apply_bindings = true,
sg_apply_uniforms = true,
sg_draw = true,
sg_draw_ex = true,
sg_query_backend = true,
sg_query_buffer_state = true,
sg_query_shader_state = true,
sg_query_pipeline_state = true,
}
local function parse_params(params)
local p = {}
if params == "(void)" then
return p
end
local body = params:sub(2, -2)
local n = 1
for decl in body:gmatch "[^,]+" do
local t, name = decl:match "^(.-)([%w_]+)%s*$"
if not t then
error("Invalid param " .. decl)
end
p[n] = { type = t, name = name }
n = n + 1
end
return p
end
local function readfile(filename)
local f = assert(io.open(filename))
local content = f:read "a"
f:close()
return content
end
local function get_apis()
local src = readfile(header)
local apis = {}
for line in src:gmatch "SOKOL_GFX_API_DECL%s*(.-;)%s*\n" do
local retv, name, params = line:match "([%s%w_*]+)%s+(sg_[%w_]+)%s*(%b());$"
if name and allowlist[name] then
apis[#apis + 1] = {
ret = retv:gsub("%s+$", ""),
name = name,
params = parse_params(params),
}
end
end
return apis
end
local function decls(params)
if #params == 0 then
return "void"
end
local r = {}
for i, p in ipairs(params) do
r[i] = p.type .. p.name
end
return table.concat(r, ", ")
end
local function args(params)
local r = {}
for i, p in ipairs(params) do
r[i] = p.name
end
return table.concat(r, ", ")
end
local function gen_decl(apis)
local r = {}
for i, api in ipairs(apis) do
r[i] = ("\t%s (*%s) (%s);"):format(api.ret, api.name, decls(api.params))
end
return table.concat(r, "\n")
end
local function gen_struct(apis)
local r = {}
for i, api in ipairs(apis) do
r[i] = ("\t\t%s,"):format(api.name)
end
return table.concat(r, "\n")
end
local function gen_impl(apis)
local r = {}
for i, api in ipairs(apis) do
local ret = api.ret == "void" and "" or "return "
r[i] = ("SOKOL_GFX_API_DECL %s\n%s(%s) {\n\t%sAPI.%s(%s);\n}\n\n"):format(
api.ret,
api.name,
decls(api.params),
ret,
api.name,
args(api.params)
)
end
return table.concat(r)
end
local function genfile(filename, temp)
local t = readfile(filename)
local output = filename:gsub("%.temp", "")
local f = assert(io.open(output, "w"))
f:write("// AUTO GENERATED by " .. filename .. ", DONT EDIT\n\n")
f:write((t:gsub("%$([%w_]+)%$", temp)))
f:close()
end
local apis = get_apis()
local convert = {
API_DECL = gen_decl(apis),
API_STRUCT = gen_struct(apis),
API_IMPL = gen_impl(apis),
}
genfile("sokolapi.temp.c", convert)
genfile("sokolapi_impl.temp.c", convert)
================================================
FILE: extlua/gen_soluna.lua
================================================
-- solunaapi.c generator
local apis = {
{
ret = "soluna_material_error",
name = "soluna_material_submit",
params = {
{ type = "const void *", name = "stream" },
{ type = "int ", name = "prim_n" },
{ type = "int ", name = "material_id" },
{ type = "int ", name = "batch_n" },
{ type = "void *", name = "ud" },
{ type = "soluna_material_submit_func ", name = "submit" },
},
},
{
ret = "int",
name = "soluna_material_sprite_rect",
params = {
{ type = "struct soluna_sprite_bank ", name = "bank" },
{ type = "int ", name = "sprite" },
{ type = "struct soluna_sprite_rect *", name = "out" },
},
},
{
ret = "sg_bindings",
name = "soluna_material_bindings",
params = {
{ type = "struct soluna_render_bindings ", name = "bindings" },
},
},
{
ret = "soluna_material_error",
name = "soluna_material_push_stream",
params = {
{ type = "int ", name = "material_id" },
{ type = "int ", name = "count" },
{ type = "size_t ", name = "payload_size" },
{ type = "soluna_material_stream_write_func ", name = "write" },
{ type = "void *", name = "ud" },
{ type = "struct soluna_material_stream *", name = "out" },
},
},
{
ret = "void",
name = "soluna_material_stream_free",
params = {
{ type = "void *", name = "ptr" },
},
},
{
ret = "int",
name = "soluna_material_stream_read",
params = {
{ type = "struct soluna_material_stream_context ", name = "ctx" },
{ type = "int ", name = "index" },
{ type = "size_t ", name = "payload_size" },
{ type = "void *", name = "payload" },
{ type = "struct soluna_material_stream_data *", name = "out" },
},
},
{
ret = "void",
name = "soluna_material_stream_error",
params = {
{ type = "struct soluna_material_stream_context ", name = "ctx" },
{ type = "const char *", name = "error" },
},
},
{
ret = "int",
name = "soluna_material_stream_failed",
params = {
{ type = "struct soluna_material_stream_context ", name = "ctx" },
},
},
}
local type_decl = [[
#define SOLUNA_EXT_API_VERSION 1
struct soluna_sprite_rect {
int texture;
float u;
float v;
float w;
float h;
float ox;
float oy;
};
struct soluna_material_stream_item {
float x;
float y;
int sprite;
const void *payload;
};
struct soluna_material_stream_data {
float x;
float y;
int sprite;
};
struct soluna_material_stream {
char *data;
size_t size;
};
typedef const char *soluna_material_error;
struct soluna_material_stream_context {
void *ctx;
};
struct soluna_render_bindings {
void *ctx;
};
struct soluna_sprite_bank {
void *ctx;
};
typedef void (*soluna_material_submit_func)(void *ud, struct soluna_material_stream_context ctx, int n);
typedef void (*soluna_material_stream_write_func)(void *ud, int index, struct soluna_material_stream_item *item);
]]
local host_type_decl = [[
#include
#include "sokol/sokol_gfx.h"
]] .. type_decl
local function readfile(filename)
local f = assert(io.open(filename))
local content = f:read "a"
f:close()
return content
end
local function genfile(filename, temp)
local t = readfile(filename)
local output = filename:gsub("%.temp", "")
local f = assert(io.open(output, "w"))
f:write("// AUTO GENERATED by " .. filename .. ", DONT EDIT\n\n")
f:write((t:gsub("%$([%w_]+)%$", temp)))
f:close()
end
local function decls(params)
if #params == 0 then
return "void"
end
local r = {}
for i, p in ipairs(params) do
r[i] = p.type .. p.name
end
return table.concat(r, ", ")
end
local function args(params)
local r = {}
for i, p in ipairs(params) do
r[i] = p.name
end
return table.concat(r, ", ")
end
local function field_name(api)
return api.field or api.name:gsub("^soluna_", "")
end
local function impl_name(api)
return api.impl or field_name(api)
end
local function gen_header_decl()
local r = { "void solunaapi_init(lua_State *L);" }
for _, api in ipairs(apis) do
r[#r + 1] = ("%s %s(%s);"):format(api.ret, api.name, decls(api.params))
end
return table.concat(r, "\n")
end
local function gen_api_decl()
local r = {}
for i, api in ipairs(apis) do
r[i] = ("\t%s (*%s) (%s);"):format(api.ret, field_name(api), decls(api.params))
end
return table.concat(r, "\n")
end
local function gen_api_struct()
local r = {}
for i, api in ipairs(apis) do
r[i] = ("\t\t%s,"):format(impl_name(api))
end
return table.concat(r, "\n")
end
local function gen_api_extern()
local r = {}
for i, api in ipairs(apis) do
r[i] = ("extern %s %s(%s);"):format(api.ret, impl_name(api), decls(api.params))
end
return table.concat(r, "\n")
end
local function gen_api_impl()
local r = {}
for i, api in ipairs(apis) do
local ret = api.ret == "void" and "" or "return "
r[i] = ("%s\n%s(%s) {\n\t%sAPI.%s(%s);\n}\n\n"):format(
api.ret,
api.name,
decls(api.params),
ret,
field_name(api),
args(api.params)
)
end
return table.concat(r)
end
local convert = {
TYPE_DECL = type_decl,
HEADER_DECL = gen_header_decl(),
API_DECL = gen_api_decl(),
API_STRUCT = gen_api_struct(),
API_EXTERN = gen_api_extern(),
API_IMPL = gen_api_impl(),
HOST_TYPE_DECL = host_type_decl,
}
genfile("solunaapi.h.temp", convert)
genfile("solunaapi.temp.c", convert)
genfile("solunaapi_impl.temp.c", convert)
genfile("../src/extapi_types.temp.h", convert)
================================================
FILE: extlua/perspective_quad.glsl
================================================
@vs vs
layout(binding=0) uniform vs_params {
vec2 framesize;
float texsize;
};
in vec3 pos_h0;
in vec3 pos_h1;
in vec3 pos_h2;
in vec4 uv_rect;
in vec4 q;
in vec4 color;
out vec3 uvq;
out vec4 frag_color;
out flat float tex_scale;
void main() {
vec2 corner = vec2(float(gl_VertexIndex & 1), float(gl_VertexIndex >> 1));
mat3 pos_h = mat3(pos_h0, pos_h1, pos_h2);
vec3 pos_hv = pos_h * vec3(corner, 1.0);
float pos_w = max(pos_hv.z, 1e-6);
vec2 pos = pos_hv.xy / pos_w;
vec2 uv = uv_rect.xy + uv_rect.zw * corner;
float qx0 = mix(q.x, q.y, corner.x);
float qx1 = mix(q.z, q.w, corner.x);
float qv = max(mix(qx0, qx1, corner.y), 1e-6);
uvq = vec3(uv * qv, qv);
vec2 clip = pos * framesize;
gl_Position = vec4(clip.x - 1.0, clip.y + 1.0, 0.0, 1.0);
frag_color = color;
tex_scale = texsize;
}
@end
@fs fs
layout(binding=1) uniform texture2D tex;
layout(binding=0) uniform sampler smp;
in vec3 uvq;
in vec4 frag_color;
in flat float tex_scale;
out vec4 out_color;
void main() {
vec3 proj_uv = vec3(uvq.xy * tex_scale, uvq.z);
out_color = textureProj(sampler2D(tex, smp), proj_uv) * frag_color;
}
@end
@program perspective_quad vs fs
================================================
FILE: extlua/sokolapi.c
================================================
// AUTO GENERATED by sokolapi.temp.c, DONT EDIT
#include
#include "sokol/sokol_gfx.h"
struct sokol_api {
int version;
sg_buffer (*sg_make_buffer) (const sg_buffer_desc* desc);
sg_shader (*sg_make_shader) (const sg_shader_desc* desc);
sg_pipeline (*sg_make_pipeline) (const sg_pipeline_desc* desc);
void (*sg_destroy_buffer) (sg_buffer buf);
void (*sg_destroy_shader) (sg_shader shd);
void (*sg_destroy_pipeline) (sg_pipeline pip);
void (*sg_update_buffer) (sg_buffer buf, const sg_range* data);
int (*sg_append_buffer) (sg_buffer buf, const sg_range* data);
void (*sg_apply_pipeline) (sg_pipeline pip);
void (*sg_apply_bindings) (const sg_bindings* bindings);
void (*sg_apply_uniforms) (int ub_slot, const sg_range* data);
void (*sg_draw) (int base_element, int num_elements, int num_instances);
void (*sg_draw_ex) (int base_element, int num_elements, int num_instances, int base_vertex, int base_instance);
sg_backend (*sg_query_backend) (void);
sg_resource_state (*sg_query_buffer_state) (sg_buffer buf);
sg_resource_state (*sg_query_shader_state) (sg_shader shd);
sg_resource_state (*sg_query_pipeline_state) (sg_pipeline pip);
};
static struct sokol_api API;
SOKOL_GFX_API_DECL sg_buffer
sg_make_buffer(const sg_buffer_desc* desc) {
return API.sg_make_buffer(desc);
}
SOKOL_GFX_API_DECL sg_shader
sg_make_shader(const sg_shader_desc* desc) {
return API.sg_make_shader(desc);
}
SOKOL_GFX_API_DECL sg_pipeline
sg_make_pipeline(const sg_pipeline_desc* desc) {
return API.sg_make_pipeline(desc);
}
SOKOL_GFX_API_DECL void
sg_destroy_buffer(sg_buffer buf) {
API.sg_destroy_buffer(buf);
}
SOKOL_GFX_API_DECL void
sg_destroy_shader(sg_shader shd) {
API.sg_destroy_shader(shd);
}
SOKOL_GFX_API_DECL void
sg_destroy_pipeline(sg_pipeline pip) {
API.sg_destroy_pipeline(pip);
}
SOKOL_GFX_API_DECL void
sg_update_buffer(sg_buffer buf, const sg_range* data) {
API.sg_update_buffer(buf, data);
}
SOKOL_GFX_API_DECL int
sg_append_buffer(sg_buffer buf, const sg_range* data) {
return API.sg_append_buffer(buf, data);
}
SOKOL_GFX_API_DECL void
sg_apply_pipeline(sg_pipeline pip) {
API.sg_apply_pipeline(pip);
}
SOKOL_GFX_API_DECL void
sg_apply_bindings(const sg_bindings* bindings) {
API.sg_apply_bindings(bindings);
}
SOKOL_GFX_API_DECL void
sg_apply_uniforms(int ub_slot, const sg_range* data) {
API.sg_apply_uniforms(ub_slot, data);
}
SOKOL_GFX_API_DECL void
sg_draw(int base_element, int num_elements, int num_instances) {
API.sg_draw(base_element, num_elements, num_instances);
}
SOKOL_GFX_API_DECL void
sg_draw_ex(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) {
API.sg_draw_ex(base_element, num_elements, num_instances, base_vertex, base_instance);
}
SOKOL_GFX_API_DECL sg_backend
sg_query_backend(void) {
return API.sg_query_backend();
}
SOKOL_GFX_API_DECL sg_resource_state
sg_query_buffer_state(sg_buffer buf) {
return API.sg_query_buffer_state(buf);
}
SOKOL_GFX_API_DECL sg_resource_state
sg_query_shader_state(sg_shader shd) {
return API.sg_query_shader_state(shd);
}
SOKOL_GFX_API_DECL sg_resource_state
sg_query_pipeline_state(sg_pipeline pip) {
return API.sg_query_pipeline_state(pip);
}
struct lua_api;
struct soluna_api;
struct extlua_apis {
struct lua_api * lua;
struct sokol_api * sokol;
struct soluna_api * soluna;
};
void
sokolapi_init(lua_State *L) {
struct extlua_apis *apis = *(struct extlua_apis **)lua_getextraspace(L);
API = *apis->sokol;
}
================================================
FILE: extlua/sokolapi.temp.c
================================================
#include
#include "sokol/sokol_gfx.h"
struct sokol_api {
int version;
$API_DECL$
};
static struct sokol_api API;
$API_IMPL$
struct lua_api;
struct soluna_api;
struct extlua_apis {
struct lua_api * lua;
struct sokol_api * sokol;
struct soluna_api * soluna;
};
void
sokolapi_init(lua_State *L) {
struct extlua_apis *apis = *(struct extlua_apis **)lua_getextraspace(L);
API = *apis->sokol;
}
================================================
FILE: extlua/sokolapi_impl.c
================================================
// AUTO GENERATED by sokolapi_impl.temp.c, DONT EDIT
#include "sokol/sokol_gfx.h"
struct sokol_api {
int version;
sg_buffer (*sg_make_buffer) (const sg_buffer_desc* desc);
sg_shader (*sg_make_shader) (const sg_shader_desc* desc);
sg_pipeline (*sg_make_pipeline) (const sg_pipeline_desc* desc);
void (*sg_destroy_buffer) (sg_buffer buf);
void (*sg_destroy_shader) (sg_shader shd);
void (*sg_destroy_pipeline) (sg_pipeline pip);
void (*sg_update_buffer) (sg_buffer buf, const sg_range* data);
int (*sg_append_buffer) (sg_buffer buf, const sg_range* data);
void (*sg_apply_pipeline) (sg_pipeline pip);
void (*sg_apply_bindings) (const sg_bindings* bindings);
void (*sg_apply_uniforms) (int ub_slot, const sg_range* data);
void (*sg_draw) (int base_element, int num_elements, int num_instances);
void (*sg_draw_ex) (int base_element, int num_elements, int num_instances, int base_vertex, int base_instance);
sg_backend (*sg_query_backend) (void);
sg_resource_state (*sg_query_buffer_state) (sg_buffer buf);
sg_resource_state (*sg_query_shader_state) (sg_shader shd);
sg_resource_state (*sg_query_pipeline_state) (sg_pipeline pip);
};
struct sokol_api *
extlua_sokol_api() {
static struct sokol_api api = {
SOKOL_GFX_INCLUDED,
sg_make_buffer,
sg_make_shader,
sg_make_pipeline,
sg_destroy_buffer,
sg_destroy_shader,
sg_destroy_pipeline,
sg_update_buffer,
sg_append_buffer,
sg_apply_pipeline,
sg_apply_bindings,
sg_apply_uniforms,
sg_draw,
sg_draw_ex,
sg_query_backend,
sg_query_buffer_state,
sg_query_shader_state,
sg_query_pipeline_state,
};
return &api;
}
================================================
FILE: extlua/sokolapi_impl.temp.c
================================================
#include "sokol/sokol_gfx.h"
struct sokol_api {
int version;
$API_DECL$
};
struct sokol_api *
extlua_sokol_api() {
static struct sokol_api api = {
SOKOL_GFX_INCLUDED,
$API_STRUCT$
};
return &api;
}
================================================
FILE: extlua/solunaapi.c
================================================
// AUTO GENERATED by solunaapi.temp.c, DONT EDIT
#include "solunaapi.h"
#include
struct soluna_api {
int version;
soluna_material_error (*material_submit) (const void *stream, int prim_n, int material_id, int batch_n, void *ud, soluna_material_submit_func submit);
int (*material_sprite_rect) (struct soluna_sprite_bank bank, int sprite, struct soluna_sprite_rect *out);
sg_bindings (*material_bindings) (struct soluna_render_bindings bindings);
soluna_material_error (*material_push_stream) (int material_id, int count, size_t payload_size, soluna_material_stream_write_func write, void *ud, struct soluna_material_stream *out);
void (*material_stream_free) (void *ptr);
int (*material_stream_read) (struct soluna_material_stream_context ctx, int index, size_t payload_size, void *payload, struct soluna_material_stream_data *out);
void (*material_stream_error) (struct soluna_material_stream_context ctx, const char *error);
int (*material_stream_failed) (struct soluna_material_stream_context ctx);
};
static struct soluna_api API;
soluna_material_error
soluna_material_submit(const void *stream, int prim_n, int material_id, int batch_n, void *ud, soluna_material_submit_func submit) {
return API.material_submit(stream, prim_n, material_id, batch_n, ud, submit);
}
int
soluna_material_sprite_rect(struct soluna_sprite_bank bank, int sprite, struct soluna_sprite_rect *out) {
return API.material_sprite_rect(bank, sprite, out);
}
sg_bindings
soluna_material_bindings(struct soluna_render_bindings bindings) {
return API.material_bindings(bindings);
}
soluna_material_error
soluna_material_push_stream(int material_id, int count, size_t payload_size, soluna_material_stream_write_func write, void *ud, struct soluna_material_stream *out) {
return API.material_push_stream(material_id, count, payload_size, write, ud, out);
}
void
soluna_material_stream_free(void *ptr) {
API.material_stream_free(ptr);
}
int
soluna_material_stream_read(struct soluna_material_stream_context ctx, int index, size_t payload_size, void *payload, struct soluna_material_stream_data *out) {
return API.material_stream_read(ctx, index, payload_size, payload, out);
}
void
soluna_material_stream_error(struct soluna_material_stream_context ctx, const char *error) {
API.material_stream_error(ctx, error);
}
int
soluna_material_stream_failed(struct soluna_material_stream_context ctx) {
return API.material_stream_failed(ctx);
}
struct lua_api;
struct sokol_api;
struct extlua_apis {
struct lua_api * lua;
struct sokol_api * sokol;
struct soluna_api * soluna;
};
void
solunaapi_init(lua_State *L) {
struct extlua_apis *apis = *(struct extlua_apis **)lua_getextraspace(L);
if (apis == NULL || apis->soluna == NULL || apis->soluna->version != SOLUNA_EXT_API_VERSION) {
int version = (apis != NULL && apis->soluna != NULL) ? apis->soluna->version : 0;
luaL_error(L, "soluna ext api version mismatch, expected %d got %d", SOLUNA_EXT_API_VERSION, version);
}
API = *apis->soluna;
}
================================================
FILE: extlua/solunaapi.h
================================================
// AUTO GENERATED by solunaapi.h.temp, DONT EDIT
#ifndef SOLUNAAPI_H
#define SOLUNAAPI_H
#include
#include
#include "sokol/sokol_gfx.h"
#define SOLUNA_EXT_API_VERSION 1
struct soluna_sprite_rect {
int texture;
float u;
float v;
float w;
float h;
float ox;
float oy;
};
struct soluna_material_stream_item {
float x;
float y;
int sprite;
const void *payload;
};
struct soluna_material_stream_data {
float x;
float y;
int sprite;
};
struct soluna_material_stream {
char *data;
size_t size;
};
typedef const char *soluna_material_error;
struct soluna_material_stream_context {
void *ctx;
};
struct soluna_render_bindings {
void *ctx;
};
struct soluna_sprite_bank {
void *ctx;
};
typedef void (*soluna_material_submit_func)(void *ud, struct soluna_material_stream_context ctx, int n);
typedef void (*soluna_material_stream_write_func)(void *ud, int index, struct soluna_material_stream_item *item);
void solunaapi_init(lua_State *L);
soluna_material_error soluna_material_submit(const void *stream, int prim_n, int material_id, int batch_n, void *ud, soluna_material_submit_func submit);
int soluna_material_sprite_rect(struct soluna_sprite_bank bank, int sprite, struct soluna_sprite_rect *out);
sg_bindings soluna_material_bindings(struct soluna_render_bindings bindings);
soluna_material_error soluna_material_push_stream(int material_id, int count, size_t payload_size, soluna_material_stream_write_func write, void *ud, struct soluna_material_stream *out);
void soluna_material_stream_free(void *ptr);
int soluna_material_stream_read(struct soluna_material_stream_context ctx, int index, size_t payload_size, void *payload, struct soluna_material_stream_data *out);
void soluna_material_stream_error(struct soluna_material_stream_context ctx, const char *error);
int soluna_material_stream_failed(struct soluna_material_stream_context ctx);
#endif
================================================
FILE: extlua/solunaapi.h.temp
================================================
#ifndef SOLUNAAPI_H
#define SOLUNAAPI_H
#include
#include
#include "sokol/sokol_gfx.h"
$TYPE_DECL$
$HEADER_DECL$
#endif
================================================
FILE: extlua/solunaapi.temp.c
================================================
#include "solunaapi.h"
#include
struct soluna_api {
int version;
$API_DECL$
};
static struct soluna_api API;
$API_IMPL$
struct lua_api;
struct sokol_api;
struct extlua_apis {
struct lua_api * lua;
struct sokol_api * sokol;
struct soluna_api * soluna;
};
void
solunaapi_init(lua_State *L) {
struct extlua_apis *apis = *(struct extlua_apis **)lua_getextraspace(L);
if (apis == NULL || apis->soluna == NULL || apis->soluna->version != SOLUNA_EXT_API_VERSION) {
int version = (apis != NULL && apis->soluna != NULL) ? apis->soluna->version : 0;
luaL_error(L, "soluna ext api version mismatch, expected %d got %d", SOLUNA_EXT_API_VERSION, version);
}
API = *apis->soluna;
}
================================================
FILE: extlua/solunaapi_impl.c
================================================
// AUTO GENERATED by solunaapi_impl.temp.c, DONT EDIT
#include
#include "sokol/sokol_gfx.h"
#define SOLUNA_EXT_API_VERSION 1
struct soluna_sprite_rect {
int texture;
float u;
float v;
float w;
float h;
float ox;
float oy;
};
struct soluna_material_stream_item {
float x;
float y;
int sprite;
const void *payload;
};
struct soluna_material_stream_data {
float x;
float y;
int sprite;
};
struct soluna_material_stream {
char *data;
size_t size;
};
typedef const char *soluna_material_error;
struct soluna_material_stream_context {
void *ctx;
};
struct soluna_render_bindings {
void *ctx;
};
struct soluna_sprite_bank {
void *ctx;
};
typedef void (*soluna_material_submit_func)(void *ud, struct soluna_material_stream_context ctx, int n);
typedef void (*soluna_material_stream_write_func)(void *ud, int index, struct soluna_material_stream_item *item);
extern soluna_material_error material_submit(const void *stream, int prim_n, int material_id, int batch_n, void *ud, soluna_material_submit_func submit);
extern int material_sprite_rect(struct soluna_sprite_bank bank, int sprite, struct soluna_sprite_rect *out);
extern sg_bindings material_bindings(struct soluna_render_bindings bindings);
extern soluna_material_error material_push_stream(int material_id, int count, size_t payload_size, soluna_material_stream_write_func write, void *ud, struct soluna_material_stream *out);
extern void material_stream_free(void *ptr);
extern int material_stream_read(struct soluna_material_stream_context ctx, int index, size_t payload_size, void *payload, struct soluna_material_stream_data *out);
extern void material_stream_error(struct soluna_material_stream_context ctx, const char *error);
extern int material_stream_failed(struct soluna_material_stream_context ctx);
struct soluna_api {
int version;
soluna_material_error (*material_submit) (const void *stream, int prim_n, int material_id, int batch_n, void *ud, soluna_material_submit_func submit);
int (*material_sprite_rect) (struct soluna_sprite_bank bank, int sprite, struct soluna_sprite_rect *out);
sg_bindings (*material_bindings) (struct soluna_render_bindings bindings);
soluna_material_error (*material_push_stream) (int material_id, int count, size_t payload_size, soluna_material_stream_write_func write, void *ud, struct soluna_material_stream *out);
void (*material_stream_free) (void *ptr);
int (*material_stream_read) (struct soluna_material_stream_context ctx, int index, size_t payload_size, void *payload, struct soluna_material_stream_data *out);
void (*material_stream_error) (struct soluna_material_stream_context ctx, const char *error);
int (*material_stream_failed) (struct soluna_material_stream_context ctx);
};
struct soluna_api *
extlua_soluna_api() {
static struct soluna_api api = {
SOLUNA_EXT_API_VERSION,
material_submit,
material_sprite_rect,
material_bindings,
material_push_stream,
material_stream_free,
material_stream_read,
material_stream_error,
material_stream_failed,
};
return &api;
}
================================================
FILE: extlua/solunaapi_impl.temp.c
================================================
$HOST_TYPE_DECL$
$API_EXTERN$
struct soluna_api {
int version;
$API_DECL$
};
struct soluna_api *
extlua_soluna_api() {
static struct soluna_api api = {
SOLUNA_EXT_API_VERSION,
$API_STRUCT$
};
return &api;
}
================================================
FILE: make.lua
================================================
local lm = require "luamake"
local fs = require "bee.filesystem"
local function detect_emcc()
if lm.compiler == "emcc" then
return true
end
if type(lm.cc) == "string" and lm.cc:find("emcc", 1, true) then
return true
end
return false
end
local osplat = (function()
if lm.os == "windows" then
if lm.compiler == "gcc" then
return "mingw"
end
if lm.cc == "clang-cl" then
return "clang-cl"
end
return "msvc"
end
return lm.os
end)()
local plat = (function()
if detect_emcc() then
return "emcc"
end
return osplat
end)()
lm.platform = plat
lm.basedir = lm:path "."
lm.bindir = ("bin/%s/%s"):format(plat, lm.mode)
lm.osbindir = ("bin/%s/%s"):format(osplat, lm.mode)
lm:conf {
cxx = "c++20",
clang = {
c = "c11",
},
flags = {
lm.mode ~= "debug" and "-O2",
},
msvc = {
c = "c11",
flags = {
"-W3",
"-utf-8",
"-experimental:c11atomics",
"/wd4244",
"/wd4267",
"/wd4305",
"/wd4996",
"/wd4018",
"/wd4113",
},
defines = {
"_CRT_SECURE_NO_WARNINGS",
"_CRT_NONSTDC_NO_DEPRECATE",
"_CRT_SECURE_NO_DEPRECATE"
},
},
mingw = {
c = "c99",
},
gcc = {
c = "c11",
flags = {
"-Wall",
},
defines = {
"_POSIX_C_SOURCE=199309L",
"_GNU_SOURCE",
},
links = {
"m",
(lm.os ~= "windows" and lm.platform ~= "emcc") and "fontconfig",
},
},
emcc = {
c = "gnu11",
flags = {
"-Wall",
"-pthread",
"-fPIC",
"--use-port=emdawnwebgpu",
"-fwasm-exceptions",
},
links = {
"idbfs.js",
},
ldflags = {
"--use-port=emdawnwebgpu",
"-s ALLOW_MEMORY_GROWTH",
"-s FORCE_FILESYSTEM=1",
"-s USE_PTHREADS=1",
"-fwasm-exceptions",
lm.mode == "debug" and "-gsource-map",
lm.mode == "debug" and "-s EXCEPTION_STACK_TRACES=1",
lm.mode == "debug" and "-s ASSERTIONS=2",
-- lm.mode == "debug" and "-s SAFE_HEAP=1",
lm.mode == "debug" and "-s STACK_OVERFLOW_CHECK=1",
lm.mode == "debug" and "-s PTHREADS_DEBUG=1",
},
defines = {
"_POSIX_C_SOURCE=200809L",
"_GNU_SOURCE",
},
},
defines = {
-- lm.mode == "debug" and "DEBUGLOG",
lm.mode == "debug" and "SOKOL_DEBUG",
}
}
local deps = { "soluna_src" }
for path in fs.pairs(lm.basedir .. "/clibs") do
local name = path:stem():string()
if name ~= "soluna" and name ~= "sample" and fs.exists(path / "make.lua") then
local makefile = ("clibs/%s/make.lua"):format(name)
lm:import(makefile)
deps[#deps + 1] = name .. "_src"
end
end
lm:import "clibs/soluna/make.lua"
lm:import "clibs/sample/make.lua"
lm:exe "soluna" {
deps = deps,
emcc = {
ldflags = {
"--js-library=src/platform/wasm/soluna_ime.js",
"--js-library=src/platform/wasm/soluna_openurl.js",
"-s MODULARIZE=1",
"-s EXPORT_ES6=1",
"-s EXPORT_NAME=createApp",
'-s EXPORTED_FUNCTIONS=\'["_main"]\'',
'-s EXPORTED_RUNTIME_METHODS=\'["FS","FS_createPath","FS_createDataFile","IDBFS"]\'',
"-s PTHREAD_POOL_SIZE='Math.max(2,navigator.hardwareConcurrency)'",
"-s PTHREAD_POOL_SIZE_STRICT=2",
"-s MAIN_MODULE=2",
"-Wl,-u,emscripten_builtin_memalign",
"-Wl,--export=emscripten_builtin_memalign",
},
},
}
lm:import "script/act_targets.lua"
lm:runlua "cc" {
script = "script/compile_commands.lua",
args = {
"build/build.ninja",
"compile_commands.json",
},
inputs = {
"build/build.ninja",
"script/compile_commands.lua",
},
outputs = {
"compile_commands.json",
},
}
lm:default "soluna"
================================================
FILE: script/act.lua
================================================
local platform = require "bee.platform"
local fs = require "bee.filesystem"
local subprocess = require "bee.subprocess"
local is_windows = platform.os == "windows"
local function quote_ps(value)
return "'" .. value:gsub("'", "''") .. "'"
end
local function cmdline(args)
local out = {}
for i = 1, #args do
local v = args[i]
if v:find("[%s\"]") then
out[#out + 1] = '"' .. v:gsub('"', '\\"') .. '"'
else
out[#out + 1] = v
end
end
return table.concat(out, " ")
end
local function run(args, option)
option = option or {}
print("> " .. cmdline(args))
local process, errmsg = subprocess.spawn {
args,
searchPath = true,
stdout = option.stdout ~= nil and option.stdout or io.stdout,
stderr = option.stderr ~= nil and option.stderr or "stdout",
}
assert(process, errmsg)
local code = process:wait()
process:detach()
if code ~= 0 then
error(("command failed (%d): %s"):format(code, cmdline(args)))
end
end
local function run_code(args)
local process = assert(subprocess.spawn {
args,
searchPath = true,
stdout = true,
stderr = true,
})
local code = process:wait()
process:detach()
return code
end
local function exists(path)
return fs.exists(path)
end
local function getenv(name, default)
local value = os.getenv(name)
if value == nil or value == "" then
return default
end
return value
end
local function reset_dir(path)
pcall(fs.remove_all, path)
if not exists(path) then
fs.create_directories(path)
end
end
local function get_home()
if is_windows then
local userprofile = os.getenv("USERPROFILE")
if userprofile and userprofile ~= "" then
return fs.path(userprofile)
end
local home_drive = os.getenv("HOMEDRIVE") or ""
local home_path = os.getenv("HOMEPATH") or ""
local combined = home_drive .. home_path
if combined ~= "" then
return fs.path(combined)
end
return nil
end
local home = os.getenv("HOME")
if home and home ~= "" then
return fs.path(home)
end
return nil
end
local function detect_python()
if is_windows then
if run_code { "where", "py" } == 0 then
return { "py", "-3" }
end
if run_code { "where", "python" } == 0 then
return { "python" }
end
else
if run_code { "which", "python3" } == 0 then
return { "python3" }
end
if run_code { "which", "python" } == 0 then
return { "python" }
end
end
error "python interpreter not found"
end
local function find_file(root, filename)
for path, status in fs.pairs_r(root) do
if status and status:is_regular_file() and path:filename():string() == filename then
return path
end
end
return nil
end
local function collect_files(root)
local files = {}
if not exists(root) then
return files
end
for path, status in fs.pairs_r(root) do
if status and status:is_regular_file() then
files[#files + 1] = path
end
end
table.sort(files, function(a, b)
return a:string() < b:string()
end)
return files
end
local workflow = (arg[1] or "pages"):lower()
local workflow_file = ({
pages = ".github/workflows/pages.yml",
nightly = ".github/workflows/nightly.yml",
})[workflow]
if not workflow_file then
error(("unknown workflow: %s (expected: pages or nightly)"):format(workflow))
end
local function parse_options(argv)
local options = {
port = tonumber(getenv("PORT", "8080")) or 8080,
host_os = platform.os,
}
for i = 2, #argv do
local value = argv[i]
local port_num = tonumber(value)
local kv_key, kv_value = value:match("^([%w_]+)=(.+)$")
if kv_key == "port" then
local p = tonumber(kv_value)
if p then
options.port = p
end
elseif kv_key == "host_os" then
options.host_os = kv_value:lower()
elseif port_num then
options.port = port_num
end
end
return options
end
local options = parse_options(arg)
local port = options.port
local home = assert(get_home(), "home directory is not set")
local root = fs.path(getenv("ACT_ROOT", (home / ".act/soluna"):string()))
local artifact_root = root / "artifacts"
local unpack_dir = root / "unpack"
local serve_root = root / "serve"
local preview_dir = serve_root / "soluna"
reset_dir(artifact_root)
reset_dir(unpack_dir)
reset_dir(preview_dir)
local act_args = {
"act",
"workflow_dispatch",
"-W",
workflow_file,
"--container-architecture",
"linux/amd64",
"--artifact-server-path",
artifact_root:string(),
}
if workflow == "nightly" then
local matrix_os = ({
windows = "windows-latest",
macos = "macos-latest",
})[options.host_os] or "ubuntu-latest"
act_args[#act_args + 1] = "--matrix"
act_args[#act_args + 1] = "os:" .. matrix_os
if matrix_os == "windows-latest" or matrix_os == "macos-latest" then
act_args[#act_args + 1] = "-P"
act_args[#act_args + 1] = matrix_os .. "=-self-hosted"
end
print(("Nightly matrix selected: %s (host_os=%s)"):format(matrix_os, options.host_os))
end
run(act_args)
if workflow ~= "pages" then
print("Workflow completed: " .. workflow)
print("Artifacts root: " .. artifact_root:string())
local files = collect_files(artifact_root)
if #files == 0 then
print("No artifact files were found under artifacts root.")
else
print("Artifact files:")
for i = 1, #files do
local rel = files[i]:string():sub(#artifact_root:string() + 2)
print(" - " .. rel)
end
end
return
end
local zip_path = find_file(artifact_root, "github-pages.zip")
if not zip_path then
error(table.concat({
"missing github-pages.zip under: " .. artifact_root:string(),
"The workflow build job was likely skipped (for example, unsupported runner image).",
"You can inspect with: act -l ; act -n -W .github/workflows/pages.yml",
"If needed, pass platform mapping manually, for example:",
" act workflow_dispatch -W .github/workflows/pages.yml -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-latest",
}, "\n"))
end
if is_windows then
run {
"powershell",
"-NoProfile",
"-Command",
"Expand-Archive -LiteralPath "
.. quote_ps(zip_path:string())
.. " -DestinationPath "
.. quote_ps(unpack_dir:string())
.. " -Force",
}
else
run {
"unzip",
"-o",
zip_path:string(),
"-d",
unpack_dir:string(),
}
end
local artifact_tar = find_file(unpack_dir, "artifact.tar")
if not artifact_tar then
error("missing artifact.tar under: " .. unpack_dir:string())
end
run {
"tar",
"-xf",
artifact_tar:string(),
"-C",
preview_dir:string(),
}
print("Preview files are ready at: " .. preview_dir:string())
print("Serving http://127.0.0.1:" .. port .. "/soluna/")
print "If the page loads before the service worker takes control, refresh once."
local python = detect_python()
python[#python + 1] = "-m"
python[#python + 1] = "http.server"
python[#python + 1] = tostring(port)
python[#python + 1] = "--directory"
python[#python + 1] = serve_root:string()
run(python)
================================================
FILE: script/act_targets.lua
================================================
local lm = require "luamake"
lm:rule "act_lua" {
args = { "$luamake", "lua", "$args" },
description = "$args",
pool = "console",
}
lm:build "pages" {
rule = "act_lua",
args = { "@act.lua", "pages" },
inputs = { "act.lua" },
}
lm:build "nightly" {
rule = "act_lua",
args = { "@act.lua", "nightly", "host_os=" .. lm.os },
inputs = { "act.lua" },
}
lm:phony "act" {
inputs = "act.lua",
}
================================================
FILE: script/compile_commands.lua
================================================
local subprocess = require "bee.subprocess"
local ninja_file, output = ...
assert(ninja_file, "missing ninja file path")
assert(output, "missing output path")
local process = assert(subprocess.spawn {
"ninja",
"-f",
ninja_file,
"-t",
"compdb",
"-x",
searchPath = true,
stdout = true,
})
local content = process.stdout:read "a"
local code = process:wait()
if code ~= 0 then
os.exit(code, true)
end
local file = assert(io.open(output, "wb"))
file:write(content)
================================================
FILE: script/datalist2c.lua
================================================
local dlsrc, cname = ...
local f = assert(io.open(dlsrc,"rb"))
local bin = f:read "a"
f:close()
local code = [[
static const unsigned char dl_$name[] = {
$bytes
};
]]
local count = 0
local function tohex(c)
local b = string.format("0x%02x,", c:byte())
count = count + 1
if count == 16 then
b = b .. "\n\t"
count = 0
end
return b
end
local p = {
name = cname:match "([^/]+)%.dl%.h$",
bytes = string.gsub(bin .. "\0", ".", tohex),
}
local source = string.gsub(code, "$(%w+)", p)
local f = assert(io.open(cname, "w"))
f:write(source)
f:close()
================================================
FILE: script/hashversion.lua
================================================
local function get_rev()
local f = io.popen "git rev-parse HEAD"
local rev = f:read "a"
f:close()
return rev:match "[%da-f]*"
end
local version = get_rev()
print("SOLUNA_HASH_VERSION", version)
local f = assert(io.open "src/version.h")
local text = f:read "a"
text = text:gsub('(SOLUNA_HASH_VERSION%s+")([%da-f]*)(")', "%1".. version .. "%3")
f:close()
local f = assert(io.open("src/version.h", "wb"))
f:write(text)
f:close()
================================================
FILE: script/lua2c.lua
================================================
local luasrc, cname = ...
local s = assert(loadfile(luasrc))
local bin = string.dump(s)
local code = [[
static const unsigned char luasrc_$name[] = {
$bytes
};
]]
local count = 0
local function tohex(c)
local b = string.format("0x%02x,", c:byte())
count = count + 1
if count == 16 then
b = b .. "\n\t"
count = 0
end
return b
end
local p = {
name = cname:match "([^/]+)%.lua%.h$",
bytes = string.gsub(bin .. "\0" , ".", tohex),
}
local source = string.gsub(code, "$(%w+)", p)
local f = assert(io.open(cname, "w"))
f:write(source)
f:close()
================================================
FILE: src/appevent.h
================================================
#ifndef soluna_app_event_h
#define soluna_app_event_h
struct event_message {
const char *typestr;
int p1;
int p2;
int p3;
};
static inline void
get_xy(struct event_message *em, float x, float y) {
float dpi_scale = sapp_dpi_scale();
float inv;
if (dpi_scale <= 0.0f) {
dpi_scale = 1.0f;
inv = 1.0f;
} else {
inv = 1.0f / dpi_scale;
}
float logical_x = x * inv;
float logical_y = y * inv;
if (logical_x >= 0.0f)
em->p1 = (int)(logical_x + 0.5f);
else
em->p1 = (int)(logical_x - 0.5f);
if (logical_y >= 0.0f)
em->p2 = (int)(logical_y + 0.5f);
else
em->p2 = (int)(logical_y - 0.5f);
}
static inline void
mouse_message(struct event_message *em, const sapp_event* ev) {
switch (ev->type) {
case SAPP_EVENTTYPE_MOUSE_MOVE:
em->typestr = "mouse_move";
get_xy(em, ev->mouse_x, ev->mouse_y);
break;
case SAPP_EVENTTYPE_MOUSE_DOWN:
case SAPP_EVENTTYPE_MOUSE_UP:
em->typestr = "mouse_button";
em->p1 = ev->mouse_button;
em->p2 = ev->type == SAPP_EVENTTYPE_MOUSE_DOWN;
break;
case SAPP_EVENTTYPE_MOUSE_SCROLL:
em->typestr = "mouse_scroll";
em->p1 = ev->scroll_y;
em->p2 = ev->scroll_x;
break;
default:
em->typestr = "mouse";
em->p1 = ev->type;
break;
}
}
static inline void
touch_message(struct event_message *em, const sapp_event* ev) {
// todo : support multi touch points
// get 1st touch point now
const sapp_touchpoint *t = &ev->touches[0];
get_xy(em, t->pos_x, t->pos_y);
em->p3 = t->changed;
switch (ev->type) {
case SAPP_EVENTTYPE_TOUCHES_BEGAN:
em->typestr = "touch_begin";
break;
case SAPP_EVENTTYPE_TOUCHES_MOVED:
em->typestr = "touch_moved";
break;
case SAPP_EVENTTYPE_TOUCHES_ENDED:
em->typestr = "touch_end";
break;
case SAPP_EVENTTYPE_TOUCHES_CANCELLED:
em->typestr = "touch_cancelled";
break;
default:
em->typestr = "touch";
break;
}
}
static inline void
window_message(struct event_message *em, const sapp_event *ev) {
switch (ev->type) {
case SAPP_EVENTTYPE_RESIZED:
em->typestr = "window_resize";
em->p1 = ev->window_width;
em->p2 = ev->window_height;
break;
default:
em->typestr = "window";
em->p1 = ev->type;
break;
}
}
static inline void
key_message(struct event_message *em, const sapp_event *ev) {
switch (ev->type) {
case SAPP_EVENTTYPE_CHAR:
em->typestr = "char";
em->p1 = (int)ev->char_code;
em->p2 = 0;
break;
default:
em->typestr = "key";
em->p1 = (int)ev->key_code;
em->p2 = ev->type == SAPP_EVENTTYPE_KEY_DOWN;
break;
}
}
static inline void
app_event_unpack(struct event_message *em, const sapp_event* ev) {
em->typestr = NULL;
em->p1 = 0;
em->p2 = 0;
em->p3 = 0;
switch (ev->type) {
case SAPP_EVENTTYPE_MOUSE_MOVE:
case SAPP_EVENTTYPE_MOUSE_DOWN:
case SAPP_EVENTTYPE_MOUSE_UP:
case SAPP_EVENTTYPE_MOUSE_SCROLL:
case SAPP_EVENTTYPE_MOUSE_ENTER:
case SAPP_EVENTTYPE_MOUSE_LEAVE:
mouse_message(em, ev);
break;
case SAPP_EVENTTYPE_TOUCHES_BEGAN:
case SAPP_EVENTTYPE_TOUCHES_MOVED:
case SAPP_EVENTTYPE_TOUCHES_ENDED:
case SAPP_EVENTTYPE_TOUCHES_CANCELLED:
touch_message(em, ev);
break;
case SAPP_EVENTTYPE_RESIZED:
window_message(em, ev);
break;
case SAPP_EVENTTYPE_CHAR:
case SAPP_EVENTTYPE_KEY_DOWN:
case SAPP_EVENTTYPE_KEY_UP:
key_message(em, ev);
break;
default:
em->typestr = "message";
em->p1 = ev->type;
break;
}
}
#endif
================================================
FILE: src/audio.c
================================================
#include
#include
#include "zipreader.h"
#ifdef __EMSCRIPTEN__
#include
#endif
#define MA_NO_WIN32_FILEIO
#define MA_NO_MP3
#define MA_NO_FLAC
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
FILE * fopen_utf8(const char *filename, const char *mode);
static ma_result
vfs_open_local(ma_vfs* pVFS, const char* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile) {
FILE* pFileStd;
const char* pOpenModeStr;
MA_ASSERT(pFilePath != NULL);
MA_ASSERT(openMode != 0);
MA_ASSERT(pFile != NULL);
(void)pVFS;
if ((openMode & MA_OPEN_MODE_READ) != 0) {
if ((openMode & MA_OPEN_MODE_WRITE) != 0) {
pOpenModeStr = "r+";
} else {
pOpenModeStr = "rb";
}
} else {
pOpenModeStr = "wb";
}
pFileStd = fopen_utf8(pFilePath, pOpenModeStr);
if (pFileStd == NULL) {
return MA_ERROR;
}
*pFile = pFileStd;
return MA_SUCCESS;
}
struct custom_vfs {
ma_default_vfs base;
struct zipreader_name *zipnames;
};
struct custom_engine {
struct ma_engine engine;
struct ma_resource_manager rm;
struct custom_vfs vfs;
};
struct audio_group {
ma_sound_group group;
int alive;
};
struct audio_sound {
ma_sound sound;
int alive;
};
#define AUDIO_GROUP_METATABLE "SOLUNA_AUDIO_GROUP"
#define AUDIO_SOUND_METATABLE "SOLUNA_AUDIO_SOUND"
static struct custom_engine *
check_engine(lua_State *L, int index) {
luaL_checktype(L, index, LUA_TLIGHTUSERDATA);
return (struct custom_engine *)lua_touserdata(L, index);
}
static struct audio_group *
check_group(lua_State *L, int index) {
struct audio_group *group = (struct audio_group *)luaL_checkudata(L, index, AUDIO_GROUP_METATABLE);
luaL_argcheck(L, group->alive, index, "closed audio group");
return group;
}
static struct audio_sound *
check_sound(lua_State *L, int index) {
struct audio_sound *sound = (struct audio_sound *)luaL_checkudata(L, index, AUDIO_SOUND_METATABLE);
luaL_argcheck(L, sound->alive, index, "closed audio sound");
return sound;
}
static int
push_error(lua_State *L, ma_result r) {
lua_pushnil(L);
lua_pushstring(L, ma_result_description(r));
return 2;
}
static int
laudio_group_uninit(lua_State *L) {
struct audio_group *group = (struct audio_group *)luaL_checkudata(L, 1, AUDIO_GROUP_METATABLE);
if (group->alive) {
ma_sound_group_uninit(&group->group);
group->alive = 0;
}
return 0;
}
static int
laudio_sound_uninit(lua_State *L) {
struct audio_sound *sound = (struct audio_sound *)luaL_checkudata(L, 1, AUDIO_SOUND_METATABLE);
if (sound->alive) {
ma_sound_uninit(&sound->sound);
sound->alive = 0;
}
return 0;
}
static ma_result
zr_open(ma_vfs* pVFS, const char* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile) {
struct custom_vfs *vfs = (struct custom_vfs *)pVFS;
if (openMode != MA_OPEN_MODE_READ)
return MA_NOT_IMPLEMENTED;
zipreader_file zf = zipreader_open(vfs->zipnames, pFilePath);
if (zf == NULL) {
return MA_ERROR;
}
*pFile = (ma_vfs_file)zf;
return MA_SUCCESS;
}
static ma_result
zr_close(ma_vfs* pVFS, ma_vfs_file file) {
(void)pVFS;
zipreader_close((zipreader_file)file);
return MA_SUCCESS;
}
static ma_result
zr_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t sizeInBytes, size_t* pBytesRead) {
(void)pVFS;
int bytes = (int)sizeInBytes;
if (bytes!= sizeInBytes || bytes < 0)
return MA_OUT_OF_RANGE;
int rd = zipreader_read((zipreader_file)file, pDst, bytes);
if (rd < 0)
return MA_IO_ERROR;
*pBytesRead = rd;
return MA_SUCCESS;
}
static ma_result
zr_seek(ma_vfs* pVFS, ma_vfs_file file, ma_int64 offset, ma_seek_origin origin) {
(void)pVFS;
int whence;
switch (origin) {
case ma_seek_origin_start :
whence = SEEK_SET;
break;
case ma_seek_origin_current :
whence = SEEK_CUR;
break;
case ma_seek_origin_end :
whence = SEEK_END;
break;
default :
return MA_INVALID_ARGS;
}
if (zipreader_seek((zipreader_file)file, offset, whence) != 0) {
return MA_ERROR;
}
return MA_SUCCESS;
}
static ma_result
zr_tell(ma_vfs* pVFS, ma_vfs_file file, ma_int64* pCursor) {
(void)pVFS;
*pCursor = zipreader_tell((zipreader_file)file);
if (*pCursor < 0)
return MA_ERROR;
return MA_SUCCESS;
}
static ma_result
zr_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo) {
(void)pVFS;
pInfo->sizeInBytes = zipreader_size((zipreader_file)file);
return MA_SUCCESS;
}
static int
laudio_init_vfs(lua_State *L) {
struct custom_engine *e = (struct custom_engine *)lua_touserdata(L, 1);
luaL_checktype(L, 2, LUA_TUSERDATA);
e->vfs.zipnames = lua_touserdata(L, 2);
e->vfs.base.cb.onOpen = zr_open;
e->vfs.base.cb.onOpenW = NULL;
e->vfs.base.cb.onClose = zr_close;
e->vfs.base.cb.onRead = zr_read;
e->vfs.base.cb.onWrite = NULL;
e->vfs.base.cb.onSeek = zr_seek;
e->vfs.base.cb.onTell = zr_tell;
e->vfs.base.cb.onInfo = zr_info;
return 0;
}
static int
laudio_init(lua_State *L) {
struct custom_engine *e = (struct custom_engine *)lua_newuserdatauv(L, sizeof(*e), 0);
ma_default_vfs_init(&e->vfs.base, NULL);
e->vfs.base.cb.onOpen = vfs_open_local;
e->vfs.zipnames = NULL;
ma_resource_manager_config config = ma_resource_manager_config_init();
config.pVFS = &e->vfs;
ma_result r = ma_resource_manager_init(&config, &e->rm);
if (r != MA_SUCCESS) {
return luaL_error(L, "ma_resource_manager_init() error : %s", ma_result_description(r));
}
ma_engine_config ec = ma_engine_config_init();
ec.pResourceManager = &e->rm;
r = ma_engine_init(&ec, &e->engine);
if (r != MA_SUCCESS) {
return luaL_error(L, "ma_engine_init() error : %s", ma_result_description(r));
}
e->rm.config.decodedFormat = ma_format_f32;
e->rm.config.decodedSampleRate = ma_engine_get_sample_rate(&e->engine);
lua_pushlightuserdata(L, (void *)e);
return 2;
}
static int
laudio_deinit(lua_State *L) {
struct custom_engine *e = check_engine(L, 1);
ma_engine_uninit(&e->engine);
ma_resource_manager_uninit(&e->rm);
return 0;
}
static int
laudio_group_init(lua_State *L) {
struct custom_engine *e = check_engine(L, 1);
struct audio_group *group = (struct audio_group *)lua_newuserdatauv(L, sizeof(*group), 0);
group->alive = 0;
ma_result r = ma_sound_group_init(&e->engine, 0, NULL, &group->group);
if (r != MA_SUCCESS) {
lua_pop(L, 1);
return push_error(L, r);
}
group->alive = 1;
luaL_setmetatable(L, AUDIO_GROUP_METATABLE);
return 1;
}
static int
laudio_group_set_volume(lua_State *L) {
struct audio_group *group = check_group(L, 1);
float volume = (float)luaL_checknumber(L, 2);
ma_sound_group_set_volume(&group->group, volume);
return 0;
}
static int
laudio_sound_init(lua_State *L) {
struct custom_engine *e = check_engine(L, 1);
const char *filename = luaL_checkstring(L, 2);
ma_uint32 flags = (ma_uint32)luaL_optinteger(L, 3, 0);
struct audio_group *group = NULL;
if (!lua_isnoneornil(L, 4)) {
group = check_group(L, 4);
}
struct audio_sound *sound = (struct audio_sound *)lua_newuserdatauv(L, sizeof(*sound), 0);
sound->alive = 0;
ma_result r = ma_sound_init_from_file(&e->engine, filename, flags, group ? &group->group : NULL, NULL, &sound->sound);
if (r != MA_SUCCESS) {
lua_pop(L, 1);
return push_error(L, r);
}
sound->alive = 1;
luaL_setmetatable(L, AUDIO_SOUND_METATABLE);
return 1;
}
static int
laudio_sound_start(lua_State *L) {
struct audio_sound *sound = check_sound(L, 1);
ma_result r = ma_sound_start(&sound->sound);
if (r != MA_SUCCESS) {
return push_error(L, r);
}
lua_pushboolean(L, 1);
return 1;
}
static int
laudio_sound_stop(lua_State *L) {
struct audio_sound *sound = check_sound(L, 1);
ma_result r;
if (lua_isnoneornil(L, 2)) {
r = ma_sound_stop(&sound->sound);
} else {
ma_uint64 fade_ms = (ma_uint64)luaL_checkinteger(L, 2);
if (fade_ms == 0) {
r = ma_sound_stop(&sound->sound);
} else {
r = ma_sound_stop_with_fade_in_milliseconds(&sound->sound, fade_ms);
}
}
if (r != MA_SUCCESS) {
return push_error(L, r);
}
lua_pushboolean(L, 1);
return 1;
}
static int
laudio_sound_playing(lua_State *L) {
struct audio_sound *sound = check_sound(L, 1);
lua_pushboolean(L, ma_sound_is_playing(&sound->sound));
return 1;
}
static int
laudio_sound_set_volume(lua_State *L) {
struct audio_sound *sound = check_sound(L, 1);
float volume = (float)luaL_checknumber(L, 2);
ma_sound_set_volume(&sound->sound, volume);
return 0;
}
static int
laudio_sound_set_pan(lua_State *L) {
struct audio_sound *sound = check_sound(L, 1);
float pan = (float)luaL_checknumber(L, 2);
ma_sound_set_pan(&sound->sound, pan);
return 0;
}
static int
laudio_sound_set_pitch(lua_State *L) {
struct audio_sound *sound = check_sound(L, 1);
float pitch = (float)luaL_checknumber(L, 2);
ma_sound_set_pitch(&sound->sound, pitch);
return 0;
}
static int
laudio_sound_set_looping(lua_State *L) {
struct audio_sound *sound = check_sound(L, 1);
int looping = lua_toboolean(L, 2);
ma_sound_set_looping(&sound->sound, looping);
return 0;
}
static int
laudio_sound_seek(lua_State *L) {
struct audio_sound *sound = check_sound(L, 1);
float seconds = (float)luaL_checknumber(L, 2);
ma_result r = ma_sound_seek_to_second(&sound->sound, seconds);
if (r != MA_SUCCESS) {
return push_error(L, r);
}
lua_pushboolean(L, 1);
return 1;
}
static int
laudio_sound_tell(lua_State *L) {
struct audio_sound *sound = check_sound(L, 1);
float seconds = 0.0f;
ma_result r = ma_sound_get_cursor_in_seconds(&sound->sound, &seconds);
if (r != MA_SUCCESS) {
return push_error(L, r);
}
lua_pushnumber(L, seconds);
return 1;
}
int
luaopen_soluna_audio(lua_State *L) {
luaL_checkversion(L);
if (luaL_newmetatable(L, AUDIO_GROUP_METATABLE)) {
lua_pushcfunction(L, laudio_group_uninit);
lua_setfield(L, -2, "__gc");
}
lua_pop(L, 1);
if (luaL_newmetatable(L, AUDIO_SOUND_METATABLE)) {
lua_pushcfunction(L, laudio_sound_uninit);
lua_setfield(L, -2, "__gc");
}
lua_pop(L, 1);
luaL_Reg l[] = {
{ "init", laudio_init },
{ "init_vfs", laudio_init_vfs },
{ "deinit", laudio_deinit },
{ "group_init", laudio_group_init },
{ "group_uninit", laudio_group_uninit },
{ "group_set_volume", laudio_group_set_volume },
{ "sound_init", laudio_sound_init },
{ "sound_uninit", laudio_sound_uninit },
{ "sound_start", laudio_sound_start },
{ "sound_stop", laudio_sound_stop },
{ "sound_playing", laudio_sound_playing },
{ "sound_set_volume", laudio_sound_set_volume },
{ "sound_set_pan", laudio_sound_set_pan },
{ "sound_set_pitch", laudio_sound_set_pitch },
{ "sound_set_looping", laudio_sound_set_looping },
{ "sound_seek", laudio_sound_seek },
{ "sound_tell", laudio_sound_tell },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: src/batch.c
================================================
#include "batch.h"
#include
#include
#include
#define DEFAULT_SIZE 1024
struct draw_batch {
int cap;
struct draw_primitive * stream;
};
struct draw_batch *
batch_new(int size) {
if (size < DEFAULT_SIZE)
size = DEFAULT_SIZE;
struct draw_batch * batch = (struct draw_batch *)malloc(sizeof(*batch));
if (batch == NULL)
return NULL;
batch->cap = size;
batch->stream = (struct draw_primitive *)malloc(sizeof(struct draw_primitive) * size);
if (batch->stream == NULL) {
free(batch);
return NULL;
}
return batch;
}
void
batch_delete(struct draw_batch *B) {
if (B == NULL)
return;
free(B->stream);
free(B);
}
struct draw_primitive *
batch_reserve(struct draw_batch *B, int size) {
if (size <= B->cap)
return B->stream;
int cap = B->cap;
do {
cap = cap * 3/2;
} while (cap < size);
struct draw_primitive * stream = realloc(B->stream, cap * sizeof(struct draw_primitive));
if (stream == NULL)
return NULL;
B->stream = stream;
B->cap = cap;
return stream;
}
================================================
FILE: src/batch.h
================================================
#ifndef soluna_batch_h
#define soluna_batch_h
#include
struct draw_primitive {
int32_t x; // sign bit + 23 + 8 fix number
int32_t y;
uint32_t sr; // scale + rot
int32_t sprite; // negative : material
};
struct draw_primitive_external {
int sprite;
};
struct draw_batch;
struct draw_batch * batch_new(int size);
struct draw_primitive * batch_reserve(struct draw_batch *, int size);
void batch_delete(struct draw_batch *);
#endif
================================================
FILE: src/blit.glsl
================================================
@vs vs
out vec2 uv;
void main() {
vec2 position = vec2(gl_VertexIndex & 1, gl_VertexIndex >> 1);
vec2 screen = position * 2.0 - 1.0;
gl_Position = vec4(screen.x, -screen.y, 0.0, 1.0);
uv = position;
}
@end
@fs fs
layout(binding=0) uniform texture2D tex;
layout(binding=0) uniform sampler smp;
in vec2 uv;
out vec4 frag_color;
void main() {
frag_color = texture(sampler2D(tex, smp), uv);
}
@end
@program blit vs fs
================================================
FILE: src/colorquad.glsl
================================================
@vs vs
layout(binding=0) uniform vs_params {
vec2 framesize;
};
struct sr_mat {
mat2 m;
};
layout(binding=0) readonly buffer sr_lut {
sr_mat sr[];
};
in vec4 position;
in uint idx;
in vec4 c;
out vec4 color;
void main() {
ivec2 u2 = ivec2(0 , position.z);
ivec2 v2 = ivec2(0 , position.w);
vec2 uv_offset = vec2(u2[gl_VertexIndex & 1] , v2[gl_VertexIndex >> 1]);
vec2 pos = (uv_offset * sr[idx].m + position.xy) * framesize;
gl_Position = vec4(pos.x - 1.0f, pos.y + 1.0f, 0, 1);
color = c;
}
@end
@fs fs
in vec4 color;
out vec4 frag_color;
void main() {
frag_color = color;
}
@end
@program colorquad vs fs
================================================
FILE: src/data/settingdefault.dl
================================================
sprite_max : 0x40000
texture_size : 2048
srbuffer_size : 0x10000
batch_size : 65536
draw_instance : 65536
entry : main.lua
project : soluna
service_path : "./?.lua"
width : 1024
height : 768
high_dpi : false
window_title : soluna
background : 0x4080c0
tmpbuffer_size : 0x20000
================================================
FILE: src/drawmgr.c
================================================
#include
#include
#include
#include "batch.h"
#include "spritemgr.h"
struct draw_element {
struct draw_primitive * base;
int n;
int material;
int texture;
};
struct drawmgr {
struct sprite_bank *bank;
int cap;
int n;
int bank_n;
struct draw_element data[1];
};
static int
ldrawmgr_len(lua_State *L) {
struct drawmgr * d = lua_touserdata(L, 1);
lua_pushinteger(L, d->n);
return 1;
}
static int
ldrawmgr_index(lua_State *L) {
struct drawmgr * d = lua_touserdata(L, 1);
int idx = luaL_checkinteger(L, 2) - 1;
if (idx < 0 || idx >= d->n) {
return 0;
}
struct draw_element *e = &(d->data[idx]);
lua_pushinteger(L, e->material);
lua_pushlightuserdata(L, e->base);
lua_pushinteger(L, e->n);
if (e->texture >= 0) {
lua_pushinteger(L, e->texture);
return 4;
} else {
return 3;
}
}
static int
ldrawmgr_reset(lua_State *L) {
struct drawmgr * d = (struct drawmgr *)luaL_checkudata(L, 1, "SOLUNA_DRAWMGR");
d->n = 0;
d->bank_n = d->bank->n;
return 0;
}
static int
append_external_material(struct drawmgr * d, struct draw_primitive *base, int n, int matid, int texid) {
int i;
struct sprite_rect * rect = d->bank->rect;
for (i=1;isprite;
if (sprite >= 0 && rect[sprite].texid != texid) {
break;
}
}
struct draw_element *e = &d->data[d->n++];
e->base = base;
e->n = i;
e->material = -matid;
e->texture = texid;
return i;
}
static int
append_default_material(struct drawmgr * d, struct draw_primitive *base, int n, int texid) {
int i;
struct sprite_rect * rect = d->bank->rect;
int rect_n = d->bank_n;
for (i=1;i rect_n)
break;
--sprite;
if (texid != rect[sprite].texid)
break;
}
struct draw_element *e = &d->data[d->n++];
e->base = base;
e->n = i;
e->material = 0;
e->texture = texid;
return i;
}
static int
ldrawmgr_append(lua_State *L) {
struct drawmgr * d = (struct drawmgr *)luaL_checkudata(L, 1, "SOLUNA_DRAWMGR");
struct draw_primitive *prim = (struct draw_primitive *)lua_touserdata(L, 2);
int prim_n = luaL_checkinteger(L, 3);
struct sprite_rect * rect = d->bank->rect;
int rect_n = d->bank_n;
int i;
struct draw_primitive *end_ptr = &prim[prim_n];
for (i=0;isprite;
if (d->n >= d->cap) {
return luaL_error(L, "Too many draw");
}
if (index <= 0) {
if (i == prim_n || index == 0) {
return luaL_error(L, "Invalid batch stream");
}
struct draw_primitive_external * ext = (struct draw_primitive_external *)&prim[i+1];
int sprite = ext->sprite;
int texid = -1;
if (sprite >= 0) {
texid = rect[sprite].texid;
}
i += append_external_material(d, p, (end_ptr - p)/2, index, texid) * 2;
} else {
--index;
if (index >= rect_n)
return luaL_error(L, "Invalid sprite id %d", index);
int texid = rect[index].texid;
i += append_default_material(d, p, end_ptr - p, texid);
}
}
return 0;
}
static int
ldrawmgr_new(lua_State *L) {
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
void * bank = lua_touserdata(L, 1);
int cap = luaL_checkinteger(L, 2);
struct drawmgr * d = (struct drawmgr *)lua_newuserdatauv(L, sizeof(*d) + (cap-1)*sizeof(d->data[0]), 0);
d->bank = (struct sprite_bank *)bank;
d->cap = cap;
d->n = 0;
if (luaL_newmetatable(L, "SOLUNA_DRAWMGR")) {
luaL_Reg l[] = {
{ "__index", NULL },
{ "__len", ldrawmgr_len },
{ "__call", ldrawmgr_index },
{ "reset", ldrawmgr_reset },
{ "append", ldrawmgr_append },
{ NULL, NULL },
};
luaL_setfuncs(L, l, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
}
lua_setmetatable(L, -2);
return 1;
}
int
luaopen_drawmgr(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "new", ldrawmgr_new },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: src/embedlua.c
================================================
#include "bootstrap.lua.h"
#include "service.lua.h"
#include "log.lua.h"
#include "timer.lua.h"
#include "root.lua.h"
#include "main.lua.h"
#include "start.lua.h"
#include "print_r.lua.h"
#include "loader.lua.h"
#include "spritebundle.lua.h"
#include "render.lua.h"
#include "settingdefault.dl.h"
#include "settings.lua.h"
#include "initsetting.lua.h"
#include "fontmgr.lua.h"
#include "gamepad.lua.h"
#include "soluna.lua.h"
#include "icon.lua.h"
#include "layout.lua.h"
#include "text.lua.h"
#include "util.lua.h"
#include "coroutine.lua.h"
#include "packageloader.lua.h"
#include "audio.lua.h"
#include "matdefault.lua.h"
#include "mattext.lua.h"
#include "matquad.lua.h"
#include "matmask.lua.h"
#include "lua.h"
#include "lauxlib.h"
#define REG_SOURCE(name) \
lua_pushlightuserdata(L, (void *)luasrc_##name); \
lua_pushinteger(L, sizeof(luasrc_##name) - 1); \
lua_pushcclosure(L, get_string, 2); \
lua_setfield(L, -2, #name);
#define REG_MATERIAL(name) \
lua_pushstring(L, #name); \
lua_rawseti(L, -2, luaL_len(L, -2) + 1); \
REG_SOURCE(name)
#define REG_DATALIST(name) \
lua_pushlightuserdata(L, (void *)dl_##name); \
lua_pushinteger(L, sizeof(dl_##name) - 1); \
lua_pushcclosure(L, get_stringloader, 2); \
lua_setfield(L, -2, #name);
static int
get_string(lua_State *L) {
const char * s = (const char *)lua_touserdata(L, lua_upvalueindex(1));
size_t sz = (size_t)lua_tointeger(L, lua_upvalueindex(2));
lua_pushexternalstring(L, s, sz, NULL, NULL);
return 1;
}
static int
get_stringloader(lua_State *L) {
lua_pushvalue(L, lua_upvalueindex(1));
lua_pushvalue(L, lua_upvalueindex(2));
return 2;
}
int
luaopen_embedsource(lua_State *L) {
lua_newtable(L);
lua_newtable(L); // runtime
REG_SOURCE(bootstrap)
REG_SOURCE(service)
REG_SOURCE(main)
REG_SOURCE(print_r)
REG_SOURCE(fontmgr)
REG_SOURCE(packageloader)
lua_setfield(L, -2, "runtime");
lua_newtable(L); // runtime
REG_SOURCE(spritebundle)
REG_SOURCE(icon)
REG_SOURCE(layout)
REG_SOURCE(text)
REG_SOURCE(soluna)
REG_SOURCE(util)
REG_SOURCE(coroutine)
REG_SOURCE(initsetting)
lua_setfield(L, -2, "lib");
lua_newtable(L); // service
REG_SOURCE(log)
REG_SOURCE(root)
REG_SOURCE(timer)
REG_SOURCE(start)
REG_SOURCE(loader)
REG_SOURCE(render)
REG_SOURCE(gamepad)
REG_SOURCE(settings)
REG_SOURCE(audio)
lua_setfield(L, -2, "service");
lua_newtable(L); // material
REG_MATERIAL(matdefault)
REG_MATERIAL(mattext)
REG_MATERIAL(matquad)
REG_MATERIAL(matmask)
lua_setfield(L, -2, "material");
lua_newtable(L); // data list
REG_DATALIST(settingdefault)
lua_setfield(L, -2, "data");
return 1;
}
================================================
FILE: src/entry.c
================================================
#define SOKOL_IMPL
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
#define SOKOL_D3D11
#elif defined(__APPLE__)
#define SOKOL_METAL
#elif defined(__EMSCRIPTEN__)
#define SOKOL_WGPU
#elif defined(__linux__)
#define SOKOL_GLCORE
#else
#error Unsupport platform
#endif
#include "version.h"
#define FRAME_CALLBACK 1
#define CLEANUP_CALLBACK 2
#define EVENT_CALLBACK 3
#define CALLBACK_COUNT 3
#include "sokol/sokol_app.h"
#include "sokol/sokol_gfx.h"
#include "sokol/sokol_glue.h"
#include "sokol/sokol_log.h"
#include "sokol/sokol_args.h"
#include "loginfo.h"
#include "appevent.h"
#include "ime_state.h"
#if defined(__APPLE__)
#include "platform/macos/soluna_macos_ime.h"
#endif
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
#include "platform/windows/soluna_windows_ime.h"
#endif
#if defined(__linux__)
#include "platform/linux/soluna_linux_ime.h"
#endif
#if defined(__EMSCRIPTEN__)
#include "platform/wasm/soluna_wasm_ime.h"
#endif
static void app_event(const sapp_event* ev);
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
#define PLATFORM "windows"
#elif defined(__APPLE__)
#define PLATFORM "macos"
#elif defined(__EMSCRIPTEN__)
#define PLATFORM "wasm"
#elif defined(__linux__)
#define PLATFORM "linux"
#else
#define PLATFORM "unknown"
#endif
struct app_context {
lua_State *L;
lua_State *quitL;
int (*send_log)(void *ud, unsigned int id, void *data, uint32_t sz);
void *send_log_ud;
void *mqueue;
};
static struct app_context *CTX = NULL;
struct soluna_ime_rect_state g_soluna_ime_rect = { 0.0f, 0.0f, 0.0f, 0.0f, 0, false };
void soluna_emit_char(uint32_t codepoint, uint32_t modifiers, bool repeat);
struct soluna_message {
const char *type;
union {
int p[2];
uint64_t u64;
} v;
};
static inline struct soluna_message *
message_create(const char *type, int p1, int p2) {
struct soluna_message *msg = (struct soluna_message *)malloc(sizeof(*msg));
msg->type = type;
msg->v.p[0] = p1;
msg->v.p[1] = p2;
return msg;
}
static inline struct soluna_message *
message_create64(const char *type, uint64_t p) {
struct soluna_message *msg = (struct soluna_message *)malloc(sizeof(*msg));
msg->type = type;
msg->v.u64 = p;
return msg;
}
static inline void
message_release(struct soluna_message *msg) {
free(msg);
}
void
soluna_emit_char(uint32_t codepoint, uint32_t modifiers, bool repeat) {
sapp_event ev;
memset(&ev, 0, sizeof(ev));
ev.type = SAPP_EVENTTYPE_CHAR;
ev.frame_count = sapp_frame_count();
ev.char_code = codepoint;
ev.modifiers = modifiers;
ev.key_repeat = repeat;
app_event(&ev);
}
static int
lmessage_send(lua_State *L) {
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
luaL_checktype(L, 2, LUA_TLIGHTUSERDATA);
int (*send_message)(void *ud, void *p) = lua_touserdata(L, 1);
void *send_message_ud = lua_touserdata(L, 2);
const char * what = NULL;
if (lua_type(L, 3) == LUA_TSTRING) {
what = lua_tostring(L, 3);
} else {
luaL_checktype(L, 3, LUA_TLIGHTUSERDATA);
what = (const char *)lua_touserdata(L, 3);
}
int64_t p1 = luaL_optinteger(L, 4, 0);
struct soluna_message * msg = NULL;
if (lua_isnoneornil(L, 5)) {
msg = message_create64(what, p1);
} else {
int p2 = luaL_checkinteger(L, 5);
msg = message_create(what, (int)p1, p2);
}
int fail = send_message(send_message_ud, msg);
if (fail) {
message_release(msg);
}
lua_pushboolean(L, !fail);
return 1;
}
static int
lmessage_unpack(lua_State *L) {
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
struct soluna_message *m = (struct soluna_message *)lua_touserdata(L,1);
lua_pushstring(L, m->type);
lua_pushinteger(L, m->v.p[0]);
lua_pushinteger(L, m->v.p[1]);
lua_pushinteger(L, m->v.u64);
message_release(m);
return 4;
}
static void
request_app_quit(void) {
if (CTX) {
CTX->quitL = CTX->L;
CTX->L = NULL;
}
}
static int
lquit_signal(lua_State *L) {
request_app_quit();
return 0;
}
#if defined(__EMSCRIPTEN__)
EMSCRIPTEN_KEEPALIVE
void
soluna_runtime_quit(void) {
request_app_quit();
}
#endif
static int
levent_unpack(lua_State *L) {
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
struct event_message em;
app_event_unpack(&em, lua_touserdata(L, 1));
lua_pushlightuserdata(L, (void *)em.typestr);
lua_pushinteger(L, em.p1);
lua_pushinteger(L, em.p2);
lua_pushinteger(L, em.p3);
return 4;
}
static int
lset_window_title(lua_State *L) {
if (CTX == NULL || lua_type(L, 1) != LUA_TSTRING)
return 0;
const char * text = lua_tostring(L, 1);
sapp_set_window_title(text);
return 0;
}
struct icon_pixels {
uint8_t *ptr;
};
static void
icon_free_pixels(struct icon_pixels *payloads, int count) {
for (int i = 0; i < count; ++i) {
free(payloads[i].ptr);
}
}
static int
icon_get_int(lua_State *L, int index, const char *field, const char *fallback) {
int abs_index = lua_absindex(L, index);
int value = 0;
int type = lua_getfield(L, abs_index, field);
if (type == LUA_TNUMBER) {
value = (int)lua_tointeger(L, -1);
lua_pop(L, 1);
return value;
}
lua_pop(L, 1);
if (fallback) {
type = lua_getfield(L, abs_index, fallback);
if (type == LUA_TNUMBER) {
value = (int)lua_tointeger(L, -1);
lua_pop(L, 1);
return value;
}
lua_pop(L, 1);
}
luaL_error(L, "icon missing %s", field);
return 0;
}
static void
icon_copy_image(lua_State *L, int index, sapp_image_desc *dst, struct icon_pixels *payload) {
int abs_index = lua_absindex(L, index);
int width = icon_get_int(L, abs_index, "w", "width");
int height = icon_get_int(L, abs_index, "h", "height");
if (width <= 0 || height <= 0) {
luaL_error(L, "icon size (%d * %d) must be positive", width, height);
}
size_t stride = 0;
if (lua_getfield(L, abs_index, "stride") == LUA_TNUMBER) {
stride = (size_t)lua_tointeger(L, -1);
}
lua_pop(L, 1);
size_t explicit_size = 0;
if (lua_getfield(L, abs_index, "size") == LUA_TNUMBER) {
explicit_size = (size_t)lua_tointeger(L, -1);
}
lua_pop(L, 1);
int type = lua_getfield(L, abs_index, "data");
const uint8_t *src = NULL;
size_t src_size = 0;
if (type == LUA_TSTRING) {
src = (const uint8_t *)lua_tolstring(L, -1, &src_size);
} else if (type == LUA_TUSERDATA) {
src = (const uint8_t *)lua_touserdata(L, -1);
src_size = lua_rawlen(L, -1);
} else if (type == LUA_TLIGHTUSERDATA) {
src = (const uint8_t *)lua_touserdata(L, -1);
src_size = explicit_size;
} else {
lua_pop(L, 1);
luaL_error(L, "icon.data must be buffer");
}
lua_pop(L, 1);
if (src == NULL) {
luaL_error(L, "icon data missing");
}
size_t row_bytes = (size_t)width * 4;
if (stride == 0) {
stride = row_bytes;
}
if (stride < row_bytes) {
luaL_error(L, "icon stride < width");
}
if (!(type == LUA_TLIGHTUSERDATA && explicit_size == 0)) {
size_t required = stride * (size_t)height;
if (src_size < required) {
luaL_error(L, "icon buffer too small");
}
}
size_t copy_size = row_bytes * (size_t)height;
uint8_t *copy = (uint8_t *)malloc(copy_size);
if (copy == NULL) {
luaL_error(L, "icon alloc fail");
}
if (stride == row_bytes) {
memcpy(copy, src, copy_size);
} else {
const uint8_t *s = src;
uint8_t *d = copy;
int y;
for (y = 0; y < height; ++y) {
memcpy(d, s, row_bytes);
s += stride;
d += row_bytes;
}
}
dst->width = width;
dst->height = height;
dst->pixels.ptr = copy;
dst->pixels.size = copy_size;
payload->ptr = copy;
}
static float
get_field_float(lua_State *L, int index, const char *field) {
lua_getfield(L, index, field);
if (lua_getfield(L, index, field) != LUA_TNUMBER) {
return luaL_error(L, "Invalid .%s type (%s is not a number)",
field, lua_typename(L, lua_type(L, -1)));
}
float value = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
return value;
}
static int
lset_icon(lua_State *L) {
if (lua_isnoneornil(L, 1))
return 0;
luaL_checktype(L, 1, LUA_TTABLE);
sapp_icon_desc desc;
memset(&desc, 0, sizeof(desc));
struct icon_pixels payloads[SAPP_MAX_ICONIMAGES];
memset(payloads, 0, sizeof(payloads));
int count = 0;
int abs_index = lua_absindex(L, 1);
if (lua_getfield(L, abs_index, "data") != LUA_TNIL) {
lua_pop(L, 1);
icon_copy_image(L, abs_index, &desc.images[count], &payloads[count]);
++count;
} else {
lua_pop(L, 1);
int len = (int)lua_rawlen(L, abs_index);
for (int i = 1; i <= len; ++i) {
if (count >= SAPP_MAX_ICONIMAGES) {
icon_free_pixels(payloads, count);
luaL_error(L, "too many icon images");
}
lua_rawgeti(L, abs_index, i);
luaL_checktype(L, -1, LUA_TTABLE);
icon_copy_image(L, -1, &desc.images[count], &payloads[count]);
++count;
lua_pop(L, 1);
}
}
if (count > 0) {
sapp_set_icon(&desc);
}
icon_free_pixels(payloads, count);
return 0;
}
static int
lset_ime_rect(lua_State *L) {
#if defined(__APPLE__) || defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__) || defined(__linux__) || defined(__EMSCRIPTEN__)
if (lua_isnoneornil(L, 1)) {
g_soluna_ime_rect.text_color = 0;
g_soluna_ime_rect.valid = false;
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
soluna_win32_apply_ime_rect();
#elif defined(__APPLE__)
soluna_macos_hide_ime_label();
#elif defined(__linux__)
soluna_linux_on_rect_cleared();
#elif defined(__EMSCRIPTEN__)
soluna_wasm_hide();
#endif
return 0;
}
luaL_checktype(L, 1, LUA_TTABLE);
g_soluna_ime_rect.x = get_field_float(L, 1, "x");
g_soluna_ime_rect.y = get_field_float(L, 1, "y");
g_soluna_ime_rect.w = get_field_float(L, 1, "width");
g_soluna_ime_rect.h = get_field_float(L, 1, "height");
if (lua_getfield(L, 1, "text_color") == LUA_TNIL) {
g_soluna_ime_rect.text_color = 0;
} else {
uint32_t color = (uint32_t)luaL_checkinteger(L, -1);
if ((color & 0xff000000) == 0) {
color |= 0xff000000;
}
g_soluna_ime_rect.text_color = color;
}
lua_pop(L, 1);
g_soluna_ime_rect.valid = true;
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
soluna_win32_apply_ime_rect();
#elif defined(__APPLE__)
soluna_macos_apply_ime_rect();
#elif defined(__linux__)
soluna_linux_update_spot();
#elif defined(__EMSCRIPTEN__)
soluna_wasm_apply_rect();
#endif
#endif
return 0;
}
static int
lset_ime_font(lua_State *L) {
const char *name = NULL;
float size = 0.0f;
int top = lua_gettop(L);
if (top == 0) {
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
soluna_win32_reset_ime_font();
#endif
#if defined(__APPLE__)
soluna_macos_set_ime_font(NULL, 0.0f);
#endif
#if defined(__EMSCRIPTEN__)
soluna_wasm_set_font(NULL, 0.0f);
#endif
return 0;
}
if (top == 1) {
size = (float)luaL_checknumber(L, 1);
} else {
if (!lua_isnoneornil(L, 1)) {
if (lua_type(L, 1) != LUA_TSTRING) {
return luaL_error(L, "set_ime_font expects string font name");
}
name = lua_tostring(L, 1);
}
size = (float)luaL_checknumber(L, 2);
}
if (size < 0.0f) {
size = 0.0f;
}
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
soluna_win32_set_ime_font(name, size);
#endif
#if defined(__APPLE__)
soluna_macos_set_ime_font(name, size);
#endif
#if defined(__EMSCRIPTEN__)
soluna_wasm_set_font(name, size);
#endif
return 0;
}
static int
lclose_window(lua_State *L) {
sapp_quit();
return 0;
}
static int
lmqueue(lua_State *L) {
if (CTX == NULL || CTX->mqueue == NULL) {
return luaL_error(L, "Not init mqueue");
}
lua_pushlightuserdata(L, CTX->mqueue);
return 1;
}
static int
lversion(lua_State *L) {
lua_pushinteger(L, SOLUNA_API_VERSION);
lua_pushstring(L, SOLUNA_HASH_VERSION);
return 2;
}
static void
desc_get_boolean(lua_State *L, bool *r, int index, const char * key) {
if (lua_getfield(L, index, key) == LUA_TBOOLEAN) {
*r = lua_toboolean(L, -1);
} else {
luaL_checktype(L, -1, LUA_TNIL);
}
lua_pop(L, 1);
}
static void
desc_get_int(lua_State *L, int *r, int index, const char * key) {
if (lua_getfield(L, index, key) == LUA_TNUMBER) {
*r = lua_tointeger(L, -1);
} else {
luaL_checktype(L, -1, LUA_TNIL);
}
lua_pop(L, 1);
}
static void
desc_get_string(lua_State *L, const char **r, int index, const char * key) {
if (lua_getfield(L, index, key) == LUA_TSTRING) {
*r = lua_tostring(L, -1);
} else {
luaL_checktype(L, -1, LUA_TNIL);
}
lua_pop(L, 1);
}
static int
linit_desc(lua_State *L) {
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
luaL_checktype(L, 2, LUA_TTABLE);
sapp_desc *d = lua_touserdata(L, 1);
desc_get_boolean(L, &d->high_dpi, 2, "high_dpi");
desc_get_boolean(L, &d->fullscreen, 2, "fullscreen");
desc_get_int(L, &d->width, 2, "width");
desc_get_int(L, &d->height, 2, "height");
desc_get_string(L, &d->window_title, 2, "window_title");
return 0;
}
int
luaopen_soluna_app(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "mqueue", lmqueue },
{ "unpackmessage", lmessage_unpack },
{ "sendmessage", lmessage_send },
{ "unpackevent", levent_unpack },
{ "set_window_title", lset_window_title },
{ "set_icon", lset_icon },
{ "set_ime_rect", lset_ime_rect },
{ "set_ime_font", lset_ime_font },
{ "quit", lquit_signal },
{ "close_window", lclose_window },
{ "platform", NULL },
{ "version", lversion },
{ "init_desc", linit_desc },
{ NULL, NULL },
};
luaL_newlib(L, l);
lua_pushliteral(L, PLATFORM);
lua_setfield(L, -2, "platform");
return 1;
}
static void
log_func(const char* tag, uint32_t log_level, uint32_t log_item, const char* message, uint32_t line_nr, const char* filename, void* user_data) {
if (CTX == NULL || CTX->send_log == NULL) {
fprintf(stderr, "%s (%d) : %s\n", filename, line_nr, message);
return;
}
struct log_info *msg = (struct log_info *)malloc(sizeof(*msg));
if (tag) {
strncpy(msg->tag, tag, sizeof(msg->tag));
msg->tag[sizeof(msg->tag)-1] = 0;
} else {
msg->tag[0] = 0;
}
msg->log_level = log_level;
msg->log_item = log_item;
msg->line_nr = line_nr;
if (message) {
strncpy(msg->message, message, sizeof(msg->message));
msg->message[sizeof(msg->message)-1] = 0;
} else {
msg->message[0] = 0;
}
msg->filename = filename;
CTX->send_log(CTX->send_log_ud, 0, msg, sizeof(*msg));
}
void soluna_openlibs(lua_State *L);
static const char *code = "local embed = require 'soluna.embedsource' ; local f = load(embed.runtime.main()) ; return f(...)";
static int
pmain(lua_State *L) {
char** argv = (char **)lua_touserdata(L, 1);
soluna_openlibs(L);
int n = sargs_num_args();
luaL_checkstack(L, n+1, NULL);
int i;
lua_newtable(L);
int arg_table = lua_gettop(L);
for (i=0;isend_log = get_ud(L, "send_log");
ctx->send_log_ud = get_ud(L, "send_log_ud");
ctx->mqueue = get_ud(L, "mqueue");
if (get_function(L, "frame", FRAME_CALLBACK))
return 1;
if (get_function(L, "cleanup", CLEANUP_CALLBACK))
return 1;
if (get_function(L, "event", EVENT_CALLBACK))
return 1;
lua_settop(L, CALLBACK_COUNT);
return 0;
}
static int
msghandler(lua_State *L) {
const char *msg = lua_tostring(L, 1);
luaL_traceback(L, L, msg, 1);
return 1;
}
static void
get_app_info(lua_State *L) {
lua_newtable(L);
const float dpi_scale = sapp_dpi_scale();
const float safe_scale = dpi_scale > 0.0f ? dpi_scale : 1.0f;
const int fb_width = sapp_width();
const int fb_height = sapp_height();
const int logical_width = (int)((float)fb_width / safe_scale + 0.5f);
const int logical_height = (int)((float)fb_height / safe_scale + 0.5f);
lua_pushinteger(L, logical_width);
lua_setfield(L, -2, "width");
lua_pushinteger(L, logical_height);
lua_setfield(L, -2, "height");
}
static int
start_app(lua_State *L) {
if (L == NULL) {
fprintf(stderr, "Can't open lua state\n");
return 1;
}
if (lua_islightuserdata(L, 1)) {
fprintf(stderr, "Init fatal : %s\n", (const char *)lua_touserdata(L, 1));
return 1;
}
if (lua_gettop(L) != 2) {
fprintf(stderr, "Invalid lua stack (top = %d)\n", lua_gettop(L));
return 1;
}
if (lua_getfield(L, -1, "start") != LUA_TFUNCTION) {
fprintf(stderr, "No start function\n");
return 1;
}
lua_replace(L, -2);
get_app_info(L);
if (lua_pcall(L, 1, 1, 1) != LUA_OK) {
fprintf(stderr, "Start fatal : %s\n", lua_tostring(L, -1));
return 1;
} else {
return init_callback(L, CTX);
}
}
static void
app_init() {
#if defined(__APPLE__)
soluna_macos_install_ime();
#endif
#if defined(__linux__)
soluna_linux_ensure_im();
#endif
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
soluna_win32_install_wndproc();
#endif
sg_setup(&(sg_desc) {
.environment = sglue_environment(),
.logger.func = log_func,
});
lua_State *L = CTX->L;
if (start_app(L)) {
if (L) {
lua_close(L);
}
CTX->L = NULL;
CTX->quitL = NULL;
sapp_quit();
}
}
static lua_State *
get_L(struct app_context *ctx) {
if (ctx == NULL)
return NULL;
lua_State *L = ctx->L;
if (L == NULL) {
if (ctx->quitL != NULL) {
sapp_quit();
}
}
return L;
}
static void
invoke_callback(lua_State *L, int index, int nargs) {
lua_pushvalue(L, index);
if (nargs > 0) {
lua_insert(L, -nargs-1);
}
if (lua_pcall(L, nargs, 0, 0) != LUA_OK) {
fprintf(stderr, "Error : %s\n", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static void
app_frame() {
lua_State *L = get_L(CTX);
if (L) {
lua_pushinteger(L, sapp_frame_count());
invoke_callback(L, FRAME_CALLBACK, 1);
}
}
static void
app_cleanup() {
if (CTX == NULL)
return;
lua_State *L = CTX->quitL;
if (L == NULL) {
L = CTX->L;
CTX->L = NULL;
}
if (L) {
invoke_callback(L, CLEANUP_CALLBACK, 0);
lua_close(L);
CTX->quitL = NULL;
}
#if defined(__linux__)
soluna_linux_shutdown_ime();
#endif
#if defined(__EMSCRIPTEN__)
soluna_wasm_hide();
#endif
sg_shutdown();
}
static void
app_event(const sapp_event* ev) {
#if defined(__APPLE__)
if (soluna_macos_is_composition_active() &&
(ev->type == SAPP_EVENTTYPE_KEY_DOWN ||
ev->type == SAPP_EVENTTYPE_KEY_UP)) {
return;
}
#endif
#if defined(__EMSCRIPTEN__)
if (soluna_wasm_should_block_key_event(ev)) {
return;
}
#endif
#if defined(__linux__)
if (soluna_linux_should_skip_event(ev)) {
return;
}
#endif
#if defined(__EMSCRIPTEN__)
if (soluna_wasm_filter_char_event(ev)) {
return;
}
#endif
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
if (ev->type == SAPP_EVENTTYPE_FOCUSED && g_soluna_ime_rect.valid) {
soluna_win32_apply_ime_rect();
}
#endif
#if defined(__linux__)
soluna_linux_handle_event(ev);
#endif
#if defined(__EMSCRIPTEN__)
soluna_wasm_handle_event(ev);
#endif
lua_State *L = get_L(CTX);
if (L) {
lua_pushlightuserdata(L, (void *)ev);
invoke_callback(L, EVENT_CALLBACK, 1);
}
}
static int
init_settings(lua_State *L, sapp_desc *desc) {
if (lua_gettop(L) != 2) {
lua_pushlightuserdata(L, (void *)"Invalid lua stack");
return 1;
}
if (lua_getfield(L, -1, "init") != LUA_TFUNCTION) {
lua_pushlightuserdata(L, (void *)"No start function");
return 1;
}
lua_pushlightuserdata(L, (void *)desc);
if (lua_pcall(L, 1, 0, 1) != LUA_OK) {
const char * err = lua_tostring(L, -1);
lua_pushlightuserdata(L, (void *)err);
return 1;
} else {
return 0;
}
}
sapp_desc
sokol_main(int argc, char* argv[]) {
// init sargs
sargs_desc arg_desc;
memset(&arg_desc, 0, sizeof(arg_desc));
arg_desc.argc = argc;
arg_desc.argv = argv;
sargs_setup(&arg_desc);
// default sapp_desc
sapp_desc d;
memset(&d, 0, sizeof(d));
d.init_cb = app_init;
d.frame_cb = app_frame;
d.cleanup_cb = app_cleanup;
d.event_cb = app_event;
d.logger.func = log_func;
d.win32.console_utf8 = 1;
d.win32.console_attach = 1;
d.alpha = 0;
// init L
static struct app_context app;
lua_State *L = luaL_newstate();
if (L) {
lua_settop(L, 0);
lua_pushcfunction(L, msghandler);
lua_pushcfunction(L, pmain);
lua_pushlightuserdata(L, (void *)argv);
if (lua_pcall(L, 1, 1, 1) != LUA_OK) {
const char * err = lua_tostring(L, -1);
lua_pushlightuserdata(L, (void *)err);
lua_replace(L, 1);
}
sargs_shutdown();
if (init_settings(L, &d)) {
lua_replace(L, 1);
}
}
app.L = L;
app.quitL = NULL;
app.send_log = NULL;
app.send_log_ud = NULL;
app.mqueue = NULL;
CTX = &app;
return d;
}
================================================
FILE: src/extapi.c
================================================
#include
#include
#include
#include
#include
#include "sokol/sokol_gfx.h"
#include "batch.h"
#include "spritemgr.h"
#include "render_bindings.h"
#include "extapi_types.h"
#define STREAM_FIX_SCALE 256.0f
#define STREAM_FIX_INV_SCALE (1.0f / STREAM_FIX_SCALE)
#define STREAM_ERROR_SIZE 128
#if defined(_MSC_VER)
#define SOLUNA_THREAD_LOCAL __declspec(thread)
#elif defined(__GNUC__)
#define SOLUNA_THREAD_LOCAL __thread
#else
#define SOLUNA_THREAD_LOCAL _Thread_local
#endif
typedef void (*material_submit_stride_func)(void *ud, struct soluna_material_stream_context ctx, int n);
struct material_stream_context_impl {
const char *data;
int n;
int material_id;
soluna_material_error error;
char error_buffer[STREAM_ERROR_SIZE];
};
static SOLUNA_THREAD_LOCAL char submit_error_buffer[STREAM_ERROR_SIZE];
static struct material_stream_context_impl *
stream_context(struct soluna_material_stream_context ctx) {
return (struct material_stream_context_impl *)ctx.ctx;
}
static soluna_material_error
copy_error(char *buffer, size_t size, const char *error) {
const char *message = error != NULL ? error : "Material stream error";
size_t len = strlen(message);
if (len >= size) {
len = size - 1;
}
memcpy(buffer, message, len);
buffer[len] = '\0';
return buffer;
}
void
material_stream_error(struct soluna_material_stream_context ctx_, const char *error) {
struct material_stream_context_impl *ctx = stream_context(ctx_);
if (ctx != NULL && ctx->error == NULL) {
ctx->error = copy_error(ctx->error_buffer, sizeof(ctx->error_buffer), error);
}
}
int
material_stream_failed(struct soluna_material_stream_context ctx_) {
struct material_stream_context_impl *ctx = stream_context(ctx_);
return ctx == NULL || ctx->error != NULL;
}
static soluna_material_error
submit_material_stride(const void *data_, int prim_n, int material_id, int batch_n, void *ud, material_submit_stride_func submit, size_t stride) {
const char *data = (const char *)data_;
if (data == NULL) {
return "Missing material stream";
}
if (material_id <= 0) {
return "Invalid material id";
}
if (batch_n <= 0) {
return "Invalid material submit batch";
}
if (prim_n < 0) {
return "Invalid material primitive count";
}
if (submit == NULL) {
return "Missing material submit function";
}
if (stride == 0) {
return "Invalid material submit stride";
}
int i = 0;
for (;;) {
int n = prim_n - i;
struct material_stream_context_impl impl = {
.data = data,
.n = n > batch_n ? batch_n : n,
.material_id = material_id,
.error = NULL,
.error_buffer = { 0 },
};
struct soluna_material_stream_context ctx = {
.ctx = &impl,
};
if (n > batch_n) {
submit(ud, ctx, batch_n);
if (impl.error != NULL) {
return copy_error(submit_error_buffer, sizeof(submit_error_buffer), impl.error);
}
i += batch_n;
data += stride * batch_n;
} else {
submit(ud, ctx, n);
if (impl.error != NULL) {
return copy_error(submit_error_buffer, sizeof(submit_error_buffer), impl.error);
}
break;
}
}
return NULL;
}
soluna_material_error
material_submit(const void *stream, int prim_n, int material_id, int batch_n, void *ud, soluna_material_submit_func submit) {
return submit_material_stride(stream, prim_n, material_id, batch_n, ud, submit, sizeof(struct draw_primitive) * 2);
}
int
material_sprite_rect(struct soluna_sprite_bank bank, int sprite, struct soluna_sprite_rect *out) {
struct sprite_bank *b = (struct sprite_bank *)bank.ctx;
if (b == NULL || out == NULL || sprite < 0 || sprite >= b->n) {
return 0;
}
struct sprite_rect *r = &b->rect[sprite];
out->texture = r->texid;
out->u = (float)(r->u >> 16);
out->v = (float)(r->v >> 16);
out->w = (float)(r->u & 0xffffu);
out->h = (float)(r->v & 0xffffu);
out->ox = (float)((r->off >> 16) & 0xffffu) - 0x8000;
out->oy = (float)(r->off & 0xffffu) - 0x8000;
return 1;
}
sg_bindings
material_bindings(struct soluna_render_bindings bindings) {
struct render_bindings *b = (struct render_bindings *)bindings.ctx;
assert(b != NULL);
return b->bindings;
}
static size_t
stream_payload_max(void) {
return sizeof(struct draw_primitive) - sizeof(struct draw_primitive_external);
}
void
material_stream_free(void *ptr) {
free(ptr);
}
soluna_material_error
material_push_stream(int material_id, int count, size_t payload_size, soluna_material_stream_write_func write, void *ud, struct soluna_material_stream *out) {
size_t payload_max = stream_payload_max();
if (out == NULL) {
return "Missing material stream output";
}
out->data = NULL;
out->size = 0;
if (material_id <= 0) {
return "Invalid material id";
}
if (count < 0) {
return "Invalid material stream count";
}
if (payload_size > payload_max) {
return "Invalid material payload size";
}
if (write == NULL) {
return "Missing material stream writer";
}
size_t item_size = sizeof(struct draw_primitive) * 2;
if ((size_t)count > (~(size_t)0 - 1) / item_size) {
return "Material stream is too large";
}
size_t stream_size = item_size * (size_t)count;
char *buffer = (char *)malloc(stream_size + 1);
if (buffer == NULL) {
return "No memory for material stream";
}
struct draw_primitive *stream = (struct draw_primitive *)buffer;
int i;
for (i=0; i 0) {
if (item.payload == NULL) {
material_stream_free(buffer);
return "Missing material stream payload";
}
memcpy((char *)ext_prim + sizeof(*ext), item.payload, payload_size);
}
pos->x = (int32_t)(item.x * STREAM_FIX_SCALE);
pos->y = (int32_t)(item.y * STREAM_FIX_SCALE);
pos->sprite = -material_id;
ext->sprite = item.sprite;
}
buffer[stream_size] = '\0';
out->data = buffer;
out->size = stream_size;
return NULL;
}
static void
clear_stream_read(size_t payload_size, void *payload, struct soluna_material_stream_data *out) {
if (out != NULL) {
memset(out, 0, sizeof(*out));
}
if (payload != NULL && payload_size > 0 && payload_size <= stream_payload_max()) {
memset(payload, 0, payload_size);
}
}
static int
fail_stream_read(struct soluna_material_stream_context ctx, const char *error, size_t payload_size, void *payload, struct soluna_material_stream_data *out) {
material_stream_error(ctx, error);
clear_stream_read(payload_size, payload, out);
return 0;
}
int
material_stream_read(struct soluna_material_stream_context ctx_, int index, size_t payload_size, void *payload, struct soluna_material_stream_data *out) {
struct material_stream_context_impl *ctx = stream_context(ctx_);
size_t payload_max = stream_payload_max();
if (ctx == NULL) {
clear_stream_read(payload_size, payload, out);
return 0;
}
if (ctx->error != NULL) {
clear_stream_read(payload_size, payload, out);
return 0;
}
if (payload_size > payload_max) {
return fail_stream_read(ctx_, "Invalid material payload size", payload_size, payload, out);
}
if (ctx->data == NULL) {
return fail_stream_read(ctx_, "Missing material stream", payload_size, payload, out);
}
if (index < 0) {
return fail_stream_read(ctx_, "Invalid material stream index", payload_size, payload, out);
}
if (index >= ctx->n) {
return fail_stream_read(ctx_, "Invalid material stream index", payload_size, payload, out);
}
if (ctx->material_id <= 0) {
return fail_stream_read(ctx_, "Invalid material id", payload_size, payload, out);
}
if (out == NULL) {
return fail_stream_read(ctx_, "Missing material stream output", payload_size, payload, out);
}
if (payload_size > 0 && payload == NULL) {
return fail_stream_read(ctx_, "Missing material stream payload output", payload_size, payload, out);
}
const struct draw_primitive *prim = (const struct draw_primitive *)ctx->data;
const struct draw_primitive *pos = &prim[index * 2];
const struct draw_primitive *ext_prim = pos + 1;
const struct draw_primitive_external *ext = (const struct draw_primitive_external *)ext_prim;
if (pos->sprite != -ctx->material_id) {
return fail_stream_read(ctx_, "Invalid material marker", payload_size, payload, out);
}
out->x = (float)pos->x * STREAM_FIX_INV_SCALE;
out->y = (float)pos->y * STREAM_FIX_INV_SCALE;
out->sprite = ext->sprite;
if (payload_size > 0) {
memcpy(payload, (const char *)ext_prim + sizeof(*ext), payload_size);
}
return 1;
}
================================================
FILE: src/extapi_types.h
================================================
// AUTO GENERATED by ../src/extapi_types.temp.h, DONT EDIT
#ifndef SOLUNA_EXTAPI_TYPES_H
#define SOLUNA_EXTAPI_TYPES_H
#include
#include "sokol/sokol_gfx.h"
#define SOLUNA_EXT_API_VERSION 1
struct soluna_sprite_rect {
int texture;
float u;
float v;
float w;
float h;
float ox;
float oy;
};
struct soluna_material_stream_item {
float x;
float y;
int sprite;
const void *payload;
};
struct soluna_material_stream_data {
float x;
float y;
int sprite;
};
struct soluna_material_stream {
char *data;
size_t size;
};
typedef const char *soluna_material_error;
struct soluna_material_stream_context {
void *ctx;
};
struct soluna_render_bindings {
void *ctx;
};
struct soluna_sprite_bank {
void *ctx;
};
typedef void (*soluna_material_submit_func)(void *ud, struct soluna_material_stream_context ctx, int n);
typedef void (*soluna_material_stream_write_func)(void *ud, int index, struct soluna_material_stream_item *item);
#endif
================================================
FILE: src/extapi_types.temp.h
================================================
#ifndef SOLUNA_EXTAPI_TYPES_H
#define SOLUNA_EXTAPI_TYPES_H
$HOST_TYPE_DECL$
#endif
================================================
FILE: src/external.c
================================================
#include
#include
#include
#include
struct lua_api;
struct sokol_api;
struct soluna_api;
extern struct lua_api * extlua_api();
extern struct sokol_api * extlua_sokol_api();
extern struct soluna_api * extlua_soluna_api();
struct extlua_apis {
struct lua_api * lua;
struct sokol_api * sokol;
struct soluna_api * soluna;
};
static struct extlua_apis *
host_apis() {
static struct extlua_apis apis;
apis.lua = extlua_api();
apis.sokol = extlua_sokol_api();
apis.soluna = extlua_soluna_api();
return &apis;
}
static void
init_extraspace(lua_State *L) {
struct extlua_apis **ex = (struct extlua_apis **)lua_getextraspace(L);
*ex = host_apis();
}
static int
get_reg(lua_State *L) {
luaL_Reg *l = (luaL_Reg *)lua_touserdata(L, 1);
lua_pushnil(L);
int i = 0;
while (lua_next(L, 2) != 0) {
l[i].name = lua_tostring(L, -2);
l[i].func = lua_tocfunction(L, -1);
if (l[i].name == NULL || l[i].func == NULL) {
return luaL_error(L, "Invalid reg table");
}
lua_pop(L, 1);
++i;
}
return 0;
}
static int
count_table(lua_State *L, int idx) {
idx = lua_absindex(L, idx);
lua_pushnil(L);
int n = 0;
while (lua_next(L, idx) != 0) {
++n;
lua_pop(L, 1);
}
return n;
}
static void
register_libs(lua_State *L, lua_State *dL, int loadonly) {
if (lua_gettop(dL) == 0 || lua_type(dL, 1) != LUA_TTABLE) {
luaL_error(L, "Invalid external libs, maybe lua version mismatch");
}
int n = count_table(dL, 1);
int tbl_index = lua_gettop(L);
luaL_Reg *l = lua_newuserdatauv(L, sizeof(luaL_Reg) * n, 0);
lua_pushcfunction(dL, get_reg);
lua_pushlightuserdata(dL, (void *)l);
lua_pushvalue(dL, 1);
if (lua_pcall(dL, 2, 0, 0) != LUA_OK) {
lua_pushstring(L, lua_tostring(dL, -1));
lua_error(L);
}
int i;
if (loadonly) {
for (i=0;iname, rb->name);
}
static int
preload_libs(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
if (PRELOAD.l != NULL) {
return luaL_error(L, "Already preload");
}
lua_newtable(L);
int result_index = lua_gettop(L);
int i=0;
while (lua_geti(L, 1, ++i) != LUA_TNIL) {
lua_CFunction init = lua_tocfunction(L, -1);
if (init == NULL)
return luaL_error(L, "Invalid init function at %d", i);
lua_pop(L, 1);
preload_lib(L, init, result_index);
}
lua_pop(L, 1);
int n = count_table(L, result_index);
struct luaL_Reg *l = lua_newuserdatauv(L, sizeof(luaL_Reg) * n, 1);
// ref name strings
lua_pushvalue(L, result_index);
lua_setiuservalue(L, -2, 1);
lua_pushcfunction(L, get_reg);
lua_pushvalue(L, -2); // l
lua_pushvalue(L, result_index);
lua_call(L, 2, 0);
qsort(l, n, sizeof(luaL_Reg), cmpreg);
PRELOAD.l = l;
PRELOAD.n = n;
lua_setfield(L, LUA_REGISTRYINDEX, "EXTLIBS");
return 1;
}
static lua_CFunction
find_func(luaL_Reg *l, int n, const char *name) {
int begin = 0;
int end = n;
while (begin < end) {
int mid = (begin + end) / 2;
int c = strcmp(name, l[mid].name);
if (c == 0)
return l[mid].func;
else if (c < 0) {
end = mid;
} else {
begin = mid + 1;
}
}
return NULL;
}
static int
searcher(lua_State *L) {
if (PRELOAD.l == NULL || PRELOAD.n == 0)
return 0;
if (lua_gettop(L) == 0) {
// test preload table
lua_pushboolean(L, 1);
return 1;
}
const char * name = lua_tostring(L, 1);
lua_CFunction func = find_func(PRELOAD.l, PRELOAD.n, name);
if (func == NULL) {
lua_pushfstring(L, "No preload extlua '%s'", name);
return 1;
}
lua_pushcfunction(L, func);
return 1;
}
int
luaopen_extlua(lua_State *L) {
luaL_Reg l[] = {
{ "load", load_libs },
{ "preload", preload_libs },
{ "searcher", searcher },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: src/file.c
================================================
#include
#include
#include
#include
FILE * fopen_utf8(const char *filename, const char *mode);
static int
lfile_exist(lua_State *L) {
const char *filename = luaL_checkstring(L, 1);
const char *mode = "rb";
FILE *f = fopen_utf8(filename, mode);
if (f == NULL)
return 0;
fclose(f);
lua_pushboolean(L, 1);
return 1;
}
static void *
external_free(void *ud, void *ptr, size_t osize, size_t nsize) {
free(ptr);
return NULL;
}
static int
lfile_load(lua_State *L) {
const char *filename = luaL_checkstring(L, 1);
const char *mode = luaL_optstring(L, 2, "rb");
FILE *f = fopen_utf8(filename, mode);
if (f == NULL)
return 0;
fseek(f, 0, SEEK_END);
size_t sz = ftell(f);
fseek(f, 0, SEEK_SET);
char * buffer = (char *)malloc(sz+1);
if (buffer == NULL) {
fclose(f);
return luaL_error(L, "lfile_load_string : Out of memory");
}
buffer[sz] = 0;
size_t rd = fread(buffer, 1, sz, f);
fclose(f);
if (rd != sz) {
free(buffer);
return luaL_error(L, "Read %s fail", filename);
}
lua_pushexternalstring(L, buffer, sz, external_free, NULL);
return 1;
}
int
luaopen_soluna_file(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "exist", lfile_exist },
{ "load", lfile_load },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: src/font.c
================================================
#include "font_manager.h"
#include "truetype.h"
#include
#include
#include
#include
#include
#include
#include
#include "luabuffer.h"
#include "stb/stb_image_write.h"
static struct {
struct font_manager *mgr;
} G;
static struct font_manager*
getF(lua_State *L) {
if (G.mgr == NULL)
luaL_error(L, "Init font manager first");
return G.mgr;
}
static int
lsubmit(lua_State *L){
struct font_manager *F = getF(L);
int dirty = font_manager_flush(F);
lua_pushboolean(L, dirty);
return 1;
}
static int
limport(lua_State *L) {
struct font_manager *F = getF(L);
size_t sz;
void* fontdata = (void *)luaL_getbuffer(L, &sz);
font_manager_import(F, fontdata, sz);
return 0;
}
static int
lname(lua_State *L) {
struct font_manager *F = getF(L);
const char* family = luaL_checkstring(L, 1);
const int fontid = font_manager_addfont_with_family(F, family);
if (fontid > 0){
lua_pushinteger(L, fontid);
return 1;
}
return 0;
}
static int
ltexture(lua_State *L) {
struct font_manager *F = getF(L);
int size = 0;
const void * ptr = font_manager_texture(F, &size);
lua_pushlightuserdata(L, (void *)ptr);
lua_pushinteger(L, size * size);
return 2;
}
static int
ltexture_write(lua_State *L) {
struct font_manager *F = getF(L);
int size = 0;
const void * ptr = font_manager_texture(F, &size);
const char * filename = luaL_checkstring(L, 1);
if (!stbi_write_png(filename, size, size, 1, ptr, size)) {
return luaL_error(L, "Write %s failed", filename);
}
return 0;
}
static int
ltouch(lua_State *L) {
struct font_manager *F = getF(L);
int fontid = luaL_checkinteger(L, 1);
int codepoint = luaL_checkinteger(L, 2);
struct font_glyph tmp1, tmp2;
font_manager_glyph(F, fontid, codepoint, 16, &tmp1, &tmp2);
return 0;
}
static int
lcobj(lua_State *L) {
struct font_manager *F = getF(L);
lua_pushlightuserdata(L, (void *)F);
return 1;
}
static int
limport_icon(lua_State *L) {
struct font_manager *F = getF(L);
luaL_checktype(L, 1, LUA_TUSERDATA);
void *data = lua_touserdata(L, 1);
size_t sz = lua_rawlen(L, 1);
int n = sz / FONT_MANAGER_GLYPHSIZE / FONT_MANAGER_GLYPHSIZE;
if (n * FONT_MANAGER_GLYPHSIZE * FONT_MANAGER_GLYPHSIZE != sz)
return luaL_error(L, "Invalid icon bundle size");
font_manager_icon_init(F, n, data);
return 0;
}
static int
lsize(lua_State *L) {
struct font_manager *F = getF(L);
int font_id = luaL_checkinteger(L, 1);
if (font_id <= 0) {
return luaL_error(L, "Invalid font_id %d", font_id);
}
int size = luaL_checkinteger(L, 2);
int ascent, descent, lineGap;
font_manager_fontheight(F, font_id, size, &ascent, &descent, &lineGap);
if (!lua_istable(L, 3)) {
lua_settop(L, 2);
lua_createtable(L, 0, 3);
} else {
lua_settop(L, 3);
}
lua_pushinteger(L, ascent);
lua_setfield(L, 3, "ascent");
lua_pushinteger(L, descent);
lua_setfield(L, 3, "descent");
lua_pushinteger(L, lineGap);
lua_setfield(L, 3, "lineGap");
return 1;
}
#define MAX_FONTNAME 256
static int
llist(lua_State *L) {
struct font_manager *F = getF(L);
int i = 1;
lua_newtable(L);
char buf[MAX_FONTNAME];
for (;;) {
int r = font_manager_enum_fontname(F, i, buf, MAX_FONTNAME);
if (r <= 0)
break;
if (r >= MAX_FONTNAME) {
char *tmp = (char *)malloc(r + 1);
font_manager_enum_fontname(F, i, tmp, r+1);
lua_pushlstring(L, tmp, r);
free(tmp);
} else {
lua_pushlstring(L, buf, r);
}
lua_seti(L, -2, i);
++i;
}
return 1;
}
int
luaopen_font(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "texture", ltexture },
{ "texture_write", ltexture_write },
{ "touch", ltouch }, // for debug
{ "import", limport },
{ "name", lname },
{ "size", lsize },
{ "submit", lsubmit },
{ "cobj", lcobj },
{ "texture_size", NULL },
{ "import_icon", limport_icon },
{ "list", llist },
{ NULL, NULL },
};
luaL_newlib(L, l);
lua_pushinteger(L, FONT_MANAGER_TEXSIZE);
lua_setfield(L, -2, "texture_size");
return 1;
}
void soluna_openlibs(lua_State *L);
static int
luavm_init(lua_State *L) {
soluna_openlibs(L);
const char* data = (const char*)lua_touserdata(L, 1);
size_t size = (size_t)lua_tointeger(L, 2);
const char* chunkname = (const char*)lua_touserdata(L, 3);
if (luaL_loadbuffer(L, data, size, chunkname) != LUA_OK) {
return lua_error(L);
}
lua_call(L, 0, 0);
return 0;
}
static int
fontm_init(lua_State *L) {
if (G.mgr != NULL) {
return luaL_error(L, "Do not init font manager twice");
}
struct font_manager* F = (struct font_manager *)malloc(font_manager_sizeof());
if (F == NULL) {
return luaL_error(L, "not enough memory");
}
size_t sz;
const char * src = luaL_checklstring(L, 1, &sz);
lua_State* managerL = luaL_newstate();
if (!managerL) {
free(F);
return luaL_error(L, "not enough memory");
}
lua_pushcfunction(managerL, luavm_init);
lua_pushlightuserdata(managerL, (void *)src);
lua_pushinteger(managerL, sz);
lua_pushlightuserdata(managerL, (void*)luaL_checkstring(L, 2));
if (lua_pcall(managerL, 3, 0, 0) != LUA_OK) {
lua_pushstring(L, lua_tostring(managerL, -1));
lua_close(managerL);
free(F);
return lua_error(L);
}
font_manager_init(F, managerL);
G.mgr = F;
return 0;
}
static int
fontm_shutdown(lua_State *L) {
struct font_manager* F = G.mgr;
if (F == NULL)
return 0;
G.mgr = NULL;
void* managerL = font_manager_shutdown(F);
if (managerL) {
lua_close((lua_State*)managerL);
}
free(F);
return 0;
}
int
luaopen_font_manager(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "init", fontm_init },
{ "shutdown", fontm_shutdown },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: src/font_define.h
================================================
#ifndef soluna_font_define_h
#define soluna_font_define_h
#define FONT_MANAGER_TEXSIZE 2048
#define FONT_MANAGER_GLYPHSIZE 64
#define FONT_POSTION_FIX_POINT 8
#define MAX_FONT_NUM 64
#include
struct font_glyph {
int16_t offset_x;
int16_t offset_y;
int16_t advance_x;
int16_t advance_y;
uint16_t w;
uint16_t h;
uint16_t u;
uint16_t v;
};
#define IMAGE_FONT_MASK 0x40 //7 bit
#define FONT_ID_MASK 0x3F //low 6 bits
#define FONT_ICON 255
static inline uint32_t
codepoint_key(int font, int codepoint) {
return (uint32_t)((font << 24) | codepoint);
}
static inline int
font_index(int fontid){
return FONT_ID_MASK&((uint8_t)fontid);
}
#endif
================================================
FILE: src/font_manager.c
================================================
#include "font_manager.h"
#include "mutex.h"
#include "truetype.h"
#include
#include
#include
#include
#define STB_TRUETYPE_IMPLEMENTATION
#include
#define FONT_MANAGER_SLOTLINE (FONT_MANAGER_TEXSIZE/FONT_MANAGER_GLYPHSIZE)
#define FONT_MANAGER_SLOTS (FONT_MANAGER_SLOTLINE*FONT_MANAGER_SLOTLINE)
#define FONT_MANAGER_HASHSLOTS (FONT_MANAGER_SLOTS * 2)
// --------------
//
// xmin xmax
// | |
// |<-------- width -------->|
// | |
// | +-------------------------+----------------- ymax
// | | ggggggggg ggggg | ^ ^
// | | g:::::::::ggg::::g | | |
// | | g:::::::::::::::::g | | |
// | | g::::::ggggg::::::gg | | |
// | | g:::::g g:::::g | | |
// offset_x -|-------->| g:::::g g:::::g | offset_y |
// | | g:::::g g:::::g | | |
// | | g::::::g g:::::g | | |
// | | g:::::::ggggg:::::g | | |
// | | g::::::::::::::::g | | height
// | | gg::::::::::::::g | | |
// baseline ---*---------|---- gggggggg::::::g-----*-------- |
// / | | g:::::g | |
// origin | | g:::::gg gg:::::g | |
// | | g:::::gg gg:::::g | |
// | | g::::::ggg:::::::g | |
// | | gg:::::::::::::g | |
// | | ggg::::::ggg | |
// | | gggggg | v
// | +-------------------------+----------------- ymin
// | |
// |------------- advance_x ---------->|
struct font_slot {
uint32_t codepoint_key; // high 8 bits (ttf index)
int16_t offset_x;
int16_t offset_y;
int16_t advance_x;
int16_t advance_y;
uint16_t w;
uint16_t h;
};
struct priority_list {
int version;
int16_t prev;
int16_t next;
};
struct truetype_font;
struct font_manager {
int version;
int count;
int16_t list_head;
struct font_slot slots[FONT_MANAGER_SLOTS];
struct priority_list priority[FONT_MANAGER_SLOTS];
int16_t hash[FONT_MANAGER_HASHSLOTS];
struct truetype_font* ttf;
void *L;
int dpi_perinch;
int dirty;
int icon_n;
unsigned char *icon_data;
mutex_t mutex;
uint8_t texture_buffer[FONT_MANAGER_TEXSIZE*FONT_MANAGER_TEXSIZE];
};
const void *
font_manager_texture(struct font_manager *F, int *sz) {
*sz = FONT_MANAGER_TEXSIZE;
return F->texture_buffer;
}
/*
F->priority is a circular linked list for the LRU cache.
F->hash is for lookup with [font, codepoint].
*/
#define COLLISION_STEP 7
#define DISTANCE_OFFSET 8
#define ORIGINAL_SIZE (FONT_MANAGER_GLYPHSIZE - DISTANCE_OFFSET * 2)
#define ONEDGE_VALUE 180
#define PIXEL_DIST_SCALE (ONEDGE_VALUE/(float)(DISTANCE_OFFSET))
static const int SAPCE_CODEPOINT[] = {
' ', '\t', '\n', '\r',
};
static inline int
is_space_codepoint(int codepoint){
for (int ii=0; ii < sizeof(SAPCE_CODEPOINT)/sizeof(SAPCE_CODEPOINT[0]); ++ii){
if (codepoint == SAPCE_CODEPOINT[ii]){
return 1;
}
}
return 0;
}
static inline void
lock(struct font_manager *F) {
mutex_acquire(F->mutex);
}
static inline void
unlock(struct font_manager *F) {
mutex_release(F->mutex);
}
static inline const stbtt_fontinfo*
get_ttf_unsafe(struct font_manager *F, int fontid){
return truetype_font(F->ttf, fontid, F->L);
}
static inline const stbtt_fontinfo *
get_ttf(struct font_manager *F, int fontid) {
lock(F);
const stbtt_fontinfo * r = get_ttf_unsafe(F, fontid);
unlock(F);
return r;
}
static inline int
ttf_with_family(struct font_manager *F, const char* family){
return truetype_name(F->L, family);
}
static inline int
hash(int value) {
return (value * 0xdeece66d + 0xb) % FONT_MANAGER_HASHSLOTS;
}
static int
hash_lookup(struct font_manager *F, int cp) {
int slot;
int position = hash(cp);
while ((slot = F->hash[position]) >= 0) {
struct font_slot * s = &F->slots[slot];
if (s->codepoint_key == cp)
return slot;
position = (position + COLLISION_STEP) % FONT_MANAGER_HASHSLOTS;
}
return -1;
}
static void rehash(struct font_manager *F);
static void
hash_insert(struct font_manager *F, int cp, int slotid) {
++F->count;
if (F->count > FONT_MANAGER_SLOTS + FONT_MANAGER_SLOTS/2) {
rehash(F);
}
int position = hash(cp);
int slot;
while ((slot = F->hash[position]) >= 0) {
struct font_slot * s = &F->slots[slot];
if (s->codepoint_key < 0)
break;
assert(s->codepoint_key != cp);
position = (position + COLLISION_STEP) % FONT_MANAGER_HASHSLOTS;
}
F->hash[position] = slotid;
F->slots[slotid].codepoint_key = cp;
}
static void
rehash(struct font_manager *F) {
int i;
for (i=0;ihash[i] = -1; // reset slots
}
F->count = 0;
int count = 0;
(void)count;
for (i=0;islots[i].codepoint_key;
if (cp >= 0) {
assert(++count <= FONT_MANAGER_SLOTS);
hash_insert(F, cp, i);
}
}
}
static void
remove_node(struct font_manager *F, struct priority_list *node) {
struct priority_list *prev_node = &F->priority[node->prev];
struct priority_list *next_node = &F->priority[node->next];
prev_node->next = node->next;
next_node->prev = node->prev;
}
static void
touch_slot(struct font_manager *F, int slotid) {
struct priority_list *node = &F->priority[slotid];
node->version = F->version;
if (slotid == F->list_head)
return;
remove_node(F, node);
// insert before head
int head = F->list_head;
int tail = F->priority[head].prev;
node->prev = tail;
node->next = head;
struct priority_list *head_node = &F->priority[head];
struct priority_list *tail_node = &F->priority[tail];
head_node->prev = slotid;
tail_node->next = slotid;
F->list_head = slotid;
}
static int
get_icon(struct font_manager *F, int cp, struct font_glyph *glyph) {
if (cp < 0 || cp >= F->icon_n) {
memset(glyph, 0, sizeof(*glyph));
return -1;
}
glyph->offset_x = 0;
glyph->offset_y = -DISTANCE_OFFSET/2;
glyph->advance_x = FONT_MANAGER_GLYPHSIZE;
glyph->advance_y = FONT_MANAGER_GLYPHSIZE;
glyph->w = FONT_MANAGER_GLYPHSIZE;
glyph->h = FONT_MANAGER_GLYPHSIZE;
glyph->u = 0;
glyph->v = 0;
return 0;
}
// 1 exist in cache. 0 not exist in cache , call font_manager_update. -1 failed.
static int
font_manager_touch_unsafe(struct font_manager *F, int font, int codepoint, struct font_glyph *glyph) {
int cp = codepoint_key(font, codepoint);
int slot = hash_lookup(F, cp);
if (slot >= 0) {
touch_slot(F, slot);
struct font_slot *s = &F->slots[slot];
glyph->offset_x = s->offset_x;
glyph->offset_y = s->offset_y;
glyph->advance_x = s->advance_x;
glyph->advance_y = s->advance_y;
glyph->w = s->w;
glyph->h = s->h;
glyph->u = (slot % FONT_MANAGER_SLOTLINE) * FONT_MANAGER_GLYPHSIZE;
glyph->v = (slot / FONT_MANAGER_SLOTLINE) * FONT_MANAGER_GLYPHSIZE;
return 1;
}
int last_slot = F->priority[F->list_head].prev;
struct priority_list *last_node = &F->priority[last_slot];
if (font == FONT_ICON) {
if (last_node->version != F->version) {
F->dirty = 1;
}
return get_icon(F, codepoint, glyph);
}
if (font_index(font) <= 0) {
// invalid font
memset(glyph, 0, sizeof(*glyph));
return -1;
}
const struct stbtt_fontinfo *fi = get_ttf_unsafe(F, font);
float scale = stbtt_ScaleForMappingEmToPixels(fi, ORIGINAL_SIZE);
int ascent, descent, lineGap;
int advance, lsb;
int ix0, iy0, ix1, iy1;
if (!stbtt_GetFontVMetricsOS2(fi, &ascent, &descent, &lineGap)) {
stbtt_GetFontVMetrics(fi, &ascent, &descent, &lineGap);
}
stbtt_GetCodepointHMetrics(fi, codepoint, &advance, &lsb);
stbtt_GetCodepointBitmapBox(fi, codepoint, scale, scale, &ix0, &iy0, &ix1, &iy1);
glyph->w = ix1-ix0 + DISTANCE_OFFSET * 2;
glyph->h = iy1-iy0 + DISTANCE_OFFSET * 2;
glyph->offset_x = (short)(lsb * scale) - DISTANCE_OFFSET;
glyph->offset_y = iy0 - DISTANCE_OFFSET;
glyph->advance_x = (short)(((float)advance) * scale + 0.5f);
glyph->advance_y = (short)((ascent - descent) * scale + 0.5f);
glyph->u = 0;
glyph->v = 0;
if (last_node->version == F->version) // full ?
return -1;
F->dirty = 1;
return 0;
}
static int
font_manager_touch(struct font_manager *F, int font, int codepoint, struct font_glyph *glyph) {
lock(F);
int r = font_manager_touch_unsafe(F, font, codepoint, glyph);
unlock(F);
return r;
}
static inline int
scale_font(int v, float scale, int size) {
return ((int)(v * scale * size) + ORIGINAL_SIZE/2) / ORIGINAL_SIZE;
}
static inline float
fscale_font(float v, float scale, int size){
return (v * scale * size) / (float)ORIGINAL_SIZE;
}
void
font_manager_fontheight(struct font_manager *F, int fontid, int size, int *ascent, int *descent, int *lineGap) {
if (fontid <= 0) {
*ascent = 0;
*descent = 0;
*lineGap = 0;
}
const struct stbtt_fontinfo *fi = get_ttf(F, fontid);
float scale = stbtt_ScaleForMappingEmToPixels(fi, ORIGINAL_SIZE);
if (!stbtt_GetFontVMetricsOS2(fi, ascent, descent, lineGap)) {
stbtt_GetFontVMetrics(fi, ascent, descent, lineGap);
}
*ascent = scale_font(*ascent, scale, size);
*descent = scale_font(*descent, scale, size);
*lineGap = scale_font(*lineGap, scale, size);
}
int
font_manager_underline(struct font_manager *F, int fontid, int size, float *position, float *thickness){
const struct stbtt_fontinfo *fi = get_ttf(F, fontid);
float scale = stbtt_ScaleForMappingEmToPixels(fi, ORIGINAL_SIZE);
stbtt_uint32 post = stbtt__find_table(fi->data, fi->fontstart, "post");
if (!post) {
return -1;
}
int16_t underline_position = ttSHORT(fi->data + post + 8);
int16_t underline_thickness = ttSHORT(fi->data + post + 10);
*position = fscale_font(underline_position, scale, size);
*thickness = fscale_font(underline_thickness, scale, size);
return 0;
}
// F->dpi_perinch is a constant, so do not need to lock
int
font_manager_pixelsize(struct font_manager *F, int fontid, int pointsize) {
//TODO: need set dpi when init font_manager
const int defaultdpi = 96;
const int dpi = F->dpi_perinch == 0 ? defaultdpi : F->dpi_perinch;
return (int)((pointsize / 72.f) * dpi + 0.5f);
}
static inline void
scale(short *v, int size) {
*v = (*v * size + ORIGINAL_SIZE/2) / ORIGINAL_SIZE;
}
static inline void
uscale(uint16_t *v, int size) {
*v = (*v * size + ORIGINAL_SIZE/2) / ORIGINAL_SIZE;
}
void
font_manager_scale(struct font_manager *F, struct font_glyph *glyph, int size) {
(void)F;
scale(&glyph->offset_x, size);
scale(&glyph->offset_y, size);
scale(&glyph->advance_x, size);
scale(&glyph->advance_y, size);
uscale(&glyph->w, size);
uscale(&glyph->h, size);
}
static void
icon_scale(struct font_glyph *glyph, int size) {
glyph->offset_x = glyph->offset_x * size / FONT_MANAGER_GLYPHSIZE;
glyph->offset_y = glyph->offset_y * size / FONT_MANAGER_GLYPHSIZE;
glyph->advance_x = glyph->advance_x * size / FONT_MANAGER_GLYPHSIZE;
glyph->advance_y = glyph->advance_y * size / FONT_MANAGER_GLYPHSIZE;
glyph->w = glyph->w * size / FONT_MANAGER_GLYPHSIZE;
glyph->h = glyph->h * size / FONT_MANAGER_GLYPHSIZE;
}
static const char *
font_manager_update(struct font_manager *F, int fontid, int codepoint, struct font_glyph *glyph, uint8_t *buffer, int stride) {
if (fontid <= 0)
return "Invalid font";
lock(F);
int cp = codepoint_key(fontid, codepoint);
int slot = hash_lookup(F, cp);
if (slot < 0) {
// move last node to head
slot = F->priority[F->list_head].prev;
struct priority_list *last_node = &F->priority[slot];
if (last_node->version == F->version) { // full ?
unlock(F);
return "Too many glyph";
}
last_node->version = F->version;
F->list_head = slot;
F->slots[slot].codepoint_key = -1;
hash_insert(F, cp, slot);
}
glyph->u = (slot % FONT_MANAGER_SLOTLINE) * FONT_MANAGER_GLYPHSIZE;
glyph->v = (slot / FONT_MANAGER_SLOTLINE) * FONT_MANAGER_GLYPHSIZE;
struct font_slot *s = &F->slots[slot];
s->codepoint_key = cp;
s->offset_x = glyph->offset_x;
s->offset_y = glyph->offset_y;
s->advance_x = glyph->advance_x;
s->advance_y = glyph->advance_y;
s->w = glyph->w;
s->h = glyph->h;
if (fontid == FONT_ICON) {
if (codepoint < 0 || codepoint >= F->icon_n) {
unlock(F);
return "Invalid icon";
}
unsigned char * icon_data = F->icon_data;
unlock(F);
icon_data += codepoint * FONT_MANAGER_GLYPHSIZE * FONT_MANAGER_GLYPHSIZE;
buffer += stride * glyph->v + glyph->u;
int i;
for (i=0;iv + glyph->u;
int src_stride = width;
if (width > FONT_MANAGER_GLYPHSIZE)
width = FONT_MANAGER_GLYPHSIZE;
if (height > FONT_MANAGER_GLYPHSIZE)
height = FONT_MANAGER_GLYPHSIZE;
if (width > glyph->w)
width = glyph->w;
if (height > glyph->h)
height = glyph->h;
int i;
for (i=0;iuserdata);
return NULL;
}
const char *
font_manager_glyph(struct font_manager *F, int fontid, int codepoint, int size, struct font_glyph *g, struct font_glyph *og) {
int updated = font_manager_touch(F, fontid, codepoint, g);
*og = *g;
if (fontid != FONT_ICON && is_space_codepoint(codepoint)){
updated = 1; // not need update
og->w = og->h = 0;
}
if (fontid == FONT_ICON) {
icon_scale(g, size);
} else {
font_manager_scale(F, g, size);
}
if (updated == 0) {
const char * err = font_manager_update(F, fontid, codepoint, og, F->texture_buffer, FONT_MANAGER_TEXSIZE);
if (err) {
return err;
}
}
return NULL;
}
int
font_manager_flush(struct font_manager *F) {
// todo : atomic inc
lock(F);
int dirty = F->dirty;
++F->version;
F->dirty = 0;
unlock(F);
return dirty;
}
static void
font_manager_import_unsafe(struct font_manager *F, void* fontdata, size_t sz) {
truetype_import(F->L, fontdata, sz);
}
void
font_manager_import(struct font_manager *F, void* fontdata, size_t sz) {
lock(F);
font_manager_import_unsafe(F, fontdata, sz);
unlock(F);
}
static int
font_manager_addfont_with_family_unsafe(struct font_manager *F, const char* family) {
return ttf_with_family(F, family);
}
int
font_manager_addfont_with_family(struct font_manager *F, const char* family) {
lock(F);
int r = font_manager_addfont_with_family_unsafe(F, family);
unlock(F);
return r;
}
int
font_manager_enum_fontname(struct font_manager *F, int idx, char buffer[], int buf_sz) {
lock(F);
int r = truetype_enum(F->L, idx, buffer, buf_sz);
unlock(F);
return r;
}
float
font_manager_sdf_mask(struct font_manager *F){
return (ONEDGE_VALUE) / 255.f;
}
float
font_manager_sdf_distance(struct font_manager *F, uint8_t numpixel){
return (numpixel * PIXEL_DIST_SCALE) / 255.f;
}
size_t
font_manager_sizeof() {
return sizeof(struct font_manager);
}
void
font_manager_icon_init(struct font_manager *F, int n, void *data) {
lock(F);
F->icon_n = n;
F->icon_data = (unsigned char *)data;
unlock(F);
}
void
font_manager_init(struct font_manager *F, void *L) {
mutex_init(F->mutex);
F->version = 1;
F->count = 0;
F->ttf = NULL;
F->L = NULL;
F->dpi_perinch = 0;
F->dirty = 0;
F->icon_n = 0;
F->icon_data = NULL;
// init priority list
int i;
for (i=0;ipriority[i].prev = i+1;
F->priority[i].next = i-1;
}
int lastslot = FONT_MANAGER_SLOTS-1;
F->priority[0].next = lastslot;
F->priority[lastslot].prev = 0;
F->list_head = lastslot;
// init hash
for (i=0;islots[i].codepoint_key = -1;
}
for (i=0;ihash[i] = -1; // empty slot
}
memset(F->texture_buffer, 0, sizeof(F->texture_buffer));
F->ttf = truetype_cstruct(L);
F->L = L;
}
void*
font_manager_shutdown(struct font_manager *F) {
lock(F);
void *L = F->L;
F->ttf = NULL;
F->L = NULL;
unlock(F);
return L;
}
================================================
FILE: src/font_manager.h
================================================
#ifndef font_manager_h
#define font_manager_h
#include
#include
#include
#include "font_define.h"
struct font_manager;
size_t font_manager_sizeof();
void font_manager_init(struct font_manager *, void *L);
void* font_manager_shutdown(struct font_manager *);
void font_manager_import(struct font_manager *F, void* fontdata, size_t sz);
int font_manager_addfont_with_family(struct font_manager *F, const char* family);
void font_manager_fontheight(struct font_manager *F, int fontid, int size, int *ascent, int *descent, int *lineGap);
int font_manager_pixelsize(struct font_manager *F, int fontid, int pointsize);
const char* font_manager_glyph(struct font_manager *F, int fontid, int codepoint, int size, struct font_glyph *g, struct font_glyph *og);
//int font_manager_touch(struct font_manager *, int font, int codepoint, struct font_glyph *glyph);
//const char * font_manager_update(struct font_manager *, int font, int codepoint, struct font_glyph *glyph, uint8_t *buffer, int stride);
int font_manager_flush(struct font_manager *);
void font_manager_scale(struct font_manager *F, struct font_glyph *glyph, int size);
int font_manager_underline(struct font_manager *F, int fontid, int size, float *underline_position, float *thickness);
float font_manager_sdf_mask(struct font_manager *F);
float font_manager_sdf_distance(struct font_manager *F, uint8_t numpixel);
void font_manager_icon_init(struct font_manager *F, int n, void *data);
int font_manager_enum_fontname(struct font_manager *F, int idx, char buffer[], int buf_sz);
// for debug
const void * font_manager_texture(struct font_manager *F, int *sz);
#endif //font_manager_h
================================================
FILE: src/font_system.c
================================================
#include
#include
#include
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
#include
#define MAX_NAME 1024
static void *
free_data(void *ud, void *ptr, size_t oszie, size_t nsize) {
free(ptr);
return NULL;
}
static int
lttfdata(lua_State *L) {
const char * familyName = luaL_checkstring(L, 1);
WCHAR familyNameW[MAX_NAME];
int n = MultiByteToWideChar(CP_UTF8,0,(const char*)familyName,-1,familyNameW,MAX_NAME);
if (n == 0 || n > LF_FACESIZE)
return luaL_error(L, "Invalid family name %s", familyName);
HDC hdc = CreateCompatibleDC(0);
LOGFONTW lf;
memset(&lf, 0, sizeof(LOGFONT));
memcpy(lf.lfFaceName, familyNameW, n * sizeof(WCHAR));
lf.lfCharSet = DEFAULT_CHARSET;
HFONT hfont = CreateFontIndirectW(&lf);
if (!hfont) {
DeleteDC(hdc);
return luaL_error(L, "Create font failed: %d", GetLastError());
}
HGDIOBJ oldobj = SelectObject(hdc, hfont);
uint32_t tags[2] = {0x66637474/*ttcf*/, 0};
int i;
DWORD bytes = 0;
char *buf = NULL;
for (i=0;i<2;i++) {
uint32_t tag = tags[i];
bytes = GetFontData(hdc, tag, 0, 0, 0);
if (bytes != GDI_ERROR) {
buf = malloc(bytes+1);//lua_newuserdatauv(L, bytes, 0);
if (buf == NULL)
return luaL_error(L, "Out of memory : sysfont");
buf[bytes] = 0;
bytes = GetFontData(hdc, tag, 0, (void *)buf, bytes);
if (bytes != GDI_ERROR) {
break;
} else {
free(buf);
bytes = 0;
}
}
}
SelectObject(hdc, oldobj);
DeleteObject(hfont);
DeleteDC(hdc);
if (bytes == 0) {
return luaL_error(L, "Read font data failed");
}
lua_pushexternalstring(L, buf, bytes, free_data , NULL);
return 1;
}
#elif defined(__APPLE__)
#include
#include
#define kCTFontTableTtcf 'ttcf'
static void *
free_data(void *ud, void *ptr, size_t oszie, size_t nsize) {
free(ptr);
return NULL;
}
static CFDataRef read_font_file_data(CFURLRef url) {
CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
if (!stream) {
return NULL;
}
if (!CFReadStreamOpen(stream)) {
CFRelease(stream);
return NULL;
}
CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 0);
UInt8 buffer[8192];
CFIndex bytesRead;
while ((bytesRead = CFReadStreamRead(stream, buffer, sizeof(buffer))) > 0) {
CFDataAppendBytes(data, buffer, bytesRead);
}
CFReadStreamClose(stream);
CFRelease(stream);
if (bytesRead < 0) {
CFRelease(data);
return NULL;
}
return data;
}
static int
lttfdata(lua_State *L) {
const char *familyName = luaL_checkstring(L, 1);
CFStringRef fontNameStr = CFStringCreateWithCString(kCFAllocatorDefault,
familyName,
kCFStringEncodingUTF8);
if (!fontNameStr) {
return luaL_error(L, "Failed to create font name string");
}
CFDictionaryRef attributes = CFDictionaryCreate(
kCFAllocatorDefault,
(const void**)&kCTFontFamilyNameAttribute,
(const void**)&fontNameStr,
1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks
);
CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes(attributes);
CFRelease(attributes);
CFRelease(fontNameStr);
if (!descriptor) {
return luaL_error(L, "Failed to create font descriptor");
}
CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 12.0, NULL);
CFRelease(descriptor);
if (!font) {
return luaL_error(L, "Failed to create font for family: %s", familyName);
}
CFDataRef fontData = NULL;
CFDataRef ttcfData = CTFontCopyTable(font, kCTFontTableTtcf, kCTFontTableOptionNoOptions);
if (ttcfData) {
fontData = ttcfData;
} else {
CTFontDescriptorRef fontDesc = CTFontCopyFontDescriptor(font);
if (fontDesc) {
CFURLRef fontURL = CTFontDescriptorCopyAttribute(fontDesc, kCTFontURLAttribute);
if (fontURL) {
fontData = read_font_file_data(fontURL);
CFRelease(fontURL);
}
CFRelease(fontDesc);
}
}
CFRelease(font);
if (!fontData) {
return luaL_error(L, "Failed to get font data for family: %s", familyName);
}
CFIndex dataLength = CFDataGetLength(fontData);
char *buf = malloc(dataLength + 1);
if (!buf) {
CFRelease(fontData);
return luaL_error(L, "Out of memory : sysfont");
}
CFDataGetBytes(fontData, CFRangeMake(0, dataLength), (UInt8*)buf);
buf[dataLength] = 0;
CFRelease(fontData);
lua_pushexternalstring(L, buf, dataLength, free_data, NULL);
return 1;
}
#elif defined(__EMSCRIPTEN__)
static int
lttfdata(lua_State *L) {
// todo :
return 0;
}
#elif defined(__linux__)
#include
#include
#include
static void *
free_data(void *ud, void *ptr, size_t oszie, size_t nsize) {
free(ptr);
return NULL;
}
static int
lttfdata(lua_State *L) {
const char *familyName = luaL_checkstring(L, 1);
if (!FcInit()) {
return luaL_error(L, "Failed to initialize fontconfig");
}
FcPattern *pattern = FcNameParse((const FcChar8*)familyName);
if (!pattern) {
FcFini();
return luaL_error(L, "Failed to parse font name: %s", familyName);
}
FcConfigSubstitute(NULL, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
FcResult result;
FcPattern *match = FcFontMatch(NULL, pattern, &result);
FcPatternDestroy(pattern);
if (!match || result != FcResultMatch) {
if (match) FcPatternDestroy(match);
FcFini();
return luaL_error(L, "Font not found: %s", familyName);
}
FcChar8 *filename;
if (FcPatternGetString(match, FC_FILE, 0, &filename) != FcResultMatch) {
FcPatternDestroy(match);
FcFini();
return luaL_error(L, "Failed to get font file path for: %s", familyName);
}
FILE *file = fopen((const char*)filename, "rb");
if (!file) {
FcPatternDestroy(match);
FcFini();
return luaL_error(L, "Failed to open font file: %s", filename);
}
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
if (fileSize <= 0) {
fclose(file);
FcPatternDestroy(match);
FcFini();
return luaL_error(L, "Invalid font file size: %s", filename);
}
char *buf = malloc(fileSize + 1);
if (!buf) {
fclose(file);
FcPatternDestroy(match);
FcFini();
return luaL_error(L, "Out of memory : sysfont");
}
size_t bytesRead = fread(buf, 1, fileSize, file);
fclose(file);
FcPatternDestroy(match);
FcFini();
if (bytesRead != fileSize) {
free(buf);
return luaL_error(L, "Failed to read font file: %s", filename);
}
buf[fileSize] = 0;
lua_pushexternalstring(L, buf, fileSize, free_data, NULL);
return 1;
}
#else
static int
lttfdata(lua_State *L) {
// always failed
return 0;
}
#endif
int
luaopen_font_system(lua_State *L) {
luaL_Reg l[] = {
{ "ttfdata", lttfdata },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: src/gamepad.c
================================================
#include
#include
#include
#include
#include "mutex.h"
// It's the same with XINPUT_GAMEAD
static const char * gamepad_buttons[] = {
"UP",
"DOWN",
"LEFT",
"RIGHT",
"START",
"BACK",
"LS",
"RS",
"LB",
"RB",
"U0", // Undef
"U1", // undef
"A",
"B",
"X",
"Y",
};
#define BUTTON_COUNT (sizeof(gamepad_buttons)/sizeof(gamepad_buttons[0]))
struct gamepad_state {
uint32_t packet;
uint16_t buttons;
uint8_t lt;
uint8_t rt;
int16_t ls_x;
int16_t ls_y;
int16_t rs_x;
int16_t rs_y;
};
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
#include
#include
static int
gamepad_getstate(int index, struct gamepad_state *state) {
XINPUT_STATE * result = (XINPUT_STATE *)state;
DWORD err = XInputGetState(index, result);
if (err == ERROR_SUCCESS)
return 0;
// should be ERROR_DEVICE_NOT_CONNECTED
return 1;
}
#elif defined(__APPLE__)
#include
#include
#include
static IOHIDManagerRef hid_manager = NULL;
static CFMutableArrayRef gamepad_devices = NULL;
static void gamepad_init() {
if (hid_manager != NULL) return;
hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
gamepad_devices = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
CFMutableDictionaryRef matching = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
int usage_page = kHIDPage_GenericDesktop;
int usage = kHIDUsage_GD_GamePad;
CFNumberRef page_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage_page);
CFNumberRef usage_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
CFDictionarySetValue(matching, CFSTR(kIOHIDDeviceUsagePageKey), page_ref);
CFDictionarySetValue(matching, CFSTR(kIOHIDDeviceUsageKey), usage_ref);
IOHIDManagerSetDeviceMatching(hid_manager, matching);
IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone);
CFSetRef device_set = IOHIDManagerCopyDevices(hid_manager);
if (device_set) {
CFIndex count = CFSetGetCount(device_set);
IOHIDDeviceRef *devices = malloc(count * sizeof(IOHIDDeviceRef));
CFSetGetValues(device_set, (const void**)devices);
for (CFIndex i = 0; i < count; i++) {
CFArrayAppendValue(gamepad_devices, devices[i]);
}
free(devices);
CFRelease(device_set);
}
CFRelease(page_ref);
CFRelease(usage_ref);
CFRelease(matching);
}
static int
gamepad_getstate(int index, struct gamepad_state *state) {
gamepad_init();
memset(state, 0, sizeof(struct gamepad_state));
if (index < 0 || index >= CFArrayGetCount(gamepad_devices)) {
return 1;
}
IOHIDDeviceRef device = (IOHIDDeviceRef)CFArrayGetValueAtIndex(gamepad_devices, index);
if (!device) {
return 1;
}
CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
if (!elements) {
return 1;
}
CFIndex element_count = CFArrayGetCount(elements);
for (CFIndex i = 0; i < element_count; i++) {
IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
IOHIDElementType type = IOHIDElementGetType(element);
if (type == kIOHIDElementTypeInput_Button ||
type == kIOHIDElementTypeInput_Axis) {
IOHIDValueRef value_ref;
if (IOHIDDeviceGetValue(device, element, &value_ref) == kIOReturnSuccess) {
CFIndex value = IOHIDValueGetIntegerValue(value_ref);
uint32_t usage_page = IOHIDElementGetUsagePage(element);
uint32_t usage = IOHIDElementGetUsage(element);
if (usage_page == kHIDPage_Button) {
if (usage >= 1 && usage <= 16) {
if (value) {
state->buttons |= (1 << (usage - 1));
}
}
}
else if (usage_page == kHIDPage_GenericDesktop) {
CFIndex min = IOHIDElementGetLogicalMin(element);
CFIndex max = IOHIDElementGetLogicalMax(element);
int16_t normalized = (int16_t)(((value - min) * 65535) / (max - min) - 32768);
switch (usage) {
case kHIDUsage_GD_X:
state->ls_x = normalized;
break;
case kHIDUsage_GD_Y:
state->ls_y = -normalized;
break;
case kHIDUsage_GD_Z:
state->rs_x = normalized;
break;
case kHIDUsage_GD_Rz:
state->rs_y = -normalized;
break;
case kHIDUsage_GD_Rx:
state->lt = (uint8_t)((normalized + 32768) >> 8);
break;
case kHIDUsage_GD_Ry:
state->rt = (uint8_t)((normalized + 32768) >> 8);
break;
}
}
}
}
}
CFRelease(elements);
static uint32_t packet_counter = 0;
state->packet = ++packet_counter;
return 0;
}
#elif defined(__linux__)
#include
#include
#include
#include
static int
gamepad_getstate(int index, struct gamepad_state *state) {
char device_path[32];
snprintf(device_path, sizeof(device_path), "/dev/input/js%d", index);
int fd = open(device_path, O_RDONLY | O_NONBLOCK);
if (fd < 0) {
return 1;
}
static struct {
uint16_t buttons;
uint8_t lt, rt;
int16_t ls_x, ls_y, rs_x, rs_y;
uint32_t packet;
} cache = {0};
struct js_event event;
while (read(fd, &event, sizeof(event)) == sizeof(event)) {
cache.packet++;
if (event.type & JS_EVENT_BUTTON) {
if (event.number < 16) {
if (event.value) {
cache.buttons |= (1 << event.number);
} else {
cache.buttons &= ~(1 << event.number);
}
}
} else if (event.type & JS_EVENT_AXIS) {
int16_t value = event.value;
switch (event.number) {
case 0: cache.ls_x = value; break;
case 1: cache.ls_y = -value; break;
case 2: cache.lt = (value + 32768) >> 8; break;
case 3: cache.rs_x = value; break;
case 4: cache.rs_y = -value; break;
case 5: cache.rt = (value + 32768) >> 8; break;
}
}
}
close(fd);
state->packet = cache.packet;
state->buttons = cache.buttons;
state->lt = cache.lt;
state->rt = cache.rt;
state->ls_x = cache.ls_x;
state->ls_y = cache.ls_y;
state->rs_x = cache.rs_x;
state->rs_y = cache.rs_y;
return 0;
}
#elif defined(__EMSCRIPTEN__)
static int
gamepad_getstate(int index, struct gamepad_state *state) {
(void)index;
(void)state;
return 1;
}
#else
// todo : linux and mac support
#error Unsupport gamepad
#endif
#define MAX_GAMEPAD 4
static struct gamepad_global {
mutex_t lock[MAX_GAMEPAD];
int connected[MAX_GAMEPAD];
uint32_t packet[MAX_GAMEPAD];
struct gamepad_state state[MAX_GAMEPAD];
} GAMEPAD;
struct gamepad_local {
int connected;
struct gamepad_state state;
};
static int
lgamepad_update(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
int index = luaL_optinteger(L, 2, 0);
if (index < 0 || index >= MAX_GAMEPAD) {
return luaL_error(L, "Invalid gamepad id %d", index);
}
struct gamepad_local * last_state = (struct gamepad_local *)lua_touserdata(L, lua_upvalueindex(1));
last_state += index;
struct gamepad_state tmp;
mutex_acquire(GAMEPAD.lock[index]);
int connected = GAMEPAD.connected[index];
if (connected) {
tmp = GAMEPAD.state[index];
}
mutex_release(GAMEPAD.lock[index]);
int i;
if (connected != last_state->connected) {
if (connected) {
// reconnected, set all states
last_state->state = tmp;
lua_pushinteger(L, tmp.lt);
lua_setfield(L, 1, "LT");
lua_pushinteger(L, tmp.rt);
lua_setfield(L, 1, "RT");
lua_pushinteger(L, tmp.ls_x);
lua_setfield(L, 1, "LS_X");
lua_pushinteger(L, tmp.ls_y);
lua_setfield(L, 1, "LS_Y");
lua_pushinteger(L, tmp.rs_x);
lua_setfield(L, 1, "RS_X");
lua_pushinteger(L, tmp.rs_y);
uint16_t mask = tmp.buttons;
for (i=0;i>= 1;
}
} else {
// disconnected, clear all states
memset(&last_state->state, 0, sizeof(last_state->state));
lua_pushinteger(L, 0);
lua_setfield(L, 1, "LT");
lua_pushinteger(L, 0);
lua_setfield(L, 1, "RT");
lua_pushinteger(L, 0);
lua_setfield(L, 1, "LS_X");
lua_pushinteger(L, 0);
lua_setfield(L, 1, "LS_Y");
lua_pushinteger(L, 0);
lua_setfield(L, 1, "RS_X");
for (i=0;istate;
if (tmp.lt != state->lt) {
state->lt = tmp.lt;
lua_pushinteger(L, tmp.lt);
lua_setfield(L, 1, "LT");
}
if (tmp.rt != state->rt) {
state->rt = tmp.rt;
lua_pushinteger(L, tmp.rt);
lua_setfield(L, 1, "RT");
}
if (tmp.ls_x != state->ls_x) {
state->ls_x = tmp.ls_x;
lua_pushinteger(L, tmp.ls_x);
lua_setfield(L, 1, "LS_X");
}
if (tmp.ls_y != state->ls_y) {
state->ls_y = tmp.ls_y;
lua_pushinteger(L, tmp.ls_y);
lua_setfield(L, 1, "LS_Y");
}
if (tmp.rs_x != state->rs_x) {
state->rs_x = tmp.rs_x;
lua_pushinteger(L, tmp.rs_x);
lua_setfield(L, 1, "RS_X");
}
if (tmp.rs_y != state->rs_y) {
state->rs_y = tmp.rs_y;
lua_pushinteger(L, tmp.rs_y);
lua_setfield(L, 1, "RS_Y");
}
uint16_t mask = tmp.buttons;
uint16_t last_mask = state->buttons;
if (mask != last_mask) {
for (i=0;i>= 1;
last_mask >>= 1;
}
state->buttons = tmp.buttons;
}
}
lua_pushboolean(L, connected);
lua_setfield(L, 1, "connect");
last_state->connected = connected;
lua_pushboolean(L, connected);
return 1;
}
int
luaopen_gamepad(lua_State *L) {
luaL_checkversion(L);
lua_newtable(L);
struct gamepad_local * state = (struct gamepad_local *)lua_newuserdatauv(L, sizeof(*state) * MAX_GAMEPAD, 0);
memset(state, 0, sizeof(*state) * MAX_GAMEPAD);
lua_pushcclosure(L, lgamepad_update, 1);
lua_setfield(L, -2, "update");
return 1;
}
static int
lgamepad_device_init(lua_State *L) {
int i;
memset(&GAMEPAD, 0, sizeof(GAMEPAD));
for (i=0;i
#include
#include
#include
#include
static void *
malloc_luastring(size_t sz) {
unsigned char * buffer = malloc(sz+1);
if (buffer) {
buffer[sz] = 0;
return (void *)buffer;
} else {
return NULL;
}
}
static void *
realloc_luastring(void *ptr, size_t sz) {
unsigned char * buffer = realloc(ptr, sz+1);
if (buffer) {
buffer[sz] = 0;
return (void *)buffer;
} else {
return NULL;
}
}
#define STBI_MALLOC malloc_luastring
#define STBI_FREE free
#define STBI_REALLOC realloc_luastring
#define STBI_ONLY_PNG
#define STBI_MAX_DIMENSIONS 65536
#define STBI_NO_STDIO
#define STB_IMAGE_IMPLEMENTATION
#include "stb/stb_image.h"
#define STBIW_WINDOWS_UTF8
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb/stb_image_write.h"
#include "stb/stb_image_resize2.h"
#include "luabuffer.h"
static void *
free_image(void *ud, void *ptr, size_t osize, size_t nsize) {
stbi_image_free(ptr);
return NULL;
}
static void *
free_buffer(void *ud, void *ptr, size_t osize, size_t nsize) {
free(ptr);
return NULL;
}
static int
load_image(lua_State *L, int *x, int *y, stbi_uc **output) {
size_t sz;
int c;
const stbi_uc *buffer = luaL_getbuffer(L, &sz);
stbi_uc * img = stbi_load_from_memory(buffer, sz, x, y, &c, 4);
if (img == NULL) {
lua_pushnil(L);
lua_pushstring(L, stbi_failure_reason());
return 2;
}
*output = img;
return 0;
}
static int
image_load(lua_State *L) {
int x, y;
stbi_uc * img = NULL;
int r = load_image(L, &x, &y, &img);
if (r)
return r;
lua_pushexternalstring(L, (const char *)img, x * y * 4, free_image, NULL);
lua_pushinteger(L, x);
lua_pushinteger(L, y);
return 3;
}
static int
image_load_alpha(lua_State *L) {
int x, y;
stbi_uc * img = NULL;
int r = load_image(L, &x, &y, &img);
if (r)
return r;
int i, j;
stbi_uc * ptr = img;
for (i=0;i x) {
return 0;
}
if (dy < 0) {
h += dy;
dy = 0;
} else if (dy > y) {
return 0;
}
if (w + dx > x) {
w = x - dx;
}
if (h + dy > y) {
h = y - dy;
}
if (w <=0 || h <= 0)
return 0;
r->ptr = buffer + x * 4 * dy + 4 * dx;
r->stride = 4 * x;
r->width = w;
r->line = h;
return 1;
}
static int
remove_top(struct rect *r) {
int x, y;
const uint8_t * ptr = r->ptr;
for (y=0;yline;y++) {
const uint8_t *cur = ptr;
for (x=0;xwidth;x++) {
if (cur[3]) {
r->ptr = ptr;
r->line -= y;
return y;
}
cur += 4;
}
ptr += r->stride;
}
return r->line;
}
static int
remove_bottom(struct rect *r) {
int x, y;
const uint8_t * ptr = r->ptr + (r->line - 1) * r->stride;
for (y=0;yline-1;y++) {
const uint8_t *cur = ptr;
for (x=0;xwidth;x++) {
if (cur[3]) {
r->line -= y;
return y;
}
cur += 4;
}
ptr -= r->stride;
}
return r->line;
}
static int
remove_left(struct rect *r) {
int x, y;
const uint8_t * ptr = r->ptr;
int min_left = r->width;
for (y=0;yline;y++) {
const uint8_t *cur = ptr;
for (x=0;xstride;
}
return min_left;
}
static int
remove_right(struct rect *r) {
int x, y;
const uint8_t * ptr = r->ptr + r->width * 4;
int min_right = r->width;
for (y=0;yline;y++) {
const uint8_t *cur = ptr;
for (x=0;xstride;
}
return min_right;
}
static int
image_crop(lua_State *L) {
size_t sz;
const uint8_t * image = luaL_getbuffer(L, &sz);
int x = luaL_checkinteger(L, 2);
int y = luaL_checkinteger(L, 3);
if (x * y * 4 != sz)
return luaL_error(L, "Invalid image size %d * %d * 4 != %z\n", x, y, sz);
int dx = luaL_optinteger(L, 4, 0);
int dy = luaL_optinteger(L, 5, 0);
int w = luaL_optinteger(L, 6, x - dx);
int h = luaL_optinteger(L, 7, y - dy);
struct rect r;
if (!(rect_init(&r, image, x, y, dx, dy, w, h))) {
return 0;
}
int top = remove_top(&r);
if (top == h)
return 0;
remove_bottom(&r);
int left = remove_left(&r);
int right = remove_right(&r);
// reserve border for alpha channel
lua_pushinteger(L, left);
lua_pushinteger(L, top);
lua_pushinteger(L, r.width - (left + right));
lua_pushinteger(L, r.line);
return 4;
}
static uint8_t *
get_image_buffer(lua_State *L, int *w, int *h) {
uint8_t * buffer = lua_touserdata(L, 1);
if (buffer == NULL || !lua_getmetatable(L, 1))
luaL_error(L, "Neet image userdata");
if (lua_getfield(L, -1, "width") != LUA_TNUMBER) {
luaL_error(L, "No .width");
}
int width = lua_tointeger(L, -1);
lua_pop(L, 1);
if (lua_getfield(L, -1, "height") != LUA_TNUMBER) {
luaL_error(L, "No .height");
}
int height = lua_tointeger(L, -1);
lua_pop(L, 1);
int size = lua_rawlen(L, 1);
if (width * height * 4 != size)
luaL_error(L, "Invalid size %d * %d * 4 != %d", width, height, size);
*w = width;
*h = height;
return buffer;
}
static int
limage_write_png(lua_State *L) {
int width, height;
uint8_t * buffer = get_image_buffer(L, &width, &height);
const char * filename = luaL_checkstring(L, 2);
if (!stbi_write_png(filename, width, height, 4, buffer, width * 4)) {
return luaL_error(L, "Write %s failed", filename);
}
return 0;
}
struct canvas {
void * buffer;
int width;
int height;
int stride;
};
static int
limage_tocanvas(lua_State *L) {
int width, height;
uint8_t * buffer = get_image_buffer(L, &width, &height);
struct canvas * c = (struct canvas *)lua_newuserdatauv(L, sizeof(*c), 1);
lua_pushvalue(L, 1);
lua_setiuservalue(L, -2, 1);
c->buffer = buffer;
c->width = width;
c->height = height;
c->stride = width * 4;
return 1;
}
static int
image_new(lua_State *L) {
int w = luaL_checkinteger(L, 1);
int h = luaL_checkinteger(L, 2);
uint8_t * buffer = (uint8_t *)lua_newuserdatauv(L, w * h * 4, 0);
size_t sz = 0;
const char *data = NULL;
switch (lua_type(L, 3)) {
case LUA_TSTRING:
data = lua_tolstring(L, 3, &sz);
break;
case LUA_TLIGHTUSERDATA:
data = (const char *)lua_touserdata(L, 3);
sz = luaL_checkinteger(L, 4);
break;
case LUA_TUSERDATA:
data = (const char *)lua_touserdata(L, 3);
sz = lua_rawlen(L, 3);
break;
}
if (sz == 0) {
memset(buffer, 0, w * h * 4);
} else if (sz == w*h*4) {
memcpy(buffer, data, sz);
} else if (sz == w*h) {
int i,j;
uint8_t *dst = buffer;
const uint8_t *src = (const uint8_t *)data;
for (i=0;i width || y+h > height) {
return luaL_error(L, "Invalid rect (%d %d %d %d) in (%d %d)", x, y, w, h, width, height);
}
width = w;
height = h;
}
struct canvas * c = (struct canvas *)lua_newuserdatauv(L, sizeof(*c), 1);
lua_pushvalue(L, 1);
lua_setiuservalue(L, -2, 1);
if (t == LUA_TSTRING) {
size_t sz;
c->buffer = (void *)lua_tolstring(L, 1, &sz);
if (stride * (y + height) > sz)
return luaL_error(L, "Invalid buffer size %d * %d > %d", stride, (y + height), sz);
} else {
c->buffer = lua_touserdata(L, 1);
}
c->buffer = (void *)((char *)c->buffer + y * stride + x * 4);
c->width = width;
c->height = height;
c->stride = stride;
return 1;
}
static int
check_canvas(lua_State *L, int index) {
if (lua_type(L, index) != LUA_TUSERDATA)
return luaL_error(L, "Need canvas");
int t = lua_getiuservalue(L, index, 1);
if (t != LUA_TSTRING && t != LUA_TUSERDATA && t != LUA_TLIGHTUSERDATA)
return luaL_error(L, "Invalid canvas at %d", index);
lua_pop(L, 1);
return t;
}
static int
image_canvas_size(lua_State *L) {
check_canvas(L, 1);
struct canvas * c = (struct canvas *)lua_touserdata(L, 1);
lua_pushinteger(L, c->width);
lua_pushinteger(L, c->height);
lua_pushlightuserdata(L, c->buffer);
return 3;
}
static int
canvas_blit(lua_State *L) {
if (check_canvas(L, 1) == LUA_TSTRING)
return luaL_error(L, "dst canvas is readonly");
check_canvas(L, 2);
struct canvas * dst = (struct canvas *)lua_touserdata(L, 1);
struct canvas * src = (struct canvas *)lua_touserdata(L, 2);
int x = luaL_optinteger(L, 3, 0);
int y = luaL_optinteger(L, 4, 0);
int w = src->width;
int h = src->height;
int sx = 0;
int sy = 0;
if (x < 0) {
w += x;
sx = -x;
x = 0;
}
if (y < 0) {
h += y;
sy = -y;
y = 0;
}
if (x + w > dst->width) {
w = dst->width - x;
}
if (y + h > dst->height) {
h = dst->height - y;
}
if (w <=0 || h <= 0)
return 0;
int i;
uint8_t * dst_ptr = (uint8_t *)dst->buffer + y * dst->stride + 4 * x;
const uint8_t *src_ptr = (const uint8_t *)src->buffer + sy * src->stride + 4 * sx;
for (i=0;istride;
dst_ptr += dst->stride;
}
return 0;
}
static int
image_makeindex(lua_State *L) {
if (lua_isnoneornil(L, 1)) {
lua_pushinteger(L, -1);
return 1;
}
int x = luaL_checkinteger(L, 1);
int y = luaL_checkinteger(L, 2);
int w = luaL_checkinteger(L, 3);
int h = luaL_checkinteger(L, 4);
union {
uint64_t index;
uint16_t v[4];
} u;
u.v[0] = (uint16_t)x;
u.v[1] = (uint16_t)y;
u.v[2] = (uint16_t)w;
u.v[3] = (uint16_t)h;
lua_pushinteger(L, u.index);
return 1;
}
static int
image_resize(lua_State *L) {
size_t sz;
const char *buffer = luaL_checklstring(L, 1, &sz);
int x = luaL_checkinteger(L, 2);
int y = luaL_checkinteger(L, 3);
float scale_x = luaL_checknumber(L, 4);
float scale_y = luaL_optnumber(L, 5, scale_x);
int channel;
size_t output_sz;
int tx = (int)(x * scale_x + 0.5);
int ty = (int)(y * scale_y + 0.5);
int stride_p;
if (x * y * 4 == sz) {
channel = STBIR_4CHANNEL;
output_sz = tx * ty * 4;
stride_p = 4;
} else {
if (x * y != sz) {
return luaL_error(L, "Invalid size (%d) != %d * %d", (int)sz, x, y);
}
channel = STBIR_1CHANNEL;
output_sz = tx * ty;
stride_p = 1;
}
unsigned char *output = (unsigned char *)malloc(output_sz +1);
if (output == NULL)
return luaL_error(L, "Out of memory");
output[output_sz] = 0;
memcpy(output, buffer, output_sz);
stbir_resize_uint8_linear((const unsigned char *)buffer , x , y, stride_p * x, output, tx, ty, stride_p * tx, channel);
lua_pushexternalstring(L, (const char *)output, output_sz, free_buffer, NULL);
lua_pushinteger(L, tx);
lua_pushinteger(L, ty);
return 3;
}
int
luaopen_image(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "load", image_load },
{ "load_alpha", image_load_alpha },
{ "resize", image_resize },
{ "info", image_info },
{ "crop", image_crop },
{ "canvas", image_canvas },
{ "canvas_size", image_canvas_size },
{ "new", image_new },
{ "blit", canvas_blit },
{ "makeindex", image_makeindex },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: src/ime_char_filter.h
================================================
#ifndef SOLUNA_IME_CHAR_FILTER_H
#define SOLUNA_IME_CHAR_FILTER_H
#include
#include
#include
struct soluna_ime_char_filter_state {
uint32_t *expected_chars;
int *expected_count;
uint32_t *ignore_chars;
int *ignore_count;
int capacity;
};
static inline void
soluna_ime_char_queue_push(uint32_t *buffer, int *count, int max, uint32_t code) {
if (*count == max) {
memmove(buffer, buffer + 1, (size_t)(max - 1) * sizeof(uint32_t));
buffer[max - 1] = code;
} else {
buffer[*count] = code;
(*count)++;
}
}
static inline bool
soluna_ime_char_queue_consume(uint32_t *buffer, int *count, uint32_t code) {
for (int i = 0; i < *count; ++i) {
if (buffer[i] == code) {
if (i < *count - 1) {
memmove(buffer + i, buffer + i + 1, (size_t)(*count - i - 1) * sizeof(uint32_t));
}
(*count)--;
return true;
}
}
return false;
}
static inline void
soluna_ime_char_filter_reset(struct soluna_ime_char_filter_state state) {
*state.expected_count = 0;
*state.ignore_count = 0;
}
static inline void
soluna_ime_char_filter_push_expected(struct soluna_ime_char_filter_state state, uint32_t code) {
soluna_ime_char_queue_push(state.expected_chars, state.expected_count, state.capacity, code);
}
static inline bool
soluna_ime_char_filter_should_skip(struct soluna_ime_char_filter_state state, uint32_t code) {
if (soluna_ime_char_queue_consume(state.expected_chars, state.expected_count, code)) {
soluna_ime_char_queue_push(state.ignore_chars, state.ignore_count, state.capacity, code);
return false;
}
if (*state.ignore_count > 0) {
if (soluna_ime_char_queue_consume(state.ignore_chars, state.ignore_count, code)) {
return true;
} else if (*state.ignore_count > 0) {
uint32_t stale = state.ignore_chars[0];
soluna_ime_char_queue_consume(state.ignore_chars, state.ignore_count, stale);
}
}
return false;
}
#endif /* SOLUNA_IME_CHAR_FILTER_H */
================================================
FILE: src/ime_state.h
================================================
#ifndef SOLUNA_IME_STATE_H
#define SOLUNA_IME_STATE_H
#include
#include
struct soluna_ime_rect_state {
float x;
float y;
float w;
float h;
uint32_t text_color;
bool valid;
};
extern struct soluna_ime_rect_state g_soluna_ime_rect;
#endif /* SOLUNA_IME_STATE_H */
================================================
FILE: src/lcrypt.c
================================================
#include
#include
#include
#include
#include
#include
#define PADDING_MODE_ISO7816_4 0
#define PADDING_MODE_PKCS7 1
#define PADDING_MODE_COUNT 2
#define SMALL_CHUNK 256
/* the eight DES S-boxes */
static uint32_t SB1[64] = {
0x01010400, 0x00000000, 0x00010000, 0x01010404,
0x01010004, 0x00010404, 0x00000004, 0x00010000,
0x00000400, 0x01010400, 0x01010404, 0x00000400,
0x01000404, 0x01010004, 0x01000000, 0x00000004,
0x00000404, 0x01000400, 0x01000400, 0x00010400,
0x00010400, 0x01010000, 0x01010000, 0x01000404,
0x00010004, 0x01000004, 0x01000004, 0x00010004,
0x00000000, 0x00000404, 0x00010404, 0x01000000,
0x00010000, 0x01010404, 0x00000004, 0x01010000,
0x01010400, 0x01000000, 0x01000000, 0x00000400,
0x01010004, 0x00010000, 0x00010400, 0x01000004,
0x00000400, 0x00000004, 0x01000404, 0x00010404,
0x01010404, 0x00010004, 0x01010000, 0x01000404,
0x01000004, 0x00000404, 0x00010404, 0x01010400,
0x00000404, 0x01000400, 0x01000400, 0x00000000,
0x00010004, 0x00010400, 0x00000000, 0x01010004
};
static uint32_t SB2[64] = {
0x80108020, 0x80008000, 0x00008000, 0x00108020,
0x00100000, 0x00000020, 0x80100020, 0x80008020,
0x80000020, 0x80108020, 0x80108000, 0x80000000,
0x80008000, 0x00100000, 0x00000020, 0x80100020,
0x00108000, 0x00100020, 0x80008020, 0x00000000,
0x80000000, 0x00008000, 0x00108020, 0x80100000,
0x00100020, 0x80000020, 0x00000000, 0x00108000,
0x00008020, 0x80108000, 0x80100000, 0x00008020,
0x00000000, 0x00108020, 0x80100020, 0x00100000,
0x80008020, 0x80100000, 0x80108000, 0x00008000,
0x80100000, 0x80008000, 0x00000020, 0x80108020,
0x00108020, 0x00000020, 0x00008000, 0x80000000,
0x00008020, 0x80108000, 0x00100000, 0x80000020,
0x00100020, 0x80008020, 0x80000020, 0x00100020,
0x00108000, 0x00000000, 0x80008000, 0x00008020,
0x80000000, 0x80100020, 0x80108020, 0x00108000
};
static uint32_t SB3[64] = {
0x00000208, 0x08020200, 0x00000000, 0x08020008,
0x08000200, 0x00000000, 0x00020208, 0x08000200,
0x00020008, 0x08000008, 0x08000008, 0x00020000,
0x08020208, 0x00020008, 0x08020000, 0x00000208,
0x08000000, 0x00000008, 0x08020200, 0x00000200,
0x00020200, 0x08020000, 0x08020008, 0x00020208,
0x08000208, 0x00020200, 0x00020000, 0x08000208,
0x00000008, 0x08020208, 0x00000200, 0x08000000,
0x08020200, 0x08000000, 0x00020008, 0x00000208,
0x00020000, 0x08020200, 0x08000200, 0x00000000,
0x00000200, 0x00020008, 0x08020208, 0x08000200,
0x08000008, 0x00000200, 0x00000000, 0x08020008,
0x08000208, 0x00020000, 0x08000000, 0x08020208,
0x00000008, 0x00020208, 0x00020200, 0x08000008,
0x08020000, 0x08000208, 0x00000208, 0x08020000,
0x00020208, 0x00000008, 0x08020008, 0x00020200
};
static uint32_t SB4[64] = {
0x00802001, 0x00002081, 0x00002081, 0x00000080,
0x00802080, 0x00800081, 0x00800001, 0x00002001,
0x00000000, 0x00802000, 0x00802000, 0x00802081,
0x00000081, 0x00000000, 0x00800080, 0x00800001,
0x00000001, 0x00002000, 0x00800000, 0x00802001,
0x00000080, 0x00800000, 0x00002001, 0x00002080,
0x00800081, 0x00000001, 0x00002080, 0x00800080,
0x00002000, 0x00802080, 0x00802081, 0x00000081,
0x00800080, 0x00800001, 0x00802000, 0x00802081,
0x00000081, 0x00000000, 0x00000000, 0x00802000,
0x00002080, 0x00800080, 0x00800081, 0x00000001,
0x00802001, 0x00002081, 0x00002081, 0x00000080,
0x00802081, 0x00000081, 0x00000001, 0x00002000,
0x00800001, 0x00002001, 0x00802080, 0x00800081,
0x00002001, 0x00002080, 0x00800000, 0x00802001,
0x00000080, 0x00800000, 0x00002000, 0x00802080
};
static uint32_t SB5[64] = {
0x00000100, 0x02080100, 0x02080000, 0x42000100,
0x00080000, 0x00000100, 0x40000000, 0x02080000,
0x40080100, 0x00080000, 0x02000100, 0x40080100,
0x42000100, 0x42080000, 0x00080100, 0x40000000,
0x02000000, 0x40080000, 0x40080000, 0x00000000,
0x40000100, 0x42080100, 0x42080100, 0x02000100,
0x42080000, 0x40000100, 0x00000000, 0x42000000,
0x02080100, 0x02000000, 0x42000000, 0x00080100,
0x00080000, 0x42000100, 0x00000100, 0x02000000,
0x40000000, 0x02080000, 0x42000100, 0x40080100,
0x02000100, 0x40000000, 0x42080000, 0x02080100,
0x40080100, 0x00000100, 0x02000000, 0x42080000,
0x42080100, 0x00080100, 0x42000000, 0x42080100,
0x02080000, 0x00000000, 0x40080000, 0x42000000,
0x00080100, 0x02000100, 0x40000100, 0x00080000,
0x00000000, 0x40080000, 0x02080100, 0x40000100
};
static uint32_t SB6[64] = {
0x20000010, 0x20400000, 0x00004000, 0x20404010,
0x20400000, 0x00000010, 0x20404010, 0x00400000,
0x20004000, 0x00404010, 0x00400000, 0x20000010,
0x00400010, 0x20004000, 0x20000000, 0x00004010,
0x00000000, 0x00400010, 0x20004010, 0x00004000,
0x00404000, 0x20004010, 0x00000010, 0x20400010,
0x20400010, 0x00000000, 0x00404010, 0x20404000,
0x00004010, 0x00404000, 0x20404000, 0x20000000,
0x20004000, 0x00000010, 0x20400010, 0x00404000,
0x20404010, 0x00400000, 0x00004010, 0x20000010,
0x00400000, 0x20004000, 0x20000000, 0x00004010,
0x20000010, 0x20404010, 0x00404000, 0x20400000,
0x00404010, 0x20404000, 0x00000000, 0x20400010,
0x00000010, 0x00004000, 0x20400000, 0x00404010,
0x00004000, 0x00400010, 0x20004010, 0x00000000,
0x20404000, 0x20000000, 0x00400010, 0x20004010
};
static uint32_t SB7[64] = {
0x00200000, 0x04200002, 0x04000802, 0x00000000,
0x00000800, 0x04000802, 0x00200802, 0x04200800,
0x04200802, 0x00200000, 0x00000000, 0x04000002,
0x00000002, 0x04000000, 0x04200002, 0x00000802,
0x04000800, 0x00200802, 0x00200002, 0x04000800,
0x04000002, 0x04200000, 0x04200800, 0x00200002,
0x04200000, 0x00000800, 0x00000802, 0x04200802,
0x00200800, 0x00000002, 0x04000000, 0x00200800,
0x04000000, 0x00200800, 0x00200000, 0x04000802,
0x04000802, 0x04200002, 0x04200002, 0x00000002,
0x00200002, 0x04000000, 0x04000800, 0x00200000,
0x04200800, 0x00000802, 0x00200802, 0x04200800,
0x00000802, 0x04000002, 0x04200802, 0x04200000,
0x00200800, 0x00000000, 0x00000002, 0x04200802,
0x00000000, 0x00200802, 0x04200000, 0x00000800,
0x04000002, 0x04000800, 0x00000800, 0x00200002
};
static uint32_t SB8[64] = {
0x10001040, 0x00001000, 0x00040000, 0x10041040,
0x10000000, 0x10001040, 0x00000040, 0x10000000,
0x00040040, 0x10040000, 0x10041040, 0x00041000,
0x10041000, 0x00041040, 0x00001000, 0x00000040,
0x10040000, 0x10000040, 0x10001000, 0x00001040,
0x00041000, 0x00040040, 0x10040040, 0x10041000,
0x00001040, 0x00000000, 0x00000000, 0x10040040,
0x10000040, 0x10001000, 0x00041040, 0x00040000,
0x00041040, 0x00040000, 0x10041000, 0x00001000,
0x00000040, 0x10040040, 0x00001000, 0x00041040,
0x10001000, 0x00000040, 0x10000040, 0x10040000,
0x10040040, 0x10000000, 0x00040000, 0x10001040,
0x00000000, 0x10041040, 0x00040040, 0x10000040,
0x10040000, 0x10001000, 0x10001040, 0x00000000,
0x10041040, 0x00041000, 0x00041000, 0x00001040,
0x00001040, 0x00040040, 0x10000000, 0x10041000
};
/* PC1: left and right halves bit-swap */
static uint32_t LHs[16] = {
0x00000000, 0x00000001, 0x00000100, 0x00000101,
0x00010000, 0x00010001, 0x00010100, 0x00010101,
0x01000000, 0x01000001, 0x01000100, 0x01000101,
0x01010000, 0x01010001, 0x01010100, 0x01010101
};
static uint32_t RHs[16] = {
0x00000000, 0x01000000, 0x00010000, 0x01010000,
0x00000100, 0x01000100, 0x00010100, 0x01010100,
0x00000001, 0x01000001, 0x00010001, 0x01010001,
0x00000101, 0x01000101, 0x00010101, 0x01010101,
};
/* platform-independant 32-bit integer manipulation macros */
#define GET_UINT32(n,b,i) \
{ \
(n) = ( (uint32_t) (b)[(i) ] << 24 ) \
| ( (uint32_t) (b)[(i) + 1] << 16 ) \
| ( (uint32_t) (b)[(i) + 2] << 8 ) \
| ( (uint32_t) (b)[(i) + 3] ); \
}
#define PUT_UINT32(n,b,i) \
{ \
(b)[(i) ] = (uint8_t) ( (n) >> 24 ); \
(b)[(i) + 1] = (uint8_t) ( (n) >> 16 ); \
(b)[(i) + 2] = (uint8_t) ( (n) >> 8 ); \
(b)[(i) + 3] = (uint8_t) ( (n) ); \
}
/* Initial Permutation macro */
#define DES_IP(X,Y) \
{ \
T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \
T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \
T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \
T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \
Y = ((Y << 1) | (Y >> 31)) & 0xFFFFFFFF; \
T = (X ^ Y) & 0xAAAAAAAA; Y ^= T; X ^= T; \
X = ((X << 1) | (X >> 31)) & 0xFFFFFFFF; \
}
/* Final Permutation macro */
#define DES_FP(X,Y) \
{ \
X = ((X << 31) | (X >> 1)) & 0xFFFFFFFF; \
T = (X ^ Y) & 0xAAAAAAAA; X ^= T; Y ^= T; \
Y = ((Y << 31) | (Y >> 1)) & 0xFFFFFFFF; \
T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \
T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \
T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \
T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \
}
/* DES round macro */
#define DES_ROUND(X,Y) \
{ \
T = *SK++ ^ X; \
Y ^= SB8[ (T ) & 0x3F ] ^ \
SB6[ (T >> 8) & 0x3F ] ^ \
SB4[ (T >> 16) & 0x3F ] ^ \
SB2[ (T >> 24) & 0x3F ]; \
\
T = *SK++ ^ ((X << 28) | (X >> 4)); \
Y ^= SB7[ (T ) & 0x3F ] ^ \
SB5[ (T >> 8) & 0x3F ] ^ \
SB3[ (T >> 16) & 0x3F ] ^ \
SB1[ (T >> 24) & 0x3F ]; \
}
/* DES key schedule */
static void
des_main_ks( uint32_t SK[32], const uint8_t key[8] ) {
int i;
uint32_t X, Y, T;
GET_UINT32( X, key, 0 );
GET_UINT32( Y, key, 4 );
/* Permuted Choice 1 */
T = ((Y >> 4) ^ X) & 0x0F0F0F0F; X ^= T; Y ^= (T << 4);
T = ((Y ) ^ X) & 0x10101010; X ^= T; Y ^= (T );
X = (LHs[ (X ) & 0xF] << 3) | (LHs[ (X >> 8) & 0xF ] << 2)
| (LHs[ (X >> 16) & 0xF] << 1) | (LHs[ (X >> 24) & 0xF ] )
| (LHs[ (X >> 5) & 0xF] << 7) | (LHs[ (X >> 13) & 0xF ] << 6)
| (LHs[ (X >> 21) & 0xF] << 5) | (LHs[ (X >> 29) & 0xF ] << 4);
Y = (RHs[ (Y >> 1) & 0xF] << 3) | (RHs[ (Y >> 9) & 0xF ] << 2)
| (RHs[ (Y >> 17) & 0xF] << 1) | (RHs[ (Y >> 25) & 0xF ] )
| (RHs[ (Y >> 4) & 0xF] << 7) | (RHs[ (Y >> 12) & 0xF ] << 6)
| (RHs[ (Y >> 20) & 0xF] << 5) | (RHs[ (Y >> 28) & 0xF ] << 4);
X &= 0x0FFFFFFF;
Y &= 0x0FFFFFFF;
/* calculate subkeys */
for( i = 0; i < 16; i++ )
{
if( i < 2 || i == 8 || i == 15 )
{
X = ((X << 1) | (X >> 27)) & 0x0FFFFFFF;
Y = ((Y << 1) | (Y >> 27)) & 0x0FFFFFFF;
}
else
{
X = ((X << 2) | (X >> 26)) & 0x0FFFFFFF;
Y = ((Y << 2) | (Y >> 26)) & 0x0FFFFFFF;
}
*SK++ = ((X << 4) & 0x24000000) | ((X << 28) & 0x10000000)
| ((X << 14) & 0x08000000) | ((X << 18) & 0x02080000)
| ((X << 6) & 0x01000000) | ((X << 9) & 0x00200000)
| ((X >> 1) & 0x00100000) | ((X << 10) & 0x00040000)
| ((X << 2) & 0x00020000) | ((X >> 10) & 0x00010000)
| ((Y >> 13) & 0x00002000) | ((Y >> 4) & 0x00001000)
| ((Y << 6) & 0x00000800) | ((Y >> 1) & 0x00000400)
| ((Y >> 14) & 0x00000200) | ((Y ) & 0x00000100)
| ((Y >> 5) & 0x00000020) | ((Y >> 10) & 0x00000010)
| ((Y >> 3) & 0x00000008) | ((Y >> 18) & 0x00000004)
| ((Y >> 26) & 0x00000002) | ((Y >> 24) & 0x00000001);
*SK++ = ((X << 15) & 0x20000000) | ((X << 17) & 0x10000000)
| ((X << 10) & 0x08000000) | ((X << 22) & 0x04000000)
| ((X >> 2) & 0x02000000) | ((X << 1) & 0x01000000)
| ((X << 16) & 0x00200000) | ((X << 11) & 0x00100000)
| ((X << 3) & 0x00080000) | ((X >> 6) & 0x00040000)
| ((X << 15) & 0x00020000) | ((X >> 4) & 0x00010000)
| ((Y >> 2) & 0x00002000) | ((Y << 8) & 0x00001000)
| ((Y >> 14) & 0x00000808) | ((Y >> 9) & 0x00000400)
| ((Y ) & 0x00000200) | ((Y << 7) & 0x00000100)
| ((Y >> 7) & 0x00000020) | ((Y >> 3) & 0x00000011)
| ((Y << 2) & 0x00000004) | ((Y >> 21) & 0x00000002);
}
}
/* DES 64-bit block encryption/decryption */
static void
des_crypt( const uint32_t SK[32], const uint8_t input[8], uint8_t output[8] ) {
uint32_t X, Y, T;
GET_UINT32( X, input, 0 );
GET_UINT32( Y, input, 4 );
DES_IP( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_FP( Y, X );
PUT_UINT32( Y, output, 0 );
PUT_UINT32( X, output, 4 );
}
static int
lrandomkey(lua_State *L) {
char tmp[8];
int i;
char x = 0;
for (i=0;i<8;i++) {
tmp[i] = rand() & 0xff;
x ^= tmp[i];
}
if (x==0) {
tmp[0] |= 1; // avoid 0
}
lua_pushlstring(L, tmp, 8);
return 1;
}
static void
padding_mode_table(lua_State *L) {
// see macros PADDING_MODE_ISO7816_4, etc.
const char * mode[] = {
"iso7816_4",
"pkcs7",
};
int n = sizeof(mode) / sizeof(mode[0]);
int i;
lua_createtable(L,0,n);
for (i=0;i= PADDING_MODE_COUNT)
luaL_error(L, "Invalid padding mode %d", mode);
}
static void
add_padding(lua_State *L, uint8_t buf[8], const uint8_t *src, int offset, int mode) {
check_padding_mode(L, mode);
if (offset >= 8)
luaL_error(L, "Invalid padding");
memcpy(buf, src, offset);
padding_add_func[mode](buf, offset);
}
static int
remove_padding(lua_State *L, const uint8_t *last, int mode) {
check_padding_mode(L, mode);
return padding_remove_func[mode](last);
}
static void
des_key(lua_State *L, uint32_t SK[32]) {
size_t keysz = 0;
const void * key = luaL_checklstring(L, 1, &keysz);
if (keysz != 8) {
luaL_error(L, "Invalid key size %d, need 8 bytes", (int)keysz);
}
des_main_ks(SK, (const uint8_t*)key);
}
static int
ldesencode(lua_State *L) {
uint32_t SK[32];
des_key(L, SK);
size_t textsz = 0;
const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 2, &textsz);
size_t chunksz = (textsz + 8) & ~7;
int padding_mode = luaL_optinteger(L, 3, PADDING_MODE_ISO7816_4);
uint8_t tmp[SMALL_CHUNK];
uint8_t *buffer = tmp;
if (chunksz > SMALL_CHUNK) {
buffer = (uint8_t*)lua_newuserdatauv(L, chunksz, 0);
}
int i;
for (i=0;i<(int)textsz-7;i+=8) {
des_crypt(SK, text+i, buffer+i);
}
uint8_t tail[8];
add_padding(L, tail, text+i, textsz - i, padding_mode);
des_crypt(SK, tail, buffer+i);
lua_pushlstring(L, (const char *)buffer, chunksz);
return 1;
}
static int
ldesdecode(lua_State *L) {
uint32_t ESK[32];
des_key(L, ESK);
uint32_t SK[32];
int i;
for( i = 0; i < 32; i += 2 ) {
SK[i] = ESK[30 - i];
SK[i + 1] = ESK[31 - i];
}
size_t textsz = 0;
const uint8_t *text = (const uint8_t *)luaL_checklstring(L, 2, &textsz);
if ((textsz & 7) || textsz == 0) {
return luaL_error(L, "Invalid des crypt text length %d", (int)textsz);
}
int padding_mode = luaL_optinteger(L, 3, PADDING_MODE_ISO7816_4);
uint8_t tmp[SMALL_CHUNK];
uint8_t *buffer = tmp;
if (textsz > SMALL_CHUNK) {
buffer = (uint8_t*)lua_newuserdatauv(L, textsz, 0);
}
for (i=0;i 8) {
return luaL_error(L, "Invalid des crypt text");
}
lua_pushlstring(L, (const char *)buffer, textsz - padding);
return 1;
}
static void
Hash(const char * str, int sz, uint8_t key[8]) {
uint32_t djb_hash = 5381L;
uint32_t js_hash = 1315423911L;
int i;
for (i=0;i> 2));
}
key[0] = djb_hash & 0xff;
key[1] = (djb_hash >> 8) & 0xff;
key[2] = (djb_hash >> 16) & 0xff;
key[3] = (djb_hash >> 24) & 0xff;
key[4] = js_hash & 0xff;
key[5] = (js_hash >> 8) & 0xff;
key[6] = (js_hash >> 16) & 0xff;
key[7] = (js_hash >> 24) & 0xff;
}
static int
lhashkey(lua_State *L) {
size_t sz = 0;
const char * key = luaL_checklstring(L, 1, &sz);
uint8_t realkey[8];
Hash(key,(int)sz,realkey);
lua_pushlstring(L, (const char *)realkey, 8);
return 1;
}
static int
ltohex(lua_State *L) {
static char hex[] = "0123456789abcdef";
size_t sz = 0;
const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz);
char tmp[SMALL_CHUNK];
char *buffer = tmp;
if (sz > SMALL_CHUNK/2) {
buffer = (char*)lua_newuserdatauv(L, sz * 2, 0);
}
int i;
for (i=0;i> 4];
buffer[i*2+1] = hex[text[i] & 0xf];
}
lua_pushlstring(L, buffer, sz * 2);
return 1;
}
#define HEX(v,c) { char tmp = (char) c; if (tmp >= '0' && tmp <= '9') { v = tmp-'0'; } else { v = tmp - 'a' + 10; } }
static int
lfromhex(lua_State *L) {
size_t sz = 0;
const char * text = luaL_checklstring(L, 1, &sz);
if (sz & 1) {
return luaL_error(L, "Invalid hex text size %d", (int)sz);
}
char tmp[SMALL_CHUNK];
char *buffer = tmp;
if (sz > SMALL_CHUNK*2) {
buffer = (char*)lua_newuserdatauv(L, sz / 2, 0);
}
int i;
for (i=0;i 16 || low > 16) {
return luaL_error(L, "Invalid hex text", text);
}
buffer[i/2] = hi<<4 | low;
}
lua_pushlstring(L, buffer, i/2);
return 1;
}
// Constants are the integer part of the sines of integers (in radians) * 2^32.
static const uint32_t k[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee ,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501 ,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be ,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821 ,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa ,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8 ,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed ,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a ,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c ,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70 ,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05 ,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665 ,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039 ,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1 ,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1 ,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 };
// r specifies the per-round shift amounts
static const uint32_t r[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
// leftrotate function definition
#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c))))
static void
digest_md5(uint32_t w[16], uint32_t result[4]) {
uint32_t a, b, c, d, f, g, temp;
int i;
a = 0x67452301u;
b = 0xefcdab89u;
c = 0x98badcfeu;
d = 0x10325476u;
for(i = 0; i<64; i++) {
if (i < 16) {
f = (b & c) | ((~b) & d);
g = i;
} else if (i < 32) {
f = (d & b) | ((~d) & c);
g = (5*i + 1) % 16;
} else if (i < 48) {
f = b ^ c ^ d;
g = (3*i + 5) % 16;
} else {
f = c ^ (b | (~d));
g = (7*i) % 16;
}
temp = d;
d = c;
c = b;
b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i]);
a = temp;
}
result[0] = a;
result[1] = b;
result[2] = c;
result[3] = d;
}
// hmac64 use md5 algorithm without padding, and the result is (c^d .. a^b)
static void
hmac(uint32_t x[2], uint32_t y[2], uint32_t result[2]) {
uint32_t w[16];
uint32_t r[4];
int i;
for (i=0;i<16;i+=4) {
w[i] = x[1];
w[i+1] = x[0];
w[i+2] = y[1];
w[i+3] = y[0];
}
digest_md5(w,r);
result[0] = r[2]^r[3];
result[1] = r[0]^r[1];
}
static void
hmac_md5(uint32_t x[2], uint32_t y[2], uint32_t result[2]) {
uint32_t w[16];
uint32_t r[4];
int i;
for (i=0;i<12;i+=4) {
w[i] = x[0];
w[i+1] = x[1];
w[i+2] = y[0];
w[i+3] = y[1];
}
w[12] = 0x80;
w[13] = 0;
w[14] = 384;
w[15] = 0;
digest_md5(w,r);
result[0] = (r[0] + 0x67452301u) ^ (r[2] + 0x98badcfeu);
result[1] = (r[1] + 0xefcdab89u) ^ (r[3] + 0x10325476u);
}
static void
read64(lua_State *L, uint32_t xx[2], uint32_t yy[2]) {
size_t sz = 0;
const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz);
if (sz != 8) {
luaL_error(L, "Invalid uint64 x");
}
const uint8_t *y = (const uint8_t *)luaL_checklstring(L, 2, &sz);
if (sz != 8) {
luaL_error(L, "Invalid uint64 y");
}
xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24;
xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24;
yy[0] = y[0] | y[1]<<8 | y[2]<<16 | y[3]<<24;
yy[1] = y[4] | y[5]<<8 | y[6]<<16 | y[7]<<24;
}
static int
pushqword(lua_State *L, uint32_t result[2]) {
uint8_t tmp[8];
tmp[0] = result[0] & 0xff;
tmp[1] = (result[0] >> 8 )& 0xff;
tmp[2] = (result[0] >> 16 )& 0xff;
tmp[3] = (result[0] >> 24 )& 0xff;
tmp[4] = result[1] & 0xff;
tmp[5] = (result[1] >> 8 )& 0xff;
tmp[6] = (result[1] >> 16 )& 0xff;
tmp[7] = (result[1] >> 24 )& 0xff;
lua_pushlstring(L, (const char *)tmp, 8);
return 1;
}
static int
lhmac64(lua_State *L) {
uint32_t x[2], y[2];
read64(L, x, y);
uint32_t result[2];
hmac(x,y,result);
return pushqword(L, result);
}
/*
h1 = crypt.hmac64_md5(a,b)
m = md5.sum((a..b):rep(3))
h2 = crypt.xor_str(m:sub(1,8), m:sub(9,16))
assert(h1 == h2)
*/
static int
lhmac64_md5(lua_State *L) {
uint32_t x[2], y[2];
read64(L, x, y);
uint32_t result[2];
hmac_md5(x,y,result);
return pushqword(L, result);
}
/*
8bytes key
string text
*/
static int
lhmac_hash(lua_State *L) {
uint32_t key[2];
size_t sz = 0;
const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz);
if (sz != 8) {
luaL_error(L, "Invalid uint64 key");
}
key[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24;
key[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24;
const char * text = luaL_checklstring(L, 2, &sz);
uint8_t h[8];
Hash(text,(int)sz,h);
uint32_t htext[2];
htext[0] = h[0] | h[1]<<8 | h[2]<<16 | h[3]<<24;
htext[1] = h[4] | h[5]<<8 | h[6]<<16 | h[7]<<24;
uint32_t result[2];
hmac(htext,key,result);
return pushqword(L, result);
}
// powmodp64 for DH-key exchange
// The biggest 64bit prime
#define P 0xffffffffffffffc5ull
static inline uint64_t
mul_mod_p(uint64_t a, uint64_t b) {
uint64_t m = 0;
while(b) {
if(b&1) {
uint64_t t = P-a;
if ( m >= t) {
m -= t;
} else {
m += a;
}
}
if (a >= P - a) {
a = a * 2 - P;
} else {
a = a * 2;
}
b>>=1;
}
return m;
}
static inline uint64_t
pow_mod_p(uint64_t a, uint64_t b) {
if (b==1) {
return a;
}
uint64_t t = pow_mod_p(a, b>>1);
t = mul_mod_p(t,t);
if (b % 2) {
t = mul_mod_p(t, a);
}
return t;
}
// calc a^b % p
static uint64_t
powmodp(uint64_t a, uint64_t b) {
if (a > P)
a%=P;
return pow_mod_p(a,b);
}
static void
push64(lua_State *L, uint64_t r) {
uint8_t tmp[8];
tmp[0] = r & 0xff;
tmp[1] = (r >> 8 )& 0xff;
tmp[2] = (r >> 16 )& 0xff;
tmp[3] = (r >> 24 )& 0xff;
tmp[4] = (r >> 32 )& 0xff;
tmp[5] = (r >> 40 )& 0xff;
tmp[6] = (r >> 48 )& 0xff;
tmp[7] = (r >> 56 )& 0xff;
lua_pushlstring(L, (const char *)tmp, 8);
}
static int
ldhsecret(lua_State *L) {
uint32_t x[2], y[2];
read64(L, x, y);
uint64_t xx = (uint64_t)x[0] | (uint64_t)x[1]<<32;
uint64_t yy = (uint64_t)y[0] | (uint64_t)y[1]<<32;
if (xx == 0 || yy == 0)
return luaL_error(L, "Can't be 0");
uint64_t r = powmodp(xx, yy);
push64(L, r);
return 1;
}
#define G 5
static int
ldhexchange(lua_State *L) {
size_t sz = 0;
const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz);
if (sz != 8) {
luaL_error(L, "Invalid dh uint64 key");
}
uint32_t xx[2];
xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24;
xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24;
uint64_t x64 = (uint64_t)xx[0] | (uint64_t)xx[1]<<32;
if (x64 == 0)
return luaL_error(L, "Can't be 0");
uint64_t r = powmodp(G, x64);
push64(L, r);
return 1;
}
// base64
static int
lb64encode(lua_State *L) {
static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t sz = 0;
const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz);
int encode_sz = (sz + 2)/3*4;
char tmp[SMALL_CHUNK];
char *buffer = tmp;
if (encode_sz > SMALL_CHUNK) {
buffer = (char*)lua_newuserdatauv(L, encode_sz, 0);
}
int i,j;
j=0;
for (i=0;i<(int)sz-2;i+=3) {
uint32_t v = text[i] << 16 | text[i+1] << 8 | text[i+2];
buffer[j] = encoding[v >> 18];
buffer[j+1] = encoding[(v >> 12) & 0x3f];
buffer[j+2] = encoding[(v >> 6) & 0x3f];
buffer[j+3] = encoding[(v) & 0x3f];
j+=4;
}
int padding = sz-i;
uint32_t v;
switch(padding) {
case 1 :
v = text[i];
buffer[j] = encoding[v >> 2];
buffer[j+1] = encoding[(v & 3) << 4];
buffer[j+2] = '=';
buffer[j+3] = '=';
break;
case 2 :
v = text[i] << 8 | text[i+1];
buffer[j] = encoding[v >> 10];
buffer[j+1] = encoding[(v >> 4) & 0x3f];
buffer[j+2] = encoding[(v & 0xf) << 2];
buffer[j+3] = '=';
break;
}
lua_pushlstring(L, buffer, encode_sz);
return 1;
}
static inline int
b64index(uint8_t c) {
static const int decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
int decoding_size = sizeof(decoding)/sizeof(decoding[0]);
if (c<43) {
return -1;
}
c -= 43;
if (c>=decoding_size)
return -1;
return decoding[c];
}
static int
lb64decode(lua_State *L) {
size_t sz = 0;
const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz);
int decode_sz = (sz+3)/4*3;
char tmp[SMALL_CHUNK];
char *buffer = tmp;
if (decode_sz > SMALL_CHUNK) {
buffer = (char*)lua_newuserdatauv(L, decode_sz, 0);
}
int i,j;
int output = 0;
for (i=0;i=sz && 4>j){
/*To improve compatibility, there may not be enough equal signs */
c[j] = -2;
}else{
c[j] = b64index(text[i]);
}
if (c[j] == -1) {
++i;
continue;
}
if (c[j] == -2) {
++padding;
}
++i;
++j;
}
uint32_t v;
switch (padding) {
case 0:
v = (unsigned)c[0] << 18 | c[1] << 12 | c[2] << 6 | c[3];
buffer[output] = v >> 16;
buffer[output+1] = (v >> 8) & 0xff;
buffer[output+2] = v & 0xff;
output += 3;
break;
case 1:
if (c[3] != -2 || (c[2] & 3)!=0) {
return luaL_error(L, "Invalid base64 text");
}
v = (unsigned)c[0] << 10 | c[1] << 4 | c[2] >> 2 ;
buffer[output] = v >> 8;
buffer[output+1] = v & 0xff;
output += 2;
break;
case 2:
if (c[3] != -2 || c[2] != -2 || (c[1] & 0xf) !=0) {
return luaL_error(L, "Invalid base64 text");
}
v = (unsigned)c[0] << 2 | c[1] >> 4;
buffer[output] = v;
++ output;
break;
default:
return luaL_error(L, "Invalid base64 text");
}
}
lua_pushlstring(L, buffer, output);
return 1;
}
static int
lxor_str(lua_State *L) {
size_t len1,len2;
const char *s1 = luaL_checklstring(L,1,&len1);
const char *s2 = luaL_checklstring(L,2,&len2);
if (len2 == 0) {
return luaL_error(L, "Can't xor empty string");
}
luaL_Buffer b;
char * buffer = luaL_buffinitsize(L, &b, len1);
int i;
for (i=0;i
#include
#include
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
#define LONGPATH_MAX 4096
#include
#include
#include
#include
#define STAT_STRUCT struct _stati64
#define STAT_FUNC _wstati64
#ifndef S_ISDIR
#define S_ISDIR(mode) (mode&_S_IFDIR)
#endif
#ifndef S_ISREG
#define S_ISREG(mode) (mode&_S_IFREG)
#endif
#ifndef S_ISLNK
#define S_ISLNK(mode) (0)
#endif
#ifndef S_ISSOCK
#define S_ISSOCK(mode) (0)
#endif
#ifndef S_ISFIFO
#define S_ISFIFO(mode) (0)
#endif
#ifndef S_ISCHR
#define S_ISCHR(mode) (mode&_S_IFCHR)
#endif
#ifndef S_ISBLK
#define S_ISBLK(mode) (0)
#endif
static int
utf8_filename(lua_State *L, const wchar_t * winfilename, int wsz, char *utf8buffer, int sz) {
int result = WideCharToMultiByte(CP_UTF8, 0, winfilename, wsz, utf8buffer, sz, NULL, NULL);
if (result == 0)
return luaL_error(L, "convert to utf-8 filename fail");
if (wsz < 0) // not include end \0
return result - 1;
if (result >= sz)
return luaL_error(L, "convert to utf-8 filename : buffer overflow");
utf8buffer[result] = 0;
return result;
}
#define DIR_METATABLE "SOLUNA_DIR"
struct dir_data {
HANDLE findfile;
int closed;
};
static int
windows_filename(lua_State *L, const char * utf8filename, int usz, wchar_t * winbuffer, int wsz) {
int result = MultiByteToWideChar(CP_UTF8, 0, utf8filename, usz, winbuffer, wsz);
if (result == 0)
return luaL_error(L, "convert to windows utf-16 filename fail");
if (result < 0)
return result - 1;
if (result >= wsz)
return luaL_error(L, "convert to windows utf-16 filename : buffer overflow");
winbuffer[result] = 0;
return result;
}
static void
system_error(lua_State *L, DWORD errcode) {
wchar_t * errormsg;
DWORD n = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errcode, 0,
(void *)&errormsg, sizeof(errormsg),
NULL);
if (n == 0) {
lua_pushfstring(L, "Unknown error %04X", errcode);
} else {
int i;
for (i=n;i>=0;i--) {
if (errormsg[i] == 0 || errormsg[i] == '\n' || errormsg[i] == '\r')
--n;
else {
break;
}
}
char tmp[LONGPATH_MAX];
int len = utf8_filename(L, errormsg, n, tmp, LONGPATH_MAX);
lua_pushlstring(L, tmp, len);
HeapFree(GetProcessHeap(), 0, errormsg);
}
}
static int
error_return(lua_State *L) {
lua_pushnil(L);
system_error(L, GetLastError());
return 2;
}
static void
push_filename(lua_State *L, WIN32_FIND_DATAW *data) {
char firstname[LONGPATH_MAX];
int ulen = utf8_filename(L, data->cFileName, -1, firstname, LONGPATH_MAX);
lua_pushlstring(L, firstname, ulen);
}
static int
dir_iter(lua_State *L) {
struct dir_data *d = luaL_checkudata(L, 1, DIR_METATABLE);
luaL_argcheck (L, d->closed == 0, 1, "closed directory");
if (d->findfile == INVALID_HANDLE_VALUE) {
// no find found
d->closed = 1;
return 0;
}
if (lua_getuservalue(L, 1) == LUA_TSTRING) {
// find time
lua_pushnil(L);
lua_setuservalue(L, 1);
return 1;
} else {
WIN32_FIND_DATAW data;
if (FindNextFileW(d->findfile, &data)) {
push_filename(L, &data);
return 1;
} else {
DWORD errcode = GetLastError();
FindClose(d->findfile);
d->findfile = INVALID_HANDLE_VALUE;
d->closed = 1;
if (errcode == ERROR_NO_MORE_FILES)
return 0;
lua_pushnil(L);
system_error(L, errcode);
return 2;
}
}
}
static int
dir_close(lua_State *L) {
struct dir_data *d = luaL_checkudata(L, 1, DIR_METATABLE);
if (d->findfile != INVALID_HANDLE_VALUE) {
FindClose(d->findfile);
d->findfile = INVALID_HANDLE_VALUE;
}
d->closed = 1;
return 0;
}
static int
ldir(lua_State *L) {
size_t sz;
const char * pathname = luaL_checklstring(L, 1, &sz);
wchar_t winname[LONGPATH_MAX-3];
int winsz = windows_filename(L, pathname, sz, winname, LONGPATH_MAX-3);
winname[winsz] = '\\';
winname[winsz+1] = '*';
winname[winsz+2] = 0;
WIN32_FIND_DATAW data;
HANDLE findfile = FindFirstFileW(winname, &data);
lua_pushcfunction(L, dir_iter);
if (findfile == INVALID_HANDLE_VALUE) {
DWORD errcode = GetLastError();
if (errcode == ERROR_FILE_NOT_FOUND) {
struct dir_data *d = lua_newuserdata(L, sizeof(*d));
d->findfile = INVALID_HANDLE_VALUE;
d->closed = 0;
} else {
system_error(L, errcode);
return lua_error(L);
}
} else {
struct dir_data *d = lua_newuserdata(L, sizeof(*d));
d->findfile = findfile;
d->closed = 0;
push_filename(L, &data);
lua_setuservalue(L, -2); // set firstname
}
if (luaL_newmetatable(L, DIR_METATABLE)) {
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
lua_pushcfunction (L, dir_iter);
lua_setfield(L, -2, "next");
lua_pushcfunction (L, dir_close);
lua_setfield(L, -2, "close");
lua_pushcfunction (L, dir_close);
lua_setfield (L, -2, "__gc");
}
lua_setmetatable(L, -2);
return 2;
}
static int
lpersonaldir(lua_State *L) {
wchar_t document[LONGPATH_MAX] = {0};
if (SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, document) == S_OK) {
char utf8path[LONGPATH_MAX];
int sz = utf8_filename(L, document, -1, utf8path, LONGPATH_MAX);
lua_pushlstring(L, utf8path, sz);
return 1;
} else {
return error_return(L);
}
}
static int
lcurrentdir(lua_State *L) {
wchar_t path[LONGPATH_MAX];
char utf8path[LONGPATH_MAX];
DWORD sz = GetCurrentDirectoryW(LONGPATH_MAX, path);
if (sz == 0) {
return error_return(L);
}
int usz = utf8_filename(L, path, -1, utf8path, LONGPATH_MAX);
lua_pushlstring(L, utf8path, usz);
return 1;
}
static int
lchdir(lua_State *L) {
size_t sz;
const char * utf8path = luaL_checklstring(L, 1, &sz);
wchar_t path[LONGPATH_MAX];
windows_filename(L, utf8path, sz, path, LONGPATH_MAX);
if (SetCurrentDirectoryW(path) == 0) {
return error_return(L);
}
lua_pushboolean(L, 1);
return 1;
}
static const char *
mode2string (unsigned short mode) {
if ( S_ISREG(mode) )
return "file";
else if ( S_ISDIR(mode) )
return "directory";
else if ( S_ISLNK(mode) )
return "link";
else if ( S_ISSOCK(mode) )
return "socket";
else if ( S_ISFIFO(mode) )
return "named pipe";
else if ( S_ISCHR(mode) )
return "char device";
else if ( S_ISBLK(mode) )
return "block device";
else
return "other";
}
/* inode protection mode */
static void push_st_mode (lua_State *L, STAT_STRUCT *info) {
lua_pushstring (L, mode2string (info->st_mode));
}
/* device inode resides on */
static void push_st_dev (lua_State *L, STAT_STRUCT *info) {
lua_pushinteger (L, (lua_Integer) info->st_dev);
}
/* inode's number */
static void push_st_ino (lua_State *L, STAT_STRUCT *info) {
lua_pushinteger (L, (lua_Integer) info->st_ino);
}
/* number of hard links to the file */
static void push_st_nlink (lua_State *L, STAT_STRUCT *info) {
lua_pushinteger (L, (lua_Integer)info->st_nlink);
}
/* user-id of owner */
static void push_st_uid (lua_State *L, STAT_STRUCT *info) {
lua_pushinteger (L, (lua_Integer)info->st_uid);
}
/* group-id of owner */
static void push_st_gid (lua_State *L, STAT_STRUCT *info) {
lua_pushinteger (L, (lua_Integer)info->st_gid);
}
/* device type, for special file inode */
static void push_st_rdev (lua_State *L, STAT_STRUCT *info) {
lua_pushinteger (L, (lua_Integer) info->st_rdev);
}
/* time of last access */
static void push_st_atime (lua_State *L, STAT_STRUCT *info) {
lua_pushinteger (L, (lua_Integer) info->st_atime);
}
/* time of last data modification */
static void push_st_mtime (lua_State *L, STAT_STRUCT *info) {
lua_pushinteger (L, (lua_Integer) info->st_mtime);
}
/* time of last file status change */
static void push_st_ctime (lua_State *L, STAT_STRUCT *info) {
lua_pushinteger (L, (lua_Integer) info->st_ctime);
}
/* file size, in bytes */
static void push_st_size (lua_State *L, STAT_STRUCT *info) {
lua_pushinteger (L, (lua_Integer)info->st_size);
}
static const char *perm2string (unsigned short mode) {
static char perms[10] = "---------";
int i;
for (i=0;i<9;i++) perms[i]='-';
if (mode & _S_IREAD)
{ perms[0] = 'r'; perms[3] = 'r'; perms[6] = 'r'; }
if (mode & _S_IWRITE)
{ perms[1] = 'w'; perms[4] = 'w'; perms[7] = 'w'; }
if (mode & _S_IEXEC)
{ perms[2] = 'x'; perms[5] = 'x'; perms[8] = 'x'; }
return perms;
}
/* permssions string */
static void push_st_perm (lua_State *L, STAT_STRUCT *info) {
lua_pushstring (L, perm2string (info->st_mode));
}
typedef void (*_push_function) (lua_State *L, STAT_STRUCT *info);
struct _stat_members {
const char *name;
_push_function push;
};
struct _stat_members members[] = {
{ "mode", push_st_mode },
{ "dev", push_st_dev },
{ "ino", push_st_ino },
{ "nlink", push_st_nlink },
{ "uid", push_st_uid },
{ "gid", push_st_gid },
{ "rdev", push_st_rdev },
{ "access", push_st_atime },
{ "modification", push_st_mtime },
{ "change", push_st_ctime },
{ "size", push_st_size },
{ "permissions", push_st_perm },
{ NULL, NULL }
};
/*
** Get file or symbolic link information
*/
static int
file_info (lua_State *L) {
STAT_STRUCT info;
size_t sz;
int i;
const char * utf8path = luaL_checklstring(L, 1, &sz);
wchar_t file[LONGPATH_MAX];
windows_filename(L, utf8path, sz, file, LONGPATH_MAX);
if (STAT_FUNC(file, &info)) {
lua_pushnil(L);
lua_pushfstring(L, "cannot obtain information from file '%s': %s", file, strerror(errno));
lua_pushinteger(L, errno);
return 3;
}
if (lua_isstring (L, 2)) {
const char *member = lua_tostring (L, 2);
for (i = 0; members[i].name; i++) {
if (strcmp(members[i].name, member) == 0) {
/* push member value and return */
members[i].push (L, &info);
return 1;
}
}
/* member not found */
return luaL_error(L, "invalid attribute name '%s'", member);
}
/* creates a table if none is given, removes extra arguments */
lua_settop(L, 2);
if (!lua_istable (L, 2)) {
lua_newtable (L);
}
/* stores all members in table on top of the stack */
for (i = 0; members[i].name; i++) {
lua_pushstring (L, members[i].name);
members[i].push (L, &info);
lua_rawset (L, -3);
}
return 1;
}
static int
lrealpath(lua_State *L) {
size_t sz;
const char * pathname = luaL_checklstring(L, 1, &sz);
wchar_t winname[LONGPATH_MAX];
wchar_t fullname[LONGPATH_MAX];
windows_filename(L, pathname, sz, winname, LONGPATH_MAX);
DWORD r = GetFullPathNameW(winname, LONGPATH_MAX, fullname, NULL);
if (r == 0) {
return error_return(L);
}
if (r > LONGPATH_MAX) {
return luaL_error(L, "Invalid path %s", pathname);
}
char result[LONGPATH_MAX];
int len = utf8_filename(L, fullname, r, result, LONGPATH_MAX);
lua_pushlstring(L, result, len);
return 1;
}
static inline int
create_dir_wchar_(const WCHAR *filenameW) {
WIN32_FIND_DATAW FindFileData;
HANDLE h = FindFirstFileW(filenameW, &FindFileData);
if (h == INVALID_HANDLE_VALUE) {
// create dir
if (CreateDirectoryW(filenameW, NULL) == 0)
return -1;
} else {
if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
FindClose(h);
// dir exist
} else {
FindClose(h);
// not a dir
return 0;
}
}
return 1;
}
static int
mkdir_utf8(const char *name) {
WCHAR filenameW[FILENAME_MAX + 0x200 + 1];
int n = MultiByteToWideChar(CP_UTF8,0,(const char*)name,-1,filenameW,FILENAME_MAX + 0x200);
if (n == 0)
return -1;
return create_dir_wchar_(filenameW);
}
static int
pusherror(lua_State * L) {
lua_pushnil(L);
DWORD err = GetLastError();
LPVOID lpMsgBuf;
if (FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
0,
(LPWSTR) &lpMsgBuf,
0, NULL) == 0) {
lua_pushstring(L, "FormatMessage failed");
}
char errtext[1024] = "unknown";
size_t sz = wcslen((LPWSTR)lpMsgBuf);
WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)lpMsgBuf, sz, errtext, 1024, NULL, NULL);
LocalFree(lpMsgBuf);
lua_pushstring(L, errtext);
lua_pushinteger(L, err);
return 3;
}
#else
#define LONGPATH_MAX 4096
#include
#include
#include
#include
#include
#include
#include
#define STAT_STRUCT struct stat
#define STAT_FUNC stat
#define DIR_METATABLE "SOLUNA_DIR"
struct dir_data {
DIR* dir;
int closed;
};
static void
system_error(lua_State *L, int errcode) {
lua_pushstring(L, strerror(errcode));
}
static int
error_return(lua_State *L) {
lua_pushnil(L);
system_error(L, errno);
return 2;
}
static int
dir_iter(lua_State *L) {
struct dir_data *d = luaL_checkudata(L, 1, DIR_METATABLE);
luaL_argcheck(L, d->closed == 0, 1, "closed directory");
if (d->dir == NULL) {
// no find found
d->closed = 1;
return 0;
}
if (lua_getuservalue(L, 1) == LUA_TSTRING) {
// first time
lua_pushnil(L);
lua_setuservalue(L, 1);
return 1;
} else {
struct dirent *entry = readdir(d->dir);
if (entry) {
lua_pushstring(L, entry->d_name);
return 1;
} else {
closedir(d->dir);
d->dir = NULL;
d->closed = 1;
return 0;
}
}
}
static int
dir_close(lua_State *L) {
struct dir_data *d = luaL_checkudata(L, 1, DIR_METATABLE);
if (d->dir != NULL) {
closedir(d->dir);
d->dir = NULL;
}
d->closed = 1;
return 0;
}
static int
ldir(lua_State *L) {
size_t sz;
const char * pathname = luaL_checklstring(L, 1, &sz);
DIR* dir = opendir(pathname);
lua_pushcfunction(L, dir_iter);
if (dir == NULL) {
if (errno == ENOENT) {
struct dir_data *d = lua_newuserdata(L, sizeof(*d));
d->dir = NULL;
d->closed = 0;
} else {
system_error(L, errno);
return lua_error(L);
}
} else {
struct dirent *entry = readdir(dir);
struct dir_data *d = lua_newuserdata(L, sizeof(*d));
d->dir = dir;
d->closed = 0;
if (entry) {
lua_pushstring(L, entry->d_name);
lua_setuservalue(L, -2); // set firstname
}
}
if (luaL_newmetatable(L, DIR_METATABLE)) {
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, dir_iter);
lua_setfield(L, -2, "next");
lua_pushcfunction(L, dir_close);
lua_setfield(L, -2, "close");
lua_pushcfunction(L, dir_close);
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
return 2;
}
static int
lpersonaldir(lua_State *L) {
#if defined(__EMSCRIPTEN__)
lua_pushstring(L, "/");
return 1;
#else
struct passwd *pw = getpwuid(getuid());
if (pw && pw->pw_dir) {
lua_pushstring(L, pw->pw_dir);
return 1;
} else {
return error_return(L);
}
#endif
}
static int
lcurrentdir(lua_State *L) {
char path[LONGPATH_MAX];
if (getcwd(path, LONGPATH_MAX) == NULL) {
return error_return(L);
}
lua_pushstring(L, path);
return 1;
}
static int
lchdir(lua_State *L) {
size_t sz;
const char * path = luaL_checklstring(L, 1, &sz);
if (chdir(path) != 0) {
return error_return(L);
}
lua_pushboolean(L, 1);
return 1;
}
static const char *
mode2string(mode_t mode) {
if (S_ISREG(mode))
return "file";
else if (S_ISDIR(mode))
return "directory";
else if (S_ISLNK(mode))
return "link";
else if (S_ISSOCK(mode))
return "socket";
else if (S_ISFIFO(mode))
return "named pipe";
else if (S_ISCHR(mode))
return "char device";
else if (S_ISBLK(mode))
return "block device";
else
return "other";
}
/* inode protection mode */
static void push_st_mode(lua_State *L, STAT_STRUCT *info) {
lua_pushstring(L, mode2string(info->st_mode));
}
/* device inode resides on */
static void push_st_dev(lua_State *L, STAT_STRUCT *info) {
lua_pushinteger(L, (lua_Integer) info->st_dev);
}
/* inode's number */
static void push_st_ino(lua_State *L, STAT_STRUCT *info) {
lua_pushinteger(L, (lua_Integer) info->st_ino);
}
/* number of hard links to the file */
static void push_st_nlink(lua_State *L, STAT_STRUCT *info) {
lua_pushinteger(L, (lua_Integer)info->st_nlink);
}
/* user-id of owner */
static void push_st_uid(lua_State *L, STAT_STRUCT *info) {
lua_pushinteger(L, (lua_Integer)info->st_uid);
}
/* group-id of owner */
static void push_st_gid(lua_State *L, STAT_STRUCT *info) {
lua_pushinteger(L, (lua_Integer)info->st_gid);
}
/* device type, for special file inode */
static void push_st_rdev(lua_State *L, STAT_STRUCT *info) {
lua_pushinteger(L, (lua_Integer) info->st_rdev);
}
/* time of last access */
static void push_st_atime(lua_State *L, STAT_STRUCT *info) {
lua_pushinteger(L, (lua_Integer) info->st_atime);
}
/* time of last data modification */
static void push_st_mtime(lua_State *L, STAT_STRUCT *info) {
lua_pushinteger(L, (lua_Integer) info->st_mtime);
}
/* time of last file status change */
static void push_st_ctime(lua_State *L, STAT_STRUCT *info) {
lua_pushinteger(L, (lua_Integer) info->st_ctime);
}
/* file size, in bytes */
static void push_st_size(lua_State *L, STAT_STRUCT *info) {
lua_pushinteger(L, (lua_Integer)info->st_size);
}
static const char *perm2string(mode_t mode) {
static char perms[10] = "---------";
int i;
for (i=0;i<9;i++) perms[i]='-';
if (mode & S_IRUSR) perms[0] = 'r';
if (mode & S_IWUSR) perms[1] = 'w';
if (mode & S_IXUSR) perms[2] = 'x';
if (mode & S_IRGRP) perms[3] = 'r';
if (mode & S_IWGRP) perms[4] = 'w';
if (mode & S_IXGRP) perms[5] = 'x';
if (mode & S_IROTH) perms[6] = 'r';
if (mode & S_IWOTH) perms[7] = 'w';
if (mode & S_IXOTH) perms[8] = 'x';
return perms;
}
/* permissions string */
static void push_st_perm(lua_State *L, STAT_STRUCT *info) {
lua_pushstring(L, perm2string(info->st_mode));
}
typedef void (*_push_function) (lua_State *L, STAT_STRUCT *info);
struct _stat_members {
const char *name;
_push_function push;
};
struct _stat_members members[] = {
{ "mode", push_st_mode },
{ "dev", push_st_dev },
{ "ino", push_st_ino },
{ "nlink", push_st_nlink },
{ "uid", push_st_uid },
{ "gid", push_st_gid },
{ "rdev", push_st_rdev },
{ "access", push_st_atime },
{ "modification", push_st_mtime },
{ "change", push_st_ctime },
{ "size", push_st_size },
{ "permissions", push_st_perm },
{ NULL, NULL }
};
/*
** Get file or symbolic link information
*/
static int
file_info(lua_State *L) {
STAT_STRUCT info;
size_t sz;
int i;
const char * path = luaL_checklstring(L, 1, &sz);
if (STAT_FUNC(path, &info)) {
lua_pushnil(L);
lua_pushfstring(L, "cannot obtain information from file '%s': %s", path, strerror(errno));
lua_pushinteger(L, errno);
return 3;
}
if (lua_isstring(L, 2)) {
const char *member = lua_tostring(L, 2);
for (i = 0; members[i].name; i++) {
if (strcmp(members[i].name, member) == 0) {
/* push member value and return */
members[i].push(L, &info);
return 1;
}
}
/* member not found */
return luaL_error(L, "invalid attribute name '%s'", member);
}
/* creates a table if none is given, removes extra arguments */
lua_settop(L, 2);
if (!lua_istable(L, 2)) {
lua_newtable(L);
}
/* stores all members in table on top of the stack */
for (i = 0; members[i].name; i++) {
lua_pushstring(L, members[i].name);
members[i].push(L, &info);
lua_rawset(L, -3);
}
return 1;
}
static int
lrealpath(lua_State *L) {
size_t sz;
const char * pathname = luaL_checklstring(L, 1, &sz);
char resolved[LONGPATH_MAX];
if (realpath(pathname, resolved) == NULL) {
return error_return(L);
}
lua_pushstring(L, resolved);
return 1;
}
#define mkdir_utf8(path) (mkdir((path), \
S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH))
// todo: succ when dir exist
static int
pusherror(lua_State * L) {
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
lua_pushinteger(L, errno);
return 3;
}
#endif
static int
pushresult(lua_State * L, int res) {
if (res == -1) {
return pusherror(L);
} else if (res == 0) {
lua_pushnil(L);
lua_pushfstring(L, "%s already exist", lua_tostring(L, 1));
return 2;
} else {
lua_pushboolean(L, 1);
return 1;
}
}
static int
lmkdir(lua_State * L) {
const char *path = luaL_checkstring(L, 1);
return pushresult(L, mkdir_utf8(path));
}
int
luaopen_localfs(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "personaldir" , lpersonaldir },
{ "dir", ldir },
{ "currentdir", lcurrentdir },
{ "chdir", lchdir },
{ "attributes", file_info }, // the same with lfs, but support utf-8 filename
{ "realpath", lrealpath },
{ "mkdir", lmkdir },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: src/loginfo.h
================================================
#ifndef soluna_loginfo_h
#define soluna_loginfo_h
#include
struct log_info {
char tag[64];
uint32_t log_level;
uint32_t log_item;
uint32_t line_nr;
char message[256];
const char *filename;
};
#endif
================================================
FILE: src/lsha1.c
================================================
/*
SHA-1 in C
By Steve Reid
100% Public Domain
-----------------
Modified 7/98
By James H. Brown
Still 100% Public Domain
Corrected a problem which generated improper hash values on 16 bit machines
Routine SHA1Update changed from
void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
len)
to
void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
long len)
The 'len' parameter was declared an int which works fine on 32 bit machines.
However, on 16 bit machines an int is too small for the shifts being done
against
it. This caused the hash function to generate incorrect values if len was
greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
Since the file IO in main() reads 16K at a time, any file 8K or larger would
be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million
"a"s).
I also changed the declaration of variables i & j in SHA1Update to
unsigned long from unsigned int for the same reason.
These changes should make no difference to any 32 bit implementations since
an
int and a long are the same size in those environments.
--
I also corrected a few compiler warnings generated by Borland C.
1. Added #include for exit() prototype
2. Removed unused variable 'j' in SHA1Final
3. Changed exit(0) to return(0) at end of main.
ALL changes I made can be located by searching for comments containing 'JHB'
-----------------
Modified 8/98
By Steve Reid
Still 100% public domain
1- Removed #include and used return() instead of exit()
2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
-----------------
Modified 4/01
By Saul Kravitz
Still 100% PD
Modified to run on Compaq Alpha hardware.
-----------------
Modified 07/2002
By Ralph Giles
Still 100% public domain
modified for use with stdint types, autoconf
code cleanup, removed attribution comments
switched SHA1Final() argument order for consistency
use SHA1_ prefix for public api
move public api to sha1.h
-----------------
Modufiled 08/2014
By Cloud Wu
Still 100% PD
Lua binding
*/
/*
Test Vectors (from FIPS PUB 180-1)
"abc"
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
A million repetitions of "a"
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
*/
#include
#include
#include
typedef struct {
uint32_t state[5];
uint32_t count[2];
uint8_t buffer[64];
} SHA1_CTX;
#define SHA1_DIGEST_SIZE 20
static void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]);
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
/* blk0() and blk() perform the initial expand. */
/* I got the idea of expanding during the round function from SSLeay */
/* FIXME: can we do this in an endian-proof way? */
#ifdef WORDS_BIGENDIAN
#define blk0(i) block.l[i]
#else
#define blk0(i) (block.l[i] = (rol(block.l[i],24)&0xFF00FF00) \
|(rol(block.l[i],8)&0x00FF00FF))
#endif
#define blk(i) (block.l[i&15] = rol(block.l[(i+13)&15]^block.l[(i+8)&15] \
^block.l[(i+2)&15]^block.l[i&15],1))
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
/* Hash a single 512-bit block. This is the core of the algorithm. */
static void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64])
{
uint32_t a, b, c, d, e;
typedef union {
uint8_t c[64];
uint32_t l[16];
} CHAR64LONG16;
CHAR64LONG16 block;
memcpy(&block, buffer, 64);
/* Copy context->state[] to working vars */
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
/* 4 rounds of 20 operations each. Loop unrolled. */
R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
/* Add the working vars back into context.state[] */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
/* Wipe variables */
a = b = c = d = e = 0;
}
/* SHA1Init - Initialize new context */
static void sat_SHA1_Init(SHA1_CTX* context)
{
/* SHA1 initialization constants */
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
context->count[0] = context->count[1] = 0;
}
/* Run your data through this. */
static void sat_SHA1_Update(SHA1_CTX* context, const uint8_t* data, const size_t len)
{
size_t i, j;
#ifdef VERBOSE
SHAPrintContext(context, "before");
#endif
j = (context->count[0] >> 3) & 63;
if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++;
context->count[1] += (len >> 29);
if ((j + len) > 63) {
memcpy(&context->buffer[j], data, (i = 64-j));
SHA1_Transform(context->state, context->buffer);
for ( ; i + 63 < len; i += 64) {
SHA1_Transform(context->state, data + i);
}
j = 0;
}
else i = 0;
memcpy(&context->buffer[j], &data[i], len - i);
#ifdef VERBOSE
SHAPrintContext(context, "after ");
#endif
}
/* Add padding and return the message digest. */
static void sat_SHA1_Final(SHA1_CTX* context, uint8_t digest[SHA1_DIGEST_SIZE])
{
uint32_t i;
uint8_t finalcount[8];
for (i = 0; i < 8; i++) {
finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
>> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
}
sat_SHA1_Update(context, (uint8_t *)"\200", 1);
while ((context->count[0] & 504) != 448) {
sat_SHA1_Update(context, (uint8_t *)"\0", 1);
}
sat_SHA1_Update(context, finalcount, 8); /* Should cause a SHA1_Transform() */
for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
digest[i] = (uint8_t)
((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
}
/* Wipe variables */
i = 0;
memset(context->buffer, 0, 64);
memset(context->state, 0, 20);
memset(context->count, 0, 8);
memset(finalcount, 0, 8); /* SWR */
}
#include
#include
int
lsha1(lua_State *L) {
size_t sz = 0;
const uint8_t * buffer = (const uint8_t *)luaL_checklstring(L, 1, &sz);
uint8_t digest[SHA1_DIGEST_SIZE];
SHA1_CTX ctx;
sat_SHA1_Init(&ctx);
sat_SHA1_Update(&ctx, buffer, sz);
sat_SHA1_Final(&ctx, digest);
lua_pushlstring(L, (const char *)digest, SHA1_DIGEST_SIZE);
return 1;
}
#define BLOCKSIZE 64
static inline void
xor_key(uint8_t key[BLOCKSIZE], uint32_t xor_) {
int i;
for (i=0;i BLOCKSIZE) {
SHA1_CTX ctx;
sat_SHA1_Init(&ctx);
sat_SHA1_Update(&ctx, key, key_sz);
sat_SHA1_Final(&ctx, rkey);
key_sz = SHA1_DIGEST_SIZE;
} else {
memcpy(rkey, key, key_sz);
}
xor_key(rkey, 0x5c5c5c5c);
sat_SHA1_Init(&ctx1);
sat_SHA1_Update(&ctx1, rkey, BLOCKSIZE);
xor_key(rkey, 0x5c5c5c5c ^ 0x36363636);
sat_SHA1_Init(&ctx2);
sat_SHA1_Update(&ctx2, rkey, BLOCKSIZE);
sat_SHA1_Update(&ctx2, text, text_sz);
sat_SHA1_Final(&ctx2, digest2);
sat_SHA1_Update(&ctx1, digest2, SHA1_DIGEST_SIZE);
sat_SHA1_Final(&ctx1, digest1);
lua_pushlstring(L, (const char *)digest1, SHA1_DIGEST_SIZE);
return 1;
}
================================================
FILE: src/luabuffer.h
================================================
#ifndef soluna_luabuffer_h
#define soluna_luabuffer_h
#include
#include
#include
static inline uint8_t const *
luaL_getbuffer(lua_State *L, size_t *sz) {
uint8_t const * ret = NULL;
switch (lua_type(L, 1)) {
case LUA_TFUNCTION: {
lua_pushvalue(L, 1);
lua_call(L, 0, 3);
ret = (uint8_t const*)lua_touserdata(L, -3);
*sz = (size_t)luaL_checkinteger(L, -2);
lua_copy(L, -1, 1);
int t = lua_type(L, 1);
if (t == LUA_TUSERDATA || t == LUA_TTABLE)
lua_toclose(L, 1);
lua_pop(L, 3);
break;
}
case LUA_TSTRING:
ret = (uint8_t const *)luaL_checklstring(L, 1, sz);
break;
default:
luaL_error(L, "Invalid buffer type %s", lua_typename(L, lua_type(L, 1)));
}
return ret;
}
#endif
================================================
FILE: src/lualib/coroutine.lua
================================================
local co = require "coroutine"
global assert, setmetatable, error
local coroutine_create = co.create
local coroutine_resume = co.resume
local coroutine_close = co.close
local coroutine_yield = co.yield
local coroutine_status = co.status
local coroutine = {}
do -- begin coroutine
local ltask_coroutines = setmetatable({}, { __mode = "kv" })
-- true : coroutine
-- false : suspend
-- nil : exit
function coroutine.create(f)
local co = coroutine_create(f)
ltask_coroutines[co] = true
return co
end
do -- begin coroutine.resume
local function unlock(co, ...)
ltask_coroutines[co] = true
return ...
end
local function ltask_yielding(co, ...)
ltask_coroutines[co] = false
return unlock(co, coroutine_resume(co, coroutine_yield(...)))
end
local function resume(co, ok, tag, ...)
if not ok then
return ok, tag, ...
elseif coroutine_status(co) == "dead" then
-- the main function exit
ltask_coroutines[co] = nil
return true, tag, ...
elseif tag == "USER" then
return true, ...
else
-- blocked in ltask framework, so raise the yielding message
return resume(co, ltask_yielding(co, tag, ...))
end
end
function coroutine.resume(co, ...)
local co_status = ltask_coroutines[co]
if not co_status then
if co_status == false then
-- is running
return false, "cannot resume a ltask coroutine suspend by ltask framework"
end
if coroutine_status(co) == "dead" then
-- always return false, "cannot resume dead coroutine"
return coroutine_resume(co, ...)
else
return false, "cannot resume none ltask coroutine"
end
end
return resume(co, coroutine_resume(co, ...))
end
end -- end coroutine.resume
function coroutine.status(co)
local status = ltask_coroutines(co)
if status == "suspended" then
if ltask_coroutines[co] == false then
return "blocked"
else
return "suspended"
end
else
return status
end
end
function coroutine.yield(...)
return coroutine_yield("USER", ...)
end
do -- begin coroutine.wrap
local function wrap_co(ok, ...)
if ok then
return ...
else
error(...)
end
end
function coroutine.wrap(f)
local co = coroutine.create(function(...)
return f(...)
end)
return function(...)
return wrap_co(coroutine.resume(co, ...))
end
end
end -- end coroutine.wrap
function coroutine.close(co)
ltask_coroutines[co] = nil
return coroutine_close(co)
end
end -- end corotuine
_ENV.coroutine = coroutine
return coroutine
================================================
FILE: src/lualib/fontmgr.lua
================================================
local ttf = require "soluna.font.truetype"
local string = string
local utf8 = utf8
local table = table
local debug = debug
global pairs, ipairs, assert, rawget, setmetatable
local MAXFONT = 64
local namelist = {}
local CACHE = {}
local function utf16toutf8(s)
local surrogate
return (s:gsub("..", function(utf16)
local cp = string.unpack(">H", utf16)
if (cp & 0xFC00) == 0xD800 then
surrogate = cp
return ""
else
if surrogate then
cp = ((surrogate - 0xD800) << 10) + (cp - 0xDC00) + 0x10000
surrogate = nil
end
return utf8.char(cp)
end
end))
end
local ids = {
UNICODE = {
id = 0,
encoding = {
UNICODE_1_0 = 0,
UNICODE_1_1 = 1,
ISO_10646 = 2,
UNICODE_2_0_BMP = 3,
UNICODE_2_0_FULL = 4,
},
lang = {
default = 0,
ENGLISH = 0,
CHINESE = 1,
FRENCH = 2,
GERMAN = 3,
JAPANESE = 4,
KOREAN = 5,
SPANISH = 6,
ITALIAN = 7,
DUTCH = 8,
SWEDISH = 9,
RUSSIAN = 10,
},
},
MICROSOFT = {
id = 3,
encoding = {
UNICODE_BMP = 1,
UNICODE_FULL = 10,
},
lang = {
ENGLISH =0x0409,
CHINESE =0x0804,
DUTCH =0x0413,
FRENCH =0x040c,
GERMAN =0x0407,
HEBREW =0x040d,
ITALIAN =0x0410,
JAPANESE =0x0411,
KOREAN =0x0412,
RUSSIAN =0x0419,
SPANISH =0x0409,
SWEDISH =0x041D,
},
},
MACINTOSH = {
id = 1,
encoding = {
ROMAN = 0,
JAPANESE = 1,
CHINESE_TRADITIONAL = 2,
KOREAN = 3,
ARABIC = 4,
HEBREW = 5,
GREEK = 6,
RUSSIAN = 7,
RSYMBOL = 8,
DEVANAGARI = 9,
GURMUKHI = 10,
GUJARATI = 11,
ORIYA = 12,
BENGALI = 13,
TAMIL = 14,
TELUGU = 15,
KANNADA = 16,
MALAYALAM = 17,
SINHALESE = 18,
BURMESE = 19,
KHMER = 20,
THAI = 21,
LAOTIAN = 22,
GEORGIAN = 23,
ARMENIAN = 24,
CHINESE_SIMPLIFIED = 25,
TIBETAN = 26,
MONGOLIAN = 27,
GEEZ = 28,
SLAVIC = 29,
VIETNAMESE = 30,
SINDHI = 31,
},
lang = {
ENGLISH = 0,
FRENCH = 1,
GERMAN = 2,
ITALIAN = 3,
DUTCH = 4,
SWEDISH = 5,
SPANISH = 6,
DANISH = 7,
PORTUGUESE = 8,
NORWEGIAN = 9,
HEBREW = 10,
JAPANESE = 11,
ARABIC = 12,
FINNISH = 13,
GREEK = 14,
ICELANDIC = 15,
MALTESE = 16,
TURKISH = 17,
CROATIAN = 18,
CHINESE_TRADITIONAL = 19,
URDU = 20,
HINDI = 21,
THAI = 22,
KOREAN = 23,
LITHUANIAN = 24,
POLISH = 25,
HUNGARIAN = 26,
ESTONIAN = 27,
LATVIAN = 28,
SAMI = 29,
FAROESE = 30,
FARSI = 31,
RUSSIAN = 32,
CHINESE_SIMPLIFIED = 33,
FLEMISH = 34,
IRISH_GAELIC = 35,
ALBANIAN = 36,
ROMANIAN = 37,
CZECH = 38,
SLOVAK = 39,
SLOVENIAN = 40,
YIDDISH = 41,
SERBIAN = 42,
MACEDONIAN = 43,
BULGARIAN = 44,
UKRAINIAN = 45,
BYELORUSSIAN = 46,
UZBEK = 47,
KAZAKH = 48,
AZERBAIJANI_CYRILLIC = 49,
AZERBAIJANI_ARABIC = 50,
ARMENIAN = 51,
GEORGIAN = 52,
MOLDAVIAN = 53,
KIRGHIZ = 54,
TAJIKI = 55,
TURKMEN = 56,
MONGOLIAN = 57,
MONGOLIAN_CYRILLIC = 58,
PASHTO = 59,
KURDISH = 60,
KASHMIRI = 61,
SINDHI = 62,
TIBETAN = 63,
NEPALI = 64,
SANSKRIT = 65,
MARATHI = 66,
BENGALI = 67,
ASSAMESE = 68,
GUJARATI = 69,
PUNJABI = 70,
ORIYA = 71,
MALAYALAM = 72,
KANNADA = 73,
TAMIL = 74,
TELUGU = 75,
SINHALESE = 76,
BURMESE = 77,
KHMER = 78,
LAO = 79,
VIETNAMESE = 80,
INDONESIAN = 81,
TAGALOG = 82,
MALAY_ROMAN = 83,
MALAY_ARABIC = 84,
AMHARIC = 85,
TIGRINYA = 86,
GALLA = 87,
SOMALI = 88,
SWAHILI = 89,
KINYARWANDA = 90,
RUNDI = 91,
NYANJA = 92,
MALAGASY = 93,
ESPERANTO = 94,
},
},
}
local function import(fontdata)
local index = 0
local cache = {}
while true do
for _, obj in pairs(ids) do
for _, encoding_id in pairs(obj.encoding) do
for _, lang_id in pairs(obj.lang) do
local fname, sname = ttf.namestring(fontdata, index, obj.id, encoding_id, lang_id)
if fname then
fname = utf16toutf8(fname)
local fullname = fname
fname = string.lower(fname)
if sname then
sname = utf16toutf8(sname)
fullname = fullname .. " " .. sname
sname = string.lower(sname)
end
if not cache[fullname] then
cache[fullname] = true
table.insert(namelist, {
fontdata = fontdata,
index = index,
family = fname,
sfamily = sname, -- sub family name
name = string.lower(fullname),
})
end
elseif fname == nil then
return
end
end
end
end
index = index + 1
end
end
local FONT_ID = 0
local function alloc_fontid()
FONT_ID = FONT_ID + 1
assert(FONT_ID <= MAXFONT)
return FONT_ID
end
local function matching(obj, name)
if obj.family == name or obj.name == name then
return true
end
end
local function fetch_name(nametable, name_)
if name_ == "" and namelist[1] then
name_ = namelist[1].name
local id = rawget(nametable, name_)
if id then
nametable[""] = id
return id
end
end
local name = string.lower(name_)
for _, obj in ipairs(namelist) do
if matching(obj, name) then
if not obj.id then
obj.id = alloc_fontid()
CACHE[obj.id] = obj
end
local id = obj.id
nametable[name_] = id
return id
end
end
end
setmetatable(ttf.nametable, { __index = fetch_name })
local function fetch_id(_, id)
local obj = assert(CACHE[id])
return ttf.update(id, obj.fontdata, obj.index)
end
setmetatable(ttf.idtable, { __index = fetch_id })
local function enum_name(_, idx)
local i = idx // 2
local what = (idx % 2 == 1) and "family" or "name"
local n = namelist[i+1]
if not n then
return
else
return n[what]
end
end
setmetatable(ttf.enum, { __index = enum_name })
debug.getregistry().TRUETYPE_IMPORT = import
================================================
FILE: src/lualib/icon.lua
================================================
local sdf = require "soluna.image.sdf"
local datalist = require "soluna.datalist"
local file = require "soluna.file"
local mattext = require "soluna.material.text"
global error, tostring, print
local icon = {}
function icon.bundle(filename)
local path = filename:match "(.*[/\\])[^/\\]+$"
local b = datalist.parse(file.load(filename))
local names = {}
local icons = {}
local n = #b
for i = 1, n do
local icon = b[i]
names[icon.name] = i - 1
local src = file.load(path .. icon.image) or error ("Open icon fail : " .. tostring(icon.name))
local img = sdf.load(src)
icons[i] = img
end
icon.names = names
return sdf.bundle(icons)
end
function icon.symbol(name, size, color)
local id = icon.names[name] or error "No icon " .. name
return mattext.char(id, 255, size, color)
end
return icon
================================================
FILE: src/lualib/initsetting.lua
================================================
local datalist = require "soluna.datalist"
local source = require "soluna.embedsource"
local lfs = require "soluna.lfs"
local file = require "soluna.file"
global type, error, pairs, assert, tonumber, print
local S = {}
local function patch(s, k, v)
if type(k) == "number" then
-- ignore
return
end
local branch, key = k:match "^([^.]+)%.(.+)"
if branch then
local tree = s[branch]
if tree == nil then
tree = {}
s[branch] = tree
elseif type(tree) ~= "table" then
error ("Conflict setting key : " .. k)
end
s = tree
k = key
end
if type(v) == "table" then
local orig_v = s[k]
if orig_v == nil then
s[k] = v
elseif type(orig_v) == "table" then
for sub_k,sub_v in pairs(v) do
patch(orig_v, sub_k, sub_v)
end
else
error ("Conflict setting key : " .. k)
end
else
s[k] = v
end
end
local function settings_filename(filename, change_root)
if filename then
local realname = assert(lfs.realpath(filename))
if change_root then
local curpath, name = realname:match "(.*)[/\\]([^/\\]+)$"
if curpath and name then
lfs.chdir(curpath)
end
return name
else
return realname
end
end
if file.exist "main.game" then
return "main.game"
end
end
function S.init(args, change_root)
local default_settings = datalist.parse(source.data.settingdefault)
local realname = settings_filename(args[1], change_root)
if realname then
local data = file.load(realname) or error ("Can't open " .. realname)
local game_settings = datalist.parse(data)
for k,v in pairs(game_settings) do
patch(default_settings, k,v)
end
end
for k,v in pairs(args) do
if type(k) == "string" then
if v == "true" then
v = true
elseif v == "false" then
v = false
else
v = tonumber(v) or v
end
patch(default_settings, k,v)
end
end
return default_settings
end
return S
================================================
FILE: src/lualib/layout.lua
================================================
local yoga = require "soluna.layout.yoga"
local datalist = require "soluna.datalist"
local file = require "soluna.file"
local table = table
global next, error, assert, type, setmetatable, pairs
local layout = {}
local document = {}
local element = {} ; element.__index = element
function document:__gc()
local root = self._root -- root yoga object
if root then
yoga.node_free(root)
self._root = nil
end
self._yoga = nil -- yoga objects for elements
self._list = nil -- image/text element lists
self._element = nil -- elements can be update
end
function document:__index(id)
return self._element[id]
end
function document:__tostring()
return "[document]"
end
function document:__pairs()
return next, self._element
end
function element:__tostring()
return "[element:"..self._id.."]"
end
function element:__newindex(key, value)
local _yoga = self._document._yoga
local cobj = (_yoga and _yoga[self._id]) or error ("No id : " .. self._id)
yoga.node_set(cobj, key, value)
end
-- update attr
function element:update(attr)
local _yoga = self._document._yoga
local cobj = (_yoga and _yoga[self._id]) or error ("No id : " .. self._id)
yoga.node_set(cobj, attr)
end
function element:get()
local _yoga = self._document._yoga
local cobj = (_yoga and _yoga[self._id]) or error ("No id : " .. self._id)
return yoga.node_get(cobj)
end
function element:attribs()
local _yoga = self._document._yoga
local cobj = (_yoga and _yoga[self._id]) or error ("No id : " .. self._id)
return _yoga[cobj]
end
do
local function parse_node(v, scripts)
local attr = {}
local content = {}
local n = 1
for i = 1, #v, 2 do
local name = v[i]
local value = v[i+1]
if name == "children" then
local c = scripts(value)
local len = #c
assert(type(c) == "table" and len % 2 == 0)
table.move(c, 1, len, n, content)
n = n + len
elseif type(value) == "table" then
content[n] = name
content[n+1] = value
n = n + 2
else
attr[name] = value
end
end
if n == 1 then
content = nil
end
return content, attr
end
local function new_element(doc, cobj, attr)
yoga.node_set(cobj, attr)
local id = attr.id
if id then
if doc._element[id] then
error (id .. " exist")
end
local elem = { _document = doc, _id = id }
doc._element[id] = setmetatable(elem, element)
doc._yoga[id] = cobj
end
if attr.image or attr.text or attr.background or attr.region then
local obj = {}
for k,v in pairs(attr) do
obj[k] = v
end
doc._yoga[obj] = cobj
doc._yoga[cobj] = obj
doc._list[#doc._list + 1] = obj
end
end
local function add_children(doc, parent, list, scripts)
for i = 1, #list, 2 do
local name = list[i] -- ignore
local content, attr = parse_node(list[i+1], scripts)
local cobj = yoga.node_new(parent)
new_element(doc, cobj, attr)
if content then
add_children(doc, cobj, content, scripts)
end
end
end
function layout.load(filename_or_list, scripts)
local list
if type(filename_or_list) == "string" then
list = datalist.parse_list(file.load(filename_or_list))
else
list = filename_or_list
end
local doc = {
_root = yoga.node_new(),
_yoga = {},
_list = {},
_element = {},
}
local children, attr = parse_node(list, scripts)
new_element(doc, doc._root, attr)
if children then
add_children(doc, doc._root, children, scripts)
end
return setmetatable(doc, document)
end
function layout.calc(doc)
yoga.node_calc(doc._root)
local list = doc._list
local yogaobj = doc._yoga
for i = 1, #list do
local obj = list[i]
local cobj = yogaobj[obj]
do local _ENV = obj
global x, y, w, h
x,y,w,h = yoga.node_get(cobj)
end
end
local _,_,w,h = yoga.node_get(doc._root)
list.width = w
list.height = h
return list
end
end
return layout
================================================
FILE: src/lualib/main.lua
================================================
local package = package
local table = table
global load, require, assert, select, error, tostring, print, type
local init_func_temp = [=[
local name, service_path = ...
local embedsource = require "soluna.embedsource"
local file = require "soluna.file"
package.path = [[${lua_path}]]
package.cpath = [[${lua_cpath}]]
local zipfile = [[${zipfile}]]
if zipfile == "" then
zipfile = nil
end
_G.print_r = load(embedsource.runtime.print_r(), "@src/lualib/print_r.lua")()
local packageloader = load(embedsource.runtime.packageloader(), "@src/lualib/packageloader.lua")
packageloader(zipfile)
local function embedloader(name)
local ename
if name == "soluna" then
ename = "soluna"
else
ename = name:match "^soluna%.(.*)"
end
if ename then
local code = embedsource.lib[ename]
if code then
return function()
local srcname = "src/lualib/"..ename..".lua"
local f = load(code(), "@" .. srcname)
return f(ename, srcname)
end
end
return "no embed soluna." .. ename
end
end
package.searchers[#package.searchers+1] = embedloader
local extlua = require "soluna.extlua"
if extlua.searcher() then -- has preload libs
package.searchers[#package.searchers+1] = extlua.searcher
end
local embedcode = embedsource.service[name]
if embedcode then
return load(embedcode(),"=("..name..")")
end
local filename, err = file.searchpath(name, service_path or "${service_path}")
if not filename then
return nil, err
end
return load(file.load(filename), "@"..filename)
]=]
local api = {}
local function start(config)
local boot = require "ltask.bootstrap"
local mqueue = require "ltask.mqueue"
local embedsource = require "soluna.embedsource"
local soluna_app = require "soluna.app"
-- set callback message handler
local root_config = {
bootstrap = config.bootstrap,
service_source = embedsource.runtime.service(),
service_chunkname = "@3rd/ltask/lualib/service.lua",
initfunc = init_func_temp:gsub("%$%{([^}]*)%}", {
lua_path = package.path,
lua_cpath = package.cpath,
service_path = config.service_path or "",
zipfile = config.args.zipfile or "",
}),
}
table.insert(root_config.bootstrap, {
name = "start",
args = {
config.args,
},
})
boot.init_socket()
local bootstrap = load(embedsource.runtime.bootstrap(), "@3rd/ltask/lualib/bootstrap.lua")()
local core = config.core or {}
core.external_queue = core.external_queue or 4096
local ctx = bootstrap.start {
core = core,
root = root_config,
root_initfunc = root_config.initfunc,
mainthread = config.mainthread,
}
-- wait for INIT_EVENT, see start.lua
boot.mainthread_wait()
local sender, sender_ud = bootstrap.external_sender(ctx)
local c_sendmessage = require "soluna.app".sendmessage
local function send_message(...)
return c_sendmessage(sender, sender_ud, ...)
end
local logger, logger_ud = bootstrap.log_sender(ctx)
local unpackevent = assert(soluna_app.unpackevent)
local appmsg_queue = mqueue.new(128)
local recvmsg = mqueue.recv
local appmsg = {}
function appmsg.set_title(text)
soluna_app.set_window_title(text)
end
function appmsg.set_icon(data)
soluna_app.set_icon(data)
end
local function do_appmsg(what, ...)
local f = appmsg[what] or error ("Unknown app message " .. tostring(what))
f(...)
end
local function dispatch_appmsg(v)
while v do
do_appmsg(boot.unpack_remove(v))
v = recvmsg(appmsg_queue, appmsg)
end
end
return {
send_log = logger,
send_log_ud = logger_ud,
mqueue = appmsg_queue,
cleanup = function()
while not send_message "cleanup" do end
bootstrap.wait(ctx)
api.deinit()
mqueue.delete(appmsg_queue)
appmsg_queue = nil
end,
frame = function(count)
local v = recvmsg(appmsg_queue)
if v then
dispatch_appmsg(v)
end
if send_message("frame", count) then
boot.mainthread_wait()
end
end,
event = function(ev)
send_message(unpackevent(ev))
end,
}
end
local args = ... or {}
for i = 2, select("#", ...) do
args[i-1] = select(i, ...)
end
if args.path then
package.path = args.path
end
if args.cpath then
package.cpath = args.cpath
end
local audio_device
function api.start(app)
app.audio_device = audio_device
args.app = app
return start {
args = args,
core = {
debuglog = "=", -- stdout
},
bootstrap = {
{
name = "timer",
unique = true,
},
{
name = "log",
unique = true,
},
{
name = "loader",
unique = true,
},
{
name = "audio",
unique = true,
},
},
}
end
local function preload_ext(list, entry_name)
if list == nil then
return
end
if type(list) == "string" then
list = { list }
end
for i = 1, #list do
local name = list[i]
local f = package.loadlib(package.searchpath(name, package.cpath), entry_name) or error ("Can't load extlua " .. name)
list[i] = f
end
local extlua = require "soluna.extlua"
extlua.preload(list)
end
function api.init(desc)
-- todo : settings
local zipfile = args[1] or args.zipfile or "main.zip"
local embedsource = require "soluna.embedsource"
local packageloader = load(embedsource.runtime.packageloader(), "@src/lualib/packageloader.lua")
if packageloader(zipfile) then
args.zipfile = zipfile
if zipfile == args[1] then
table.remove(args, 1)
end
end
local initsetting = load(embedsource.lib.initsetting, "@3rd/ltask/lualib/initsetting.lua")()
local settings = initsetting.init(args)
preload_ext(settings.extlua_preload, settings.extlua_entry)
local soluna_app = require "soluna.app"
soluna_app.init_desc(desc, settings)
local audio = require "soluna.audio"
audio.device, audio_device = audio.init()
end
function api.deinit()
if audio_device then
local audio = require "soluna.audio"
audio.deinit(audio_device)
audio_device = nil
end
end
return api
================================================
FILE: src/lualib/packageloader.lua
================================================
local file = require "soluna.file"
local zip = require "soluna.zip"
local lfs = require "soluna.lfs"
local package = package
local string = string
local io = io
global load, print, setmetatable, table, type, tostring, ipairs, require, error, assert
local dir_sep, temp_sep, temp_marker = package.config:match "(.)\n(.)\n(.)"
local temp_pat = "[^"..temp_sep.."]+"
local function load_zips(zipnames)
if zipnames == nil then
return
end
local n = 0
local r = {}
for fullname in zipnames:gmatch "[^:;]+" do
local name, root = fullname:match "(.-)@(.*)"
if name then
root = root .. "/"
else
name = fullname
end
local zf = zip.open(name, "r")
if not zf then
-- print("Can't open patch", name)
else
-- print("Load patch", name)
n = n + 1
r[n] = { zip = zf, root = root, name = name }
end
end
r.n = n
if n > 0 then
return r
else
-- print("No zip, use local files")
end
end
local zipfile = load_zips(...)
local file_load = file.load
local file_exist = file.exist
if zipfile then
local function find_file(cache, fullname)
local name = fullname:match "%./(.*)" or fullname
for i = zipfile.n, 1, -1 do
local root = zipfile[i].root
local name_in_zip
if root then
local n = #root
if name:sub(1, n) == root then
name_in_zip = name:sub(n+1)
end
else
name_in_zip = name
end
local zf = zipfile[i].zip
if name_in_zip and zf:exist(name_in_zip) then
cache[name] = function()
return zf:readfile(name_in_zip)
end
-- print(name, "in zipfile", i)
return cache[name]
end
end
end
local list
local names_cache = setmetatable({}, { __index = find_file})
function file_load(name)
local loader = names_cache[name]
return loader and loader()
end
function file_exist(name)
return names_cache[name] ~= nil
end
file.local_load = file.load
file.local_exist = file.exist
file.load = file_load
file.ziplist = function () return zip.list(zipfile) end
file.exist = file_exist
local function gen_list()
local tmp = {}
local r = {}
local n = 1
for i = zipfile.n, 1, -1 do
local flist = zipfile[i].zip:list()
local root = zipfile[i].root
for j = 1, #flist do
local name = flist[j]
if root then
name = root and root .. name
end
if tmp[name] == nil then
tmp[name] = true
-- todo : add path of name
end
r[n] = name
n = n + 1
end
end
table.sort(r)
return r
end
function file.dir(root)
list = list or gen_list()
root = root:gsub("[^/]$", "%0/")
local iter = 1
local n = #list
local root_n = #root
local last
return function()
while iter <= n do
local t = list[iter]
iter = iter + 1
if t:sub(1, root_n) == root then
local sname = t:sub(root_n+1):match "[^/]+"
if sname ~= last then
last = sname
return sname
end
end
end
end
end
function file.attributes(fullname)
list = list or gen_list()
local pathname = fullname .. "/"
local pathn = #pathname
for i = 1, #list do
local t = list[i]
if fullname == t then
return "file"
elseif t:sub(1, pathn) == pathname then
return "directory"
end
end
end
function file.searchpath(name, path)
local cname = name:gsub("%.", "/")
for temp in path:gmatch(temp_pat) do
local fullname = temp:gsub(temp_marker, cname)
if dir_sep ~= '/' then
fullname = fullname:gsub(dir_sep, "/")
end
if file_exist(fullname) then
return fullname
end
end
end
else
file.dir = lfs.dir
file.attributes = lfs.attributes
file.local_load = file.load
file.local_exist = file.exist
file.searchpath = package.searchpath
end
local function fileload(name, fullname)
local s, err = file_load(fullname)
local f = assert(load(s, "@"..fullname))
return f(name, fullname)
end
local function search_file(name)
local cname = name:gsub("%.", "/")
for temp in package.path:gmatch(temp_pat) do
local fullname = temp:gsub(temp_marker, cname)
if dir_sep ~= '/' then
fullname = fullname:gsub(dir_sep, "/")
end
if file_exist(fullname) then
return fileload, fullname
end
end
return "No package : " .. name
end
package.searchers[2] = search_file
return zipfile
================================================
FILE: src/lualib/print_r.lua
================================================
local ltask = require "ltask"
local log_info = ltask.log.info
local table = table
local math = math
global pairs, tostring, type, assert, pcall, error, select
local function keys(o)
local len = #o
local skeys = {}
local ukeys = {}
local n = 1
for k,v in pairs(o) do
local nk = math.tointeger(k)
if nk == nil or nk <= 0 or nk > len then
local sk = tostring(k)
if type(k) == "string" then
skeys[n] = k; n = n + 1
else
ukeys[k] = true
end
end
end
table.sort(skeys)
for k in pairs(ukeys) do
skeys[n] = k; n = n + 1
end
return skeys
end
local function try_no_circular(o)
local cache = {}
local function seri_no_circular(o)
assert(cache[o] == nil, cache)
if type(o) == "table" then
local result = { "{" }
local n = 2
cache[o] = true
for i = 1, #o do
local v = o[i]
result[n] = seri_no_circular(v); n = n + 1
result[n] = " "; n = n + 1
end
local key = keys(o)
for i = 1, #key do
local k = key[i]
local v = seri_no_circular(o[k])
result[n] = seri_no_circular(k); n = n + 1
result[n] = ":"; n = n + 1
result[n] = v; n = n + 1
result[n] = " "; n = n + 1
end
result[n] = "}"
return table.concat(result)
else
return tostring(o)
end
end
local ok, r = pcall(seri_no_circular, o)
if not ok then
if r == cache then
return
else
error(r)
end
end
return r
end
local function mark_circular(o)
local cache = {}
local n = 1
local function mark(o)
if type(o) == "table" then
local v = cache[o]
if v == nil then
cache[o] = false
else
if v == false then
cache[o] = n; n = n + 1
end
return
end
for k,v in pairs(o) do
mark(k)
mark(v)
end
end
end
mark(o)
for k,v in pairs(cache) do
if not v then
cache[k] = nil
end
end
return cache
end
local function seri_circular(o)
local cache = mark_circular(o)
local function seri_object(o)
if type(o) ~= "table" then
return tostring(o)
end
local result
local s = cache[o]
if s then
if type(s) == "number" then
result = { "#"..s.."{" }
cache[o] = "[#"..s.."]"
else
return s
end
else
result = { "{" }
end
local n = 2
for i = 1, #o do
local v = o[i]
result[n] = seri_object(v); n = n + 1
result[n] = " "; n = n + 1
end
local key = keys(o)
for i = 1, #key do
local k = key[i]
local v = seri_object(o[k])
result[n] = seri_object(k); n = n + 1
result[n] = ":"; n = n + 1
result[n] = v; n = n + 1
result[n] = " "; n = n + 1
end
result[n] = "}"
return table.concat(result)
end
return seri_object(o)
end
local function seri(o)
return try_no_circular(o) or seri_circular(o)
end
local function print_r(...)
local len = select("#", ...)
local str
if len == 1 then
str = seri(...)
else
local r = {}
local n = 1
for i = 1, len do
local o = select(i, ...)
r[n] = seri(o); n = n + 1
end
str = table.concat(r, "\t")
end
local n = #str
if n > 1024 then
str = str:sub(1, 1024) .. "[..." .. n .. "]"
end
ltask.pushlog(ltask.pack("print", str))
end
return print_r
================================================
FILE: src/lualib/soluna.lua
================================================
local ltask = require "ltask"
local app = require "soluna.app"
local mqueue = require "ltask.mqueue"
global require, error, string, assert, package, setmetatable, tostring
local soluna = {
platform = app.platform
}
function soluna.gamepad_init()
local gamepad = require "soluna.gamepad"
local state = {}
soluna.gamepad = state
local gs = ltask.uniqueservice "gamepad"
local S = ltask.dispatch()
function S._gamepad_update()
gamepad.update(state)
end
ltask.call(gs, "register", ltask.self(), "_gamepad_update")
return state
end
local settings
function soluna.settings()
if settings == nil then
local s = ltask.queryservice "settings"
settings = ltask.call(s, "get")
end
return settings
end
function soluna.set_window_title(text)
mqueue.send(app.mqueue(), ltask.pack("set_title", text))
end
function soluna.set_icon(data)
mqueue.send(app.mqueue(), ltask.pack("set_icon", data))
end
local function recursion_mkdir(root, path)
local lfs = require "soluna.lfs"
for p in path:gmatch "[^/\\]+" do
root = root .. "/" .. p
lfs.mkdir(root)
end
return (root:gsub("[^/\\]$", "%0/"))
end
function soluna.gamedir(name)
if name == nil then
settings = settings and soluna.settings()
name = settings.project or error "missing project name in settings"
end
local lfs = require "soluna.lfs"
local path
if soluna.platform == "windows" then
path = "My Games/"
elseif soluna.platform == "macos" or soluna.platform == "linux" then
path = ".local/share/"
elseif soluna.platform == "wasm" then
path = "persistent/games/"
else
error "TODO: support none windows"
end
path = path .. name
return recursion_mkdir(lfs.personaldir(), path)
end
function soluna.load_sprites(filename)
local render = ltask.uniqueservice "render"
local sprites = ltask.call(render, "load_sprites", filename)
return sprites
end
local audio_service
local voice_index = {}
local voice_mt = { __index = voice_index }
function voice_index:stop(fade_seconds)
return ltask.call(audio_service, "voice_stop", self.id, fade_seconds)
end
function voice_index:playing()
return ltask.call(audio_service, "voice_playing", self.id)
end
function voice_index:set_volume(volume)
return ltask.call(audio_service, "voice_set_volume", self.id, volume)
end
function voice_index:set_pan(pan)
return ltask.call(audio_service, "voice_set_pan", self.id, pan)
end
function voice_index:set_pitch(pitch)
return ltask.call(audio_service, "voice_set_pitch", self.id, pitch)
end
function voice_index:set_loop(loop)
return ltask.call(audio_service, "voice_set_loop", self.id, loop)
end
function voice_index:seek(seconds)
return ltask.call(audio_service, "voice_seek", self.id, seconds)
end
function voice_index:tell()
return ltask.call(audio_service, "voice_tell", self.id)
end
local bus_index = {}
local bus_mt = { __index = bus_index }
function bus_index:set_volume(volume)
return ltask.call(audio_service, "bus_set_volume", self.name, volume)
end
function soluna.load_sounds(filename)
audio_service = audio_service or ltask.uniqueservice "audio"
ltask.call(audio_service, "init", filename)
end
function soluna.play_sound(name, opts)
local id, err = ltask.call(audio_service, "play_sound", name, opts)
if not id then
return nil, err
end
return setmetatable({ id = id }, voice_mt)
end
function soluna.audio_bus(name)
if not ltask.call(audio_service, "has_bus", name) then
return nil, "Unknown audio bus " .. tostring(name)
end
return setmetatable({ name = name }, bus_mt)
end
function soluna.preload(spr)
local loader = ltask.uniqueservice "loader"
if #spr == 0 then
ltask.call(loader, "preload", spr.filename, spr.content, spr.w, spr.h)
else
local async = ltask.async()
for i = 1, #spr do
local s = spr[i]
async:request(loader, "preload", s.filename, s.content, s.w, s.h)
end
async:wait()
end
end
local function version()
local api, hash = app.version()
soluna.version_api = api
return string.format("%03x", api) .. hash:sub(1, 7)
end
soluna.version = version()
return soluna
================================================
FILE: src/lualib/spritebundle.lua
================================================
local image = require "soluna.image"
local file = require "soluna.file"
local datalist = require "soluna.datalist"
global type, tonumber, error, assert, ipairs, print
local M = {}
local function load_bundle(filename)
local b = datalist.parse(file.load(filename))
return b
end
local function crop_(item, c)
local x = item.cx
local y = item.cy
local w = item.cw
local h = item.ch
local offx = item.x or 0
local offy = item.y or 0
if offx < 0 then
offx = - c.w * offx // 1 | 0
end
if offy < 0 then
offy = - c.h * offy // 1 | 0
end
local cx, cy, cw, ch = image.crop(c.data, c.w, c.h, x, y, w, h)
offx = offx - cx
offy = offy - cy
item.x = offx
item.y = offy
item.cx = cx + (x or 0)
item.cy = cy + (y or 0)
item.cw = cw
item.ch = ch
end
local function unpack_size(size)
if type(size) == "number" then
return size, size
else
local x, y = size:match "(%d+)[xX*](%d+)"
return tonumber(x), tonumber(y)
end
end
local function crop(item, filecache)
local c = filecache[item.filename]
if c == nil then
error("No file : " .. item.filename)
end
local number = item.number
if number then
-- multi sprites
local cw, ch = unpack_size(assert(item.size))
local gap = item.gap
local gap_x = 0
local gap_y = 0
if gap then
gap_x, gap_y = unpack_size(gap)
end
local cx = 0
local cy = 0
local col = 1
local row
if type(number) == "number" then
row = number
else
row, col = unpack_size(number)
end
local count = 1
local offx = item.x
local offy = item.y
gap_x = gap_x + cw
gap_y = gap_y + ch
local filename = item.filename
for i = 1, col do
cx = 0
for j = 1, row do
local s = { cx = cx, cy = cy, cw = cw, ch = ch , x = offx , y = offy, filename = filename }
item[count] = s
crop_(s, c)
count = count + 1
cx = cx + gap_x
end
cy = cy + gap_y
end
else
crop_(item, c)
end
end
local function load_list(filecache, v, path)
for idx, item in ipairs(v) do
local fname = item.filename or "Need filename for item " .. idx
if path then
item.filename = path .. fname
end
crop(item, filecache)
end
end
function M.load(filecache, filename, path)
local v
if type(filename) == "table" then
v = filename
else
path = path or filename:match "(.*[/\\])[^/\\]+$"
v = load_bundle(filename)
end
load_list(filecache, v, path)
return v
end
function M.loadimage(filecache, filename)
local content = file.load(filename)
if not content then
if not filecache.__missing[filename] then
filecache.__missing[filename] = true
print("Missing file : " .. filename)
end
return
end
local load = image.load
if filename:find "%.alpha%." then
load = image.load_alpha
end
local data, w, h = load(content)
if data == nil then
if not filecache.__missing[filename] then
filecache.__missing[filename] = true
print("Invalid image : " .. filename .. "(" .. w .. ")")
end
return
end
local r = { data = data, w = w, h = h }
filecache[filename] = r
return r
end
return M
================================================
FILE: src/lualib/text.lua
================================================
local font = require "soluna.font"
local icon = require "soluna.icon"
global setmetatable, print, tonumber
local text = {}
local bundle_data
function text.init(bundle)
bundle_data = icon.bundle(bundle) -- prevent gc to collect bundle_data
font.import_icon(bundle_data)
end
local colors = {
red = "[FF0000]",
green = "[00FF00]",
blue = "[0000FF]",
white = "[FFFFFF]",
black = "[000000]",
aqua = "[00FFFF]",
yellow = "[FFFF00]",
pink = "[FF00FF]",
gray = "[808080]",
bracket = "[bracket]",
}
local function user_color(self, name)
if name:byte() == 99 then -- 'c'
local cvalue = name:sub(2)
local c = tonumber(cvalue, 16)
if c then
local cname = "[" .. cvalue .. "]"
self[name] = cname
return cname
end
end
end
setmetatable(colors, { __index = user_color })
local function icon_id(name)
local cname = colors[name]
if cname then
return cname
end
local id = icon.names[name]
if not id then
return "["..name.."]"
end
return "[i"..id.."]"
end
local function convert(tbl, key)
local escape = key:gsub("%[%[", "[bracket]")
local value = escape:gsub("%[(%w+)%]", icon_id)
if escape ~= key then
value = value:gsub("%[bracket%]", "[[")
elseif value == key then
-- uniforming long string
value = key
end
tbl[key]=value
return value
end
text.convert = setmetatable({}, { __mode = "kv", __index = convert })
return text
================================================
FILE: src/lualib/util.lua
================================================
local table = table
global setmetatable
local util = {}
local func_chain = {}; func_chain.__index = func_chain
function func_chain:add(f)
table.insert(self, f)
end
function func_chain:__call()
for i = 1, #self do
local f = self[i]
f()
end
end
function util.func_chain()
return setmetatable({}, func_chain)
end
return util
================================================
FILE: src/luamods.c
================================================
#include
#include
int luaopen_ltask(lua_State *L);
int luaopen_ltask_root(lua_State *L);
int luaopen_ltask_bootstrap(lua_State *L);
int luaopen_ltask_mqueue(lua_State *L);
int luaopen_embedsource(lua_State *L);
int luaopen_appmessage(lua_State *L);
int luaopen_applog(lua_State *L);
int luaopen_image(lua_State *L);
int luaopen_render(lua_State *L);
int luaopen_spritemgr(lua_State *L);
int luaopen_datalist(lua_State *L);
int luaopen_soluna_file(lua_State *L);
int luaopen_font_truetype(lua_State *L);
int luaopen_font_manager(lua_State *L);
int luaopen_font(lua_State *L);
int luaopen_drawmgr(lua_State *L);
int luaopen_material_default(lua_State *L);
int luaopen_material_text(lua_State *L);
int luaopen_material_quad(lua_State *L);
int luaopen_material_mask(lua_State *L);
int luaopen_material_blit(lua_State *L);
int luaopen_soluna_app(lua_State *L);
int luaopen_font_system(lua_State *L);
int luaopen_gamepad_device(lua_State *L);
int luaopen_gamepad(lua_State *L);
int luaopen_localfs(lua_State *L);
int luaopen_image_sdf(lua_State *L);
int luaopen_layout_yoga(lua_State *L);
int luaopen_url(lua_State *L);
int luaopen_skynet_crypt(lua_State *L);
int luaopen_zip(lua_State *L);
int luaopen_extlua(lua_State *L);
int luaopen_soluna_audio(lua_State *L);
void soluna_embed(lua_State* L) {
static const luaL_Reg modules[] = {
{ "ltask", luaopen_ltask},
{ "ltask.root", luaopen_ltask_root},
{ "ltask.bootstrap", luaopen_ltask_bootstrap},
{ "ltask.mqueue", luaopen_ltask_mqueue},
{ "soluna.app", luaopen_soluna_app },
{ "soluna.embedsource", luaopen_embedsource},
{ "soluna.log", luaopen_applog },
{ "soluna.image", luaopen_image },
{ "soluna.render", luaopen_render },
{ "soluna.spritemgr", luaopen_spritemgr },
{ "soluna.drawmgr", luaopen_drawmgr },
{ "soluna.material.default", luaopen_material_default },
{ "soluna.material.text", luaopen_material_text },
{ "soluna.material.quad", luaopen_material_quad },
{ "soluna.material.mask", luaopen_material_mask },
{ "soluna.material.blit", luaopen_material_blit },
{ "soluna.datalist", luaopen_datalist },
{ "soluna.file", luaopen_soluna_file },
{ "soluna.font", luaopen_font },
{ "soluna.font.truetype", luaopen_font_truetype },
{ "soluna.font.manager", luaopen_font_manager },
{ "soluna.font.system", luaopen_font_system },
{ "soluna.gamepad", luaopen_gamepad },
{ "soluna.gamepad.device", luaopen_gamepad_device },
{ "soluna.lfs", luaopen_localfs },
{ "soluna.image.sdf", luaopen_image_sdf },
{ "soluna.layout.yoga", luaopen_layout_yoga },
{ "soluna.url", luaopen_url },
{ "soluna.crypt", luaopen_skynet_crypt },
{ "soluna.zip", luaopen_zip },
{ "soluna.extlua", luaopen_extlua },
{ "soluna.audio", luaopen_soluna_audio },
{ NULL, NULL },
};
const luaL_Reg *lib;
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
for (lib = modules; lib->func; lib++) {
lua_pushcfunction(L, lib->func);
lua_setfield(L, -2, lib->name);
}
lua_pop(L, 1);
}
================================================
FILE: src/luayoga.c
================================================
#define LUA_LIB
#include
#include
#include
#include
#include "yoga/Yoga.h"
#define FlexDirection 1
#define Justify 2
#define Align 4
#define Wrap 8
#define Display 16
#define PositionType 32
#define ENUM(x, what) { YG##x##ToString(YG##x##what), (x) << 16 | (YG##x##what) },
struct enum_string {
const char * name;
int value;
};
struct set_number {
void (*set)(YGNodeRef node, float width);
void (*setPercent)(YGNodeRef node, float width);
void (*setAuto)(YGNodeRef node);
void (*setMaxContent)(YGNodeRef node);
void (*setFitContent)(YGNodeRef node);
void (*setStretch)(YGNodeRef node);
};
struct set_edge_number {
void (*set)(YGNodeRef node, YGEdge edge, float v);
void (*setPercent)(YGNodeRef node, YGEdge edge, float v);
void (*setAuto)(YGNodeRef node, YGEdge edge);
};
struct set_two_number {
void (*set)(YGNodeRef node, YGGutter gutter, float v);
void (*setPercent)(YGNodeRef node, YGGutter gutter, float v);
};
static int
lnodeNew(lua_State *L) {
YGNodeRef node = YGNodeNew();
if (lua_islightuserdata(L, 1)) {
YGNodeRef parent = lua_touserdata(L, 1);
size_t n = YGNodeGetChildCount(parent);
YGNodeInsertChild(parent, node, n);
}
lua_pushlightuserdata(L, node);
return 1;
}
static int
lnodeFree(lua_State *L) {
YGNodeRef node = lua_touserdata(L, 1);
YGNodeFreeRecursive(node);
return 0;
}
static int
lnodeCalc(lua_State *L) {
YGNodeRef node = lua_touserdata(L, 1);
YGNodeCalculateLayout(node, YGUndefined, YGUndefined, YGDirectionLTR);
return 0;
}
struct pos {
float x;
float y;
};
static void
get_pos(struct pos *p, YGNodeRef node) {
p->x = 0;
p->y = 0;
while (node) {
p->x += YGNodeLayoutGetLeft(node);
p->y += YGNodeLayoutGetTop(node);
node = YGNodeGetParent(node);
}
}
static int
lnodeGet(lua_State *L) {
YGNodeRef node = lua_touserdata(L, 1);
struct pos p;
get_pos(&p, node);
float r[] = {
p.x,
p.y,
YGNodeLayoutGetWidth(node),
YGNodeLayoutGetHeight(node)
};
int i;
for (i=0;i<4;i++) {
lua_pushnumber(L, r[i]);
}
return 4;
}
typedef void (*setfunc)(lua_State *L, YGNodeRef node);
static inline int
is_whitespace(char c) {
return c =='\0' || c == ' ' || c == '\t';
}
static void
setNumberString(lua_State *L, YGNodeRef node, const char *v, const struct set_number *setter) {
char* endptr = NULL;
float number = strtof(v, &endptr);
if (*endptr == '%') {
setter->setPercent(node, number);
} else if (is_whitespace(*endptr)) {
setter->set(node, number);
} else if (setter->setAuto && strcmp(v, "auto") == 0) {
setter->setAuto(node);
} else if (strcmp(v, "stretch") == 0) {
setter->setStretch(node);
} else if (strcmp(v, "max-content") == 0) {
setter->setMaxContent(node);
} else if (strcmp(v, "fit-content") == 0) {
setter->setFitContent(node);
} else {
luaL_error(L, "Invalid number %s", v);
}
}
static void
setNumber(lua_State *L, YGNodeRef node, const struct set_number *setter) {
if (lua_type(L, -1) == LUA_TNUMBER) {
float v = lua_tonumber(L, -1);
setter->set(node, v);
} else {
const char * v = luaL_checkstring(L, -1);
setNumberString(L, node, v, setter);
}
}
static void
lsetWidth(lua_State *L, YGNodeRef node) {
static const struct set_number setter = {
YGNodeStyleSetWidth,
YGNodeStyleSetWidthPercent,
YGNodeStyleSetWidthAuto,
YGNodeStyleSetWidthMaxContent,
YGNodeStyleSetWidthFitContent,
};
setNumber(L, node, &setter);
}
static void
lsetMinWidth(lua_State *L, YGNodeRef node) {
static const struct set_number setter = {
YGNodeStyleSetMinWidth,
YGNodeStyleSetMinWidthPercent,
NULL,
YGNodeStyleSetMinWidthMaxContent,
YGNodeStyleSetMinWidthFitContent,
};
setNumber(L, node, &setter);
}
static void
lsetMaxWidth(lua_State *L, YGNodeRef node) {
static const struct set_number setter = {
YGNodeStyleSetMaxWidth,
YGNodeStyleSetMaxWidthPercent,
NULL,
YGNodeStyleSetMaxWidthMaxContent,
YGNodeStyleSetMaxWidthFitContent,
};
setNumber(L, node, &setter);
}
static void
lsetHeight(lua_State *L, YGNodeRef node) {
static const struct set_number setter = {
YGNodeStyleSetHeight,
YGNodeStyleSetHeightPercent,
YGNodeStyleSetHeightAuto,
YGNodeStyleSetHeightMaxContent,
YGNodeStyleSetHeightFitContent,
};
setNumber(L, node, &setter);
}
static void
lsetMinHeight(lua_State *L, YGNodeRef node) {
static const struct set_number setter = {
YGNodeStyleSetMinHeight,
YGNodeStyleSetMinHeightPercent,
NULL,
YGNodeStyleSetMinHeightMaxContent,
YGNodeStyleSetMinHeightFitContent,
};
setNumber(L, node, &setter);
}
static void
lsetMaxHeight(lua_State *L, YGNodeRef node) {
static const struct set_number setter = {
YGNodeStyleSetMaxHeight,
YGNodeStyleSetMaxHeightPercent,
NULL,
YGNodeStyleSetMaxHeightMaxContent,
YGNodeStyleSetMaxHeightFitContent,
};
setNumber(L, node, &setter);
}
static const char *
skip_whitespace(const char *v) {
while(*v == ' ' || *v == '\t') {
++v;
}
return v;
}
static int
count_words(const char *v) {
int n = 0;
do {
v = skip_whitespace(v);
if (*v != '\0') {
++n;
while (!is_whitespace(*v))
++v;
}
} while (*v != '\0');
return n;
}
static const char *
setEdgeNumber(lua_State *L, YGNodeRef node, YGEdge edge, const char *v, const struct set_edge_number *setter) {
v = skip_whitespace(v);
char* endptr = NULL;
float number = strtof(v, &endptr);
if (is_whitespace(*endptr)) {
setter->set(node, edge, number);
return endptr;
} else if (setter->setPercent && *endptr == '%') {
setter->setPercent(node, edge, number);
return endptr+1;
} else if (setter->setAuto && memcmp("auto", v, 4) == 0) {
if (!is_whitespace(v[4]))
luaL_error(L, "Invalid number %s", v);
setter->setAuto(node, edge);
return v + 4;
} else {
luaL_error(L, "Invalid number %s", v);
}
return NULL;
}
static void
setFourNumber(lua_State *L, YGNodeRef node, const struct set_edge_number *setter) {
if (lua_type(L, -1) == LUA_TNUMBER) {
float v = lua_tonumber(L, -1);
setter->set(node, YGEdgeAll, v);
} else {
const char * v = luaL_checkstring(L, -1);
switch (count_words(v)) {
case 1:
setEdgeNumber(L, node, YGEdgeAll, v, setter);
break;
case 2:
v = setEdgeNumber(L, node, YGEdgeVertical, v, setter);
setEdgeNumber(L, node, YGEdgeHorizontal, v, setter);
break;
case 3:
v = setEdgeNumber(L, node, YGEdgeTop, v, setter);
v = setEdgeNumber(L, node, YGEdgeHorizontal, v, setter);
setEdgeNumber(L, node, YGEdgeBottom, v, setter);
break;
case 4:
v = setEdgeNumber(L, node, YGEdgeTop, v, setter);
v = setEdgeNumber(L, node, YGEdgeEnd, v, setter);
v = setEdgeNumber(L, node, YGEdgeBottom, v, setter);
setEdgeNumber(L, node, YGEdgeStart, v, setter);
break;
default:
luaL_error(L, "Invalid numbers %s", v);
}
}
}
static const char *
setGapNumber(lua_State *L, YGNodeRef node, YGGutter gutter, const char *v, const struct set_two_number *setter) {
v = skip_whitespace(v);
char* endptr = NULL;
float number = strtof(v, &endptr);
if (is_whitespace(*endptr)) {
setter->set(node, gutter, number);
return endptr;
} else if (setter->setPercent && *endptr == '%') {
setter->setPercent(node, gutter, number);
return endptr+1;
} else {
luaL_error(L, "Invalid number %s", v);
}
return NULL;
}
static void
setTwoNumber(lua_State *L, YGNodeRef node, const struct set_two_number *setter) {
if (lua_type(L, -1) == LUA_TNUMBER) {
float v = lua_tonumber(L, -1);
setter->set(node, YGGutterAll, v);
} else {
const char * v = luaL_checkstring(L, -1);
switch (count_words(v)) {
case 1:
setGapNumber(L, node, YGGutterAll, v, setter);
break;
case 2:
v = setGapNumber(L, node, YGGutterRow, v, setter);
setGapNumber(L, node, YGGutterColumn, v, setter);
break;
default:
luaL_error(L, "Invalid numbers %s", v);
}
}
}
static const char *
getNumber(lua_State *L, const char *v, float *num) {
char* endptr = NULL;
*num = strtof(v, &endptr);
if (!is_whitespace(*endptr))
luaL_error(L, "Invalid number %s", v);
return endptr;
}
static void
setFlexBasis(lua_State *L, YGNodeRef node, const char *v) {
static const struct set_number setter = {
YGNodeStyleSetFlexBasis,
YGNodeStyleSetFlexBasisPercent,
YGNodeStyleSetFlexBasisAuto,
YGNodeStyleSetFlexBasisMaxContent,
YGNodeStyleSetFlexBasisFitContent,
};
v = skip_whitespace(v);
setNumberString(L, node, v, &setter);
}
static void
lsetFlex(lua_State *L, YGNodeRef node) {
if (lua_type(L, -1) == LUA_TNUMBER) {
float v = lua_tonumber(L, -1);
YGNodeStyleSetFlex(node, v);
} else {
const char * v = luaL_checkstring(L, -1);
char* endptr = NULL;
float number;
// https://developer.mozilla.org/en-US/docs/Web/CSS/flex
switch (count_words(v)) {
case 1:
// only one word
number = strtof(v, &endptr);
if (is_whitespace(*endptr)) {
// is number, example : 1
// flex-glow 1 0%
YGNodeStyleSetFlex(node, number);
} else {
// not a number, example : 50%
// 1 1 flex-basis
YGNodeStyleSetFlexGrow(node, 1);
YGNodeStyleSetFlexShrink(node, 1);
setFlexBasis(L, node, v);
}
break;
case 2:
v = getNumber(L, v, &number);
YGNodeStyleSetFlexGrow(node, number);
number = strtof(v, &endptr);
if (is_whitespace(*endptr)) {
YGNodeStyleSetFlexShrink(node, number);
YGNodeStyleSetFlexBasisPercent(node, 0);
} else {
YGNodeStyleSetFlexShrink(node, 1);
setFlexBasis(L, node, v);
}
break;
case 3:
v = getNumber(L, v, &number);
YGNodeStyleSetFlexGrow(node, number);
v = getNumber(L, v, &number);
YGNodeStyleSetFlexShrink(node, number);
setFlexBasis(L, node, v);
break;
default:
luaL_error(L, "Invalid flex %s", v);
}
}
}
static void
lsetMargin(lua_State *L, YGNodeRef node) {
static const struct set_edge_number setter = {
YGNodeStyleSetMargin,
YGNodeStyleSetMarginPercent,
YGNodeStyleSetMarginAuto,
};
setFourNumber(L, node, &setter);
}
static void
lsetPadding(lua_State *L, YGNodeRef node) {
static const struct set_edge_number setter = {
YGNodeStyleSetPadding,
YGNodeStyleSetPaddingPercent,
NULL,
};
setFourNumber(L, node, &setter);
}
static void
lsetBorder(lua_State *L, YGNodeRef node) {
static const struct set_edge_number setter = {
YGNodeStyleSetBorder,
NULL,
NULL,
};
setFourNumber(L, node, &setter);
}
static void
lsetGap(lua_State *L, YGNodeRef node) {
static const struct set_two_number setter = {
YGNodeStyleSetGap,
YGNodeStyleSetGapPercent,
};
setTwoNumber(L, node, &setter);
}
static int
getEnum(lua_State *L, int type, const char *pname) {
lua_pushvalue(L, -1);
if (lua_rawget(L, lua_upvalueindex(2)) == LUA_TNUMBER) {
int v = lua_tointeger(L, -1);
if (((v >> 16) & type) == type) {
v &= 0xffff;
return v;
}
}
return luaL_error(L, "Invalid enum %s for %s", luaL_tolstring(L, -2, NULL), pname);
}
static int
getEnumHigh(lua_State *L, int type, const char *pname) {
int e = getEnum(L, type, pname);
return e >> 8;
}
static int
getEnumLow(lua_State *L, int type, const char *pname) {
int e = getEnum(L, type, pname);
return e & 0xff;
}
static void
lsetFlexDirection(lua_State *L, YGNodeRef node) {
YGNodeStyleSetFlexDirection(node, getEnum(L, FlexDirection, "flex-direction"));
}
static void
lsetJustifyContent(lua_State *L, YGNodeRef node) {
YGNodeStyleSetJustifyContent(node, getEnumHigh(L, Justify, "justify-content"));
}
static void
lsetAlignItems(lua_State *L, YGNodeRef node) {
YGNodeStyleSetAlignItems(node, getEnumLow(L, Align, "align-items"));
}
static void
lsetAlignContent(lua_State *L, YGNodeRef node) {
YGNodeStyleSetAlignContent(node, getEnumLow(L, Align, "align-content"));
}
static void
lsetAlignSelf(lua_State *L, YGNodeRef node) {
YGNodeStyleSetAlignSelf(node, getEnumLow(L, Align, "align-self"));
}
static void
lsetWrap(lua_State *L, YGNodeRef node) {
YGNodeStyleSetFlexWrap(node, getEnum(L, Wrap, "wrap"));
}
static void
lsetDisplay(lua_State *L, YGNodeRef node) {
YGNodeStyleSetDisplay(node, getEnum(L, Display, "display"));
}
static void
lsetPosition(lua_State *L, YGNodeRef node) {
YGNodeStyleSetPositionType(node, getEnum(L, PositionType, "position"));
}
static void
setPosition(lua_State *L, YGNodeRef node, YGEdge edge) {
static const struct set_edge_number setter = {
YGNodeStyleSetPosition,
YGNodeStyleSetPositionPercent,
YGNodeStyleSetPositionAuto,
};
if (lua_type(L, -1) == LUA_TNUMBER) {
float v = luaL_checknumber(L, -1);
setter.set(node, edge, v);
} else {
const char *v = luaL_checkstring(L, -1);
setEdgeNumber(L, node, edge, v, &setter);
}
}
static void
lsetTop(lua_State *L, YGNodeRef node) {
setPosition(L, node, YGEdgeTop);
}
static void
lsetBottom(lua_State *L, YGNodeRef node) {
setPosition(L, node, YGEdgeBottom);
}
static void
lsetLeft(lua_State *L, YGNodeRef node) {
setPosition(L, node, YGEdgeLeft);
}
static void
lsetRight(lua_State *L, YGNodeRef node) {
setPosition(L, node, YGEdgeRight);
}
static void
lsetAspectRatio(lua_State *L, YGNodeRef node) {
float v = luaL_checknumber(L, -1);
YGNodeStyleSetAspectRatio(node, v);
}
static void
set_array(lua_State *L, YGNodeRef node) {
lua_pushnil(L);
int top = lua_gettop(L);
while (lua_next(L, 2) != 0) {
lua_pushvalue(L, -2);
if (lua_rawget(L, lua_upvalueindex(1)) == LUA_TLIGHTUSERDATA) {
setfunc func = (setfunc)lua_touserdata(L, -1);
lua_pop(L, 1);
func(L, node);
}
lua_settop(L, top);
}
}
static void
set_one(lua_State *L, YGNodeRef node) {
lua_settop(L, 3);
lua_pushvalue(L, 2);
if (lua_rawget(L, lua_upvalueindex(1)) == LUA_TLIGHTUSERDATA) {
setfunc func = (setfunc)lua_touserdata(L, -1);
lua_pop(L, 1);
func(L, node);
} else {
luaL_error(L, "Invalid attrib name : %s", lua_tostring(L, 2));
}
}
static int
lnodeSet(lua_State *L) {
YGNodeRef node = lua_touserdata(L, 1);
switch(lua_type(L, 2)) {
case LUA_TTABLE:
set_array(L, node);
break;
case LUA_TSTRING:
set_one(L, node);
break;
default:
return luaL_error(L, "Set table or key, value");
}
return 0;
}
LUAMOD_API int
luaopen_layout_yoga(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "node_new", lnodeNew },
{ "node_free", lnodeFree },
{ "node_calc", lnodeCalc },
{ "node_get", lnodeGet },
{ "node_set", NULL },
{ NULL, NULL },
};
luaL_newlib(L, l);
struct {
const char *name;
setfunc func;
} setter [] = {
{ "width", lsetWidth },
{ "height", lsetHeight },
{ "minWidth", lsetMinWidth },
{ "maxWidth", lsetMaxWidth },
{ "minHeight", lsetMinHeight },
{ "maxHeight", lsetMaxHeight },
{ "direction", lsetFlexDirection },
{ "justify", lsetJustifyContent },
{ "alignItems", lsetAlignItems },
{ "alignContent", lsetAlignContent },
{ "alignSelf", lsetAlignSelf },
{ "margin", lsetMargin },
{ "padding", lsetPadding },
{ "border", lsetBorder },
{ "gap", lsetGap },
{ "wrap", lsetWrap },
{ "display", lsetDisplay },
{ "flex", lsetFlex },
{ "position", lsetPosition },
{ "top", lsetTop },
{ "bottom", lsetBottom },
{ "left", lsetLeft },
{ "right", lsetRight },
{ "aspectRatio", lsetAspectRatio },
};
int n = sizeof(setter) / sizeof(setter[0]);
int i;
lua_createtable(L, n, 0);
for (i=0;i
#include
#include
#include
#define ZLIB_UTF8_FLAG (1<<11)
#define FILECHUNK (4096 * 4)
static void *
external_free(void *ud, void *ptr, size_t osize, size_t nsize) {
free(ptr);
return NULL;
}
static int
lcompress(lua_State *L) {
size_t sz;
const char * src = luaL_checklstring(L, 1, &sz);
uLongf len = compressBound(sz);
char * buf = (char *)malloc(len + 1);
if (buf == NULL) {
return luaL_error(L, "Compress OOM");
}
if (compress((void *)(buf + 1), &len, (void *)src, sz) != Z_OK) {
free(buf);
return luaL_error(L, "Compress error");
}
int idx = 0;
size_t tmp = len;
while (sz > tmp) {
++idx;
tmp *= 2;
}
// 0 : the same size
// 1 : 2x size
// 2...n : (1<
#include "zlib/contrib/minizip/iowin32.h"
struct filename_convert {
WCHAR tmp[4096];
};
static zipFile
zip_open(lua_State *L, const char *filename, int append) {
struct filename_convert tmp;
if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, tmp.tmp, sizeof(tmp)) == 0) {
if (L == NULL)
return NULL;
luaL_error(L, "Can't convert %s to utf16", filename);
}
zlib_filefunc64_def ffunc;
fill_win32_filefunc64W(&ffunc);
return zipOpen2_64((const char *)tmp.tmp, append ? APPEND_STATUS_ADDINZIP : 0, NULL, &ffunc);
}
static unzFile
unzip_open(lua_State *L, const char *filename) {
struct filename_convert tmp;
if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, tmp.tmp, sizeof(tmp)) == 0) {
if (L == NULL)
return NULL;
luaL_error(L, "Can't convert %s to utf16", filename);
}
zlib_filefunc64_def ffunc;
fill_win32_filefunc64W(&ffunc);
return unzOpen2_64((const char *)tmp.tmp, &ffunc);
}
static FILE *
file_open(lua_State *L, const char *filename, const char *mode, struct filename_convert *tmp) {
if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, tmp->tmp, sizeof(*tmp)) == 0) {
if (L == NULL)
return NULL;
luaL_error(L, "Can't convert %s to utf16", filename);
}
WCHAR m[32];
int i;
for (i=0; mode[i]; i++) {
if (i == 31) {
if (L == NULL)
return NULL;
luaL_error(L, "Invalid mode %s", mode);
}
m[i] = mode[i];
}
m[i] = 0;
return _wfopen(tmp->tmp, m);
}
#else
struct filename_convert {};
static zipFile
zip_open(lua_State *L, const char *filename, int append) {
return zipOpen(filename, append ? APPEND_STATUS_ADDINZIP : 0);
}
static unzFile
unzip_open(lua_State *L, const char *filename) {
return unzOpen2(filename, 0);
}
static FILE *
file_open(lua_State *L, const char *filename, const char *mode, struct filename_convert *tmp) {
return fopen(filename, mode);
}
#endif
struct ziphandle {
zipFile h;
};
struct zipraw {
int method;
int level;
};
static zipFile
open_new(lua_State *L, int index, const struct zipraw *raw, int level) {
const char *filename = luaL_checkstring(L, index);
struct ziphandle *z = (struct ziphandle *)luaL_checkudata(L, 1, "ZIP_WRITE");
if (z->h == NULL)
luaL_error(L, "Error: closed");
if (lua_getiuservalue(L, 1, 1) != LUA_TTABLE)
luaL_error(L, "Invalid zip userdata");
int cache = lua_gettop(L);
lua_pushvalue(L, index);
if (lua_rawget(L, cache) != LUA_TNIL) {
luaL_error(L, "Error: %s exist", filename);
}
int err = zipOpenNewFileInZip4(z->h, filename, NULL, NULL, 0, NULL, 0, NULL,
raw ? raw->method : Z_DEFLATED,
raw ? raw->level : level,
raw != NULL,
-MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
NULL, 0, 0,
ZLIB_UTF8_FLAG
);
if (err != ZIP_OK) {
luaL_error(L, "Error: open in file");
}
lua_pushvalue(L, 2);
lua_pushboolean(L, 1);
lua_rawset(L, cache);
lua_settop(L, cache - 1);
return z->h;
}
static inline void
close_inzip(lua_State *L, zipFile zf) {
if (zipCloseFileInZip(zf) != ZIP_OK) {
luaL_error(L, "Error: close in file");
}
}
static int
zipwrite_add(lua_State *L) {
int level = luaL_optinteger(L, 4, Z_DEFAULT_COMPRESSION);
zipFile zf = open_new(L, 2, NULL, level);
size_t sz;
const char * content = luaL_checklstring(L, 3, &sz);
int err = zipWriteInFileInZip(zf, content, sz);
if (err != ZIP_OK) {
return luaL_error(L, "Error: write in file");
}
close_inzip(L, zf);
return 0;
}
static int
zipwrite_addfile(lua_State *L) {
int level = luaL_optinteger(L, 4, Z_DEFAULT_COMPRESSION);
zipFile zf = open_new(L, 2, NULL, level);
const char * addfile = luaL_checkstring(L, 3);
struct filename_convert tmp;
FILE *f = file_open(L, addfile, "rb", &tmp);
if (f == NULL)
return luaL_error(L, "Can't open %s", addfile);
char buf[FILECHUNK];
for (;;) {
int bytes = fread(buf, 1, FILECHUNK, f);
if (bytes <= 0) {
if (bytes == 0)
break;
return luaL_error(L, "Error: read file %s", addfile);
}
int err = zipWriteInFileInZip(zf, buf, bytes);
if (err != ZIP_OK) {
fclose(f);
return luaL_error(L, "Error: write in file");
}
if (bytes < FILECHUNK)
break;
}
fclose(f);
close_inzip(L, zf);
return 0;
}
static int
zipwrite_open(lua_State *L) {
int level = luaL_optinteger(L, 3, Z_DEFAULT_COMPRESSION);
open_new(L, 2, NULL, level);
return 0;
}
static int
zipwrite_close(lua_State *L) {
struct ziphandle *z = (struct ziphandle *)luaL_checkudata(L, 1, "ZIP_WRITE");
if (z->h == NULL)
luaL_error(L, "Error: closed");
close_inzip(L, z->h);
return 0;
}
static int
zipwrite_closezip(lua_State *L) {
struct ziphandle *z = (struct ziphandle *)luaL_checkudata(L, 1, "ZIP_WRITE");
if (z->h == NULL)
return 0;
int err = zipClose(z->h, NULL);
z->h = NULL;
if (err != Z_OK)
return luaL_error(L, "Error: close");
return 0;
}
static int
zipwrite_write(lua_State *L) {
struct ziphandle *z = (struct ziphandle *)luaL_checkudata(L, 1, "ZIP_WRITE");
if (z->h == NULL)
luaL_error(L, "Error: closed");
size_t sz;
const char *content = luaL_checklstring(L, 2, &sz);
int err = zipWriteInFileInZip(z->h, content, sz);
if (err != ZIP_OK) {
return luaL_error(L, "Error: write in file");
}
return 0;
}
struct unzhandle {
unzFile h;
};
static int
zipread_closezip(lua_State *L) {
struct unzhandle *z = (struct unzhandle *)luaL_checkudata(L, 1, "ZIP_READ");
if (z->h == NULL)
luaL_error(L, "Error: closed");
int err = unzClose(z->h);
z->h = NULL;
if (err != UNZ_OK)
return luaL_error(L, "Error: close");
return 0;
}
static inline lua_Integer
file_pos_to_luaint(const unz_file_pos *pos) {
uint64_t p = pos->pos_in_zip_directory;
uint64_t n = pos->num_of_file;
return (lua_Integer)(p << 32 | n);
}
static inline unz_file_pos *
luaint_to_file_pos(lua_Integer v, unz_file_pos *pos) {
pos->pos_in_zip_directory = (uint64_t)v >> 32;
pos->num_of_file = v & 0xffffffff;
return pos;
}
static void
get_filelist(lua_State *L, unzFile zf) {
lua_newtable(L);
int err = unzGoToFirstFile(zf);
if (err != UNZ_OK)
luaL_error(L, "Error: goto first file");
char filename[4096];
for (;;) {
unz_file_pos pos;
unzGetFilePos(zf, &pos);
int err = unzGetCurrentFileInfo(zf, NULL, filename, sizeof(filename), NULL, 0, NULL, 0);
if (err != UNZ_OK)
luaL_error(L, "Error: get file info %d", pos.num_of_file);
lua_pushinteger(L, file_pos_to_luaint(&pos));
lua_setfield(L, -2, filename);
err = unzGoToNextFile(zf);
if (err != UNZ_OK) {
if (err == UNZ_END_OF_LIST_OF_FILE)
break;
luaL_error(L, "Error: goto next file %d", pos.num_of_file);
}
}
}
static int
zipread_list(lua_State *L) {
if (lua_getiuservalue(L, 1, 1) != LUA_TTABLE)
return luaL_error(L, "Invalid zip userdata");
int t = lua_gettop(L);
lua_newtable(L);
int r = t+1;
lua_pushnil(L);
while (lua_next(L, t) != 0) {
if (lua_type(L, -1) == LUA_TNUMBER) {
int n = luaL_checkinteger(L, -1);
lua_pop(L, 1);
lua_pushvalue(L, -1);
lua_rawseti(L, r, n+1);
} else {
// filename
lua_pop(L, 1);
}
}
return 1;
}
static void
locate_file(lua_State *L, unzFile zf, lua_Integer pos) {
unz_file_pos tmp;
int err = unzGoToFilePos(zf, luaint_to_file_pos(pos, &tmp));
if (err != UNZ_OK)
luaL_error(L, "Error: unzGoToFilePos");
}
static int
zipread_exist(lua_State *L) {
if (lua_getiuservalue(L, 1, 1) != LUA_TTABLE) {
luaL_error(L, "Invalid zip userdata");
}
lua_pushvalue(L, 2); // filename
int exist = (lua_rawget(L, -2) == LUA_TNUMBER);
lua_pushboolean(L, exist);
return 1;
}
static unzFile
open_file(lua_State *L, int rzip, int filename, struct zipraw *raw) {
if (lua_getiuservalue(L, rzip, 1) != LUA_TTABLE) {
luaL_error(L, "Invalid zip userdata");
}
lua_pushvalue(L, filename); // filename
if (lua_rawget(L, -2) != LUA_TNUMBER) {
lua_pop(L, 1);
return NULL;
}
lua_Integer pos = luaL_checkinteger(L, -1);
lua_pop(L, 2);
struct unzhandle *z = (struct unzhandle *)luaL_checkudata(L, rzip, "ZIP_READ");
if (z->h == NULL)
luaL_error(L, "Error: closed");
locate_file(L, z->h, pos);
int err;
if (raw) {
err = unzOpenCurrentFile2(z->h, &raw->method, &raw->level, 1);
} else {
err = unzOpenCurrentFile(z->h);
}
if (err != UNZ_OK)
luaL_error(L, "Error: open file %s", lua_tostring(L, filename));
return z->h;
}
static void
close_file(lua_State *L, unzFile zf) {
int err = unzCloseCurrentFile(zf);
if (err != UNZ_OK) {
if (err == UNZ_CRCERROR)
luaL_error(L, "Error: CRC");
else
luaL_error(L, "Error: close file");
}
}
static int
zipread_readfile(lua_State *L) {
unzFile zf = open_file(L, 1, 2, NULL);
if (zf == NULL)
return 0;
unz_file_info info;
int err = unzGetCurrentFileInfo(zf, &info, NULL, 0, NULL, 0, NULL, 0);
if (err != UNZ_OK) {
close_file(L, zf);
luaL_error(L, "Error: get file info %s", lua_tostring(L, 2));
}
void *buf = malloc(info.uncompressed_size);
if (buf == NULL) {
close_file(L, zf);
luaL_error(L, "Error: out of memory");
}
int bytes = unzReadCurrentFile(zf, buf, info.uncompressed_size);
if (bytes != info.uncompressed_size) {
free(buf);
close_file(L, zf);
luaL_error(L, "Error: read file %s", lua_tostring(L, 2));
}
lua_pushexternalstring(L, buf, bytes, external_free, NULL);
close_file(L, zf);
return 1;
}
static int
zipread_size(lua_State *L) {
unzFile zf = open_file(L, 1, 2, NULL);
if (zf == NULL)
return 0;
unz_file_info info;
int err = unzGetCurrentFileInfo(zf, &info, NULL, 0, NULL, 0, NULL, 0);
if (err != UNZ_OK)
luaL_error(L, "Error: get file info %s", lua_tostring(L, 2));
lua_pushinteger(L, info.uncompressed_size);
return 1;
}
static int
zipread_extract(lua_State *L) {
unzFile zf = open_file(L, 1, 2, NULL);
if (zf == NULL)
return luaL_error(L, "Error: no file %s", lua_tostring(L, 2));
const char * filename = luaL_checkstring(L, 3);
struct filename_convert tmp;
FILE *f = file_open(L, filename, "wb", &tmp);
if (f == NULL) {
close_file(L, zf);
return luaL_error(L, "Error: open %s", filename);
}
char buf[FILECHUNK];
int bytes = 0;
do {
bytes = unzReadCurrentFile(zf, buf, sizeof(buf));
if (bytes < 0) {
close_file(L, zf);
return luaL_error(L, "Error: read %s", lua_tostring(L, 2));
}
if (bytes > 0 && fwrite(buf, 1, bytes, f) != bytes) {
close_file(L, zf);
return luaL_error(L, "Error: write %s", filename);
}
} while (bytes == sizeof(buf));
fclose(f);
close_file(L, zf);
return 0;
}
static int
zipread_openfile(lua_State *L) {
open_file(L, 1, 2, NULL);
return 0;
}
static int
zipread_closefile(lua_State *L) {
struct unzhandle *z = (struct unzhandle *)luaL_checkudata(L, 1, "ZIP_READ");
if (z->h == NULL)
luaL_error(L, "Error: closed");
close_file(L, z->h);
return 0;
}
static int
zipread_read(lua_State *L) {
struct unzhandle *z = (struct unzhandle *)luaL_checkudata(L, 1, "ZIP_READ");
if (z->h == NULL)
luaL_error(L, "Error: closed");
int n = luaL_checkinteger(L, 2);
void *buf = malloc(n);
if (buf == NULL)
return luaL_error(L, "Error: out of memory");
int bytes = unzReadCurrentFile(z->h, buf, n);
if (bytes <= 0) {
free(buf);
if (bytes == 0)
return 0;
luaL_error(L, "Error: read file");
}
lua_pushexternalstring(L, buf, bytes, external_free, NULL);
return 1;
}
// 2: filename
// 3: readzip (userdata)
// 4: opt: altername
static int
zipwrite_copyfrom(lua_State *L) {
int filename = lua_isnoneornil(L, 4) ? 2 : 4;
struct zipraw raw;
unzFile rd = open_file(L, 3, filename, &raw);
if (rd == NULL)
return luaL_error(L, "Error: open %s", lua_tostring(L, filename));
zipFile zf = open_new(L, 2, &raw, raw.level);
if (zf == NULL) {
close_file(L, rd);
return luaL_error(L, "Error: open %s", lua_tostring(L, 2));
}
// copy file from rd to zf
// todo : zipCloseFileInZipRaw on error
unz_file_info info;
int err = unzGetCurrentFileInfo(rd, &info, NULL, 0, NULL, 0, NULL, 0);
if (err != UNZ_OK) {
close_file(L, rd);
luaL_error(L, "Error: get file info %s", lua_tostring(L, filename));
}
char buf[FILECHUNK];
int bytes;
do {
bytes = unzReadCurrentFile(rd, buf, sizeof(buf));
if (bytes < 0) {
close_file(L, rd);
return luaL_error(L, "Error: read %s", lua_tostring(L, filename));
}
if (bytes > 0 && zipWriteInFileInZip(zf, buf, bytes) != ZIP_OK) {
close_file(L, rd);
return luaL_error(L, "Error: write %s", lua_tostring(L, 2));
}
} while (bytes == sizeof(buf));
close_file(L, rd);
if (zipCloseFileInZipRaw(zf, info.uncompressed_size, info.crc) != ZIP_OK)
return luaL_error(L, "Error: close %s", 2);
return 0;
}
static int
zipread_filename(lua_State *L) {
const int rzip = 1;
const int filename = 2;
if (lua_getiuservalue(L, rzip, 1) != LUA_TTABLE) {
luaL_error(L, "Invalid zip userdata");
}
lua_pushvalue(L, filename); // filename
if (lua_rawget(L, -2) != LUA_TNUMBER) {
return 0;
}
lua_Integer v = luaL_checkinteger(L, -1);
lua_pop(L, 1);
unz_file_pos pos;
luaint_to_file_pos(v, &pos);
if (lua_rawgeti(L, -1, 0) != LUA_TSTRING) {
return luaL_error(L, "No zip filename");
}
lua_pushfstring(L, "%s|%d", lua_tostring(L, -1), pos.num_of_file);
return 1;
}
static int
unzip(lua_State *L, const char *filename) {
unzFile zf = unzip_open(L, filename);
if (zf == NULL)
return 0;
struct unzhandle *z = (struct unzhandle *)lua_newuserdatauv(L, sizeof(*z), 1);
get_filelist(L, zf);
lua_pushstring(L, filename);
lua_rawseti(L, -2, 0);
lua_setiuservalue(L, -2, 1);
z->h = zf;
if (luaL_newmetatable(L, "ZIP_READ")) {
luaL_Reg l[] = {
{ "__index", NULL },
{ "__gc", zipread_closezip },
{ "close", zipread_closezip },
{ "list", zipread_list },
{ "extract", zipread_extract },
{ "readfile", zipread_readfile },
{ "exist", zipread_exist },
{ "openfile", zipread_openfile },
{ "closefile", zipread_closefile },
{ "read", zipread_read },
{ "size", zipread_size },
{ "filename", zipread_filename },
{ NULL, NULL },
};
luaL_setfuncs(L, l, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
}
lua_setmetatable(L, -2);
return 1;
}
static int
zip(lua_State *L, const char *filename, int append) {
if (append) {
unzFile uzf = unzip_open(L, filename);
if (uzf == NULL)
return 0;
get_filelist(L, uzf);
unzClose(uzf);
} else {
lua_newtable(L); // cache filenames
}
zipFile zf = zip_open(L, filename, append);
if (zf == NULL)
return 0;
struct ziphandle *z = (struct ziphandle *)lua_newuserdatauv(L, sizeof(*z), 1);
lua_insert(L, -2);
lua_setiuservalue(L, -2, 1);
z->h = zf;
if (luaL_newmetatable(L, "ZIP_WRITE")) {
luaL_Reg l[] = {
{ "__index", NULL },
{ "__gc", zipwrite_closezip },
{ "copyfrom", zipwrite_copyfrom },
{ "addfile", zipwrite_addfile },
{ "add", zipwrite_add },
{ "openfile", zipwrite_open },
{ "closefile", zipwrite_close },
{ "write", zipwrite_write },
{ "close", zipwrite_closezip },
{ NULL, NULL },
};
luaL_setfuncs(L, l, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
}
lua_setmetatable(L, -2);
return 1;
}
static int
lzip(lua_State *L) {
const char * filename = luaL_checkstring(L, 1);
const char * mode = luaL_checkstring(L, 2);
switch (mode[0]) {
case 'w':
return zip(L, filename, 0);
case 'r':
return unzip(L, filename);
case 'a':
return zip(L, filename, 1);
default:
return luaL_error(L, "Invalid Mode %s", mode);
}
}
static int
lziplist(lua_State *L) {
if (lua_isnoneornil(L, 1)) {
return 0;
}
luaL_checktype(L, 1, LUA_TTABLE);
int n = lua_rawlen(L, 1);
struct zipreader_name * names = (struct zipreader_name *)lua_newuserdatauv(L, sizeof(struct zipreader_name) * (n + 1), n * 2);
int ud_index = lua_gettop(L);
names[n].zipfile = NULL;
names[n].root = NULL;
names[n].root_size = 0;
int i;
for (i = 0; i < n; i++) {
struct zipreader_name * name = &names[i];
if (lua_geti(L, 1, n - i) != LUA_TTABLE) {
return luaL_error(L, "Invalid ziplist table");
}
if (lua_getfield(L, -1, "name") != LUA_TSTRING) {
return luaL_error(L, "Invalid ziplist table, missing .name");
}
name->zipfile = lua_tostring(L, -1);
lua_setiuservalue(L, ud_index, i * 2 + 1);
if (lua_getfield(L, -1, "root") == LUA_TSTRING) {
name->root = lua_tolstring(L, -1, &name->root_size);
lua_setiuservalue(L, ud_index, i * 2 + 2);
} else if (lua_isnil(L, -1)) {
name->root = NULL;
name->root_size = 0;
lua_pop(L, 1);
} else {
return luaL_error(L, "Invalid ziplist table, .root is not a string");
}
lua_pop(L, 1);
}
return 1;
}
// #define ZIPTEST
#ifdef ZIPTEST
static int lziptest(lua_State *L);
#endif
int
luaopen_zip(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "compress", lcompress },
{ "uncompress", luncompress },
{ "open", lzip },
{ "list", lziplist },
#ifdef ZIPTEST
{ "test", lziptest },
#endif
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
// zip reader
static unzFile
try_open(struct zipreader_name *name, const char *filename) {
if (name->root) {
if (memcmp(name->root, filename, name->root_size) != 0) {
return NULL;
}
filename += name->root_size;
}
unzFile zf = unzip_open(NULL, name->zipfile);
if (zf == NULL)
return NULL;
if (unzLocateFile(zf, filename, 0) != UNZ_OK || unzOpenCurrentFile(zf) != UNZ_OK) {
unzClose(zf);
return NULL;
}
return zf;
}
zipreader_file
zipreader_open(struct zipreader_name *names, const char * filename) {
int i;
for (i=0;names[i].zipfile;i++) {
unzFile zf = try_open(&names[i], filename);
if (zf)
return (zipreader_file)zf;
}
return NULL;
}
void
zipreader_close(zipreader_file zf) {
unzClose((unzFile)zf);
}
int
zipreader_read(zipreader_file zf, void *dst, int bytes) {
return unzReadCurrentFile(zf, dst, bytes);
}
int64_t
zipreader_tell(zipreader_file zf) {
return unztell64(zf);
}
size_t
zipreader_size(zipreader_file zf) {
unz_file_info64 info;
int err = unzGetCurrentFileInfo64(zf, &info, NULL, 0, NULL, 0, NULL, 0);
if (err != UNZ_OK) {
return 0;
}
return info.uncompressed_size;
}
static void
reopen_file(zipreader_file zf) {
unz64_file_pos pos;
unzGetFilePos64(zf, &pos);
unzCloseCurrentFile(zf);
unzGoToFilePos64(zf, &pos);
unzOpenCurrentFile(zf);
}
#define TMP_SKIP_BUFFER 4096
static int
skip_bytes(zipreader_file zf, int64_t offset) {
char tmp[TMP_SKIP_BUFFER];
while (offset >= TMP_SKIP_BUFFER) {
if (unzReadCurrentFile(zf, tmp, TMP_SKIP_BUFFER) != TMP_SKIP_BUFFER)
return -1;
offset -= TMP_SKIP_BUFFER;
}
if (offset > 0 && unzReadCurrentFile(zf, tmp, offset) != offset)
return -1;
return 0;
}
int
zipreader_seek(zipreader_file zf, int64_t offset, int origin) {
if (origin == SEEK_CUR && offset >= 0) {
return skip_bytes(zf, offset);
}
size_t size = zipreader_size(zf);
size_t cur_pos = unztell64(zf);
int64_t new_pos;
switch (origin) {
case SEEK_SET:
new_pos = offset;
break;
case SEEK_CUR:
new_pos = (int64_t)cur_pos + offset;
break;
case SEEK_END:
new_pos = (int64_t)size + offset;
break;
default :
return -1;
}
if (new_pos < 0)
new_pos = 0;
else if (new_pos > size)
new_pos = size;
if (new_pos >= cur_pos) {
return skip_bytes(zf, new_pos - cur_pos);
}
reopen_file(zf);
return skip_bytes(zf, new_pos);
}
#ifdef ZIPTEST
static int
lziptest(lua_State *L) {
luaL_checktype(L, 1, LUA_TUSERDATA);
struct zipreader_name *names = (struct zipreader_name *)lua_touserdata(L, 1);
const char *filename = luaL_checkstring(L, 2);
zipreader_file zf = zipreader_open(names, filename);
if (zf == NULL)
return 0;
int sz = zipreader_size(zf);
void * buf = lua_newuserdatauv(L, sz, 0);
int rd = zipreader_read(zf, buf, sz);
if (sz != rd) {
zipreader_close(zf);
return luaL_error(L, "Read zipfile error");
}
lua_pushlstring(L, (const char *)buf, rd);
zipreader_seek(zf, 10, SEEK_SET);
rd = zipreader_read(zf, buf, sz);
lua_pushlstring(L, (const char *)buf, rd);
zipreader_close(zf);
return 2;
}
#endif
================================================
FILE: src/maskquad.glsl
================================================
@vs vs
layout(binding=0) uniform vs_params {
vec2 framesize;
float texsize;
};
struct sr_mat {
mat2 m;
};
layout(binding=0) readonly buffer sr_lut {
sr_mat sr[];
};
in vec3 position;
in vec4 color;
in uint offset;
in uint u;
in uint v;
out vec2 uv;
out vec4 maskcolor;
void main() {
ivec2 uv_base = ivec2(u >> 16, v >> 16);
ivec2 u2 = ivec2(0 , u & 0xffff);
ivec2 v2 = ivec2(0 , v & 0xffff);
ivec2 off = ivec2(offset >> 16 , offset & 0xffff) - 0x8000;
vec2 uv_offset = vec2(u2[gl_VertexIndex & 1] , v2[gl_VertexIndex >> 1]);
vec2 pos = ((uv_offset - off) * sr[int(position.z)].m + position.xy) * framesize;
gl_Position = vec4(pos.x - 1.0f, pos.y + 1.0f, 0, 1);
uv = (uv_base + uv_offset) * texsize;
maskcolor = color;
}
@end
@fs fs
layout(binding=1) uniform texture2D tex;
layout(binding=0) uniform sampler smp;
in vec2 uv;
in vec4 maskcolor;
out vec4 frag_color;
void main() {
float alpha = texture(sampler2D(tex,smp), uv).a;
frag_color = maskcolor;
frag_color.a = alpha * maskcolor.a;
}
@end
@program maskquad vs fs
================================================
FILE: src/material/matdefault.lua
================================================
local render = require "soluna.render"
local defmat = require "soluna.material.default"
local ctx = ...
local state = ctx.state
local setting = ctx.settings
local inst_buffer = render.buffer {
type = "vertex",
usage = "stream",
label = "texquad-instance",
size = defmat.instance_size * setting.draw_instance,
}
local bindings = render.bindings()
bindings:vbuffer(0, inst_buffer)
bindings:view(0, state.views.storage)
bindings:sampler(0, state.default_sampler)
state.inst = assert(inst_buffer)
state.bindings = bindings
state.material = defmat.new {
inst_buffer = state.inst,
bindings = state.bindings,
uniform = state.uniform,
sr_buffer = state.srbuffer_mem,
sprite_bank = ctx.arg.bank_ptr,
tmp_buffer = ctx.tmp_buffer,
}
local material = {}
function material.reset()
bindings:base(0)
end
function material.submit(ptr, n)
state.material:submit(ptr, n)
end
function material.draw(ptr, n, tex)
bindings:view(1, state.views[tex + 1])
state.material:draw(ptr, n, tex)
end
return material
================================================
FILE: src/material/matmask.lua
================================================
local render = require "soluna.render"
local maskmat = require "soluna.material.mask"
local ctx = ...
local state = ctx.state
maskmat.set_material_id(ctx.id)
state.mask_inst = render.buffer {
type = "vertex",
usage = "stream",
label = "mask-instance",
size = maskmat.instance_size * ctx.settings.draw_instance,
}
local mask_bindings = render.bindings()
mask_bindings:vbuffer(0, state.mask_inst)
mask_bindings:view(0, state.views.storage)
mask_bindings:sampler(0, state.default_sampler)
state.mask_bindings = mask_bindings
state.material_mask = maskmat.new {
inst_buffer = state.mask_inst,
bindings = state.mask_bindings,
uniform = state.uniform,
sr_buffer = state.srbuffer_mem,
sprite_bank = ctx.arg.bank_ptr,
tmp_buffer = ctx.tmp_buffer,
}
local material = {}
function material.reset()
mask_bindings:base(0)
end
function material.submit(ptr, n)
state.material_mask:submit(ptr, n)
end
function material.draw(ptr, n, tex)
mask_bindings:view(1, state.views[tex + 1])
state.material_mask:draw(ptr, n, tex)
end
return material
================================================
FILE: src/material/matquad.lua
================================================
local render = require "soluna.render"
local quadmat = require "soluna.material.quad"
local ctx = ...
local state = ctx.state
quadmat.set_material_id(ctx.id)
state.quad_inst = render.buffer {
type = "vertex",
usage = "stream",
label = "quad-instance",
size = quadmat.instance_size * ctx.settings.draw_instance,
}
local quad_bindings = render.bindings()
quad_bindings:vbuffer(0, state.quad_inst)
quad_bindings:view(0, state.views.storage)
state.quad_bindings = quad_bindings
state.material_quad = quadmat.new {
inst_buffer = state.quad_inst,
bindings = state.quad_bindings,
uniform = state.uniform,
sr_buffer = state.srbuffer_mem,
tmp_buffer = ctx.tmp_buffer,
}
local material = {}
function material.reset()
quad_bindings:base(0)
end
function material.submit(ptr, n)
state.material_quad:submit(ptr, n)
end
function material.draw(ptr, n)
state.material_quad:draw(ptr, n)
end
return material
================================================
FILE: src/material/mattext.lua
================================================
local render = require "soluna.render"
local textmat = require "soluna.material.text"
local ctx = ...
local state = ctx.state
local setting = ctx.settings
textmat.set_material_id(ctx.id)
local text_bindings
local text_sampler_desc = setting.text_sampler
if text_sampler_desc then
text_sampler_desc.label = text_sampler_desc.label or "text-sampler"
state.text_sampler = render.sampler(text_sampler_desc)
state.text_inst = render.buffer {
type = "vertex",
usage = "stream",
label = "text-instance",
size = textmat.instance_size * setting.draw_instance,
}
text_bindings = render.bindings()
text_bindings:vbuffer(0, state.text_inst)
text_bindings:view(0, state.views.storage)
text_bindings:sampler(0, state.text_sampler)
else
state.text_inst = state.inst
text_bindings = state.bindings
end
state.text_bindings = text_bindings
state.material_text = textmat.normal {
inst_buffer = state.text_inst,
bindings = state.text_bindings,
uniform = state.uniform,
sr_buffer = state.srbuffer_mem,
font_manager = ctx.font.cobj,
tmp_buffer = ctx.tmp_buffer,
}
local material = {}
function material.reset()
text_bindings:base(0)
end
function material.submit(ptr, n)
state.material_text:submit(ptr, n)
end
function material.draw(ptr, n)
text_bindings:view(1, state.views.font)
state.material_text:draw(ptr, n)
end
return material
================================================
FILE: src/material_blit.c
================================================
#include
#include
#include "sokol/sokol_gfx.h"
#include "blit.glsl.h"
#include "render_bindings.h"
#include "material_util.h"
struct material_blit {
sg_pipeline pip;
struct render_bindings *bind;
};
static int
lmaterial_blit_draw(lua_State *L) {
struct material_blit *m = (struct material_blit *)luaL_checkudata(L, 1, "SOLUNA_MATERIAL_BLIT");
sg_apply_pipeline(m->pip);
sg_apply_bindings(&m->bind->bindings);
sg_draw(0, 4, 1);
return 0;
}
static void
init_pipeline(struct material_blit *p) {
sg_pipeline_desc desc = {
.layout.buffers[0].step_func = SG_VERTEXSTEP_PER_VERTEX,
};
p->pip = util_make_pipeline(&desc, blit_shader_desc, "blit-pipeline", 0);
}
static int
lnew_material_blit(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
struct material_blit *m = (struct material_blit *)lua_newuserdatauv(L, sizeof(*m), 1);
init_pipeline(m);
util_ref_object(L, &m->bind, 1, "bindings", "SOKOL_BINDINGS", 1);
if (luaL_newmetatable(L, "SOLUNA_MATERIAL_BLIT")) {
luaL_Reg l[] = {
{ "__index", NULL },
{ "draw", lmaterial_blit_draw },
{ NULL, NULL },
};
luaL_setfuncs(L, l, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
}
lua_setmetatable(L, -2);
return 1;
}
int
luaopen_material_blit(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "new", lnew_material_blit },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: src/material_default.c
================================================
#include
#include
#include
#include
#include "sokol/sokol_gfx.h"
#include "texquad.glsl.h"
#include "srbuffer.h"
#include "batch.h"
#include "spritemgr.h"
#include "material_util.h"
#include "render_bindings.h"
#include "tmpbuffer.h"
struct inst_object {
float x, y;
float sr_index;
uint32_t offset;
uint32_t u;
uint32_t v;
};
struct material_default {
sg_pipeline pip;
sg_buffer inst;
struct render_bindings *bind;
vs_params_t *uniform;
struct sr_buffer *srbuffer;
struct sprite_bank *bank;
struct tmp_buffer tmp;
};
static void
submit(lua_State *L, void *m_, struct draw_primitive *prim, int n) {
struct material_default *m = (struct material_default *)m_;
struct sprite_rect *rect = m->bank->rect;
struct inst_object *tmp = TMPBUFFER_PTR(struct inst_object, &m->tmp);
int i;
for (i=0;isrbuffer, p->sr);
if (sr_index < 0) {
// todo: support multiply srbuffer
luaL_error(L, "sr buffer is full");
}
tmp[i].x = (float)p->x / 256.0f;
tmp[i].y = (float)p->y / 256.0f;
tmp[i].sr_index = (float)sr_index;
int index = p->sprite - 1;
assert(index >= 0);
struct sprite_rect *r = &rect[index];
tmp[i].offset = r->off;
tmp[i].u = r->u;
tmp[i].v = r->v;
}
sg_append_buffer(m->inst, &(sg_range) { tmp , n * sizeof(tmp[0]) });
}
static int
lmaterial_default_submit(lua_State *L) {
struct material_default *m = (struct material_default *)luaL_checkudata(L, 1, "SOLUNA_MATERIAL_DEFAULT");
int batch_n = TMPBUFFER_SIZE(struct inst_object, &m->tmp);
util_submit_material(L, batch_n, m, submit);
return 0;
}
static inline int
lmaterial_default_draw_(lua_State *L, int ex) {
struct material_default *m = (struct material_default *)luaL_checkudata(L, 1, "SOLUNA_MATERIAL_DEFAULT");
// struct draw_primitive *prim = lua_touserdata(L, 2);
int prim_n = luaL_checkinteger(L, 3);
// int tex_id = luaL_checkinteger(L, 4);
sg_apply_pipeline(m->pip);
sg_apply_uniforms(UB_vs_params, &(sg_range){ m->uniform, sizeof(vs_params_t) });
if (ex) {
sg_apply_bindings(&m->bind->bindings);
sg_draw_ex(0, 4, prim_n, 0, m->bind->base);
} else {
size_t base = m->bind->base * sizeof(struct inst_object);
m->bind->bindings.vertex_buffer_offsets[0] += base;
sg_apply_bindings(&m->bind->bindings);
sg_draw(0, 4, prim_n);
m->bind->bindings.vertex_buffer_offsets[0] -= base;
}
m->bind->base += prim_n;
return 0;
}
static int
lmaterial_default_draw(lua_State *L) {
return lmaterial_default_draw_(L, 0);
}
static int
lmaterial_default_draw_ex(lua_State *L) {
return lmaterial_default_draw_(L, 1);
}
static void
init_pipeline(struct material_default *p) {
sg_pipeline_desc desc = {
.layout.attrs = {
[ATTR_texquad_position].format = SG_VERTEXFORMAT_FLOAT3,
[ATTR_texquad_offset].format = SG_VERTEXFORMAT_UINT,
[ATTR_texquad_u].format = SG_VERTEXFORMAT_UINT,
[ATTR_texquad_v].format = SG_VERTEXFORMAT_UINT,
},
};
p->pip = util_make_pipeline(&desc, texquad_shader_desc, "default-pipeline", 1);
}
static int
lnew_material_default(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
struct material_default *m = (struct material_default *)lua_newuserdatauv(L, sizeof(*m), 5);
init_pipeline(m);
util_ref_object(L, &m->inst, 1, "inst_buffer", "SOKOL_BUFFER", 0);
util_ref_object(L, &m->bind, 2, "bindings", "SOKOL_BINDINGS", 1);
util_ref_object(L, &m->uniform, 3, "uniform", "SOKOL_UNIFORM", 1);
util_ref_object(L, &m->srbuffer, 4, "sr_buffer", "SOLUNA_SRBUFFER", 1);
tmp_buffer_init(L, &m->tmp, 5, "tmp_buffer");
if (lua_getfield(L, 1, "sprite_bank") != LUA_TLIGHTUSERDATA) {
return luaL_error(L, "Missing .sprite_bank");
}
m->bank = lua_touserdata(L, -1);
lua_pop(L, 1);
if (luaL_newmetatable(L, "SOLUNA_MATERIAL_DEFAULT")) {
luaL_Reg l[] = {
{ "__index", NULL },
{ "submit", lmaterial_default_submit },
{ "draw", DRAWFUNC(lmaterial_default_draw) },
{ NULL, NULL },
};
luaL_setfuncs(L, l, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
}
lua_setmetatable(L, -2);
return 1;
}
int
luaopen_material_default(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "new", lnew_material_default },
{ "instance_size", NULL },
{ NULL, NULL },
};
luaL_newlib(L, l);
lua_pushinteger(L, sizeof(struct inst_object));
lua_setfield(L, -2, "instance_size");
return 1;
}
================================================
FILE: src/material_mask.c
================================================
#include
#include
#include
#include
#include "sokol/sokol_gfx.h"
#include "maskquad.glsl.h"
#include "srbuffer.h"
#include "batch.h"
#include "spritemgr.h"
#include "material_util.h"
#include "render_bindings.h"
#include "tmpbuffer.h"
struct color {
unsigned char channel[4];
};
struct inst_object {
float x, y;
float sr_index;
struct color maskcolor;
uint32_t offset;
uint32_t u;
uint32_t v;
};
struct mask {
struct draw_primitive_external header;
struct color c;
};
struct material_mask {
sg_pipeline pip;
sg_buffer inst;
struct render_bindings *bind;
vs_params_t *uniform;
struct sr_buffer *srbuffer;
struct sprite_bank *bank;
struct tmp_buffer tmp;
};
static int material_id = 0;
static void
submit(lua_State *L, void *m_, struct draw_primitive *prim, int n) {
struct material_mask *m =(struct material_mask *)m_;
struct sprite_rect *rect = m->bank->rect;
struct inst_object *tmp = TMPBUFFER_PTR(struct inst_object, &m->tmp);
int i;
for (i=0;isprite == -material_id);
struct mask * mask = (struct mask *)&prim[i*2+1];
// calc scale/rot index
int sr_index = srbuffer_add(m->srbuffer, p->sr);
if (sr_index < 0) {
// todo: support multiply srbuffer
luaL_error(L, "sr buffer is full");
}
tmp[i].x = (float)p->x / 256.0f;
tmp[i].y = (float)p->y / 256.0f;
tmp[i].sr_index = (float)sr_index;
tmp[i].maskcolor = mask->c;
int index = mask->header.sprite;
assert(index >= 0);
struct sprite_rect *r = &rect[index];
tmp[i].offset = r->off;
tmp[i].u = r->u;
tmp[i].v = r->v;
}
sg_append_buffer(m->inst, &(sg_range) { tmp , n * sizeof(tmp[0]) });
}
static int
lmaterial_mask_submit(lua_State *L) {
struct material_mask *m = (struct material_mask *)luaL_checkudata(L, 1, "SOLUNA_MATERIAL_MASK");
int batch_n = TMPBUFFER_SIZE(struct inst_object, &m->tmp);
util_submit_material(L, batch_n, m, submit);
return 0;
}
static inline int
lmaterial_mask_draw_(lua_State *L, int ex) {
struct material_mask *m = (struct material_mask *)luaL_checkudata(L, 1, "SOLUNA_MATERIAL_MASK");
// struct draw_primitive *prim = lua_touserdata(L, 2);
int prim_n = luaL_checkinteger(L, 3);
// int tex_id = luaL_checkinteger(L, 4);
sg_apply_pipeline(m->pip);
sg_apply_uniforms(UB_vs_params, &(sg_range){ m->uniform, sizeof(vs_params_t) });
if (ex) {
sg_apply_bindings(&m->bind->bindings);
sg_draw_ex(0, 4, prim_n, 0, m->bind->base);
} else {
size_t base = m->bind->base * sizeof(struct inst_object);
m->bind->bindings.vertex_buffer_offsets[0] += base;
sg_apply_bindings(&m->bind->bindings);
sg_draw(0, 4, prim_n);
m->bind->bindings.vertex_buffer_offsets[0] -= base;
}
m->bind->base += prim_n;
return 0;
}
static int
lmaterial_mask_draw(lua_State *L) {
return lmaterial_mask_draw_(L, 0);
}
static int
lmaterial_mask_draw_ex(lua_State *L) {
return lmaterial_mask_draw_(L, 1);
}
static int
lset_material_id(lua_State *L) {
int id = luaL_checkinteger(L, 1);
if (id <= 0) {
return luaL_error(L, "Invalid mask material id %d", id);
}
material_id = id;
return 0;
}
static void
init_pipeline(struct material_mask *p) {
sg_pipeline_desc desc = {
.layout.attrs = {
[ATTR_maskquad_position].format = SG_VERTEXFORMAT_FLOAT3,
[ATTR_maskquad_color].format = SG_VERTEXFORMAT_UBYTE4N,
[ATTR_maskquad_offset].format = SG_VERTEXFORMAT_UINT,
[ATTR_maskquad_u].format = SG_VERTEXFORMAT_UINT,
[ATTR_maskquad_v].format = SG_VERTEXFORMAT_UINT,
},
};
p->pip = util_make_pipeline(&desc, maskquad_shader_desc, "mask-pipeline", 1);
}
static int
lnew_material_mask(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
struct material_mask *m = (struct material_mask *)lua_newuserdatauv(L, sizeof(*m), 5);
init_pipeline(m);
util_ref_object(L, &m->inst, 1, "inst_buffer", "SOKOL_BUFFER", 0);
util_ref_object(L, &m->bind, 2, "bindings", "SOKOL_BINDINGS", 1);
util_ref_object(L, &m->uniform, 3, "uniform", "SOKOL_UNIFORM", 1);
util_ref_object(L, &m->srbuffer, 4, "sr_buffer", "SOLUNA_SRBUFFER", 1);
tmp_buffer_init(L, &m->tmp, 5, "tmp_buffer");
if (lua_getfield(L, 1, "sprite_bank") != LUA_TLIGHTUSERDATA) {
return luaL_error(L, "Missing .sprite_bank");
}
m->bank = lua_touserdata(L, -1);
lua_pop(L, 1);
if (luaL_newmetatable(L, "SOLUNA_MATERIAL_MASK")) {
luaL_Reg l[] = {
{ "__index", NULL },
{ "submit", lmaterial_mask_submit },
{ "draw", DRAWFUNC(lmaterial_mask_draw) },
{ NULL, NULL },
};
luaL_setfuncs(L, l, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
}
lua_setmetatable(L, -2);
return 1;
}
struct mask_primitive {
struct draw_primitive pos;
union {
struct draw_primitive dummy;
struct mask m;
} u;
};
static int
lmask(lua_State *L) {
if (material_id <= 0) {
return luaL_error(L, "Mask material is not registered");
}
struct mask_primitive prim;
prim.pos.x = 0;
prim.pos.y = 0;
prim.pos.sr = 0;
prim.pos.sprite = -material_id;
prim.u.m.header.sprite = luaL_checkinteger(L, 1) - 1;
uint32_t color = luaL_checkinteger(L, 2);
if (!(color & 0xff000000))
color |= 0xff000000;
prim.u.m.c.channel[0] = (color >> 16) & 0xff;
prim.u.m.c.channel[1] = (color >> 8) & 0xff;
prim.u.m.c.channel[2] = color & 0xff;
prim.u.m.c.channel[3] = (color >> 24) & 0xff;
lua_pushlstring(L, (const char *)&prim, sizeof(prim));
return 1;
}
int
luaopen_material_mask(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "set_material_id", lset_material_id },
{ "mask", lmask },
{ "new", lnew_material_mask },
{ "instance_size", NULL },
{ NULL, NULL },
};
luaL_newlib(L, l);
lua_pushinteger(L, sizeof(struct inst_object));
lua_setfield(L, -2, "instance_size");
return 1;
}
================================================
FILE: src/material_quad.c
================================================
#include
#include
#include
#include
#include "sokol/sokol_gfx.h"
#include "colorquad.glsl.h"
#include "srbuffer.h"
#include "batch.h"
#include "spritemgr.h"
#include "material_util.h"
#include "render_bindings.h"
#include "tmpbuffer.h"
struct color {
unsigned char channel[4];
};
struct quad {
struct draw_primitive_external header;
float w;
float h;
struct color c;
};
struct inst_object {
float x, y;
float w, h;
int sr_index;
struct color c;
};
struct material_quad {
sg_pipeline pip;
sg_buffer inst;
struct render_bindings *bind;
vs_params_t *uniform;
struct sr_buffer *srbuffer;
struct tmp_buffer tmp;
};
static int material_id = 0;
static void
submit(lua_State *L, void *m_, struct draw_primitive *prim, int n) {
struct material_quad *m = (struct material_quad *)m_;
struct inst_object *tmp = TMPBUFFER_PTR(struct inst_object, &m->tmp);
int i;
for (i=0;isprite == -material_id);
struct quad * q = (struct quad *)&prim[i*2+1];
// calc scale/rot index
int sr_index = srbuffer_add(m->srbuffer, p->sr);
if (sr_index < 0) {
// todo: support multiply srbuffer
luaL_error(L, "sr buffer is full");
}
struct inst_object *inst = &tmp[i];
inst->x = (float)p->x / 256.0f;
inst->y = (float)p->y / 256.0f;
inst->w = q->w;
inst->h = q->h;
inst->sr_index = sr_index;
inst->c = q->c;
}
sg_append_buffer(m->inst, &(sg_range) { tmp , n * sizeof(tmp[0]) });
}
static int
lmateraial_quad_submit(lua_State *L) {
struct material_quad *m = (struct material_quad *)luaL_checkudata(L, 1, "SOLUNA_MATERIAL_QUAD");
int batch_n = TMPBUFFER_SIZE(struct inst_object, &m->tmp);
util_submit_material(L, batch_n, m, submit);
return 0;
}
static inline int
lmateraial_quad_draw_(lua_State *L, int ex) {
struct material_quad *m = (struct material_quad *)luaL_checkudata(L, 1, "SOLUNA_MATERIAL_QUAD");
// struct draw_primitive *prim = lua_touserdata(L, 2);
int prim_n = luaL_checkinteger(L, 3);
if (prim_n <= 0)
return 0;
sg_apply_pipeline(m->pip);
sg_apply_uniforms(UB_vs_params, &(sg_range){ m->uniform, sizeof(vs_params_t) });
if (ex) {
sg_apply_bindings(&m->bind->bindings);
sg_draw_ex(0, 4, prim_n, 0, m->bind->base);
} else {
size_t base = m->bind->base * sizeof(struct inst_object);
m->bind->bindings.vertex_buffer_offsets[0] += base;
sg_apply_bindings(&m->bind->bindings);
sg_draw(0, 4, prim_n);
m->bind->bindings.vertex_buffer_offsets[0] -= base;
}
m->bind->base += prim_n;
return 0;
}
static int
lmateraial_quad_draw(lua_State *L) {
return lmateraial_quad_draw_(L, 0);
}
static int
lmateraial_quad_draw_ex(lua_State *L) {
return lmateraial_quad_draw_(L, 1);
}
static int
lset_material_id(lua_State *L) {
int id = luaL_checkinteger(L, 1);
if (id <= 0) {
return luaL_error(L, "Invalid quad material id %d", id);
}
material_id = id;
return 0;
}
static void
init_pipeline(struct material_quad *p) {
sg_pipeline_desc desc = {
.layout.attrs = {
[ATTR_colorquad_position].format = SG_VERTEXFORMAT_FLOAT4,
[ATTR_colorquad_idx].format = SG_VERTEXFORMAT_UINT,
[ATTR_colorquad_c].format = SG_VERTEXFORMAT_UBYTE4N,
},
};
p->pip = util_make_pipeline(&desc, colorquad_shader_desc, "colorquad-pipeline", 1);
}
static int
lnew_material_quad(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
struct material_quad *m = (struct material_quad *)lua_newuserdatauv(L, sizeof(*m), 5);
util_ref_object(L, &m->inst, 1, "inst_buffer", "SOKOL_BUFFER", 0);
util_ref_object(L, &m->bind, 2, "bindings", "SOKOL_BINDINGS", 1);
util_ref_object(L, &m->uniform, 3, "uniform", "SOKOL_UNIFORM", 1);
util_ref_object(L, &m->srbuffer, 4, "sr_buffer", "SOLUNA_SRBUFFER", 1);
tmp_buffer_init(L, &m->tmp, 5, "tmp_buffer");
init_pipeline(m);
if (luaL_newmetatable(L, "SOLUNA_MATERIAL_QUAD")) {
luaL_Reg l[] = {
{ "__index", NULL },
{ "submit", lmateraial_quad_submit },
{ "draw", DRAWFUNC(lmateraial_quad_draw) },
{ NULL, NULL },
};
luaL_setfuncs(L, l, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
}
lua_setmetatable(L, -2);
return 1;
}
struct quad_primitive {
struct draw_primitive pos;
union {
struct draw_primitive dummy;
struct quad q;
} u;
};
static int
lquad(lua_State *L) {
if (material_id <= 0) {
return luaL_error(L, "Quad material is not registered");
}
struct quad_primitive prim;
prim.pos.x = 0;
prim.pos.y = 0;
prim.pos.sr = 0;
prim.pos.sprite = -material_id;
prim.u.q.w = luaL_checkinteger(L, 1);
prim.u.q.h = luaL_checkinteger(L, 2);
uint32_t color = luaL_checkinteger(L, 3);
if (!(color & 0xff000000))
color |= 0xff000000;
prim.u.q.header.sprite = -1;
prim.u.q.c.channel[0] = (color >> 16) & 0xff;
prim.u.q.c.channel[1] = (color >> 8) & 0xff;
prim.u.q.c.channel[2] = color & 0xff;
prim.u.q.c.channel[3] = (color >> 24) & 0xff;
lua_pushlstring(L, (const char *)&prim, sizeof(prim));
return 1;
}
int
luaopen_material_quad(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "set_material_id", lset_material_id },
{ "quad", lquad },
{ "new", lnew_material_quad },
{ "instance_size", NULL },
{ NULL, NULL },
};
luaL_newlib(L, l);
lua_pushinteger(L, sizeof(struct inst_object));
lua_setfield(L, -2, "instance_size");
return 1;
}
================================================
FILE: src/material_text.c
================================================
#include
#include
#include
#include
#include
#include
#include
#include "sokol/sokol_gfx.h"
#include "sdftext.glsl.h"
#include "srbuffer.h"
#include "batch.h"
#include "spritemgr.h"
#include "font_manager.h"
#include "sprite_submit.h"
#include "material_util.h"
#include "render_bindings.h"
#include "tmpbuffer.h"
struct text {
struct draw_primitive_external header;
int codepoint;
uint16_t font;
uint16_t size;
uint32_t color;
};
struct inst_object {
float x, y;
float sr_index;
uint32_t offset;
uint32_t u;
uint32_t v;
};
struct material_text {
sg_pipeline pip;
sg_buffer inst;
struct render_bindings *bind;
vs_params_t *uniform;
struct sr_buffer *srbuffer;
struct font_manager *font;
fs_params_t fs_uniform;
struct tmp_buffer tmp;
};
static int material_id = 0;
static void
submit(lua_State *L, void *m_, struct draw_primitive *prim, int n) {
struct material_text *m = (struct material_text *)m_;
struct inst_object *tmp = TMPBUFFER_PTR(struct inst_object, &m->tmp);
int i;
int count = 0;
for (i=0;isprite == -material_id);
struct text * t = (struct text *)&prim[i*2+1];
struct font_glyph g, og;
const char* err = font_manager_glyph(m->font, t->font, t->codepoint, t->size, &g, &og);
if (err == NULL) {
tmp[count].offset = (-og.offset_x + 0x8000) << 16 | (-og.offset_y + 0x8000);
tmp[count].u = og.u << 16 | FONT_MANAGER_GLYPHSIZE;
tmp[count].v = og.v << 16 | FONT_MANAGER_GLYPHSIZE;
uint32_t scale_fix = og.w == 0 ? 0 : (g.w << 12) / og.w;
sprite_apply_scale(p, scale_fix);
// calc scale/rot index
int sr_index = srbuffer_add(m->srbuffer, p->sr);
if (sr_index < 0) {
// todo: support multiply srbuffer
luaL_error(L, "sr buffer is full");
}
tmp[count].x = (float)p->x / 256.0f;
tmp[count].y = (float)p->y / 256.0f;
tmp[count].sr_index = (float)sr_index;
++count;
} else {
t->codepoint = -1;
}
}
sg_append_buffer(m->inst, &(sg_range) { tmp , count * sizeof(tmp[0]) });
}
static int
lmateraial_text_submit(lua_State *L) {
struct material_text *m = (struct material_text *)luaL_checkudata(L, 1, "SOLUNA_MATERIAL_TEXT");
int batch_n = TMPBUFFER_SIZE(struct inst_object, &m->tmp);
util_submit_material(L, batch_n, m, submit);
return 0;
}
static inline void
draw_text(struct material_text *m, uint32_t color, int count, int ex) {
m->fs_uniform.color = color;
sg_apply_uniforms(UB_vs_params, &(sg_range){ m->uniform, sizeof(vs_params_t) });
sg_apply_uniforms(UB_fs_params, &(sg_range){ &m->fs_uniform, sizeof(fs_params_t) });
if (ex) {
sg_apply_bindings(&m->bind->bindings);
sg_draw_ex(0, 4, count, 0, m->bind->base);
} else {
size_t base = m->bind->base * sizeof(struct inst_object);
m->bind->bindings.vertex_buffer_offsets[0] += base;
sg_apply_bindings(&m->bind->bindings);
sg_draw(0, 4, count);
m->bind->bindings.vertex_buffer_offsets[0] -= base;
}
m->bind->base += count;
}
static inline int
lmateraial_text_draw_(lua_State *L, int ex) {
struct material_text *m = (struct material_text *)luaL_checkudata(L, 1, "SOLUNA_MATERIAL_TEXT");
struct draw_primitive *prim = lua_touserdata(L, 2);
int prim_n = luaL_checkinteger(L, 3);
if (prim_n <= 0)
return 0;
int i;
float texsize = m->uniform->texsize;
m->uniform->texsize = 1.0f / FONT_MANAGER_TEXSIZE;
sg_apply_pipeline(m->pip);
int count = -1;
uint32_t color = 0;
for (i=0;icodepoint >= 0) {
if (count < 0) {
color = t->color;
count = 1;
} else if (t->color != color) {
draw_text(m, color, count, ex);
color = t->color;
count = 1;
} else {
++count;
}
}
}
draw_text(m, color, count, ex);
m->uniform->texsize = texsize;
return 0;
}
static int
lmateraial_text_draw(lua_State *L) {
return lmateraial_text_draw_(L, 0);
}
static int
lmateraial_text_draw_ex(lua_State *L) {
return lmateraial_text_draw_(L, 1);
}
static void
init_pipeline(struct material_text *p) {
sg_pipeline_desc desc = {
.layout.attrs = {
[ATTR_texquad_position].format = SG_VERTEXFORMAT_FLOAT3,
[ATTR_texquad_offset].format = SG_VERTEXFORMAT_UINT,
[ATTR_texquad_u].format = SG_VERTEXFORMAT_UINT,
[ATTR_texquad_v].format = SG_VERTEXFORMAT_UINT,
},
};
p->pip = util_make_pipeline(&desc, texquad_shader_desc, "text-pipeline", 1);
}
static int
lnew_material_text_normal(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
struct material_text *m = (struct material_text *)lua_newuserdatauv(L, sizeof(*m), 5);
util_ref_object(L, &m->inst, 1, "inst_buffer", "SOKOL_BUFFER", 0);
util_ref_object(L, &m->bind, 2, "bindings", "SOKOL_BINDINGS", 1);
util_ref_object(L, &m->uniform, 3, "uniform", "SOKOL_UNIFORM", 1);
util_ref_object(L, &m->srbuffer, 4, "sr_buffer", "SOLUNA_SRBUFFER", 1);
tmp_buffer_init(L, &m->tmp, 5, "tmp_buffer");
init_pipeline(m);
if (lua_getfield(L, 1, "font_manager") != LUA_TLIGHTUSERDATA) {
return luaL_error(L, "Missing .font_manager");
}
m->font = lua_touserdata(L, -1);
lua_pop(L, 1);
fs_params_t temp = {
.edge_mask = font_manager_sdf_mask(m->font),
.dist_multiplier = 1.0f,
.color = 0xff000000,
};
memcpy(&m->fs_uniform, &temp, sizeof(fs_params_t));
if (luaL_newmetatable(L, "SOLUNA_MATERIAL_TEXT")) {
luaL_Reg l[] = {
{ "__index", NULL },
{ "submit", lmateraial_text_submit },
{ "draw", DRAWFUNC(lmateraial_text_draw) },
{ NULL, NULL },
};
luaL_setfuncs(L, l, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
}
lua_setmetatable(L, -2);
return 1;
}
static int
lchar_for_batch(lua_State *L) {
if (material_id <= 0) {
return luaL_error(L, "Text material is not registered");
}
struct text * t = (struct text *)lua_touserdata(L, lua_upvalueindex(1));
t->header.sprite = -1;
t->codepoint = luaL_checkinteger(L, 1);
t->font = luaL_checkinteger(L, 2);
t->size = luaL_checkinteger(L, 3);
t->color = luaL_checkinteger(L, 4);
if (!(t->color & 0xff000000))
t->color |= 0xff000000;
lua_pushvalue(L, lua_upvalueindex(1));
return 1;
}
static int
lset_material_id(lua_State *L) {
int id = luaL_checkinteger(L, 1);
if (id <= 0) {
return luaL_error(L, "Invalid text material id %d", id);
}
material_id = id;
lua_pushvalue(L, lua_upvalueindex(1));
lua_pushinteger(L, id);
lua_setiuservalue(L, -2, 1);
lua_pop(L, 1);
return 0;
}
struct text_primitive {
struct draw_primitive pos;
union {
struct draw_primitive dummy;
struct text text;
} u;
};
/*
** From lua utf8lib :
** Decode one UTF-8 sequence, returning NULL if byte sequence is
** invalid. The array 'limits' stores the minimum value for each
** sequence length, to check for overlong representations. Its first
** entry forces an error for non-ascii bytes with no continuation
** bytes (count == 0).
*/
#define iscont(c) (((c) & 0xC0) == 0x80)
#define l_uint32 uint32_t
#define MAXUTF 0x7FFFFFFFu
static const char *utf8_decode (const char *s, l_uint32 *val) {
static const l_uint32 limits[] =
{~(l_uint32)0, 0x80, 0x800, 0x10000u, 0x200000u, 0x4000000u};
unsigned int c = (unsigned char)s[0];
l_uint32 res = 0; /* final result */
if (c < 0x80) /* ascii? */
res = c;
else {
int count = 0; /* to count number of continuation bytes */
for (; c & 0x40; c <<= 1) { /* while it needs continuation bytes... */
unsigned int cc = (unsigned char)s[++count]; /* read next byte */
if (!iscont(cc)) /* not a continuation byte? */
return NULL; /* invalid byte sequence */
res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */
}
res |= ((l_uint32)(c & 0x7F) << (count * 5)); /* add first byte */
if (count > 5 || res > MAXUTF || res < limits[count])
return NULL; /* invalid byte sequence */
s += count; /* skip continuation bytes read */
}
*val = res;
return s + 1; /* +1 to include first byte */
}
static const char *
skip_bracket(const char *str) {
for (;;) {
if (*str == ']') {
return str + 1;
} else if (*str == '\0') {
return str;
}
++str;
}
}
static int
count_string(const char *str) {
uint32_t val = 0;
int n = 0;
while ((str = utf8_decode(str, &val))) {
if (val == 0)
break;
if (val > 32) {
if (val == '[') {
char c = *str;
if (c == '[') {
++str;
++n;
} else {
if (c == 'i') {
// icons
++n;
}
str = skip_bracket(str);
}
} else {
++n;
}
}
}
return n;
}
#define MAX_WIDTH 4096
#define MAX_HEIGHT 4096
#define DEFAULT_FONTSIZE 24
static void *
free_primitive(void *ud, void *ptr, size_t osize, size_t nsize) {
free(ptr);
return NULL;
}
#define ALIGNMENT_LEFT 0
#define ALIGNMENT_CENTER 1
#define ALIGNMENT_RIGHT 2
#define ALIGNMENT_MASK 3
#define VALIGNMENT_TOP (1<<2)
#define VALIGNMENT_CENTER 0
#define VALIGNMENT_BOTTOM (2<<2)
#define VALIGNMENT_MASK (3<<2)
// todo: support multi font/size
struct block_context {
int width;
int height;
int x;
int y;
int ascent;
int decent;
int line_prim;
int line_width;
int alignment;
uint32_t default_color;
uint32_t color;
};
static inline int
advance(struct block_context *ctx, int x) {
if (x + ctx->x > ctx->width)
return 1;
ctx->x += x;
return 0;
}
struct position {
// input
int n;
// output
int x;
int y;
int w;
int h;
int decent;
};
static inline int
newline(struct block_context *ctx, struct text_primitive * prim, int n, struct position *pos) {
int from = ctx->line_prim;
ctx->line_prim = n;
int line_width = ctx->line_width;
ctx->line_width = 0;
int offx = 0;
int align = ctx->alignment & ALIGNMENT_MASK;
switch (align) {
case ALIGNMENT_CENTER:
offx = (ctx->width - line_width) / 2 * 256;
break;
case ALIGNMENT_RIGHT:
offx = (ctx->width - line_width) * 256;
break;
}
if (pos && pos->n <= 0 && pos->y == ctx->y) {
pos->x += offx / 256;
}
if (prim != NULL && offx > 0) {
int i;
for (i=from;iy + ctx->ascent + ctx->decent > ctx->height) {
return 1;
} else {
ctx->y += ctx->ascent + ctx->decent;
ctx->x = 0;
return 0;
}
}
static inline int
tohex(char c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static const char *
parse_bracket(struct block_context *ctx, const char *str, int *icon) {
char c = *str;
int hex = -1;
if (c == 'i') {
++str;
int num = 0;
while (*str >= '0' && *str <= '9') {
num = num * 10 + (*str - '0');
++str;
}
*icon = num + 1;
} else if ((hex = tohex(c)) >= 0) {
int color = hex;
for (;;) {
++str;
if ((hex = tohex(*str)) >= 0) {
color = color * 16 + hex;
} else {
break;
}
}
if (!(color & 0xff000000))
color |= 0xff000000;
ctx->color = color;
} else if (c == 'n') {
ctx->color = ctx->default_color;
}
// todo: other command
return skip_bracket(str);
}
static inline void
advance_position(struct position *pos, struct block_context *ctx) {
if (pos) {
if (pos->n == 0) {
pos->x = ctx->x;
pos->y = ctx->y;
}
--pos->n;
}
}
// todo: support color
static int
ltext_(lua_State *L, struct position *pos) {
const char * str = luaL_checkstring(L, 1);
int count = count_string(str);
struct block_context ctx;
ctx.width = luaL_optinteger(L, 2, MAX_WIDTH);
ctx.height = luaL_optinteger(L, 3, MAX_HEIGHT);
struct font_manager *mgr = (struct font_manager *)lua_touserdata(L, lua_upvalueindex(1));
int fontid = lua_tointeger(L, lua_upvalueindex(2));
int fontsize = lua_tointeger(L, lua_upvalueindex(3));
ctx.default_color = lua_tointeger(L, lua_upvalueindex(4));
ctx.color = ctx.default_color;
ctx.x = 0;
int decent, gap;
font_manager_fontheight(mgr, fontid, fontsize, &ctx.ascent, &decent, &gap);
if (gap == 0)
gap = 1;
ctx.decent = -decent + gap;
ctx.y = ctx.ascent;
ctx.line_prim = 0;
ctx.line_width = 0;
ctx.alignment = lua_tointeger(L, lua_upvalueindex(5));
int pos_n = 0;
if (pos) {
if (pos->n < 0) {
pos->n = 0;
}
pos_n = pos->n;
}
char * buffer = NULL;
struct text_primitive * prim = NULL;
if (pos == NULL) {
buffer = (char *)malloc(count * sizeof(struct text_primitive)+1);
prim = (struct text_primitive *)buffer;
}
int i;
int n = 0;
for (i=0;i ctx.line_width)
ctx.line_width = ctx.x;
if (newline(&ctx, prim, n, pos))
break;
} else {
struct font_glyph g, og;
if (font_manager_glyph(mgr, fontid, ' ', fontsize, &g, &og) == NULL) {
if (ctx.x > ctx.line_width)
ctx.line_width = ctx.x;
if (advance(&ctx, g.advance_x)) {
if (newline(&ctx, prim, n, pos))
break;
advance(&ctx, g.advance_x);
}
}
}
} else {
int icon = 0;
if (val == '[') {
if (*str != '[') {
str = parse_bracket(&ctx, str, &icon);
if (!icon) {
continue;
}
} else {
++str;
}
}
int dy = 0;
int codepoint = val;
int font = fontid;
if (icon > 0) {
codepoint = icon -1;
font = FONT_ICON;
dy = - ( ctx.ascent ) * 256;
}
if (prim) {
prim[n].pos.x = ctx.x * 256;
prim[n].pos.y = ctx.y * 256 + dy;
prim[n].pos.sr = 0;
prim[n].pos.sprite = -material_id;
}
struct font_glyph g, og;
if (font_manager_glyph(mgr, font, codepoint, fontsize, &g, &og) == NULL) {
advance_position(pos, &ctx);
if (ctx.x > ctx.line_width)
ctx.line_width = ctx.x;
if (advance(&ctx, g.advance_x)) {
if (newline(&ctx, prim, n, pos))
break;
if (prim) {
prim[n].pos.x = ctx.x * 256;
prim[n].pos.y = ctx.y * 256 + dy;
}
advance(&ctx, g.advance_x);
}
if (prim) {
prim[n].u.text.header.sprite = -1;
prim[n].u.text.codepoint = codepoint;
prim[n].u.text.font = font;
prim[n].u.text.size = fontsize;
prim[n].u.text.color = ctx.color;
}
++n;
}
++i;
}
}
if (ctx.x > ctx.line_width)
ctx.line_width = ctx.x;
int height = ctx.y + ctx.decent - gap;
if (pos && pos->n >= 0) {
pos_n -= pos->n;
pos->n = 0;
advance_position(pos, &ctx);
}
newline(&ctx, prim, n, pos);
int offy;
int valign = ctx.alignment & VALIGNMENT_MASK;
switch (valign) {
case VALIGNMENT_CENTER:
offy = (ctx.height - height) / 2 * 256;
break;
case VALIGNMENT_BOTTOM:
offy = (ctx.height - height) * 256;
break;
default:
offy = 0;
break;
}
if (prim != NULL) {
if (offy != 0) {
for (i=0;iy += offy / 256 - ctx.ascent;
pos->w = 2;
pos->h = ctx.ascent + ctx.decent - gap;
pos->n = pos_n;
pos->decent = ctx.decent;
return 0;
}
}
static int
ltext(lua_State *L) {
return ltext_(L, NULL);
}
static int
ltext_position(lua_State *L) {
struct position pos;
pos.n = luaL_checkinteger(L, 2);
pos.x = 0;
pos.y = 0;
pos.w = 0;
pos.h = 0;
lua_remove(L, 2);
ltext_(L, &pos);
lua_pushinteger(L, pos.x);
lua_pushinteger(L, pos.y);
lua_pushinteger(L, pos.w);
lua_pushinteger(L, pos.h);
lua_pushinteger(L, pos.n);
lua_pushinteger(L, pos.decent);
return 6;
}
static uint32_t
parse_alignment(lua_State *L, int index) {
const char *alignment_string = lua_tostring(L, index);
int i;
char c;
uint32_t alignment = 0;
for (i=0;(c = alignment_string[i]);i++) {
switch(c) {
case 'l' :
case 'L' :
alignment |= ALIGNMENT_LEFT;
break;
case 'r' :
case 'R' :
alignment |= ALIGNMENT_RIGHT;
break;
case 'c' :
case 'C' :
alignment |= ALIGNMENT_CENTER;
break;
case 't' :
case 'T' :
alignment |= VALIGNMENT_TOP;
break;
case 'v' :
case 'V' :
alignment |= VALIGNMENT_CENTER;
break;
case 'b' :
case 'B' :
alignment |= VALIGNMENT_BOTTOM;
break;
}
}
return alignment;
}
static int
ltext_block(lua_State *L) {
if (material_id <= 0) {
return luaL_error(L, "Text material is not registered");
}
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
void * font_mgr = lua_touserdata(L, 1);
int fontid = luaL_checkinteger(L, 2);
int fontsize = luaL_optinteger(L, 3, DEFAULT_FONTSIZE);
uint32_t color = luaL_optinteger(L, 4, 0xff000000);
uint32_t alignment = 0;
if (lua_type(L, 5) == LUA_TSTRING) {
alignment = parse_alignment(L, 5);
}
if (!(color & 0xff000000))
color |= 0xff000000;
lua_pushlightuserdata(L, font_mgr); // 1
lua_pushinteger(L, fontid); // 2
lua_pushinteger(L, fontsize); // 3
lua_pushinteger(L, color); // 4
lua_pushinteger(L, alignment); // 5
lua_pushcclosure(L, ltext, 5);
lua_pushlightuserdata(L, font_mgr); // 1
lua_pushinteger(L, fontid); // 2
lua_pushinteger(L, fontsize); // 3
lua_pushinteger(L, color); // 4
lua_pushinteger(L, alignment); // 5
lua_pushcclosure(L, ltext_position, 5);
return 2;
}
int
luaopen_material_text(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "char", NULL },
{ "set_material_id", NULL },
{ "block", ltext_block },
{ "normal", lnew_material_text_normal },
{ "instance_size", NULL },
{ NULL, NULL },
};
luaL_newlib(L, l);
// char()
struct text * t = lua_newuserdatauv(L, sizeof(*t), 1);
memset(t, 0, sizeof(*t));
lua_pushvalue(L, -1);
lua_pushcclosure(L, lset_material_id, 1);
lua_setfield(L, -3, "set_material_id");
lua_pushcclosure(L, lchar_for_batch, 1);
lua_setfield(L, -2, "char");
lua_pushinteger(L, sizeof(struct inst_object));
lua_setfield(L, -2, "instance_size");
return 1;
}
================================================
FILE: src/material_util.c
================================================
#include
#include
#include "material_util.h"
void
util_ref_object(lua_State *L, void *ptr, int uv_index, const char *key, const char *luatype, int direct) {
if (lua_getfield(L, 1, key) != LUA_TUSERDATA)
luaL_error(L, "Invalid key .%s", key);
void *obj = luaL_checkudata(L, -1, luatype);
lua_pushvalue(L, -1);
// ud, object, object
lua_setiuservalue(L, -3, uv_index);
if (!direct) {
lua_pushlightuserdata(L, ptr);
lua_call(L, 1, 0);
} else {
lua_pop(L, 1);
void **ref = (void **)ptr;
*ref = obj;
}
}
void
util_submit_material(lua_State *L, int batch_n, void *mat, util_submit_func submit) {
struct draw_primitive *prim = lua_touserdata(L, 2);
int prim_n = luaL_checkinteger(L, 3);
int i = 0;
for (;;) {
int n = prim_n - i;
if (n > batch_n) {
submit(L, mat, prim, batch_n);
i += batch_n;
prim += batch_n;
} else {
submit(L, mat, prim, n);
i += batch_n;
break;
}
}
}
sg_pipeline
util_make_pipeline(sg_pipeline_desc *desc, util_shader_desc_func func, const char *what, int blend) {
sg_shader shd = sg_make_shader(func(sg_query_backend()));
if (sg_query_shader_state(shd) != SG_RESOURCESTATE_VALID) {
fprintf(stderr, "Failed to create shader for %s!\n", what);
}
desc->shader = shd;
if (desc->primitive_type == 0) {
desc->primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP;
}
desc->label = what;
if (desc->layout.buffers[0].step_func == 0) {
desc->layout.buffers[0].step_func = SG_VERTEXSTEP_PER_INSTANCE;
}
if (blend) {
desc->colors[0].blend = (sg_blend_state) {
.enabled = true,
.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.src_factor_alpha = SG_BLENDFACTOR_ONE,
.dst_factor_alpha = SG_BLENDFACTOR_ZERO
};
}
sg_pipeline pip = sg_make_pipeline(desc);
if (sg_query_pipeline_state(pip) != SG_RESOURCESTATE_VALID) {
fprintf(stderr, "failed to create pipeline %s\n", what);
}
return pip;
}
================================================
FILE: src/material_util.h
================================================
#ifndef soluna_material_util_h
#define soluna_material_util_h
#include
#include "sokol/sokol_gfx.h"
#include "batch.h"
void util_ref_object(lua_State *L, void *ptr, int uv_index, const char *key, const char *luatype, int direct);
typedef void (*util_submit_func)(lua_State *L, void *m_, struct draw_primitive *prim, int n);
void util_submit_material(lua_State *L, int batch_n, void *mat, util_submit_func submit);
typedef const sg_shader_desc* (*util_shader_desc_func)(sg_backend backend);
sg_pipeline util_make_pipeline(sg_pipeline_desc *desc, util_shader_desc_func func, const char *what, int blend);
#endif
================================================
FILE: src/mutex.h
================================================
#ifndef __fontmutex_h_
#define __fontmutex_h_
#if defined(_WIN32)
#include
#define mutex_t SRWLOCK
#define mutex_init(m) InitializeSRWLock(&m)
#define mutex_acquire(m) AcquireSRWLockExclusive(&m)
#define mutex_release(m) ReleaseSRWLockExclusive(&m)
#else
#include
#define mutex_t pthread_mutex_t
#define mutex_init(m) pthread_mutex_init(&m, NULL)
#define mutex_acquire(m) pthread_mutex_lock(&m)
#define mutex_release(m) pthread_mutex_unlock(&m)
#endif
#endif
================================================
FILE: src/openlibs.c
================================================
#define linit_c
#define LUA_LIB
#include
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
void soluna_embed(lua_State* L);
void
soluna_openlibs(lua_State *L) {
// ignore env. vars.
lua_pushboolean(L, 1);
lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");
luaL_openlibs(L);
soluna_embed(L);
}
================================================
FILE: src/openurl.c
================================================
#include
#include
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
#include
static void
open_url(lua_State *L, const char *url) {
int n = MultiByteToWideChar(CP_UTF8, 0, url, -1, NULL, 0);
if (n == 0)
luaL_error(L, "Invalid url string : %s", url);
void * buf = lua_newuserdatauv(L, n * sizeof(WCHAR), 0);
MultiByteToWideChar(CP_UTF8, 0, url, -1, (WCHAR *)buf, n);
ShellExecuteW(NULL, L"open", (WCHAR *)buf, NULL, NULL, SW_SHOWNORMAL);
}
#elif defined(__EMSCRIPTEN__)
#include
#if defined(__EMSCRIPTEN_PTHREADS__)
#include
#endif
extern void soluna_wasm_open_url(const char *url);
static void
soluna_wasm_call_open_url(const char *url) {
#if defined(__EMSCRIPTEN_PTHREADS__)
if (!emscripten_is_main_browser_thread()) {
emscripten_async_run_in_main_runtime_thread(EM_FUNC_SIG_VI, soluna_wasm_open_url, url);
return;
}
#endif
soluna_wasm_open_url(url);
}
static void
open_url(lua_State *L, const char *url) {
soluna_wasm_call_open_url(url);
}
#elif defined(__APPLE__) || defined(__linux__)
#include
#include
#include
#include
static void
open_url(lua_State *L, const char *url) {
pid_t pid = fork();
if (pid < 0) {
luaL_error(L, "fork() failed");
} else if (pid == 0) {
// child
#if defined(__APPLE__)
execl("/usr/bin/open", "open", url, (char *)NULL);
#else
execl("/usr/bin/xdg-open", "xdg-open", url, (char *)NULL);
#endif
// if execl return, it's error
_exit(127);
} else {
// parent
int status;
waitpid(pid, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
luaL_error(L, "failed to open url: %s", url);
}
}
}
#else
static void
open_url(lua_State *L, const char *url) {
}
#endif
static int
lurl_open(lua_State *L) {
const char * url = luaL_checkstring(L, 1);
open_url(L, url);
return 0;
}
int
luaopen_url(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "open", lurl_open },
{ NULL, NULL },
};
luaL_newlib(L, l);
return 1;
}
================================================
FILE: src/platform/linux/soluna_linux_ime.c
================================================
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "sokol/sokol_app.h"
#include "ime_state.h"
#include "ime_char_filter.h"
#include "soluna_linux_ime.h"
void soluna_emit_char(uint32_t codepoint, uint32_t modifiers, bool repeat);
static XIM g_soluna_linux_im = NULL;
static XIC g_soluna_linux_ic = NULL;
static bool g_soluna_linux_xim_failed = false;
static bool g_soluna_linux_has_focus = false;
static bool g_soluna_linux_locale_ready = false;
static const int SOLUNA_LINUX_CHAR_QUEUE_CAP = 32;
static uint32_t g_soluna_linux_expected_chars[32];
static int g_soluna_linux_expected_count = 0;
static uint32_t g_soluna_linux_ignore_chars[32];
static int g_soluna_linux_ignore_count = 0;
static inline struct soluna_ime_char_filter_state
soluna_linux_char_filter_state(void) {
return (struct soluna_ime_char_filter_state) {
.expected_chars = g_soluna_linux_expected_chars,
.expected_count = &g_soluna_linux_expected_count,
.ignore_chars = g_soluna_linux_ignore_chars,
.ignore_count = &g_soluna_linux_ignore_count,
.capacity = SOLUNA_LINUX_CHAR_QUEUE_CAP,
};
}
static void
soluna_linux_reset_char_queues(void) {
soluna_ime_char_filter_reset(soluna_linux_char_filter_state());
}
static Display *
soluna_linux_display(void) {
return (Display *)sapp_x11_get_display();
}
static Window
soluna_linux_window(void) {
return (Window)(uintptr_t)sapp_x11_get_window();
}
static void
soluna_linux_ensure_locale(void) {
if (g_soluna_linux_locale_ready) {
return;
}
if (!setlocale(LC_CTYPE, "")) {
fprintf(stderr, "soluna: failed to set locale\n");
}
if (!XSupportsLocale()) {
fprintf(stderr, "soluna: current locale is not supported by Xlib\n");
}
XSetLocaleModifiers("");
g_soluna_linux_locale_ready = true;
}
static void
soluna_linux_set_spot(short x, short y) {
if (!g_soluna_linux_ic) {
return;
}
XVaNestedList preedit = XVaCreateNestedList(0,
XNSpotLocation, &(XPoint){ x, y },
NULL);
if (!preedit) {
return;
}
XSetICValues(g_soluna_linux_ic, XNPreeditAttributes, preedit, NULL);
XFree(preedit);
}
bool
soluna_linux_ensure_im(void) {
if (g_soluna_linux_ic) {
return true;
}
if (g_soluna_linux_xim_failed) {
return false;
}
Display *dpy = soluna_linux_display();
Window win = soluna_linux_window();
if (!dpy || !win) {
return false;
}
soluna_linux_ensure_locale();
XIM im = XOpenIM(dpy, NULL, NULL, NULL);
if (!im) {
g_soluna_linux_xim_failed = true;
return false;
}
XIC ic = XCreateIC(
im,
XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win,
XNFocusWindow, win,
NULL);
if (!ic) {
XCloseIM(im);
return false;
}
g_soluna_linux_im = im;
g_soluna_linux_ic = ic;
soluna_linux_set_spot(0, 0);
return true;
}
void
soluna_linux_update_spot(void) {
if (!g_soluna_ime_rect.valid) {
return;
}
if (!soluna_linux_ensure_im()) {
return;
}
float scale = sapp_dpi_scale();
if (scale <= 0.0f) {
scale = 1.0f;
}
float caret_x = g_soluna_ime_rect.x;
float caret_y = g_soluna_ime_rect.y + g_soluna_ime_rect.h;
if (caret_x < 0.0f) {
caret_x = 0.0f;
}
if (caret_y < 0.0f) {
caret_y = 0.0f;
}
short spot_x = (short)(caret_x * scale + 0.5f);
short spot_y = (short)(caret_y * scale + 0.5f);
soluna_linux_set_spot(spot_x, spot_y);
}
static void
soluna_linux_emit_utf8(const char *text, int len, uint32_t mods, bool repeat) {
if (!text || len <= 0) {
return;
}
mbstate_t state;
memset(&state, 0, sizeof(state));
const char *ptr = text;
const char *end = text + len;
bool first = true;
while (ptr < end) {
char32_t ch = 0;
size_t consumed = mbrtoc32(&ch, ptr, (size_t)(end - ptr), &state);
if (consumed == (size_t)-1 || consumed == (size_t)-2) {
memset(&state, 0, sizeof(state));
++ptr;
continue;
}
if (consumed == 0) {
consumed = 1;
}
soluna_ime_char_filter_push_expected(soluna_linux_char_filter_state(), (uint32_t)ch);
soluna_emit_char((uint32_t)ch, mods, first ? repeat : false);
first = false;
ptr += consumed;
}
}
static bool
soluna_linux_handle_keypress(XKeyEvent *kev) {
if (!kev) {
return false;
}
Display *dpy = soluna_linux_display();
if (!dpy || kev->display != dpy || kev->window != soluna_linux_window()) {
return false;
}
if (!soluna_linux_ensure_im()) {
return false;
}
char local_buf[128];
char *buf = local_buf;
int cap = sizeof(local_buf);
KeySym ks = 0;
Status status = 0;
int len = Xutf8LookupString(g_soluna_linux_ic, kev, buf, cap, &ks, &status);
if (status == XBufferOverflow) {
buf = (char *)malloc((size_t)len);
if (!buf) {
return false;
}
cap = len;
len = Xutf8LookupString(g_soluna_linux_ic, kev, buf, cap, &ks, &status);
}
bool handled = false;
if (status == XLookupChars || status == XLookupBoth) {
uint32_t mods = 0;
if (kev->state & ShiftMask) mods |= SAPP_MODIFIER_SHIFT;
if (kev->state & ControlMask) mods |= SAPP_MODIFIER_CTRL;
if (kev->state & Mod1Mask) mods |= SAPP_MODIFIER_ALT;
bool repeat = (kev->type == KeyPress) && (kev->state & (1 << 14));
soluna_linux_emit_utf8(buf, len, mods, repeat);
handled = true;
}
if (buf != local_buf) {
free(buf);
}
return handled;
}
static bool
soluna_linux_filter_event(Display *dpy, XEvent *event) {
if (!event) {
return true;
}
if (XFilterEvent(event, None)) {
return true;
}
if (event->type == KeyPress) {
if (!g_soluna_ime_rect.valid) {
return false;
}
if (event->xkey.display != soluna_linux_display() || event->xkey.window != soluna_linux_window()) {
return false;
}
if (soluna_linux_handle_keypress(&event->xkey)) {
return true;
}
}
return false;
}
static int (*soluna_linux_real_XNextEvent)(Display *, XEvent *) = NULL;
static int
soluna_linux_XNextEvent(Display *display, XEvent *event) {
if (!soluna_linux_real_XNextEvent) {
soluna_linux_real_XNextEvent = (int (*)(Display *, XEvent *))dlsym(RTLD_NEXT, "XNextEvent");
if (!soluna_linux_real_XNextEvent) {
fprintf(stderr, "soluna: failed to resolve XNextEvent\n");
abort();
}
}
int r = soluna_linux_real_XNextEvent(display, event);
if (r != 0) {
return r;
}
if (soluna_linux_filter_event(display, event)) {
event->type = 0;
}
return 0;
}
int
XNextEvent(Display *display, XEvent *event) {
return soluna_linux_XNextEvent(display, event);
}
static void
soluna_linux_focus_reset(void) {
soluna_linux_reset_char_queues();
}
void
soluna_linux_focus_in(void) {
g_soluna_linux_xim_failed = false;
if (!soluna_linux_ensure_im()) {
return;
}
if (!g_soluna_linux_has_focus) {
XSetICFocus(g_soluna_linux_ic);
g_soluna_linux_has_focus = true;
}
soluna_linux_update_spot();
}
void
soluna_linux_focus_out(void) {
if (!g_soluna_linux_ic) {
return;
}
if (g_soluna_linux_has_focus) {
XUnsetICFocus(g_soluna_linux_ic);
g_soluna_linux_has_focus = false;
}
soluna_linux_focus_reset();
}
void
soluna_linux_shutdown_ime(void) {
if (g_soluna_linux_ic) {
XDestroyIC(g_soluna_linux_ic);
g_soluna_linux_ic = NULL;
}
if (g_soluna_linux_im) {
XCloseIM(g_soluna_linux_im);
g_soluna_linux_im = NULL;
}
g_soluna_linux_has_focus = false;
g_soluna_linux_xim_failed = false;
soluna_linux_focus_reset();
}
void
soluna_linux_on_rect_cleared(void) {
if (soluna_linux_ensure_im()) {
soluna_linux_set_spot(0, 0);
}
soluna_linux_focus_reset();
}
bool
soluna_linux_should_skip_event(const sapp_event *ev) {
if (!ev || ev->type != SAPP_EVENTTYPE_CHAR) {
return false;
}
return soluna_ime_char_filter_should_skip(soluna_linux_char_filter_state(), ev->char_code);
}
void
soluna_linux_handle_event(const sapp_event *ev) {
if (!ev) {
return;
}
switch (ev->type) {
case SAPP_EVENTTYPE_FOCUSED:
soluna_linux_focus_in();
if (g_soluna_ime_rect.valid) {
soluna_linux_update_spot();
}
break;
case SAPP_EVENTTYPE_UNFOCUSED:
soluna_linux_focus_out();
break;
case SAPP_EVENTTYPE_RESIZED:
if (g_soluna_ime_rect.valid) {
soluna_linux_update_spot();
}
break;
default:
break;
}
}
================================================
FILE: src/platform/linux/soluna_linux_ime.h
================================================
#ifndef SOLUNA_LINUX_IME_H
#define SOLUNA_LINUX_IME_H
#include
bool soluna_linux_ensure_im(void);
void soluna_linux_on_rect_cleared(void);
void soluna_linux_update_spot(void);
void soluna_linux_focus_in(void);
void soluna_linux_focus_out(void);
void soluna_linux_shutdown_ime(void);
bool soluna_linux_should_skip_event(const sapp_event *ev);
void soluna_linux_handle_event(const sapp_event *ev);
#endif /* SOLUNA_LINUX_IME_H */
================================================
FILE: src/platform/macos/soluna_macos_ime.h
================================================
#ifndef SOLUNA_MACOS_IME_H
#define SOLUNA_MACOS_IME_H
#include
void soluna_macos_install_ime(void);
void soluna_macos_hide_ime_label(void);
void soluna_macos_apply_ime_rect(void);
void soluna_macos_set_ime_font(const char *font_name, float height_px);
bool soluna_macos_is_composition_active(void);
#endif /* SOLUNA_MACOS_IME_H */
================================================
FILE: src/platform/macos/soluna_macos_ime.m
================================================
#import