Compare commits
13 Commits
android_co
...
content_de
Author | SHA1 | Date | |
---|---|---|---|
bad140dc70 | |||
fae1910f1a | |||
2f44b45e8a | |||
fbaf8133ab | |||
568d1a3f93 | |||
803bce93fb | |||
31905b5468 | |||
471fac409e | |||
b8132afabb | |||
8a55c0f763 | |||
a8ebcdc970 | |||
f46d0a713d | |||
d52a1a44f8 |
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 cmake
|
run: sudo apt update && sudo apt -y install libsodium-dev
|
||||||
|
|
||||||
- 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,91 +50,12 @@ 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
|
||||||
@ -153,7 +74,7 @@ jobs:
|
|||||||
git pull
|
git pull
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: vcpkg install libsodium:x64-windows-static pthreads:x64-windows-static pkgconf:x64-windows
|
run: vcpkg install pkgconf:x64-windows libsodium:x64-windows-static pthreads:x64-windows-static opus:x64-windows-static libvpx:x64-windows-static
|
||||||
|
|
||||||
# setup vs env
|
# setup vs env
|
||||||
- uses: ilammy/msvc-dev-cmd@v1
|
- uses: ilammy/msvc-dev-cmd@v1
|
||||||
@ -189,7 +110,8 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{github.event.repository.name}}-${{steps.tag.outputs.name}}-${{runner.os}}-msvc-x86_64
|
# TODO: simpler name?
|
||||||
|
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
|
||||||
@ -212,7 +134,7 @@ jobs:
|
|||||||
git pull
|
git pull
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: vcpkg install libsodium:x64-windows-static pthreads:x64-windows-static pkgconf:x64-windows
|
run: vcpkg install pkgconf:x64-windows libsodium:x64-windows-static pthreads:x64-windows-static opus:x64-windows-static libvpx:x64-windows-static
|
||||||
|
|
||||||
# setup vs env
|
# setup vs env
|
||||||
- uses: ilammy/msvc-dev-cmd@v1
|
- uses: ilammy/msvc-dev-cmd@v1
|
||||||
@ -248,6 +170,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-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: |
|
||||||
@ -260,7 +183,6 @@ 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 cmake
|
run: sudo apt update && sudo apt -y install libsodium-dev
|
||||||
|
|
||||||
- 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,70 +29,6 @@ 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
|
||||||
|
|
||||||
@ -129,7 +65,7 @@ jobs:
|
|||||||
git pull
|
git pull
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: vcpkg install libsodium:x64-windows-static pthreads:x64-windows-static pkgconf:x64-windows
|
run: vcpkg install pkgconf:x64-windows libsodium:x64-windows-static pthreads:x64-windows-static opus:x64-windows-static libvpx:x64-windows-static
|
||||||
|
|
||||||
# setup vs env
|
# setup vs env
|
||||||
- uses: ilammy/msvc-dev-cmd@v1
|
- uses: ilammy/msvc-dev-cmd@v1
|
||||||
|
@ -18,16 +18,22 @@ 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
|
||||||
#link_libraries(-fsanitize=address)
|
add_compile_options(-fsanitize=address,undefined)
|
||||||
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()
|
||||||
@ -70,10 +76,3 @@ 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()
|
|
||||||
|
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
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()
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
|||||||
<?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>
|
|
@ -1,18 +0,0 @@
|
|||||||
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"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Before Width: | Height: | Size: 94 KiB |
@ -1,6 +0,0 @@
|
|||||||
<?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>
|
|
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="colorPrimary">#3F51B5</color>
|
|
||||||
<color name="colorPrimaryDark">#303F9F</color>
|
|
||||||
<color name="colorAccent">#FF4081</color>
|
|
||||||
</resources>
|
|
@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<string name="app_name">Tomato</string>
|
|
||||||
</resources>
|
|
@ -1,10 +0,0 @@
|
|||||||
<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,8 +2,6 @@ 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
@ -1,13 +0,0 @@
|
|||||||
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,7 +6,6 @@ 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
|
||||||
@ -17,7 +16,6 @@ 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,6 +74,8 @@
|
|||||||
#(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,21 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.9...3.24 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
|
|
||||||
if (TOMATO_MAIN_SO)
|
add_executable(tomato
|
||||||
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
|
||||||
@ -26,6 +16,9 @@ target_sources(tomato PUBLIC
|
|||||||
./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
|
||||||
|
|
||||||
@ -73,6 +66,10 @@ target_sources(tomato PUBLIC
|
|||||||
./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
|
||||||
|
|
||||||
@ -84,6 +81,14 @@ target_sources(tomato PUBLIC
|
|||||||
|
|
||||||
./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,7 +151,8 @@ void SendImagePopup::sendMemory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// copy paste data to memory
|
// copy paste data to memory
|
||||||
original_data = {data, data+data_size};
|
original_data.clear();
|
||||||
|
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";
|
||||||
|
237
src/content/SPSCQueue.h
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
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
|
69
src/content/audio_stream.hpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#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>;
|
||||||
|
|
49
src/content/content.hpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
|
|
132
src/content/frame_stream2.hpp
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
170
src/content/sdl_audio_frame_stream2.cpp
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
61
src/content/sdl_audio_frame_stream2.hpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#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);
|
||||||
|
};
|
||||||
|
|
142
src/content/sdl_video_frame_stream2.cpp
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#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
|
||||||
|
}
|
65
src/content/sdl_video_frame_stream2.hpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
|
|
15
src/content/stream_reader.hpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
|
|
39
src/content/stream_reader_sdl_audio.cpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#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};
|
||||||
|
}
|
||||||
|
|
31
src/content/stream_reader_sdl_audio.hpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
|
|
84
src/content/stream_reader_sdl_video.cpp
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#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 {};
|
||||||
|
}
|
||||||
|
|
34
src/content/stream_reader_sdl_video.hpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#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,6 +27,8 @@ 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 = {img_data, img_data+(desc.width*desc.height*4)};
|
new_frame.data.insert(new_frame.data.cbegin(), 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 = {(const uint8_t*)conv_surf->pixels, ((const uint8_t*)conv_surf->pixels) + (surf->w*surf->h*4)};
|
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));
|
||||||
|
|
||||||
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 = {(const uint8_t*)conv_surf->pixels, ((const uint8_t*)conv_surf->pixels) + (anim->w*anim->h*4)};
|
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));
|
||||||
|
|
||||||
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 = {img_data + (i*stride), img_data + ((i+1)*stride)};
|
new_frame.data.insert(new_frame.data.cbegin(), 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 = {img_data, img_data+(x*y*4)};
|
new_frame.data.insert(new_frame.data.cbegin(), 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 = {buf, buf+(res.width*res.height*4)};
|
new_frame.data.insert(new_frame.data.end(), buf, buf+(res.width*res.height*4));
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(anim_info.frame_count == res.frames.size());
|
assert(anim_info.frame_count == res.frames.size());
|
||||||
|
319
src/imgui_entt_entity_editor.hpp
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
// 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.
|
||||||
|
|
@ -1,67 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
|||||||
#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,32 +9,19 @@
|
|||||||
#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();
|
||||||
@ -73,6 +60,46 @@ 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();
|
||||||
|
|
||||||
@ -106,9 +133,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>(args, renderer.get(), theme);
|
std::unique_ptr<Screen> screen = std::make_unique<StartScreen>(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();
|
||||||
@ -133,10 +160,6 @@ 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)) {
|
||||||
@ -149,10 +172,6 @@ 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(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::MainScreen(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,6 +35,7 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme
|
|||||||
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)
|
||||||
{
|
{
|
||||||
@ -69,6 +70,7 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme
|
|||||||
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?
|
||||||
@ -245,6 +247,7 @@ 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,6 +11,7 @@
|
|||||||
#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>
|
||||||
@ -29,6 +30,7 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@ -57,6 +59,7 @@ 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;
|
||||||
@ -77,6 +80,7 @@ struct MainScreen final : public Screen {
|
|||||||
|
|
||||||
ChatGui4 cg;
|
ChatGui4 cg;
|
||||||
SettingsWindow sw;
|
SettingsWindow sw;
|
||||||
|
ObjectStoreUI osui;
|
||||||
ToxUIUtils tuiu;
|
ToxUIUtils tuiu;
|
||||||
ToxDHTCapHisto tdch;
|
ToxDHTCapHisto tdch;
|
||||||
|
|
||||||
@ -91,7 +95,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(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(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;
|
||||||
|
42
src/object_store_ui.cpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
19
src/object_store_ui.hpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#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,11 +31,6 @@ 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,54 +2,14 @@
|
|||||||
|
|
||||||
#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&) {
|
||||||
@ -183,8 +143,7 @@ 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,8 +5,6 @@
|
|||||||
#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>
|
||||||
|
|
||||||
@ -18,7 +16,6 @@ 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};
|
||||||
@ -31,7 +28,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(const std::vector<std::string_view>& args, SDL_Renderer* renderer, Theme& theme);
|
StartScreen(SDL_Renderer* renderer, Theme& theme);
|
||||||
~StartScreen(void) = default;
|
~StartScreen(void) = default;
|
||||||
|
|
||||||
// return nullptr if not next
|
// return nullptr if not next
|
||||||
|
82
src/tox_av.cpp
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
37
src/tox_av.hpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#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);
|
||||||
|
|
||||||
|
};
|
||||||
|
|