Compare commits
16 Commits
content_de
...
android_co
Author | SHA1 | Date | |
---|---|---|---|
5a2a30ada6 | |||
64959270a9 | |||
9d6404d130 | |||
ef4e0d0857 | |||
56f1bf559c | |||
b0e25627b3 | |||
e7b1eec2cc | |||
7a2d7336fb | |||
fc5023ec1c | |||
95eb0eb26d | |||
d7e658eba6 | |||
04191858de | |||
df449a475c | |||
9a95dba138 | |||
ef79aa8b80 | |||
ba7188cf66 |
92
.github/workflows/cd.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: sudo apt update && sudo apt -y install libsodium-dev
|
run: sudo apt update && sudo apt -y install libsodium-dev cmake
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||||
@ -50,12 +50,91 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
# TODO: simpler name?
|
|
||||||
name: ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-ubuntu20.04-x86_64
|
name: ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-ubuntu20.04-x86_64
|
||||||
# TODO: do propper packing
|
# TODO: do propper packing
|
||||||
path: |
|
path: |
|
||||||
${{github.workspace}}/${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-ubuntu20.04-x86_64.tar.gz
|
${{github.workspace}}/${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-ubuntu20.04-x86_64.tar.gz
|
||||||
|
|
||||||
|
android:
|
||||||
|
timeout-minutes: 30
|
||||||
|
# contains sections copied from sdl repo
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- vcpkg_toolkit: arm64-android
|
||||||
|
ndk_abi: arm64-v8a
|
||||||
|
- vcpkg_toolkit: x64-android
|
||||||
|
ndk_abi: x86_64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: nttld/setup-ndk@v1
|
||||||
|
id: setup_ndk
|
||||||
|
with:
|
||||||
|
local-cache: false # https://github.com/nttld/setup-ndk/issues/518
|
||||||
|
ndk-version: r26d
|
||||||
|
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
|
||||||
|
- name: update vcpkg
|
||||||
|
run: |
|
||||||
|
git clone https://github.com/microsoft/vcpkg.git
|
||||||
|
|
||||||
|
- name: Install Dependencies (host)
|
||||||
|
run: sudo apt update && sudo apt -y install cmake pkg-config nasm
|
||||||
|
|
||||||
|
- name: Install Dependencies (target)
|
||||||
|
env:
|
||||||
|
ANDROID_NDK_HOME: ${{steps.setup_ndk.outputs.ndk-path}}
|
||||||
|
run: vcpkg install --triplet ${{matrix.platform.vcpkg_toolkit}} --overlay-ports=vcpkg/ports libsodium opus libvpx libpng libjpeg-turbo
|
||||||
|
|
||||||
|
# vcpkg scripts root /usr/local/share/vcpkg/scripts
|
||||||
|
- name: Configure CMake
|
||||||
|
env:
|
||||||
|
ANDROID_NDK_HOME: ${{steps.setup_ndk.outputs.ndk-path}}
|
||||||
|
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=${{matrix.platform.vcpkg_toolkit}} -DANDROID=1 -DANDROID_PLATFORM=23 -DANDROID_ABI=${{matrix.platform.ndk_abi}} -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=${{steps.setup_ndk.outputs.ndk-path}}/build/cmake/android.toolchain.cmake -DSDL3IMAGE_JPG_SHARED=OFF -DSDL3IMAGE_PNG_SHARED=OFF -DTOMATO_MAIN_SO=ON
|
||||||
|
|
||||||
|
- name: Build (tomato)
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato
|
||||||
|
|
||||||
|
- name: Build (SDL3-jar) (workaround)
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t SDL3-jar
|
||||||
|
|
||||||
|
- name: Build (apk)
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato-apk
|
||||||
|
|
||||||
|
- name: Determine tag name
|
||||||
|
id: tag
|
||||||
|
shell: bash
|
||||||
|
# taken from llama.cpp
|
||||||
|
run: |
|
||||||
|
SHORT_HASH="$(git rev-parse --short=7 HEAD)"
|
||||||
|
if [[ "${{ env.BRANCH_NAME }}" == "master" ]]; then
|
||||||
|
echo "name=dev-${SHORT_HASH}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
SAFE_NAME=$(echo "${{ env.BRANCH_NAME }}" | tr '/' '-')
|
||||||
|
echo "name=dev-${SAFE_NAME}-${SHORT_HASH}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: rename apk
|
||||||
|
id: rename_apk
|
||||||
|
shell: bash
|
||||||
|
run: mv "${{github.workspace}}/build/android/tomato.apk" "${{github.workspace}}/build/android/${{github.event.repository.name}}-${{steps.tag.outputs.name}}-android-${{matrix.platform.ndk_abi}}.apk"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{github.event.repository.name}}-${{steps.tag.outputs.name}}-${{runner.os}}-android-${{matrix.platform.ndk_abi}}
|
||||||
|
path: |
|
||||||
|
${{github.workspace}}/build/android/${{github.event.repository.name}}-${{steps.tag.outputs.name}}-android-${{matrix.platform.ndk_abi}}.apk
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
@ -74,7 +153,7 @@ jobs:
|
|||||||
git pull
|
git pull
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: vcpkg install pkgconf:x64-windows libsodium:x64-windows-static pthreads:x64-windows-static opus:x64-windows-static libvpx:x64-windows-static
|
run: vcpkg install libsodium:x64-windows-static pthreads:x64-windows-static pkgconf:x64-windows
|
||||||
|
|
||||||
# setup vs env
|
# setup vs env
|
||||||
- uses: ilammy/msvc-dev-cmd@v1
|
- uses: ilammy/msvc-dev-cmd@v1
|
||||||
@ -110,8 +189,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
# TODO: simpler name?
|
name: ${{github.event.repository.name}}-${{steps.tag.outputs.name}}-${{runner.os}}-msvc-x86_64
|
||||||
name: ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-msvc-x86_64
|
|
||||||
# TODO: do propper packing
|
# TODO: do propper packing
|
||||||
path: |
|
path: |
|
||||||
${{github.workspace}}/${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-msvc-x86_64.zip
|
${{github.workspace}}/${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-msvc-x86_64.zip
|
||||||
@ -134,7 +212,7 @@ jobs:
|
|||||||
git pull
|
git pull
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: vcpkg install pkgconf:x64-windows libsodium:x64-windows-static pthreads:x64-windows-static opus:x64-windows-static libvpx:x64-windows-static
|
run: vcpkg install libsodium:x64-windows-static pthreads:x64-windows-static pkgconf:x64-windows
|
||||||
|
|
||||||
# setup vs env
|
# setup vs env
|
||||||
- uses: ilammy/msvc-dev-cmd@v1
|
- uses: ilammy/msvc-dev-cmd@v1
|
||||||
@ -170,7 +248,6 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
# TODO: simpler name?
|
|
||||||
name: ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-msvc-asan-x86_64
|
name: ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-msvc-asan-x86_64
|
||||||
# TODO: do propper packing
|
# TODO: do propper packing
|
||||||
path: |
|
path: |
|
||||||
@ -183,6 +260,7 @@ jobs:
|
|||||||
|
|
||||||
needs:
|
needs:
|
||||||
- linux-ubuntu
|
- linux-ubuntu
|
||||||
|
- android
|
||||||
- windows
|
- windows
|
||||||
- windows-asan
|
- windows-asan
|
||||||
|
|
||||||
|
68
.github/workflows/ci.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: sudo apt update && sudo apt -y install libsodium-dev
|
run: sudo apt update && sudo apt -y install libsodium-dev cmake
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||||
@ -29,6 +29,70 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato
|
||||||
|
|
||||||
|
android:
|
||||||
|
timeout-minutes: 30
|
||||||
|
# contains sections copied from sdl repo
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- vcpkg_toolkit: arm64-android
|
||||||
|
ndk_abi: arm64-v8a
|
||||||
|
- vcpkg_toolkit: x64-android
|
||||||
|
ndk_abi: x86_64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: nttld/setup-ndk@v1
|
||||||
|
id: setup_ndk
|
||||||
|
with:
|
||||||
|
local-cache: false # https://github.com/nttld/setup-ndk/issues/518
|
||||||
|
ndk-version: r26d
|
||||||
|
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
|
||||||
|
- name: update vcpkg
|
||||||
|
run: |
|
||||||
|
git clone https://github.com/microsoft/vcpkg.git
|
||||||
|
|
||||||
|
- name: Install Dependencies (host)
|
||||||
|
run: sudo apt update && sudo apt -y install cmake pkg-config nasm
|
||||||
|
|
||||||
|
- name: Install Dependencies (target)
|
||||||
|
env:
|
||||||
|
ANDROID_NDK_HOME: ${{steps.setup_ndk.outputs.ndk-path}}
|
||||||
|
run: vcpkg install --triplet ${{matrix.platform.vcpkg_toolkit}} --overlay-ports=vcpkg/ports libsodium opus libvpx libpng libjpeg-turbo
|
||||||
|
|
||||||
|
# vcpkg scripts root /usr/local/share/vcpkg/scripts
|
||||||
|
- name: Configure CMake
|
||||||
|
env:
|
||||||
|
ANDROID_NDK_HOME: ${{steps.setup_ndk.outputs.ndk-path}}
|
||||||
|
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=${{matrix.platform.vcpkg_toolkit}} -DANDROID=1 -DANDROID_PLATFORM=23 -DANDROID_ABI=${{matrix.platform.ndk_abi}} -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=${{steps.setup_ndk.outputs.ndk-path}}/build/cmake/android.toolchain.cmake -DSDL3IMAGE_JPG_SHARED=OFF -DSDL3IMAGE_PNG_SHARED=OFF -DTOMATO_MAIN_SO=ON
|
||||||
|
|
||||||
|
- name: Build (tomato)
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato
|
||||||
|
|
||||||
|
- name: Build (SDL3-jar) (workaround)
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t SDL3-jar
|
||||||
|
|
||||||
|
- name: Build (apk)
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato-apk
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.event.repository.name }}-${{matrix.platform.vcpkg_toolkit}}
|
||||||
|
# TODO: do propper packing
|
||||||
|
path: |
|
||||||
|
${{github.workspace}}/build/android/tomato.apk
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
@ -65,7 +129,7 @@ jobs:
|
|||||||
git pull
|
git pull
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: vcpkg install pkgconf:x64-windows libsodium:x64-windows-static pthreads:x64-windows-static opus:x64-windows-static libvpx:x64-windows-static
|
run: vcpkg install libsodium:x64-windows-static pthreads:x64-windows-static pkgconf:x64-windows
|
||||||
|
|
||||||
# setup vs env
|
# setup vs env
|
||||||
- uses: ilammy/msvc-dev-cmd@v1
|
- uses: ilammy/msvc-dev-cmd@v1
|
||||||
|
@ -18,22 +18,16 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
|||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||||
|
|
||||||
|
option(TOMATO_MAIN_SO "Build tomato as a shared object (for eg android apps)" ANDROID)
|
||||||
option(TOMATO_ASAN "Build tomato with asan (gcc/clang/msvc)" OFF)
|
option(TOMATO_ASAN "Build tomato with asan (gcc/clang/msvc)" OFF)
|
||||||
|
|
||||||
if (TOMATO_ASAN)
|
if (TOMATO_ASAN)
|
||||||
if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
|
if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
|
||||||
if (NOT WIN32) # exclude mingw
|
if (NOT WIN32) # exclude mingw
|
||||||
add_compile_options(-fsanitize=address,undefined)
|
#link_libraries(-fsanitize=address)
|
||||||
link_libraries(-fsanitize=address,undefined)
|
link_libraries(-fsanitize=address,undefined)
|
||||||
|
#link_libraries(-fsanitize=undefined)
|
||||||
#add_compile_options(-fsanitize=thread)
|
|
||||||
#link_libraries(-fsanitize=thread)
|
|
||||||
|
|
||||||
message("II enabled ASAN")
|
message("II enabled ASAN")
|
||||||
if (OFF) # TODO: switch for minimal runtime in deployed scenarios
|
|
||||||
add_compile_options(-fsanitize-minimal-runtime)
|
|
||||||
link_libraries(-fsanitize-minimal-runtime)
|
|
||||||
endif()
|
|
||||||
else()
|
else()
|
||||||
message("!! can not enable ASAN on this platform (gcc/clang + win)")
|
message("!! can not enable ASAN on this platform (gcc/clang + win)")
|
||||||
endif()
|
endif()
|
||||||
@ -76,3 +70,10 @@ endif()
|
|||||||
|
|
||||||
add_subdirectory(./src)
|
add_subdirectory(./src)
|
||||||
|
|
||||||
|
# TODO: move to src
|
||||||
|
if (ANDROID AND TARGET SDL3::Jar)
|
||||||
|
message("II building for ANDROID!!!")
|
||||||
|
|
||||||
|
add_subdirectory(android)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
100
android/CMakeLists.txt
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.14...3.24 FATAL_ERROR)
|
||||||
|
|
||||||
|
project(tomato_android)
|
||||||
|
|
||||||
|
# here be dragons
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${SDL3_SOURCE_DIR}/cmake/android")
|
||||||
|
|
||||||
|
find_package(SdlAndroid MODULE)
|
||||||
|
find_package(Java)
|
||||||
|
find_package(SdlAndroidPlatform MODULE)
|
||||||
|
# the existence of SDL3::Jar usually implies platform
|
||||||
|
if(SdlAndroid_FOUND)
|
||||||
|
include(SdlAndroidFunctions)
|
||||||
|
sdl_create_android_debug_keystore(tomato-debug-keystore)
|
||||||
|
sdl_android_compile_resources(tomato-resources RESFOLDER app/res)
|
||||||
|
|
||||||
|
|
||||||
|
set(ANDROID_MANIFEST_PACKAGE "org.libsdl.app.tomato")
|
||||||
|
#set(generated_manifest_path "${CMAKE_CURRENT_BINARY_DIR}/android/${TEST}-src/AndroidManifest.xml")
|
||||||
|
string(REPLACE "." "/" JAVA_PACKAGE_DIR "${ANDROID_MANIFEST_PACKAGE}")
|
||||||
|
#set(GENERATED_SRC_FOLDER "${CMAKE_CURRENT_BINARY_DIR}/android/${TEST}-src")
|
||||||
|
#set(GENERATED_RES_FOLDER "${GENERATED_SRC_FOLDER}/res")
|
||||||
|
#set(JAVA_PACKAGE_DIR "${GENERATED_SRC_FOLDER}/${JAVA_PACKAGE_DIR}")
|
||||||
|
set(JAVA_PACKAGE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/app/java/${JAVA_PACKAGE_DIR}")
|
||||||
|
|
||||||
|
sdl_android_link_resources(tomato-apk-linked
|
||||||
|
MANIFEST "app/AndroidManifest.xml"
|
||||||
|
PACKAGE ${ANDROID_MANIFEST_PACKAGE}
|
||||||
|
RES_TARGETS tomato-resources
|
||||||
|
TARGET_SDK_VERSION 31
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CMAKE_JAVA_COMPILE_FLAGS "-encoding;utf-8")
|
||||||
|
set(classes_path "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/tomato-java.dir/classes")
|
||||||
|
# Some CMake versions have a slow `cmake -E make_directory` implementation
|
||||||
|
if(NOT IS_DIRECTORY "${classes_path}")
|
||||||
|
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory "${classes_path}")
|
||||||
|
endif()
|
||||||
|
set(OUT_JAR "${CMAKE_CURRENT_BINARY_DIR}/tomato.jar")
|
||||||
|
# TODO: convert to cmake's add_jar
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${OUT_JAR}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E rm -rf "${classes_path}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${classes_path}"
|
||||||
|
COMMAND ${Java_JAVAC_EXECUTABLE}
|
||||||
|
-source 1.8 -target 1.8
|
||||||
|
-bootclasspath "$<TARGET_PROPERTY:SDL3::Jar,JAR_FILE>"
|
||||||
|
"${JAVA_PACKAGE_DIR}/TomatoActivity.java"
|
||||||
|
$<TARGET_PROPERTY:tomato-apk-linked,JAVA_R>
|
||||||
|
-cp "$<TARGET_PROPERTY:SDL3::Jar,JAR_FILE>:${SDL_ANDROID_PLATFORM_ANDROID_JAR}"
|
||||||
|
-d "${classes_path}"
|
||||||
|
COMMAND ${Java_JAR_EXECUTABLE} cf "${OUT_JAR}" -C "${classes_path}" .
|
||||||
|
DEPENDS $<TARGET_PROPERTY:tomato-apk-linked,OUTPUTS> "$<TARGET_PROPERTY:SDL3::Jar,JAR_FILE>"
|
||||||
|
)
|
||||||
|
add_custom_target(tomato-jar DEPENDS "${OUT_JAR}")
|
||||||
|
add_dependencies(tomato-jar SDL3::Jar) # HACK: somehow their jar is not registered as an output
|
||||||
|
set_property(TARGET tomato-jar PROPERTY OUTPUT "${OUT_JAR}")
|
||||||
|
|
||||||
|
set(dexworkdir "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/tomato-dex.dir")
|
||||||
|
# Some CMake versions have a slow `cmake -E make_directory` implementation
|
||||||
|
if(NOT IS_DIRECTORY "${dexworkdir}")
|
||||||
|
execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${dexworkdir}")
|
||||||
|
endif()
|
||||||
|
set(classes_dex_base_name "classes.dex")
|
||||||
|
set(classes_dex "${dexworkdir}/${classes_dex_base_name}")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${classes_dex}"
|
||||||
|
COMMAND SdlAndroid::d8
|
||||||
|
$<TARGET_PROPERTY:tomato-jar,OUTPUT>
|
||||||
|
$<TARGET_PROPERTY:SDL3::Jar,JAR_FILE>
|
||||||
|
--lib "${SDL_ANDROID_PLATFORM_ANDROID_JAR}"
|
||||||
|
--output "${dexworkdir}"
|
||||||
|
DEPENDS $<TARGET_PROPERTY:tomato-jar,OUTPUT> $<TARGET_PROPERTY:SDL3::Jar,JAR_FILE>
|
||||||
|
)
|
||||||
|
add_custom_target(tomato-dex DEPENDS "${classes_dex}")
|
||||||
|
set_property(TARGET tomato-dex PROPERTY OUTPUT "${classes_dex}")
|
||||||
|
set_property(TARGET tomato-dex PROPERTY OUTPUT_BASE_NAME "${classes_dex_base_name}")
|
||||||
|
|
||||||
|
# file(GLOB RESOURCE_FILES *.bmp *.wav *.hex moose.dat utf8.txt)
|
||||||
|
|
||||||
|
sdl_add_to_apk_unaligned(tomato-unaligned-apk
|
||||||
|
APK_IN tomato-apk-linked
|
||||||
|
OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/intermediates"
|
||||||
|
#ASSETS ${RESOURCE_FILES}
|
||||||
|
#NATIVE_LIBS SDL3::SDL3-shared tomato
|
||||||
|
NATIVE_LIBS tomato
|
||||||
|
DEX tomato-dex
|
||||||
|
)
|
||||||
|
|
||||||
|
sdl_apk_align(tomato-aligned-apk tomato-unaligned-apk
|
||||||
|
OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/intermediates"
|
||||||
|
)
|
||||||
|
sdl_apk_sign(tomato-apk tomato-aligned-apk
|
||||||
|
KEYSTORE tomato-debug-keystore
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message("EE SdlAndroid module not found")
|
||||||
|
endif()
|
||||||
|
|
109
android/app/AndroidManifest.xml
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Replace com.test.game with the identifier of your game below, e.g.
|
||||||
|
com.gamemaker.game
|
||||||
|
-->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.libsdl.app.tomato"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="1.0"
|
||||||
|
android:installLocation="auto">
|
||||||
|
|
||||||
|
<!-- OpenGL ES 2.0 -->
|
||||||
|
<uses-feature android:glEsVersion="0x00020000" />
|
||||||
|
|
||||||
|
<!-- Touchscreen support -->
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.touchscreen"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<!-- Game controller support -->
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.bluetooth"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.gamepad"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.usb.host"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<!-- External mouse input events -->
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.type.pc"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<!-- Audio recording support -->
|
||||||
|
<!-- if you want to capture audio, uncomment this. -->
|
||||||
|
<!-- <uses-feature
|
||||||
|
android:name="android.hardware.microphone"
|
||||||
|
android:required="false" /> -->
|
||||||
|
|
||||||
|
<!-- Camera support -->
|
||||||
|
<!-- if you want to record video, uncomment this. -->
|
||||||
|
<!--
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-feature android:name="android.hardware.camera" />
|
||||||
|
-->
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||||
|
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
|
||||||
|
|
||||||
|
<!-- Allow access to Bluetooth devices -->
|
||||||
|
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||||
|
<!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> -->
|
||||||
|
<!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> -->
|
||||||
|
|
||||||
|
<!-- Allow access to the vibrator -->
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
|
<!-- if you want to capture audio, uncomment this. -->
|
||||||
|
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->
|
||||||
|
|
||||||
|
<!-- Create a Java class extending SDLActivity and place it in a
|
||||||
|
directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java
|
||||||
|
|
||||||
|
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
|
||||||
|
in the XML below.
|
||||||
|
|
||||||
|
An example Java class can be found in README-android.md
|
||||||
|
-->
|
||||||
|
<application android:label="@string/app_name"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:appCategory="social"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:theme="@style/AppTheme"
|
||||||
|
android:hardwareAccelerated="true" >
|
||||||
|
|
||||||
|
<!-- setting sdl hints. uses the string value -->
|
||||||
|
<meta-data android:name="SDL_ENV.SDL_ANDROID_BLOCK_ON_PAUSE" android:value="0"/>
|
||||||
|
|
||||||
|
<activity android:name="TomatoActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:alwaysRetainTaskState="true"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
||||||
|
android:preferMinimalPostProcessing="true"
|
||||||
|
android:exported="true"
|
||||||
|
>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
<!-- Let Android know that we can handle some USB devices and should receive this event -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||||
|
</intent-filter>
|
||||||
|
<!-- Drop file event -->
|
||||||
|
<!--
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
|
</intent-filter>
|
||||||
|
-->
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
18
android/app/java/org/libsdl/app/tomato/TomatoActivity.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package org.libsdl.app.tomato;
|
||||||
|
|
||||||
|
import org.libsdl.app.SDLActivity;
|
||||||
|
|
||||||
|
public class TomatoActivity extends SDLActivity {
|
||||||
|
protected String[] getLibraries() {
|
||||||
|
return new String[] {
|
||||||
|
// "SDL3", // we link statically
|
||||||
|
// "SDL3_image",
|
||||||
|
// "SDL3_mixer",
|
||||||
|
// "SDL3_net",
|
||||||
|
// "SDL3_ttf",
|
||||||
|
// "main"
|
||||||
|
"tomato"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
android/app/play_store_512.png
Normal file
After Width: | Height: | Size: 94 KiB |
6
android/app/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||||
|
</adaptive-icon>
|
BIN
android/app/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
android/app/res/mipmap-hdpi/ic_launcher_background.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
android/app/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
android/app/res/mipmap-hdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
android/app/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
android/app/res/mipmap-mdpi/ic_launcher_background.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
android/app/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/res/mipmap-mdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
android/app/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
android/app/res/mipmap-xhdpi/ic_launcher_background.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
android/app/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
android/app/res/mipmap-xhdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
android/app/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
android/app/res/mipmap-xxhdpi/ic_launcher_background.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
android/app/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
android/app/res/mipmap-xxhdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
android/app/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
android/app/res/mipmap-xxxhdpi/ic_launcher_background.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
android/app/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
android/app/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 15 KiB |
6
android/app/res/values/colors.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#3F51B5</color>
|
||||||
|
<color name="colorPrimaryDark">#303F9F</color>
|
||||||
|
<color name="colorAccent">#FF4081</color>
|
||||||
|
</resources>
|
3
android/app/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">Tomato</string>
|
||||||
|
</resources>
|
10
android/app/res/values/styles.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<!-- <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> -->
|
||||||
|
<!--<style name="AppTheme" parent="android:Theme.AppCompat">-->
|
||||||
|
<style name="AppTheme" parent="android:Theme">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
2
external/CMakeLists.txt
vendored
@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.14...3.24 FATAL_ERROR)
|
|||||||
|
|
||||||
add_subdirectory(./entt)
|
add_subdirectory(./entt)
|
||||||
|
|
||||||
|
add_subdirectory(./json)
|
||||||
|
|
||||||
add_subdirectory(./solanaceae_util)
|
add_subdirectory(./solanaceae_util)
|
||||||
add_subdirectory(./solanaceae_contact)
|
add_subdirectory(./solanaceae_contact)
|
||||||
add_subdirectory(./solanaceae_message3)
|
add_subdirectory(./solanaceae_message3)
|
||||||
|
13
external/json/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16...3.24 FATAL_ERROR)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
if (NOT TARGET nlohmann_json::nlohmann_json)
|
||||||
|
FetchContent_Declare(json
|
||||||
|
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
|
||||||
|
URL_HASH SHA256=d6c65aca6b1ed68e7a182f4757257b107ae403032760ed6ef121c9d55e81757d
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(json)
|
||||||
|
endif()
|
||||||
|
|
2
external/sdl/CMakeLists.txt
vendored
@ -6,6 +6,7 @@ if (NOT TARGET SDL3::SDL3)
|
|||||||
set(SDL_SHARED OFF CACHE INTERNAL "")
|
set(SDL_SHARED OFF CACHE INTERNAL "")
|
||||||
set(SDL_STATIC ON CACHE INTERNAL "")
|
set(SDL_STATIC ON CACHE INTERNAL "")
|
||||||
#TODO: pic ?
|
#TODO: pic ?
|
||||||
|
set(SDL_DISABLE_ANDROID_JAR OFF CACHE INTERNAL "")
|
||||||
|
|
||||||
FetchContent_Declare(SDL3
|
FetchContent_Declare(SDL3
|
||||||
GIT_REPOSITORY https://github.com/libsdl-org/SDL
|
GIT_REPOSITORY https://github.com/libsdl-org/SDL
|
||||||
@ -16,6 +17,7 @@ if (NOT TARGET SDL3::SDL3)
|
|||||||
#GIT_TAG 1103294d33f47ab4c697bb22a9cf27c79c658630 # tip 15-05-2024
|
#GIT_TAG 1103294d33f47ab4c697bb22a9cf27c79c658630 # tip 15-05-2024
|
||||||
#GIT_TAG aacafd62336363077470f678b6217214b3b49473 # tip 28-05-2024
|
#GIT_TAG aacafd62336363077470f678b6217214b3b49473 # tip 28-05-2024
|
||||||
GIT_TAG 5fa9432b7d1c1722de93e1ab46e7a9569a47071e # tip 27-05-2024 - before changes made breaking sdl_image
|
GIT_TAG 5fa9432b7d1c1722de93e1ab46e7a9569a47071e # tip 27-05-2024 - before changes made breaking sdl_image
|
||||||
|
|
||||||
FIND_PACKAGE_ARGS # for the future
|
FIND_PACKAGE_ARGS # for the future
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(SDL3)
|
FetchContent_MakeAvailable(SDL3)
|
||||||
|
2
external/solanaceae_plugin
vendored
@ -74,8 +74,6 @@
|
|||||||
#(libsodium.override { stdenv = pkgs.pkgsStatic.stdenv; })
|
#(libsodium.override { stdenv = pkgs.pkgsStatic.stdenv; })
|
||||||
#pkgsStatic.libsodium
|
#pkgsStatic.libsodium
|
||||||
libsodium
|
libsodium
|
||||||
libopus
|
|
||||||
libvpx
|
|
||||||
] ++ self.packages.${system}.default.dlopenBuildInputs;
|
] ++ self.packages.${system}.default.dlopenBuildInputs;
|
||||||
|
|
||||||
cmakeFlags = [
|
cmakeFlags = [
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.9...3.24 FATAL_ERROR)
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
|
|
||||||
add_executable(tomato
|
if (TOMATO_MAIN_SO)
|
||||||
|
add_library(tomato MODULE)
|
||||||
|
target_compile_definitions(tomato PUBLIC TOMATO_MAIN_SO)
|
||||||
|
else()
|
||||||
|
add_executable(tomato)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_sources(tomato PUBLIC
|
||||||
./main.cpp
|
./main.cpp
|
||||||
./icon.rc
|
./icon.rc
|
||||||
|
|
||||||
|
./json_to_config.hpp
|
||||||
|
./json_to_config.cpp
|
||||||
|
|
||||||
./screen.hpp
|
./screen.hpp
|
||||||
./start_screen.hpp
|
./start_screen.hpp
|
||||||
./start_screen.cpp
|
./start_screen.cpp
|
||||||
@ -16,9 +26,6 @@ add_executable(tomato
|
|||||||
./tox_client.cpp
|
./tox_client.cpp
|
||||||
./auto_dirty.hpp
|
./auto_dirty.hpp
|
||||||
./auto_dirty.cpp
|
./auto_dirty.cpp
|
||||||
./tox_private_impl.hpp
|
|
||||||
./tox_av.hpp
|
|
||||||
./tox_av.cpp
|
|
||||||
|
|
||||||
./theme.hpp
|
./theme.hpp
|
||||||
|
|
||||||
@ -66,10 +73,6 @@ add_executable(tomato
|
|||||||
./chat_gui/settings_window.hpp
|
./chat_gui/settings_window.hpp
|
||||||
./chat_gui/settings_window.cpp
|
./chat_gui/settings_window.cpp
|
||||||
|
|
||||||
./imgui_entt_entity_editor.hpp
|
|
||||||
./object_store_ui.hpp
|
|
||||||
./object_store_ui.cpp
|
|
||||||
|
|
||||||
./tox_ui_utils.hpp
|
./tox_ui_utils.hpp
|
||||||
./tox_ui_utils.cpp
|
./tox_ui_utils.cpp
|
||||||
|
|
||||||
@ -81,14 +84,6 @@ add_executable(tomato
|
|||||||
|
|
||||||
./chat_gui4.hpp
|
./chat_gui4.hpp
|
||||||
./chat_gui4.cpp
|
./chat_gui4.cpp
|
||||||
|
|
||||||
./content/content.hpp
|
|
||||||
./content/frame_stream2.hpp
|
|
||||||
./content/sdl_video_frame_stream2.hpp
|
|
||||||
./content/sdl_video_frame_stream2.cpp
|
|
||||||
./content/audio_stream.hpp
|
|
||||||
./content/sdl_audio_frame_stream2.hpp
|
|
||||||
./content/sdl_audio_frame_stream2.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_features(tomato PUBLIC cxx_std_17)
|
target_compile_features(tomato PUBLIC cxx_std_17)
|
||||||
|
@ -151,8 +151,7 @@ void SendImagePopup::sendMemory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// copy paste data to memory
|
// copy paste data to memory
|
||||||
original_data.clear();
|
original_data = {data, data+data_size};
|
||||||
original_data.insert(original_data.begin(), data, data+data_size);
|
|
||||||
|
|
||||||
if (!load()) {
|
if (!load()) {
|
||||||
std::cerr << "SIP: failed to load image from memory\n";
|
std::cerr << "SIP: failed to load image from memory\n";
|
||||||
|
@ -1,237 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se>
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <memory> // std::allocator
|
|
||||||
#include <new> // std::hardware_destructive_interference_size
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <type_traits> // std::enable_if, std::is_*_constructible
|
|
||||||
|
|
||||||
#ifdef __has_cpp_attribute
|
|
||||||
#if __has_cpp_attribute(nodiscard)
|
|
||||||
#define RIGTORP_NODISCARD [[nodiscard]]
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#ifndef RIGTORP_NODISCARD
|
|
||||||
#define RIGTORP_NODISCARD
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace rigtorp {
|
|
||||||
|
|
||||||
template <typename T, typename Allocator = std::allocator<T>> class SPSCQueue {
|
|
||||||
|
|
||||||
#if defined(__cpp_if_constexpr) && defined(__cpp_lib_void_t)
|
|
||||||
template <typename Alloc2, typename = void>
|
|
||||||
struct has_allocate_at_least : std::false_type {};
|
|
||||||
|
|
||||||
template <typename Alloc2>
|
|
||||||
struct has_allocate_at_least<
|
|
||||||
Alloc2, std::void_t<typename Alloc2::value_type,
|
|
||||||
decltype(std::declval<Alloc2 &>().allocate_at_least(
|
|
||||||
size_t{}))>> : std::true_type {};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit SPSCQueue(const size_t capacity,
|
|
||||||
const Allocator &allocator = Allocator())
|
|
||||||
: capacity_(capacity), allocator_(allocator) {
|
|
||||||
// The queue needs at least one element
|
|
||||||
if (capacity_ < 1) {
|
|
||||||
capacity_ = 1;
|
|
||||||
}
|
|
||||||
capacity_++; // Needs one slack element
|
|
||||||
// Prevent overflowing size_t
|
|
||||||
if (capacity_ > SIZE_MAX - 2 * kPadding) {
|
|
||||||
capacity_ = SIZE_MAX - 2 * kPadding;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(__cpp_if_constexpr) && defined(__cpp_lib_void_t)
|
|
||||||
if constexpr (has_allocate_at_least<Allocator>::value) {
|
|
||||||
auto res = allocator_.allocate_at_least(capacity_ + 2 * kPadding);
|
|
||||||
slots_ = res.ptr;
|
|
||||||
capacity_ = res.count - 2 * kPadding;
|
|
||||||
} else {
|
|
||||||
slots_ = std::allocator_traits<Allocator>::allocate(
|
|
||||||
allocator_, capacity_ + 2 * kPadding);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
slots_ = std::allocator_traits<Allocator>::allocate(
|
|
||||||
allocator_, capacity_ + 2 * kPadding);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static_assert(alignof(SPSCQueue<T>) == kCacheLineSize, "");
|
|
||||||
static_assert(sizeof(SPSCQueue<T>) >= 3 * kCacheLineSize, "");
|
|
||||||
assert(reinterpret_cast<char *>(&readIdx_) -
|
|
||||||
reinterpret_cast<char *>(&writeIdx_) >=
|
|
||||||
static_cast<std::ptrdiff_t>(kCacheLineSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
~SPSCQueue() {
|
|
||||||
while (front()) {
|
|
||||||
pop();
|
|
||||||
}
|
|
||||||
std::allocator_traits<Allocator>::deallocate(allocator_, slots_,
|
|
||||||
capacity_ + 2 * kPadding);
|
|
||||||
}
|
|
||||||
|
|
||||||
// non-copyable and non-movable
|
|
||||||
SPSCQueue(const SPSCQueue &) = delete;
|
|
||||||
SPSCQueue &operator=(const SPSCQueue &) = delete;
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void emplace(Args &&...args) noexcept(
|
|
||||||
std::is_nothrow_constructible<T, Args &&...>::value) {
|
|
||||||
static_assert(std::is_constructible<T, Args &&...>::value,
|
|
||||||
"T must be constructible with Args&&...");
|
|
||||||
auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
|
|
||||||
auto nextWriteIdx = writeIdx + 1;
|
|
||||||
if (nextWriteIdx == capacity_) {
|
|
||||||
nextWriteIdx = 0;
|
|
||||||
}
|
|
||||||
while (nextWriteIdx == readIdxCache_) {
|
|
||||||
readIdxCache_ = readIdx_.load(std::memory_order_acquire);
|
|
||||||
}
|
|
||||||
new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
|
|
||||||
writeIdx_.store(nextWriteIdx, std::memory_order_release);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
RIGTORP_NODISCARD bool try_emplace(Args &&...args) noexcept(
|
|
||||||
std::is_nothrow_constructible<T, Args &&...>::value) {
|
|
||||||
static_assert(std::is_constructible<T, Args &&...>::value,
|
|
||||||
"T must be constructible with Args&&...");
|
|
||||||
auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
|
|
||||||
auto nextWriteIdx = writeIdx + 1;
|
|
||||||
if (nextWriteIdx == capacity_) {
|
|
||||||
nextWriteIdx = 0;
|
|
||||||
}
|
|
||||||
if (nextWriteIdx == readIdxCache_) {
|
|
||||||
readIdxCache_ = readIdx_.load(std::memory_order_acquire);
|
|
||||||
if (nextWriteIdx == readIdxCache_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
|
|
||||||
writeIdx_.store(nextWriteIdx, std::memory_order_release);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void push(const T &v) noexcept(std::is_nothrow_copy_constructible<T>::value) {
|
|
||||||
static_assert(std::is_copy_constructible<T>::value,
|
|
||||||
"T must be copy constructible");
|
|
||||||
emplace(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename P, typename = typename std::enable_if<
|
|
||||||
std::is_constructible<T, P &&>::value>::type>
|
|
||||||
void push(P &&v) noexcept(std::is_nothrow_constructible<T, P &&>::value) {
|
|
||||||
emplace(std::forward<P>(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
RIGTORP_NODISCARD bool
|
|
||||||
try_push(const T &v) noexcept(std::is_nothrow_copy_constructible<T>::value) {
|
|
||||||
static_assert(std::is_copy_constructible<T>::value,
|
|
||||||
"T must be copy constructible");
|
|
||||||
return try_emplace(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename P, typename = typename std::enable_if<
|
|
||||||
std::is_constructible<T, P &&>::value>::type>
|
|
||||||
RIGTORP_NODISCARD bool
|
|
||||||
try_push(P &&v) noexcept(std::is_nothrow_constructible<T, P &&>::value) {
|
|
||||||
return try_emplace(std::forward<P>(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
RIGTORP_NODISCARD T *front() noexcept {
|
|
||||||
auto const readIdx = readIdx_.load(std::memory_order_relaxed);
|
|
||||||
if (readIdx == writeIdxCache_) {
|
|
||||||
writeIdxCache_ = writeIdx_.load(std::memory_order_acquire);
|
|
||||||
if (writeIdxCache_ == readIdx) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &slots_[readIdx + kPadding];
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop() noexcept {
|
|
||||||
static_assert(std::is_nothrow_destructible<T>::value,
|
|
||||||
"T must be nothrow destructible");
|
|
||||||
auto const readIdx = readIdx_.load(std::memory_order_relaxed);
|
|
||||||
assert(writeIdx_.load(std::memory_order_acquire) != readIdx &&
|
|
||||||
"Can only call pop() after front() has returned a non-nullptr");
|
|
||||||
slots_[readIdx + kPadding].~T();
|
|
||||||
auto nextReadIdx = readIdx + 1;
|
|
||||||
if (nextReadIdx == capacity_) {
|
|
||||||
nextReadIdx = 0;
|
|
||||||
}
|
|
||||||
readIdx_.store(nextReadIdx, std::memory_order_release);
|
|
||||||
}
|
|
||||||
|
|
||||||
RIGTORP_NODISCARD size_t size() const noexcept {
|
|
||||||
std::ptrdiff_t diff = writeIdx_.load(std::memory_order_acquire) -
|
|
||||||
readIdx_.load(std::memory_order_acquire);
|
|
||||||
if (diff < 0) {
|
|
||||||
diff += capacity_;
|
|
||||||
}
|
|
||||||
return static_cast<size_t>(diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
RIGTORP_NODISCARD bool empty() const noexcept {
|
|
||||||
return writeIdx_.load(std::memory_order_acquire) ==
|
|
||||||
readIdx_.load(std::memory_order_acquire);
|
|
||||||
}
|
|
||||||
|
|
||||||
RIGTORP_NODISCARD size_t capacity() const noexcept { return capacity_ - 1; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
#ifdef __cpp_lib_hardware_interference_size
|
|
||||||
static constexpr size_t kCacheLineSize =
|
|
||||||
std::hardware_destructive_interference_size;
|
|
||||||
#else
|
|
||||||
static constexpr size_t kCacheLineSize = 64;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Padding to avoid false sharing between slots_ and adjacent allocations
|
|
||||||
static constexpr size_t kPadding = (kCacheLineSize - 1) / sizeof(T) + 1;
|
|
||||||
|
|
||||||
private:
|
|
||||||
size_t capacity_;
|
|
||||||
T *slots_;
|
|
||||||
#if defined(__has_cpp_attribute) && __has_cpp_attribute(no_unique_address)
|
|
||||||
Allocator allocator_ [[no_unique_address]];
|
|
||||||
#else
|
|
||||||
Allocator allocator_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Align to cache line size in order to avoid false sharing
|
|
||||||
// readIdxCache_ and writeIdxCache_ is used to reduce the amount of cache
|
|
||||||
// coherency traffic
|
|
||||||
alignas(kCacheLineSize) std::atomic<size_t> writeIdx_ = {0};
|
|
||||||
alignas(kCacheLineSize) size_t readIdxCache_ = 0;
|
|
||||||
alignas(kCacheLineSize) std::atomic<size_t> readIdx_ = {0};
|
|
||||||
alignas(kCacheLineSize) size_t writeIdxCache_ = 0;
|
|
||||||
};
|
|
||||||
} // namespace rigtorp
|
|
@ -1,69 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "./frame_stream2.hpp"
|
|
||||||
|
|
||||||
#include <solanaceae/util/span.hpp>
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
// raw audio
|
|
||||||
// channels make samples interleaved,
|
|
||||||
// planar channels are not supported
|
|
||||||
struct AudioFrame {
|
|
||||||
// sequence number, to detect gaps
|
|
||||||
uint32_t seq {0};
|
|
||||||
// TODO: maybe use ts instead to discard old?
|
|
||||||
// since buffer size is variable, some timestamp would be needed to estimate the lost time
|
|
||||||
|
|
||||||
// samples per second
|
|
||||||
uint32_t sample_rate {48'000};
|
|
||||||
|
|
||||||
size_t channels {0};
|
|
||||||
std::variant<
|
|
||||||
std::vector<int16_t>, // S16, platform endianess
|
|
||||||
Span<int16_t>, // non owning variant, for direct consumption
|
|
||||||
|
|
||||||
std::vector<float>, // f32
|
|
||||||
Span<float> // non owning variant, for direct consumption
|
|
||||||
> buffer;
|
|
||||||
|
|
||||||
// helpers
|
|
||||||
|
|
||||||
bool isS16(void) const {
|
|
||||||
return
|
|
||||||
std::holds_alternative<std::vector<int16_t>>(buffer) ||
|
|
||||||
std::holds_alternative<Span<int16_t>>(buffer)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
bool isF32(void) const {
|
|
||||||
return
|
|
||||||
std::holds_alternative<std::vector<float>>(buffer) ||
|
|
||||||
std::holds_alternative<Span<float>>(buffer)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
template<typename T>
|
|
||||||
Span<T> getSpan(void) const {
|
|
||||||
static_assert(std::is_same_v<int16_t, T> || std::is_same_v<float, T>);
|
|
||||||
if constexpr (std::is_same_v<int16_t, T>) {
|
|
||||||
assert(isS16());
|
|
||||||
if (std::holds_alternative<std::vector<int16_t>>(buffer)) {
|
|
||||||
return Span<int16_t>{std::get<std::vector<int16_t>>(buffer)};
|
|
||||||
} else {
|
|
||||||
return std::get<Span<int16_t>>(buffer);
|
|
||||||
}
|
|
||||||
} else if constexpr (std::is_same_v<float, T>) {
|
|
||||||
assert(isF32());
|
|
||||||
if (std::holds_alternative<std::vector<float>>(buffer)) {
|
|
||||||
return Span<float>{std::get<std::vector<float>>(buffer)};
|
|
||||||
} else {
|
|
||||||
return std::get<Span<float>>(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using AudioFrameStream2I = FrameStream2I<AudioFrame>;
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <entt/container/dense_set.hpp>
|
|
||||||
|
|
||||||
#include <solanaceae/object_store/object_store.hpp>
|
|
||||||
#include <solanaceae/contact/contact_model3.hpp>
|
|
||||||
#include <solanaceae/message3/registry_message_model.hpp>
|
|
||||||
|
|
||||||
#include <solanaceae/file/file2.hpp>
|
|
||||||
|
|
||||||
namespace Content1::Components {
|
|
||||||
|
|
||||||
// TODO: design it as a tree?
|
|
||||||
|
|
||||||
// or something
|
|
||||||
struct TagFile {};
|
|
||||||
struct TagAudioStream {};
|
|
||||||
struct TagVideoStream {};
|
|
||||||
|
|
||||||
struct TimingTiedTo {
|
|
||||||
entt::dense_set<ObjectHandle> ties;
|
|
||||||
};
|
|
||||||
|
|
||||||
// the associated messages, if any
|
|
||||||
// useful if you want to update progress on the message
|
|
||||||
struct Messages {
|
|
||||||
std::vector<Message3Handle> messages;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ?
|
|
||||||
struct SuspectedParticipants {
|
|
||||||
entt::dense_set<Contact3> participants;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ReadHeadHint {
|
|
||||||
// points to the first byte we want
|
|
||||||
// this is just a hint, that can be set from outside
|
|
||||||
// to guide the sequential "piece picker" strategy
|
|
||||||
// the strategy *should* set this to the first byte we dont yet have
|
|
||||||
uint64_t offset_into_file {0u};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // Content::Components
|
|
||||||
|
|
||||||
// TODO: i have no idea
|
|
||||||
struct RawFile2ReadFromContentFactoryI {
|
|
||||||
virtual std::shared_ptr<File2I> open(ObjectHandle h) = 0;
|
|
||||||
};
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <vector>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include "./SPSCQueue.h"
|
|
||||||
|
|
||||||
// Frames ofen consist of:
|
|
||||||
// - seq id // incremental sequential id, gaps in ids can be used to detect loss
|
|
||||||
// - data // the frame data
|
|
||||||
// eg:
|
|
||||||
//struct ExampleFrame {
|
|
||||||
//int64_t seq_id {0};
|
|
||||||
//std::vector<uint8_t> data;
|
|
||||||
//};
|
|
||||||
|
|
||||||
template<typename FrameType>
|
|
||||||
struct FrameStream2I {
|
|
||||||
virtual ~FrameStream2I(void) {}
|
|
||||||
|
|
||||||
// get number of available frames
|
|
||||||
[[nodiscard]] virtual int32_t size(void) = 0;
|
|
||||||
|
|
||||||
// get next frame
|
|
||||||
// TODO: optional instead?
|
|
||||||
// data sharing? -> no, data is copied for each fsr, if concurency supported
|
|
||||||
[[nodiscard]] virtual std::optional<FrameType> pop(void) = 0;
|
|
||||||
|
|
||||||
// returns true if there are readers (or we dont know)
|
|
||||||
virtual bool push(const FrameType& value) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// needs count frames queue size
|
|
||||||
// having ~1-2sec buffer size is often sufficent
|
|
||||||
template<typename FrameType>
|
|
||||||
struct QueuedFrameStream2 : public FrameStream2I<FrameType> {
|
|
||||||
using frame_type = FrameType;
|
|
||||||
|
|
||||||
rigtorp::SPSCQueue<FrameType> _queue;
|
|
||||||
|
|
||||||
// discard values if queue full
|
|
||||||
// will block if not lossy and full on push
|
|
||||||
const bool _lossy {true};
|
|
||||||
|
|
||||||
explicit QueuedFrameStream2(size_t queue_size, bool lossy = true) : _queue(queue_size), _lossy(lossy) {}
|
|
||||||
|
|
||||||
int32_t size(void) override {
|
|
||||||
return _queue.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<FrameType> pop(void) override {
|
|
||||||
auto* ret_ptr = _queue.front();
|
|
||||||
if (ret_ptr == nullptr) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// move away
|
|
||||||
FrameType ret = std::move(*ret_ptr);
|
|
||||||
|
|
||||||
_queue.pop();
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool push(const FrameType& value) override {
|
|
||||||
if (_lossy) {
|
|
||||||
[[maybe_unused]] auto _ = _queue.try_emplace(value);
|
|
||||||
// TODO: maybe return ?
|
|
||||||
} else {
|
|
||||||
_queue.push(value);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// implements a stream that pops or pushes to all sub streams
|
|
||||||
// you need to mind the direction you intend it to use
|
|
||||||
// release all streams before destructing! // TODO: improve lifetime here, maybe some shared semaphore?
|
|
||||||
template<typename FrameType, typename SubStreamType = QueuedFrameStream2<FrameType>>
|
|
||||||
struct FrameStream2MultiStream : public FrameStream2I<FrameType> {
|
|
||||||
using sub_stream_type_t = SubStreamType;
|
|
||||||
|
|
||||||
// pointer stability
|
|
||||||
std::vector<std::unique_ptr<SubStreamType>> _sub_streams;
|
|
||||||
std::mutex _sub_stream_lock; // accessing the _sub_streams array needs to be exclusive
|
|
||||||
// a simple lock here is ok, since this tends to be a rare operation,
|
|
||||||
// except for the push, which is always on the same thread
|
|
||||||
|
|
||||||
// TODO: forward args instead
|
|
||||||
SubStreamType* aquireSubStream(size_t queue_size = 10, bool lossy = true) {
|
|
||||||
std::lock_guard lg{_sub_stream_lock};
|
|
||||||
return _sub_streams.emplace_back(std::make_unique<SubStreamType>(queue_size, lossy)).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void releaseSubStream(SubStreamType* sub) {
|
|
||||||
std::lock_guard lg{_sub_stream_lock};
|
|
||||||
for (auto it = _sub_streams.begin(); it != _sub_streams.end(); it++) {
|
|
||||||
if (it->get() == sub) {
|
|
||||||
_sub_streams.erase(it);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stream interface
|
|
||||||
|
|
||||||
int32_t size(void) override {
|
|
||||||
// TODO: return something sensible?
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<FrameType> pop(void) override {
|
|
||||||
assert(false && "this logic is very frame type specific, provide an impl");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns true if there are readers
|
|
||||||
bool push(const FrameType& value) override {
|
|
||||||
std::lock_guard lg{_sub_stream_lock};
|
|
||||||
bool have_readers{false};
|
|
||||||
for (auto& it : _sub_streams) {
|
|
||||||
[[maybe_unused]] auto _ = it->push(value);
|
|
||||||
have_readers = true; // even if queue full, we still continue believing in them
|
|
||||||
// maybe consider push return value?
|
|
||||||
}
|
|
||||||
return have_readers;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
|||||||
#include "./sdl_audio_frame_stream2.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
SDLAudioInputDeviceDefault::SDLAudioInputDeviceDefault(void) : _stream{nullptr, &SDL_DestroyAudioStream} {
|
|
||||||
constexpr SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, 48000 };
|
|
||||||
|
|
||||||
_stream = {
|
|
||||||
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_CAPTURE, &spec, nullptr, nullptr),
|
|
||||||
&SDL_DestroyAudioStream
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!static_cast<bool>(_stream)) {
|
|
||||||
std::cerr << "SDL open audio device failed!\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto audio_device_id = SDL_GetAudioStreamDevice(_stream.get());
|
|
||||||
SDL_ResumeAudioDevice(audio_device_id);
|
|
||||||
|
|
||||||
static constexpr size_t buffer_size {512*2}; // in samples
|
|
||||||
const auto interval_ms {(buffer_size * 1000) / spec.freq};
|
|
||||||
|
|
||||||
_thread = std::thread([this, interval_ms, spec](void) {
|
|
||||||
while (!_thread_should_quit) {
|
|
||||||
//static std::vector<int16_t> buffer(buffer_size);
|
|
||||||
static AudioFrame tmp_frame {
|
|
||||||
0, // TODO: seq
|
|
||||||
spec.freq, spec.channels,
|
|
||||||
std::vector<int16_t>(buffer_size)
|
|
||||||
};
|
|
||||||
|
|
||||||
auto& buffer = std::get<std::vector<int16_t>>(tmp_frame.buffer);
|
|
||||||
buffer.resize(buffer_size);
|
|
||||||
|
|
||||||
const auto read_bytes = SDL_GetAudioStreamData(
|
|
||||||
_stream.get(),
|
|
||||||
buffer.data(),
|
|
||||||
buffer.size()*sizeof(int16_t)
|
|
||||||
);
|
|
||||||
//if (read_bytes != 0) {
|
|
||||||
//std::cerr << "read " << read_bytes << "/" << buffer.size()*sizeof(int16_t) << " audio bytes\n";
|
|
||||||
//}
|
|
||||||
|
|
||||||
// no new frame yet, or error
|
|
||||||
if (read_bytes <= 0) {
|
|
||||||
// only sleep 1/5, we expected a frame
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(int64_t(interval_ms/5)));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.resize(read_bytes/sizeof(int16_t)); // this might be costly?
|
|
||||||
|
|
||||||
bool someone_listening {false};
|
|
||||||
someone_listening = push(tmp_frame);
|
|
||||||
|
|
||||||
if (someone_listening) {
|
|
||||||
// double the interval on acquire
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(int64_t(interval_ms/2)));
|
|
||||||
} else {
|
|
||||||
std::cerr << "i guess no one is listening\n";
|
|
||||||
// we just sleep 32x as long, bc no one is listening
|
|
||||||
// with the hardcoded settings, this is ~320ms
|
|
||||||
// TODO: just hardcode something like 500ms?
|
|
||||||
// TODO: suspend
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(int64_t(interval_ms*32)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLAudioInputDeviceDefault::~SDLAudioInputDeviceDefault(void) {
|
|
||||||
// TODO: pause audio device?
|
|
||||||
_thread_should_quit = true;
|
|
||||||
_thread.join();
|
|
||||||
// TODO: what to do if readers are still present?
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t SDLAudioOutputDeviceDefaultInstance::size(void) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<AudioFrame> SDLAudioOutputDeviceDefaultInstance::pop(void) {
|
|
||||||
assert(false);
|
|
||||||
// this is an output device, there is no data to pop
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SDLAudioOutputDeviceDefaultInstance::push(const AudioFrame& value) {
|
|
||||||
if (!static_cast<bool>(_stream)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify here the fame has the same sample type, channel count and sample freq
|
|
||||||
// if something changed, we need to either use a temporary stream, just for conversion, or reopen _stream with the new params
|
|
||||||
// because of data temporality, the second options looks like a better candidate
|
|
||||||
if (
|
|
||||||
value.sample_rate != _last_sample_rate ||
|
|
||||||
value.channels != _last_channels ||
|
|
||||||
(value.isF32() && _last_format != SDL_AUDIO_F32) ||
|
|
||||||
(value.isS16() && _last_format != SDL_AUDIO_S16)
|
|
||||||
) {
|
|
||||||
const auto device_id = SDL_GetAudioStreamDevice(_stream.get());
|
|
||||||
SDL_FlushAudioStream(_stream.get());
|
|
||||||
|
|
||||||
const SDL_AudioSpec spec = {
|
|
||||||
static_cast<SDL_AudioFormat>((value.isF32() ? SDL_AUDIO_F32 : SDL_AUDIO_S16)),
|
|
||||||
static_cast<int>(value.channels),
|
|
||||||
static_cast<int>(value.sample_rate)
|
|
||||||
};
|
|
||||||
|
|
||||||
_stream = {
|
|
||||||
SDL_OpenAudioDeviceStream(device_id, &spec, nullptr, nullptr),
|
|
||||||
&SDL_DestroyAudioStream
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK
|
|
||||||
assert(value.isS16());
|
|
||||||
|
|
||||||
auto data = value.getSpan<int16_t>();
|
|
||||||
|
|
||||||
if (data.size == 0) {
|
|
||||||
std::cerr << "empty audio frame??\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SDL_PutAudioStreamData(_stream.get(), data.ptr, data.size * sizeof(int16_t)) < 0) {
|
|
||||||
std::cerr << "put data error\n";
|
|
||||||
return false; // return true?
|
|
||||||
}
|
|
||||||
|
|
||||||
_last_sample_rate = value.sample_rate;
|
|
||||||
_last_channels = value.channels;
|
|
||||||
_last_format = value.isF32() ? SDL_AUDIO_F32 : SDL_AUDIO_S16;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLAudioOutputDeviceDefaultInstance::SDLAudioOutputDeviceDefaultInstance(void) : _stream(nullptr, nullptr) {
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLAudioOutputDeviceDefaultInstance::SDLAudioOutputDeviceDefaultInstance(SDLAudioOutputDeviceDefaultInstance&& other) : _stream(std::move(other._stream)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLAudioOutputDeviceDefaultInstance::~SDLAudioOutputDeviceDefaultInstance(void) {
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLAudioOutputDeviceDefaultInstance SDLAudioOutputDeviceDefaultFactory::create(void) {
|
|
||||||
SDLAudioOutputDeviceDefaultInstance new_instance;
|
|
||||||
|
|
||||||
constexpr SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, 48000 };
|
|
||||||
|
|
||||||
new_instance._stream = {
|
|
||||||
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec, nullptr, nullptr),
|
|
||||||
&SDL_DestroyAudioStream
|
|
||||||
};
|
|
||||||
new_instance._last_sample_rate = spec.freq;
|
|
||||||
new_instance._last_channels = spec.channels;
|
|
||||||
new_instance._last_format = spec.format;
|
|
||||||
|
|
||||||
if (!static_cast<bool>(new_instance._stream)) {
|
|
||||||
std::cerr << "SDL open audio device failed!\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto audio_device_id = SDL_GetAudioStreamDevice(new_instance._stream.get());
|
|
||||||
SDL_ResumeAudioDevice(audio_device_id);
|
|
||||||
|
|
||||||
return new_instance;
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "./frame_stream2.hpp"
|
|
||||||
#include "./audio_stream.hpp"
|
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
// we dont have to multicast ourself, because sdl streams and virtual devices already do this, but we do it anyway
|
|
||||||
using AudioFrameStream2MultiStream = FrameStream2MultiStream<AudioFrame>;
|
|
||||||
using AudioFrameStream2 = AudioFrameStream2MultiStream::sub_stream_type_t; // just use the default for now
|
|
||||||
|
|
||||||
// object components?
|
|
||||||
|
|
||||||
// source
|
|
||||||
struct SDLAudioInputDeviceDefault : protected AudioFrameStream2MultiStream {
|
|
||||||
std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream;
|
|
||||||
|
|
||||||
std::atomic<bool> _thread_should_quit {false};
|
|
||||||
std::thread _thread;
|
|
||||||
|
|
||||||
// construct source and start thread
|
|
||||||
// TODO: optimize so the thread is not always running
|
|
||||||
SDLAudioInputDeviceDefault(void);
|
|
||||||
|
|
||||||
// stops the thread and closes the device?
|
|
||||||
~SDLAudioInputDeviceDefault(void);
|
|
||||||
|
|
||||||
using AudioFrameStream2MultiStream::aquireSubStream;
|
|
||||||
using AudioFrameStream2MultiStream::releaseSubStream;
|
|
||||||
};
|
|
||||||
|
|
||||||
// sink
|
|
||||||
struct SDLAudioOutputDeviceDefaultInstance : protected AudioFrameStream2I {
|
|
||||||
std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream;
|
|
||||||
|
|
||||||
uint32_t _last_sample_rate {48'000};
|
|
||||||
size_t _last_channels {0};
|
|
||||||
SDL_AudioFormat _last_format {0};
|
|
||||||
|
|
||||||
SDLAudioOutputDeviceDefaultInstance(void);
|
|
||||||
SDLAudioOutputDeviceDefaultInstance(SDLAudioOutputDeviceDefaultInstance&& other);
|
|
||||||
|
|
||||||
~SDLAudioOutputDeviceDefaultInstance(void);
|
|
||||||
|
|
||||||
int32_t size(void) override;
|
|
||||||
std::optional<AudioFrame> pop(void) override;
|
|
||||||
bool push(const AudioFrame& value) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
// constructs entirely new streams, since sdl handles sync and mixing for us (or should)
|
|
||||||
struct SDLAudioOutputDeviceDefaultFactory {
|
|
||||||
// TODO: pause device?
|
|
||||||
|
|
||||||
SDLAudioOutputDeviceDefaultInstance create(void);
|
|
||||||
};
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
|||||||
#include "./sdl_video_frame_stream2.hpp"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <iostream>
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
SDLVideoCameraContent::SDLVideoCameraContent(void) {
|
|
||||||
int devcount {0};
|
|
||||||
SDL_CameraDeviceID *devices = SDL_GetCameraDevices(&devcount);
|
|
||||||
std::cout << "SDL Camera Driver: " << SDL_GetCurrentCameraDriver() << "\n";
|
|
||||||
|
|
||||||
if (devices == nullptr || devcount < 1) {
|
|
||||||
throw int(1); // TODO: proper exception?
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "### found cameras:\n";
|
|
||||||
for (int i = 0; i < devcount; i++) {
|
|
||||||
const SDL_CameraDeviceID device = devices[i];
|
|
||||||
|
|
||||||
char *name = SDL_GetCameraDeviceName(device);
|
|
||||||
std::cout << " - Camera #" << i << ": " << name << "\n";
|
|
||||||
SDL_free(name);
|
|
||||||
|
|
||||||
int speccount {0};
|
|
||||||
SDL_CameraSpec* specs = SDL_GetCameraDeviceSupportedFormats(device, &speccount);
|
|
||||||
if (specs == nullptr) {
|
|
||||||
std::cout << " - no supported spec\n";
|
|
||||||
} else {
|
|
||||||
for (int spec_i = 0; spec_i < speccount; spec_i++) {
|
|
||||||
std::cout << " - " << specs[spec_i].width << "x" << specs[spec_i].height << "@" << float(specs[spec_i].interval_denominator)/specs[spec_i].interval_numerator << " " << SDL_GetPixelFormatName(specs[spec_i].format) << "\n";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_free(specs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
SDL_CameraSpec spec {
|
|
||||||
// FORCE a diffrent pixel format
|
|
||||||
SDL_PIXELFORMAT_RGBA8888,
|
|
||||||
|
|
||||||
//1280, 720,
|
|
||||||
//640, 360,
|
|
||||||
640, 480,
|
|
||||||
|
|
||||||
1, 30
|
|
||||||
};
|
|
||||||
_camera = {
|
|
||||||
SDL_OpenCameraDevice(devices[0], &spec),
|
|
||||||
&SDL_CloseCamera
|
|
||||||
};
|
|
||||||
}
|
|
||||||
SDL_free(devices);
|
|
||||||
if (!static_cast<bool>(_camera)) {
|
|
||||||
throw int(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_CameraSpec spec;
|
|
||||||
float interval {0.1f};
|
|
||||||
if (SDL_GetCameraFormat(_camera.get(), &spec) < 0) {
|
|
||||||
// meh
|
|
||||||
} else {
|
|
||||||
// interval
|
|
||||||
interval = float(spec.interval_numerator)/float(spec.interval_denominator);
|
|
||||||
std::cout << "camera interval: " << interval*1000 << "ms\n";
|
|
||||||
auto* format_name = SDL_GetPixelFormatName(spec.format);
|
|
||||||
std::cout << "camera format: " << format_name << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
_thread = std::thread([this, interval](void) {
|
|
||||||
while (!_thread_should_quit) {
|
|
||||||
Uint64 timestampNS = 0;
|
|
||||||
SDL_Surface* sdl_frame_next = SDL_AcquireCameraFrame(_camera.get(), ×tampNS);
|
|
||||||
|
|
||||||
// no new frame yet, or error
|
|
||||||
if (sdl_frame_next == nullptr) {
|
|
||||||
// only sleep 1/10, we expected a frame
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(int64_t(interval*1000 / 10)));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
{ // test copy to trigger bug
|
|
||||||
SDL_Surface* test_surf = SDL_CreateSurface(
|
|
||||||
sdl_frame_next->w,
|
|
||||||
sdl_frame_next->h,
|
|
||||||
SDL_PIXELFORMAT_RGBA8888
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(test_surf != nullptr);
|
|
||||||
|
|
||||||
SDL_BlitSurface(sdl_frame_next, nullptr, test_surf, nullptr);
|
|
||||||
|
|
||||||
SDL_DestroySurface(test_surf);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool someone_listening {false};
|
|
||||||
{
|
|
||||||
SDLVideoFrame new_frame_non_owning {
|
|
||||||
timestampNS,
|
|
||||||
sdl_frame_next
|
|
||||||
};
|
|
||||||
|
|
||||||
// creates surface copies
|
|
||||||
someone_listening = push(new_frame_non_owning);
|
|
||||||
}
|
|
||||||
SDL_ReleaseCameraFrame(_camera.get(), sdl_frame_next);
|
|
||||||
|
|
||||||
if (someone_listening) {
|
|
||||||
// double the interval on acquire
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(int64_t(interval*1000*0.5)));
|
|
||||||
} else {
|
|
||||||
std::cerr << "i guess no one is listening\n";
|
|
||||||
// we just sleep 4x as long, bc no one is listening
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(int64_t(interval*1000*4)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLVideoCameraContent::~SDLVideoCameraContent(void) {
|
|
||||||
_thread_should_quit = true;
|
|
||||||
_thread.join();
|
|
||||||
// TODO: what to do if readers are still present?
|
|
||||||
|
|
||||||
// HACK: sdl is buggy and freezes otherwise. it is likely still possible, but rare to freeze here
|
|
||||||
// flush unused frames
|
|
||||||
#if 1
|
|
||||||
while (true) {
|
|
||||||
SDL_Surface* sdl_frame_next = SDL_AcquireCameraFrame(_camera.get(), nullptr);
|
|
||||||
if (sdl_frame_next != nullptr) {
|
|
||||||
SDL_ReleaseCameraFrame(_camera.get(), sdl_frame_next);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "./frame_stream2.hpp"
|
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
inline void nopSurfaceDestructor(SDL_Surface*) {}
|
|
||||||
|
|
||||||
// this is very sdl specific
|
|
||||||
struct SDLVideoFrame {
|
|
||||||
// TODO: sequence numbering?
|
|
||||||
uint64_t timestampNS {0};
|
|
||||||
|
|
||||||
std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> surface {nullptr, &SDL_DestroySurface};
|
|
||||||
|
|
||||||
// special non-owning constructor?
|
|
||||||
SDLVideoFrame(
|
|
||||||
uint64_t ts,
|
|
||||||
SDL_Surface* surf
|
|
||||||
) {
|
|
||||||
timestampNS = ts;
|
|
||||||
surface = {surf, &nopSurfaceDestructor};
|
|
||||||
}
|
|
||||||
// copy
|
|
||||||
SDLVideoFrame(const SDLVideoFrame& other) {
|
|
||||||
timestampNS = other.timestampNS;
|
|
||||||
if (static_cast<bool>(other.surface)) {
|
|
||||||
surface = {
|
|
||||||
SDL_CreateSurface(
|
|
||||||
other.surface->w,
|
|
||||||
other.surface->h,
|
|
||||||
SDL_PIXELFORMAT_RGBA8888 // meh
|
|
||||||
),
|
|
||||||
&SDL_DestroySurface
|
|
||||||
};
|
|
||||||
SDL_BlitSurface(other.surface.get(), nullptr, surface.get(), nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SDLVideoFrame& operator=(const SDLVideoFrame& other) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
using SDLVideoFrameStream2MultiStream = FrameStream2MultiStream<SDLVideoFrame>;
|
|
||||||
using SDLVideoFrameStream2 = SDLVideoFrameStream2MultiStream::sub_stream_type_t; // just use the default for now
|
|
||||||
|
|
||||||
struct SDLVideoCameraContent : protected SDLVideoFrameStream2MultiStream {
|
|
||||||
// meh, empty default
|
|
||||||
std::unique_ptr<SDL_Camera, decltype(&SDL_CloseCamera)> _camera {nullptr, &SDL_CloseCamera};
|
|
||||||
std::atomic<bool> _thread_should_quit {false};
|
|
||||||
std::thread _thread;
|
|
||||||
|
|
||||||
// construct source and start thread
|
|
||||||
// TODO: optimize so the thread is not always running
|
|
||||||
SDLVideoCameraContent(void);
|
|
||||||
|
|
||||||
// stops the thread and closes the camera
|
|
||||||
~SDLVideoCameraContent(void);
|
|
||||||
|
|
||||||
// make only some of writer public
|
|
||||||
using SDLVideoFrameStream2MultiStream::aquireSubStream;
|
|
||||||
using SDLVideoFrameStream2MultiStream::releaseSubStream;
|
|
||||||
};
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <solanaceae/util/span.hpp>
|
|
||||||
|
|
||||||
// most media that can be counted as "stream" comes in packets/frames/messages
|
|
||||||
// so this class provides an interface for ideal async fetching of frames
|
|
||||||
struct RawFrameStreamReaderI {
|
|
||||||
// return the number of ready frames in cache
|
|
||||||
// returns -1 if unknown
|
|
||||||
virtual int64_t have(void) = 0;
|
|
||||||
|
|
||||||
// get next frame, empty if none
|
|
||||||
virtual ByteSpan getNext(void) = 0;
|
|
||||||
};
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
|||||||
#include "./stream_reader_sdl_audio.hpp"
|
|
||||||
|
|
||||||
SDLAudioFrameStreamReader::SDLAudioFrameStreamReader(int32_t buffer_size) : _buffer_size(buffer_size), _stream{nullptr, &SDL_DestroyAudioStream} {
|
|
||||||
_buffer.resize(_buffer_size);
|
|
||||||
|
|
||||||
const SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, 48000 };
|
|
||||||
|
|
||||||
_stream = {
|
|
||||||
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_CAPTURE, &spec, nullptr, nullptr),
|
|
||||||
&SDL_DestroyAudioStream
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Span<int16_t> SDLAudioFrameStreamReader::getNextAudioFrame(void) {
|
|
||||||
const int32_t needed_bytes = (_buffer.size() - _remaining_size) * sizeof(int16_t);
|
|
||||||
const auto read_bytes = SDL_GetAudioStreamData(_stream.get(), _buffer.data()+_remaining_size, needed_bytes);
|
|
||||||
if (read_bytes < 0) {
|
|
||||||
// error
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (read_bytes < needed_bytes) {
|
|
||||||
// HACK: we are just assuming here that sdl never gives us half a sample!
|
|
||||||
_remaining_size += read_bytes / sizeof(int16_t);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
_remaining_size = 0;
|
|
||||||
return Span<int16_t>{_buffer};
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t SDLAudioFrameStreamReader::have(void) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteSpan SDLAudioFrameStreamReader::getNext(void) {
|
|
||||||
auto next_frame_span = getNextAudioFrame();
|
|
||||||
return ByteSpan{reinterpret_cast<const uint8_t*>(next_frame_span.ptr), next_frame_span.size};
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "./stream_reader.hpp"
|
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
struct SDLAudioFrameStreamReader : public RawFrameStreamReaderI {
|
|
||||||
// count samples per buffer
|
|
||||||
const int32_t _buffer_size {1024};
|
|
||||||
std::vector<int16_t> _buffer;
|
|
||||||
size_t _remaining_size {0}; // data still in buffer, that was remaining from last call and not enough to fill a full frame
|
|
||||||
|
|
||||||
// meh, empty default
|
|
||||||
std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream;
|
|
||||||
|
|
||||||
// buffer_size in number of samples
|
|
||||||
SDLAudioFrameStreamReader(int32_t buffer_size = 1024);
|
|
||||||
|
|
||||||
// data owned by StreamReader, overwritten by next call to getNext*()
|
|
||||||
Span<int16_t> getNextAudioFrame(void);
|
|
||||||
|
|
||||||
public: // interface
|
|
||||||
int64_t have(void) override;
|
|
||||||
|
|
||||||
ByteSpan getNext(void) override;
|
|
||||||
};
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
|||||||
#include "./stream_reader_sdl_video.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
SDLVideoFrameStreamReader::SDLVideoFrameStreamReader() : _camera{nullptr, &SDL_CloseCamera}, _surface{nullptr, &SDL_DestroySurface} {
|
|
||||||
// enumerate
|
|
||||||
int devcount = 0;
|
|
||||||
SDL_CameraDeviceID *devices = SDL_GetCameraDevices(&devcount);
|
|
||||||
|
|
||||||
if (devices == nullptr || devcount < 1) {
|
|
||||||
throw int(1); // TODO: proper exception?
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "### found cameras:\n";
|
|
||||||
for (int i = 0; i < devcount; i++) {
|
|
||||||
const SDL_CameraDeviceID device = devices[i];
|
|
||||||
char *name = SDL_GetCameraDeviceName(device);
|
|
||||||
|
|
||||||
std::cout << " - Camera #" << i << ": " << name << "\n";
|
|
||||||
|
|
||||||
SDL_free(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
_camera = {
|
|
||||||
SDL_OpenCameraDevice(devices[0], nullptr),
|
|
||||||
&SDL_CloseCamera
|
|
||||||
};
|
|
||||||
if (!static_cast<bool>(_camera)) {
|
|
||||||
throw int(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_CameraSpec spec;
|
|
||||||
if (SDL_GetCameraFormat(_camera.get(), &spec) < 0) {
|
|
||||||
// meh
|
|
||||||
} else {
|
|
||||||
// interval
|
|
||||||
float interval = float(spec.interval_numerator)/float(spec.interval_denominator);
|
|
||||||
std::cout << "camera interval: " << interval*1000 << "ms\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLVideoFrameStreamReader::VideoFrame SDLVideoFrameStreamReader::getNextVideoFrameRGBA(void) {
|
|
||||||
if (!static_cast<bool>(_camera)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint64 timestampNS = 0;
|
|
||||||
SDL_Surface* frame_next = SDL_AcquireCameraFrame(_camera.get(), ×tampNS);
|
|
||||||
|
|
||||||
// no new frame yet, or error
|
|
||||||
if (frame_next == nullptr) {
|
|
||||||
//std::cout << "failed acquiring frame\n";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: investigate zero copy
|
|
||||||
_surface = {
|
|
||||||
SDL_ConvertSurfaceFormat(frame_next, SDL_PIXELFORMAT_RGBA8888),
|
|
||||||
&SDL_DestroySurface
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_ReleaseCameraFrame(_camera.get(), frame_next);
|
|
||||||
|
|
||||||
SDL_LockSurface(_surface.get());
|
|
||||||
|
|
||||||
return {
|
|
||||||
_surface->w,
|
|
||||||
_surface->h,
|
|
||||||
timestampNS,
|
|
||||||
{
|
|
||||||
reinterpret_cast<const uint8_t*>(_surface->pixels),
|
|
||||||
uint64_t(_surface->w*_surface->h*4) // rgba
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t SDLVideoFrameStreamReader::have(void) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteSpan SDLVideoFrameStreamReader::getNext(void) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "./stream_reader.hpp"
|
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
struct SDLVideoFrameStreamReader : public RawFrameStreamReaderI {
|
|
||||||
// meh, empty default
|
|
||||||
std::unique_ptr<SDL_Camera, decltype(&SDL_CloseCamera)> _camera;
|
|
||||||
std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> _surface;
|
|
||||||
|
|
||||||
SDLVideoFrameStreamReader(void);
|
|
||||||
|
|
||||||
struct VideoFrame {
|
|
||||||
int32_t width {0};
|
|
||||||
int32_t height {0};
|
|
||||||
|
|
||||||
uint64_t timestampNS {0};
|
|
||||||
|
|
||||||
ByteSpan data;
|
|
||||||
};
|
|
||||||
|
|
||||||
// data owned by StreamReader, overwritten by next call to getNext*()
|
|
||||||
VideoFrame getNextVideoFrameRGBA(void);
|
|
||||||
|
|
||||||
public: // interface
|
|
||||||
int64_t have(void) override;
|
|
||||||
ByteSpan getNext(void) override;
|
|
||||||
};
|
|
||||||
|
|
@ -27,8 +27,6 @@ struct ImageLoaderI {
|
|||||||
|
|
||||||
// only positive values are valid
|
// only positive values are valid
|
||||||
ImageResult crop(int32_t c_x, int32_t c_y, int32_t c_w, int32_t c_h) const;
|
ImageResult crop(int32_t c_x, int32_t c_y, int32_t c_w, int32_t c_h) const;
|
||||||
|
|
||||||
// TODO: scale
|
|
||||||
};
|
};
|
||||||
virtual ImageResult loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) = 0;
|
virtual ImageResult loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) = 0;
|
||||||
};
|
};
|
||||||
|
@ -41,7 +41,7 @@ ImageLoaderQOI::ImageResult ImageLoaderQOI::loadFromMemoryRGBA(const uint8_t* da
|
|||||||
|
|
||||||
auto& new_frame = res.frames.emplace_back();
|
auto& new_frame = res.frames.emplace_back();
|
||||||
new_frame.ms = 0;
|
new_frame.ms = 0;
|
||||||
new_frame.data.insert(new_frame.data.cbegin(), img_data, img_data+(desc.width*desc.height*4));
|
new_frame.data = {img_data, img_data+(desc.width*desc.height*4)};
|
||||||
|
|
||||||
free(img_data);
|
free(img_data);
|
||||||
return res;
|
return res;
|
||||||
|
@ -47,7 +47,7 @@ ImageLoaderSDLBMP::ImageResult ImageLoaderSDLBMP::loadFromMemoryRGBA(const uint8
|
|||||||
|
|
||||||
auto& new_frame = res.frames.emplace_back();
|
auto& new_frame = res.frames.emplace_back();
|
||||||
new_frame.ms = 0;
|
new_frame.ms = 0;
|
||||||
new_frame.data.insert(new_frame.data.cbegin(), (const uint8_t*)conv_surf->pixels, ((const uint8_t*)conv_surf->pixels) + (surf->w*surf->h*4));
|
new_frame.data = {(const uint8_t*)conv_surf->pixels, ((const uint8_t*)conv_surf->pixels) + (surf->w*surf->h*4)};
|
||||||
|
|
||||||
SDL_UnlockSurface(conv_surf);
|
SDL_UnlockSurface(conv_surf);
|
||||||
SDL_DestroySurface(conv_surf);
|
SDL_DestroySurface(conv_surf);
|
||||||
|
@ -99,7 +99,7 @@ ImageLoaderSDLImage::ImageResult ImageLoaderSDLImage::loadFromMemoryRGBA(const u
|
|||||||
|
|
||||||
auto& new_frame = res.frames.emplace_back();
|
auto& new_frame = res.frames.emplace_back();
|
||||||
new_frame.ms = anim->delays[i];
|
new_frame.ms = anim->delays[i];
|
||||||
new_frame.data.insert(new_frame.data.cbegin(), (const uint8_t*)conv_surf->pixels, ((const uint8_t*)conv_surf->pixels) + (anim->w*anim->h*4));
|
new_frame.data = {(const uint8_t*)conv_surf->pixels, ((const uint8_t*)conv_surf->pixels) + (anim->w*anim->h*4)};
|
||||||
|
|
||||||
SDL_UnlockSurface(conv_surf);
|
SDL_UnlockSurface(conv_surf);
|
||||||
SDL_DestroySurface(conv_surf);
|
SDL_DestroySurface(conv_surf);
|
||||||
|
@ -41,7 +41,7 @@ ImageLoaderSTB::ImageResult ImageLoaderSTB::loadFromMemoryRGBA(const uint8_t* da
|
|||||||
for (int i = 0; i < z; i++) {
|
for (int i = 0; i < z; i++) {
|
||||||
auto& new_frame = res.frames.emplace_back();
|
auto& new_frame = res.frames.emplace_back();
|
||||||
new_frame.ms = delays[i];
|
new_frame.ms = delays[i];
|
||||||
new_frame.data.insert(new_frame.data.cbegin(), img_data + (i*stride), img_data + ((i+1)*stride));
|
new_frame.data = {img_data + (i*stride), img_data + ((i+1)*stride)};
|
||||||
}
|
}
|
||||||
|
|
||||||
stbi_image_free(delays); // hope this is right
|
stbi_image_free(delays); // hope this is right
|
||||||
@ -62,7 +62,7 @@ ImageLoaderSTB::ImageResult ImageLoaderSTB::loadFromMemoryRGBA(const uint8_t* da
|
|||||||
|
|
||||||
auto& new_frame = res.frames.emplace_back();
|
auto& new_frame = res.frames.emplace_back();
|
||||||
new_frame.ms = 0;
|
new_frame.ms = 0;
|
||||||
new_frame.data.insert(new_frame.data.cbegin(), img_data, img_data+(x*y*4));
|
new_frame.data = {img_data, img_data+(x*y*4)};
|
||||||
|
|
||||||
stbi_image_free(img_data);
|
stbi_image_free(img_data);
|
||||||
return res;
|
return res;
|
||||||
|
@ -78,7 +78,7 @@ ImageLoaderWebP::ImageResult ImageLoaderWebP::loadFromMemoryRGBA(const uint8_t*
|
|||||||
auto& new_frame = res.frames.emplace_back();
|
auto& new_frame = res.frames.emplace_back();
|
||||||
new_frame.ms = timestamp-prev_timestamp;
|
new_frame.ms = timestamp-prev_timestamp;
|
||||||
prev_timestamp = timestamp;
|
prev_timestamp = timestamp;
|
||||||
new_frame.data.insert(new_frame.data.end(), buf, buf+(res.width*res.height*4));
|
new_frame.data = {buf, buf+(res.width*res.height*4)};
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(anim_info.frame_count == res.frames.size());
|
assert(anim_info.frame_count == res.frames.size());
|
||||||
|
@ -1,319 +0,0 @@
|
|||||||
// for the license, see the end of the file
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "entt/entity/fwd.hpp"
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <entt/entt.hpp>
|
|
||||||
#include <imgui.h>
|
|
||||||
|
|
||||||
#ifndef MM_IEEE_ASSERT
|
|
||||||
#define MM_IEEE_ASSERT(x) assert(x)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY "MM_IEEE_ENTITY"
|
|
||||||
|
|
||||||
#ifndef MM_IEEE_ENTITY_WIDGET
|
|
||||||
#define MM_IEEE_ENTITY_WIDGET ::MM::EntityWidget
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace MM {
|
|
||||||
|
|
||||||
template <class EntityType>
|
|
||||||
inline void EntityWidget(EntityType& e, entt::basic_registry<EntityType>& reg, bool dropTarget = false)
|
|
||||||
{
|
|
||||||
ImGui::PushID(static_cast<int>(entt::to_integral(e)));
|
|
||||||
|
|
||||||
if (reg.valid(e)) {
|
|
||||||
ImGui::Text("ID: %d", entt::to_integral(e));
|
|
||||||
} else {
|
|
||||||
ImGui::Text("Invalid Entity");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reg.valid(e)) {
|
|
||||||
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
|
|
||||||
ImGui::SetDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY, &e, sizeof(e));
|
|
||||||
ImGui::Text("ID: %d", entt::to_integral(e));
|
|
||||||
ImGui::EndDragDropSource();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dropTarget && ImGui::BeginDragDropTarget()) {
|
|
||||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY)) {
|
|
||||||
e = *(EntityType*)payload->Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndDragDropTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Component, class EntityType>
|
|
||||||
void ComponentEditorWidget([[maybe_unused]] entt::basic_registry<EntityType>& registry, [[maybe_unused]] EntityType entity) {}
|
|
||||||
|
|
||||||
template <class Component, class EntityType>
|
|
||||||
void ComponentAddAction(entt::basic_registry<EntityType>& registry, EntityType entity)
|
|
||||||
{
|
|
||||||
registry.template emplace<Component>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Component, class EntityType>
|
|
||||||
void ComponentRemoveAction(entt::basic_registry<EntityType>& registry, EntityType entity)
|
|
||||||
{
|
|
||||||
registry.template remove<Component>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class EntityType>
|
|
||||||
class EntityEditor {
|
|
||||||
public:
|
|
||||||
using Registry = entt::basic_registry<EntityType>;
|
|
||||||
using ComponentTypeID = entt::id_type;
|
|
||||||
|
|
||||||
struct ComponentInfo {
|
|
||||||
using Callback = std::function<void(Registry&, EntityType)>;
|
|
||||||
std::string name;
|
|
||||||
Callback widget, create, destroy;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool show_window = true;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::map<ComponentTypeID, ComponentInfo> component_infos;
|
|
||||||
|
|
||||||
bool entityHasComponent(Registry& registry, EntityType& entity, ComponentTypeID type_id)
|
|
||||||
{
|
|
||||||
const auto* storage_ptr = registry.storage(type_id);
|
|
||||||
return storage_ptr != nullptr && storage_ptr->contains(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
template <class Component>
|
|
||||||
ComponentInfo& registerComponent(const ComponentInfo& component_info)
|
|
||||||
{
|
|
||||||
auto index = entt::type_hash<Component>::value();
|
|
||||||
auto insert_info = component_infos.insert_or_assign(index, component_info);
|
|
||||||
MM_IEEE_ASSERT(insert_info.second);
|
|
||||||
return std::get<ComponentInfo>(*insert_info.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Component>
|
|
||||||
ComponentInfo& registerComponent(const std::string& name, typename ComponentInfo::Callback widget)
|
|
||||||
{
|
|
||||||
return registerComponent<Component>(ComponentInfo{
|
|
||||||
name,
|
|
||||||
widget,
|
|
||||||
ComponentAddAction<Component, EntityType>,
|
|
||||||
ComponentRemoveAction<Component, EntityType>,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Component>
|
|
||||||
ComponentInfo& registerComponent(const std::string& name)
|
|
||||||
{
|
|
||||||
return registerComponent<Component>(name, ComponentEditorWidget<Component, EntityType>);
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderEditor(Registry& registry, EntityType& e)
|
|
||||||
{
|
|
||||||
ImGui::TextUnformatted("Editing:");
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
MM_IEEE_ENTITY_WIDGET(e, registry, true);
|
|
||||||
|
|
||||||
if (ImGui::Button("New")) {
|
|
||||||
e = registry.create();
|
|
||||||
}
|
|
||||||
if (registry.valid(e)) {
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
if (ImGui::Button("Clone")) {
|
|
||||||
auto old_e = e;
|
|
||||||
e = registry.create();
|
|
||||||
|
|
||||||
// create a copy of an entity component by component
|
|
||||||
for (auto &&curr: registry.storage()) {
|
|
||||||
if (auto &storage = curr.second; storage.contains(old_e)) {
|
|
||||||
// TODO: do something with the return value. returns false on failure.
|
|
||||||
storage.push(e, storage.value(old_e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
ImGui::Dummy({10, 0}); // space destroy a bit, to not accidentally click it
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
// red button
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.65f, 0.15f, 0.15f, 1.f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.8f, 0.3f, 0.3f, 1.f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.f, 0.2f, 0.2f, 1.f));
|
|
||||||
if (ImGui::Button("Destroy")) {
|
|
||||||
registry.destroy(e);
|
|
||||||
e = entt::null;
|
|
||||||
}
|
|
||||||
ImGui::PopStyleColor(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
if (registry.valid(e)) {
|
|
||||||
ImGui::PushID(static_cast<int>(entt::to_integral(e)));
|
|
||||||
std::map<ComponentTypeID, ComponentInfo> has_not;
|
|
||||||
for (auto& [component_type_id, ci] : component_infos) {
|
|
||||||
if (entityHasComponent(registry, e, component_type_id)) {
|
|
||||||
ImGui::PushID(component_type_id);
|
|
||||||
if (ImGui::Button("-")) {
|
|
||||||
ci.destroy(registry, e);
|
|
||||||
ImGui::PopID();
|
|
||||||
continue; // early out to prevent access to deleted data
|
|
||||||
} else {
|
|
||||||
ImGui::SameLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader(ci.name.c_str())) {
|
|
||||||
ImGui::Indent(30.f);
|
|
||||||
ImGui::PushID("Widget");
|
|
||||||
ci.widget(registry, e);
|
|
||||||
ImGui::PopID();
|
|
||||||
ImGui::Unindent(30.f);
|
|
||||||
}
|
|
||||||
ImGui::PopID();
|
|
||||||
} else {
|
|
||||||
has_not[component_type_id] = ci;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!has_not.empty()) {
|
|
||||||
if (ImGui::Button("+ Add Component")) {
|
|
||||||
ImGui::OpenPopup("Add Component");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginPopup("Add Component")) {
|
|
||||||
ImGui::TextUnformatted("Available:");
|
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
for (auto& [component_type_id, ci] : has_not) {
|
|
||||||
ImGui::PushID(component_type_id);
|
|
||||||
if (ImGui::Selectable(ci.name.c_str())) {
|
|
||||||
ci.create(registry, e);
|
|
||||||
}
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderEntityList(Registry& registry, std::set<ComponentTypeID>& comp_list)
|
|
||||||
{
|
|
||||||
ImGui::Text("Components Filter:");
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::SmallButton("clear")) {
|
|
||||||
comp_list.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Indent();
|
|
||||||
|
|
||||||
for (const auto& [component_type_id, ci] : component_infos) {
|
|
||||||
bool is_in_list = comp_list.count(component_type_id);
|
|
||||||
bool active = is_in_list;
|
|
||||||
|
|
||||||
ImGui::Checkbox(ci.name.c_str(), &active);
|
|
||||||
|
|
||||||
if (is_in_list && !active) { // remove
|
|
||||||
comp_list.erase(component_type_id);
|
|
||||||
} else if (!is_in_list && active) { // add
|
|
||||||
comp_list.emplace(component_type_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Unindent();
|
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
if (comp_list.empty()) {
|
|
||||||
ImGui::Text("Orphans:");
|
|
||||||
for (EntityType e : registry.template storage<EntityType>()) {
|
|
||||||
if (registry.orphan(e)) {
|
|
||||||
MM_IEEE_ENTITY_WIDGET(e, registry, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entt::basic_runtime_view<entt::basic_sparse_set<EntityType>> view{};
|
|
||||||
for (const auto type : comp_list) {
|
|
||||||
auto* storage_ptr = registry.storage(type);
|
|
||||||
if (storage_ptr != nullptr) {
|
|
||||||
view.iterate(*storage_ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add support for exclude
|
|
||||||
|
|
||||||
ImGui::Text("%lu Entities Matching:", view.size_hint());
|
|
||||||
|
|
||||||
if (ImGui::BeginChild("entity list")) {
|
|
||||||
for (auto e : view) {
|
|
||||||
MM_IEEE_ENTITY_WIDGET(e, registry, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// displays both, editor and list
|
|
||||||
// uses static internally, use only as a quick way to get going!
|
|
||||||
void renderSimpleCombo(Registry& registry, EntityType& e)
|
|
||||||
{
|
|
||||||
if (show_window) {
|
|
||||||
ImGui::SetNextWindowSize(ImVec2(550, 400), ImGuiCond_FirstUseEver);
|
|
||||||
if (ImGui::Begin("Entity Editor", &show_window)) {
|
|
||||||
if (ImGui::BeginChild("list", {200, 0}, true)) {
|
|
||||||
static std::set<ComponentTypeID> comp_list;
|
|
||||||
renderEntityList(registry, comp_list);
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
if (ImGui::BeginChild("editor")) {
|
|
||||||
renderEditor(registry, e);
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
} // MM
|
|
||||||
|
|
||||||
// MIT License
|
|
||||||
|
|
||||||
// Copyright (c) 2019-2022 Erik Scholz
|
|
||||||
// Copyright (c) 2020 Gnik Droy
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
67
src/json_to_config.cpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#include "./json_to_config.hpp"
|
||||||
|
|
||||||
|
#include <solanaceae/util/simple_config_model.hpp>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
bool load_json_into_config(const nlohmann::ordered_json& config_json, SimpleConfigModel& conf) {
|
||||||
|
if (!config_json.is_object()) {
|
||||||
|
std::cerr << "TOMATO error: config file is not an json object!!!\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto& [mod, cats] : config_json.items()) {
|
||||||
|
for (const auto& [cat, cat_v] : cats.items()) {
|
||||||
|
if (cat_v.is_object()) {
|
||||||
|
if (cat_v.contains("default")) {
|
||||||
|
const auto& value = cat_v["default"];
|
||||||
|
if (value.is_string()) {
|
||||||
|
conf.set(mod, cat, value.get_ref<const std::string&>());
|
||||||
|
} else if (value.is_boolean()) {
|
||||||
|
conf.set(mod, cat, value.get_ref<const bool&>());
|
||||||
|
} else if (value.is_number_float()) {
|
||||||
|
conf.set(mod, cat, value.get_ref<const double&>());
|
||||||
|
} else if (value.is_number_integer()) {
|
||||||
|
conf.set(mod, cat, value.get_ref<const int64_t&>());
|
||||||
|
} else {
|
||||||
|
std::cerr << "JSON error: wrong value type in " << mod << "::" << cat << " = " << value << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cat_v.contains("entries")) {
|
||||||
|
for (const auto& [ent, ent_v] : cat_v["entries"].items()) {
|
||||||
|
if (ent_v.is_string()) {
|
||||||
|
conf.set(mod, cat, ent, ent_v.get_ref<const std::string&>());
|
||||||
|
} else if (ent_v.is_boolean()) {
|
||||||
|
conf.set(mod, cat, ent, ent_v.get_ref<const bool&>());
|
||||||
|
} else if (ent_v.is_number_float()) {
|
||||||
|
conf.set(mod, cat, ent, ent_v.get_ref<const double&>());
|
||||||
|
} else if (ent_v.is_number_integer()) {
|
||||||
|
conf.set(mod, cat, ent, ent_v.get_ref<const int64_t&>());
|
||||||
|
} else {
|
||||||
|
std::cerr << "JSON error: wrong value type in " << mod << "::" << cat << "::" << ent << " = " << ent_v << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (cat_v.is_string()) {
|
||||||
|
conf.set(mod, cat, cat_v.get_ref<const std::string&>());
|
||||||
|
} else if (cat_v.is_boolean()) {
|
||||||
|
conf.set(mod, cat, cat_v.get_ref<const bool&>());
|
||||||
|
} else if (cat_v.is_number_float()) {
|
||||||
|
conf.set(mod, cat, cat_v.get_ref<const double&>());
|
||||||
|
} else if (cat_v.is_number_integer()) {
|
||||||
|
conf.set(mod, cat, cat_v.get_ref<const int64_t&>());
|
||||||
|
} else {
|
||||||
|
std::cerr << "JSON error: wrong value type in " << mod << "::" << cat << " = " << cat_v << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
9
src/json_to_config.hpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <nlohmann/json_fwd.hpp>
|
||||||
|
|
||||||
|
// fwd
|
||||||
|
struct SimpleConfigModel;
|
||||||
|
|
||||||
|
bool load_json_into_config(const nlohmann::ordered_json& config_json, SimpleConfigModel& conf);
|
||||||
|
|
69
src/main.cpp
@ -9,19 +9,32 @@
|
|||||||
#include "./chat_gui/theme.hpp"
|
#include "./chat_gui/theme.hpp"
|
||||||
|
|
||||||
#include "./start_screen.hpp"
|
#include "./start_screen.hpp"
|
||||||
#include "./content/sdl_audio_frame_stream2.hpp"
|
|
||||||
#include "./content/sdl_video_frame_stream2.hpp"
|
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string_view>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
|
// better args
|
||||||
|
std::vector<std::string_view> args;
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
args.push_back(argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
// change current working dir to internal storage
|
||||||
|
std::filesystem::current_path(SDL_AndroidGetInternalStoragePath());
|
||||||
|
#endif
|
||||||
|
|
||||||
// setup hints
|
// setup hints
|
||||||
|
#ifndef __ANDROID__
|
||||||
if (SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1") != SDL_TRUE) {
|
if (SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1") != SDL_TRUE) {
|
||||||
std::cerr << "Failed to set '" << SDL_HINT_VIDEO_ALLOW_SCREENSAVER << "' to 1\n";
|
std::cerr << "Failed to set '" << SDL_HINT_VIDEO_ALLOW_SCREENSAVER << "' to 1\n";
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
auto last_time_render = std::chrono::steady_clock::now();
|
auto last_time_render = std::chrono::steady_clock::now();
|
||||||
auto last_time_tick = std::chrono::steady_clock::now();
|
auto last_time_tick = std::chrono::steady_clock::now();
|
||||||
@ -60,46 +73,6 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// optionally init audio and camera
|
|
||||||
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
|
|
||||||
std::cerr << "SDL_Init AUDIO failed (" << SDL_GetError() << ")\n";
|
|
||||||
} else if (false) {
|
|
||||||
SDLAudioInputDeviceDefault aidd;
|
|
||||||
auto* reader = aidd.aquireSubStream();
|
|
||||||
|
|
||||||
auto writer = SDLAudioOutputDeviceDefaultFactory{}.create();
|
|
||||||
|
|
||||||
for (size_t i = 0; i < 20; i++) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
||||||
auto new_frame_opt = reader->pop();
|
|
||||||
if (new_frame_opt.has_value()) {
|
|
||||||
std::cout << "audio frame was seq:" << new_frame_opt.value().seq << " sr:" << new_frame_opt.value().sample_rate << " " << (new_frame_opt.value().isS16()?"S16":"F32") << " l:" << (new_frame_opt.value().isS16()?new_frame_opt.value().getSpan<int16_t>().size:new_frame_opt.value().getSpan<float>().size) << "\n";
|
|
||||||
writer.push(new_frame_opt.value());
|
|
||||||
} else {
|
|
||||||
std::cout << "no audio frame\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
aidd.releaseSubStream(reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SDL_Init(SDL_INIT_CAMERA) < 0) {
|
|
||||||
std::cerr << "SDL_Init CAMERA failed (" << SDL_GetError() << ")\n";
|
|
||||||
} else if (false) { // HACK
|
|
||||||
std::cerr << "CAMERA initialized\n";
|
|
||||||
SDLVideoCameraContent vcc;
|
|
||||||
auto* reader = vcc.aquireSubStream();
|
|
||||||
for (size_t i = 0; i < 20; i++) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
||||||
auto new_frame_opt = reader->pop();
|
|
||||||
if (new_frame_opt.has_value()) {
|
|
||||||
std::cout << "video frame was " << new_frame_opt.value().surface->w << "x" << new_frame_opt.value().surface->h << " " << new_frame_opt.value().timestampNS << "ns\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vcc.releaseSubStream(reader);
|
|
||||||
}
|
|
||||||
std::cout << "after sdl video stuffery\n";
|
|
||||||
|
|
||||||
IMGUI_CHECKVERSION();
|
IMGUI_CHECKVERSION();
|
||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
|
|
||||||
@ -133,9 +106,9 @@ int main(int argc, char** argv) {
|
|||||||
ImGui_ImplSDL3_InitForSDLRenderer(window.get(), renderer.get());
|
ImGui_ImplSDL3_InitForSDLRenderer(window.get(), renderer.get());
|
||||||
ImGui_ImplSDLRenderer3_Init(renderer.get());
|
ImGui_ImplSDLRenderer3_Init(renderer.get());
|
||||||
|
|
||||||
std::unique_ptr<Screen> screen = std::make_unique<StartScreen>(renderer.get(), theme);
|
std::unique_ptr<Screen> screen = std::make_unique<StartScreen>(args, renderer.get(), theme);
|
||||||
|
|
||||||
|
|
||||||
|
bool is_background = false;
|
||||||
bool quit = false;
|
bool quit = false;
|
||||||
while (!quit) {
|
while (!quit) {
|
||||||
auto new_time = std::chrono::steady_clock::now();
|
auto new_time = std::chrono::steady_clock::now();
|
||||||
@ -160,6 +133,10 @@ int main(int argc, char** argv) {
|
|||||||
if (event.type == SDL_EVENT_QUIT) {
|
if (event.type == SDL_EVENT_QUIT) {
|
||||||
quit = true;
|
quit = true;
|
||||||
break;
|
break;
|
||||||
|
} else if (event.type == SDL_EVENT_WILL_ENTER_BACKGROUND) {
|
||||||
|
is_background = true;
|
||||||
|
} else if (event.type == SDL_EVENT_DID_ENTER_FOREGROUND) {
|
||||||
|
is_background = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screen->handleEvent(event)) {
|
if (screen->handleEvent(event)) {
|
||||||
@ -172,6 +149,10 @@ int main(int argc, char** argv) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_background) {
|
||||||
|
render = false;
|
||||||
|
}
|
||||||
|
|
||||||
// can do both in the same loop
|
// can do both in the same loop
|
||||||
if (render) {
|
if (render) {
|
||||||
ImGui_ImplSDLRenderer3_NewFrame();
|
ImGui_ImplSDLRenderer3_NewFrame();
|
||||||
|
@ -12,15 +12,15 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
MainScreen::MainScreen(SDL_Renderer* renderer_, Theme& theme_, std::string save_path, std::string save_password, std::string new_username, std::vector<std::string> plugins) :
|
MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme& theme_, std::string save_path, std::string save_password, std::string new_username, std::vector<std::string> plugins) :
|
||||||
renderer(renderer_),
|
renderer(renderer_),
|
||||||
|
conf(std::move(conf_)),
|
||||||
rmm(cr),
|
rmm(cr),
|
||||||
msnj{cr, {}, {}},
|
msnj{cr, {}, {}},
|
||||||
mts(rmm),
|
mts(rmm),
|
||||||
tc(save_path, save_password),
|
tc(save_path, save_password),
|
||||||
tpi(tc.getTox()),
|
tpi(tc.getTox()),
|
||||||
ad(tc),
|
ad(tc),
|
||||||
tav(tc.getTox()),
|
|
||||||
tcm(cr, tc, tc),
|
tcm(cr, tc, tc),
|
||||||
tmm(rmm, cr, tcm, tc, tc),
|
tmm(rmm, cr, tcm, tc, tc),
|
||||||
ttm(rmm, cr, tcm, tc, tc),
|
ttm(rmm, cr, tcm, tc, tc),
|
||||||
@ -35,7 +35,6 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, Theme& theme_, std::string save_
|
|||||||
msg_tc(mil, sdlrtu),
|
msg_tc(mil, sdlrtu),
|
||||||
cg(conf, rmm, cr, sdlrtu, contact_tc, msg_tc, theme),
|
cg(conf, rmm, cr, sdlrtu, contact_tc, msg_tc, theme),
|
||||||
sw(conf),
|
sw(conf),
|
||||||
osui(os),
|
|
||||||
tuiu(tc, conf),
|
tuiu(tc, conf),
|
||||||
tdch(tpi)
|
tdch(tpi)
|
||||||
{
|
{
|
||||||
@ -70,7 +69,6 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, Theme& theme_, std::string save_
|
|||||||
g_provideInstance<ToxI>("ToxI", "host", &tc);
|
g_provideInstance<ToxI>("ToxI", "host", &tc);
|
||||||
g_provideInstance<ToxPrivateI>("ToxPrivateI", "host", &tpi);
|
g_provideInstance<ToxPrivateI>("ToxPrivateI", "host", &tpi);
|
||||||
g_provideInstance<ToxEventProviderI>("ToxEventProviderI", "host", &tc);
|
g_provideInstance<ToxEventProviderI>("ToxEventProviderI", "host", &tc);
|
||||||
g_provideInstance<ToxAV>("ToxAV", "host", &tav);
|
|
||||||
g_provideInstance<ToxContactModel2>("ToxContactModel2", "host", &tcm);
|
g_provideInstance<ToxContactModel2>("ToxContactModel2", "host", &tcm);
|
||||||
|
|
||||||
// TODO: pm?
|
// TODO: pm?
|
||||||
@ -247,7 +245,6 @@ Screen* MainScreen::render(float time_delta, bool&) {
|
|||||||
|
|
||||||
const float cg_interval = cg.render(time_delta); // render
|
const float cg_interval = cg.render(time_delta); // render
|
||||||
sw.render(); // render
|
sw.render(); // render
|
||||||
osui.render();
|
|
||||||
tuiu.render(); // render
|
tuiu.render(); // render
|
||||||
tdch.render(); // render
|
tdch.render(); // render
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
#include <solanaceae/plugin/plugin_manager.hpp>
|
#include <solanaceae/plugin/plugin_manager.hpp>
|
||||||
#include <solanaceae/toxcore/tox_event_logger.hpp>
|
#include <solanaceae/toxcore/tox_event_logger.hpp>
|
||||||
#include "./tox_private_impl.hpp"
|
#include "./tox_private_impl.hpp"
|
||||||
#include "./tox_av.hpp"
|
|
||||||
|
|
||||||
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
|
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
|
||||||
#include <solanaceae/tox_messages/tox_message_manager.hpp>
|
#include <solanaceae/tox_messages/tox_message_manager.hpp>
|
||||||
@ -30,7 +29,6 @@
|
|||||||
|
|
||||||
#include "./chat_gui4.hpp"
|
#include "./chat_gui4.hpp"
|
||||||
#include "./chat_gui/settings_window.hpp"
|
#include "./chat_gui/settings_window.hpp"
|
||||||
#include "./object_store_ui.hpp"
|
|
||||||
#include "./tox_ui_utils.hpp"
|
#include "./tox_ui_utils.hpp"
|
||||||
#include "./tox_dht_cap_histo.hpp"
|
#include "./tox_dht_cap_histo.hpp"
|
||||||
#include "./tox_friend_faux_offline_messaging.hpp"
|
#include "./tox_friend_faux_offline_messaging.hpp"
|
||||||
@ -59,7 +57,6 @@ struct MainScreen final : public Screen {
|
|||||||
ToxClient tc;
|
ToxClient tc;
|
||||||
ToxPrivateImpl tpi;
|
ToxPrivateImpl tpi;
|
||||||
AutoDirty ad;
|
AutoDirty ad;
|
||||||
ToxAV tav;
|
|
||||||
ToxContactModel2 tcm;
|
ToxContactModel2 tcm;
|
||||||
ToxMessageManager tmm;
|
ToxMessageManager tmm;
|
||||||
ToxTransferManager ttm;
|
ToxTransferManager ttm;
|
||||||
@ -80,7 +77,6 @@ struct MainScreen final : public Screen {
|
|||||||
|
|
||||||
ChatGui4 cg;
|
ChatGui4 cg;
|
||||||
SettingsWindow sw;
|
SettingsWindow sw;
|
||||||
ObjectStoreUI osui;
|
|
||||||
ToxUIUtils tuiu;
|
ToxUIUtils tuiu;
|
||||||
ToxDHTCapHisto tdch;
|
ToxDHTCapHisto tdch;
|
||||||
|
|
||||||
@ -95,7 +91,7 @@ struct MainScreen final : public Screen {
|
|||||||
uint64_t _window_hidden_ts {0};
|
uint64_t _window_hidden_ts {0};
|
||||||
float _time_since_event {0.f};
|
float _time_since_event {0.f};
|
||||||
|
|
||||||
MainScreen(SDL_Renderer* renderer_, Theme& theme_, std::string save_path, std::string save_password, std::string new_username, std::vector<std::string> plugins);
|
MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme& theme_, std::string save_path, std::string save_password, std::string new_username, std::vector<std::string> plugins);
|
||||||
~MainScreen(void);
|
~MainScreen(void);
|
||||||
|
|
||||||
bool handleEvent(SDL_Event& e) override;
|
bool handleEvent(SDL_Event& e) override;
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
#include "./object_store_ui.hpp"
|
|
||||||
|
|
||||||
#include <solanaceae/object_store/meta_components.hpp>
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include <solanaceae/message3/components.hpp>
|
|
||||||
|
|
||||||
ObjectStoreUI::ObjectStoreUI(
|
|
||||||
ObjectStore2& os
|
|
||||||
) : _os(os) {
|
|
||||||
_ee.show_window = false;
|
|
||||||
|
|
||||||
_ee.registerComponent<ObjectStore::Components::ID>("ID");
|
|
||||||
_ee.registerComponent<ObjectStore::Components::DataCompressionType>("DataCompressionType");
|
|
||||||
|
|
||||||
_ee.registerComponent<Message::Components::Transfer::FileInfo>("Transfer::FileInfo");
|
|
||||||
_ee.registerComponent<Message::Components::Transfer::FileInfoLocal>("Transfer::FileInfoLocal");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectStoreUI::render(void) {
|
|
||||||
{ // main window menubar injection
|
|
||||||
// assumes the window "tomato" was rendered already by cg
|
|
||||||
if (ImGui::Begin("tomato")) {
|
|
||||||
if (ImGui::BeginMenuBar()) {
|
|
||||||
ImGui::Separator();
|
|
||||||
if (ImGui::BeginMenu("ObjectStore")) {
|
|
||||||
if (ImGui::MenuItem("Inspector")) {
|
|
||||||
_ee.show_window = true;
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
ImGui::EndMenuBar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Object selected_ent {entt::null};
|
|
||||||
_ee.renderSimpleCombo(_os.registry(), selected_ent);
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <solanaceae/object_store/object_store.hpp>
|
|
||||||
|
|
||||||
#include "./imgui_entt_entity_editor.hpp"
|
|
||||||
|
|
||||||
class ObjectStoreUI {
|
|
||||||
ObjectStore2& _os;
|
|
||||||
|
|
||||||
MM::EntityEditor<Object> _ee;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ObjectStoreUI(
|
|
||||||
ObjectStore2& os
|
|
||||||
);
|
|
||||||
|
|
||||||
void render(void);
|
|
||||||
};
|
|
||||||
|
|
@ -31,6 +31,11 @@ uint64_t SDLRendererTextureUploader::uploadRGBA(const uint8_t* data, uint32_t wi
|
|||||||
// TODO: error reporting
|
// TODO: error reporting
|
||||||
SDL_UpdateTexture(tex, nullptr, surf->pixels, surf->pitch);
|
SDL_UpdateTexture(tex, nullptr, surf->pixels, surf->pitch);
|
||||||
|
|
||||||
|
SDL_BlendMode surf_blend_mode = SDL_BLENDMODE_NONE;
|
||||||
|
if (SDL_GetSurfaceBlendMode(surf, &surf_blend_mode) == 0) {
|
||||||
|
SDL_SetTextureBlendMode(tex, surf_blend_mode);
|
||||||
|
}
|
||||||
|
|
||||||
if (filter == NEAREST) {
|
if (filter == NEAREST) {
|
||||||
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_NEAREST);
|
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_NEAREST);
|
||||||
} else if (filter == LINEAR) {
|
} else if (filter == LINEAR) {
|
||||||
|
@ -2,14 +2,54 @@
|
|||||||
|
|
||||||
#include "./main_screen.hpp"
|
#include "./main_screen.hpp"
|
||||||
|
|
||||||
|
#include "./json_to_config.hpp"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include <imgui/imgui.h>
|
||||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||||
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
StartScreen::StartScreen(const std::vector<std::string_view>& args, SDL_Renderer* renderer, Theme& theme) : _renderer(renderer), _theme(theme) {
|
||||||
|
for (size_t ai = 1; ai < args.size(); ai++) {
|
||||||
|
if (args.at(ai) == "--config" || args.at(ai) == "-c") {
|
||||||
|
if (args.size() == ai+1) {
|
||||||
|
std::cerr << "TOMATO error: argument '" << args.at(ai) << "' missing parameter!\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ai++;
|
||||||
|
|
||||||
|
const auto& config_path = args.at(ai);
|
||||||
|
auto config_file = std::ifstream(static_cast<std::string>(config_path));
|
||||||
|
if (!config_file.is_open()) {
|
||||||
|
std::cerr << "TOMATO error: failed to open config file '" << config_path << "'\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto config_json = nlohmann::ordered_json::parse(config_file);
|
||||||
|
if (!load_json_into_config(config_json, _conf)) {
|
||||||
|
std::cerr << "TOMATO error in config json, exiting...\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (args.at(ai) == "--plugin" || args.at(ai) == "-p") {
|
||||||
|
if (args.size() == ai+1) {
|
||||||
|
std::cerr << "TOMATO error: argument '" << args.at(ai) << "' missing parameter!\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ai++;
|
||||||
|
|
||||||
|
const auto& plugin_path = args.at(ai);
|
||||||
|
// TODO: check for dups
|
||||||
|
queued_plugin_paths.push_back(static_cast<std::string>(plugin_path));
|
||||||
|
} else {
|
||||||
|
std::cerr << "TOMATO error: unknown cli arg: '" << args.at(ai) << "'\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StartScreen::StartScreen(SDL_Renderer* renderer, Theme& theme) : _renderer(renderer), _theme(theme) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Screen* StartScreen::render(float, bool&) {
|
Screen* StartScreen::render(float, bool&) {
|
||||||
@ -143,7 +183,8 @@ Screen* StartScreen::render(float, bool&) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ImGui::Button("load", {60, 25})) {
|
if (ImGui::Button("load", {60, 25})) {
|
||||||
auto new_screen = std::make_unique<MainScreen>(_renderer, _theme, _tox_profile_path, _password, _user_name, queued_plugin_paths);
|
|
||||||
|
auto new_screen = std::make_unique<MainScreen>(std::move(_conf), _renderer, _theme, _tox_profile_path, _password, _user_name, queued_plugin_paths);
|
||||||
return new_screen.release();
|
return new_screen.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
#include "./chat_gui/theme.hpp"
|
#include "./chat_gui/theme.hpp"
|
||||||
#include "./chat_gui/file_selector.hpp"
|
#include "./chat_gui/file_selector.hpp"
|
||||||
|
|
||||||
|
#include <solanaceae/util/simple_config_model.hpp>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -16,6 +18,7 @@ extern "C" {
|
|||||||
struct StartScreen final : public Screen {
|
struct StartScreen final : public Screen {
|
||||||
SDL_Renderer* _renderer;
|
SDL_Renderer* _renderer;
|
||||||
Theme& _theme;
|
Theme& _theme;
|
||||||
|
SimpleConfigModel _conf;
|
||||||
FileSelector _fss;
|
FileSelector _fss;
|
||||||
|
|
||||||
bool _new_save {false};
|
bool _new_save {false};
|
||||||
@ -28,7 +31,7 @@ struct StartScreen final : public Screen {
|
|||||||
std::vector<std::string> queued_plugin_paths;
|
std::vector<std::string> queued_plugin_paths;
|
||||||
|
|
||||||
StartScreen(void) = delete;
|
StartScreen(void) = delete;
|
||||||
StartScreen(SDL_Renderer* renderer, Theme& theme);
|
StartScreen(const std::vector<std::string_view>& args, SDL_Renderer* renderer, Theme& theme);
|
||||||
~StartScreen(void) = default;
|
~StartScreen(void) = default;
|
||||||
|
|
||||||
// return nullptr if not next
|
// return nullptr if not next
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
#include "./tox_av.hpp"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
// https://almogfx.bandcamp.com/track/crushed-w-cassade
|
|
||||||
|
|
||||||
ToxAV::ToxAV(Tox* tox) : _tox(tox) {
|
|
||||||
Toxav_Err_New err_new {TOXAV_ERR_NEW_OK};
|
|
||||||
_tox_av = toxav_new(_tox, &err_new);
|
|
||||||
// TODO: throw
|
|
||||||
assert(err_new == TOXAV_ERR_NEW_OK);
|
|
||||||
}
|
|
||||||
ToxAV::~ToxAV(void) {
|
|
||||||
toxav_kill(_tox_av);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t ToxAV::toxavIterationInterval(void) const {
|
|
||||||
return toxav_iteration_interval(_tox_av);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToxAV::toxavIterate(void) {
|
|
||||||
toxav_iterate(_tox_av);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t ToxAV::toxavAudioIterationInterval(void) const {
|
|
||||||
return toxav_audio_iteration_interval(_tox_av);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToxAV::toxavAudioIterate(void) {
|
|
||||||
toxav_audio_iterate(_tox_av);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t ToxAV::toxavVideoIterationInterval(void) const {
|
|
||||||
return toxav_video_iteration_interval(_tox_av);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToxAV::toxavVideoIterate(void) {
|
|
||||||
toxav_video_iterate(_tox_av);
|
|
||||||
}
|
|
||||||
|
|
||||||
Toxav_Err_Call ToxAV::toxavCall(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) {
|
|
||||||
Toxav_Err_Call err {TOXAV_ERR_CALL_OK};
|
|
||||||
toxav_call(_tox_av, friend_number, audio_bit_rate, video_bit_rate, &err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toxav_Err_Answer ToxAV::toxavAnswer(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) {
|
|
||||||
Toxav_Err_Answer err {TOXAV_ERR_ANSWER_OK};
|
|
||||||
toxav_answer(_tox_av, friend_number, audio_bit_rate, video_bit_rate, &err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toxav_Err_Call_Control ToxAV::toxavCallControl(uint32_t friend_number, Toxav_Call_Control control) {
|
|
||||||
Toxav_Err_Call_Control err {TOXAV_ERR_CALL_CONTROL_OK};
|
|
||||||
toxav_call_control(_tox_av, friend_number, control, &err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toxav_Err_Send_Frame ToxAV::toxavAudioSendFrame(uint32_t friend_number, const int16_t pcm[], size_t sample_count, uint8_t channels, uint32_t sampling_rate) {
|
|
||||||
Toxav_Err_Send_Frame err {TOXAV_ERR_SEND_FRAME_OK};
|
|
||||||
toxav_audio_send_frame(_tox_av, friend_number, pcm, sample_count, channels, sampling_rate, &err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toxav_Err_Bit_Rate_Set ToxAV::toxavAudioSetBitRate(uint32_t friend_number, uint32_t bit_rate) {
|
|
||||||
Toxav_Err_Bit_Rate_Set err {TOXAV_ERR_BIT_RATE_SET_OK};
|
|
||||||
toxav_audio_set_bit_rate(_tox_av, friend_number, bit_rate, &err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toxav_Err_Send_Frame ToxAV::toxavVideoSendFrame(uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t y[], const uint8_t u[], const uint8_t v[]) {
|
|
||||||
Toxav_Err_Send_Frame err {TOXAV_ERR_SEND_FRAME_OK};
|
|
||||||
toxav_video_send_frame(_tox_av, friend_number, width, height, y, u, v, &err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toxav_Err_Bit_Rate_Set ToxAV::toxavVideoSetBitRate(uint32_t friend_number, uint32_t bit_rate) {
|
|
||||||
Toxav_Err_Bit_Rate_Set err {TOXAV_ERR_BIT_RATE_SET_OK};
|
|
||||||
toxav_video_set_bit_rate(_tox_av, friend_number, bit_rate, &err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <tox/toxav.h>
|
|
||||||
|
|
||||||
struct ToxAV {
|
|
||||||
Tox* _tox = nullptr;
|
|
||||||
ToxAV* _tox_av = nullptr;
|
|
||||||
|
|
||||||
ToxAV(Tox* tox);
|
|
||||||
virtual ~ToxAV(void);
|
|
||||||
|
|
||||||
// interface
|
|
||||||
uint32_t toxavIterationInterval(void) const;
|
|
||||||
void toxavIterate(void);
|
|
||||||
|
|
||||||
uint32_t toxavAudioIterationInterval(void) const;
|
|
||||||
void toxavAudioIterate(void);
|
|
||||||
uint32_t toxavVideoIterationInterval(void) const;
|
|
||||||
void toxavVideoIterate(void);
|
|
||||||
|
|
||||||
Toxav_Err_Call toxavCall(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate);
|
|
||||||
Toxav_Err_Answer toxavAnswer(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate);
|
|
||||||
Toxav_Err_Call_Control toxavCallControl(uint32_t friend_number, Toxav_Call_Control control);
|
|
||||||
Toxav_Err_Send_Frame toxavAudioSendFrame(uint32_t friend_number, const int16_t pcm[], size_t sample_count, uint8_t channels, uint32_t sampling_rate);
|
|
||||||
Toxav_Err_Bit_Rate_Set toxavAudioSetBitRate(uint32_t friend_number, uint32_t bit_rate);
|
|
||||||
Toxav_Err_Send_Frame toxavVideoSendFrame(uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t y[/*! height * width */], const uint8_t u[/*! height/2 * width/2 */], const uint8_t v[/*! height/2 * width/2 */]);
|
|
||||||
Toxav_Err_Bit_Rate_Set toxavVideoSetBitRate(uint32_t friend_number, uint32_t bit_rate);
|
|
||||||
|
|
||||||
//int32_t toxav_add_av_groupchat(Tox *tox, toxav_audio_data_cb *audio_callback, void *userdata);
|
|
||||||
//int32_t toxav_join_av_groupchat(Tox *tox, uint32_t friendnumber, const uint8_t data[], uint16_t length, toxav_audio_data_cb *audio_callback, void *userdata);
|
|
||||||
//int32_t toxav_group_send_audio(Tox *tox, uint32_t groupnumber, const int16_t pcm[], uint32_t samples, uint8_t channels, uint32_t sample_rate);
|
|
||||||
//int32_t toxav_groupchat_enable_av(Tox *tox, uint32_t groupnumber, toxav_audio_data_cb *audio_callback, void *userdata);
|
|
||||||
//int32_t toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber);
|
|
||||||
//bool toxav_groupchat_av_enabled(Tox *tox, uint32_t groupnumber);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|