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
|
||||
|
||||
- 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
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
@ -50,91 +50,12 @@ jobs:
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
# TODO: simpler name?
|
||||
name: ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-ubuntu20.04-x86_64
|
||||
# TODO: do propper packing
|
||||
path: |
|
||||
${{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:
|
||||
timeout-minutes: 15
|
||||
@ -153,7 +74,7 @@ jobs:
|
||||
git pull
|
||||
|
||||
- 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
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
@ -189,7 +110,8 @@ jobs:
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
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
|
||||
path: |
|
||||
${{github.workspace}}/${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-msvc-x86_64.zip
|
||||
@ -212,7 +134,7 @@ jobs:
|
||||
git pull
|
||||
|
||||
- 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
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
@ -248,6 +170,7 @@ jobs:
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
# TODO: simpler name?
|
||||
name: ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-msvc-asan-x86_64
|
||||
# TODO: do propper packing
|
||||
path: |
|
||||
@ -260,7 +183,6 @@ jobs:
|
||||
|
||||
needs:
|
||||
- linux-ubuntu
|
||||
- android
|
||||
- windows
|
||||
- windows-asan
|
||||
|
||||
|
68
.github/workflows/ci.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- 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
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
@ -29,70 +29,6 @@ jobs:
|
||||
- name: Build
|
||||
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:
|
||||
timeout-minutes: 10
|
||||
|
||||
@ -129,7 +65,7 @@ jobs:
|
||||
git pull
|
||||
|
||||
- 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
|
||||
- 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_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)
|
||||
|
||||
if (TOMATO_ASAN)
|
||||
if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
|
||||
if (NOT WIN32) # exclude mingw
|
||||
#link_libraries(-fsanitize=address)
|
||||
add_compile_options(-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")
|
||||
if (OFF) # TODO: switch for minimal runtime in deployed scenarios
|
||||
add_compile_options(-fsanitize-minimal-runtime)
|
||||
link_libraries(-fsanitize-minimal-runtime)
|
||||
endif()
|
||||
else()
|
||||
message("!! can not enable ASAN on this platform (gcc/clang + win)")
|
||||
endif()
|
||||
@ -70,10 +76,3 @@ endif()
|
||||
|
||||
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(./json)
|
||||
|
||||
add_subdirectory(./solanaceae_util)
|
||||
add_subdirectory(./solanaceae_contact)
|
||||
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_STATIC ON CACHE INTERNAL "")
|
||||
#TODO: pic ?
|
||||
set(SDL_DISABLE_ANDROID_JAR OFF CACHE INTERNAL "")
|
||||
|
||||
FetchContent_Declare(SDL3
|
||||
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 aacafd62336363077470f678b6217214b3b49473 # tip 28-05-2024
|
||||
GIT_TAG 5fa9432b7d1c1722de93e1ab46e7a9569a47071e # tip 27-05-2024 - before changes made breaking sdl_image
|
||||
|
||||
FIND_PACKAGE_ARGS # for the future
|
||||
)
|
||||
FetchContent_MakeAvailable(SDL3)
|
||||
|
2
external/solanaceae_plugin
vendored
@ -74,6 +74,8 @@
|
||||
#(libsodium.override { stdenv = pkgs.pkgsStatic.stdenv; })
|
||||
#pkgsStatic.libsodium
|
||||
libsodium
|
||||
libopus
|
||||
libvpx
|
||||
] ++ self.packages.${system}.default.dlopenBuildInputs;
|
||||
|
||||
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_library(tomato MODULE)
|
||||
target_compile_definitions(tomato PUBLIC TOMATO_MAIN_SO)
|
||||
else()
|
||||
add_executable(tomato)
|
||||
endif()
|
||||
|
||||
target_sources(tomato PUBLIC
|
||||
add_executable(tomato
|
||||
./main.cpp
|
||||
./icon.rc
|
||||
|
||||
./json_to_config.hpp
|
||||
./json_to_config.cpp
|
||||
|
||||
./screen.hpp
|
||||
./start_screen.hpp
|
||||
./start_screen.cpp
|
||||
@ -26,6 +16,9 @@ target_sources(tomato PUBLIC
|
||||
./tox_client.cpp
|
||||
./auto_dirty.hpp
|
||||
./auto_dirty.cpp
|
||||
./tox_private_impl.hpp
|
||||
./tox_av.hpp
|
||||
./tox_av.cpp
|
||||
|
||||
./theme.hpp
|
||||
|
||||
@ -73,6 +66,10 @@ target_sources(tomato PUBLIC
|
||||
./chat_gui/settings_window.hpp
|
||||
./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.cpp
|
||||
|
||||
@ -84,6 +81,14 @@ target_sources(tomato PUBLIC
|
||||
|
||||
./chat_gui4.hpp
|
||||
./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)
|
||||
|
@ -151,7 +151,8 @@ void SendImagePopup::sendMemory(
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
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
|
||||
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;
|
||||
};
|
||||
|
@ -41,7 +41,7 @@ ImageLoaderQOI::ImageResult ImageLoaderQOI::loadFromMemoryRGBA(const uint8_t* da
|
||||
|
||||
auto& new_frame = res.frames.emplace_back();
|
||||
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);
|
||||
return res;
|
||||
|
@ -47,7 +47,7 @@ ImageLoaderSDLBMP::ImageResult ImageLoaderSDLBMP::loadFromMemoryRGBA(const uint8
|
||||
|
||||
auto& new_frame = res.frames.emplace_back();
|
||||
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_DestroySurface(conv_surf);
|
||||
|
@ -99,7 +99,7 @@ ImageLoaderSDLImage::ImageResult ImageLoaderSDLImage::loadFromMemoryRGBA(const u
|
||||
|
||||
auto& new_frame = res.frames.emplace_back();
|
||||
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_DestroySurface(conv_surf);
|
||||
|
@ -41,7 +41,7 @@ ImageLoaderSTB::ImageResult ImageLoaderSTB::loadFromMemoryRGBA(const uint8_t* da
|
||||
for (int i = 0; i < z; i++) {
|
||||
auto& new_frame = res.frames.emplace_back();
|
||||
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
|
||||
@ -62,7 +62,7 @@ ImageLoaderSTB::ImageResult ImageLoaderSTB::loadFromMemoryRGBA(const uint8_t* da
|
||||
|
||||
auto& new_frame = res.frames.emplace_back();
|
||||
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);
|
||||
return res;
|
||||
|
@ -78,7 +78,7 @@ ImageLoaderWebP::ImageResult ImageLoaderWebP::loadFromMemoryRGBA(const uint8_t*
|
||||
auto& new_frame = res.frames.emplace_back();
|
||||
new_frame.ms = timestamp-prev_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());
|
||||
|
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 "./start_screen.hpp"
|
||||
#include "./content/sdl_audio_frame_stream2.hpp"
|
||||
#include "./content/sdl_video_frame_stream2.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
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
|
||||
#ifndef __ANDROID__
|
||||
if (SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1") != SDL_TRUE) {
|
||||
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_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::CreateContext();
|
||||
|
||||
@ -106,9 +133,9 @@ int main(int argc, char** argv) {
|
||||
ImGui_ImplSDL3_InitForSDLRenderer(window.get(), 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;
|
||||
while (!quit) {
|
||||
auto new_time = std::chrono::steady_clock::now();
|
||||
@ -133,10 +160,6 @@ int main(int argc, char** argv) {
|
||||
if (event.type == SDL_EVENT_QUIT) {
|
||||
quit = true;
|
||||
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)) {
|
||||
@ -149,10 +172,6 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_background) {
|
||||
render = false;
|
||||
}
|
||||
|
||||
// can do both in the same loop
|
||||
if (render) {
|
||||
ImGui_ImplSDLRenderer3_NewFrame();
|
||||
|
@ -12,15 +12,15 @@
|
||||
#include <memory>
|
||||
#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_),
|
||||
conf(std::move(conf_)),
|
||||
rmm(cr),
|
||||
msnj{cr, {}, {}},
|
||||
mts(rmm),
|
||||
tc(save_path, save_password),
|
||||
tpi(tc.getTox()),
|
||||
ad(tc),
|
||||
tav(tc.getTox()),
|
||||
tcm(cr, tc, tc),
|
||||
tmm(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),
|
||||
cg(conf, rmm, cr, sdlrtu, contact_tc, msg_tc, theme),
|
||||
sw(conf),
|
||||
osui(os),
|
||||
tuiu(tc, conf),
|
||||
tdch(tpi)
|
||||
{
|
||||
@ -69,6 +70,7 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme
|
||||
g_provideInstance<ToxI>("ToxI", "host", &tc);
|
||||
g_provideInstance<ToxPrivateI>("ToxPrivateI", "host", &tpi);
|
||||
g_provideInstance<ToxEventProviderI>("ToxEventProviderI", "host", &tc);
|
||||
g_provideInstance<ToxAV>("ToxAV", "host", &tav);
|
||||
g_provideInstance<ToxContactModel2>("ToxContactModel2", "host", &tcm);
|
||||
|
||||
// TODO: pm?
|
||||
@ -245,6 +247,7 @@ Screen* MainScreen::render(float time_delta, bool&) {
|
||||
|
||||
const float cg_interval = cg.render(time_delta); // render
|
||||
sw.render(); // render
|
||||
osui.render();
|
||||
tuiu.render(); // render
|
||||
tdch.render(); // render
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <solanaceae/plugin/plugin_manager.hpp>
|
||||
#include <solanaceae/toxcore/tox_event_logger.hpp>
|
||||
#include "./tox_private_impl.hpp"
|
||||
#include "./tox_av.hpp"
|
||||
|
||||
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
|
||||
#include <solanaceae/tox_messages/tox_message_manager.hpp>
|
||||
@ -29,6 +30,7 @@
|
||||
|
||||
#include "./chat_gui4.hpp"
|
||||
#include "./chat_gui/settings_window.hpp"
|
||||
#include "./object_store_ui.hpp"
|
||||
#include "./tox_ui_utils.hpp"
|
||||
#include "./tox_dht_cap_histo.hpp"
|
||||
#include "./tox_friend_faux_offline_messaging.hpp"
|
||||
@ -57,6 +59,7 @@ struct MainScreen final : public Screen {
|
||||
ToxClient tc;
|
||||
ToxPrivateImpl tpi;
|
||||
AutoDirty ad;
|
||||
ToxAV tav;
|
||||
ToxContactModel2 tcm;
|
||||
ToxMessageManager tmm;
|
||||
ToxTransferManager ttm;
|
||||
@ -77,6 +80,7 @@ struct MainScreen final : public Screen {
|
||||
|
||||
ChatGui4 cg;
|
||||
SettingsWindow sw;
|
||||
ObjectStoreUI osui;
|
||||
ToxUIUtils tuiu;
|
||||
ToxDHTCapHisto tdch;
|
||||
|
||||
@ -91,7 +95,7 @@ struct MainScreen final : public Screen {
|
||||
uint64_t _window_hidden_ts {0};
|
||||
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);
|
||||
|
||||
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
|
||||
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) {
|
||||
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_NEAREST);
|
||||
} else if (filter == LINEAR) {
|
||||
|
@ -2,54 +2,14 @@
|
||||
|
||||
#include "./main_screen.hpp"
|
||||
|
||||
#include "./json_to_config.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <memory>
|
||||
#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&) {
|
||||
@ -183,8 +143,7 @@ Screen* StartScreen::render(float, bool&) {
|
||||
}
|
||||
} else {
|
||||
if (ImGui::Button("load", {60, 25})) {
|
||||
|
||||
auto new_screen = std::make_unique<MainScreen>(std::move(_conf), _renderer, _theme, _tox_profile_path, _password, _user_name, queued_plugin_paths);
|
||||
auto new_screen = std::make_unique<MainScreen>(_renderer, _theme, _tox_profile_path, _password, _user_name, queued_plugin_paths);
|
||||
return new_screen.release();
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,6 @@
|
||||
#include "./chat_gui/theme.hpp"
|
||||
#include "./chat_gui/file_selector.hpp"
|
||||
|
||||
#include <solanaceae/util/simple_config_model.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
@ -18,7 +16,6 @@ extern "C" {
|
||||
struct StartScreen final : public Screen {
|
||||
SDL_Renderer* _renderer;
|
||||
Theme& _theme;
|
||||
SimpleConfigModel _conf;
|
||||
FileSelector _fss;
|
||||
|
||||
bool _new_save {false};
|
||||
@ -31,7 +28,7 @@ struct StartScreen final : public Screen {
|
||||
std::vector<std::string> queued_plugin_paths;
|
||||
|
||||
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;
|
||||
|
||||
// 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);
|
||||
|
||||
};
|
||||
|