diff --git a/.github/workflows/build_ci.yml b/.github/workflows/build_ci.yml index 041a8cb3..967f3d05 100644 --- a/.github/workflows/build_ci.yml +++ b/.github/workflows/build_ci.yml @@ -1,10 +1,17 @@ -name: Build and test with Gradle +name: Build, Test & Release + +on: + push: + branches: [ master, dev ] + tags: ['v*'] + pull_request: -on: [push, pull_request] jobs: - test_linux: - runs-on: ubuntu-latest + build_linux: + runs-on: ubuntu-24.04 + env: + BUILD_ENV: ${{ startsWith(github.ref, 'refs/tags/v') && 'release' || 'dev' }} steps: - uses: actions/checkout@v2 @@ -12,14 +19,67 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: '17' + java-version: '25' - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build and test with Gradle - run: ./gradlew test + run: ./gradlew -Penv=${{ env.BUILD_ENV }} clean build shadowJar + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: eocvsim-ubuntu-latest + path: | + EOCV-Sim/build/libs/*.jar + + build_linux_arm: + runs-on: ubuntu-24.04-arm + env: + BUILD_ENV: ${{ startsWith(github.ref, 'refs/tags/v') && 'release' || 'dev' }} - test_windows: + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '25' + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build and test with Gradle + run: ./gradlew -Penv=${{ env.BUILD_ENV }} clean build shadowJar + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: eocvsim-ubuntu-arm64 + path: | + EOCV-Sim/build/libs/*.jar + + build_windows: runs-on: windows-latest + env: + BUILD_ENV: ${{ startsWith(github.ref, 'refs/tags/v') && 'release' || 'dev' }} + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '25' + - name: Build and test with Gradle (Windows) + shell: pwsh + run: .\gradlew.bat -Penv=${{ env.BUILD_ENV }} clean build shadowJar + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: eocvsim-windows-latest + path: | + EOCV-Sim/build/libs/*.jar + + build_mac: + runs-on: macos-26 + env: + BUILD_ENV: ${{ startsWith(github.ref, 'refs/tags/v') && 'release' || 'dev' }} steps: - uses: actions/checkout@v2 @@ -27,14 +87,22 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: '17' + java-version: '25' - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build and test with Gradle - run: ./gradlew test + run: ./gradlew -Penv=${{ env.BUILD_ENV }} clean build shadowJar + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: eocvsim-macos-latest + path: | + EOCV-Sim/build/libs/*.jar - test_mac: - runs-on: macos-latest + build_mac_intel: + runs-on: macos-26-intel + env: + BUILD_ENV: ${{ startsWith(github.ref, 'refs/tags/v') && 'release' || 'dev' }} steps: - uses: actions/checkout@v2 @@ -42,8 +110,164 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: '17' + java-version: '25' - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build and test with Gradle - run: ./gradlew test + run: ./gradlew -Penv=${{ env.BUILD_ENV }} clean build shadowJar + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: eocvsim-macos-13 + path: | + EOCV-Sim/build/libs/*.jar + + release: + needs: [build_linux, build_linux_arm, build_windows, build_mac, build_mac_intel] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '25' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Publish package to maven central + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.JRELEASER_MAVENCENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.JRELEASER_MAVENCENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.JRELEASER_GPG_PASSPHRASE }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.JRELEASER_GPG_SECRET_KEY }} + run: ./gradlew -Penv=release :Common:publishToMavenCentral :Vision:publishToMavenCentral :EOCV-Sim:publishToMavenCentral -x test -x :EOCV-Sim:shadowJar + + - name: Build release shadow jar with Gradle + run: ./gradlew -Penv=release shadowJar -x test + + - name: Download Linux artifacts + uses: actions/download-artifact@v4 + with: + name: eocvsim-ubuntu-latest + path: artifacts/ubuntu + + - name: Download Linux ARM64 artifacts + uses: actions/download-artifact@v4 + with: + name: eocvsim-ubuntu-arm64 + path: artifacts/ubuntu-arm64 + + - name: Download macOS artifacts + uses: actions/download-artifact@v4 + with: + name: eocvsim-macos-latest + path: artifacts/macos + + - name: Download Intel macOS artifacts + uses: actions/download-artifact@v4 + with: + name: eocvsim-macos-13 + path: artifacts/macos-intel + + - name: Download Windows artifacts + uses: actions/download-artifact@v4 + with: + name: eocvsim-windows-latest + path: artifacts/windows + + - name: List downloaded artifacts + run: | + ls -la artifacts || true + ls -la artifacts/ubuntu || true + ls -la artifacts/ubuntu-arm64 || true + ls -la artifacts/macos || true + ls -la artifacts/macos-intel || true + ls -la artifacts/windows || true + + - name: Select platform jars for release + run: | + mkdir -p release-jars + # Copy the first (and should be only) jar from each platform folder + cp artifacts/ubuntu/EOCV-Sim-*.jar release-jars/ 2>/dev/null || echo "No Linux jar found" + cp artifacts/ubuntu-arm64/EOCV-Sim-*.jar release-jars/ 2>/dev/null || echo "No Linux ARM64 jar found" + cp artifacts/windows/EOCV-Sim-*.jar release-jars/ 2>/dev/null || echo "No Windows jar found" + cp artifacts/macos/EOCV-Sim-*.jar release-jars/ 2>/dev/null || echo "No macOS jar found" + cp artifacts/macos-intel/EOCV-Sim-*.jar release-jars/ 2>/dev/null || echo "No Intel macOS jar found" + # Remove source jars if any were copied + rm -f release-jars/*-sources.jar + # Remove the generic jar (no platform suffix) + find release-jars -name "EOCV-Sim-*.jar" ! -name "*x86*" ! -name "*arm*" ! -name "*win*" -delete + echo "Release jars to upload:" + ls -la release-jars/ + + - name: Create GitHub Release and upload artifacts + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.ref_name }} + files: release-jars/*.jar + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + dev_release: + needs: [build_linux, build_linux_arm, build_windows, build_mac, build_mac_intel] + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/tags/v') + steps: + - name: Download Linux artifacts + uses: actions/download-artifact@v4 + with: + name: eocvsim-ubuntu-latest + path: artifacts/dev/ubuntu + + - name: Download Linux ARM64 artifacts + uses: actions/download-artifact@v4 + with: + name: eocvsim-ubuntu-arm64 + path: artifacts/dev/ubuntu-arm64 + + - name: Download macOS artifacts + uses: actions/download-artifact@v4 + with: + name: eocvsim-macos-latest + path: artifacts/dev/macos + + - name: Download Windows artifacts + uses: actions/download-artifact@v4 + with: + name: eocvsim-windows-latest + path: artifacts/dev/windows + + - name: Download Intel macOS artifacts + uses: actions/download-artifact@v4 + with: + name: eocvsim-macos-13 + path: artifacts/dev/macos-intel + + - name: Select platform jars for dev release + run: | + mkdir -p release-jars + # Copy the first (and should be only) jar from each platform folder + cp artifacts/dev/ubuntu/EOCV-Sim-*.jar release-jars/ 2>/dev/null || echo "No Linux jar found" + cp artifacts/dev/ubuntu-arm64/EOCV-Sim-*.jar release-jars/ 2>/dev/null || echo "No Linux ARM64 jar found" + cp artifacts/dev/windows/EOCV-Sim-*.jar release-jars/ 2>/dev/null || echo "No Windows jar found" + cp artifacts/dev/macos/EOCV-Sim-*.jar release-jars/ 2>/dev/null || echo "No macOS jar found" + cp artifacts/dev/macos-intel/EOCV-Sim-*.jar release-jars/ 2>/dev/null || echo "No Intel macOS jar found" + # Remove source jars if any were copied + rm -f release-jars/*-sources.jar + # Remove the generic jar (no platform suffix) + find release-jars -name "EOCV-Sim-*.jar" ! -name "*x86*" ! -name "*arm*" ! -name "*win*" -delete + echo "Dev release jars to upload:" + ls -la release-jars/ + + - name: Create Dev Release and upload artifacts + uses: pyTooling/Actions/releaser@r0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: 'Dev' + rm: true + files: | + release-jars/*.jar diff --git a/.github/workflows/release_ci.yml b/.github/workflows/release_ci.yml deleted file mode 100644 index d5449fbf..00000000 --- a/.github/workflows/release_ci.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Maven Publish & Create GitHub Release(s) - -on: - push: - branches: [ master, dev ] - tags: 'v*' - -jobs: - build-and-release: - if: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref != 'ref/heads/master' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up JDK - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '17' - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Publish package to maven central - env: - ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.JRELEASER_MAVENCENTRAL_USERNAME }} - ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.JRELEASER_MAVENCENTRAL_PASSWORD }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.JRELEASER_GPG_PASSPHRASE }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.JRELEASER_GPG_SECRET_KEY }} - run: ./gradlew -Penv=release :Common:publishToMavenCentral :Vision:publishToMavenCentral :EOCV-Sim:publishToMavenCentral -x test -x :EOCV-Sim:shadowJar - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - - - name: Build release shadow jar with Gradle - run: ./gradlew -Penv=release shadowJar -x test - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - - - name: Build dev shadow jar with Gradle - run: | - SHA_SHORT="$(git rev-parse --short HEAD)" - ./gradlew -Phash=$SHA_SHORT shadowJar -x test - if: ${{ !startsWith(github.ref, 'refs/tags/v') && github.ref != 'refs/heads/master' }} - - - uses: pyTooling/Actions/releaser@r0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - tag: 'Dev' - rm: true - files: | - EOCV-Sim/build/libs/*.jar - if: ${{ github.event_name == 'push' && github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/tags/v')}} - - - uses: softprops/action-gh-release@v1 - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - with: - token: ${{ secrets.GITHUB_TOKEN }} - files: 'EOCV-Sim/build/libs/*.jar' diff --git a/.gitignore b/.gitignore index 3928031c..604e21ee 100644 --- a/.gitignore +++ b/.gitignore @@ -113,4 +113,5 @@ fabric.properties *.DS_Store -imgui.ini \ No newline at end of file +imgui.ini +.vscode \ No newline at end of file diff --git a/Common/build.gradle b/Common/build.gradle index 654472dc..c2486375 100644 --- a/Common/build.gradle +++ b/Common/build.gradle @@ -2,6 +2,7 @@ plugins { id 'kotlin' id 'signing' id 'com.gradleup.shadow' + id 'org.photonvision.tools.WpilibTools' id "com.vanniktech.maven.publish" version "0.30.0" } @@ -9,36 +10,31 @@ apply from: '../build.common.gradle' components.java { tasks.named("shadowJar").configure { - // only run shadowJar when explicitly specified by the user - // check if user invoked gradle with :shadowJar enabled = project.gradle.startParameter.taskNames.contains("shadowJar") } } -shadowJar { - dependencies { - exclude "nu/pattern/*" - } -} +wpilibTools.deps.wpilibVersion = wpilibVersion dependencies { - api "org.openpnp:opencv:$opencv_version" + // WPILib OpenCV replaces openpnp + compileOnly wpilibTools.deps.wpilibOpenCvJava(opencvVersion) - implementation "com.moandjiezana.toml:toml4j:$toml4j_version" + // Replace toml4j with Jackson TOML dataformat + implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-toml:$jackson_version" implementation "info.picocli:picocli:$picocli_version" implementation "org.slf4j:slf4j-api:$slf4j_version" + implementation 'org.jetbrains.kotlin:kotlin-stdlib' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version" implementation "com.formdev:flatlaf:$flatlaf_version" implementation "com.miglayout:miglayout-swing:$miglayout_version" - // Compatibility: Skiko supports many platforms but we will only be adding - // those that are supported by AprilTagDesktop as well - implementation("org.jetbrains.skiko:skiko-awt-runtime-windows-x64:$skiko_version") implementation("org.jetbrains.skiko:skiko-awt-runtime-linux-x64:$skiko_version") - implementation("org.jetbrains.skiko:skiko-awt-runtime-linux-x64:$skiko_version") implementation("org.jetbrains.skiko:skiko-awt-runtime-linux-arm64:$skiko_version") implementation("org.jetbrains.skiko:skiko-awt-runtime-macos-x64:$skiko_version") implementation("org.jetbrains.skiko:skiko-awt-runtime-macos-arm64:$skiko_version") + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2" } \ No newline at end of file diff --git a/Common/src/main/java/android/annotation/AnyThread.java b/Common/src/main/java/android/annotation/AnyThread.java index 6c2f7d40..35020851 100644 --- a/Common/src/main/java/android/annotation/AnyThread.java +++ b/Common/src/main/java/android/annotation/AnyThread.java @@ -41,4 +41,5 @@ @Retention(CLASS) @Target({METHOD,CONSTRUCTOR,TYPE}) public @interface AnyThread { -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/annotation/ColorInt.java b/Common/src/main/java/android/annotation/ColorInt.java index b6f9e900..7dd3b447 100644 --- a/Common/src/main/java/android/annotation/ColorInt.java +++ b/Common/src/main/java/android/annotation/ColorInt.java @@ -34,4 +34,6 @@ @Retention(CLASS) @Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD}) public @interface ColorInt { -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/android/annotation/ColorLong.java b/Common/src/main/java/android/annotation/ColorLong.java index 62184800..ed96189a 100644 --- a/Common/src/main/java/android/annotation/ColorLong.java +++ b/Common/src/main/java/android/annotation/ColorLong.java @@ -41,4 +41,5 @@ @Retention(SOURCE) @Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD}) public @interface ColorLong { -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/annotation/HalfFloat.java b/Common/src/main/java/android/annotation/HalfFloat.java index 69eef7ec..12b9de26 100644 --- a/Common/src/main/java/android/annotation/HalfFloat.java +++ b/Common/src/main/java/android/annotation/HalfFloat.java @@ -42,4 +42,5 @@ @Retention(SOURCE) @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD}) public @interface HalfFloat { -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/annotation/IntDef.java b/Common/src/main/java/android/annotation/IntDef.java index 4fbf3560..97dc7105 100644 --- a/Common/src/main/java/android/annotation/IntDef.java +++ b/Common/src/main/java/android/annotation/IntDef.java @@ -52,4 +52,5 @@ long[] value() default {}; /** Defines whether the constants can be used as a flag, or just as an enum (the default) */ boolean flag() default false; -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/annotation/IntRange.java b/Common/src/main/java/android/annotation/IntRange.java index 78cd60f7..dcea7528 100644 --- a/Common/src/main/java/android/annotation/IntRange.java +++ b/Common/src/main/java/android/annotation/IntRange.java @@ -42,4 +42,6 @@ long from() default Long.MIN_VALUE; /** Largest value, inclusive */ long to() default Long.MAX_VALUE; -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/android/annotation/NonNull.java b/Common/src/main/java/android/annotation/NonNull.java index a19512e3..2ee2b7de 100644 --- a/Common/src/main/java/android/annotation/NonNull.java +++ b/Common/src/main/java/android/annotation/NonNull.java @@ -31,4 +31,5 @@ @Retention(CLASS) @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE}) public @interface NonNull { -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/annotation/Nullable.java b/Common/src/main/java/android/annotation/Nullable.java index 75a9412d..1c6165e8 100644 --- a/Common/src/main/java/android/annotation/Nullable.java +++ b/Common/src/main/java/android/annotation/Nullable.java @@ -1,4 +1,3 @@ - /* * Copyright (C) 2013 The Android Open Source Project * @@ -42,4 +41,5 @@ @Target({METHOD, PARAMETER, FIELD}) // @SystemApi(client = Client.MODULE_LIBRARIES) public @interface Nullable { -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/annotation/Size.java b/Common/src/main/java/android/annotation/Size.java index f2de3177..bf17afcc 100644 --- a/Common/src/main/java/android/annotation/Size.java +++ b/Common/src/main/java/android/annotation/Size.java @@ -46,4 +46,6 @@ long max() default Long.MAX_VALUE; /** The size must be a multiple of this factor */ long multiple() default 1; -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/android/annotation/SuppressAutoDoc.java b/Common/src/main/java/android/annotation/SuppressAutoDoc.java index 3418d658..9e715f48 100644 --- a/Common/src/main/java/android/annotation/SuppressAutoDoc.java +++ b/Common/src/main/java/android/annotation/SuppressAutoDoc.java @@ -32,4 +32,5 @@ @Retention(SOURCE) @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) public @interface SuppressAutoDoc { -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/annotation/SuppressLint.java b/Common/src/main/java/android/annotation/SuppressLint.java index b51c3f0e..e7910de2 100644 --- a/Common/src/main/java/android/annotation/SuppressLint.java +++ b/Common/src/main/java/android/annotation/SuppressLint.java @@ -32,4 +32,5 @@ * ignored by lint. It is not an error to specify an unrecognized name. */ String[] value(); -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/annotation/SystemApi.java b/Common/src/main/java/android/annotation/SystemApi.java index 58d4dd9c..beede907 100644 --- a/Common/src/main/java/android/annotation/SystemApi.java +++ b/Common/src/main/java/android/annotation/SystemApi.java @@ -37,7 +37,7 @@ */ @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE}) @Retention(RetentionPolicy.RUNTIME) -@Repeatable(SystemApi.Container.class) // TODO(b/146727827): make this non-repeatable +@Repeatable(SystemApi.Container.class) public @interface SystemApi { enum Client { /** @@ -71,4 +71,5 @@ enum Client { @interface Container { SystemApi[] value(); } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/graphics/Bitmap.java b/Common/src/main/java/android/graphics/Bitmap.java index d8cf4e09..8457e2bc 100644 --- a/Common/src/main/java/android/graphics/Bitmap.java +++ b/Common/src/main/java/android/graphics/Bitmap.java @@ -1,24 +1,20 @@ /* * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. * - * 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: + * Portions of this file are derived from the Android Open Source Project, + * Copyright (C) 2006 The Android Open Source Project + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package android.graphics; @@ -243,4 +239,4 @@ public Config getConfig() { public void recycle() { theBitmap.close(); } -} +} \ No newline at end of file diff --git a/Common/src/main/java/android/graphics/Canvas.java b/Common/src/main/java/android/graphics/Canvas.java index 87d862bf..8a19391c 100644 --- a/Common/src/main/java/android/graphics/Canvas.java +++ b/Common/src/main/java/android/graphics/Canvas.java @@ -1,24 +1,20 @@ /* * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. * - * 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: + * Portions of this file are derived from the Android Open Source Project, + * Copyright (C) 2006 The Android Open Source Project + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package android.graphics; @@ -216,4 +212,4 @@ public int getHeight() { return providedHeight; } -} \ No newline at end of file +} diff --git a/Common/src/main/java/android/graphics/Color.java b/Common/src/main/java/android/graphics/Color.java index 02243cc7..1b04aedd 100644 --- a/Common/src/main/java/android/graphics/Color.java +++ b/Common/src/main/java/android/graphics/Color.java @@ -1480,4 +1480,5 @@ private static int nativeHSVToColor(int alpha, float hsv[]) { sColorNameMap.put("silver", 0xFFC0C0C0); sColorNameMap.put("teal", 0xFF008080); } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/graphics/ColorSpace.java b/Common/src/main/java/android/graphics/ColorSpace.java index e4559fe2..8ce2e725 100644 --- a/Common/src/main/java/android/graphics/ColorSpace.java +++ b/Common/src/main/java/android/graphics/ColorSpace.java @@ -3639,4 +3639,5 @@ public float[] transform(@NonNull @Size(min = 3) float[] v) { }; } } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/graphics/FontCache.java b/Common/src/main/java/android/graphics/FontCache.java index 71bd9256..283b7a49 100644 --- a/Common/src/main/java/android/graphics/FontCache.java +++ b/Common/src/main/java/android/graphics/FontCache.java @@ -1,24 +1,6 @@ /* * Copyright (c) 2023 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package android.graphics; @@ -49,3 +31,4 @@ static Font makeFont(Typeface typeface, float textSize) { } } + diff --git a/Common/src/main/java/android/graphics/Paint.java b/Common/src/main/java/android/graphics/Paint.java index 058922b1..6c163daa 100644 --- a/Common/src/main/java/android/graphics/Paint.java +++ b/Common/src/main/java/android/graphics/Paint.java @@ -1,30 +1,20 @@ -/* - * Some Original work Copyright (C) 2006 The Android Open Source Project - * Licensed under the Apache License, Version 2.0 (the "License"); - * https://www.apache.org/licenses/LICENSE-2.0 - */ - /* * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. * - * 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. + * Portions of this file are derived from the Android Open Source Project, + * Copyright (C) 2006 The Android Open Source Project + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package android.graphics; @@ -373,3 +363,4 @@ public float getTextSize() { } } + diff --git a/Common/src/main/java/android/graphics/Path.java b/Common/src/main/java/android/graphics/Path.java index 2db40082..8dfd4bd5 100644 --- a/Common/src/main/java/android/graphics/Path.java +++ b/Common/src/main/java/android/graphics/Path.java @@ -1,30 +1,20 @@ -/* - * Some Original work Copyright (C) 2006 The Android Open Source Project - * Licensed under the Apache License, Version 2.0 (the "License"); - * https://www.apache.org/licenses/LICENSE-2.0 - */ - /* * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. * - * 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. + * Portions of this file are derived from the Android Open Source Project, + * Copyright (C) 2006 The Android Open Source Project + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package android.graphics; @@ -226,3 +216,4 @@ public void addPath(Path path) { } } + diff --git a/Common/src/main/java/android/graphics/Rect.java b/Common/src/main/java/android/graphics/Rect.java index 3ebea575..80b9b54d 100644 --- a/Common/src/main/java/android/graphics/Rect.java +++ b/Common/src/main/java/android/graphics/Rect.java @@ -616,4 +616,5 @@ public void splitHorizontally(@NonNull Rect ...outSplits) { public org.jetbrains.skia.Rect toSkijaRect() { return new org.jetbrains.skia.Rect(left, top, right, bottom); } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/graphics/TemporaryBuffer.java b/Common/src/main/java/android/graphics/TemporaryBuffer.java index b98e3d9b..352a04a0 100644 --- a/Common/src/main/java/android/graphics/TemporaryBuffer.java +++ b/Common/src/main/java/android/graphics/TemporaryBuffer.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.graphics; import com.android.internal.util.ArrayUtils; @@ -17,11 +33,13 @@ public static char[] obtain(int len) { } return buf; } + public static void recycle(char[] temp) { if (temp.length > 1000) return; synchronized (TemporaryBuffer.class) { sTemp = temp; } } + private static char[] sTemp = null; -} \ No newline at end of file +} diff --git a/Common/src/main/java/android/graphics/Typeface.java b/Common/src/main/java/android/graphics/Typeface.java index bb9f078d..22e5a23a 100644 --- a/Common/src/main/java/android/graphics/Typeface.java +++ b/Common/src/main/java/android/graphics/Typeface.java @@ -1,24 +1,20 @@ /* * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. * - * 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: + * Portions of this file are derived from the Android Open Source Project, + * Copyright (C) 2006 The Android Open Source Project + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package android.graphics; @@ -71,3 +67,4 @@ private static Data loadDataFromResource(String resource) { } } + diff --git a/Common/src/main/java/android/hardware/Camera.java b/Common/src/main/java/android/hardware/Camera.java index e1811fdd..7b88bb4e 100644 --- a/Common/src/main/java/android/hardware/Camera.java +++ b/Common/src/main/java/android/hardware/Camera.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.hardware; public class Camera { @@ -56,3 +72,4 @@ public static class CameraInfo { }; } + diff --git a/Common/src/main/java/android/hardware/DataSpace.java b/Common/src/main/java/android/hardware/DataSpace.java index f8ca324f..9b7d6d8d 100644 --- a/Common/src/main/java/android/hardware/DataSpace.java +++ b/Common/src/main/java/android/hardware/DataSpace.java @@ -627,4 +627,5 @@ private DataSpace() {} @DataSpaceRange int range = dataSpace & RANGE_MASK; return range; } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/opengl/Matrix.java b/Common/src/main/java/android/opengl/Matrix.java index 709339ef..90feb7cc 100644 --- a/Common/src/main/java/android/opengl/Matrix.java +++ b/Common/src/main/java/android/opengl/Matrix.java @@ -850,4 +850,5 @@ public static void setLookAtM(float[] rm, int rmOffset, translateM(rm, rmOffset, -eyeX, -eyeY, -eyeZ); } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/util/ArraySet.java b/Common/src/main/java/android/util/ArraySet.java index 0ddae3a1..7bcc46d3 100644 --- a/Common/src/main/java/android/util/ArraySet.java +++ b/Common/src/main/java/android/util/ArraySet.java @@ -900,4 +900,5 @@ public boolean retainAll(Collection collection) { } return removed; } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/util/ContainerHelpers.java b/Common/src/main/java/android/util/ContainerHelpers.java index 8ee2ed87..7c57d3ab 100644 --- a/Common/src/main/java/android/util/ContainerHelpers.java +++ b/Common/src/main/java/android/util/ContainerHelpers.java @@ -48,4 +48,5 @@ static int binarySearch(long[] array, int size, long value) { } return ~lo; // value not present } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/util/Half.java b/Common/src/main/java/android/util/Half.java index f0666394..5cf94a51 100644 --- a/Common/src/main/java/android/util/Half.java +++ b/Common/src/main/java/android/util/Half.java @@ -844,4 +844,5 @@ public static String toString(@HalfFloat short h) { public static String toHexString(@HalfFloat short h) { return FP16.toHexString(h); } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/util/MapCollections.java b/Common/src/main/java/android/util/MapCollections.java index eb501515..390f5f47 100644 --- a/Common/src/main/java/android/util/MapCollections.java +++ b/Common/src/main/java/android/util/MapCollections.java @@ -484,4 +484,5 @@ public Collection getValues() { protected abstract V colSetValue(int index, V value); protected abstract void colRemoveAt(int index); protected abstract void colClear(); -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/util/Size.java b/Common/src/main/java/android/util/Size.java index b31f94f8..2072054a 100644 --- a/Common/src/main/java/android/util/Size.java +++ b/Common/src/main/java/android/util/Size.java @@ -137,4 +137,5 @@ public int hashCode() { } private final int mWidth; private final int mHeight; -} \ No newline at end of file +} + diff --git a/Common/src/main/java/android/util/SparseIntArray.java b/Common/src/main/java/android/util/SparseIntArray.java index 921b1292..84eb9d0d 100644 --- a/Common/src/main/java/android/util/SparseIntArray.java +++ b/Common/src/main/java/android/util/SparseIntArray.java @@ -281,4 +281,5 @@ public String toString() { buffer.append('}'); return buffer.toString(); } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/androidx/annotation/ColorInt.java b/Common/src/main/java/androidx/annotation/ColorInt.java index ca5942a2..056ac765 100644 --- a/Common/src/main/java/androidx/annotation/ColorInt.java +++ b/Common/src/main/java/androidx/annotation/ColorInt.java @@ -34,4 +34,6 @@ @Retention(CLASS) @Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD}) public @interface ColorInt { -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/androidx/annotation/NonNull.java b/Common/src/main/java/androidx/annotation/NonNull.java index d3c9d885..f4d8d11e 100644 --- a/Common/src/main/java/androidx/annotation/NonNull.java +++ b/Common/src/main/java/androidx/annotation/NonNull.java @@ -33,4 +33,5 @@ @Retention(CLASS) @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE}) public @interface NonNull { -} \ No newline at end of file +} + diff --git a/Common/src/main/java/androidx/annotation/Nullable.java b/Common/src/main/java/androidx/annotation/Nullable.java index 33feaf89..a5208a2f 100644 --- a/Common/src/main/java/androidx/annotation/Nullable.java +++ b/Common/src/main/java/androidx/annotation/Nullable.java @@ -39,4 +39,5 @@ @Retention(SOURCE) @Target({METHOD, PARAMETER, FIELD}) public @interface Nullable { -} \ No newline at end of file +} + diff --git a/Common/src/main/java/androidx/annotation/StringRes.java b/Common/src/main/java/androidx/annotation/StringRes.java index c6824fc9..888b13ee 100644 --- a/Common/src/main/java/androidx/annotation/StringRes.java +++ b/Common/src/main/java/androidx/annotation/StringRes.java @@ -30,4 +30,5 @@ @Retention(CLASS) @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) public @interface StringRes { -} \ No newline at end of file +} + diff --git a/Common/src/main/java/com/android/internal/util/ArrayUtils.java b/Common/src/main/java/com/android/internal/util/ArrayUtils.java index 4af45f05..1135a904 100644 --- a/Common/src/main/java/com/android/internal/util/ArrayUtils.java +++ b/Common/src/main/java/com/android/internal/util/ArrayUtils.java @@ -860,4 +860,5 @@ public static List toList(T[] array) { } return list; } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/com/android/internal/util/GrowingArrayUtils.java b/Common/src/main/java/com/android/internal/util/GrowingArrayUtils.java index 6ed02cd1..956fbba4 100644 --- a/Common/src/main/java/com/android/internal/util/GrowingArrayUtils.java +++ b/Common/src/main/java/com/android/internal/util/GrowingArrayUtils.java @@ -183,4 +183,5 @@ public static int growSize(int currentSize) { } // Uninstantiable private GrowingArrayUtils() {} -} \ No newline at end of file +} + diff --git a/Common/src/main/java/com/formdev/flatlaf/demo/HintManager.java b/Common/src/main/java/com/formdev/flatlaf/demo/HintManager.java index 92fd6552..d278ceda 100644 --- a/Common/src/main/java/com/formdev/flatlaf/demo/HintManager.java +++ b/Common/src/main/java/com/formdev/flatlaf/demo/HintManager.java @@ -349,4 +349,4 @@ private Shape createBalloonShape( int width, int height ) { return area; } } -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/CollapsiblePanelX.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/CollapsiblePanelX.kt index b7f27c3c..835c2d96 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/CollapsiblePanelX.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/CollapsiblePanelX.kt @@ -1,101 +1,62 @@ /* * Copyright (c) 2023 Sebastian Erives - * Credit where it's due - based off of https://stackoverflow.com/a/52956783 - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.gui.component +import java.awt.BorderLayout import java.awt.Color +import java.awt.FlowLayout import java.awt.Font -import java.awt.event.MouseAdapter -import java.awt.event.MouseEvent import javax.swing.BorderFactory -import javax.swing.JLabel +import javax.swing.JButton import javax.swing.JPanel -import javax.swing.border.TitledBorder class CollapsiblePanelX @JvmOverloads constructor( - title: String?, + var titleText: String?, titleCol: Color?, borderCol: Color? = Color.white -) : JPanel() { - private val border: TitledBorder +) : JPanel(BorderLayout()) { private var collapsible = true var isHidden = false private set - init { - val titleAndDescriptor = if(isHidden) { - "$title (click here to expand)" - } else { - "$title (click here to hide)" + val contentPanel = JPanel() + + private val toggleButton = JButton("Hide $titleText").apply { + isFocusable = false + if (titleCol != null) foreground = titleCol + + addActionListener { + if (!collapsible) return@addActionListener + isHidden = !isHidden + contentPanel.isVisible = !isHidden + text = if (isHidden) "Show $titleText" else "Hide $titleText" + this@CollapsiblePanelX.revalidate() } + } - border = TitledBorder(titleAndDescriptor) - - border.titleColor = titleCol - border.titleFont = border.titleFont.deriveFont(Font.BOLD) - border.border = BorderFactory.createMatteBorder(1, 1, 1, 1, borderCol) - - setBorder(border) - - // as Titleborder has no access to the Label we fake the size data ;) - val l = JLabel(titleAndDescriptor) - val size = l.getPreferredSize() - - addMouseListener(object : MouseAdapter() { - override fun mouseClicked(e: MouseEvent) { - if (!collapsible) { - return - } - - val i = getBorder().getBorderInsets(this@CollapsiblePanelX) - if (e.x < i.left + size.width && e.y < i.bottom + size.height) { - - for(c in components) { - c.isVisible = !isHidden - - border.title = if(isHidden) { - "$title (click here to expand)" - } else { - "$title (click here to hide)" - } + init { + border = BorderFactory.createMatteBorder(1, 1, 1, 1, borderCol) - isHidden = !isHidden - } + val headerPanel = JPanel(FlowLayout(FlowLayout.LEFT, 5, 0)).apply { + border = BorderFactory.createEmptyBorder(2, 2, 2, 5) + add(toggleButton) + } - revalidate() - e.consume() - } - } - }) + super.add(headerPanel, BorderLayout.NORTH) + super.add(contentPanel, BorderLayout.CENTER) } fun setCollapsible(collapsible: Boolean) { this.collapsible = collapsible + toggleButton.isVisible = collapsible } fun setTitle(title: String?) { - border.title = title + titleText = title + toggleButton.text = if (isHidden) "Show $titleText" else "Hide $titleText" } -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/PopupX.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/PopupX.kt index ea4d9412..db1c6059 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/PopupX.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/PopupX.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component import com.github.serivesmejia.eocvsim.gui.util.Corner @@ -165,4 +147,4 @@ class PopupX @JvmOverloads constructor(private val windowAncestor: Window, } -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/SliderX.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/SliderX.kt index bfb5e775..fdd76340 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/SliderX.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/component/SliderX.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component import com.qualcomm.robotcore.util.Range @@ -69,4 +51,4 @@ open class SliderX(private var minBound: Double, maximum = this.maxBound.roundToInt() } -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/Enums.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/Enums.kt index 26ba7737..ff831fb6 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/Enums.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/Enums.kt @@ -1,6 +1,11 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.util enum class Corner { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ValidCharactersDocumentFilter.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ValidCharactersDocumentFilter.kt index e75e7820..1ddc608d 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ValidCharactersDocumentFilter.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ValidCharactersDocumentFilter.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.util import javax.swing.text.AttributeSet @@ -65,4 +47,4 @@ class ValidCharactersDocumentFilter(val validCharacters: Array) : Document return false } -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/extension/SwingExt.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/extension/SwingExt.kt index db512e93..5a0eec39 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/extension/SwingExt.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/gui/util/extension/SwingExt.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.util.extension import javax.swing.JTextField @@ -38,4 +20,4 @@ var JTextField.documentFilter: DocumentFilter } set(value) { abstractDocument.documentFilter = value - } \ No newline at end of file + } diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/DefaultPipelineInstantiator.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/DefaultPipelineInstantiator.kt index 9fb206bd..1a529a1b 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/DefaultPipelineInstantiator.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/DefaultPipelineInstantiator.kt @@ -1,29 +1,11 @@ /* * Copyright (c) 2023 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.pipeline.instantiator -import io.github.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection +import org.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection import org.firstinspires.ftc.robotcore.external.Telemetry import org.openftc.easyopencv.OpenCvPipeline @@ -43,4 +25,4 @@ object DefaultPipelineInstantiator : PipelineInstantiator { override fun variableTunerTarget(pipeline: OpenCvPipeline) = pipeline -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/PipelineInstantiator.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/PipelineInstantiator.kt index aaaaaa5e..3c92b6fa 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/PipelineInstantiator.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/PipelineInstantiator.kt @@ -1,29 +1,11 @@ /* * Copyright (c) 2023 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.pipeline.instantiator -import io.github.deltacv.eocvsim.virtualreflect.VirtualReflection +import org.deltacv.eocvsim.virtualreflect.VirtualReflection import org.firstinspires.ftc.robotcore.external.Telemetry import org.openftc.easyopencv.OpenCvPipeline @@ -37,4 +19,4 @@ interface PipelineInstantiator { fun virtualReflectOf(pipeline: OpenCvPipeline): VirtualReflection fun variableTunerTarget(pipeline: OpenCvPipeline): Any? -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/plugin/output/PluginOutputHandler.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/plugin/output/PluginOutputHandler.kt new file mode 100644 index 00000000..51b861fc --- /dev/null +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/plugin/output/PluginOutputHandler.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.plugin.output + +import com.github.serivesmejia.eocvsim.util.event.ParamEventHandler + +/** + * Dialog control signal - structured events replacing magic string codes. + */ +sealed class PluginDialogSignal { + /** Show the plugin dialog (focus on output tab for messages) */ + object ShowOutput : PluginDialogSignal() + + /** Show the plugin dialog (focus on plugins manager tab) */ + object ShowPlugins : PluginDialogSignal() + + /** Hide the plugin dialog */ + object Hide : PluginDialogSignal() + + /** Enable the "Continue" button (user can proceed) */ + object EnableContinue : PluginDialogSignal() + + /** Disable the "Continue" button (user must wait) */ + object DisableContinue : PluginDialogSignal() +} + +/** + * Abstraction for plugin output and UI control. + * + * PluginManager is completely UI-agnostic and uses this handler to: + * - Send output messages to the UI + * - Signal dialog visibility and button state changes + * - Wait for user continuation (e.g., when user clicks "Continue" button) + * + * The actual UI dialog is owned by Visualizer, which subscribes to these + * event handlers and manages the dialog lifecycle. + * + * Design: No magic string codes, fully structured events. + */ +interface PluginOutputHandler { + + /** + * Event handler for all output messages. + * Subscribers (e.g., Visualizer) display these in the output area. + */ + val onOutput: ParamEventHandler + + /** + * Event handler for structured dialog control signals. + * Subscribers get explicit instructions for UI state (show/hide/button state). + */ + val onDialogSignal: ParamEventHandler + + /** + * Sends an output message (logged and emitted). + * + * @param message the message to send + */ + fun sendOutput(message: String) + + /** + * Convenience method to send a message with a newline. + * + * @param message the message to send + */ + fun sendOutputLine(message: String) = sendOutput(message + "\n") + + /** + * Sends a dialog control signal (show/hide/button state). + * + * @param signal the control signal + */ + fun sendDialogSignal(signal: PluginDialogSignal) + + /** + * Waits for continuation signal from the UI (e.g., when user clicks "Continue"). + * + * This is a coroutine-suspending function that awaits until the UI signals + * completion or the timeout expires. + * + * Must be called from a coroutine context. Blocks via suspension, not threads. + * + * @param timeoutMillis timeout in milliseconds (0 = wait indefinitely) + * @return true if completed by UI, false if timeout expired + * @throws CancellationException if the coroutine is cancelled + */ + suspend fun waitForContinuation(timeoutMillis: Long = 0L): Boolean + + /** + * Programmatically signal that continuation is complete. + */ + fun signalContinuation() +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java index 383785b0..c67638ca 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2024 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2024 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util; import org.slf4j.Logger; @@ -190,19 +172,19 @@ public static int execClasspath( String classpath, List jvmArgs, List args - ) throws InterruptedException, IOException { - + ) throws IOException { return execClasspathAsync(klass, receiver, classpath, jvmArgs, args) .join(); // wait synchronously } public static int exec(Class klass, List jvmArgs, List args) - throws InterruptedException, IOException { + throws IOException { return execClasspath(klass, null, System.getProperty("java.class.path"), jvmArgs, args); } public static int exec(Class klass, ProcessIOReceiver ioReceiver, List jvmArgs, List args) - throws InterruptedException, IOException { + throws IOException { return execClasspath(klass, ioReceiver, System.getProperty("java.class.path"), jvmArgs, args); } } + diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/ReflectUtil.java b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/ReflectUtil.java index 54906e0f..f88c6b8b 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/ReflectUtil.java +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/ReflectUtil.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util; import java.lang.annotation.Annotation; @@ -111,3 +93,4 @@ public static Class wrap(Class c) { } } + diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/StrUtil.java b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/StrUtil.java index 96f17c31..b95626c9 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/StrUtil.java +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/StrUtil.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util; import java.io.PrintWriter; @@ -136,3 +118,4 @@ public static int editDistance(String s1, String s2) { } + diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/SysUtil.java b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/SysUtil.java index b4f1855f..79528523 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/SysUtil.java +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/SysUtil.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util; import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder; @@ -374,3 +356,4 @@ public static class CommandResult { } } + diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventHandler.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventHandler.kt index afff697a..000e4a05 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventHandler.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventHandler.kt @@ -1,69 +1,72 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.event -import io.github.deltacv.common.util.loggerOf +import org.deltacv.common.util.loggerOf +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import java.util.concurrent.atomic.AtomicInteger /** - * Event handler with: - * - Persistent listeners (ID-based, queue-based adding) - * - Once listeners (double buffered queue-based, deferred) + * Generic parameterized event handler. Listeners receive a payload of type T. + * This is a new implementation; the original zero-arg `EventHandler` is kept + * for compatibility and extends this class with `T = Unit`. */ -class EventHandler(val name: String) : Runnable { +typealias ParamOnceEventListener = (T) -> Unit +typealias ParamEventListener = ParamEventListenerContext.(T) -> Unit + +class ParamEventListenerContext( + private val handler: ParamEventHandler, + val id: EventListenerId, +) { + fun removeListener() { + handler.removeListener(id) + } +} - // ------------------------------------------------------------ - // config - // ------------------------------------------------------------ +open class ParamEventHandler @JvmOverloads constructor( + val name: String, + var callRightAway: EventHandler.CallRightAway = EventHandler.CallRightAway.Disabled, + val catchExceptions: Boolean = true +) { private val logger by loggerOf("EventHandler-$name") - var callRightAway = false - - // ------------------------------------------------------------ // ids - // ------------------------------------------------------------ - - private val idCounter = AtomicInteger(Int.MIN_VALUE) - - // ------------------------------------------------------------ - // persistent listeners (stable + queues) - // ------------------------------------------------------------ - - private val persistentListeners = HashMap() - - private val persistentAddQueue = ArrayDeque>() - private val persistentRemoveQueue = ArrayDeque() - - private val persistentQueueLock = Any() - - private val persistentContextCache = HashMap() - - // ------------------------------------------------------------ - // once listeners (swappable double buffer) - // ------------------------------------------------------------ - - private var onceListenersCurrent = ArrayDeque() - private var onceIdsCurrent = ArrayDeque() - - private var onceListenersQueue = ArrayDeque() - private var onceIdsQueue = ArrayDeque() - - private val onceLock = Any() - - // ------------------------------------------------------------ - // run - // ------------------------------------------------------------ - - override fun run() { - runPersistentListeners() - runOnceListeners() + protected val idCounter = AtomicInteger(Int.MIN_VALUE) + + // persistent listeners + protected val persistentListeners = HashMap>() + protected val persistentAddQueue = ArrayDeque>>() + protected val persistentRemoveQueue = ArrayDeque() + protected val persistentQueueLock = Any() + protected val persistentContextCache = HashMap>() + + // once listeners + protected var onceListenersCurrent = ArrayDeque>() + protected var onceIdsCurrent = ArrayDeque() + + protected var onceListenersQueue = ArrayDeque>() + protected var onceIdsQueue = ArrayDeque() + protected val onceLock = Any() + + // run with payload + fun run(payload: T) { + runPersistentListeners(payload) + runOnceListeners(payload) } - // ------------------------------------------------------------ - // persistent execution - // ------------------------------------------------------------ + // public attach/once for payload listeners (named to avoid conflict with + // legacy zero-arg EventHandler overloads) + fun attachPayload(listener: ParamEventListener): EventListenerId = attachRaw(listener) + + fun oncePayload(listener: ParamOnceEventListener): EventListenerId = onceRaw(listener) - fun runPersistentListeners() { - // apply pending adds/removes + protected fun runPersistentListeners(payload: T) { synchronized(persistentQueueLock) { while (persistentAddQueue.isNotEmpty()) { val (id, listener) = persistentAddQueue.removeFirst() @@ -76,28 +79,30 @@ class EventHandler(val name: String) : Runnable { } } - // execute + fun run(id: Int, listener: ParamEventListener) { + val remover = persistentContextCache.getOrPut(id) { ParamEventListenerContext(this, EventListenerId(id)) } + listener(remover, payload) + } + for ((id, listener) in persistentListeners) { - try { - val remover = persistentContextCache.getOrPut(id) { EventListenerContext(this, EventListenerId(id)) } - listener(remover) - } catch (e: Exception) { - if (e is InterruptedException) throw e - logger.error("Exception in listener", e) + if (catchExceptions) { + try { + run(id, listener) + } catch (e: Exception) { + if (e is InterruptedException) throw e + logger.error("Exception in listener", e) + } + } else { + run(id, listener) } } } - // ------------------------------------------------------------ - // once execution - // ------------------------------------------------------------ - - fun runOnceListeners() { - val toRunListeners: ArrayDeque + protected fun runOnceListeners(payload: T) { + val toRunListeners: ArrayDeque> val toRunIds: ArrayDeque synchronized(onceLock) { - // swap toRunListeners = onceListenersQueue toRunIds = onceIdsQueue @@ -110,13 +115,17 @@ class EventHandler(val name: String) : Runnable { while (toRunListeners.isNotEmpty()) { val listener = toRunListeners.removeFirst() - toRunIds.removeFirst() // keep in sync - - try { - listener() - } catch (e: Exception) { - if (e is InterruptedException) throw e - logger.error("Exception in once listener", e) + toRunIds.removeFirst() + + if (catchExceptions) { + try { + listener(payload) + } catch (e: Exception) { + if (e is InterruptedException) throw e + logger.error("Exception in once listener", e) + } + } else { + listener(payload) } } @@ -124,60 +133,41 @@ class EventHandler(val name: String) : Runnable { toRunIds.clear() } - // ------------------------------------------------------------ - // attach - // ------------------------------------------------------------ - - operator fun invoke(listener: EventListener): EventListenerId = attach(listener) - - fun attach(listener: EventListener): EventListenerId { + // attach/once that don't implement callRightAway semantics; subclasses + // (like the legacy EventHandler) may implement immediate invocation. + protected fun attachRaw(listener: ParamEventListener): EventListenerId { val id = EventListenerId(idCounter.getAndIncrement()) - synchronized(persistentQueueLock) { persistentAddQueue.addLast(id.value to listener) } - - if (callRightAway) { - listener(EventListenerContext(this, id)) - } - return id } - fun once(listener: OnceEventListener): EventListenerId { + protected fun onceRaw(listener: ParamOnceEventListener): EventListenerId { val id = EventListenerId(idCounter.getAndIncrement()) - if (callRightAway) { - listener() - } else { - synchronized(onceLock) { - onceListenersQueue.addLast(listener) - onceIdsQueue.addLast(id.value) - } + // For the generic/payload handler we don't attempt to call listeners + // immediately since we don't have a payload value here. Always enqueue. + synchronized(onceLock) { + onceListenersQueue.addLast(listener) + onceIdsQueue.addLast(id.value) } return id } - @JvmName("attach") - fun attach(runnable: Runnable): EventListenerId = attach{ runnable.run() } + protected fun attachRunnable(runnable: Runnable): EventListenerId = attachRaw { runnable.run() } - @JvmName("once") - fun once(runnable: Runnable): EventListenerId = once { runnable.run() } + protected fun onceRunnable(runnable: Runnable): EventListenerId = onceRaw { runnable.run() } - // ------------------------------------------------------------ // remove - // ------------------------------------------------------------ - - @JvmName("removeListener") - fun removeListener(id: EventListenerId) { + open fun removeListener(id: EventListenerId) { synchronized(onceLock) { val index = onceIdsQueue.indexOf(id.value) if (index >= 0) { onceIdsQueue.removeAt(index) onceListenersQueue.removeAt(index) - - return@removeListener // removed from once queue, no need to check persistent + return } } @@ -200,4 +190,92 @@ class EventHandler(val name: String) : Runnable { onceIdsQueue.clear() } } -} \ No newline at end of file +} + +/** + * Legacy zero-arg EventHandler kept for compatibility. It extends the + * parameterized implementation with T = Unit and provides the exact old API. + */ + +class EventHandler @JvmOverloads constructor( + name: String, + callRightAway: CallRightAway = CallRightAway.Disabled, + catchExceptions: Boolean = true +) : ParamEventHandler(name, callRightAway, catchExceptions), Runnable { + + private val logger by loggerOf("EventHandler-$name") + + override fun run() { + run(Unit) + } + + // ------------------------------------------------------------ + // attach (legacy API) + // ------------------------------------------------------------ + operator fun invoke(listener: EventListener): EventListenerId = attach(listener) + + fun attach(listener: EventListener): EventListenerId { + // wrapper adapts legacy EventListenerContext receiver to ParamEventListenerContext + val wrapper: ParamEventListener = { _ -> + // `this` is ParamEventListenerContext + val ctx = EventListenerContext(this@EventHandler, EventListenerId(this.id.value)) + listener(ctx) + } + + val id = attachRaw(wrapper) + + when (val mode = callRightAway) { + CallRightAway.InPlace -> listener(EventListenerContext(this, id)) + is CallRightAway.InScope -> { + val job = mode.scope.launch { + listener(EventListenerContext(this@EventHandler, id)) + } + if (mode.shouldJoin) runBlocking { job.join() } + } + CallRightAway.Disabled -> {} + } + + return id + } + + fun once(listener: OnceEventListener): EventListenerId { + val wrapper: ParamOnceEventListener = { _ -> listener() } + + val id = onceRaw(wrapper) + + when (val mode = callRightAway) { + CallRightAway.InPlace -> listener() + is CallRightAway.InScope -> { + val job = mode.scope.launch { listener() } + if (mode.shouldJoin) runBlocking { job.join() } + } + CallRightAway.Disabled -> {} + } + + return id + } + + @JvmName("attach") + fun attach(runnable: Runnable): EventListenerId = super.attachRunnable(runnable) + + @JvmName("once") + fun once(runnable: Runnable): EventListenerId = super.onceRunnable(runnable) + + // reuse removeListener from ParamEventHandler + + sealed interface CallRightAway { + data class InScope(val scope: CoroutineScope, val shouldJoin: Boolean = false) : CallRightAway + object InPlace : CallRightAway + object Disabled : CallRightAway + } + + companion object { + fun batchOnce(vararg eventHandler: EventHandler, listener: OnceEventListener): List { + return eventHandler.map { it.once(listener) } + } + + fun batchAttach(vararg eventHandler: EventHandler, listener: EventListener): List { + return eventHandler.map { it.attach(listener) } + } + } +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventListener.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventListener.kt index 6ee2196b..a8f2f0c9 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventListener.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventListener.kt @@ -1,24 +1,6 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.util.event @@ -39,11 +21,10 @@ class EventListenerContext( private val handler: EventHandler, private val id: EventListenerId, ) { - /** * Removes the listener from the event handler */ fun removeListener() { handler.removeListener(id) } -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/FileExt.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/FileExt.kt index fa6010bd..a172ea57 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/FileExt.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/FileExt.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.extension import java.io.File @@ -50,4 +32,4 @@ fun byteArrayToHex(bytes: ByteArray): String { sb.append(hex[b.toInt() and 0x0F]) } return sb.toString() -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/NumberExt.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/NumberExt.kt index 194119a5..72b75484 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/NumberExt.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/NumberExt.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.extension /** @@ -37,4 +19,4 @@ fun Int.clipUpperZero(): Int { /** * Clip something like a List size to be an index */ -val Int.zeroBased get() = (this - 1).clipUpperZero() \ No newline at end of file +val Int.zeroBased get() = (this - 1).clipUpperZero() diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/StrExt.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/StrExt.kt index 55bbbd52..36043f20 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/StrExt.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/extension/StrExt.kt @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.extension import java.security.MessageDigest @@ -20,4 +25,4 @@ val String.hashString: String get() { val messageDigest = MessageDigest.getInstance("SHA-256") val hash = messageDigest.digest(toByteArray()) return byteArrayToHex(hash) -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsCounter.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsCounter.kt index 0f422e99..c22d473e 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsCounter.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsCounter.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.fps import com.qualcomm.robotcore.util.ElapsedTime @@ -55,4 +37,4 @@ class FpsCounter { } } -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsLimiter.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsLimiter.kt index 1c36b284..d8e0110e 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsLimiter.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsLimiter.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.fps class FpsLimiter(var maxFPS: Double = 30.0) { @@ -39,4 +21,4 @@ class FpsLimiter(var maxFPS: Double = 30.0) { start = System.currentTimeMillis().toDouble() } -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/io/EOCVSimFolder.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/io/EOCVSimFolder.kt index ede1673a..ff1ead47 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/io/EOCVSimFolder.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/io/EOCVSimFolder.kt @@ -1,7 +1,12 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.io import com.github.serivesmejia.eocvsim.util.extension.appData -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import java.io.File /** @@ -49,4 +54,4 @@ object EOCVSimFolder : File(appData.absolutePath + separator + ".eocvsim") { } } -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/io/Lock.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/io/Lock.kt index 591ad141..fecf254b 100644 --- a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/io/Lock.kt +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/io/Lock.kt @@ -1,6 +1,11 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.io -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import java.io.File import java.io.RandomAccessFile import java.nio.channels.FileLock @@ -100,4 +105,4 @@ fun File.lockDirectory(): LockFile? { return null return lockFile -} \ No newline at end of file +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/orchestration/Orchestrable.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/orchestration/Orchestrable.kt new file mode 100644 index 00000000..68cf5bb9 --- /dev/null +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/orchestration/Orchestrable.kt @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +@file:Suppress("unused") + +package com.github.serivesmejia.eocvsim.util.orchestration + +import kotlin.reflect.KClass + +interface Orchestrable { + fun wire(orchestrator: Orchestrator) +} + +class PhaseDependencies { + private val _initDependencies = mutableListOf() + private val _runDependencies = mutableListOf() + private val _destroyDependencies = mutableListOf() + + val initDependencies: List get() = _initDependencies + val runDependencies: List get() = _runDependencies + val destroyDependencies: List get() = _destroyDependencies + + /** + * @param phase if empty, will bind to all phases + */ + fun dependency(target: KClass, vararg phase: Orchestrator.Phase): KClass { + if (phase.isEmpty()) { + initDependency(target) + runDependency(target) + destroyDependency(target) + } else { + for (p in phase) { + when (p) { + Orchestrator.Phase.INIT -> initDependency(target) + Orchestrator.Phase.RUN -> runDependency(target) + Orchestrator.Phase.DESTROY -> destroyDependency(target) + } + } + } + + return target + } + + fun dependency(target: T, vararg phase: Orchestrator.Phase): T { + if (phase.isEmpty()) { + initDependency(target) + runDependency(target) + destroyDependency(target) + } else { + for (p in phase) { + when (p) { + Orchestrator.Phase.INIT -> initDependency(target) + Orchestrator.Phase.RUN -> runDependency(target) + Orchestrator.Phase.DESTROY -> destroyDependency(target) + } + } + } + + return target + } + + inline fun dependency(target: Lazy, vararg phase: Orchestrator.Phase): Lazy { + dependency(T::class, *phase) + return target + } + + // -- INIT DEPENDENCY SUGAR -- + + fun initDependency(target: KClass): KClass { + _initDependencies.add(Orchestrator.Dependency.Class(target)) + return target + } + + fun initDependency(target: T): T { + _initDependencies.add(Orchestrator.Dependency.Instance(target)) + return target + } + + inline fun initDependency(target: Lazy): Lazy { + initDependency(T::class) + return target + } + + // -- RUN DEPENDENCY SUGAR -- + + fun runDependency(target: KClass): KClass { + _runDependencies.add(Orchestrator.Dependency.Class(target)) + return target + } + + fun runDependency(target: T): T { + _runDependencies.add(Orchestrator.Dependency.Instance(target)) + return target + } + + inline fun runDependency(target: Lazy): Lazy { + runDependency(T::class) + return target + } + + // -- DESTROY DEPENDENCY SUGAR -- + + fun destroyDependency(target: KClass): KClass { + _destroyDependencies.add(Orchestrator.Dependency.Class(target)) + return target + } + + fun destroyDependency(target: T): T { + _destroyDependencies.add(Orchestrator.Dependency.Instance(target)) + return target + } + + inline fun destroyDependency(target: Lazy): Lazy { + destroyDependency(T::class) + return target + } +} + +interface PhaseOrchestrable : Orchestrable { + val phaseDependencies: PhaseDependencies + + suspend fun init() + suspend fun run() + suspend fun destroy() + + override fun wire(orchestrator: Orchestrator) { + orchestrator.register(this) { + phase(Orchestrator.Phase.INIT) { + target { init() } + dependsOn(*phaseDependencies.initDependencies.toTypedArray()) + } + + phase(Orchestrator.Phase.RUN) { + target { run() } + dependsOn(*phaseDependencies.runDependencies.toTypedArray()) + } + + phase(Orchestrator.Phase.DESTROY) { + target { destroy() } + dependsOn(*phaseDependencies.destroyDependencies.toTypedArray()) + } + } + } +} + +/** + * Dependency helper extensions for any [PhaseOrchestrable]. + */ + +/** + * @param phase if empty, will bind to all phases + */ +fun PhaseOrchestrable.dependency(target: KClass, vararg phase: Orchestrator.Phase): KClass { + phaseDependencies.dependency(target, *phase) + return target +} + +fun PhaseOrchestrable.dependency(target: T, vararg phase: Orchestrator.Phase): T { + phaseDependencies.dependency(target, *phase) + return target +} + +inline fun PhaseOrchestrable.dependency(target: Lazy, vararg phase: Orchestrator.Phase): Lazy { + phaseDependencies.dependency(T::class, *phase) + return target +} + +// -- INIT DEPENDENCY SUGAR -- + +fun PhaseOrchestrable.initDependency(target: KClass): KClass = phaseDependencies.initDependency(target) + +fun PhaseOrchestrable.initDependency(target: T): T = phaseDependencies.initDependency(target) + +inline fun PhaseOrchestrable.initDependency(target: Lazy): Lazy { + phaseDependencies.initDependency(T::class) + return target +} + +// -- RUN DEPENDENCY SUGAR -- + +fun PhaseOrchestrable.runDependency(target: KClass): KClass = phaseDependencies.runDependency(target) + +fun PhaseOrchestrable.runDependency(target: T): T = phaseDependencies.runDependency(target) + +inline fun PhaseOrchestrable.runDependency(target: Lazy): Lazy { + phaseDependencies.runDependency(T::class) + return target +} + +// -- DESTROY DEPENDENCY SUGAR -- + +fun PhaseOrchestrable.destroyDependency(target: KClass): KClass = phaseDependencies.destroyDependency(target) + +fun PhaseOrchestrable.destroyDependency(target: T): T = phaseDependencies.destroyDependency(target) + +inline fun PhaseOrchestrable.destroyDependency(target: Lazy): Lazy { + phaseDependencies.destroyDependency(T::class) + return target +} + +abstract class PhaseOrchestrableBase : PhaseOrchestrable { + final override val phaseDependencies: PhaseDependencies = PhaseDependencies() +} diff --git a/Common/src/main/java/com/github/serivesmejia/eocvsim/util/orchestration/Orchestrator.kt b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/orchestration/Orchestrator.kt new file mode 100644 index 00000000..9dd7113c --- /dev/null +++ b/Common/src/main/java/com/github/serivesmejia/eocvsim/util/orchestration/Orchestrator.kt @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +@file:Suppress("unused") + +package com.github.serivesmejia.eocvsim.util.orchestration + +import org.deltacv.common.util.loggerOf +import kotlinx.coroutines.* +import kotlin.reflect.KClass + +class Orchestrator( + private val scope: CoroutineScope, + val tasks: List = emptyList(), + val name: String? = null, +) { + + enum class Phase { + INIT, + RUN, + DESTROY + } + + sealed class Dependency { + data class Instance(val target: Orchestrable) : Dependency() + data class Class(val target: KClass) : Dependency() + } + + private val logger by loggerOf("Orchestrator:${name ?: Integer.toHexString(hashCode())}") + + data class Registration( + val instance: Orchestrable, + val phases: Map + ) { + data class PhaseRegistration( + val target: suspend () -> Unit, + val dependencies: List + ) + } + + class RegistrationCtx internal constructor( + val instance: T + ) { + + private data class MutablePhaseRegistration( + val dependencyInstances: MutableList = mutableListOf(), + var targetRunner: (suspend (T) -> Unit)? = null + ) + + private val phaseRegistrations = mutableMapOf>() + private var activePhase: Phase = Phase.INIT + + private fun currentPhaseRegistration(): MutablePhaseRegistration { + return phaseRegistrations.getOrPut(activePhase) { MutablePhaseRegistration() } + } + + fun phase(phase: Phase, block: RegistrationCtx.() -> Unit) { + val previous = activePhase + activePhase = phase + try { + block() + } finally { + activePhase = previous + } + } + + fun dependsOn(vararg targets: Orchestrable) { + for (target in targets) { + addDependency(Dependency.Instance(target)) + } + } + + fun dependsOn(vararg targets: KClass) { + for (target in targets) { + addDependency(Dependency.Class(target)) + } + } + + fun dependsOn(vararg dependencies: Dependency) { + for (dependency in dependencies) { + addDependency(dependency) + } + } + + inline fun dependsOn() { + addDependency(Dependency.Class(D::class)) + } + + @PublishedApi + internal fun addDependency(dependency: Dependency) { + val current = currentPhaseRegistration().dependencyInstances + if (current.none { matchesDependency(it, dependency) }) { + current.add(dependency) + } + } + + private fun matchesDependency(left: Dependency, right: Dependency): Boolean { + return when (left) { + is Dependency.Instance -> when (right) { + is Dependency.Instance -> left.target === right.target + is Dependency.Class -> false + } + is Dependency.Class -> when (right) { + is Dependency.Instance -> false + is Dependency.Class -> left.target == right.target + } + } + } + + fun target(block: suspend T.() -> Unit) { + currentPhaseRegistration().targetRunner = block + } + + internal fun build(): Registration { + val builtPhases = phaseRegistrations.mapNotNull { (phase, phaseRegistration) -> + val runner = phaseRegistration.targetRunner ?: return@mapNotNull null + + phase to Registration.PhaseRegistration( + target = { runner(instance) }, + dependencies = phaseRegistration.dependencyInstances.toList() + ) + }.toMap() + + if (builtPhases.isEmpty()) { + throw IllegalArgumentException("register { } requires at least one phase target { ... }") + } + + return Registration( + instance = instance, + phases = builtPhases + ) + } + } + + private val registrations = mutableListOf() + private var tasksWired = false + private var currentPhase: Phase = Phase.INIT + private var runPhaseStarted = false + private val executionGraphLoggedPhases = mutableSetOf() + private var runGraphLogged = false + + fun changePhase(phase: Phase) { + if (phase == currentPhase) { + return + } + + if (currentPhase == Phase.RUN && runPhaseStarted) { + logger.info("Orchestration phase RUN finished successfully") + runPhaseStarted = false + } + + currentPhase = phase + } + + fun getPhase(): Phase = currentPhase + + private fun ensureTasksWired() { + if (tasksWired) { + return + } + + if (tasks.isNotEmpty()) { + logger.debug("Wiring ${tasks.size} task(s) into orchestrator") + } + + tasks.forEach { task -> + task.wire(this) + } + + tasksWired = true + } + + fun register(targetInstance: T, block: RegistrationCtx.() -> Unit = {}) { + val registration = RegistrationCtx(targetInstance).apply(block).build() + register(registration) + } + + fun register(registration: Registration) { + if (registrations.any { it.instance === registration.instance }) { + throw IllegalArgumentException( + "Target instance already registered: ${registration.instance::class.qualifiedName}" + ) + } + + registrations.add(registration) + + logger.debug( + "Registered target ${registration.instance::class.qualifiedName} with ${registration.phases.size} phase(s)" + ) + } + + fun orchestrate() { + ensureTasksWired() + + val phase = currentPhase + + runBlocking(scope.coroutineContext) { + val verboseDebug = phase != Phase.RUN + val logLifecycle = beginLifecycleLog(phase) + + val phaseRegistrations = activeRegistrationsFor(phase) + + logExecutionGraphOnce(phase, phaseRegistrations) + + if (logLifecycle) { + logger.info("Starting orchestration phase $phase with ${phaseRegistrations.size} registration(s)") + } + validateDependenciesExist(phase, phaseRegistrations, verboseDebug) + + val pending = phaseRegistrations.toMutableList() + val executedTargets = mutableListOf() + var wave = 0 + + while (pending.isNotEmpty()) { + wave++ + + val ready = pending.filter { registration -> + registration.dependencies.all { dependency -> + executedTargets.any { executed -> matchesDependency(executed, dependency) } + } + } + + if (verboseDebug) { + logger.debug( + "Wave $wave -> pending=${pending.size}, ready=${ready.size}, executed=${executedTargets.size}" + ) + } + + if (verboseDebug && ready.isNotEmpty()) { + val readyNames = ready.joinToString { it.instance::class.qualifiedName ?: "" } + logger.debug("Wave $wave ready targets: $readyNames") + } + + if (ready.isEmpty()) { + val remaining = pending.joinToString { it.instance::class.qualifiedName ?: "" } + logger.error("Deadlock/cycle detected in phase $phase, remaining: $remaining") + + throw IllegalStateException( + "Circular dependency detected in phase $phase among remaining targets: $remaining" + ) + } + + // Execute this dependency wave in parallel. + runBatchInParallel(ready, wave, verboseDebug) + + for (registration in ready) { + executedTargets.add(registration.instance) + pending.remove(registration) + } + } + + if (logLifecycle && phase != Phase.RUN) { + logger.info("Orchestration phase $phase finished successfully") + } + } + } + + fun orchestrate(phase: Phase) { + changePhase(phase) + orchestrate() + } + + private fun beginLifecycleLog(phase: Phase): Boolean { + if (phase != Phase.RUN) { + return true + } + + if (runPhaseStarted) { + return false + } + + runPhaseStarted = true + return true + } + + private fun activeRegistrationsFor(phase: Phase): List { + return registrations.mapNotNull { registration -> + val phaseRegistration = registration.phases[phase] ?: return@mapNotNull null + ActiveRegistration( + instance = registration.instance, + target = phaseRegistration.target, + dependencies = phaseRegistration.dependencies + ) + } + } + + private fun logExecutionGraphOnce(phase: Phase, phaseRegistrations: List) { + if (phase in executionGraphLoggedPhases) { + return + } + + executionGraphLoggedPhases.add(phase) + + logger.debug("Execution graph plan for phase {}:", phase) + logger.debug("Phase {}: {} target(s)", phase, phaseRegistrations.size) + + if (phaseRegistrations.isEmpty()) { + return + } + + val pending = phaseRegistrations.toMutableList() + val executedTargets = mutableListOf() + var wave = 0 + + while (pending.isNotEmpty()) { + wave++ + + val ready = pending.filter { registration -> + registration.dependencies.all { dependency -> + executedTargets.any { executed -> matchesDependency(executed, dependency) } + } + } + + if (ready.isEmpty()) { + val remaining = pending.joinToString { it.instance::class.qualifiedName ?: "" } + logger.debug("Phase {} wave {}: remaining=[{}]", phase, wave, remaining) + break + } + + val readyNames = ready.joinToString { it.instance::class.qualifiedName ?: "" } + logger.debug("Phase {} wave {} (parallel={}): [{}]", phase, wave, ready.size, readyNames) + + for (registration in ready) { + executedTargets.add(registration.instance) + pending.remove(registration) + } + } + } + + private fun logRunGraphOnce(phaseRegistrations: List) { + if (runGraphLogged) { + return + } + + runGraphLogged = true + + logger.debug("RUN graph has ${phaseRegistrations.size} registration(s)") + phaseRegistrations.forEach { registration -> + val target = registration.instance::class.qualifiedName ?: "" + val deps = if (registration.dependencies.isEmpty()) { + "" + } else { + registration.dependencies.joinToString { dependency -> + when (dependency) { + is Dependency.Instance -> dependency.target::class.qualifiedName ?: "" + is Dependency.Class -> dependency.target.qualifiedName ?: "" + } + } + } + + logger.debug("RUN node: $target dependsOn [$deps]") + } + } + + private data class ActiveRegistration( + val instance: Orchestrable, + val target: suspend () -> Unit, + val dependencies: List + ) + + private suspend fun runBatchInParallel( + registrations: List, + wave: Int, + verboseDebug: Boolean + ) = coroutineScope { + val start = System.nanoTime() + if (verboseDebug) { + logger.debug("Wave $wave launching ${registrations.size} parallel target(s)") + } + + registrations.map { registration -> + async { + val targetName = registration.instance::class.qualifiedName ?: "" + val targetStart = System.nanoTime() + + if (verboseDebug) { + logger.debug("Wave $wave start target $targetName") + } + invokeTarget(registration) + + val targetMs = (System.nanoTime() - targetStart) / 1_000_000 + if (verboseDebug) { + logger.debug("Wave $wave finished target $targetName in ${targetMs}ms") + } + } + }.awaitAll() + + val batchMs = (System.nanoTime() - start) / 1_000_000 + if (verboseDebug) { + logger.debug("Wave $wave completed in ${batchMs}ms") + } + } + + private fun validateDependenciesExist( + phase: Phase, + phaseRegistrations: List, + verboseDebug: Boolean + ) { + if (verboseDebug) { + logger.debug("Validating dependencies for phase {}", phase) + } + + for (registration in phaseRegistrations) { + for (dependency in registration.dependencies) { + val exists = phaseRegistrations.any { candidate -> + matchesDependency(candidate.instance, dependency) + } + + if (!exists) { + val dependencyName = when (dependency) { + is Dependency.Instance -> dependency.target::class.qualifiedName + is Dependency.Class -> dependency.target.qualifiedName + } + + logger.error( + "Unresolved dependency $dependencyName required by ${registration.instance::class.qualifiedName} in phase $phase" + ) + + throw IllegalStateException( + "Unresolved dependency $dependencyName required by ${registration.instance::class.simpleName} in phase $phase" + ) + } + } + } + + if (verboseDebug) { + logger.debug("Dependency validation passed for phase {}", phase) + } + } + + private suspend fun invokeTarget(registration: ActiveRegistration) { + try { + registration.target() + } catch (ex: Exception) { + val targetName = registration.instance::class.qualifiedName ?: "" + logger.error("Invocation failed for $targetName", ex) + + throw IllegalStateException( + "Invocation failed for $targetName", + ex + ) + } + } + + private fun matchesDependency(candidate: Orchestrable, dependency: Dependency): Boolean { + return when (dependency) { + is Dependency.Instance -> candidate === dependency.target + is Dependency.Class -> dependency.target.isInstance(candidate) + } + } +} + diff --git a/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/Autonomous.java b/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/Autonomous.java index 71447a1c..e633428e 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/Autonomous.java +++ b/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/Autonomous.java @@ -72,4 +72,5 @@ * @return see above */ String preselectTeleOp() default ""; -} \ No newline at end of file +} + diff --git a/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/Disabled.java b/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/Disabled.java index 398baf99..a741fb3d 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/Disabled.java +++ b/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/Disabled.java @@ -52,4 +52,5 @@ @Retention(RetentionPolicy.RUNTIME) public @interface Disabled { -} \ No newline at end of file +} + diff --git a/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/TeleOp.java b/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/TeleOp.java index 3e160911..9d10c9b8 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/TeleOp.java +++ b/Common/src/main/java/com/qualcomm/robotcore/eventloop/opmode/TeleOp.java @@ -63,4 +63,5 @@ * @return the group into which the annotated OpMode is to be categorized */ String group() default ""; -} \ No newline at end of file +} + diff --git a/Common/src/main/java/com/qualcomm/robotcore/exception/RobotCoreException.java b/Common/src/main/java/com/qualcomm/robotcore/exception/RobotCoreException.java index 2f983153..8e305b27 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/exception/RobotCoreException.java +++ b/Common/src/main/java/com/qualcomm/robotcore/exception/RobotCoreException.java @@ -51,4 +51,5 @@ public RobotCoreException(String format, Object... args) { public static RobotCoreException createChained(Exception e, String format, Object... args) { return new RobotCoreException(String.format(format, args), e); } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/com/qualcomm/robotcore/util/ElapsedTime.java b/Common/src/main/java/com/qualcomm/robotcore/util/ElapsedTime.java index 850b0944..cad1179f 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/util/ElapsedTime.java +++ b/Common/src/main/java/com/qualcomm/robotcore/util/ElapsedTime.java @@ -266,4 +266,6 @@ public enum Resolution { SECONDS, MILLISECONDS } -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/com/qualcomm/robotcore/util/MovingStatistics.java b/Common/src/main/java/com/qualcomm/robotcore/util/MovingStatistics.java index 5b2ec6f6..6d709904 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/util/MovingStatistics.java +++ b/Common/src/main/java/com/qualcomm/robotcore/util/MovingStatistics.java @@ -122,4 +122,6 @@ public void add(double x) this.statistics.remove(this.samples.remove()); } } -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/com/qualcomm/robotcore/util/Range.java b/Common/src/main/java/com/qualcomm/robotcore/util/Range.java index 16f5eb28..e82fa86c 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/util/Range.java +++ b/Common/src/main/java/com/qualcomm/robotcore/util/Range.java @@ -1,4 +1,3 @@ - /* * Copyright (c) 2014, 2015 Qualcomm Technologies Inc * @@ -155,4 +154,5 @@ public static void throwIfRangeIsInvalid(int number, int min, int max) throws Il String.format("number %d is invalid; valid ranges are %d..%d", number, min, max)); } } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/com/qualcomm/robotcore/util/RobotLog.java b/Common/src/main/java/com/qualcomm/robotcore/util/RobotLog.java index b41530c2..aad21c83 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/util/RobotLog.java +++ b/Common/src/main/java/com/qualcomm/robotcore/util/RobotLog.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2014, 2015 Qualcomm Technologies Inc + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * (subject to the limitations in the disclaimer below) provided that the following conditions are + * met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Qualcomm Technologies Inc nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS + * SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.qualcomm.robotcore.util; import org.slf4j.Logger; @@ -15,3 +45,4 @@ public static void ee(String tag, String message) { LoggerFactory.getLogger(tag).error(message); } } + diff --git a/Common/src/main/java/com/qualcomm/robotcore/util/SerialNumber.java b/Common/src/main/java/com/qualcomm/robotcore/util/SerialNumber.java index 158c4ac1..07216797 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/util/SerialNumber.java +++ b/Common/src/main/java/com/qualcomm/robotcore/util/SerialNumber.java @@ -104,3 +104,6 @@ public int hashCode() { } } + + + diff --git a/Common/src/main/java/com/qualcomm/robotcore/util/SortOrder.java b/Common/src/main/java/com/qualcomm/robotcore/util/SortOrder.java index bc4fbcf8..1ef868ad 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/util/SortOrder.java +++ b/Common/src/main/java/com/qualcomm/robotcore/util/SortOrder.java @@ -19,6 +19,7 @@ * SOFTWARE. */ + package com.qualcomm.robotcore.util; public enum SortOrder @@ -26,3 +27,4 @@ public enum SortOrder ASCENDING, DESCENDING } + diff --git a/Common/src/main/java/com/qualcomm/robotcore/util/Statistics.java b/Common/src/main/java/com/qualcomm/robotcore/util/Statistics.java index 520b64f6..992c6060 100644 --- a/Common/src/main/java/com/qualcomm/robotcore/util/Statistics.java +++ b/Common/src/main/java/com/qualcomm/robotcore/util/Statistics.java @@ -137,4 +137,6 @@ public void remove(double x) n = nPrev; } } -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/dalvik/system/VMRuntime.java b/Common/src/main/java/dalvik/system/VMRuntime.java index 78c1e13a..b655ccac 100644 --- a/Common/src/main/java/dalvik/system/VMRuntime.java +++ b/Common/src/main/java/dalvik/system/VMRuntime.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package dalvik.system; import java.lang.reflect.Array; @@ -16,7 +21,8 @@ public static VMRuntime getRuntime() { } public Object newUnpaddedArray(Class componentType, int length) { - return Array.newInstance(componentType, length); // we do a little bit of trolling -SEM + return Array.newInstance(componentType, length); // we do a little bit of trolling } } + diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/Folders.kt b/Common/src/main/java/io/github/deltacv/eocvsim/plugin/Folders.kt deleted file mode 100644 index 94b4ee9f..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/Folders.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin - -import com.github.serivesmejia.eocvsim.util.extension.plus -import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder -import java.io.File - -val PLUGIN_FOLDER = (EOCVSimFolder + File.separator + "plugins").apply { mkdir() } -val EMBEDDED_PLUGIN_FOLDER = (PLUGIN_FOLDER + File.separator + "embedded").apply { mkdir() } -val PLUGIN_CACHING_FOLDER = (PLUGIN_FOLDER + File.separator + "caching").apply { mkdir() } -val FILESYSTEMS_FOLDER = (PLUGIN_FOLDER + File.separator + "filesystems").apply { mkdir() } \ No newline at end of file diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/ApiDisabler.kt b/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/ApiDisabler.kt deleted file mode 100644 index b6d77081..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/ApiDisabler.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api - -/** - * Utility object to disable APIs - * - * Exposes the internalDisableApi() method of - * each Api in an indirect way to avoid misuse. - */ -object ApiDisabler { - /** - * Disables the given APIs. - * @param apis The APIs to disable. - */ - fun disableApis(vararg apis: Api) { - apis.forEach { it.internalDisableApi() } - } -} \ No newline at end of file diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/EOCVSimApi.kt b/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/EOCVSimApi.kt deleted file mode 100644 index 9ca9e4f6..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/EOCVSimApi.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api - -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin - -/** - * Root API entry point exposed to plugins by EOCV-Sim. - * - * This API provides access to the main subsystems of the application, - * including visualization, input sources, pipelines, configuration, - * and the main simulation loop. - * - * @param owner the plugin that owns this API instance - */ -abstract class EOCVSimApi(owner: EOCVSimPlugin) : Api(owner) { - - /** - * Hook into the main application loop. - */ - abstract val mainLoopHook: HookApi - - /** - * API for the GUI of the application. - */ - abstract val visualizerApi: VisualizerApi - - /** - * API for managing input sources used by pipelines. - */ - abstract val inputSourceManagerApi: InputSourceManagerApi - - /** - * API for creating and managing pipelines. - */ - abstract val pipelineManagerApi: PipelineManagerApi - - /** - * API for exposing tunable variables to the UI. - */ - abstract val variableTunerApi: VariableTunerApi - - /** - * API for saved configuration. - */ - abstract val configApi: ConfigApi -} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/exception/EOCVSimApiException.kt b/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/exception/EOCVSimApiException.kt deleted file mode 100644 index 243f0243..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/exception/EOCVSimApiException.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api.exception - -import io.github.deltacv.eocvsim.plugin.api.Api - -class EOCVSimApiException(message: String, api: Api) : RuntimeException(message) \ No newline at end of file diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/Exceptions.kt b/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/Exceptions.kt deleted file mode 100644 index 9a68df9e..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/Exceptions.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2024 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.loader - -import kotlin.RuntimeException - -class InvalidPluginException(message: String, cause: Throwable? = null) : RuntimeException(message, cause) - -class UnsupportedPluginException(message: String) : RuntimeException(message) \ No newline at end of file diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/FilePluginLoader.kt b/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/FilePluginLoader.kt deleted file mode 100644 index b3d6f4e7..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/FilePluginLoader.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.loader - -import java.io.File - -/** - * Base [PluginLoader] implementation for plugins loaded from a file. - * - * This loader variant is backed by a single plugin file, typically a JAR, - * and is responsible for resolving the plugin classpath and metadata from it. - */ -abstract class FilePluginLoader : PluginLoader() { - - /** - * The file from which the plugin is loaded. - * - * This is usually the plugin JAR, but may represent any file-based - * plugin source supported by the loader implementation. - */ - abstract val pluginFile: File -} \ No newline at end of file diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/Providers.kt b/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/Providers.kt deleted file mode 100644 index 3abcdb4e..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/Providers.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.loader - -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.EOCVSimApi - -/** - * Marks an object as carrying a [PluginContext]. - * - * This is used in cases where code is executed outside of the plugin's - * classloader or without an active thread-local context, but still needs - * access to the plugin context. - */ -interface PluginContextHolder { - /** - * The associated [PluginContext]. - */ - val pluginContext: PluginContext -} - -/** - * Provides an [EOCVSimApi] instance for a given plugin. - * - * This abstraction exists to decouple plugins from the concrete source - * of the API instance, allowing it to be resolved dynamically based - * on the plugin being loaded. - */ -fun interface EOCVSimApiProvider { - /** - * Returns the [EOCVSimApi] instance associated with the given plugin. - * - * @param plugin the plugin requesting access to the API - */ - fun provideEOCVSimApiFor(plugin: EOCVSimPlugin): EOCVSimApi -} \ No newline at end of file diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/VirtualField.kt b/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/VirtualField.kt deleted file mode 100644 index 71efc62c..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/VirtualField.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2022 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.virtualreflect - -interface VirtualField { - - val name: String - val type: Class<*> - - val isFinal: Boolean - - val visibility: Visibility - - val label: String? - - fun get(): Any? - fun set(value: Any?) - -} - -enum class Visibility { - PUBLIC, PROTECTED, PRIVATE, PACKAGE_PRIVATE -} \ No newline at end of file diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/VirtualReflection.kt b/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/VirtualReflection.kt deleted file mode 100644 index 27a68b9e..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/VirtualReflection.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2022 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.virtualreflect - -interface VirtualReflection { - - fun contextOf(c: Class<*>): VirtualReflectContext? - - fun contextOf(value: Any): VirtualReflectContext? - -} \ No newline at end of file diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflectContext.kt b/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflectContext.kt deleted file mode 100644 index fb2c6f9b..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflectContext.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2022 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.virtualreflect.jvm - -import io.github.deltacv.eocvsim.virtualreflect.VirtualReflectContext -import io.github.deltacv.eocvsim.virtualreflect.VirtualField -import java.lang.reflect.Field - -class JvmVirtualReflectContext( - val instance: Any? = null, - val clazz: Class<*> -) : VirtualReflectContext { - - override val name: String = clazz.name - override val simpleName: String = clazz.simpleName - - private val cachedVirtualFields = mutableMapOf() - - override val fields: Array = clazz.fields.map { virtualFieldFor(it) }.toTypedArray() - - override fun getField(name: String): VirtualField? { - val field = clazz.getField(name) ?: return null - return virtualFieldFor(field) - } - - override fun getLabeledField(label: String): VirtualField? { - var labeledField: VirtualField? = null - - for(field in fields) { - if(field.label == label) { - labeledField = field - break - } - } - - return labeledField - } - - private fun virtualFieldFor(field: Field): JvmVirtualField { - if(!cachedVirtualFields.containsKey(field)) { - cachedVirtualFields[field] = JvmVirtualField(instance, field) - } - - return cachedVirtualFields[field]!! - } - -} \ No newline at end of file diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflection.kt b/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflection.kt deleted file mode 100644 index 1053279e..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflection.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2022 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.virtualreflect.jvm - -import io.github.deltacv.eocvsim.virtualreflect.VirtualReflectContext -import io.github.deltacv.eocvsim.virtualreflect.VirtualReflection -import java.lang.ref.WeakReference -import java.util.* - -object JvmVirtualReflection : VirtualReflection { - - private val cache = WeakHashMap>() - - override fun contextOf(c: Class<*>) = cacheContextOf(null, c) - - override fun contextOf(value: Any) = cacheContextOf(value, value::class.java) - - private fun cacheContextOf(value: Any?, clazz: Class<*>): VirtualReflectContext { - if(!cache.containsKey(value) || cache[value]?.get() == null) { - cache[value] = WeakReference(JvmVirtualReflectContext(value, clazz)) - } - - return cache[value]!!.get()!! - } - -} \ No newline at end of file diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/Label.java b/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/Label.java deleted file mode 100644 index 18069124..00000000 --- a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/Label.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2024 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.virtualreflect.jvm; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.FIELD}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Label { - String name(); -} diff --git a/Common/src/main/java/libcore/util/EmptyArray.java b/Common/src/main/java/libcore/util/EmptyArray.java index acb9f6ed..9450fdb1 100644 --- a/Common/src/main/java/libcore/util/EmptyArray.java +++ b/Common/src/main/java/libcore/util/EmptyArray.java @@ -69,4 +69,5 @@ private EmptyArray() {} new java.lang.reflect.TypeVariable[0]; /** @hide */ public static final Annotation[] ANNOTATION = new Annotation[0]; -} \ No newline at end of file +} + diff --git a/Common/src/main/java/libcore/util/FP16.java b/Common/src/main/java/libcore/util/FP16.java index 69a3c996..7666793d 100644 --- a/Common/src/main/java/libcore/util/FP16.java +++ b/Common/src/main/java/libcore/util/FP16.java @@ -791,4 +791,5 @@ public static String toHexString(short h) { } return o.toString(); } -} \ No newline at end of file +} + diff --git a/Common/src/main/java/io/github/deltacv/common/image/BufferedImageRecycler.java b/Common/src/main/java/org/deltacv/common/image/BufferedImageRecycler.java similarity index 82% rename from Common/src/main/java/io/github/deltacv/common/image/BufferedImageRecycler.java rename to Common/src/main/java/org/deltacv/common/image/BufferedImageRecycler.java index 76a9d66d..4999a751 100644 --- a/Common/src/main/java/io/github/deltacv/common/image/BufferedImageRecycler.java +++ b/Common/src/main/java/org/deltacv/common/image/BufferedImageRecycler.java @@ -1,27 +1,9 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.common.image; +package org.deltacv.common.image; import java.awt.*; import java.awt.image.BufferedImage; @@ -163,3 +145,4 @@ private RecyclableBufferedImage(int idx, int width, int height, int imageType) { } } + diff --git a/Common/src/main/java/io/github/deltacv/common/image/DynamicBufferedImageRecycler.java b/Common/src/main/java/org/deltacv/common/image/DynamicBufferedImageRecycler.java similarity index 68% rename from Common/src/main/java/io/github/deltacv/common/image/DynamicBufferedImageRecycler.java rename to Common/src/main/java/org/deltacv/common/image/DynamicBufferedImageRecycler.java index bb3b0ce3..c4275be2 100644 --- a/Common/src/main/java/io/github/deltacv/common/image/DynamicBufferedImageRecycler.java +++ b/Common/src/main/java/org/deltacv/common/image/DynamicBufferedImageRecycler.java @@ -1,27 +1,9 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.common.image; +package org.deltacv.common.image; import java.awt.*; import java.awt.image.BufferedImage; @@ -90,4 +72,4 @@ public synchronized void flushAll() { } } -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/common/image/MatPoster.java b/Common/src/main/java/org/deltacv/common/image/MatPoster.java similarity index 79% rename from Common/src/main/java/io/github/deltacv/common/image/MatPoster.java rename to Common/src/main/java/org/deltacv/common/image/MatPoster.java index d012753b..763d3b83 100644 --- a/Common/src/main/java/io/github/deltacv/common/image/MatPoster.java +++ b/Common/src/main/java/org/deltacv/common/image/MatPoster.java @@ -1,4 +1,9 @@ -package io.github.deltacv.common.image; +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.common.image; import org.opencv.core.Mat; @@ -23,3 +28,4 @@ default void post(Mat m) { void post(Mat m, Object context); } + diff --git a/Common/src/main/java/io/github/deltacv/common/pipeline/util/PipelineStatisticsCalculator.kt b/Common/src/main/java/org/deltacv/common/pipeline/PipelineStatisticsCalculator.kt similarity index 95% rename from Common/src/main/java/io/github/deltacv/common/pipeline/util/PipelineStatisticsCalculator.kt rename to Common/src/main/java/org/deltacv/common/pipeline/PipelineStatisticsCalculator.kt index 4a9c29cd..aa17fffd 100644 --- a/Common/src/main/java/io/github/deltacv/common/pipeline/util/PipelineStatisticsCalculator.kt +++ b/Common/src/main/java/org/deltacv/common/pipeline/PipelineStatisticsCalculator.kt @@ -1,4 +1,9 @@ -package io.github.deltacv.common.pipeline.util +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.common.pipeline import com.qualcomm.robotcore.util.ElapsedTime import com.qualcomm.robotcore.util.MovingStatistics @@ -95,4 +100,4 @@ class PipelineStatisticsCalculator { avgOverheadTime = avgTotalFrameTime - avgPipelineTime } -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/common/util/LoggerDelegates.kt b/Common/src/main/java/org/deltacv/common/util/LoggerDelegates.kt similarity index 69% rename from Common/src/main/java/io/github/deltacv/common/util/LoggerDelegates.kt rename to Common/src/main/java/org/deltacv/common/util/LoggerDelegates.kt index c5ce422e..07ca8bf2 100644 --- a/Common/src/main/java/io/github/deltacv/common/util/LoggerDelegates.kt +++ b/Common/src/main/java/org/deltacv/common/util/LoggerDelegates.kt @@ -1,4 +1,9 @@ -package io.github.deltacv.common.util +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.common.util import org.slf4j.LoggerFactory import kotlin.reflect.KClass @@ -13,4 +18,4 @@ fun Any.loggerForThis() = lazy { fun loggerOf(name: String) = lazy { LoggerFactory.getLogger(name)!! -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/common/util/ParsedVersion.kt b/Common/src/main/java/org/deltacv/common/util/ParsedVersion.kt similarity index 92% rename from Common/src/main/java/io/github/deltacv/common/util/ParsedVersion.kt rename to Common/src/main/java/org/deltacv/common/util/ParsedVersion.kt index b6b6cc06..06ddcc5f 100644 --- a/Common/src/main/java/io/github/deltacv/common/util/ParsedVersion.kt +++ b/Common/src/main/java/org/deltacv/common/util/ParsedVersion.kt @@ -1,4 +1,9 @@ -package io.github.deltacv.common.util +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.common.util /** * ParsedVersion class to parse and compare versions @@ -45,4 +50,4 @@ class ParsedVersion(val version: String) { return "$major.$minor.$patch" } -} \ No newline at end of file +} diff --git a/Common/src/main/java/org/deltacv/common/util/serialization/Toml.java b/Common/src/main/java/org/deltacv/common/util/serialization/Toml.java new file mode 100644 index 00000000..4c78a4cc --- /dev/null +++ b/Common/src/main/java/org/deltacv/common/util/serialization/Toml.java @@ -0,0 +1,106 @@ +package org.deltacv.common.util.serialization; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.toml.TomlFactory; + +import java.io.File; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Minimal TOML helper backed by Jackson TOML. Provides a thin API + * compatible with the previous toml4j usages in this project: + * new Toml().read(file) -> returns Toml + * getString/getLong/getBoolean/getList/getTable/toMap + */ +public class Toml { + + private final Map data; + + private static final ObjectMapper MAPPER = new ObjectMapper(new TomlFactory()); + + public Toml() { + this.data = Collections.emptyMap(); + } + + private Toml(Map data) { + this.data = data == null ? Collections.emptyMap() : data; + } + + public Toml read(File f) { + try (InputStream in = java.nio.file.Files.newInputStream(f.toPath())) { + Map m = MAPPER.readValue(in, new TypeReference>() {}); + return new Toml(m); + } catch (Exception e) { + throw new RuntimeException("Failed to read TOML file: " + f.getAbsolutePath(), e); + } + } + + public Toml read(InputStream in) { + try { + Map m = MAPPER.readValue(in, new TypeReference>() {}); + return new Toml(m); + } catch (Exception e) { + throw new RuntimeException("Failed to read TOML from stream", e); + } + } + + @SuppressWarnings("unchecked") + public String getString(String key) { + Object v = data.get(key); + return v == null ? null : v.toString(); + } + + public String getString(String key, String def) { + String s = getString(key); + return s == null ? def : s; + } + + public boolean contains(String key) { + return data != null && data.containsKey(key); + } + + @SuppressWarnings("unchecked") + public Long getLong(String key) { + Object v = data.get(key); + if (v == null) return null; + if (v instanceof Number) return ((Number) v).longValue(); + try { return Long.parseLong(v.toString()); } catch (Exception e) { return null; } + } + + @SuppressWarnings("unchecked") + public Boolean getBoolean(String key) { + Object v = data.get(key); + if (v == null) return null; + if (v instanceof Boolean) return (Boolean) v; + return Boolean.parseBoolean(v.toString()); + } + + public boolean getBoolean(String key, boolean def) { + Boolean b = getBoolean(key); + return b == null ? def : b; + } + + @SuppressWarnings("unchecked") + public List getList(String key) { + Object v = data.get(key); + if (v instanceof List) return (List) v; + return null; + } + + @SuppressWarnings("unchecked") + public Toml getTable(String key) { + Object v = data.get(key); + if (v instanceof Map) return new Toml((Map) v); + return null; + } + + public Map toMap() { + return data == null ? Collections.emptyMap() : data; + } +} + + diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/EOCVSimPlugin.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/EOCVSimPlugin.kt similarity index 89% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/EOCVSimPlugin.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/EOCVSimPlugin.kt index 8ef7b4dd..7e41e63c 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/EOCVSimPlugin.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/EOCVSimPlugin.kt @@ -1,7 +1,12 @@ -package io.github.deltacv.eocvsim.plugin +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin -import io.github.deltacv.common.util.loggerForThis -import io.github.deltacv.eocvsim.plugin.loader.PluginContext +import org.deltacv.common.util.loggerForThis +import org.deltacv.eocvsim.plugin.loader.PluginContext /** * Base class for all EOCV-Sim plugins. @@ -49,7 +54,7 @@ abstract class EOCVSimPlugin { * Without super access, the plugin is restricted to its own isolated * virtual filesystem and cannot access host or other plugin data. * - * @see io.github.deltacv.eocvsim.sandbox.nio.SandboxFileSystem + * @see org.deltacv.eocvsim.sandbox.nio.SandboxFileSystem */ val fileSystem get() = context.fileSystem @@ -60,7 +65,7 @@ abstract class EOCVSimPlugin { * incorporated into the runtime classpath (for example, whether it was * loaded from a local file or a Maven repository). * - * @see io.github.deltacv.eocvsim.plugin.loader.PluginSource + * @see org.deltacv.eocvsim.plugin.loader.PluginSource */ val pluginSource get() = context.pluginSource @@ -114,3 +119,4 @@ abstract class EOCVSimPlugin { */ abstract fun onDisable() } + diff --git a/Common/src/main/java/org/deltacv/eocvsim/plugin/Folders.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/Folders.kt new file mode 100644 index 00000000..3a60dbf1 --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/Folders.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin + +import com.github.serivesmejia.eocvsim.util.extension.plus +import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder +import java.io.File + +val PLUGIN_FOLDER get() = (EOCVSimFolder + File.separator + "plugins").apply { mkdir() } +val PLUGIN_CACHING_FOLDER get() = (PLUGIN_FOLDER + File.separator + "caching").apply { mkdir() } +val EMBEDDED_PLUGIN_FOLDER get() = (PLUGIN_CACHING_FOLDER + File.separator + "embedded").apply { mkdir() } +val FILESYSTEMS_FOLDER get() = (PLUGIN_FOLDER + File.separator + "filesystems").apply { mkdir() } diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/Api.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/Api.kt similarity index 83% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/Api.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/api/Api.kt index 9be0d843..ca54fd08 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/Api.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/Api.kt @@ -1,30 +1,12 @@ /* * Copyright (c) 2026 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.api +package org.deltacv.eocvsim.plugin.api -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.exception.EOCVSimApiException +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.exception.EOCVSimApiException import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -51,7 +33,7 @@ abstract class Api(val owner: EOCVSimPlugin) { /** * Simple name of the owning plugin class, for error messages */ - val ownerName: String get() = owner::class.java.simpleName + val ownerName: String get() = owner::class.simpleName ?: "???" /** * Whether this API has been disabled. @@ -272,4 +254,4 @@ abstract class Api(val owner: EOCVSimPlugin) { @JvmStatic protected fun nullableApiField(value: T?): ReadOnlyProperty = nullableApiField { value } } -} \ No newline at end of file +} diff --git a/Common/src/main/java/org/deltacv/eocvsim/plugin/api/ApiDisabler.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/ApiDisabler.kt new file mode 100644 index 00000000..73f73a6d --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/ApiDisabler.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api + +/** + * Utility object to disable APIs + * + * Exposes the internalDisableApi() method of + * each Api in an indirect way to avoid misuse. + */ +object ApiDisabler { + /** + * Disables the given APIs. + * @param apis The APIs to disable. + */ + fun disableApis(vararg apis: Api) { + apis.forEach { it.internalDisableApi() } + } +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/ConfigApi.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/ConfigApi.kt similarity index 55% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/ConfigApi.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/api/ConfigApi.kt index 548be1ab..0ac68e57 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/ConfigApi.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/ConfigApi.kt @@ -1,29 +1,11 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.EOCVSimPlugin /** * Base API for managing plugin configuration flags. @@ -70,4 +52,4 @@ abstract class ConfigApi(owner: EOCVSimPlugin) : Api(owner) { * @return `true` if the flag is set, `false` otherwise */ abstract fun hasFlag(flag: String): Boolean -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/DialogFactoryApi.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/DialogFactoryApi.kt similarity index 76% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/DialogFactoryApi.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/api/DialogFactoryApi.kt index c93ebfbe..123a5423 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/DialogFactoryApi.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/DialogFactoryApi.kt @@ -1,29 +1,11 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api - -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api + +import org.deltacv.eocvsim.plugin.EOCVSimPlugin import java.io.File import javax.swing.filechooser.FileFilter @@ -166,4 +148,4 @@ abstract class DialogFactoryApi(owner: EOCVSimPlugin) : Api(owner) { * @param report the crash report content */ abstract fun createCrashReportDialog(report: String) -} \ No newline at end of file +} diff --git a/Common/src/main/java/org/deltacv/eocvsim/plugin/api/EOCVSimApi.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/EOCVSimApi.kt new file mode 100644 index 00000000..bbf58de7 --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/EOCVSimApi.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api + +import org.deltacv.eocvsim.plugin.EOCVSimPlugin + +/** + * Root API entry point exposed to plugins by EOCV-Sim. + * + * This API provides access to the main subsystems of the application, + * including visualization, input sources, pipelines, configuration, + * and the main simulation loop. + * + * @param owner the plugin that owns this API instance + */ +abstract class EOCVSimApi(owner: EOCVSimPlugin) : Api(owner) { + + /** + * Hook into the main application loop. + */ + abstract val mainLoopHook: HookApi + + /** + * API for the GUI of the application. + */ + abstract val visualizerApi: VisualizerApi + + /** + * API for managing input sources used by pipelines. + */ + abstract val inputSourceManagerApi: InputSourceManagerApi + + /** + * API for creating and managing pipelines. + */ + abstract val pipelineManagerApi: PipelineManagerApi + + /** + * API for exposing tunable variables to the UI. + */ + abstract val variableTunerApi: VariableTunerApi + + /** + * API for saved configuration. + */ + abstract val configApi: ConfigApi +} + diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/HookApi.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/HookApi.kt similarity index 60% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/HookApi.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/api/HookApi.kt index 3c661429..74b48d69 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/HookApi.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/HookApi.kt @@ -1,29 +1,11 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.EOCVSimPlugin /** * API for registering and executing lifecycle hooks. @@ -92,3 +74,4 @@ abstract class HookApi(owner: EOCVSimPlugin) : Api(owner) { fun detach() } } + diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/InputSourceApis.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/InputSourceApis.kt similarity index 75% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/InputSourceApis.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/api/InputSourceApis.kt index d9db7908..4f49a9e6 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/InputSourceApis.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/InputSourceApis.kt @@ -1,29 +1,11 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api - -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api + +import org.deltacv.eocvsim.plugin.EOCVSimPlugin import org.opencv.core.Size /** @@ -176,4 +158,4 @@ abstract class InputSourceManagerApi(owner: EOCVSimPlugin) : Api(owner) { name: String, aNew: InputSourceApi.New ): InputSourceApi -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/PipelineManagerApi.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/PipelineManagerApi.kt similarity index 84% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/PipelineManagerApi.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/api/PipelineManagerApi.kt index 62014d36..4c6679ef 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/PipelineManagerApi.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/PipelineManagerApi.kt @@ -1,30 +1,12 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api - -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.virtualreflect.VirtualReflection +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api + +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.virtualreflect.VirtualReflection import org.firstinspires.ftc.robotcore.external.Telemetry import org.openftc.easyopencv.OpenCvPipeline @@ -306,4 +288,4 @@ abstract class PipelineManagerApi(owner: EOCVSimPlugin) : Api(owner) { USER_REQUESTED, IMAGE_SINGLE_SHOT } -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/SwingApis.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/SwingApis.kt similarity index 68% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/SwingApis.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/api/SwingApis.kt index 8d8148bb..a373f6cf 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/SwingApis.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/SwingApis.kt @@ -1,29 +1,11 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api - -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api + +import org.deltacv.eocvsim.plugin.EOCVSimPlugin import java.io.File import javax.swing.JMenuItem import javax.swing.filechooser.FileFilter @@ -138,4 +120,4 @@ abstract class JFileChooserApi(owner: EOCVSimPlugin) : Api(owner) { /** An error occurred */ ERROR } -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/VariableTunerApi.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/VariableTunerApi.kt similarity index 60% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/VariableTunerApi.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/api/VariableTunerApi.kt index 35be3de0..bba57edf 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/VariableTunerApi.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/VariableTunerApi.kt @@ -1,30 +1,12 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.virtualreflect.VirtualField +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.virtualreflect.VirtualField /** * Represents a single tunable field exposed by the variable tuner. * @@ -81,9 +63,9 @@ abstract class VariableTunerApi(owner: EOCVSimPlugin) : Api(owner) { * Retrieves an existing tunable field by its label, specified * by [VirtualField.label]. * - * @see [io.github.deltacv.eocvsim.virtualreflect.jvm.Label] + * @see [org.deltacv.eocvsim.virtualreflect.jvm.Label] * @param label label identifying the tunable field * @return the matching tunable field, or null if none exists */ abstract fun getTunableFieldWithLabel(label: String): TunableFieldApi? -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/VisualizerApi.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/VisualizerApi.kt similarity index 72% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/VisualizerApi.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/api/VisualizerApi.kt index 49ffda21..72fe15e1 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/api/VisualizerApi.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/VisualizerApi.kt @@ -1,29 +1,11 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.api - -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api + +import org.deltacv.eocvsim.plugin.EOCVSimPlugin import javax.swing.JFrame import javax.swing.JPanel @@ -34,7 +16,6 @@ import javax.swing.JPanel * sidebar tabs, and dialog creation facilities. */ abstract class VisualizerApi(owner: EOCVSimPlugin) : Api(owner) { - /** * The visualizer window frame. * @@ -61,6 +42,16 @@ abstract class VisualizerApi(owner: EOCVSimPlugin) : Api(owner) { */ abstract val sidebarApi: VisualizerSidebarApi + /** + * Access to the main OpenCV viewport API. + */ + abstract val viewportApi: VisualizerViewportApi + + /** + * Create some of the components used by the + */ + abstract val visualizerComponentsFactoryApi: VisualizerComponentsFactoryApi + /** * Factory for creating dialogs tied to the visualizer. */ @@ -71,7 +62,6 @@ abstract class VisualizerApi(owner: EOCVSimPlugin) : Api(owner) { * API for accessing and modifying the visualizer's top menu bar. */ abstract class VisualizerTopMenuBarApi(owner: EOCVSimPlugin) : Api(owner) { - /** * The "File" menu. */ @@ -95,7 +85,6 @@ abstract class VisualizerTopMenuBarApi(owner: EOCVSimPlugin) : Api(owner) { * dynamically by plugins. */ abstract class VisualizerSidebarApi(owner: EOCVSimPlugin) : Api(owner) { - /** * Fired whenever the active sidebar tab changes. */ @@ -136,7 +125,6 @@ abstract class VisualizerSidebarApi(owner: EOCVSimPlugin) : Api(owner) { * is disabled. */ abstract class Tab(owner: EOCVSimPlugin) : Api(owner) { - /** * Display title of the tab. */ @@ -163,6 +151,28 @@ abstract class VisualizerSidebarApi(owner: EOCVSimPlugin) : Api(owner) { * Sidebar tabs do not manage external resources and * require no explicit cleanup by default. */ - override fun disableApi() { } + override fun disableApi() {} } -} \ No newline at end of file +} + +/** + * API for interacting with the visualizer's main OpenCV viewport. + */ +abstract class VisualizerViewportApi(owner: EOCVSimPlugin) : Api(owner) { + /** + * Activates the viewport. This does not affect the main + * pipeline processing, it simply enables the visualization + * of the current pipeline's output. + */ + abstract fun activate() + + /** + * Deactivates the viewport. This does not affect the main + * pipeline processing, it simply disables the visualization + * of the current pipeline's output, as it stops rendering + * incoming frames. + */ + abstract fun deactivate() + + abstract fun setFpsMeterEnabled(enabled: Boolean) +} diff --git a/Common/src/main/java/org/deltacv/eocvsim/plugin/api/VisualizerComponentsApi.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/VisualizerComponentsApi.kt new file mode 100644 index 00000000..ed259f25 --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/api/VisualizerComponentsApi.kt @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.api + +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import javax.swing.JPanel + +/** + * Factory API for creating complex visualizer components as raw JPanel instances. + * + * This API provides methods to create UI panels for pipeline selection, source selection, + * and telemetry display. These panels are automatically wired to relevant events and hooks + * for seamless integration with the EOCV-Sim environment. + * + * @param owner The plugin that owns this API instance. + */ +abstract class VisualizerComponentsFactoryApi(owner: EOCVSimPlugin) : Api(owner) { + /** + * Creates a new instance of the JPanel for selecting pipelines. + * + * The panel displays a scrollable list of all available pipelines managed by + * [PipelineManagerApi]. It allows users to select a pipeline and is automatically + * wired to handle relevant events, such as pipeline addition, removal, or selection. + * + * @return A new instance of [PipelineSelectorPanelApi]. + */ + abstract fun createPipelineSelectorPanel(): PipelineSelectorPanelApi + + /** + * Creates a new instance of the JPanel for selecting input sources. + * + * The panel displays a scrollable list of all available input sources managed by + * [InputSourceManagerApi]. It allows users to select an input source and is automatically + * wired to handle relevant events, such as source addition, removal, or selection. + * + * @return A new instance of [SourceSelectorPanelApi]. + */ + abstract fun createSourceSelectorPanel(): SourceSelectorPanelApi + + /** + * Creates a new instance of the JPanel for displaying telemetry data. + * + * The panel displays a scrollable list of telemetry messages, with each message + * formatted according to the specified separators. It is designed to provide + * real-time updates and clear functionality. + * + * @return A new instance of [TelemetryPanelApi]. + */ + abstract fun createTelemetryPanel(): TelemetryPanelApi +} + +/** + * Represents the panel for selecting pipelines. + * + * This panel provides a user interface for browsing and selecting pipelines. + * It supports enabling/disabling user interaction and switching between pipelines. + * The panel is automatically updated to reflect the latest state of the pipelines. + * + * @param owner The plugin that owns this API instance. + */ +abstract class PipelineSelectorPanelApi(owner: EOCVSimPlugin) : Api(owner) { + /** + * The JPanel instance representing the UI component. + */ + abstract val jPanel: JPanel + + /** + * Indicates whether user interaction with the panel is enabled. + */ + abstract var isInteractionEnabled: Boolean + + /** + * Indicates whether switching between pipelines is allowed. + */ + abstract var allowSwitching: Boolean + + /** + * Gets the name of the currently selected pipeline, or null if no pipeline is selected. + */ + abstract val selectedPipelineName: String? + + /** + * Gets the index of the currently selected pipeline, or -1 if no pipeline is selected. + */ + abstract val selectedPipelineIndex: Int + + /** + * Refreshes the panel to reflect the latest state of the pipelines. + */ + abstract fun refresh() +} + +/** + * Represents the panel for selecting input sources. + * + * This panel provides a user interface for browsing and selecting input sources. + * It supports enabling/disabling user interaction and switching between sources. + * The panel is automatically updated to reflect the latest state of the sources. + * + * @param owner The plugin that owns this API instance. + */ +abstract class SourceSelectorPanelApi(owner: EOCVSimPlugin) : Api(owner) { + /** + * The JPanel instance representing the UI component. + */ + abstract val jPanel: JPanel + + /** + * Indicates whether user interaction with the panel is enabled. + */ + abstract var isInteractionEnabled: Boolean + + /** + * Indicates whether switching between sources is allowed. + */ + abstract var allowSwitching: Boolean + + /** + * Gets the name of the currently selected source, or null if no source is selected. + */ + abstract val selectedSourceName: String? + + /** + * Gets the index of the currently selected source, or -1 if no source is selected. + */ + abstract val selectedSourceIndex: Int + + /** + * Refreshes the panel to reflect the latest state of the sources. + */ + abstract fun refresh() +} + +/** + * Represents the panel for displaying telemetry data. + * + * This panel provides a user interface for displaying telemetry messages. + * It supports real-time updates and clearing of telemetry data. The panel + * is designed to handle large volumes of data efficiently. + * + * @param owner The plugin that owns this API instance. + */ +abstract class TelemetryPanelApi(owner: EOCVSimPlugin) : Api(owner) { + /** + * The JPanel instance representing the UI component. + */ + abstract val jPanel: JPanel + + /** + * Updates the telemetry panel with the given text, using the specified separators. + * + * @param text The telemetry data to display. + * @param captionSeparator The separator between captions and their values. + * @param itemSeparator The separator between different telemetry items. + */ + abstract fun update(text: String, captionSeparator: String = " : ", itemSeparator: String = " | ") + + /** + * Clears all telemetry data from the panel. + */ + abstract fun clear() +} diff --git a/Common/src/main/java/org/deltacv/eocvsim/plugin/exception/EOCVSimApiException.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/exception/EOCVSimApiException.kt new file mode 100644 index 00000000..a5d4173c --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/exception/EOCVSimApiException.kt @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.exception + +import org.deltacv.eocvsim.plugin.api.Api + +class EOCVSimApiException(message: String, val api: Api) : RuntimeException("Exception thrown by API ${api::class.simpleName}: $message") \ No newline at end of file diff --git a/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/Exceptions.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/Exceptions.kt new file mode 100644 index 00000000..deaae82e --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/Exceptions.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2024 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.loader + +import kotlin.RuntimeException + +class InvalidPluginException(message: String, cause: Throwable? = null) : RuntimeException(message, cause) + +class UnsupportedPluginException(message: String) : RuntimeException(message) diff --git a/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/FilePluginLoader.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/FilePluginLoader.kt new file mode 100644 index 00000000..59702bb2 --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/FilePluginLoader.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.loader + +import java.io.File + +/** + * Base [PluginLoader] implementation for plugins loaded from a file. + * + * This loader variant is backed by a single plugin file, typically a JAR, + * and is responsible for resolving the plugin classpath and metadata from it. + */ +abstract class FilePluginLoader : PluginLoader() { + + /** + * The file from which the plugin is loaded. + * + * This is usually the plugin JAR, but may represent any file-based + * plugin source supported by the loader implementation. + */ + abstract val pluginFile: File +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginContext.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginContext.kt similarity index 86% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginContext.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginContext.kt index a81b76c6..b8bc837c 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginContext.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginContext.kt @@ -1,33 +1,16 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ + @file:Suppress("unused") -package io.github.deltacv.eocvsim.plugin.loader +package org.deltacv.eocvsim.plugin.loader -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.loader.PluginContext.Companion.clearContext -import io.github.deltacv.eocvsim.plugin.loader.PluginContext.Companion.pushContext -import io.github.deltacv.eocvsim.sandbox.nio.SandboxFileSystem +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.loader.PluginContext.Companion.clearContext +import org.deltacv.eocvsim.plugin.loader.PluginContext.Companion.pushContext +import org.deltacv.eocvsim.sandbox.nio.SandboxFileSystem import java.util.* /** @@ -84,16 +67,19 @@ constructor( * in order of precedence: * * 1. **Cached plugin association** + * * Once a plugin instance has been created, the resolved context is cached * using weak references to avoid repeated resolution and to allow garbage * collection when a plugin is unloaded. * * 2. **ClassLoader-provided context** + * * If the plugin’s defining class loader implements [PluginContextHolder], * the context is resolved directly from it. This is the preferred and most * efficient resolution path during normal runtime execution. * * 3. **Thread-local context (highest priority)** + * * During plugin instantiation, the loader temporarily binds the active * `PluginContext` to the current thread. This guarantees that constructor * code and field initializers can access loader services even when no class @@ -276,4 +262,4 @@ constructor( * @param reason a human-readable explanation of why elevated access is required */ fun requestSuperAccess(reason: String) = loader.requestSuperAccess(reason) -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginLoader.kt similarity index 72% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginLoader.kt index 5819d93b..3573f412 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginLoader.kt @@ -1,34 +1,16 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.loader +package org.deltacv.eocvsim.plugin.loader import com.github.serivesmejia.eocvsim.util.extension.hashString -import com.moandjiezana.toml.Toml -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.EOCVSimApi -import io.github.deltacv.eocvsim.plugin.security.PluginSignature -import io.github.deltacv.eocvsim.sandbox.nio.SandboxFileSystem +import org.deltacv.common.util.serialization.Toml +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.EOCVSimApi +import org.deltacv.eocvsim.plugin.security.PluginSignature +import org.deltacv.eocvsim.sandbox.nio.SandboxFileSystem import java.io.File enum class PluginSource { @@ -62,7 +44,7 @@ data class PluginInfo( val nameWithVersion = "$name v$version" /** Human-readable name including version and author */ - val nameWithAuthorVersion = "$name v$version by $author" + val nameWithVersionAndAuthor = "$name v$version by $author" companion object { @@ -179,3 +161,4 @@ abstract class PluginLoader { */ fun hash(): String = pluginInfo.hash() } + diff --git a/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/Providers.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/Providers.kt new file mode 100644 index 00000000..2db78126 --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/loader/Providers.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.loader + +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.EOCVSimApi + +/** + * Marks an object as carrying a [PluginContext]. + * + * This is used in cases where code is executed outside of the plugin's + * classloader or without an active thread-local context, but still needs + * access to the plugin context. + */ +interface PluginContextHolder { + /** + * The associated [PluginContext]. + */ + val pluginContext: PluginContext +} + +/** + * Provides an [EOCVSimApi] instance for a given plugin. + * + * This abstraction exists to decouple plugins from the concrete source + * of the API instance, allowing it to be resolved dynamically based + * on the plugin being loaded. + */ +fun interface EOCVSimApiProvider { + /** + * Returns the [EOCVSimApi] instance associated with the given plugin. + * + * @param plugin the plugin requesting access to the API + */ + fun provideEOCVSimApiFor(plugin: EOCVSimPlugin): EOCVSimApi +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/Authority.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/security/Authority.kt similarity index 76% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/Authority.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/security/Authority.kt index 154017ed..4dedf074 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/Authority.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/security/Authority.kt @@ -1,38 +1,21 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.security +package org.deltacv.eocvsim.plugin.security import com.github.serivesmejia.eocvsim.util.extension.plus import com.github.serivesmejia.eocvsim.util.io.LockFile -import io.github.deltacv.common.util.loggerForThis -import io.github.deltacv.eocvsim.plugin.PLUGIN_CACHING_FOLDER +import org.deltacv.common.util.loggerForThis +import org.deltacv.common.util.serialization.Toml +import org.deltacv.eocvsim.plugin.PLUGIN_CACHING_FOLDER import java.io.File -import java.net.URL +import java.net.URI import java.security.KeyFactory import java.security.PublicKey import java.security.spec.X509EncodedKeySpec -import java.util.Base64 +import java.util.* import java.util.concurrent.TimeUnit data class Authority( @@ -40,9 +23,9 @@ data class Authority( val publicKey: PublicKey ) -data class MutableAuthority( - var name: String, - var publicKey: ByteArray +class MutableAuthority( + var name: String = "", + var publicKey: ByteArray = byteArrayOf() ) fun Authority.toMutable() = MutableAuthority(name, publicKey.encoded) @@ -78,7 +61,7 @@ object AuthorityFetcher { // Load authorities from file if it exists if (AUTHORITIES_FILE.exists() && tryLockAuthoritiesFile()) { try { - val authoritiesToml = com.moandjiezana.toml.Toml().read(AUTHORITIES_FILE) + val authoritiesToml = Toml().read(AUTHORITIES_FILE) val timestamp = authoritiesToml.getLong("timestamp") if(System.currentTimeMillis() - timestamp > TTL_DURATION_MS) { @@ -107,12 +90,12 @@ object AuthorityFetcher { } // Fetch the authority from the server - val authorityUrl = "${AUTHORITY_SERVER_URL.trim('/')}/${name}.txt" + val authorityUrl = "${AUTHORITY_SERVER_URL.trim('/')}/${name}" return try { logger.info("Fetching authority from URL: $authorityUrl") - val authorityPublicKey = URL(authorityUrl).readText() + val authorityPublicKey = URI.create(authorityUrl).toURL().readText() val pem = parsePem(authorityPublicKey) val authority = Authority(name, parsePublicKey(pem)) @@ -141,7 +124,7 @@ object AuthorityFetcher { AUTHORITIES_FILE.writeText("timestamp = $currentTime\n") } - val authoritiesToml = com.moandjiezana.toml.Toml().read(AUTHORITIES_FILE) + val authoritiesToml = Toml().read(AUTHORITIES_FILE) val timestamp = authoritiesToml.getLong("timestamp") if(timestamp != null && currentTime - timestamp > TTL_DURATION_MS) { @@ -211,4 +194,4 @@ object AuthorityFetcher { val keyFactory = KeyFactory.getInstance("RSA") return keyFactory.generatePublic(keySpec) } -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/KeyGeneratorTool.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/security/KeyGeneratorTool.kt similarity index 50% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/KeyGeneratorTool.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/security/KeyGeneratorTool.kt index adbaf8ef..c1e795e5 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/KeyGeneratorTool.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/security/KeyGeneratorTool.kt @@ -1,27 +1,9 @@ -/* - * Copyright (c) 2024 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.security +/* + * Copyright (c) 2024 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.security import java.io.File import java.io.FileWriter @@ -59,4 +41,4 @@ fun saveKeyToFile(filename: String, key: java.security.Key) { FileWriter(File(filename)).use { writer -> writer.write(pemFormat) } -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/PluginSignatureVerifier.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/security/PluginSignatureVerifier.kt similarity index 75% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/PluginSignatureVerifier.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/security/PluginSignatureVerifier.kt index 8f2822fb..d5de8386 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/PluginSignatureVerifier.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/security/PluginSignatureVerifier.kt @@ -1,32 +1,14 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.security +package org.deltacv.eocvsim.plugin.security import com.github.serivesmejia.eocvsim.util.extension.hashString -import io.github.deltacv.common.util.loggerForThis -import com.moandjiezana.toml.Toml -import io.github.deltacv.eocvsim.plugin.loader.InvalidPluginException +import org.deltacv.common.util.loggerForThis +import org.deltacv.common.util.serialization.Toml +import org.deltacv.eocvsim.plugin.loader.InvalidPluginException import java.io.File import java.security.PublicKey import java.security.Signature @@ -69,8 +51,10 @@ object PluginSignatureVerifier { return emptyResult } - val signatureToml = zip.getInputStream(signatureEntry).bufferedReader() - val signature = Toml().read(signatureToml) + // Read the signature TOML from the entry as an InputStream so it matches + // the Toml.read(InputStream) overload (avoid BufferedReader which is not supported) + // force the Toml type so Kotlin sees the helper API + val signature: Toml = zip.getInputStream(signatureEntry).use { Toml().read(it) } val authorityName = signature.getString("authority") if (authorityName == null) { @@ -95,7 +79,7 @@ object PluginSignatureVerifier { return emptyResult } - val signatureData = signature.getTable("signatures") ?: run { + val signatureData: Toml = signature.getTable("signatures") ?: run { logger.warn("signature.toml does not contain a signatures table") return emptyResult } @@ -106,13 +90,15 @@ object PluginSignatureVerifier { val signatures = mutableMapOf() // Verify each signature - for ((path, sign) in signatureData.toMap()) { - if(sign !is String) { + // Convert table to a typed map so Kotlin can destructure entries reliably + val signatureMap = signatureData.toMap() as Map + for ((path, signAny) in signatureMap) { + if (signAny !is String) { logger.warn("Signature for class $path is not a string") return emptyResult } - signatures[path] = sign + signatures[path] = signAny } // Now, verify each class in the JAR file @@ -178,4 +164,4 @@ object PluginSignatureVerifier { // Decode the Base64-encoded string into a byte array return Base64.getDecoder().decode(cleanSignature) } -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/PluginSigningTool.kt b/Common/src/main/java/org/deltacv/eocvsim/plugin/security/PluginSigningTool.kt similarity index 85% rename from Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/PluginSigningTool.kt rename to Common/src/main/java/org/deltacv/eocvsim/plugin/security/PluginSigningTool.kt index 47114cf6..b2293b11 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/PluginSigningTool.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/plugin/security/PluginSigningTool.kt @@ -1,27 +1,9 @@ -/* - * Copyright (c) 2024 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.plugin.security +/* + * Copyright (c) 2024 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.plugin.security import com.github.serivesmejia.eocvsim.util.extension.hashString import picocli.CommandLine @@ -231,4 +213,4 @@ class PluginSigningTool : Runnable { } } } -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/sandbox/nio/SandboxFileSystem.kt b/Common/src/main/java/org/deltacv/eocvsim/sandbox/nio/SandboxFileSystem.kt similarity index 76% rename from Common/src/main/java/io/github/deltacv/eocvsim/sandbox/nio/SandboxFileSystem.kt rename to Common/src/main/java/org/deltacv/eocvsim/sandbox/nio/SandboxFileSystem.kt index 92cde2e8..4d7b8223 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/sandbox/nio/SandboxFileSystem.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/sandbox/nio/SandboxFileSystem.kt @@ -1,29 +1,11 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.sandbox.nio +package org.deltacv.eocvsim.sandbox.nio -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import java.nio.file.* import java.nio.file.attribute.FileAttribute @@ -149,4 +131,4 @@ class SandboxFileSystem( Files.createDirectories(dir, *attrs) } -} \ No newline at end of file +} diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/sandbox/restrictions/DynamicCodeRestrictions.kt b/Common/src/main/java/org/deltacv/eocvsim/sandbox/restrictions/DynamicCodeRestrictions.kt similarity index 88% rename from Common/src/main/java/io/github/deltacv/eocvsim/sandbox/restrictions/DynamicCodeRestrictions.kt rename to Common/src/main/java/org/deltacv/eocvsim/sandbox/restrictions/DynamicCodeRestrictions.kt index 7278dcb0..c245b7fc 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/sandbox/restrictions/DynamicCodeRestrictions.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/sandbox/restrictions/DynamicCodeRestrictions.kt @@ -1,17 +1,9 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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 SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.sandbox.restrictions +package org.deltacv.eocvsim.sandbox.restrictions /** * === Dynamic Code Sandbox Restrictions === @@ -45,10 +37,10 @@ package io.github.deltacv.eocvsim.sandbox.restrictions * Denies access to entire namespaces considered dangerous. * * - If a class name contains a blacklisted package name, loading is denied. - * - HOWEVER: if the class matched the whitelist, this blacklist is SKIPPED. + * - This list is checked AFTER the whitelist, meaning it OVERRIDES the whitelist. * * NOTE: - * This means whitelist entries OVERRIDE this blacklist. + * This is used to block dangerous sub-packages within a whitelisted parent package. * * AUTHORITY: * - Higher authority than the whitelist @@ -145,13 +137,12 @@ val dynamicCodePackageWhitelist = setOf( "android", // API - "io.github.deltacv.eocvsim.plugin", - "io.github.deltacv.eocvsim.sandbox", + "org.deltacv.eocvsim.plugin", + "org.deltacv.eocvsim.sandbox", // Third-party libs explicitly allowed - "com.moandjiezana.toml", + "org.deltacv.common.util", "net.lingala.zip4j", - "com.google.gson", "com.google.jimfs", "org.slf4j", "com.apache.logging", @@ -260,7 +251,7 @@ val dynamicCodePackageBlacklist = setOf( val dynamicCodePackageAlwaysBlacklist = setOf( // Embedded PaperVision (avoid duplicate / unsafe classloading) - "io.github.deltacv.papervision", + "org.deltacv.papervision", // Logging system isolation "org.apache.logging.log4j" diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualField.kt b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualField.kt similarity index 52% rename from Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualField.kt rename to Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualField.kt index aea7811c..2c687eb5 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualField.kt +++ b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualField.kt @@ -1,30 +1,12 @@ /* * Copyright (c) 2022 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.virtualreflect.jvm +package org.deltacv.eocvsim.virtualreflect.jvm -import io.github.deltacv.eocvsim.virtualreflect.VirtualField -import io.github.deltacv.eocvsim.virtualreflect.Visibility +import org.deltacv.eocvsim.virtualreflect.VirtualField +import org.deltacv.eocvsim.virtualreflect.Visibility import java.lang.reflect.Field import java.lang.reflect.Modifier @@ -70,4 +52,4 @@ class JvmVirtualField( field.set(instance, value) } -} \ No newline at end of file +} diff --git a/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflectContext.kt b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflectContext.kt new file mode 100644 index 00000000..38c1dba2 --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflectContext.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.virtualreflect.jvm + +import org.deltacv.eocvsim.virtualreflect.VirtualReflectContext +import org.deltacv.eocvsim.virtualreflect.VirtualField +import java.lang.reflect.Field + +class JvmVirtualReflectContext( + val instance: Any? = null, + val clazz: Class<*> +) : VirtualReflectContext { + + override val name: String = clazz.name + override val simpleName: String = clazz.simpleName + + private val cachedVirtualFields = mutableMapOf() + + override val fields: Array = clazz.fields.map { virtualFieldFor(it) }.toTypedArray() + + override fun getField(name: String): VirtualField? { + val field = clazz.getField(name) ?: return null + return virtualFieldFor(field) + } + + override fun getLabeledField(label: String): VirtualField? { + var labeledField: VirtualField? = null + + for(field in fields) { + if(field.label == label) { + labeledField = field + break + } + } + + return labeledField + } + + private fun virtualFieldFor(field: Field): JvmVirtualField { + if(!cachedVirtualFields.containsKey(field)) { + cachedVirtualFields[field] = JvmVirtualField(instance, field) + } + + return cachedVirtualFields[field]!! + } + +} diff --git a/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflection.kt b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflection.kt new file mode 100644 index 00000000..b2e4303e --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/JvmVirtualReflection.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.virtualreflect.jvm + +import org.deltacv.eocvsim.virtualreflect.VirtualReflectContext +import org.deltacv.eocvsim.virtualreflect.VirtualReflection +import java.lang.ref.WeakReference +import java.util.* + +object JvmVirtualReflection : VirtualReflection { + + private val cache = WeakHashMap>() + + override fun contextOf(c: Class<*>) = cacheContextOf(null, c) + + override fun contextOf(value: Any) = cacheContextOf(value, value::class.java) + + private fun cacheContextOf(value: Any?, clazz: Class<*>): VirtualReflectContext { + if(!cache.containsKey(value) || cache[value]?.get() == null) { + cache[value] = WeakReference(JvmVirtualReflectContext(value, clazz)) + } + + return cache[value]!!.get()!! + } + +} diff --git a/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/Label.java b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/Label.java new file mode 100644 index 00000000..16674f78 --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/jvm/Label.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.virtualreflect.jvm; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to label fields in a class. This is used to identify fields that are used in the virtual reflection system. + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Label { + String name(); +} + diff --git a/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/virtualreflect/VirtualField.kt b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/virtualreflect/VirtualField.kt new file mode 100644 index 00000000..3da92de3 --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/virtualreflect/VirtualField.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.virtualreflect + +/** + * Represents a field of a class, but is not necessarily backed by an actual Java Field. + */ +interface VirtualField { + + val name: String + val type: Class<*> + + val isFinal: Boolean + + val visibility: Visibility + + val label: String? + + fun get(): Any? + fun set(value: Any?) + +} + +enum class Visibility { + PUBLIC, PROTECTED, PRIVATE, PACKAGE_PRIVATE +} diff --git a/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/virtualreflect/VirtualReflectContext.kt b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/virtualreflect/VirtualReflectContext.kt new file mode 100644 index 00000000..77aa4c92 --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/virtualreflect/VirtualReflectContext.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.virtualreflect + +/** + * Context for virtual reflection, which makes for a multi-platform way to reflect on classes, fields, and methods. + */ +interface VirtualReflectContext { + + /** + * The name of the class this context is reflecting on, including package name + */ + val name: String + + /** + * The simple name of the class this context is reflecting on, without package name + */ + val simpleName: String + + /** + * The fields of the class this context is reflecting on + */ + val fields: Array + + /** + * Gets a field by its name, or null if it doesn't exist + */ + fun getField(name: String): VirtualField? + + /** + * Gets a field by its label, or null if it doesn't exist + * @see org.deltacv.eocvsim.virtualreflect.jvm.Label + */ + fun getLabeledField(label: String): VirtualField? + +} diff --git a/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/virtualreflect/VirtualReflection.kt b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/virtualreflect/VirtualReflection.kt new file mode 100644 index 00000000..ee86dcb6 --- /dev/null +++ b/Common/src/main/java/org/deltacv/eocvsim/virtualreflect/virtualreflect/VirtualReflection.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2022 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.virtualreflect + +/** + * Interface for virtual reflection, which allows to get a [VirtualReflectContext] from a class or an instance + */ +interface VirtualReflection { + + fun contextOf(c: Class<*>): VirtualReflectContext? + + fun contextOf(value: Any): VirtualReflectContext? + +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Const.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Const.java index 97652a60..82128efb 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Const.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Const.java @@ -52,3 +52,6 @@ are permitted (subject to the limitations in the disclaimer below) provided that public @interface Const { } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Func.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Func.java index 3737d2fe..988b1bde 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Func.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Func.java @@ -1,3 +1,36 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + package org.firstinspires.ftc.robotcore.external; public interface Func { @@ -5,3 +38,4 @@ public interface Func { T value(); } + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/NonConst.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/NonConst.java index 36f8dffb..a9741cd8 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/NonConst.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/NonConst.java @@ -52,3 +52,6 @@ are permitted (subject to the limitations in the disclaimer below) provided that public @interface NonConst { } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Predicate.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Predicate.java index 1ee8f137..e3fb4a83 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Predicate.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Predicate.java @@ -29,4 +29,6 @@ are permitted (subject to the limitations in the disclaimer below) provided that public interface Predicate { boolean test(T t); -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java index b15b12e9..35ac4d53 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java @@ -630,3 +630,6 @@ enum DisplayOrder { NEWEST_FIRST, OLDEST_FIRST } */ Log log(); } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/android/util/Size.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/android/util/Size.java index 095b9e23..083534b0 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/android/util/Size.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/android/util/Size.java @@ -149,4 +149,5 @@ public int hashCode() { private final int mWidth; private final int mHeight; -} \ No newline at end of file +} + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/function/Consumer.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/function/Consumer.java index 640cf070..99bb2145 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/function/Consumer.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/function/Consumer.java @@ -36,4 +36,6 @@ public interface Consumer { * @param value the input argument */ void accept(T value); -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMajorMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMajorMatrixF.java index 1d6f5f22..e8190443 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMajorMatrixF.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMajorMatrixF.java @@ -54,3 +54,6 @@ public ColumnMajorMatrixF(int nRows, int nCols) return new VectorF(this.getData()); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMatrixF.java index 79e845f8..cc675ae2 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMatrixF.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMatrixF.java @@ -60,3 +60,6 @@ public ColumnMatrixF(VectorF vector) return new GeneralMatrixF(numRows, numCols); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/DenseMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/DenseMatrixF.java index 396bd97a..09d94bab 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/DenseMatrixF.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/DenseMatrixF.java @@ -73,3 +73,6 @@ protected DenseMatrixF(int nRows, int nCols) */ protected abstract int indexFromRowCol(int row, int col); } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/GeneralMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/GeneralMatrixF.java index 3e86df07..3494f08f 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/GeneralMatrixF.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/GeneralMatrixF.java @@ -73,3 +73,6 @@ public GeneralMatrixF transposed() return (GeneralMatrixF)super.transposed(); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/MatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/MatrixF.java index bfbd11f3..59dc1b1b 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/MatrixF.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/MatrixF.java @@ -801,4 +801,6 @@ protected static RuntimeException dimensionsError(int numRows, int numCols) throw dimensionsError(); // really NYI: we haven't bothered to code other cases } -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/OpenGLMatrix.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/OpenGLMatrix.java index 277867f9..e5c9ed44 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/OpenGLMatrix.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/OpenGLMatrix.java @@ -262,3 +262,6 @@ public static OpenGLMatrix identityMatrix() super.multiply(him); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMajorMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMajorMatrixF.java index 9a36052a..c74ee803 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMajorMatrixF.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMajorMatrixF.java @@ -55,3 +55,6 @@ protected int indexFromRowCol(int row, int col) return new VectorF(this.getData()); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMatrixF.java index 86b5cee8..3ac2a06f 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMatrixF.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMatrixF.java @@ -60,3 +60,6 @@ public RowMatrixF(VectorF vector) return new GeneralMatrixF(numRows, numCols); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/SliceMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/SliceMatrixF.java index d85b9d9e..8b464d9a 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/SliceMatrixF.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/SliceMatrixF.java @@ -88,3 +88,6 @@ public SliceMatrixF(MatrixF matrix, int row, int col, int numRows, int numCols) return this.matrix.emptyMatrix(numRows, numCols); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/VectorF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/VectorF.java index 67ae41ba..3d1d1a89 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/VectorF.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/VectorF.java @@ -316,3 +316,6 @@ protected RuntimeException dimensionsError() return new IllegalArgumentException(String.format("vector dimensions are incorrect: length=%d", length)); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Acceleration.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Acceleration.java index da450693..63d94686 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Acceleration.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Acceleration.java @@ -118,3 +118,6 @@ public Acceleration toUnit(DistanceUnit distanceUnit) return String.format(Locale.getDefault(), "(%.3f %.3f %.3f)%s/s^2", xAccel, yAccel, zAccel, unit.toString()); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AngleUnit.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AngleUnit.java index 39150c91..e7cb276e 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AngleUnit.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AngleUnit.java @@ -237,3 +237,6 @@ public UnnormalizedAngleUnit getUnnormalized() } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesOrder.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesOrder.java index 4bb3f743..b2830ea0 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesOrder.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesOrder.java @@ -100,4 +100,6 @@ public AxesOrder reverse() case ZXY: return YXZ; } } -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesReference.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesReference.java index bc34ce26..8a93b5df 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesReference.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesReference.java @@ -55,3 +55,6 @@ public AxesReference reverse() } } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Axis.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Axis.java index a8064ecb..bac1c687 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Axis.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Axis.java @@ -57,3 +57,6 @@ public static Axis fromIndex(int index) } } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/DistanceUnit.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/DistanceUnit.java index e44675c3..725d7bd3 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/DistanceUnit.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/DistanceUnit.java @@ -200,3 +200,6 @@ public String toString(double inOurUnits) } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Orientation.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Orientation.java index c5899e5b..a52af343 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Orientation.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Orientation.java @@ -884,4 +884,6 @@ else if (test == 1) 0); } -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Pose3D.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Pose3D.java index 9e68bedd..a1dc9b7f 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Pose3D.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Pose3D.java @@ -87,3 +87,5 @@ public Position getPosition() return position; } } + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Position.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Position.java index 6d69652a..b308e7a3 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Position.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Position.java @@ -99,3 +99,6 @@ public Position toUnit(DistanceUnit distanceUnit) return String.format(Locale.getDefault(), "(%.3f %.3f %.3f)%s", x, y, z, unit.toString()); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Quaternion.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Quaternion.java index 0dcef9a5..a3333e88 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Quaternion.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Quaternion.java @@ -298,3 +298,6 @@ public String toString() return String.format(Locale.US, "{w=%.3f, x=%.3f, y=%.3f, z=%.3f}", w, x, y, z); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/UnnormalizedAngleUnit.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/UnnormalizedAngleUnit.java index 5679223c..88baca5f 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/UnnormalizedAngleUnit.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/UnnormalizedAngleUnit.java @@ -195,3 +195,6 @@ public AngleUnit getNormalized() } } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Velocity.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Velocity.java index e8b0009b..6a73b589 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Velocity.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Velocity.java @@ -102,3 +102,6 @@ public Velocity toUnit(DistanceUnit distanceUnit) return String.format(Locale.getDefault(), "(%.3f %.3f %.3f)%s/s", xVeloc, yVeloc, zVeloc, unit.toString()); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/YawPitchRollAngles.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/YawPitchRollAngles.java index 328362fa..34dcec3c 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/YawPitchRollAngles.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/YawPitchRollAngles.java @@ -142,3 +142,6 @@ public String toString() { angleUnit.toDegrees(yaw), angleUnit.toDegrees(pitch), angleUnit.toDegrees(roll)); } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibration.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibration.java index a2180236..9e5811a8 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibration.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibration.java @@ -172,3 +172,6 @@ protected static double getAspectRatio(Size size) } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibrationIdentity.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibrationIdentity.java index 33e73d9d..62aa3e83 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibrationIdentity.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibrationIdentity.java @@ -35,4 +35,6 @@ are permitted (subject to the limitations in the disclaimer below) provided that public interface CameraCalibrationIdentity { boolean isDegenerate(); -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraIntrinsics.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraIntrinsics.java index 06004e24..7e1e7d1c 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraIntrinsics.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraIntrinsics.java @@ -114,4 +114,6 @@ public boolean isDegenerate() && distortionCoefficients[7]==0; } -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/VendorProductCalibrationIdentity.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/VendorProductCalibrationIdentity.java index c88f775b..f558d8b6 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/VendorProductCalibrationIdentity.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/VendorProductCalibrationIdentity.java @@ -82,3 +82,6 @@ public VendorProductCalibrationIdentity(int vid, int pid) return Integer.valueOf(vid).hashCode() ^ Integer.valueOf(pid).hashCode() ^ 738187; } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/EvictingBlockingQueue.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/EvictingBlockingQueue.java index 895a2004..a20078a0 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/EvictingBlockingQueue.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/EvictingBlockingQueue.java @@ -212,4 +212,6 @@ public void clear() { targetQueue.clear(); } } -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/MutableReference.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/MutableReference.java index eaf9e077..a2c53585 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/MutableReference.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/MutableReference.java @@ -66,3 +66,6 @@ public T getValue() return "[{" + getValue() + "}]"; } } + + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/AppUtil.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/AppUtil.java index 1d7171a0..6b7417b8 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/AppUtil.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/AppUtil.java @@ -1,3 +1,36 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + package org.firstinspires.ftc.robotcore.internal.system; import com.qualcomm.robotcore.util.RobotLog; @@ -68,3 +101,4 @@ public void exitApplication(int code) { System.exit(code); } } + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Assert.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Assert.java index 954b111d..02d5aa61 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Assert.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Assert.java @@ -116,4 +116,6 @@ public static void assertFailed(String format, Object[] args) { logger.error(banner, e); } } -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Misc.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Misc.java index 9b915ac4..ff38c962 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Misc.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Misc.java @@ -472,4 +472,6 @@ public static RuntimeException internalError(Throwable throwable, String message { return new RuntimeException("internal error:" + message, throwable); } -} \ No newline at end of file +} + + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/robocol/TelemetryMessage.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/robocol/TelemetryMessage.java index 41a62006..2b0c9992 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/robocol/TelemetryMessage.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/robocol/TelemetryMessage.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package org.firstinspires.ftc.robotcore.robocol; /** @@ -20,3 +25,4 @@ public class TelemetryMessage { public final static int cbValueMax = (1 << (cbValueLen*8)) - 1; } + diff --git a/Common/src/main/java/org/openftc/easyopencv/MatRecycler.java b/Common/src/main/java/org/openftc/easyopencv/MatRecycler.java index cea5a597..e8e76329 100644 --- a/Common/src/main/java/org/openftc/easyopencv/MatRecycler.java +++ b/Common/src/main/java/org/openftc/easyopencv/MatRecycler.java @@ -19,6 +19,7 @@ * SOFTWARE. */ + package org.openftc.easyopencv; import org.opencv.core.CvType; @@ -151,3 +152,4 @@ public void copyTo(Mat mat) { } } } + diff --git a/Common/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java b/Common/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java index 2daee177..c235df5e 100644 --- a/Common/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java +++ b/Common/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java @@ -91,4 +91,4 @@ Mat processFrameInternal(Mat input) return processFrame(input); } -} \ No newline at end of file +} diff --git a/EOCV-Sim/build.gradle b/EOCV-Sim/build.gradle index 6be391de..6cad12e5 100644 --- a/EOCV-Sim/build.gradle +++ b/EOCV-Sim/build.gradle @@ -1,151 +1,212 @@ -import io.github.fvarrui.javapackager.gradle.PackageTask - -import java.nio.file.Paths -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter - -plugins { - id 'org.jetbrains.kotlin.jvm' - id 'com.gradleup.shadow' - - id 'signing' - id "com.vanniktech.maven.publish" version "0.30.0" - - id 'io.github.fvarrui.javapackager.plugin' -} - -apply from: '../build.common.gradle' - -ext.kotest_version = '5.7.2' - -components.java { - tasks.named("shadowJar").configure { - // only run shadowJar when explicitly specified by the user - // check if user invoked gradle with :shadowJar - enabled = project.gradle.startParameter.taskNames.contains("shadowJar") - } -} - -shadowJar { - mergeServiceFiles() -} - -test { - useJUnitPlatform() -} - -apply from: '../test-logging.gradle' - -tasks.register('pack', PackageTask) { - dependsOn build - // mandatory - mainClass = 'com.github.serivesmejia.eocvsim.Main' - // optional - bundleJre = true - customizedJre = false - generateInstaller = true - platform = "auto" - - winConfig { - icoFile = file('src/main/resources/images/icon/ico_eocvsim.ico') - - generateMsi = false - disableDirPage = false - disableProgramGroupPage = false - disableFinishedPage = false - } - - linuxConfig { - pngFile = file('src/main/resources/images/icon/ico_eocvsim.png') - } -} - - -tasks.processResources { - from({ - project(":PaperVisionShadow").tasks.shadowJar - }) { - rename { "PaperVisionPlugin.jar" } // name inside resources - into("embedded_plugins") // folder inside resources - } -} - -dependencies { - api project(':Common') - api project(':Vision') - - implementation 'org.jetbrains.kotlin:kotlin-stdlib' - - implementation "org.eclipse.jdt:ecj:3.21.0" - - api "org.openpnp:opencv:$opencv_version" - - implementation "org.slf4j:slf4j-api:$slf4j_version" - implementation "org.apache.logging.log4j:log4j-api:$log4j_version" - implementation "org.apache.logging.log4j:log4j-core:$log4j_version" - implementation "org.apache.logging.log4j:log4j-slf4j2-impl:$log4j_version" - - implementation "org.deltacv.steve:core:1.1.3" - implementation "org.deltacv.steve:backend-openpnp:1.1.3" - - implementation "info.picocli:picocli:$picocli_version" - implementation 'com.google.code.gson:gson:2.8.9' - implementation "io.github.classgraph:classgraph:$classgraph_version" - - implementation "com.formdev:flatlaf:$flatlaf_version" - implementation "com.formdev:flatlaf-intellij-themes:$flatlaf_version" - implementation "com.miglayout:miglayout-swing:$miglayout_version" - - implementation("org.java-websocket:Java-WebSocket:1.5.2") - - implementation 'net.lingala.zip4j:zip4j:2.11.3' - - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-swing:$kotlinx_coroutines_version" - - testImplementation "io.kotest:kotest-runner-junit5:$kotest_version" - testImplementation "io.kotest:kotest-assertions-core:$kotest_version" - - implementation "com.moandjiezana.toml:toml4j:$toml4j_version" - implementation 'org.ow2.asm:asm:9.7' - - implementation 'org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-depchain:3.3.2' - implementation 'org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven-archive:3.3.2' - - implementation('org.deltacv.visionloop:streaming:1.2.9') { transitive = false } - - // implementation "org.deltacv.PaperVision:EOCVSimPlugin:$papervision_version" -} - -tasks.register('writeBuildClassJava') { - - String date = DateTimeFormatter.ofPattern("yyyy-M-d hh:mm:ss").format(LocalDateTime.now()) - - File versionFile = Paths.get( - projectDir.absolutePath, 'src', 'main', 'java', - 'com', 'github', 'serivesmejia', 'eocvsim', 'Build.java' - ).toFile() - - versionFile.delete() - - versionFile << "package com.github.serivesmejia.eocvsim;\n" + - "\n" + - "/*\n" + - " * Autogenerated file! Do not manually edit this file, as\n" + - " * it is regenerated any time the build task is run.\n" + - " *\n" + - " * Based from PhotonVision PhotonVersion generator task\n" + - " */\n" + - "@SuppressWarnings(\"ALL\")\n" + - "public final class Build {\n" + - " public static final String versionString = \"$version\";\n" + - " public static final String standardVersionString = \"$standardVersion\";\n" + - " public static final String buildDate = \"$date\";\n" + - " public static final boolean isDev = ${version.contains("dev")};\n\n" + - " public static final String opencvVersion = \"$opencv_version\";\n" + - " public static final String apriltagPluginVersion = \"$apriltag_plugin_version\";\n" + - " public static final String paperVisionVersion = \"$papervision_version\";\n" + - "}" -} - -build.dependsOn writeBuildClassJava \ No newline at end of file +import io.github.fvarrui.javapackager.gradle.PackageTask + +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'com.gradleup.shadow' + id 'org.photonvision.tools.WpilibTools' + id 'signing' + id "com.vanniktech.maven.publish" version "0.30.0" + id 'io.github.fvarrui.javapackager.plugin' +} + +apply from: '../build.common.gradle' + +ext.kotest_version = '5.7.2' + +def generatedSourcesDir = layout.buildDirectory.dir("generated/sources/buildinfo/java/main") + +sourceSets { + main { + java { + srcDirs += generatedSourcesDir + } + } + + bootstrap { + java { + srcDirs = ['src/bootstrap/java'] + } + } +} + +tasks.named('compileBootstrapJava', JavaCompile) { + options.release.set(8) +} + +components.java { + tasks.named("shadowJar").configure { + enabled = project.gradle.startParameter.taskNames.contains("shadowJar") + } +} + +shadowJar { + mergeServiceFiles() + archiveClassifier.set(wpilibTools.currentPlatform.platformName) + + from(sourceSets.main.output) + from(sourceSets.bootstrap.output) + + manifest { + attributes( + 'Main-Class': 'com.github.serivesmejia.eocvsim.Bootstrap' + ) + } +} + +test { + useJUnitPlatform() +} + +apply from: '../test-logging.gradle' + +tasks.register('pack', PackageTask) { + dependsOn build + mainClass = 'com.github.serivesmejia.eocvsim.Main' + bundleJre = true + customizedJre = false + generateInstaller = true + platform = "auto" + + winConfig { + icoFile = file('src/main/resources/images/icon/ico_eocvsim.ico') + generateMsi = false + disableDirPage = false + disableProgramGroupPage = false + disableFinishedPage = false + } + + linuxConfig { + pngFile = file('src/main/resources/images/icon/ico_eocvsim.png') + } +} + +wpilibTools.deps.wpilibVersion = wpilibVersion + +def nativeConfigName = 'wpilibNatives' +def nativeConfig = configurations.create(nativeConfigName) + +def nativeTasks = wpilibTools.createExtractionTasks { + configurationName = nativeConfigName +} + +nativeTasks.addToSourceSetResources(sourceSets.main) + +nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpiutil") +nativeConfig.dependencies.add wpilibTools.deps.wpilib("cscore") +nativeConfig.dependencies.add wpilibTools.deps.wpilibOpenCv(opencvVersion) + +dependencies { + api project(':Common') + api project(':Vision') + + implementation 'org.jetbrains.kotlin:kotlin-stdlib' + + implementation(platform("io.insert-koin:koin-bom:$koin_version")) + implementation("io.insert-koin:koin-core") + + implementation "org.eclipse.jdt:ecj:3.21.0" + + // WPILib OpenCV replaces openpnp + api wpilibTools.deps.wpilibOpenCvJava(opencvVersion) + wpilibNatives wpilibTools.deps.wpilibOpenCv(opencvVersion) + + // WPILib AprilTag replaces AprilTagDesktop + api wpilibTools.deps.wpilibJava("apriltag") + wpilibNatives wpilibTools.deps.wpilib("apriltag") + + // WPILib cscore replaces steve + implementation wpilibTools.deps.wpilibJava("cscore") + wpilibNatives wpilibTools.deps.wpilib("cscore") + + // cscore depends on these + implementation wpilibTools.deps.wpilibJava("wpinet") + wpilibNatives wpilibTools.deps.wpilib("wpinet") + + implementation wpilibTools.deps.wpilibJava("wpiutil") + wpilibNatives wpilibTools.deps.wpilib("wpiutil") + + // needed for apriltags + implementation wpilibTools.deps.wpilibJava("wpimath") + wpilibNatives wpilibTools.deps.wpilib("wpimath") + + // we don't use this at all... But if we don't have it in the classpath, + // we can't use WPILib's Geometry classes for apriltags. + implementation "us.hebi.quickbuf:quickbuf-runtime:$quickbufVersion" + + implementation "org.slf4j:slf4j-api:$slf4j_version" + implementation "org.apache.logging.log4j:log4j-api:$log4j_version" + implementation 'org.apache.logging.log4j:log4j-core:2.25.4' + implementation "org.apache.logging.log4j:log4j-slf4j2-impl:$log4j_version" + + implementation "info.picocli:picocli:$picocli_version" + implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version" + implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version" + implementation "io.github.classgraph:classgraph:$classgraph_version" + + implementation "com.formdev:flatlaf:$flatlaf_version" + implementation "com.formdev:flatlaf-intellij-themes:$flatlaf_version" + implementation "com.miglayout:miglayout-swing:$miglayout_version" + + implementation("org.java-websocket:Java-WebSocket:1.5.2") + + implementation 'net.lingala.zip4j:zip4j:2.11.3' + + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-swing:$kotlinx_coroutines_version" + + testImplementation "io.kotest:kotest-runner-junit5:$kotest_version" + testImplementation "io.kotest:kotest-assertions-core:$kotest_version" + + implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-toml:$jackson_version" + implementation 'org.ow2.asm:asm:9.7' + + implementation 'org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-depchain:3.3.3' + implementation 'org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven-archive:3.3.2' + + implementation('org.deltacv.visionloop:streaming:1.2.9') { transitive = false } +} + +tasks.register('writeBuildClassJava') { + outputs.dir(generatedSourcesDir) + + doLast { + String date = DateTimeFormatter.ofPattern("yyyy-M-d hh:mm:ss").format(LocalDateTime.now()) + + def packageStr = "com.github.serivesmejia.eocvsim" + def packagePath = packageStr.replace('.', '/') + + def outputDir = file("${generatedSourcesDir.get().asFile}/$packagePath") + outputDir.mkdirs() + + def versionFile = file("${outputDir}/Build.java") + + versionFile.text = """package $packageStr; + +/* + * Autogenerated file! Do not manually edit this file, as + * it is regenerated any time the build task is run. + * + * Based from PhotonVision PhotonVersion generator task + */ +@SuppressWarnings("ALL") +public final class Build { + public static final String versionString = "$version"; + public static final String standardVersionString = "$standardVersion"; + public static final String buildDate = "$date"; + public static final boolean isDev = ${version.contains("dev")}; + public static final String packagePlatform = "${wpilibTools.currentPlatform.platformName}"; + + public static final String opencvVersion = "$opencvVersion"; + public static final String paperVisionVersion = "$papervision_version"; +} +""" + } +} + +tasks.matching { it.name in ["compileJava", "compileKotlin", "sourcesJar"] } + .configureEach { + dependsOn writeBuildClassJava + } \ No newline at end of file diff --git a/EOCV-Sim/src/bootstrap/java/com/github/serivesmejia/eocvsim/Bootstrap.java b/EOCV-Sim/src/bootstrap/java/com/github/serivesmejia/eocvsim/Bootstrap.java new file mode 100644 index 00000000..94db21fb --- /dev/null +++ b/EOCV-Sim/src/bootstrap/java/com/github/serivesmejia/eocvsim/Bootstrap.java @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2026 Sebastian Erives, deltacv + * + * Use of this source code is governed by the MIT license + * that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ + +package com.github.serivesmejia.eocvsim; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.*; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Bootstrap { + + public static void main(String[] args) throws Exception { + log("======================================"); + log("Starting EOCV-Sim Bootstrap"); + log("======================================"); + + log("Runtime:"); + log(" java.version = " + System.getProperty("java.version")); + log(" os.name = " + System.getProperty("os.name")); + log(" java.home = " + System.getProperty("java.home")); + log(" JAVA_HOME = " + System.getenv("JAVA_HOME")); + + if (Boolean.getBoolean("eocvsim.bypass.bootstrap")) { + log("Bootstrap bypass enabled"); + launchApp(args); + return; + } + + int current = getJavaMajorVersion(System.getProperty("java.version")); + log("Detected runtime Java major: " + current); + + if (current >= 25) { + log("Java 25+ detected → launching directly"); + launchApp(args); + return; + } + + log("Java 25+ NOT detected"); + + File detected = findJava25(); + + if (detected != null) { + log("Autodetected Java 25+: " + detected.getAbsolutePath()); + } else { + log("No Java 25+ installations detected"); + } + + File chosen = promptUser(detected, current); + + if (chosen == null) { + log("User exited bootstrap"); + System.exit(0); + return; + } + + log("User selected: " + chosen.getAbsolutePath()); + + relaunch(chosen, args); + } + + // ---------------- UI ---------------- + + public static final class PromptTest { + public static void main(String[] args) { + promptUser(new File("C:/Program Files/Java/jdk-25"), 0); + } + } + + static File promptUser(File detected, int currentJava) { + + boolean hasDetected = detected != null; + + log("Opening selection UI (detected=" + hasDetected + ")"); + + final File[] result = new File[1]; + final Object lock = new Object(); + + JFrame dialog = new JFrame("EOCV-Sim: Java 25 Required"); + + try { + URL icon = Bootstrap.class.getResource("/images/icon/ico_eocvsim_new_128.png"); + if (icon != null) { + dialog.setIconImage(new ImageIcon(icon).getImage()); + } + } catch (Exception ignored) { } + + dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + JPanel root = new JPanel(); + root.setBorder(BorderFactory.createEmptyBorder(14, 16, 14, 16)); + root.setLayout(new BoxLayout(root, BoxLayout.Y_AXIS)); + + // ---------------- TITLE ---------------- + JLabel title = new JLabel("EOCV-Sim requires Java 25 or newer"); + title.setAlignmentX(Component.CENTER_ALIGNMENT); + title.setHorizontalAlignment(SwingConstants.CENTER); + title.setFont(title.getFont().deriveFont(Font.BOLD, 16f)); + + // ---------------- INFO TEXT ---------------- + JTextArea info = new JTextArea(); + info.setEditable(false); + info.setLineWrap(true); + info.setWrapStyleWord(true); + info.setOpaque(false); + info.setFocusable(false); + + StringBuilder text = new StringBuilder(); + text.append("EOCV-Sim was started with Java ") + .append(currentJava) + .append(", but requires Java 25 or newer to run.\n"); + if (!hasDetected) { + text.append("No compatible Java installation was found automatically.\n\n"); + } + text.append("Select a Java 25+ installation or continue with the detected one if it is correct."); + info.setText(text.toString()); + info.setMaximumSize(new Dimension(420, 120)); + + // ---------------- DETECTED AREAS ---------------- + JLabel detectedLabel = null; + JLabel detectedPath = null; + + if (hasDetected) { + detectedLabel = new JLabel("Autodetected installation:"); + detectedLabel.setFont(info.getFont().deriveFont(Font.BOLD)); + detectedLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + detectedLabel.setHorizontalAlignment(SwingConstants.CENTER); + + detectedPath = new JLabel("
" + detected.getAbsolutePath() + "
"); + detectedPath.setFont(info.getFont().deriveFont(Font.BOLD)); + detectedPath.setForeground(new Color(0, 120, 215)); + detectedPath.setAlignmentX(Component.CENTER_ALIGNMENT); + detectedPath.setHorizontalAlignment(SwingConstants.CENTER); + } + + // ---------------- BUTTONS ---------------- + JButton continueBtn = new JButton("Continue with detected"); + JButton selectBtn = new JButton("Select from disk"); + JButton exitBtn = new JButton("Exit"); + + continueBtn.setEnabled(hasDetected); + + // Make the continue button long and the other two standard width + continueBtn.setPreferredSize(new Dimension(360, 30)); + selectBtn.setPreferredSize(new Dimension(180, 30)); + exitBtn.setPreferredSize(new Dimension(180, 30)); + + // ---------------- ACTIONS ---------------- + continueBtn.addActionListener(e -> { + log("User: continue with detected"); + result[0] = detected; + dialog.dispose(); + synchronized (lock) { lock.notifyAll(); } + }); + + selectBtn.addActionListener(e -> { + log("User: browse disk"); + + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + if (chooser.showOpenDialog(dialog) == JFileChooser.APPROVE_OPTION) { + File selected = chooser.getSelectedFile(); + log("Selected: " + selected.getAbsolutePath()); + + if (isJava25OrNewer(selected)) { + log("Valid Java 25+ selected"); + result[0] = selected; + dialog.dispose(); + synchronized (lock) { lock.notifyAll(); } + } else { + log("Invalid Java selected"); + JOptionPane.showMessageDialog( + dialog, + "This directory is not a valid Java 25 or newer installation.", + "Invalid Java", + JOptionPane.ERROR_MESSAGE + ); + } + } + }); + + exitBtn.addActionListener(e -> { + log("User exit"); + result[0] = null; + dialog.dispose(); + synchronized (lock) { lock.notifyAll(); } + }); + + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + synchronized (lock) { lock.notifyAll(); } + } + }); + + // ---------------- LAYOUT ---------------- + root.add(title); + root.add(Box.createVerticalStrut(10)); + root.add(info); + + if (hasDetected) { + root.add(Box.createVerticalStrut(8)); + root.add(detectedLabel); + root.add(Box.createVerticalStrut(2)); + root.add(detectedPath); + } + + root.add(Box.createVerticalStrut(12)); + + JPanel buttonBlock = new JPanel(new GridBagLayout()); + buttonBlock.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8)); + buttonBlock.setAlignmentX(Component.CENTER_ALIGNMENT); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(0, 8, 8, 8); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + + // Row 0: Continue with detected (spans both columns) + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 2; + gbc.anchor = GridBagConstraints.CENTER; + buttonBlock.add(continueBtn, gbc); + + // Row 1: Select and Exit + gbc.gridwidth = 1; + gbc.gridy = 1; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.CENTER; + buttonBlock.add(selectBtn, gbc); + + gbc.gridx = 1; + buttonBlock.add(exitBtn, gbc); + + + root.add(buttonBlock); + + dialog.setContentPane(root); + + dialog.setMinimumSize(new Dimension(460, 260)); + dialog.pack(); + dialog.setLocationRelativeTo(null); + dialog.setResizable(false); + + dialog.setVisible(true); + + synchronized (lock) { + try { lock.wait(); } catch (InterruptedException ignored) {} + } + + return result[0]; + } + + // ---------------- LAUNCH ---------------- + + private static void launchApp(String[] args) throws Exception { + log("Launching Main via reflection"); + + Class.forName("com.github.serivesmejia.eocvsim.Main") + .getMethod("main", String[].class) + .invoke(null, (Object) args); + } + + private static void relaunch(File javaHome, String[] args) throws IOException { + String os = System.getProperty("os.name").toLowerCase(); + + String javaBin = javaHome.getAbsolutePath() + + File.separator + "bin" + + File.separator + "java"; + + if (os.contains("win")) javaBin += ".exe"; + + File jar = new File( + Bootstrap.class.getProtectionDomain() + .getCodeSource() + .getLocation() + .getPath() + ); + + log("Relaunching:"); + log(" Java: " + javaBin); + log(" Jar : " + jar.getAbsolutePath()); + + List cmd = new ArrayList<>(); + cmd.add(javaBin); + cmd.add("-cp"); + cmd.add(jar.getAbsolutePath()); + cmd.add("com.github.serivesmejia.eocvsim.Main"); + + Collections.addAll(cmd, args); + + new ProcessBuilder(cmd).inheritIO().start(); + System.exit(0); + } + + // ---------------- DETECTION ---------------- + + private static File findJava25() { + List roots = new ArrayList<>(); + + roots.add(new File(System.getProperty("user.home"), ".jdks")); + + String javaHome = System.getenv("JAVA_HOME"); + if (javaHome != null && !javaHome.isEmpty()) { + roots.add(new File(javaHome)); + } + + String osName = System.getProperty("os.name", "").toLowerCase(); + if (osName.contains("win")) { + String pf = System.getenv("ProgramW6432"); + if (pf == null) { + pf = System.getenv("ProgramFiles"); + } + + if (pf != null && !pf.isEmpty()) { + roots.add(new File(pf, "Java")); + } + } + + for (File root : roots) { + log("Scanning: " + root.getAbsolutePath()); + + File detected = scan(root); + if (detected != null) { + return detected; + } + } + + return null; + } + + private static File scan(File root) { + if (root == null || !root.exists()) return null; + + File[] files = root.listFiles(); + if (files == null) return null; + + for (File f : files) { + log("Checking: " + f.getName()); + if (isJava25OrNewer(f)) { + log("Matched Java 25+: " + f.getAbsolutePath()); + return f; + } + + if (f.isDirectory()) { + File nested = scan(f); + if (nested != null) { + return nested; + } + } + } + + return null; + } + + // ---------------- VERSION ---------------- + + private static int getJavaMajorVersion(String version) { + try { + if (version.startsWith("1.")) return Integer.parseInt(version.split("\\.")[1]); + return Integer.parseInt(version.split("\\.")[0]); + } catch (Exception e) { + return -1; + } + } + + private static int getJavaMajorVersion(File javaHome) { + File bin = new File(javaHome, "bin/java"); + if (!bin.exists()) bin = new File(javaHome, "bin/java.exe"); + if (!bin.exists()) return -1; + + try { + Process p = new ProcessBuilder(bin.getAbsolutePath(), "-version") + .redirectErrorStream(true) + .start(); + + BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line = r.readLine(); + + int q1 = line.indexOf('"'); + int q2 = line.indexOf('"', q1 + 1); + + String v = line.substring(q1 + 1, q2); + + if (v.startsWith("1.")) return Integer.parseInt(v.split("\\.")[1]); + return Integer.parseInt(v.split("\\.")[0]); + + } catch (Exception e) { + log("Failed reading version: " + javaHome); + return -1; + } + } + + private static boolean isJava25OrNewer(File home) { + return getJavaMajorVersion(home) >= 25; + } + + // ---------------- LOG ---------------- + + private static void log(String msg) { + System.out.println("[EOCV-Sim/Bootstrap] " + msg); + } +} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt index 19f9e29f..141651ff 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt @@ -1,24 +1,6 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim @@ -27,41 +9,40 @@ import com.github.serivesmejia.eocvsim.config.Config import com.github.serivesmejia.eocvsim.config.ConfigManager import com.github.serivesmejia.eocvsim.gui.DialogFactory import com.github.serivesmejia.eocvsim.gui.Visualizer -import com.github.serivesmejia.eocvsim.gui.dialog.FileAlreadyExists import com.github.serivesmejia.eocvsim.input.InputSourceManager -import com.github.serivesmejia.eocvsim.output.VideoRecordingSession +import com.github.serivesmejia.eocvsim.output.RecordingManager import com.github.serivesmejia.eocvsim.pipeline.PipelineManager import com.github.serivesmejia.eocvsim.pipeline.PipelineSource import com.github.serivesmejia.eocvsim.tuner.TunerManager -import com.github.serivesmejia.eocvsim.util.ClasspathScan -import com.github.serivesmejia.eocvsim.util.FileFilters import com.github.serivesmejia.eocvsim.util.JavaProcess -import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.LibraryLoader import com.github.serivesmejia.eocvsim.util.event.EventHandler import com.github.serivesmejia.eocvsim.util.exception.handling.CrashReport import com.github.serivesmejia.eocvsim.util.exception.handling.EOCVSimUncaughtExceptionHandler import com.github.serivesmejia.eocvsim.util.fps.FpsLimiter import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder -import io.github.deltacv.common.util.loggerFor +import com.github.serivesmejia.eocvsim.util.orchestration.Orchestrator import com.github.serivesmejia.eocvsim.workspace.WorkspaceManager -import com.qualcomm.robotcore.eventloop.opmode.OpMode import com.qualcomm.robotcore.eventloop.opmode.OpModePipelineHandler -import io.github.deltacv.common.pipeline.util.PipelineStatisticsCalculator -import io.github.deltacv.common.util.ParsedVersion -import io.github.deltacv.eocvsim.plugin.loader.PluginManager -import io.github.deltacv.vision.external.PipelineRenderHook -import nu.pattern.OpenCV -import org.opencv.core.Mat +import org.deltacv.common.pipeline.PipelineStatisticsCalculator +import org.deltacv.common.util.ParsedVersion +import org.deltacv.common.util.loggerFor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.swing.Swing +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named import org.opencv.core.Size import org.openftc.easyopencv.TimestampedPipelineHandler -import java.awt.Dimension import java.io.File import java.lang.Thread.sleep import javax.swing.JOptionPane -import javax.swing.SwingUtilities -import javax.swing.filechooser.FileFilter -import javax.swing.filechooser.FileNameExtensionFilter -import kotlin.jvm.Throws import kotlin.system.exitProcess /** @@ -69,10 +50,9 @@ import kotlin.system.exitProcess * This class is the entry point of the program * and is responsible for initializing all the * components of the simulator. - * @param params the parameters to initialize the simulator with * @see Parameters */ -class EOCVSim(val params: Parameters = Parameters()) { +class EOCVSim : KoinComponent { companion object { const val VERSION = Build.versionString @@ -86,58 +66,15 @@ class EOCVSim(val params: Parameters = Parameters()) { @JvmField val DEFAULT_EOCV_SIZE = Size(DEFAULT_EOCV_WIDTH.toDouble(), DEFAULT_EOCV_HEIGHT.toDouble()) - private var hasScanned = false - private val classpathScan = ClasspathScan() - val logger by loggerFor(EOCVSim::class) init { - EOCVSimFolder // mkdir needed folders - } - - private var isNativeLibLoaded = false - - /** - * Load the OpenCV native library - * @param alternativeNative the alternative native library file to load instead of the packaged one - */ - fun loadOpenCvLib(alternativeNative: File? = null) { - if (isNativeLibLoaded) return - - if (alternativeNative != null) { - logger.info("Loading native lib from ${alternativeNative.absolutePath}...") - - try { - System.load(alternativeNative.absolutePath) - - Mat().release() //test if native lib is loaded correctly - - isNativeLibLoaded = true - logger.info("Successfully loaded the OpenCV native lib from specified path") - - return - } catch (ex: Throwable) { - logger.error("Failure loading the OpenCV native lib from specified path", ex) - logger.info("Retrying with loadLocally...") - } - } - - try { - OpenCV.loadLocally() - logger.info("Successfully loaded the OpenCV native lib") - } catch (ex: Throwable) { - logger.error("Failure loading the OpenCV native lib", ex) - logger.error("The sim will exit now as it's impossible to continue execution without OpenCV") - - CrashReport(ex).saveCrashReport() - - exitProcess(-1) - } - - isNativeLibLoaded = true + EOCVSimFolder.mkdir() // mkdir needed folders } } + val orchestrator: Orchestrator by inject() + /** * Event handler for the main update loop * This event handler is called every frame @@ -145,29 +82,22 @@ class EOCVSim(val params: Parameters = Parameters()) { * posted by the different components of the simulator * @see EventHandler */ - @JvmField val onMainUpdate = EventHandler("OnMainUpdate") + private val onMainLoop: EventHandler by inject(named("onMainLoop")) + private val onCrash: EventHandler by inject(named("onCrash")) /** * The visualizer instance in charge of managing the GUI * and the viewport where the pipeline output is shown * @see Visualizer */ - @JvmField val visualizer = Visualizer(this) + val visualizer: Visualizer by inject() - /** - * The configuration manager instance in charge of managing - * the configuration of the simulator from the config json file - */ - @JvmField val configManager = ConfigManager() + val configManager: ConfigManager by inject() + val inputSourceManager: InputSourceManager by inject() + val pluginManager: org.deltacv.eocvsim.plugin.loader.PluginManager by inject() - /** - * The input source manager instance in charge of managing input sources - * and their loading, as well as the current input source. - * Input Sources are the sources of the frames that the pipeline processes - * they can be from a webcam, a video or image file, etc. - * @see InputSourceManager - */ - @JvmField val inputSourceManager = InputSourceManager(this) + val recordingManager: RecordingManager by inject() + val dialogFactory: DialogFactory by inject() /** * The pipeline statistics calculator instance in charge of @@ -175,25 +105,25 @@ class EOCVSim(val params: Parameters = Parameters()) { * of the current pipeline * @see PipelineStatisticsCalculator */ - @JvmField val pipelineStatisticsCalculator = PipelineStatisticsCalculator() + val pipelineStatisticsCalculator: PipelineStatisticsCalculator by inject() /** * The pipeline manager instance in charge of managing pipelines * and their execution, as well as making sure the pipeline * does not take too long to process a frame */ - @JvmField val pipelineManager = PipelineManager(this, pipelineStatisticsCalculator) + val pipelineManager: PipelineManager by inject() /** * The tuner manager instance in charge of managing pipeline and processor * tunable variables and their values that can be changed in runtime */ - @JvmField val tunerManager = TunerManager(this) + val tunerManager: TunerManager by inject() /** * The workspace manager instance in charge of managing user workspaces */ - @JvmField val workspaceManager = WorkspaceManager(this) + val workspaceManager: WorkspaceManager by inject() /** * The current configuration of the simulator @@ -202,20 +132,7 @@ class EOCVSim(val params: Parameters = Parameters()) { */ val config: Config get() = configManager.config - /** - * The classpath scanner instance containing all the scanned classes - * From OpenCvPipeline, VisionProcessor, TunableField and OpMode classes - * @see ClasspathScan - */ - val classpathScan get() = Companion.classpathScan - - /** - * The current recording session of the simulator - * This allows the user to record the output of the pipeline - * and save it to a file - * @see VideoRecordingSession - */ - var currentRecordingSession: VideoRecordingSession? = null + val lifecycleChannel: Channel by inject(named("lifecycle")) /** * Utility in charge of limiting the FPS of the simulator @@ -223,37 +140,23 @@ class EOCVSim(val params: Parameters = Parameters()) { */ val fpsLimiter = FpsLimiter(30.0) - /** - * Manager in charge of loading and managing plugins - * @see PluginManager - */ - val pluginManager = PluginManager(this) - lateinit var eocvSimThread: Thread private set + @JvmField val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + private val hexCode = Integer.toHexString(hashCode()) private var isRestarting = false private var destroying = false - /** - * The reason why the simulator was destroyed - * to handle different actions when flushing - * the simulator away. - * @see destroy - */ - enum class DestroyReason { - USER_REQUESTED, THREAD_EXIT, RESTART, CRASH - } - /** * Initializes the simulator * This method is called to initialize all the components * of the simulator and start the main loop - * @see start + * @see mainLoop */ - fun init() { + fun start() { eocvSimThread = Thread.currentThread() if (!EOCVSimFolder.couldLock) { @@ -274,39 +177,55 @@ class EOCVSim(val params: Parameters = Parameters()) { logger.info("Confirmed claiming of the lock file in ${EOCVSimFolder.absolutePath}") } - DialogFactory.createSplashScreen(visualizer.onInitFinished) + dialogFactory.createSplashScreen(visualizer.onInitFinished, onCrash) logger.info("-- Initializing EasyOpenCV Simulator v$VERSION ($hexCode) --") EOCVSimUncaughtExceptionHandler.register() - //loading native lib only once in the app runtime - loadOpenCvLib(params.opencvNativeLibrary) + val loadLibrariesResult = LibraryLoader.loadLibraries() - if (!hasScanned) { - classpathScan.asyncScan() - hasScanned = true - } + if(!loadLibrariesResult.success) { + logger.error("Exception in loadLibraries():", loadLibrariesResult.error) + logger.error("The sim will exit now as it's impossible to continue without the required libraries") - configManager.init() + onCrash.run() - configManager.config.simTheme.install() + runBlocking { + launch(Dispatchers.Swing) { + val crashHeader = """ + Failed to load one or more WPILib native libraries during initialization. - pluginManager.init() // woah - pluginManager.loadPlugins() + This EOCV-Sim package is intended for ${Build.packagePlatform}. + Please verify that you are running the correct build. - workspaceManager.init() + Additional details are available in the crash report below. + If this seems like a bug, please report it on GitHub. + """.trimIndent() + + dialogFactory.instantiateCrashReport( + crash = "$crashHeader\n\n${CrashReport(loadLibrariesResult.error())}", + headerText = "An error occurred while loading the required native libraries for your platform" + ) + } + } + + exitProcess(-1) + } + + orchestrator.changePhase(Orchestrator.Phase.INIT) + orchestrator.orchestrate() visualizer.onInitFinished { // SHOW WELCOME DIALOGS TO NEW USERS if(!config.flags.contains("hasShownIamA") || config.flags["hasShownIamA"] == false) { // Initial dialog to introduce our cool stuff to the user - DialogFactory.createIAmA(visualizer) + dialogFactory.createIAmA() } else if(!config.flags.contains("hasShownIamPaperVision") || config.flags["hasShownIamPaperVision"] == false) { // sometimes the users might miss the PaperVision dialog after the IAmA dialog // so we show it here if the user hasn't seen it yet - DialogFactory.createIAmAPaperVision(visualizer, false) + dialogFactory.createIAmAPaperVision(false) } else if(config.flags["prefersPaperVision"] == true) { // if the user prefers PaperVision, switch to it upon start up val indexOfTab = visualizer.sidebarPanel.indexOfTab("PaperVision") @@ -318,26 +237,6 @@ class EOCVSim(val params: Parameters = Parameters()) { // END OF WELCOME DIALOGS } - visualizer.initAsync(configManager.config.simTheme) //create gui in the EDT - - inputSourceManager.init() //loading user created input sources - - pipelineManager.init() //init pipeline manager (scan for pipelines) - - tunerManager.init() //init tunable variables manager - - //shows a warning when a pipeline gets "stuck" - pipelineManager.onPipelineTimeout { - visualizer.asyncPleaseWaitDialog( - "Current pipeline took too long to ${pipelineManager.lastPipelineAction}", - "Falling back to DefaultPipeline", - "Close", - Dimension(310, 150), - true, - true - ) - } - inputSourceManager.inputSourceLoader.saveInputSourcesToFile() visualizer.joinInit() @@ -361,48 +260,21 @@ class EOCVSim(val params: Parameters = Parameters()) { //post output mats from the pipeline to the visualizer viewport pipelineManager.pipelineOutputPosters.add(visualizer.viewport) - // now that we have two different runnable units (OpenCvPipeline and OpMode) - // we have to give a more special treatment to the OpenCvPipeline - // OpModes can take care of themselves, setting up their own stuff - // but we need to do some hand holding for OpenCvPipelines... - pipelineManager.onPipelineChange { - pipelineStatisticsCalculator.init() - - if(pipelineManager.currentPipeline !is OpMode && pipelineManager.currentPipeline != null) { - visualizer.viewport.activate() - visualizer.viewport.setRenderHook(PipelineRenderHook) // calls OpenCvPipeline#onDrawFrame on the viewport (UI) thread - } else { - // opmodes are on their own, lol - visualizer.viewport.deactivate() - visualizer.viewport.clearViewport() - } - } - - pipelineManager.onUpdate { - if(pipelineManager.currentPipeline !is OpMode && pipelineManager.currentPipeline != null) { - visualizer.viewport.notifyStatistics( - pipelineStatisticsCalculator.avgFps, - pipelineStatisticsCalculator.avgPipelineTime, - pipelineStatisticsCalculator.avgOverheadTime - ) - } - - updateVisualizerTitle() // update current pipeline in title - } - pluginManager.enablePlugins() + // BEGIN + try { - start() + mainLoop() } catch (e: InterruptedException) { logger.warn("Main thread interrupted ($hexCode)", e) } - if(!destroying) { - destroy(DestroyReason.THREAD_EXIT) - } + // HANDLE MAIN LOOP EXITS - if (isRestarting) { + if(!destroying) { + destroy(LifecycleSignal.Destroy.Reason.THREAD_EXIT) + } else if (isRestarting) { Thread.interrupted() //clear interrupted flag EOCVSimFolder.lock?.lock?.close() @@ -427,38 +299,45 @@ class EOCVSim(val params: Parameters = Parameters()) { * such as the GUI, the pipelines, the input sources, * and the different manages declared at the top level * are explicitly updated within this method - * @see init + * @see start */ @Throws(InterruptedException::class) - private fun start() { + private fun mainLoop() { if(Thread.currentThread() != eocvSimThread) { throw IllegalStateException("start() must be called from the EOCVSim thread") } logger.info("-- Begin EOCVSim loop ($hexCode) --") + orchestrator.changePhase(Orchestrator.Phase.RUN) + while (!eocvSimThread.isInterrupted && !destroying) { //run all pending requested runnables - onMainUpdate.run() + onMainLoop.run() pipelineStatisticsCalculator.newInputFrameStart() - inputSourceManager.update(pipelineManager.paused) - tunerManager.update() - - pipelineManager.update( - if (inputSourceManager.lastMatFromSource != null && !inputSourceManager.lastMatFromSource.empty()) { - inputSourceManager.lastMatFromSource - } else null - ) + orchestrator.orchestrate() //limit FPS fpsLimiter.maxFPS = config.pipelineMaxFps.fps.toDouble() try { fpsLimiter.sync() - } catch (e: InterruptedException) { + } catch (_: InterruptedException) { break } + + when(val signal = lifecycleChannel.tryReceive().getOrNull()) { + is LifecycleSignal.Restart -> { + logger.info("Restart signal received") + restart() + } + is LifecycleSignal.Destroy -> { + logger.info("Destroy signal received") + destroy(signal.reason) + } + null -> {} + } } logger.warn("Main thread interrupted ($hexCode)") @@ -468,38 +347,32 @@ class EOCVSim(val params: Parameters = Parameters()) { * Destroys the simulator * @param reason the reason why the simulator is being destroyed, it mainly allows to restart the simulator if requested */ - fun destroy(reason: DestroyReason) { + fun destroy(reason: LifecycleSignal.Destroy.Reason) { logger.warn("-- Destroying current EOCVSim ($hexCode) due to $reason, it is normal to see InterruptedExceptions and other kinds of stack traces below --") - pluginManager.disablePlugins() - - //stop recording session if there's currently an ongoing one - currentRecordingSession?.stopRecordingSession() - currentRecordingSession?.discardVideo() - logger.info("Trying to save config file...") - inputSourceManager.currentInputSource?.close() - workspaceManager.stopFileWatcher() - configManager.saveToFile() - visualizer.close() + orchestrator.changePhase(Orchestrator.Phase.DESTROY) + orchestrator.orchestrate() destroying = true + scope.cancel() - if(reason == DestroyReason.THREAD_EXIT) { + if(reason == LifecycleSignal.Destroy.Reason.THREAD_EXIT) { exitProcess(0) } else { eocvSimThread.interrupt() } - if (reason == DestroyReason.USER_REQUESTED || reason == DestroyReason.CRASH) jvmMainThread.interrupt() + if (reason == LifecycleSignal.Destroy.Reason.USER_REQUESTED || reason == LifecycleSignal.Destroy.Reason.CRASH) + jvmMainThread.interrupt() } /** * Destroys the simulator with the reason being USER_REQUESTED */ fun destroy() { - destroy(DestroyReason.USER_REQUESTED) + destroy(LifecycleSignal.Destroy.Reason.USER_REQUESTED) } /** @@ -512,108 +385,14 @@ class EOCVSim(val params: Parameters = Parameters()) { pipelineManager.captureStaticSnapshot() isRestarting = true - destroy(DestroyReason.RESTART) - } - - /** - * Starts a recording session to record the output of the pipeline - * to a video file in real-time which will be prompted to the user - * to save it to a file after stopping the recording session. - */ - fun startRecordingSession() { - if (currentRecordingSession == null) { - currentRecordingSession = VideoRecordingSession( - config.videoRecordingFps.fps.toDouble(), config.videoRecordingSize - ) - - currentRecordingSession!!.startRecordingSession() - - logger.info("Recording session started") - - pipelineManager.pipelineOutputPosters.add(currentRecordingSession!!.matPoster) - } - } - - /** - * Stops the current recording session and prompts the user to save the recorded video - */ - fun stopRecordingSession() { - currentRecordingSession?.let { itVideo -> - - visualizer.pipelineSelectorPanel.buttonsPanel.pipelineRecordBtt.isEnabled = false - - itVideo.stopRecordingSession() - pipelineManager.pipelineOutputPosters.remove(itVideo.matPoster) - - logger.info("Recording session stopped") - - DialogFactory.createFileChooser( - visualizer.frame, DialogFactory.FileChooser.Mode.SAVE_FILE_SELECT, FileFilters.recordedVideoFilter - ).addCloseListener { _: Int, file: File?, selectedFileFilter: FileFilter? -> - onMainUpdate.once { - if (file != null) { - var correctedFile = file - val extension = SysUtil.getExtensionByStringHandling(file.name) - - if (selectedFileFilter is FileNameExtensionFilter) { //if user selected an extension - //get selected extension - correctedFile = File(file.absolutePath + "." + selectedFileFilter.extensions[0]) - } else if (extension.isPresent) { - if (!extension.get().equals("avi", true)) { - correctedFile = File(file.absolutePath + ".avi") - } - } else { - correctedFile = File(file.absolutePath + ".avi") - } - - if (correctedFile.exists()) { - SwingUtilities.invokeLater { - if (DialogFactory.createFileAlreadyExistsDialog(this@EOCVSim) == FileAlreadyExists.UserChoice.REPLACE) { - onMainUpdate.once { itVideo.saveTo(correctedFile) } - } - } - } else { - itVideo.saveTo(correctedFile) - } - } else { - itVideo.discardVideo() - } - - currentRecordingSession = null - visualizer.pipelineSelectorPanel.buttonsPanel.pipelineRecordBtt.isEnabled = true - } - } - } + destroy(LifecycleSignal.Destroy.Reason.USER_REQUESTED) } /** * Checks if the simulator is currently recording * @return true if the simulator is currently recording, false otherwise */ - fun isCurrentlyRecording() = currentRecordingSession?.isRecording == true - - /** - * Updates the visualizer title message - * with different information such as the current pipeline - * the workspace file, if the pipeline is paused, if the pipeline - * is currently building, and if the simulator is currently recording - */ - private fun updateVisualizerTitle() { - val isBuildRunning = if (pipelineManager.compiledPipelineManager.isBuildRunning) "(Building)" else "" - - val workspaceMsg = " - ${workspaceManager.workspaceFile.absolutePath} $isBuildRunning" - - val isPaused = if (pipelineManager.paused) " (Paused)" else "" - val isRecording = if (isCurrentlyRecording()) " RECORDING" else "" - - val msg = isRecording + isPaused - - if (pipelineManager.currentPipeline == null) { - visualizer.setTitleMessage("No pipeline$msg${workspaceMsg}") - } else { - visualizer.setTitleMessage("${pipelineManager.currentPipelineName}$msg${workspaceMsg}") - } - } + fun isCurrentlyRecording() = recordingManager.isCurrentlyRecording() /** * Parameters class to initialize the simulator with @@ -621,14 +400,13 @@ class EOCVSim(val params: Parameters = Parameters()) { */ class Parameters { /** - * The initial user workspace file to load - * Overrides the workspace file in the config file + * The initial user workspace path to load + * Overrides the workspace path in the config */ var initialWorkspace: File? = null /** - * The initial pipeline name to load - * specified by class name + * The initial pipeline to load */ var initialPipelineName: String? = null @@ -636,11 +414,6 @@ class EOCVSim(val params: Parameters = Parameters()) { * Whether the specified pipeline must be searched in the CLASSPATH or from the workspace */ var initialPipelineSource: PipelineSource? = null - - /** - * An alternative path for the OpenCV native library to be loaded at runtime - */ - var opencvNativeLibrary: File? = null } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Lifecycle.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Lifecycle.kt new file mode 100644 index 00000000..e906a8d8 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Lifecycle.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim + +sealed interface LifecycleSignal { + class Destroy(val reason: Reason) : LifecycleSignal { + enum class Reason { USER_REQUESTED, THREAD_EXIT, CRASH } + } + object Restart : LifecycleSignal +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Main.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Main.kt index 5300382d..37f5807a 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Main.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Main.kt @@ -1,29 +1,13 @@ /* * Copyright (c) 2026 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim import com.github.serivesmejia.eocvsim.pipeline.PipelineSource +import org.koin.core.context.GlobalContext +import org.koin.dsl.module import picocli.CommandLine import java.io.File import java.nio.file.Paths @@ -85,13 +69,6 @@ class EOCVSimCommandInterface : Runnable { @JvmField var initialPipelineSource = PipelineSource.CLASSPATH - @CommandLine.Option( - names = ["-o", "--opencvpath"], - description = ["Specifies an alternative path for the OpenCV native to be loaded at runtime"] - ) - @JvmField - var opencvNativePath: String? = null - override fun run() { val parameters = EOCVSim.Parameters() @@ -104,11 +81,18 @@ class EOCVSimCommandInterface : Runnable { parameters.initialPipelineSource = initialPipelineSource } - if (opencvNativePath != null) { - parameters.opencvNativeLibrary = checkPath("OpenCV Native", opencvNativePath!!, false) + GlobalContext.startKoin { + modules( + eocvSimModule, + module { + single { EOCVSim() } + single { parameters } + } + ) } - EOCVSim(parameters).init() + // get eocv-sim from DI and start + GlobalContext.get().get().start() } private fun checkPath(parameter: String, path: String, shouldBeDirectory: Boolean): File { @@ -131,4 +115,4 @@ class EOCVSimCommandInterface : Runnable { return file } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Module.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Module.kt new file mode 100644 index 00000000..776a72d1 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Module.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim + +import com.github.serivesmejia.eocvsim.config.ConfigManager +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.input.InputSourceInitializer +import com.github.serivesmejia.eocvsim.input.InputSourceManager +import com.github.serivesmejia.eocvsim.output.RecordingManager +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.pipeline.compiled.CompiledPipelineManager +import com.github.serivesmejia.eocvsim.plugin.output.PluginOutputHandler +import com.github.serivesmejia.eocvsim.plugin.output.VisualPluginOutputHandler +import com.github.serivesmejia.eocvsim.tuner.TunerManager +import com.github.serivesmejia.eocvsim.util.InitClasspathScan +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.orchestration.Orchestrable +import com.github.serivesmejia.eocvsim.util.orchestration.Orchestrator +import com.github.serivesmejia.eocvsim.workspace.WorkspaceManager +import org.deltacv.common.pipeline.PipelineStatisticsCalculator +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.channels.Channel +import org.deltacv.eocvsim.plugin.loader.PluginManager +import org.koin.core.definition.KoinDefinition +import org.koin.core.qualifier.named +import org.koin.dsl.bind +import org.koin.dsl.module + +val eocvSimModule = module { + single { PipelineStatisticsCalculator() } + single { InitClasspathScan() }.bindOrchestrable() + + // global scope for launching coroutines within the app + single { CoroutineScope(SupervisorJob() + Dispatchers.Default) } + + single { ConfigManager() }.bindOrchestrable() + single { WorkspaceManager() }.bindOrchestrable() + single { PipelineManager() }.bindOrchestrable() + single { InputSourceManager() }.bindOrchestrable() + single { InputSourceInitializer() } + single { TunerManager() }.bindOrchestrable() + single { PluginManager() }.bindOrchestrable() + single { CompiledPipelineManager() }.bindOrchestrable() + + single { Visualizer() }.bindOrchestrable() + single { DialogFactory() } + single { VisualPluginOutputHandler() } + + single { RecordingManager() } + + single { Orchestrator(scope = get(), tasks = getAll(), name = "Main") } + single(named("lifecycle")) { Channel(Channel.BUFFERED) } + single(named("onCrash")) { EventHandler("OnCrash") } + single(named("onMainLoop")) { EventHandler("OnMainLoop") } +} + +private fun KoinDefinition.bindOrchestrable() = this bind Orchestrable::class diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java index 7064d338..bb2d2fff 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java @@ -1,58 +1,35 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.config; import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanelConfig; import com.github.serivesmejia.eocvsim.gui.theme.Theme; -import com.github.serivesmejia.eocvsim.gui.util.WebcamDriver; import com.github.serivesmejia.eocvsim.pipeline.PipelineFps; import com.github.serivesmejia.eocvsim.pipeline.PipelineTimeout; -import com.github.serivesmejia.eocvsim.pipeline.compiler.CompiledPipelineManager; -import com.github.serivesmejia.eocvsim.util.SysUtil; +import com.github.serivesmejia.eocvsim.pipeline.compiled.CompiledPipelineManager; import org.opencv.core.Size; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; public class Config { public volatile Theme simTheme = Theme.Dark; - @Deprecated - public volatile double zoom = 1; - public volatile PipelineFps pipelineMaxFps = PipelineFps.MEDIUM; public volatile PipelineTimeout pipelineTimeout = PipelineTimeout.MEDIUM; public volatile boolean pauseOnImages = true; + public volatile double webcamOpenTimeoutSec = 5.0; + public volatile double webcamNewFrameTimeoutSec = 3.0; + public volatile Size videoRecordingSize = new Size(640, 480); public volatile PipelineFps videoRecordingFps = PipelineFps.MEDIUM; - public volatile WebcamDriver preferredWebcamDriver = WebcamDriver.OpenPnp; - public volatile String workspacePath = CompiledPipelineManager.Companion.getDEF_WORKSPACE_FOLDER().getAbsolutePath(); + public volatile String workspacePath = CompiledPipelineManager.Companion.getDEF_WORKSPACE_FOLDER().getAbsolutePath(); public volatile TunableFieldPanelConfig.Config globalTunableFieldsConfig = new TunableFieldPanelConfig.Config( @@ -64,9 +41,6 @@ public class Config { public volatile HashMap specificTunableFieldConfig = new HashMap<>(); - @Deprecated - public volatile List superAccessPluginHashes = new ArrayList<>(); - public volatile boolean autoAcceptSuperAccessOnTrusted = true; public volatile HashMap flags = new HashMap<>(); @@ -79,4 +53,4 @@ public boolean getFlag(String flagName) { return flags.get(flagName) != null && flags.get(flagName); } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigLoader.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigLoader.java index 47ed7fbf..39ef2b5b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigLoader.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigLoader.java @@ -1,31 +1,12 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.config; import com.github.serivesmejia.eocvsim.util.SysUtil; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import com.github.serivesmejia.eocvsim.util.serialization.JacksonJsonSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,8 +22,6 @@ public class ConfigLoader { Logger logger = LoggerFactory.getLogger(getClass()); - static Gson gson = new GsonBuilder().setPrettyPrinting().create(); - public Config loadFromFile(File file) throws FileNotFoundException { if (!file.exists()) throw new FileNotFoundException(); @@ -50,9 +29,9 @@ public Config loadFromFile(File file) throws FileNotFoundException { if (jsonConfig.trim().equals("")) return null; try { - return gson.fromJson(jsonConfig, Config.class); + return JacksonJsonSupport.persistenceMapper.readValue(jsonConfig, Config.class); } catch (Exception ex) { - logger.info("Gson exception while parsing config file", ex); + logger.info("Jackson exception while parsing config file", ex); return null; } } @@ -63,7 +42,15 @@ public Config loadFromFile() throws FileNotFoundException { } public void saveToFile(File file, Config conf) { - String jsonConfig = gson.toJson(conf); + String jsonConfig; + + try { + jsonConfig = JacksonJsonSupport.persistenceMapper.writeValueAsString(conf); + } catch (Exception ex) { + logger.error("Failed to serialize config file", ex); + return; + } + SysUtil.saveFileStr(file, jsonConfig); } @@ -72,3 +59,4 @@ public void saveToFile(Config conf) { } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigManager.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigManager.java deleted file mode 100644 index d8c1605a..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigManager.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.config; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ConfigManager { - - public final ConfigLoader configLoader = new ConfigLoader(); - private Config config; - - Logger logger = LoggerFactory.getLogger(getClass()); - - public void init() { - logger.info("Initializing..."); - - try { - config = configLoader.loadFromFile(); - if (config == null) { - logger.error("Error while parsing config file, it will be replaced and fixed, but the user configurations will be reset"); - throw new NullPointerException(); //for it to be caught later and handle the creation of a new config - } else { - logger.info("Loaded config from file successfully"); - } - } catch (Exception ex) { //handles FileNotFoundException & a NullPointerException thrown above - config = new Config(); - logger.info("Creating config file..."); - configLoader.saveToFile(config); - } - - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - logger.info("SHUTDOWN - Saving config to file..."); - saveToFile(); - })); - } - - public void saveToFile() { - configLoader.saveToFile(config); - } - - public Config getConfig() { - return config; - } - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigManager.kt new file mode 100644 index 00000000..4179b09c --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigManager.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.config + +import com.github.serivesmejia.eocvsim.util.orchestration.PhaseOrchestrableBase +import org.deltacv.common.util.loggerForThis +import org.koin.core.component.KoinComponent + +class ConfigManager : PhaseOrchestrableBase(), KoinComponent { + val configLoader = ConfigLoader() + + var config: Config = Config() + private set + + private val logger by loggerForThis() + + override suspend fun init() { + logger.info("Initializing...") + + try { + val loadedConfig = configLoader.loadFromFile() + if (loadedConfig == null) { + logger.error("Error while parsing config file, it will be replaced and fixed, but the user configurations will be reset") + throw NullPointerException() // for it to be caught later and handle the creation of a new config + } else { + config = loadedConfig + logger.info("Loaded config from file successfully") + } + } catch (ex: Exception) { // handles FileNotFoundException & a NullPointerException thrown above + config = Config() + logger.info("Creating config file...") + configLoader.saveToFile(config) + } + } + + override suspend fun run() { } + + override suspend fun destroy() { + saveToFile() + } + + fun saveToFile() { + configLoader.saveToFile(config) + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java deleted file mode 100644 index ba605c77..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.gui; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.dialog.*; -import com.github.serivesmejia.eocvsim.gui.dialog.SplashScreen; -import com.github.serivesmejia.eocvsim.gui.dialog.iama.IAmA; -import com.github.serivesmejia.eocvsim.gui.dialog.iama.IAmAPaperVision; -import com.github.serivesmejia.eocvsim.gui.dialog.source.*; -import com.github.serivesmejia.eocvsim.input.SourceType; -import com.github.serivesmejia.eocvsim.util.event.EventHandler; -import io.github.deltacv.eocvsim.plugin.loader.PluginManager; - -import javax.swing.*; -import javax.swing.filechooser.FileFilter; -import javax.swing.filechooser.FileNameExtensionFilter; -import java.awt.*; -import java.io.File; -import java.util.ArrayList; -import java.util.function.IntConsumer; - -public class DialogFactory { - - private DialogFactory() { } - - public static void createYesOrNo(Component parent, String message, String submessage, IntConsumer result) { - JPanel panel = new JPanel(); - - JLabel label1 = new JLabel(message); - panel.add(label1); - - if (!submessage.isBlank()) { - JLabel label2 = new JLabel(submessage); - panel.add(label2); - panel.setLayout(new GridLayout(2, 1)); - } - - invokeLater(() -> result.accept( - JOptionPane.showConfirmDialog(parent, panel, "Confirm", - JOptionPane.YES_NO_OPTION, - JOptionPane.PLAIN_MESSAGE - ) - )); - } - - public static FileChooser createFileChooser(Component parent, FileChooser.Mode mode, FileFilter... filters) { - FileChooser fileChooser = new FileChooser(parent, mode, "", filters); - invokeLater(fileChooser::init); - return fileChooser; - } - - public static FileChooser createFileChooser(Component parent, FileChooser.Mode mode, String initialFileName, FileFilter... filters) { - FileChooser fileChooser = new FileChooser(parent, mode, initialFileName, filters); - invokeLater(fileChooser::init); - return fileChooser; - } - - public static FileChooser createFileChooser(Component parent, FileFilter... filters) { - return createFileChooser(parent, null, "", filters); - } - - public static FileChooser createFileChooser(Component parent, FileChooser.Mode mode) { - return createFileChooser(parent, mode, new FileFilter[0]); - } - - public static FileChooser createFileChooser(Component parent) { - return createFileChooser(parent, null, new FileFilter[0]); - } - - public static void createSourceDialog(EOCVSim eocvSim, - SourceType type, - File initialFile) { - invokeLater(() -> { - switch (type) { - case IMAGE: - new CreateImageSource(eocvSim.visualizer.frame, eocvSim, initialFile); - break; - case CAMERA: - new CreateCameraSource(eocvSim.visualizer.frame, eocvSim); - break; - case VIDEO: - new CreateVideoSource(eocvSim.visualizer.frame, eocvSim, initialFile); - break; - case HTTP: - new CreateHttpSource(eocvSim.visualizer.frame, eocvSim); - break; - } - }); - } - - public static void createSourceDialog(EOCVSim eocvSim, SourceType type) { - createSourceDialog(eocvSim, type, null); - } - - public static void createSourceDialog(EOCVSim eocvSim) { - invokeLater(() -> new CreateSource(eocvSim.visualizer.frame, eocvSim)); - } - - public static void createSourceExDialog(EOCVSim eocvSim) { - invokeLater(() -> new CreateSourceEx(eocvSim.visualizer.frame, eocvSim.visualizer)); - } - - public static void createConfigDialog(EOCVSim eocvSim) { - invokeLater(() -> new Configuration(eocvSim.visualizer.frame, eocvSim)); - } - - public static void createAboutDialog(EOCVSim eocvSim) { - invokeLater(() -> new About(eocvSim.visualizer.frame, eocvSim)); - } - - public static void createOutput(EOCVSim eocvSim, boolean wasManuallyOpened) { - invokeLater(() -> { - if(!Output.Companion.isAlreadyOpened()) - new Output(eocvSim.visualizer.frame, eocvSim, Output.Companion.getLatestIndex(), wasManuallyOpened); - }); - } - - public static void createOutput(EOCVSim eocvSim) { - createOutput(eocvSim, false); - } - - public static void createBuildOutput(EOCVSim eocvSim) { - invokeLater(() -> { - if(!Output.Companion.isAlreadyOpened()) - new Output(eocvSim.visualizer.frame, eocvSim, 1); - }); - } - - public static void createPipelineOutput(EOCVSim eocvSim) { - invokeLater(() -> { - if(!Output.Companion.isAlreadyOpened()) - new Output(eocvSim.visualizer.frame, eocvSim, 0); - }); - } - - public static AppendDelegate createMavenOutput(PluginManager manager, Runnable onContinue) { - AppendDelegate delegate = new AppendDelegate(); - - invokeLater(() -> new PluginOutput(delegate, manager, manager.getEocvSim(), onContinue)); - - return delegate; - } - - public static void createSplashScreen(EventHandler closeHandler) { - invokeLater(() -> new SplashScreen(closeHandler)); - } - - public static void createIAmA(Visualizer visualizer) { - invokeLater(() -> new IAmA(visualizer.frame, visualizer)); - } - - public static void createIAmAPaperVision(Visualizer visualizer, boolean showWorkspacesButton) { - invokeLater(() -> new IAmAPaperVision(visualizer.frame, visualizer, false, showWorkspacesButton)); - } - - public static void createWorkspace(Visualizer visualizer) { - invokeLater(() -> new CreateWorkspace(visualizer.frame, visualizer)); - } - - public static void createCrashReport(Visualizer visualizer, String crash) { - invokeLater(() -> new CrashReportOutput(visualizer == null ? null : visualizer.frame, crash)); - } - - public static FileAlreadyExists.UserChoice createFileAlreadyExistsDialog(EOCVSim eocvSim) { - return new FileAlreadyExists(eocvSim.visualizer.frame, eocvSim).run(); - } - - private static void invokeLater(Runnable runn) { - SwingUtilities.invokeLater(runn); - } - - public static class FileChooser { - private final JFileChooser chooser; - private final Component parent; - - private final Mode mode; - - private final ArrayList closeListeners = new ArrayList<>(); - - public FileChooser(Component parent, Mode mode, String initialFileName, FileFilter... filters) { - if (mode == null) mode = Mode.FILE_SELECT; - - chooser = new JFileChooser(); - - this.parent = parent; - this.mode = mode; - - if (mode == Mode.DIRECTORY_SELECT) { - chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - // disable the "All files" option. - chooser.setAcceptAllFileFilterUsed(false); - } - - chooser.setSelectedFile(new File(chooser.getSelectedFile(), initialFileName)); - - if (filters != null) { - for (FileFilter filter : filters) { - if(filter != null) chooser.addChoosableFileFilter(filter); - } - if(filters.length > 0) { - chooser.setFileFilter(filters[0]); - } - } - } - - protected void init() { - int returnVal; - - if (mode == Mode.SAVE_FILE_SELECT) { - returnVal = chooser.showSaveDialog(parent); - } else { - returnVal = chooser.showOpenDialog(parent); - } - - executeCloseListeners(returnVal, chooser.getSelectedFile(), chooser.getFileFilter()); - } - - public void addCloseListener(FileChooserCloseListener listener) { - this.closeListeners.add(listener); - } - - private void executeCloseListeners(int OPTION, File selectedFile, FileFilter selectedFileFilter) { - for (FileChooserCloseListener listener : closeListeners) { - listener.onClose(OPTION, selectedFile, selectedFileFilter); - } - } - - public void close() { - chooser.setVisible(false); - executeCloseListeners(JFileChooser.CANCEL_OPTION, new File(""), new FileNameExtensionFilter("", "")); - } - - public enum Mode { FILE_SELECT, DIRECTORY_SELECT, SAVE_FILE_SELECT } - - public interface FileChooserCloseListener { - void onClose(int OPTION, File selectedFile, FileFilter selectedFileFilter); - } - } - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.kt new file mode 100644 index 00000000..a3a68516 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.kt @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.gui + +import com.github.serivesmejia.eocvsim.config.ConfigManager +import com.github.serivesmejia.eocvsim.gui.dialog.* +import com.github.serivesmejia.eocvsim.gui.dialog.iama.IAmA +import com.github.serivesmejia.eocvsim.gui.dialog.iama.IAmAPaperVision +import com.github.serivesmejia.eocvsim.gui.dialog.source.* +import com.github.serivesmejia.eocvsim.input.SourceType +import com.github.serivesmejia.eocvsim.plugin.output.PluginOutputHandler +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.exception.handling.CrashReport +import org.deltacv.eocvsim.plugin.loader.PluginManager +import kotlinx.coroutines.CoroutineScope +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.awt.Component +import java.awt.GridLayout +import java.io.File +import javax.swing.* +import javax.swing.filechooser.FileFilter +import javax.swing.filechooser.FileNameExtensionFilter + +class DialogFactory : KoinComponent { + + private val visualizer: Visualizer by inject() + private val pluginManager: PluginManager by inject() + private val configManager: ConfigManager by inject() + private val scope: CoroutineScope by inject() + private val outputHandler: PluginOutputHandler by inject() + + private val frame: JFrame? get() = if (visualizer.hasFinishedInitializing) visualizer.frame else null + + fun createYesOrNo(parent: Component?, message: String, submessage: String, result: (Int) -> Unit) { + val panel = JPanel() + val label1 = JLabel(message) + panel.add(label1) + + if (submessage.isNotBlank()) { + val label2 = JLabel(submessage) + panel.add(label2) + panel.layout = GridLayout(2, 1) + } + + invokeLater { + result( + JOptionPane.showConfirmDialog( + parent, panel, "Confirm", + JOptionPane.YES_NO_OPTION, + JOptionPane.PLAIN_MESSAGE + ) + ) + } + } + + @JvmOverloads + fun createInformation( + parent: Component?, + message: String, + submessage: String? = null, + title: String = "Information", + cancelText: String? = null, + onCancel: (() -> Unit)? = null + ): JDialog { + val panel = JPanel().apply { + layout = GridLayout(if (submessage != null) 2 else 1, 1) + add(JLabel(message)) + if (submessage != null) add(JLabel(submessage)) + } + + val options = if (cancelText != null) arrayOf(cancelText) else null + val pane = JOptionPane(panel, JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION, null, options) + val dialog = pane.createDialog(parent, title) + + if (cancelText != null) { + pane.addPropertyChangeListener { evt -> + if (evt.propertyName == JOptionPane.VALUE_PROPERTY) { + if (pane.value == cancelText) { + onCancel?.invoke() + } + } + } + } + + dialog.isModal = false + + invokeLater { dialog.isVisible = true } + + return dialog + } + + fun createFileChooser( + parent: Component?, + mode: FileChooser.Mode?, + initialFileName: String, + vararg filters: FileFilter? + ): FileChooser { + val fileChooser = FileChooser(parent, mode, initialFileName, *filters) + invokeLater { fileChooser.init() } + return fileChooser + } + + fun createFileChooser( + parent: Component?, + mode: FileChooser.Mode?, + vararg filters: FileFilter? + ): FileChooser = createFileChooser(parent, mode, "", *filters) + + fun createFileChooser(parent: Component?, vararg filters: FileFilter?): FileChooser = + createFileChooser(parent, null, "", *filters) + + fun createSourceDialog(type: SourceType, initialFile: File? = null) { + invokeLater { + when (type) { + SourceType.IMAGE -> CreateImageSource(initialFile) + SourceType.CAMERA -> CreateCameraSource() + SourceType.VIDEO -> CreateVideoSource(initialFile) + SourceType.HTTP -> CreateHttpSource() + else -> {} + } + } + } + + fun createSourceExDialog() { + invokeLater { CreateSourceEx() } + } + + fun createConfigDialog() { + invokeLater { Configuration() } + } + + fun createAboutDialog() { + invokeLater { About() } + } + + fun createOutput(wasManuallyOpened: Boolean = false) { + invokeLater { + if (!Output.isAlreadyOpened) { + Output(Output.latestIndex, wasManuallyOpened) + } + } + } + + fun createBuildOutput() { + invokeLater { + if (!Output.isAlreadyOpened) { + Output(1) + } + } + } + + fun createPipelineOutput() { + invokeLater { + if (!Output.isAlreadyOpened) { + Output(0) + } + } + } + + fun createPluginOutput() { + invokeLater { PluginOutput(outputHandler, pluginManager, configManager, scope) } + } + + fun createSplashScreen(vararg closeHandlers: EventHandler) { + invokeLater { SplashScreen(*closeHandlers) } + } + + fun createIAmA() { + invokeLater { IAmA() } + } + + fun createIAmAPaperVision(showWorkspacesButton: Boolean) { + invokeLater { IAmAPaperVision(false, showWorkspacesButton) } + } + + fun createWorkspace() { + invokeLater { CreateWorkspace() } + } + + fun instantiateCrashReport(crash: String?, headerText: String? = null) = + CrashReportOutput(frame, crash ?: "", headerText) + + fun createCrashReport(crash: String?, headerText: String? = null) { + invokeLater { instantiateCrashReport(crash, headerText) } + } + + fun createFileAlreadyExistsDialog(): FileAlreadyExists.UserChoice { + return FileAlreadyExists().run() + } + + private fun invokeLater(runn: Runnable) { + SwingUtilities.invokeLater(runn) + } + + class FileChooser @JvmOverloads constructor( + private val parent: Component?, + val mode: Mode? = null, + initialFileName: String = "", + vararg filters: FileFilter? + ) { + private val chooser: JFileChooser = JFileChooser() + private val closeListeners = ArrayList() + + init { + val finalMode = mode ?: Mode.FILE_SELECT + if (finalMode == Mode.DIRECTORY_SELECT) { + chooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY + chooser.isAcceptAllFileFilterUsed = false + } + + chooser.selectedFile = File(chooser.selectedFile, initialFileName) + + filters.filterNotNull().forEach { chooser.addChoosableFileFilter(it) } + if (filters.isNotEmpty()) { + chooser.fileFilter = filters[0] + } + } + + fun init() { + val returnVal = if (mode == Mode.SAVE_FILE_SELECT) { + chooser.showSaveDialog(parent) + } else { + chooser.showOpenDialog(parent) + } + executeCloseListeners(returnVal, chooser.selectedFile, chooser.fileFilter) + } + + fun addCloseListener(listener: FileChooserCloseListener): FileChooser { + closeListeners.add(listener) + return this + } + + private fun executeCloseListeners(OPTION: Int, selectedFile: File?, selectedFileFilter: FileFilter?) { + for (listener in closeListeners) { + listener.onClose(OPTION, selectedFile, selectedFileFilter) + } + } + + fun close() { + chooser.isVisible = false + executeCloseListeners(JFileChooser.CANCEL_OPTION, File(""), FileNameExtensionFilter("", "")) + } + + enum class Mode { FILE_SELECT, DIRECTORY_SELECT, SAVE_FILE_SELECT } + + fun interface FileChooserCloseListener { + fun onClose(OPTION: Int, selectedFile: File?, selectedFileFilter: FileFilter?) + } + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/EOCVSimIconLibrary.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/EOCVSimIconLibrary.kt index 7efc4ccc..6fee1b37 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/EOCVSimIconLibrary.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/EOCVSimIconLibrary.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui object EOCVSimIconLibrary { @@ -61,4 +43,4 @@ object EOCVSimIconLibrary { val icoVsCode by icon("ico_vscode", "/images/icon/ico_vscode.png", false) val icoUser by icon("ico_user", "/images/icon/ico_user.png") -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconDelegate.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconDelegate.kt index 8f23937f..36effe94 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconDelegate.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconDelegate.kt @@ -1,24 +1,6 @@ /* * Copyright (c) 2026 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.gui @@ -35,4 +17,4 @@ class IconDelegate(val name: String, val path: String, allowInvert: Boolean = tr operator fun getValue(any: Any, property: KProperty<*>): Icons.NamedImageIcon { return Icons.getImage(name) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Icons.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Icons.kt index 0caf2086..c7b7555b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Icons.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Icons.kt @@ -1,36 +1,25 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui import com.github.serivesmejia.eocvsim.gui.util.GuiUtil -import io.github.deltacv.common.util.loggerForThis -import io.github.deltacv.vision.external.gui.util.ImgUtil +import org.deltacv.common.util.loggerForThis +import org.deltacv.vision.external.gui.util.ImgUtil import java.awt.image.BufferedImage import java.util.NoSuchElementException +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import javax.swing.ImageIcon -object Icons { + +object Icons : KoinComponent { + + private val visualizer: Visualizer by inject() + + private val bufferedImages = HashMap() @@ -94,6 +83,8 @@ object Icons { GuiUtil.invertBufferedImageColors(buffImg) } + + bufferedImages[name] = Image(buffImg, allowInvert) icons[name] = NamedImageIcon(name, buffImg) } @@ -120,6 +111,8 @@ object Icons { } } + + data class Image(val img: BufferedImage, val allowInvert: Boolean) data class FutureIcon(val name: String, val resourcePath: String, val allowInvert: Boolean) @@ -143,4 +136,4 @@ object Icons { } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java deleted file mode 100644 index 99af9304..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java +++ /dev/null @@ -1,572 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.gui; - -import com.formdev.flatlaf.FlatLaf; -import com.github.serivesmejia.eocvsim.Build; -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.CollapsiblePanelX; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.*; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.opmode.OpModeSelectorPanel; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.opmode.SidebarOpModeTabPanel; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.SidebarPipelineTabPanel; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.SourceSelectorPanel; -import io.github.deltacv.vision.external.gui.SwingOpenCvViewport; -import com.github.serivesmejia.eocvsim.gui.component.tuner.ColorPicker; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.PipelineSelectorPanel; -import com.github.serivesmejia.eocvsim.gui.theme.Theme; -import com.github.serivesmejia.eocvsim.pipeline.compiler.PipelineCompiler; -import com.github.serivesmejia.eocvsim.util.event.EventHandler; -import com.github.serivesmejia.eocvsim.workspace.util.VSCodeLauncher; -import com.github.serivesmejia.eocvsim.workspace.util.template.GradleWorkspaceTemplate; -import kotlin.Unit; -import org.opencv.core.Size; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.awt.event.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class Visualizer { - - public final EventHandler onInitFinished = new EventHandler("OnVisualizerInitFinish"); - public final EventHandler onPluginGuiAttachment = new EventHandler("OnPluginGuiAttachment"); - - public final ArrayList pleaseWaitDialogs = new ArrayList<>(); - - public final ArrayList childFrames = new ArrayList<>(); - public final ArrayList childDialogs = new ArrayList<>(); - - public final EOCVSim eocvSim; - public JFrame frame; - - public SwingOpenCvViewport viewport = null; - - public TopMenuBar menuBar = null; - public JPanel tunerMenuPanel; - - public JPanel sidebarContainer = null; - - public SidebarPanel sidebarPanel = null; - - public SidebarPipelineTabPanel sidebarPipelineTabPanel= null; - public SidebarOpModeTabPanel sidebarOpModeTabPanel= null; - - public PipelineSelectorPanel pipelineSelectorPanel = null; - public SourceSelectorPanel sourceSelectorPanel = null; - - public OpModeSelectorPanel opModeSelectorPanel = null; - - public JPanel tunerCollapsible; - - private String title = "EasyOpenCV Simulator v" + Build.standardVersionString; - private String titleMsg = "No pipeline"; - private String beforeTitle = ""; - private String beforeTitleMsg = ""; - - public ColorPicker colorPicker = null; - - private volatile boolean hasFinishedInitializing = false; - - Logger logger = LoggerFactory.getLogger(getClass()); - - public Visualizer(EOCVSim eocvSim) { - this.eocvSim = eocvSim; - } - - public void init(Theme theme) { - if (Taskbar.isTaskbarSupported()) { - try { - //set icon for mac os (and other systems which do support this method) - Taskbar.getTaskbar().setIconImage(EOCVSimIconLibrary.INSTANCE.getIcoEOCVSim128().getImage()); - } catch (final UnsupportedOperationException e) { - logger.warn("Setting the Taskbar icon image is not supported on this platform"); - } catch (final SecurityException e) { - logger.error("Security exception while setting TaskBar icon", e); - } - } - - try { - theme.install(); - } catch (Exception e) { - logger.error("Failed to install theme {}", theme.name(), e); - } - - Icons.INSTANCE.setDark(FlatLaf.isLafDark()); - - if (Build.isDev) { - title += "-dev "; - } - - //instantiate all swing elements after theme installation - frame = new JFrame(); - - String fpsMeterDescriptor = "deltacv EOCV-Sim v" + Build.standardVersionString; - if (Build.isDev) fpsMeterDescriptor += "-dev"; - - viewport = new SwingOpenCvViewport(new Size(1080, 720), fpsMeterDescriptor); - viewport.setDark(FlatLaf.isLafDark()); - - colorPicker = new ColorPicker(viewport); - - JLayeredPane skiaPanel = viewport.skiaPanel(); - skiaPanel.setLayout(new BorderLayout()); - - frame.add(skiaPanel); - - menuBar = new TopMenuBar(this, eocvSim); - - tunerMenuPanel = new JPanel(); - - sidebarPanel = new SidebarPanel(eocvSim); - - sidebarPipelineTabPanel = new SidebarPipelineTabPanel(eocvSim); - pipelineSelectorPanel = sidebarPipelineTabPanel.getPipelineSelectorPanel(); - sourceSelectorPanel = sidebarPipelineTabPanel.getSourceSelectorPanel(); - - sidebarOpModeTabPanel = new SidebarOpModeTabPanel(eocvSim); - opModeSelectorPanel = sidebarOpModeTabPanel.getOpModeSelectorPanel(); - - sidebarPanel.add("Pipeline", sidebarPipelineTabPanel); - sidebarPanel.add("OpMode", sidebarOpModeTabPanel); - - sidebarContainer = new JPanel(); - - /* - * TOP MENU BAR - */ - - frame.setJMenuBar(menuBar); - - /* - * IMG VISUALIZER & SCROLL PANE - */ - - sidebarContainer.setLayout(new BoxLayout(sidebarContainer, BoxLayout.Y_AXIS)); - // add pretty border - sidebarContainer.setBorder( - BorderFactory.createMatteBorder(0, 1, 0, 0, UIManager.getColor("Separator.foreground")) - ); - - sidebarPanel.setBorder(new EmptyBorder(0, 0, 0, 0)); - sidebarContainer.add(sidebarPanel); - - //global - frame.getContentPane().setDropTarget(new InputSourceDropTarget(eocvSim)); - - tunerCollapsible = new CollapsiblePanelX("Variable Tuner", null, null); - tunerCollapsible.setLayout(new BoxLayout(tunerCollapsible, BoxLayout.LINE_AXIS)); - tunerCollapsible.setVisible(false); - - JScrollPane tunerScrollPane = new JScrollPane(tunerMenuPanel); - tunerScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); - tunerScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); - - tunerCollapsible.add(tunerScrollPane); - - onPluginGuiAttachment.run(); - onPluginGuiAttachment.setCallRightAway(true); - - frame.add(tunerCollapsible, BorderLayout.SOUTH); - frame.add(sidebarContainer, BorderLayout.EAST); - - //initialize other various stuff of the frame - frame.setSize(780, 645); - frame.setMinimumSize(frame.getSize()); - frame.setTitle("EasyOpenCV Simulator - No Pipeline"); - - frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); - - frame.setIconImages(List.of( - EOCVSimIconLibrary.INSTANCE.getIcoEOCVSim128().getImage(), - EOCVSimIconLibrary.INSTANCE.getIcoEOCVSim64().getImage(), - EOCVSimIconLibrary.INSTANCE.getIcoEOCVSim32().getImage(), - EOCVSimIconLibrary.INSTANCE.getIcoEOCVSim16().getImage() - )); - - frame.setLocationRelativeTo(null); - frame.setExtendedState(JFrame.MAXIMIZED_BOTH); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - - frame.setVisible(true); - - onInitFinished.run(); - onInitFinished.setCallRightAway(true); - - registerListeners(); - - hasFinishedInitializing = true; - - if (!PipelineCompiler.Companion.getIS_USABLE()) { - compilerUnsupported(); - } - } - - public void initAsync(Theme simTheme) { - SwingUtilities.invokeLater(() -> init(simTheme)); - } - - private void registerListeners() { - - frame.addWindowListener(new WindowAdapter() { - public void windowClosing(WindowEvent e) { - eocvSim.onMainUpdate.once(eocvSim::destroy); - } - }); - - //handling onViewportTapped evts - viewport.getComponent().addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if (!colorPicker.isPicking()) - eocvSim.pipelineManager.callViewportTapped(); - } - }); - - // stop color-picking mode when changing pipeline - eocvSim.pipelineManager.onPipelineChange.attach(() -> colorPicker.stopPicking()); - } - - public boolean hasFinishedInit() { - return hasFinishedInitializing; - } - - public void joinInit() { - while (!hasFinishedInitializing) { - Thread.onSpinWait(); - } - } - - public void close() { - SwingUtilities.invokeLater(() -> { - frame.setVisible(false); - viewport.deactivate(); - - //close all asyncpleasewait dialogs - for (AsyncPleaseWaitDialog dialog : pleaseWaitDialogs) { - if (dialog != null) { - dialog.destroyDialog(); - } - } - - pleaseWaitDialogs.clear(); - - //close all opened frames - for (JFrame frame : childFrames) { - if (frame != null && frame.isVisible()) { - frame.setVisible(false); - frame.dispose(); - } - } - - childFrames.clear(); - - //close all opened dialogs - for (JDialog dialog : childDialogs) { - if (dialog != null && dialog.isVisible()) { - dialog.setVisible(false); - dialog.dispose(); - } - } - - childDialogs.clear(); - frame.dispose(); - viewport.deactivate(); - }); - } - - private void setFrameTitle(String title, String titleMsg) { - frame.setTitle(title + " - " + titleMsg); - } - - public void setTitle(String title) { - this.title = title; - if (!beforeTitle.equals(title)) setFrameTitle(title, titleMsg); - beforeTitle = title; - } - - public void setTitleMessage(String titleMsg) { - this.titleMsg = titleMsg; - if (!beforeTitleMsg.equals(title)) setFrameTitle(title, titleMsg); - beforeTitleMsg = titleMsg; - } - - public void updateTunerFields(List fields) { - tunerMenuPanel.removeAll(); - tunerMenuPanel.repaint(); - - for (TunableFieldPanel fieldPanel : fields) { - tunerMenuPanel.add(fieldPanel); - fieldPanel.showFieldPanel(); - } - - tunerCollapsible.setVisible(!fields.isEmpty()); - } - - public void asyncCompilePipelines() { - if (PipelineCompiler.Companion.getIS_USABLE()) { - menuBar.workspCompile.setEnabled(false); - pipelineSelectorPanel.getButtonsPanel().getPipelineCompileBtt().setEnabled(false); - - eocvSim.pipelineManager.compiledPipelineManager.asyncBuild((result) -> { - menuBar.workspCompile.setEnabled(true); - pipelineSelectorPanel.getButtonsPanel().getPipelineCompileBtt().setEnabled(true); - - return Unit.INSTANCE; - }); - } else { - compilerUnsupported(); - } - } - - public void compilerUnsupported() { - asyncPleaseWaitDialog( - "Runtime pipeline builds are not supported on this JVM", - "For further info, check the EOCV-Sim docs", - "Close", - new Dimension(320, 160), - true, true - ); - } - - public void selectPipelinesWorkspace() { - DialogFactory.createFileChooser( - frame, DialogFactory.FileChooser.Mode.DIRECTORY_SELECT - ).addCloseListener((OPTION, selectedFile, selectedFileFilter) -> { - if (OPTION == JFileChooser.APPROVE_OPTION) { - if (!selectedFile.exists()) selectedFile.mkdir(); - - eocvSim.onMainUpdate.once(() -> - eocvSim.workspaceManager.setWorkspaceFile(selectedFile) - ); - } - }); - } - - public void createVSCodeWorkspace() { - DialogFactory.createFileChooser(frame, DialogFactory.FileChooser.Mode.DIRECTORY_SELECT) - .addCloseListener((OPTION, selectedFile, selectedFileFilter) -> { - if (OPTION == JFileChooser.APPROVE_OPTION) { - if (!selectedFile.exists()) selectedFile.mkdir(); - - if (selectedFile.isDirectory() && Objects.requireNonNull(selectedFile.listFiles()).length == 0) { - eocvSim.workspaceManager.createWorkspaceWithTemplateAsync( - selectedFile, GradleWorkspaceTemplate.INSTANCE, - () -> { - askOpenVSCode(); - return Unit.INSTANCE; // weird kotlin interop - } - ); - } else { - asyncPleaseWaitDialog( - "The selected directory must be empty", "Select an empty directory or create a new one", - "Retry", new Dimension(320, 160), true, true - ).onCancel(this::createVSCodeWorkspace); - } - } - }); - } - - public void askOpenVSCode() { - DialogFactory.createYesOrNo(frame, "A new workspace was created. Do you want to open VS Code?", "", - (result) -> { - if (result == 0) { - JOptionPane.showMessageDialog( - frame, - "After opening VS Code, you will need to install the Extension Pack for Java, for proper autocompletion support. Ensure you do so when asked by the editor!" - ); - - VSCodeLauncher.INSTANCE.asyncLaunch(eocvSim.workspaceManager.getWorkspaceFile()); - } - } - ); - } - - // PLEASE WAIT DIALOGS - - public boolean pleaseWaitDialog(JDialog diag, String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable, AsyncPleaseWaitDialog apwd, boolean isError) { - final JDialog dialog = diag == null ? new JDialog(this.frame) : diag; - - boolean addSubMessage = subMessage != null; - - int rows = 3; - if (!addSubMessage) { - rows--; - } - - dialog.setModal(false); - dialog.setAlwaysOnTop(true); - dialog.setLayout(new GridLayout(rows, 1)); - - if (isError) { - dialog.setTitle("Operation failed"); - } else { - dialog.setTitle("Operation in progress"); - } - - JLabel msg = new JLabel(message); - msg.setHorizontalAlignment(JLabel.CENTER); - msg.setVerticalAlignment(JLabel.CENTER); - - dialog.add(msg); - - JLabel subMsg = null; - if (addSubMessage) { - subMsg = new JLabel(subMessage); - subMsg.setHorizontalAlignment(JLabel.CENTER); - subMsg.setVerticalAlignment(JLabel.CENTER); - - dialog.add(subMsg); - } - - JPanel exitBttPanel = new JPanel(new FlowLayout()); - JButton cancelBtt = new JButton(cancelBttText); - - cancelBtt.setEnabled(cancellable); - - exitBttPanel.add(cancelBtt); - - boolean[] cancelled = {false}; - - cancelBtt.addActionListener(e -> { - cancelled[0] = true; - dialog.setVisible(false); - dialog.dispose(); - }); - - dialog.add(exitBttPanel); - - if (apwd != null) { - apwd.msg = msg; - apwd.subMsg = subMsg; - apwd.cancelBtt = cancelBtt; - } - - if (size == null) size = new Dimension(400, 200); - dialog.setSize(size); - - dialog.setLocationRelativeTo(null); - dialog.setResizable(false); - dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); - - dialog.setVisible(true); - - return cancelled[0]; - } - - public void pleaseWaitDialog(JDialog dialog, String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable) { - pleaseWaitDialog(dialog, message, subMessage, cancelBttText, size, cancellable, null, false); - } - - public void pleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable) { - pleaseWaitDialog(null, message, subMessage, cancelBttText, size, cancellable, null, false); - } - - public AsyncPleaseWaitDialog asyncPleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable, boolean isError) { - AsyncPleaseWaitDialog rPWD = new AsyncPleaseWaitDialog(message, subMessage, cancelBttText, size, cancellable, isError, eocvSim); - SwingUtilities.invokeLater(rPWD); - - return rPWD; - } - - public AsyncPleaseWaitDialog asyncPleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable) { - AsyncPleaseWaitDialog rPWD = new AsyncPleaseWaitDialog(message, subMessage, cancelBttText, size, cancellable, false, eocvSim); - SwingUtilities.invokeLater(rPWD); - - return rPWD; - } - - public class AsyncPleaseWaitDialog implements Runnable { - - public volatile JDialog dialog = new JDialog(frame); - - public volatile JLabel msg = null; - public volatile JLabel subMsg = null; - - public volatile JButton cancelBtt = null; - - public volatile boolean wasCancelled = false; - public volatile boolean isError; - - public volatile String initialMessage; - public volatile String initialSubMessage; - - public volatile boolean isDestroyed = false; - - String message; - String subMessage; - String cancelBttText; - - Dimension size; - - boolean cancellable; - - private final ArrayList onCancelRunnables = new ArrayList<>(); - - public AsyncPleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable, boolean isError, EOCVSim eocvSim) { - this.message = message; - this.subMessage = subMessage; - this.initialMessage = message; - this.initialSubMessage = subMessage; - this.cancelBttText = cancelBttText; - - this.size = size; - this.cancellable = cancellable; - - this.isError = isError; - - eocvSim.visualizer.pleaseWaitDialogs.add(this); - } - - public void onCancel(Runnable runn) { - onCancelRunnables.add(runn); - } - - @Override - public void run() { - wasCancelled = pleaseWaitDialog(dialog, message, subMessage, cancelBttText, size, cancellable, this, isError); - - if (wasCancelled) { - for (Runnable runn : onCancelRunnables) { - runn.run(); - } - } - } - - public void destroyDialog() { - if (!isDestroyed) { - dialog.setVisible(false); - dialog.dispose(); - isDestroyed = true; - } - } - - } - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.kt new file mode 100644 index 00000000..376c3d8e --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.kt @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.gui + +import com.formdev.flatlaf.FlatLaf +import com.github.serivesmejia.eocvsim.Build +import com.github.serivesmejia.eocvsim.LifecycleSignal +import com.github.serivesmejia.eocvsim.LifecycleSignal.Destroy.Reason +import com.github.serivesmejia.eocvsim.config.ConfigManager +import com.github.serivesmejia.eocvsim.gui.component.CollapsiblePanelX +import com.github.serivesmejia.eocvsim.gui.component.tuner.ColorPicker +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel +import com.github.serivesmejia.eocvsim.gui.component.visualizer.InputSourceDropTarget +import com.github.serivesmejia.eocvsim.gui.component.visualizer.SidebarPanel +import com.github.serivesmejia.eocvsim.gui.component.visualizer.TopMenuBar +import com.github.serivesmejia.eocvsim.gui.component.visualizer.opmode.OpModeSelectorPanel +import com.github.serivesmejia.eocvsim.gui.component.visualizer.opmode.SidebarOpModeTabPanel +import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.PipelineSelectorPanel +import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.SidebarPipelineTabPanel +import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.SourceSelectorPanel +import com.github.serivesmejia.eocvsim.input.InputSourceManager +import com.github.serivesmejia.eocvsim.output.RecordingManager +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.pipeline.compiled.PipelineCompiler +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.orchestration.initDependency +import com.github.serivesmejia.eocvsim.util.orchestration.PhaseOrchestrableBase +import com.github.serivesmejia.eocvsim.workspace.WorkspaceManager +import com.github.serivesmejia.eocvsim.workspace.util.VSCodeLauncher +import com.github.serivesmejia.eocvsim.workspace.util.template.GradleWorkspaceTemplate +import com.qualcomm.robotcore.eventloop.opmode.OpMode +import org.deltacv.common.pipeline.PipelineStatisticsCalculator +import org.deltacv.common.util.loggerForThis +import org.deltacv.vision.external.gui.SwingOpenCvViewport +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.swing.Swing +import kotlinx.coroutines.withContext +import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named +import org.opencv.core.Size +import org.openftc.easyopencv.OpenCvViewport +import java.awt.BorderLayout +import java.util.concurrent.CancellationException +import java.awt.Dimension +import java.awt.Taskbar +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent +import javax.swing.* + +class Visualizer : PhaseOrchestrableBase(), KoinComponent { + + val onMainUpdate: EventHandler by inject(named("onMainLoop")) + + val lifecycleChannel: Channel by inject(named("lifecycle")) + + val pipelineManager: PipelineManager by inject() + val inputSourceManager: InputSourceManager by inject() + val configManager: ConfigManager by initDependency(inject()) + val workspaceManager: WorkspaceManager by inject() + val dialogFactory: DialogFactory by inject() + val recordingManager: RecordingManager by inject() + val pipelineStatisticsCalculator: PipelineStatisticsCalculator by inject() + val scope: CoroutineScope by inject() + + val onInitFinished = EventHandler("OnVisualizerInitFinish") + val onPluginGuiAttachment = EventHandler("OnPluginGuiAttachment") + + lateinit var frame: JFrame + private set + + private val fpsMeterDescriptor: String + get() = "deltacv EOCV-Sim v" + Build.standardVersionString + if (Build.isDev) "-dev" else "" + + @JvmField + val viewport = SwingOpenCvViewport(Size(1080.0, 720.0), fpsMeterDescriptor) + + lateinit var menuBar: TopMenuBar + private set + + lateinit var tunerMenuPanel: JPanel + private set + + lateinit var sidebarContainer: JPanel + private set + + lateinit var sidebarPanel: SidebarPanel + private set + + lateinit var sidebarPipelineTabPanel: SidebarPipelineTabPanel + private set + lateinit var sidebarOpModeTabPanel: SidebarOpModeTabPanel + private set + + lateinit var pipelineSelectorPanel: PipelineSelectorPanel + private set + lateinit var sourceSelectorPanel: SourceSelectorPanel + private set + + lateinit var opModeSelectorPanel: OpModeSelectorPanel + private set + + lateinit var tunerCollapsible: CollapsiblePanelX + private set + + private var title = "EasyOpenCV Simulator v" + Build.standardVersionString + private var titleMsg = "No pipeline" + private var beforeTitleMsg = "" + + lateinit var colorPicker: ColorPicker + private set + + @Volatile + var hasFinishedInitializing = false + private set + + private val logger by loggerForThis() + + private val pipelineRenderHook = + OpenCvViewport.RenderHook { + canvas, onscreenWidth, onscreenHeight, scaleBmpPxToCanvasPx, scaleCanvasDensity, userContext -> + if (pipelineManager.hasInitCurrentPipeline) { + pipelineManager.currentPipeline?.onDrawFrame(canvas, onscreenWidth, onscreenHeight, scaleBmpPxToCanvasPx, scaleCanvasDensity, userContext) + } + } + + override suspend fun init() = withContext(Dispatchers.Swing) { + try { + configManager.config.simTheme.install() + } catch (e: Exception) { + logger.error("Failed to install theme ${configManager.config.simTheme.name}", e) + } + + Icons.setDark(FlatLaf.isLafDark()) + + if (Build.isDev) { + title += "-dev " + } + + // PluginOutput is only ever created once in the lifetime + // of the software. It however does not show up right away + // upon creation, as it simply attaches to the appropriate + // EventHandler-s to show up whenever signaled to do so. + dialogFactory.createPluginOutput() + + frame = JFrame() + + viewport.init() + viewport.dark = FlatLaf.isLafDark() + + colorPicker = ColorPicker(viewport) + + val skiaPanel = viewport.skiaPanel() + skiaPanel.layout = BorderLayout() + + frame.add(skiaPanel) + + menuBar = TopMenuBar() + tunerMenuPanel = JPanel() + + sidebarPanel = SidebarPanel() + + sidebarPipelineTabPanel = SidebarPipelineTabPanel() + pipelineSelectorPanel = sidebarPipelineTabPanel.pipelineSelectorPanel + sourceSelectorPanel = sidebarPipelineTabPanel.sourceSelectorPanel + + sidebarOpModeTabPanel = SidebarOpModeTabPanel() + opModeSelectorPanel = sidebarOpModeTabPanel.opModeSelectorPanel + + sidebarPanel.add("Pipeline", sidebarPipelineTabPanel) + sidebarPanel.add("OpMode", sidebarOpModeTabPanel) + + sidebarContainer = JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + border = BorderFactory.createMatteBorder(0, 1, 0, 0, UIManager.getColor("Separator.foreground")) + add(sidebarPanel) + } + + frame.jMenuBar = menuBar + + frame.contentPane.dropTarget = InputSourceDropTarget() + + tunerCollapsible = CollapsiblePanelX("Variable Tuner", null, null).apply { + contentPanel.layout = BoxLayout(contentPanel, BoxLayout.LINE_AXIS) + isVisible = false + } + + val tunerScrollPane = JScrollPane(tunerMenuPanel).apply { + horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS + verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_NEVER + } + + tunerCollapsible.contentPanel.add(tunerScrollPane) + + onPluginGuiAttachment.run() + onPluginGuiAttachment.callRightAway = EventHandler.CallRightAway.InPlace + + frame.add(tunerCollapsible, BorderLayout.SOUTH) + frame.add(sidebarContainer, BorderLayout.EAST) + + frame.size = Dimension(780, 645) + frame.minimumSize = frame.size + frame.title = "EasyOpenCV Simulator - No Pipeline" + + frame.defaultCloseOperation = JFrame.DO_NOTHING_ON_CLOSE + + frame.iconImages = listOf( + EOCVSimIconLibrary.icoEOCVSim128.image, + EOCVSimIconLibrary.icoEOCVSim64.image, + EOCVSimIconLibrary.icoEOCVSim32.image, + EOCVSimIconLibrary.icoEOCVSim16.image + ) + + if (Taskbar.isTaskbarSupported()) { + val taskbar = Taskbar.getTaskbar() + try { + taskbar.iconImage = EOCVSimIconLibrary.icoEOCVSim128.image + } catch (_: UnsupportedOperationException) { + logger.warn("Setting the Taskbar icon image is not supported on this platform") + } catch (_: SecurityException) { + logger.warn("Setting the Taskbar icon image was not allowed by the security manager") + } + } + + frame.setLocationRelativeTo(null) + frame.extendedState = JFrame.MAXIMIZED_BOTH + + frame.isVisible = true + + onInitFinished.run() + onInitFinished.callRightAway = EventHandler.CallRightAway.InPlace + + registerListeners() + + inputSourceManager.onInputSourceInitError { + dialogFactory.createInformation( + frame, + "Error while loading source.", "Falling back to previous source.", + "Operation failed" + ) + } + + // Subscribe to initializer begin events so the UI can show a cancelable loading + // dialog for slow initializations without the initializer knowing about the UI. + val inputSourceInitializer: com.github.serivesmejia.eocvsim.input.InputSourceInitializer by inject() + inputSourceInitializer.onInitBegin.attachPayload { session -> + // Let listeners decide whether to show a dialog. We show it when the + // session explicitly requested a dialog or when the source itself + // signals a slow initialization. + val shouldShow = session.hasSlowInitialization || (session.inputSource?.hasSlowInitialization == true) + + if (!shouldShow) return@attachPayload + + val dialog = dialogFactory.createInformation( + frame, + if (session.sourceName.isNullOrBlank()) "Opening source..." else "Opening ${session.sourceName}...", + null, + "Information", + "Cancel" + ) { + session.cancelJob.cancel(CancellationException()) + } + + // Dispose dialog when the session finishes (success/failure/cancel/timeout) + scope.launch { + try { + session.resultSignal.await() + } catch (_: Exception) { + } finally { + dialog.dispose() + } + } + } + + hasFinishedInitializing = true + + if (!PipelineCompiler.IS_USABLE) { + compilerUnsupported() + } + + setupSubscriptions() + } + + override suspend fun run() { } + + private fun registerListeners() { + frame.addWindowListener(object : WindowAdapter() { + override fun windowClosing(e: WindowEvent) { + lifecycleChannel.trySend(LifecycleSignal.Destroy(Reason.USER_REQUESTED)) + } + }) + + viewport.component.addMouseListener(object : MouseAdapter() { + override fun mouseClicked(e: MouseEvent) { + if (!colorPicker.isPicking) { + pipelineManager.callViewportTapped() + } + } + }) + + pipelineManager.onPipelineChange.attach { colorPicker.stopPicking() } + } + + fun joinInit() { + while (!hasFinishedInitializing) { + Thread.onSpinWait() + } + } + + override suspend fun destroy() { + SwingUtilities.invokeLater { + frame.isVisible = false + viewport.dispose() + frame.dispose() + } + } + + private fun updateFrameTitle(title: String, titleMsg: String) { + frame.title = "$title - $titleMsg" + } + + fun setTitleMessage(titleMsg: String) { + this.titleMsg = titleMsg + if (beforeTitleMsg != titleMsg) updateFrameTitle(title, titleMsg) + beforeTitleMsg = titleMsg + } + + private fun updateTitle() { + val isBuildRunning = if (pipelineManager.compiledPipelineManager.isBuildRunning) "(Building)" else "" + + val workspaceMsg = " - ${workspaceManager.workspaceFile.absolutePath} $isBuildRunning" + + val isPaused = if (pipelineManager.paused) " (Paused)" else "" + val isRecording = if (recordingManager.isCurrentlyRecording()) " RECORDING" else "" + + val msg = isRecording + isPaused + + if (pipelineManager.currentPipeline == null) { + setTitleMessage("No pipeline$msg${workspaceMsg}") + } else { + setTitleMessage("${pipelineManager.currentPipelineName}$msg${workspaceMsg}") + } + } + + private fun setupSubscriptions() { + pipelineManager.onPipelineChange { + colorPicker.stopPicking() + pipelineStatisticsCalculator.init() + + if(pipelineManager.currentPipeline !is OpMode && pipelineManager.currentPipeline != null) { + viewport.activate() + viewport.setRenderHook(pipelineRenderHook) + } else { + viewport.deactivate() + viewport.clearViewport() + } + } + + pipelineManager.onUpdate { + if(pipelineManager.currentPipeline !is OpMode && pipelineManager.currentPipeline != null) { + viewport.notifyStatistics( + pipelineStatisticsCalculator.avgFps, + pipelineStatisticsCalculator.avgPipelineTime, + pipelineStatisticsCalculator.avgOverheadTime + ) + } + + updateTitle() + } + + pipelineManager.onPipelineTimeout { + dialogFactory.createInformation( + frame, + "Current pipeline took too long to ${pipelineManager.lastPipelineAction}", + "Falling back to DefaultPipeline", + "Operation failed" + ) + } + } + + fun updateTunerFields(fields: List) { + tunerMenuPanel.removeAll() + tunerMenuPanel.repaint() + + for (fieldPanel in fields) { + tunerMenuPanel.add(fieldPanel) + fieldPanel.showFieldPanel() + } + + tunerCollapsible.isVisible = fields.isNotEmpty() + } + + fun compilerUnsupported() { + dialogFactory.createInformation( + frame, + "Runtime pipeline builds are not supported on this JVM", + "For further info, check the EOCV-Sim docs", + "Operation failed" + ) + } + + + fun selectPipelinesWorkspace() { + dialogFactory.createFileChooser(frame, DialogFactory.FileChooser.Mode.DIRECTORY_SELECT) + .addCloseListener { option, selectedFile, _ -> + if (option == JFileChooser.APPROVE_OPTION && selectedFile != null) { + if (!selectedFile.exists()) selectedFile.mkdir() + onMainUpdate.once { + + workspaceManager.workspaceFile = selectedFile + } + } + } + } + + + fun createVSCodeWorkspace() { + dialogFactory.createFileChooser(frame, DialogFactory.FileChooser.Mode.DIRECTORY_SELECT) + .addCloseListener { option, selectedFile, _ -> + if (option == JFileChooser.APPROVE_OPTION && selectedFile != null) { + if (!selectedFile.exists()) selectedFile.mkdir() + + if (selectedFile.isDirectory && (selectedFile.listFiles()?.size ?: 0) == 0) { + workspaceManager.createWorkspaceWithTemplateAsync(selectedFile, GradleWorkspaceTemplate) { + askOpenVSCode() + } + } else { + dialogFactory.createYesOrNo( + frame, + "Open Workspace", + "The workspace has been successfully created. Do you want to open it?", + ) { result -> + if (result == JOptionPane.YES_OPTION) { + VSCodeLauncher.launch(selectedFile) + } + } + } + } + } + } + + fun askOpenVSCode() { + dialogFactory.createYesOrNo(frame, "A new workspace was created. Do you want to open VS Code?", "") { result -> + if (result == 0) { + JOptionPane.showMessageDialog( + frame, + "After opening VS Code, you will need to install the Extension Pack for Java, for proper autocompletion support. Ensure you do so when asked by the editor!" + ) + VSCodeLauncher.asyncLaunch(workspaceManager.workspaceFile, scope) + + } + } + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/EnumComboBox.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/EnumComboBox.kt index 3530403c..4b19682b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/EnumComboBox.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/EnumComboBox.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.input import com.github.serivesmejia.eocvsim.util.event.EventHandler @@ -76,3 +58,4 @@ class EnumComboBox> @JvmOverloads constructor( } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/FileSelector.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/FileSelector.kt index 2c9cd08d..f7cbf5b1 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/FileSelector.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/FileSelector.kt @@ -1,30 +1,14 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.input import com.github.serivesmejia.eocvsim.gui.DialogFactory import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.awt.FlowLayout import java.io.File import javax.swing.* @@ -32,7 +16,9 @@ import javax.swing.filechooser.FileFilter class FileSelector(columns: Int = 18, mode: DialogFactory.FileChooser.Mode, - vararg fileFilters: FileFilter?) : JPanel(FlowLayout()) { + vararg fileFilters: FileFilter?) : JPanel(FlowLayout()), KoinComponent { + + private val dialogFactory: DialogFactory by inject() constructor(columns: Int, vararg fileFilters: FileFilter?) : this(columns, DialogFactory.FileChooser.Mode.FILE_SELECT, *fileFilters) @@ -58,7 +44,7 @@ class FileSelector(columns: Int = 18, selectDirButton.addActionListener { val frame = SwingUtilities.getWindowAncestor(this) - DialogFactory.createFileChooser(frame, mode, *fileFilters).addCloseListener { returnVal: Int, selectedFile: File?, selectedFileFilter: FileFilter? -> + dialogFactory.createFileChooser(frame, mode, "", *fileFilters).addCloseListener { returnVal: Int, selectedFile: File?, selectedFileFilter: FileFilter? -> if (returnVal == JFileChooser.APPROVE_OPTION) { lastSelectedFileFilter = selectedFileFilter lastSelectedFile = selectedFile @@ -71,3 +57,4 @@ class FileSelector(columns: Int = 18, } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/SizeFields.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/SizeFields.kt index e63c99ce..26d59348 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/SizeFields.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/SizeFields.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.input import com.github.serivesmejia.eocvsim.EOCVSim @@ -138,4 +120,4 @@ class SizeFields(initialSize: Size = EOCVSim.DEFAULT_EOCV_SIZE, } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/ColorPicker.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/ColorPicker.kt index 7826c415..544874a6 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/ColorPicker.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/ColorPicker.kt @@ -1,32 +1,14 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.tuner import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary import com.github.serivesmejia.eocvsim.util.SysUtil import com.github.serivesmejia.eocvsim.util.event.EventHandler -import io.github.deltacv.vision.external.gui.SwingOpenCvViewport +import org.deltacv.vision.external.gui.SwingOpenCvViewport import org.opencv.core.Scalar import java.awt.Cursor import java.awt.Point @@ -111,3 +93,4 @@ class ColorPicker(private val viewport: SwingOpenCvViewport) { } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java deleted file mode 100644 index 87857b52..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.tuner; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableComboBox; -import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableSlider; -import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableTextField; -import com.github.serivesmejia.eocvsim.tuner.TunableField; - -import javax.swing.*; -import javax.swing.border.SoftBevelBorder; -import java.awt.*; - -@SuppressWarnings("unchecked") -public class TunableFieldPanel extends JPanel { - - public final TunableField tunableField; - - public TunableTextField[] fields; - public JPanel fieldsPanel; - - public TunableSlider[] sliders; - public JPanel slidersPanel; - - public JComboBox[] comboBoxes; - - public TunableFieldPanelOptions panelOptions = null; - private final EOCVSim eocvSim; - - private Mode mode; - private boolean reevalConfigRequested = false; - - private boolean hasBeenShown = false; - - public enum Mode { TEXTBOXES, SLIDERS } - - public TunableFieldPanel(TunableField tunableField, EOCVSim eocvSim) { - super(); - - this.tunableField = tunableField; - this.eocvSim = eocvSim; - - tunableField.setTunableFieldPanel(this); - - init(); - } - - private void init() { - //nice look - setBorder(new SoftBevelBorder(SoftBevelBorder.RAISED)); - - panelOptions = new TunableFieldPanelOptions(this, eocvSim); - - if(tunableField.getGuiFieldAmount() > 0) { - add(panelOptions); - } - - JLabel fieldNameLabel = new JLabel(); - fieldNameLabel.setText(tunableField.getFieldName()); - - add(fieldNameLabel); - - int fieldAmount = tunableField.getGuiFieldAmount(); - - fields = new TunableTextField[fieldAmount]; - sliders = new TunableSlider[fieldAmount]; - - fieldsPanel = new JPanel(); - slidersPanel = new JPanel(new GridBagLayout()); - - for (int i = 0 ; i < tunableField.getGuiFieldAmount() ; i++) { - //add the tunable field as a field - TunableTextField field = new TunableTextField(i, tunableField, eocvSim); - fields[i] = field; - - field.setEditable(true); - fieldsPanel.add(field); - - //add the tunable field as a slider - JLabel sliderLabel = new JLabel("0"); - TunableSlider slider = new TunableSlider(i, tunableField, eocvSim, sliderLabel); - sliders[i] = slider; - - GridBagConstraints cSlider = new GridBagConstraints(); - cSlider.gridx = 0; - cSlider.gridy = i; - - GridBagConstraints cLabel = new GridBagConstraints(); - cLabel.gridy = 1; - cLabel.gridy = i; - - slidersPanel.add(slider, cSlider); - slidersPanel.add(sliderLabel, cLabel); - } - - setMode(Mode.TEXTBOXES); - - comboBoxes = new JComboBox[tunableField.getGuiComboBoxAmount()]; - - for (int i = 0; i < comboBoxes.length; i++) { - TunableComboBox comboBox = new TunableComboBox(i, tunableField, eocvSim); - add(comboBox); - - comboBoxes[i] = comboBox; - } - } - - //method that should be called when this panel is added to the visualizer gui - public void showFieldPanel() { - if(hasBeenShown) return; - hasBeenShown = true; - - //updates the slider ranges from config - panelOptions.getConfigPanel().updateFieldGuiFromConfig(); - tunableField.evalRecommendedPanelMode(); - } - - public void setFieldValue(int index, Object value) { - if(index >= fields.length) return; - if(fields[index].isFocusOwner() || sliders[index].isFocusOwner()) return; // don't update if the field is being edited - - String text; - if(tunableField.getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS) { - text = String.valueOf((int) Math.round(Double.parseDouble(value.toString()))); - } else { - text = value.toString(); - } - - fields[index].setText(text); - - try { - sliders[index].setScaledValue(Double.parseDouble(value.toString())); - } catch(NumberFormatException ignored) {} - } - - public void setComboBoxSelection(int index, Object selection) { - comboBoxes[index].setSelectedItem(selection.toString()); - } - - protected void requestAllConfigReeval() { - reevalConfigRequested = true; - } - - public void setMode(Mode mode) { - switch(mode) { - case TEXTBOXES: - if(this.mode == Mode.SLIDERS) { - remove(slidersPanel); - } - - for(int i = 0 ; i < tunableField.getGuiFieldAmount() ; i++) { - fields[i].setInControl(true); - sliders[i].setInControl(false); - setFieldValue(i, tunableField.getGuiFieldValue(i)); - } - - add(fieldsPanel); - break; - case SLIDERS: - if(this.mode == Mode.TEXTBOXES) { - remove(fieldsPanel); - } - - for(int i = 0 ; i < tunableField.getGuiFieldAmount() ; i++) { - fields[i].setInControl(false); - sliders[i].setInControl(true); - setFieldValue(i, tunableField.getGuiFieldValue(i)); - } - - add(slidersPanel); - break; - } - - this.mode = mode; - - if(panelOptions.getMode() != mode) { - panelOptions.setMode(mode); - } - - revalidate(); repaint(); - } - - public void setSlidersRange(double min, double max) { - if(sliders == null) return; - for(TunableSlider slider : sliders) { - slider.setScaledBounds(min, max); - } - } - - public Mode getMode() { return this.mode; } - - public boolean hasRequestedAllConfigReeval() { - boolean current = reevalConfigRequested; - reevalConfigRequested = false; - - return current; - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.kt new file mode 100644 index 00000000..4b828c25 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.kt @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.gui.component.tuner + + +import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableComboBox +import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableSlider +import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableTextField +import com.github.serivesmejia.eocvsim.tuner.* +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import javax.swing.* +import javax.swing.border.SoftBevelBorder + +class TunableFieldPanel(val tunableField: TunableField<*>) : JPanel(), KoinComponent { + + + + var fields: Array? = null + var fieldsPanel: JPanel? = null + + var sliders: Array? = null + var slidersPanel: JPanel? = null + + var comboBoxes: Array?>? = null + + var panelOptions: TunableFieldPanelOptions? = null + + var mode: Mode = Mode.TEXTBOXES + set(value) { + when (value) { + Mode.TEXTBOXES -> { + if (this.mode == Mode.SLIDERS) { + slidersPanel?.let { remove(it) } + } + + fields?.let { + for (i in it.indices) { + it[i]?.isInControl = true + sliders?.get(i)?.inControl = false + it[i]?.let { setFieldValue(i, tunableField.tunableValues[i].value) } + } + } + + fieldsPanel?.let { add(it) } + } + + Mode.SLIDERS -> { + if (this.mode == Mode.TEXTBOXES) { + fieldsPanel?.let { remove(it) } + } + + fields?.let { + for (i in it.indices) { + it[i]?.isInControl = false + sliders?.get(i)?.inControl = true + it[i]?.let { setFieldValue(i, tunableField.tunableValues[i].value) } + } + } + + slidersPanel?.let { add(it) } + } + } + + field = value + + if (panelOptions?.mode != value) { + panelOptions?.mode = field + } + + revalidate() + repaint() + } + + private var reevalConfigRequested = false + private var hasBeenShown = false + + enum class Mode { TEXTBOXES, SLIDERS } + + init { + tunableField.setTunableFieldPanel(this) + initComponents() + } + + private fun initComponents() { + border = SoftBevelBorder(SoftBevelBorder.RAISED) + + val values = tunableField.tunableValues + + fields = arrayOfNulls(values.size) + sliders = arrayOfNulls(values.size) + comboBoxes = arrayOfNulls(values.size) + + fieldsPanel = JPanel() + slidersPanel = JPanel(GridBagLayout()) + + panelOptions = TunableFieldPanelOptions(this) + + val hasFieldsOrSliders = values.any { it is TunableNumber || it is TunableString } + + if (hasFieldsOrSliders) { + add(panelOptions) + } + + val fieldNameLabel = JLabel(tunableField.fieldName) + add(fieldNameLabel) + + for (i in values.indices) { + val value = values[i] + + if (value is TunableNumber || value is TunableString) { + val field = TunableTextField(value) + fields!![i] = field + field.isEditable = true + fieldsPanel!!.add(field) + + if (value is TunableNumber) { + val sliderLabel = JLabel("0") + val slider = TunableSlider(value, sliderLabel) + sliders!![i] = slider + + val cSlider = GridBagConstraints().apply { + gridx = 0 + gridy = i + } + + val cLabel = GridBagConstraints().apply { + gridx = 1 + gridy = i + } + + slidersPanel!!.add(slider, cSlider) + slidersPanel!!.add(sliderLabel, cLabel) + } + } else if (value is TunableEnum<*>) { + val comboBox = TunableComboBox(value) + add(comboBox) + comboBoxes!![i] = comboBox + } + } + + mode = Mode.TEXTBOXES + } + + fun showFieldPanel() { + if (hasBeenShown) return + hasBeenShown = true + + panelOptions?.configPanel?.updateFieldGuiFromConfig() + if (!tunableField.isOnlyNumbers) { + mode = Mode.SLIDERS + } + } + + fun setFieldValue(index: Int, value: Any) { + if (fields == null || index >= fields!!.size) return + val field = fields!![index] ?: return + + if (field.isFocusOwner) return + if (sliders?.get(index)?.isFocusOwner == true) return + + val tv = tunableField.tunableValues[index] + + val text = if (tv is TunableNumber && tv.isOnlyNumbers) { + (value.toString().toDouble().toInt()).toString() + } else { + value.toString() + } + + field.text = text + + try { + sliders?.get(index)?.scaledValue = value.toString().toDouble() + } catch (_: NumberFormatException) { } + } + + fun setComboBoxSelection(index: Int, selection: Any) { + comboBoxes?.get(index)?.selectedItem = selection.toString() + } + + fun requestAllConfigReeval() { + reevalConfigRequested = true + } + + fun setSlidersRange(min: Double, max: Double) { + sliders?.let { + for (slider in it) { + slider?.setScaledBounds(min, max) + } + } + } + + fun hasRequestedAllConfigReeval(): Boolean { + val current = reevalConfigRequested + reevalConfigRequested = false + return current + } + +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelConfig.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelConfig.kt index 4c1cc071..bac0156c 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelConfig.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelConfig.kt @@ -1,33 +1,18 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.gui.component.tuner -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.config.ConfigManager import com.github.serivesmejia.eocvsim.gui.component.PopupX import com.github.serivesmejia.eocvsim.gui.component.input.EnumComboBox import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields import com.github.serivesmejia.eocvsim.tuner.TunableField +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.deltacv.eocvsim.plugin.loader.PluginManager import kotlinx.coroutines.* import kotlinx.coroutines.swing.Swing import org.opencv.core.Size @@ -38,11 +23,20 @@ import java.awt.GridBagLayout import javax.swing.* import javax.swing.border.EmptyBorder +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named + @OptIn(DelicateCoroutinesApi::class) -class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions, - private val eocvSim: EOCVSim) : JPanel() { +class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions) : JPanel(), KoinComponent { + + private val configManager: ConfigManager by inject() + private val scope: CoroutineScope by inject() + + + + var localConfig = configManager.config.globalTunableFieldsConfig.copy() - var localConfig = eocvSim.config.globalTunableFieldsConfig.copy() private set private var lastApplyPopup: PopupX? = null @@ -159,16 +153,17 @@ class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions it.closeOnFocusLost = true //launch the waiting in the background - GlobalScope.launch { - delay(100) - //close config popup if still hasn't focused after a bit - launch(Dispatchers.Swing) { - if (!it.window.isFocused && (lastApplyPopup == null || lastApplyPopup?.window?.isFocused == false)) { - it.hide() - } + scope.launch { + delay(100) + //close config popup if still hasn't focused after a bit + launch(Dispatchers.Swing) { + if (!it.window.isFocused && (lastApplyPopup == null || lastApplyPopup?.window?.isFocused == false)) { + it.hide() } } } + + } } popup.show() @@ -232,7 +227,8 @@ class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions applyToConfig() //saves the current values to the current local config localConfig.source = ConfigSource.GLOBAL //changes the source of the local config to global - eocvSim.config.globalTunableFieldsConfig = localConfig.copy() + configManager.config.globalTunableFieldsConfig = localConfig.copy() + updateConfigSourceLabel() fieldOptions.fieldPanel.requestAllConfigReeval() @@ -244,7 +240,8 @@ class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions val typeClass = fieldOptions.fieldPanel.tunableField::class.java localConfig.source = ConfigSource.TYPE_SPECIFIC //changes the source of the local config to type specific - eocvSim.config.specificTunableFieldConfig[typeClass.name] = localConfig.copy() + configManager.config.specificTunableFieldConfig[typeClass.name] = localConfig.copy() + updateConfigSourceLabel() fieldOptions.fieldPanel.requestAllConfigReeval() @@ -252,19 +249,20 @@ class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions //loads the config from global eocv sim config file internal fun applyFromEOCVSimConfig() { - val specificConfigs = eocvSim.config.specificTunableFieldConfig + val specificConfigs = configManager.config.specificTunableFieldConfig //apply specific config if we have one, or else, apply global localConfig = if(specificConfigs.containsKey(fieldTypeClass.name)) { specificConfigs[fieldTypeClass.name]!!.copy() } else { - eocvSim.config.globalTunableFieldsConfig.copy() + configManager.config.globalTunableFieldsConfig.copy() } updateConfigGuiFromConfig() updateConfigSourceLabel() } + //applies the current values to the specified config, defaults to local @Suppress("UNNECESSARY_SAFE_CALL") private fun applyToConfig(config: Config = localConfig) { @@ -282,9 +280,7 @@ class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions } //sets the panel mode (sliders or textboxes) to config from the current mode - if(fieldOptions.fieldPanel?.mode != null) { - config.fieldPanelMode = fieldOptions.fieldPanel.mode - } + config.fieldPanelMode = fieldOptions.fieldPanel.mode } private fun updateConfigSourceLabel(currentConfig: Config = localConfig) { @@ -306,7 +302,7 @@ class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions //sets the slider range from config fieldOptions.fieldPanel.setSlidersRange(localConfig.sliderRange.width, localConfig.sliderRange.height) //sets the panel mode (sliders or textboxes) to config from the current mode - if(fieldOptions.fieldPanel?.fields != null){ + if(fieldOptions.fieldPanel.fields != null){ fieldOptions.fieldPanel.mode = localConfig.fieldPanelMode } } @@ -333,4 +329,4 @@ class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions return fields } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelOptions.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelOptions.kt index 20a3e28a..464f8249 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelOptions.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelOptions.kt @@ -1,32 +1,15 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.tuner -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.Visualizer import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary import com.github.serivesmejia.eocvsim.gui.component.PopupX -import io.github.deltacv.vision.external.util.extension.cvtColor +import com.github.serivesmejia.eocvsim.tuner.TunableNumber +import org.deltacv.vision.external.util.extension.cvtColor import com.github.serivesmejia.eocvsim.util.extension.clipUpperZero import java.awt.FlowLayout import java.awt.GridLayout @@ -36,8 +19,13 @@ import javax.swing.* import javax.swing.event.AncestorEvent import javax.swing.event.AncestorListener -class TunableFieldPanelOptions(val fieldPanel: TunableFieldPanel, - eocvSim: EOCVSim) : JPanel() { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class TunableFieldPanelOptions(val fieldPanel: TunableFieldPanel) : JPanel(), KoinComponent { + + val visualizer: Visualizer by inject() + private val sliderIco by EOCVSimIconLibrary.icoSlider.lazyResized(15, 15) private val textBoxIco by EOCVSimIconLibrary.icoTextbox.lazyResized(15, 15) @@ -48,7 +36,8 @@ class TunableFieldPanelOptions(val fieldPanel: TunableFieldPanel, private val configButton = JButton() private val colorPickButton = JToggleButton() - val configPanel = TunableFieldPanelConfig(this, eocvSim) + val configPanel = TunableFieldPanelConfig(this) + var lastConfigPopup: PopupX? = null private set @@ -114,7 +103,8 @@ class TunableFieldPanelOptions(val fieldPanel: TunableFieldPanel, } colorPickButton.addActionListener { - val colorPicker = eocvSim.visualizer.colorPicker + val colorPicker = visualizer.colorPicker + //start picking if global color picker is not being used by other panel if(!colorPicker.isPicking && colorPickButton.isSelected) { @@ -144,15 +134,21 @@ class TunableFieldPanelOptions(val fieldPanel: TunableFieldPanel, colorPicker.onPick.once { val colorScalar = colorPicker.colorRgb.cvtColor(configPanel.localConfig.pickerColorSpace.cvtCode) - //setting the scalar value in order from first to fourth field - for(i in 0..(fieldPanel.fields.size - 1).clipUpperZero()) { - //if we're still in range of the scalar values amount - if(i < colorScalar.`val`.size) { - val colorVal = colorScalar.`val`[i] - fieldPanel.setFieldValue(i, colorVal) - fieldPanel.tunableField.setFieldValueFromGui(i, colorVal.toString()) - } else { break } //keep looping until we write the entire scalar value + fieldPanel.fields?.let { + //setting the scalar value in order from first to fourth field + for (i in 0..(it.size - 1).clipUpperZero()) { + //if we're still in range of the scalar values amount + if (i < colorScalar.`val`.size) { + val colorVal = colorScalar.`val`[i] + + val tv = fieldPanel.tunableField.tunableValues.getOrNull(i) as? TunableNumber + tv?.setFromGui(colorVal) + } else { + break + } //keep looping until we write the entire scalar value + } } + colorPickButton.isSelected = false } @@ -192,4 +188,4 @@ class TunableFieldPanelOptions(val fieldPanel: TunableFieldPanel, repaint() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableComboBox.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableComboBox.java deleted file mode 100644 index 8a542aa9..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableComboBox.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.tuner.element; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.TunableField; - -import javax.swing.*; -import java.util.Objects; - -public class TunableComboBox extends JComboBox { - - private final TunableField tunableField; - private final int index; - - private final EOCVSim eocvSim; - - public TunableComboBox(int index, TunableField tunableField, EOCVSim eocvSim) { - super(); - - this.tunableField = tunableField; - this.index = index; - this.eocvSim = eocvSim; - - init(); - } - - private void init() { - for (Object obj : tunableField.getGuiComboBoxValues(index)) { - this.addItem(obj.toString()); - } - - addItemListener(evt -> eocvSim.onMainUpdate.once(() -> { - try { - tunableField.setComboBoxValueFromGui(index, Objects.requireNonNull(getSelectedItem()).toString()); - } catch (IllegalAccessException ignored) {} - - if (eocvSim.pipelineManager.getPaused()) { - eocvSim.pipelineManager.requestSetPaused(false); - } - })); - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableComboBox.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableComboBox.kt new file mode 100644 index 00000000..2c02a2c6 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableComboBox.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.gui.component.tuner.element + +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.tuner.TunableEnum +import org.koin.core.qualifier.named +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.awt.event.ItemEvent +import javax.swing.JComboBox + +class TunableComboBox(val tunableValue: TunableEnum<*>) : JComboBox(), KoinComponent { + + private val pipelineManager: PipelineManager by inject() + private val onMainUpdate: EventHandler by inject(named("onMainLoop")) + + + init { + initComponents() + } + + private fun initComponents() { + for (obj in tunableValue.enumValues) { + addItem(obj.toString()) + } + + addItemListener { evt -> + onMainUpdate.once { + if (evt.stateChange == ItemEvent.SELECTED) { + val values = tunableValue.enumValues + var selected: Any? = null + val selectedStr = selectedItem?.toString() ?: return@once + + for (valObj in values) { + if (valObj.toString() == selectedStr) { + selected = valObj + break + } + } + + if (selected != null) { + @Suppress("UNCHECKED_CAST") + (tunableValue as TunableEnum>).setFromGui(selected as Enum<*>) + } + + if (pipelineManager.paused) { + pipelineManager.requestSetPaused(false) + } + } + } + } + + } + +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableSlider.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableSlider.kt index b64252c1..c8f5c539 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableSlider.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableSlider.kt @@ -1,73 +1,61 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.tuner.element -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.util.event.EventHandler import com.github.serivesmejia.eocvsim.gui.component.SliderX -import com.github.serivesmejia.eocvsim.tuner.TunableField -import com.github.serivesmejia.eocvsim.util.event.EventListener +import org.koin.core.qualifier.named +import com.github.serivesmejia.eocvsim.tuner.TunableNumber import javax.swing.JLabel import kotlin.math.roundToInt -class TunableSlider(val index: Int, - val tunableField: TunableField<*>, - val eocvSim: EOCVSim, +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class TunableSlider(val tunableValue: TunableNumber, val valueLabel: JLabel? = null, minBound: Double = 0.0, - maxBound: Double = 255.0) : SliderX(minBound, maxBound, 10) { + maxBound: Double = 255.0) : SliderX(minBound, maxBound, 10), KoinComponent { + + private val pipelineManager: PipelineManager by inject() + private val onMainUpdate: EventHandler by inject(named("onMainLoop")) + + var inControl = false - constructor(i: Int, tunableField: TunableField, eocvSim: EOCVSim, valueLabel: JLabel) : this(i, tunableField, eocvSim, valueLabel, 0.0, 255.0) + constructor(tunableValue: TunableNumber, valueLabel: JLabel) : this(tunableValue, valueLabel, 0.0, 255.0) - constructor(i: Int, tunableField: TunableField, eocvSim: EOCVSim) : this(i, tunableField, eocvSim, null, 0.0, 255.0) + constructor(tunableValue: TunableNumber) : this(tunableValue, null, 0.0, 255.0) init { addChangeListener { - eocvSim.onMainUpdate.once { - if(!tunableField.shouldIgnoreGuiUpdates() && inControl) { - tunableField.setFieldValueFromGui(index, scaledValue.toString()) + onMainUpdate.once { + if(inControl) { + tunableValue.setFromGui(scaledValue) - if (eocvSim.pipelineManager.paused) - eocvSim.pipelineManager.setPaused(false) + if (pipelineManager.paused) + pipelineManager.setPaused(false) } } - valueLabel?.text = if (tunableField.allowMode == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL) { + + valueLabel?.text = if (!tunableValue.isOnlyNumbers) { scaledValue.toString() } else { scaledValue.roundToInt().toString() } } - tunableField.onValueChange { + tunableValue.onValueChange { if (!inControl) { - scaledValue = try { - tunableField.getGuiFieldValue(index).toString().toDouble() - } catch(_: NumberFormatException) { 0.0 } + scaledValue = tunableValue.value } } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableTextField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableTextField.java deleted file mode 100644 index 5b5f9703..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableTextField.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.tuner.element; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.TunableField; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.LineBorder; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.AbstractDocument; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.DocumentFilter; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.util.ArrayList; -import java.util.Collections; - -public class TunableTextField extends JTextField { - - private final ArrayList validCharsIfNumber = new ArrayList<>(); - - private final TunableField tunableField; - private final int index; - private final EOCVSim eocvSim; - - private final Border initialBorder; - - private volatile boolean hasValidText = true; - - private boolean inControl = false; - - public TunableTextField(int index, TunableField tunableField, EOCVSim eocvSim) { - super(); - - this.initialBorder = this.getBorder(); - - this.tunableField = tunableField; - this.index = index; - this.eocvSim = eocvSim; - - setText(tunableField.getGuiFieldValue(index).toString()); - - int plusW = Math.round(getText().length() / 5f) * 10; - this.setPreferredSize(new Dimension(40 + plusW, getPreferredSize().height)); - - tunableField.onValueChange.attach(() -> { - if(!inControl) { - setText(tunableField.getGuiFieldValue(index).toString()); - } - }); - - if (tunableField.isOnlyNumbers()) { - - //add all valid characters for non decimal numeric fields - Collections.addAll(validCharsIfNumber, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'); - - //allow dots for decimal numeric fields - if (tunableField.getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL) { - validCharsIfNumber.add('.'); - } - - ((AbstractDocument) getDocument()).setDocumentFilter(new DocumentFilter() { - - @Override - public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { - text = text.replace(" ", ""); - - for (char c : text.toCharArray()) { - if (!isNumberCharacter(c)) return; - } - - boolean invalidNumber = false; - - try { //check if entered text is valid number - Double.valueOf(text); - } catch (NumberFormatException ex) { - invalidNumber = true; - } - - hasValidText = !invalidNumber || !text.isEmpty(); - - if (hasValidText) { - setNormalBorder(); - } else { - setRedBorder(); - } - - super.replace(fb, offset, length, text, attrs); - } - - }); - - } - - getDocument().addDocumentListener(new DocumentListener() { - - Runnable changeFieldValue = () -> { - if(tunableField.shouldIgnoreGuiUpdates()) return; - - if ((!hasValidText || !tunableField.isOnlyNumbers() || !getText().trim().equals(""))) { - try { - tunableField.setFieldValueFromGui(index, getText()); - } catch (Exception e) { - setRedBorder(); - } - } else { - setRedBorder(); - } - }; - - @Override - public void insertUpdate(DocumentEvent e) { - change(); - } - @Override - public void removeUpdate(DocumentEvent e) { change(); } - @Override - public void changedUpdate(DocumentEvent e) { change(); } - - public void change() { - eocvSim.onMainUpdate.once(changeFieldValue); - } - }); - - //unpausing when typing on any tunable text box - addKeyListener(new KeyListener() { - @Override - public void keyTyped(KeyEvent e) { - execute(); - } - @Override - public void keyPressed(KeyEvent e) { - execute(); - } - @Override - public void keyReleased(KeyEvent e) { execute(); } - - public void execute() { - if (eocvSim.pipelineManager.getPaused()) { - eocvSim.pipelineManager.requestSetPaused(false); - } - } - }); - } - - public void setNormalBorder() { - setBorder(initialBorder); - } - - public void setRedBorder() { - setBorder(new LineBorder(new Color(255, 79, 79), 2)); - } - - public void setInControl(boolean inControl) { - this.inControl = inControl; - } - - private boolean isNumberCharacter(char c) { - for (char validC : validCharsIfNumber) { - if (c == validC) return true; - } - return false; - } - - public boolean isInControl() { return inControl; } - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableTextField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableTextField.kt new file mode 100644 index 00000000..fc7c10ef --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableTextField.kt @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.gui.component.tuner.element + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.tuner.TunableNumber +import com.github.serivesmejia.eocvsim.tuner.TunableString +import com.github.serivesmejia.eocvsim.tuner.TunableValue +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.koin.core.qualifier.named +import javax.swing.JTextField +import javax.swing.border.Border +import javax.swing.border.LineBorder +import javax.swing.event.DocumentEvent +import javax.swing.event.DocumentListener +import javax.swing.text.AbstractDocument +import javax.swing.text.AttributeSet +import javax.swing.text.BadLocationException +import javax.swing.text.DocumentFilter +import java.awt.Color +import java.awt.Dimension +import java.awt.event.KeyEvent +import java.awt.event.KeyListener + +class TunableTextField(val tunableValue: TunableValue<*>) : JTextField(), KoinComponent { + + private val pipelineManager: PipelineManager by inject() + private val onMainUpdate: EventHandler by inject(named("onMainLoop")) + + + private val validCharsIfNumber = mutableListOf() + private val initialBorder: Border = this.border + @Volatile private var hasValidText = true + var isInControl = false + + init { + text = tunableValue.value.toString() + + val plusW = (text.length / 5f).toInt() * 10 + preferredSize = Dimension(40 + plusW, preferredSize.height) + + tunableValue.onValueChange.attach { + if (!isInControl) { + text = tunableValue.value.toString() + } + } + + val isNumber = tunableValue is TunableNumber + + if (isNumber) { + val numValue = tunableValue + + //add all valid characters for non decimal numeric fields + validCharsIfNumber.addAll(listOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-')) + + //allow dots for decimal numeric fields + if (!numValue.isOnlyNumbers) { + validCharsIfNumber.add('.') + } + + (document as AbstractDocument).documentFilter = object : DocumentFilter() { + @Throws(BadLocationException::class) + override fun replace( + fb: FilterBypass, + offset: Int, + length: Int, + text: String, + attrs: AttributeSet? + ) { + val filteredText = text.replace(" ".toRegex(), "") + + for (c in filteredText.toCharArray()) { + if (!isNumberCharacter(c)) return + } + + var invalidNumber = false + + try { //check if entered text is valid number + filteredText.toDouble() + } catch (ex: NumberFormatException) { + invalidNumber = true + } + + hasValidText = !invalidNumber || filteredText.isNotEmpty() + + if (hasValidText) { + setNormalBorder() + } else { + setRedBorder() + } + + super.replace(fb, offset, length, filteredText, attrs) + } + } + } + + document.addDocumentListener(object : DocumentListener { + val changeFieldValue = Runnable { + val currentText = text + + if (!hasValidText || !isNumber || (currentText != null && currentText.trim().isNotEmpty())) { + try { + if (isNumber) { + tunableValue.setFromGui(currentText.toDouble()) + } else if (tunableValue is TunableString) { + tunableValue.setFromGui(currentText) + } + } catch (e: Exception) { + setRedBorder() + } + } else { + setRedBorder() + } + } + + override fun insertUpdate(e: DocumentEvent) = change() + override fun removeUpdate(e: DocumentEvent) = change() + override fun changedUpdate(e: DocumentEvent) = change() + + private fun change() { + onMainUpdate.once(changeFieldValue) + + } + }) + + //unpausing when typing on any tunable text box + addKeyListener(object : KeyListener { + override fun keyTyped(e: KeyEvent) = execute() + override fun keyPressed(e: KeyEvent) = execute() + override fun keyReleased(e: KeyEvent) = execute() + + private fun execute() { + if (pipelineManager.paused) { + pipelineManager.requestSetPaused(false) + } + + } + }) + } + + fun setNormalBorder() { + border = initialBorder + } + + fun setRedBorder() { + border = LineBorder(Color(255, 79, 79), 2) + } + + private fun isNumberCharacter(c: Char): Boolean { + return validCharsIfNumber.contains(c) + } + +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/CreateSourcePanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/CreateSourcePanel.kt index 68a379e4..aa5ebbaa 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/CreateSourcePanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/CreateSourcePanel.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.visualizer import com.github.serivesmejia.eocvsim.EOCVSim @@ -28,12 +10,16 @@ import com.github.serivesmejia.eocvsim.gui.DialogFactory import com.github.serivesmejia.eocvsim.gui.component.PopupX import com.github.serivesmejia.eocvsim.gui.component.input.EnumComboBox import com.github.serivesmejia.eocvsim.input.SourceType +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.awt.FlowLayout import java.awt.GridLayout import javax.swing.JButton import javax.swing.JPanel -class CreateSourcePanel(eocvSim: EOCVSim) : JPanel(GridLayout(2, 1)) { +class CreateSourcePanel(eocvSim: EOCVSim) : JPanel(GridLayout(2, 1)), KoinComponent { + + private val dialogFactory: DialogFactory by inject() private val sourceSelectComboBox = EnumComboBox( "", SourceType::class.java, SourceType.values(), @@ -54,7 +40,7 @@ class CreateSourcePanel(eocvSim: EOCVSim) : JPanel(GridLayout(2, 1)) { nextButton.addActionListener { //creates "create source" dialog from selected enum - DialogFactory.createSourceDialog(eocvSim, sourceSelectComboBox.selectedEnum!!) + dialogFactory.createSourceDialog(sourceSelectComboBox.selectedEnum!!) popup?.hide() } nextPanel.add(nextButton) @@ -62,4 +48,4 @@ class CreateSourcePanel(eocvSim: EOCVSim) : JPanel(GridLayout(2, 1)) { add(nextPanel) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/InputSourceDropTarget.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/InputSourceDropTarget.kt index c8c541ff..55678040 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/InputSourceDropTarget.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/InputSourceDropTarget.kt @@ -1,42 +1,28 @@ -/* - * Copyright (c) 2024 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2024 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.visualizer import com.github.serivesmejia.eocvsim.EOCVSim import com.github.serivesmejia.eocvsim.gui.DialogFactory import com.github.serivesmejia.eocvsim.input.SourceType -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.awt.datatransfer.DataFlavor import java.awt.dnd.DnDConstants import java.awt.dnd.DropTarget import java.awt.dnd.DropTargetDropEvent import java.io.File -class InputSourceDropTarget(val eocvSim: EOCVSim) : DropTarget() { +class InputSourceDropTarget : DropTarget(), KoinComponent { val logger by loggerForThis() + private val dialogFactory: DialogFactory by inject() + @Suppress("UNCHECKED_CAST") override fun drop(evt: DropTargetDropEvent) { try { @@ -49,7 +35,7 @@ class InputSourceDropTarget(val eocvSim: EOCVSim) : DropTarget() { val sourceType = SourceType.isFileUsableForSource(file) if(sourceType != SourceType.UNKNOWN) { - DialogFactory.createSourceDialog(eocvSim, sourceType, file) + dialogFactory.createSourceDialog(sourceType, file) break } } @@ -60,4 +46,4 @@ class InputSourceDropTarget(val eocvSim: EOCVSim) : DropTarget() { } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/SidebarPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/SidebarPanel.kt index 916d1655..19e06ffd 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/SidebarPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/SidebarPanel.kt @@ -1,39 +1,23 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.gui.component.visualizer -import com.github.serivesmejia.eocvsim.EOCVSim + import com.github.serivesmejia.eocvsim.util.event.EventHandler -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import java.awt.Font import java.awt.LayoutManager import javax.swing.JPanel import javax.swing.JTabbedPane -class SidebarPanel(val eocvSim: EOCVSim) : JTabbedPane() { +import org.koin.core.component.KoinComponent + +class SidebarPanel : JTabbedPane(), KoinComponent { - private var previousActiveIndex = -1; + private var previousActiveIndex = -1 val onTabChange = EventHandler("SidebarPanel-OnTabChange") @@ -57,9 +41,12 @@ class SidebarPanel(val eocvSim: EOCVSim) : JTabbedPane() { addChangeListener { val index = this.selectedIndex + val currentIndexValid = index in 0 until componentCount + val previousIndexValid = previousActiveIndex in 0 until componentCount // deactivate previous pane first - val previousPane = if (previousActiveIndex != -1) this.getComponentAt(previousActiveIndex) else null + val previousPane = if (previousIndexValid) this.getComponentAt(previousActiveIndex) else null + if (previousPane is TabJPanel && previousActiveIndex != index) { previousPane.isActive = false @@ -68,7 +55,7 @@ class SidebarPanel(val eocvSim: EOCVSim) : JTabbedPane() { } // activate current pane - val currentPane = this.getComponentAt(index) + val currentPane = if (currentIndexValid) this.getComponentAt(index) else null if (currentPane is TabJPanel && previousActiveIndex != index) { currentPane.isActive = true @@ -76,7 +63,7 @@ class SidebarPanel(val eocvSim: EOCVSim) : JTabbedPane() { logger.info("Activating $name") } - previousActiveIndex = index + previousActiveIndex = if (currentIndexValid) index else -1 onTabChange.run() } @@ -103,4 +90,4 @@ class SidebarPanel(val eocvSim: EOCVSim) : JTabbedPane() { abstract fun onActivated() abstract fun onDeactivated() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TelemetryPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TelemetryPanel.kt index 9011ce03..32c90aa3 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TelemetryPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TelemetryPanel.kt @@ -1,24 +1,6 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.gui.component.visualizer @@ -38,7 +20,13 @@ import javax.swing.* import javax.swing.border.EmptyBorder import javax.swing.border.TitledBorder -class TelemetryPanel(pipelineManager: PipelineManager? = null) : JPanel(), TelemetryTransmissionReceiver { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class TelemetryPanel : JPanel(), TelemetryTransmissionReceiver, KoinComponent { + + private val pipelineManager: PipelineManager by inject() + val telemetryScroll = JScrollPane() val telemetryList = JList() @@ -95,14 +83,13 @@ class TelemetryPanel(pipelineManager: PipelineManager? = null) : JPanel(), Telem ipady = 20 }) - if(pipelineManager != null) { - pipelineManager.onPipelineChange { // update telemetry receiver on pipeline change - val telemetry = pipelineManager.currentTelemetry - if (telemetry is EOCVSimTelemetryImpl) { - telemetry.addTransmissionReceiver(this@TelemetryPanel) - } + pipelineManager.onPipelineChange { // update telemetry receiver on pipeline change + val telemetry = pipelineManager.currentTelemetry + if (telemetry is EOCVSimTelemetryImpl) { + telemetry.addTransmissionReceiver(this@TelemetryPanel) } } + } fun revalAndRepaint() { @@ -139,7 +126,7 @@ class TelemetryPanel(pipelineManager: PipelineManager? = null) : JPanel(), Telem private var lastTelemetry = ""; - override fun onTelemetryTransmission(text: String, srcTelemetry: Telemetry) { + override fun consumeTelemetry(text: String, srcTelemetry: Telemetry) { SwingUtilities.invokeLater { if(lastTelemetry != text) { updateTelemetry(text, srcTelemetry.captionValueSeparator, srcTelemetry.itemSeparator) @@ -149,3 +136,4 @@ class TelemetryPanel(pipelineManager: PipelineManager? = null) : JPanel(), Telem } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TopMenuBar.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TopMenuBar.kt index b49e8d5b..29249d80 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TopMenuBar.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TopMenuBar.kt @@ -1,36 +1,19 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.gui.component.visualizer -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.LifecycleSignal import com.github.serivesmejia.eocvsim.gui.DialogFactory import com.github.serivesmejia.eocvsim.gui.Visualizer import com.github.serivesmejia.eocvsim.gui.dialog.Output -import com.github.serivesmejia.eocvsim.gui.dialog.PluginOutput import com.github.serivesmejia.eocvsim.gui.util.GuiUtil import com.github.serivesmejia.eocvsim.input.SourceType -import com.github.serivesmejia.eocvsim.pipeline.compiler.CompiledPipelineManager +import com.github.serivesmejia.eocvsim.pipeline.compiled.CompiledPipelineManager +import com.github.serivesmejia.eocvsim.plugin.output.PluginDialogSignal +import com.github.serivesmejia.eocvsim.plugin.output.PluginOutputHandler import com.github.serivesmejia.eocvsim.util.FileFilters import com.github.serivesmejia.eocvsim.util.exception.handling.CrashReport import com.github.serivesmejia.eocvsim.workspace.util.VSCodeLauncher @@ -44,7 +27,27 @@ import javax.swing.JMenu import javax.swing.JMenuBar import javax.swing.JMenuItem -class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { +import org.deltacv.eocvsim.plugin.loader.PluginManager +import com.github.serivesmejia.eocvsim.workspace.WorkspaceManager +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.koin.core.qualifier.named +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class TopMenuBar : JMenuBar(), KoinComponent { + + val visualizer: Visualizer by inject() + val dialogFactory: DialogFactory by inject() + val pluginManager: org.deltacv.eocvsim.plugin.loader.PluginManager by inject() + val outputHandler: PluginOutputHandler by inject() + val workspaceManager: WorkspaceManager by inject() + val pipelineManager: PipelineManager by inject() + val onMainUpdate: EventHandler by inject(named("onMainLoop")) + val lifecycleChannel: Channel by inject(named("lifecycle")) + val scope: CoroutineScope by inject() companion object { val docsUrl = URI("https://docs.deltacv.org/eocv-sim/") @@ -72,7 +75,7 @@ class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { val fileNewInputSourceItem = JMenuItem(type.coolName) fileNewInputSourceItem.addActionListener { - DialogFactory.createSourceDialog(eocvSim, type) + dialogFactory.createSourceDialog(type) } fileNewInputSourceSubmenu.add(fileNewInputSourceItem) @@ -88,7 +91,7 @@ class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { GuiUtil.saveMatFileChooser( visualizer.frame, mat, - eocvSim + dialogFactory ) mat.release() @@ -98,15 +101,17 @@ class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { mFileMenu.addSeparator() if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) { - desktop.setPreferencesHandler { DialogFactory.createConfigDialog(eocvSim) } + desktop.setPreferencesHandler { dialogFactory.createConfigDialog() } } else { val editSettings = JMenuItem("Settings") - editSettings.addActionListener { DialogFactory.createConfigDialog(eocvSim) } + editSettings.addActionListener { dialogFactory.createConfigDialog() } mFileMenu.add(editSettings) } val filePlugins = JMenuItem("Manage Plugins") - filePlugins.addActionListener { eocvSim.pluginManager.appender.append(PluginOutput.SPECIAL_OPEN_MGR)} + filePlugins.addActionListener { + outputHandler.sendDialogSignal(PluginDialogSignal.ShowPlugins) + } mFileMenu.add(filePlugins) @@ -114,7 +119,8 @@ class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { val fileRestart = JMenuItem("Restart") - fileRestart.addActionListener { eocvSim.onMainUpdate.once { eocvSim.restart() } } + fileRestart.addActionListener { onMainUpdate.once { lifecycleChannel.trySend(LifecycleSignal.Restart) } } + mFileMenu.add(fileRestart) add(mFileMenu) @@ -123,28 +129,30 @@ class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { val workspSetWorkspace = JMenuItem("Select Workspace") - workspSetWorkspace.addActionListener { DialogFactory.createWorkspace(visualizer) } + workspSetWorkspace.addActionListener { dialogFactory.createWorkspace() } mWorkspMenu.add(workspSetWorkspace) val workspClose = JMenuItem("Close Current Workspace") workspClose.addActionListener { - eocvSim.onMainUpdate.once { - eocvSim.workspaceManager.workspaceFile = CompiledPipelineManager.DEF_WORKSPACE_FOLDER + onMainUpdate.once { + workspaceManager.workspaceFile = CompiledPipelineManager.DEF_WORKSPACE_FOLDER } } + mWorkspMenu.add(workspClose) mWorkspMenu.addSeparator() - workspCompile.addActionListener { visualizer.asyncCompilePipelines() } + workspCompile.addActionListener { pipelineManager.compiledPipelineManager.asyncBuild() } + mWorkspMenu.add(workspCompile) val workspBuildOutput = JMenuItem("Output") workspBuildOutput.addActionListener { if(!Output.isAlreadyOpened) - DialogFactory.createOutput(eocvSim, true) + dialogFactory.createOutput(true) } mWorkspMenu.add(workspBuildOutput) @@ -162,7 +170,8 @@ class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { val workspVSCodeOpen = JMenuItem("Open VS Code Here") workspVSCodeOpen.addActionListener { - VSCodeLauncher.asyncLaunch(eocvSim.workspaceManager.workspaceFile) + VSCodeLauncher.asyncLaunch(workspaceManager.workspaceFile, scope) + } workspVSCode.add(workspVSCodeOpen) @@ -192,12 +201,12 @@ class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { crashReport = CrashReport(e, isDummy = true) } - DialogFactory.createFileChooser(visualizer.frame, + dialogFactory.createFileChooser(visualizer.frame, DialogFactory.FileChooser.Mode.SAVE_FILE_SELECT, CrashReport.defaultCrashFileName, FileFilters.logFileFilter ).addCloseListener { OPTION, selectedFile, _ -> if(OPTION == JFileChooser.APPROVE_OPTION) { - var path = selectedFile.absolutePath + var path = selectedFile?.absolutePath ?: return@addCloseListener if (path.endsWith(File.separator)) path = path.removeSuffix(File.separator) @@ -215,16 +224,16 @@ class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { mHelpMenu.addSeparator() val helpIAmA = JMenuItem("I am a...") - helpIAmA.addActionListener { DialogFactory.createIAmA(eocvSim.visualizer) } + helpIAmA.addActionListener { dialogFactory.createIAmA() } mHelpMenu.add(helpIAmA) if (desktop.isSupported(Desktop.Action.APP_ABOUT)) { - desktop.setAboutHandler { DialogFactory.createAboutDialog(eocvSim) } + desktop.setAboutHandler { dialogFactory.createAboutDialog() } } else { val helpAbout = JMenuItem("About") - helpAbout.addActionListener { DialogFactory.createAboutDialog(eocvSim) } + helpAbout.addActionListener { dialogFactory.createAboutDialog() } mHelpMenu.add(helpAbout) } @@ -232,3 +241,4 @@ class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModeControlsPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModeControlsPanel.kt index 3387877d..df48e8ba 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModeControlsPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModeControlsPanel.kt @@ -1,40 +1,28 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.visualizer.opmode -import com.github.serivesmejia.eocvsim.EOCVSim import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary import com.github.serivesmejia.eocvsim.pipeline.PipelineManager import com.qualcomm.robotcore.eventloop.opmode.OpMode -import io.github.deltacv.vision.internal.opmode.OpModeNotification -import io.github.deltacv.vision.internal.opmode.OpModeState +import org.deltacv.vision.internal.opmode.OpModeNotification +import org.deltacv.vision.internal.opmode.OpModeState import java.awt.BorderLayout import javax.swing.JPanel import javax.swing.JButton import javax.swing.SwingUtilities -class OpModeControlsPanel(val eocvSim: EOCVSim) : JPanel() { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class OpModeControlsPanel : JPanel(), KoinComponent { + + private val pipelineManager: PipelineManager by inject() + + val controlButton = JButton() @@ -55,12 +43,12 @@ class OpModeControlsPanel(val eocvSim: EOCVSim) : JPanel() { controlButton.icon = EOCVSimIconLibrary.icoFlag controlButton.addActionListener { - eocvSim.pipelineManager.onUpdate.once { - if(eocvSim.pipelineManager.currentPipeline !is OpMode) return@once + pipelineManager.onUpdate.once { + if(pipelineManager.currentPipeline !is OpMode) return@once - eocvSim.pipelineManager.setPaused(false, PipelineManager.PauseReason.NOT_PAUSED) + pipelineManager.setPaused(false, PipelineManager.PauseReason.NOT_PAUSED) - val opMode = eocvSim.pipelineManager.currentPipeline as OpMode + val opMode = pipelineManager.currentPipeline as OpMode val state = opMode.notifier.state opMode.notifier.notify(when(state) { @@ -71,18 +59,21 @@ class OpModeControlsPanel(val eocvSim: EOCVSim) : JPanel() { }) } } + } fun stopCurrentOpMode() { - if(eocvSim.pipelineManager.currentPipeline != currentOpMode || currentOpMode == null) return + if(pipelineManager.currentPipeline != currentOpMode || currentOpMode == null) return + currentOpMode!!.notifier.notify(OpModeNotification.STOP) } private fun notifySelected() { if(!isActive) return - if(eocvSim.pipelineManager.currentPipeline !is OpMode) return - val opMode = eocvSim.pipelineManager.currentPipeline as OpMode + if(pipelineManager.currentPipeline !is OpMode) return + val opMode = pipelineManager.currentPipeline as OpMode + val opModeIndex = currentManagerIndex!! opMode.notifier.onStateChange { @@ -123,20 +114,21 @@ class OpModeControlsPanel(val eocvSim: EOCVSim) : JPanel() { } fun opModeSelected(managerIndex: Int, forceChangePipeline: Boolean = true) { - eocvSim.pipelineManager.requestSetPaused(false) + pipelineManager.requestSetPaused(false) if(forceChangePipeline) { - eocvSim.pipelineManager.requestForceChangePipeline(managerIndex) + pipelineManager.requestForceChangePipeline(managerIndex) } upcomingIndex = managerIndex - eocvSim.pipelineManager.onUpdate.once { + pipelineManager.onUpdate.once { currentManagerIndex = managerIndex notifySelected() } } + fun reset() { controlButton.isEnabled = false controlButton.icon = EOCVSimIconLibrary.icoFlag @@ -144,4 +136,4 @@ class OpModeControlsPanel(val eocvSim: EOCVSim) : JPanel() { currentOpMode?.requestOpModeStop() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModePopupPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModePopupPanel.kt index f3bea7cb..2f525eae 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModePopupPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModePopupPanel.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.visualizer.opmode import javax.swing.JList @@ -41,4 +23,4 @@ class OpModePopupPanel(autonomousSelector: JList<*>) : JPanel() { autonomousSelector.selectionModel.clearSelection() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModeSelectorPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModeSelectorPanel.kt index 79807d2f..5b85e77b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModeSelectorPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/OpModeSelectorPanel.kt @@ -1,49 +1,38 @@ /* * Copyright (c) 2023 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.gui.component.visualizer.opmode -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary import com.github.serivesmejia.eocvsim.gui.component.PopupX.Companion.popUpXOnThis import com.github.serivesmejia.eocvsim.gui.util.Corner import com.github.serivesmejia.eocvsim.gui.util.icon.PipelineListIconRenderer import com.github.serivesmejia.eocvsim.pipeline.PipelineData import com.github.serivesmejia.eocvsim.util.ReflectUtil -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import com.qualcomm.robotcore.eventloop.opmode.* import com.qualcomm.robotcore.util.Range -import io.github.deltacv.vision.internal.opmode.OpModeState +import org.deltacv.vision.internal.opmode.OpModeState import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.* +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.awt.GridBagLayout +import java.awt.event.MouseAdapter + +class OpModeSelectorPanel(val opModeControlsPanel: OpModeControlsPanel) : JPanel(), KoinComponent { + + private val pipelineManager: PipelineManager by inject() + -class OpModeSelectorPanel(val eocvSim: EOCVSim, val opModeControlsPanel: OpModeControlsPanel) : JPanel() { private var _selectedIndex = -1 + private val logger by loggerForThis() var selectedIndex: Int @@ -93,8 +82,9 @@ class OpModeSelectorPanel(val eocvSim: EOCVSim, val opModeControlsPanel: OpModeC autonomousSelector.selectionMode = ListSelectionModel.SINGLE_SELECTION teleopSelector.selectionMode = ListSelectionModel.SINGLE_SELECTION - autonomousSelector.cellRenderer = PipelineListIconRenderer(eocvSim.pipelineManager) { autonomousIndexMap } - teleopSelector.cellRenderer = PipelineListIconRenderer(eocvSim.pipelineManager) { teleopIndexMap } + autonomousSelector.cellRenderer = PipelineListIconRenderer(pipelineManager) { autonomousIndexMap } + teleopSelector.cellRenderer = PipelineListIconRenderer(pipelineManager) { teleopIndexMap } + autonomousButton.icon = EOCVSimIconLibrary.icoArrowDropdown @@ -216,7 +206,7 @@ class OpModeSelectorPanel(val eocvSim: EOCVSim, val opModeControlsPanel: OpModeC } }) - eocvSim.pipelineManager.onPipelineChange { + pipelineManager.onPipelineChange { if (!isActive) return@onPipelineChange // we are doing this to detect external pipeline changes and reflect them @@ -226,9 +216,9 @@ class OpModeSelectorPanel(val eocvSim: EOCVSim, val opModeControlsPanel: OpModeC // we need to hold on a cycle so that the state has been fully updated, // just to be able to check correctly and, if it was requested by // OpModeSelectorPanel, skip this message and not do anything. - eocvSim.pipelineManager.onUpdate.once { - if (isActive && opModeControlsPanel.currentOpMode != eocvSim.pipelineManager.currentPipeline && eocvSim.pipelineManager.currentPipeline != null) { - val opMode = eocvSim.pipelineManager.currentPipeline + pipelineManager.onUpdate.once { + if (isActive && opModeControlsPanel.currentOpMode != pipelineManager.currentPipeline && pipelineManager.currentPipeline != null) { + val opMode = pipelineManager.currentPipeline if (opMode is OpMode) { val name = if (opMode.opModeType == OpModeType.AUTONOMOUS) @@ -237,7 +227,7 @@ class OpModeSelectorPanel(val eocvSim: EOCVSim, val opModeControlsPanel: OpModeC logger.info("External change detected \"$name\"") - opModeSelected(eocvSim.pipelineManager.currentPipelineIndex, name, false) + opModeSelected(pipelineManager.currentPipelineIndex, name, false) } else if (isActive) { reset(-1) } @@ -245,16 +235,17 @@ class OpModeSelectorPanel(val eocvSim: EOCVSim, val opModeControlsPanel: OpModeC } } - eocvSim.pipelineManager.onPipelineListRefresh { + pipelineManager.onPipelineListRefresh { updateOpModesList() } - eocvSim.pipelineManager.onExternalSwitchingEnable { + pipelineManager.onExternalSwitchingEnable { allowOpModeSwitching = true } - eocvSim.pipelineManager.onExternalSwitchingDisable { + pipelineManager.onExternalSwitchingDisable { allowOpModeSwitching = false } + } private fun teleOpSelected(index: Int) { @@ -298,7 +289,8 @@ class OpModeSelectorPanel(val eocvSim: EOCVSim, val opModeControlsPanel: OpModeC val autonomousListModel = DefaultListModel() val teleopListModel = DefaultListModel() - pipelinesData = eocvSim.pipelineManager.pipelines.toArray(arrayOf()) + pipelinesData = pipelineManager.pipelines.toArray(arrayOf()) + var autonomousSelectorIndex = Range.clip(autonomousListModel.size() - 1, 0, Int.MAX_VALUE) var teleopSelectorIndex = Range.clip(teleopListModel.size() - 1, 0, Int.MAX_VALUE) @@ -306,7 +298,8 @@ class OpModeSelectorPanel(val eocvSim: EOCVSim, val opModeControlsPanel: OpModeC autonomousIndexMap.clear() teleopIndexMap.clear() - for ((managerIndex, pipeline) in eocvSim.pipelineManager.pipelines.withIndex()) { + for ((managerIndex, pipeline) in pipelineManager.pipelines.withIndex()) { + if (ReflectUtil.hasSuperclass(pipeline.clazz, OpMode::class.java)) { val type = pipeline.clazz.opModeType @@ -345,30 +338,29 @@ class OpModeSelectorPanel(val eocvSim: EOCVSim, val opModeControlsPanel: OpModeC val opMode = opModeControlsPanel.currentOpMode - if (eocvSim.pipelineManager.currentPipeline == opMode && opMode != null && opMode.notifier.state != OpModeState.SELECTED) { - opMode.notifier?.onStateChange?.let { it -> - it { - val state = opMode.notifier.state + if (pipelineManager.currentPipeline == opMode && opMode != null && opMode.notifier.state != OpModeState.SELECTED) { + opMode.notifier?.onStateChange?.attach { + val state = opMode.notifier.state - if (state == OpModeState.STOPPED) { - removeListener() + if (state == OpModeState.STOPPED) { + removeListener() - if (nextPipeline == null || nextPipeline >= 0) { - eocvSim.pipelineManager.onUpdate.once { - eocvSim.pipelineManager.changePipeline(nextPipeline) - } + if (nextPipeline == null || nextPipeline >= 0) { + pipelineManager.onUpdate.once { + pipelineManager.changePipeline(nextPipeline) } } } } } else if (nextPipeline == null || nextPipeline >= 0) { - eocvSim.pipelineManager.onUpdate.once { - eocvSim.pipelineManager.requestChangePipeline(nextPipeline) + pipelineManager.onUpdate.once { + pipelineManager.requestChangePipeline(nextPipeline) } } + _selectedIndex = -1 opModeControlsPanel.stopCurrentOpMode() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/SidebarOpModeTabPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/SidebarOpModeTabPanel.kt index 7a210f5e..05c73e5b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/SidebarOpModeTabPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/opmode/SidebarOpModeTabPanel.kt @@ -1,29 +1,10 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.visualizer.opmode -import com.github.serivesmejia.eocvsim.EOCVSim import com.github.serivesmejia.eocvsim.gui.component.visualizer.SidebarPanel import com.github.serivesmejia.eocvsim.gui.component.visualizer.TelemetryPanel import java.awt.Font @@ -32,12 +13,17 @@ import javax.swing.BoxLayout import javax.swing.JPanel import javax.swing.border.EmptyBorder -class SidebarOpModeTabPanel(eocvSim: EOCVSim) : SidebarPanel.TabJPanel() { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class SidebarOpModeTabPanel : SidebarPanel.TabJPanel(), KoinComponent { + + val opModeControlsPanel = OpModeControlsPanel() + val opModeSelectorPanel = OpModeSelectorPanel(opModeControlsPanel) + - val opModeControlsPanel = OpModeControlsPanel(eocvSim) - val opModeSelectorPanel = OpModeSelectorPanel(eocvSim, opModeControlsPanel) + val telemetryPanel = TelemetryPanel() - val telemetryPanel = TelemetryPanel(eocvSim.pipelineManager) init { font = font.deriveFont(Font.PLAIN, 14f) @@ -64,4 +50,4 @@ class SidebarOpModeTabPanel(eocvSim: EOCVSim) : SidebarPanel.TabJPanel() { opModeSelectorPanel.isActive = false opModeSelectorPanel.reset(-1) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorButtonsPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorButtonsPanel.kt index cf5d9bbf..861980d3 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorButtonsPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorButtonsPanel.kt @@ -1,30 +1,16 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.output.RecordingManager +import com.github.serivesmejia.eocvsim.util.event.EventHandler import com.github.serivesmejia.eocvsim.gui.DialogFactory +import org.koin.core.qualifier.named + import com.github.serivesmejia.eocvsim.gui.component.PopupX import java.awt.GridBagConstraints import java.awt.GridBagLayout @@ -35,7 +21,17 @@ import javax.swing.JPanel import javax.swing.JToggleButton import javax.swing.SwingUtilities -class PipelineSelectorButtonsPanel(eocvSim: EOCVSim) : JPanel(GridBagLayout()) { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class PipelineSelectorButtonsPanel : JPanel(GridBagLayout()), KoinComponent { + + private val pipelineManager: PipelineManager by inject() + private val recordingManager: RecordingManager by inject() + private val onMainUpdate: EventHandler by inject(named("onMainLoop")) + + private val dialogFactory: DialogFactory by inject() + val pipelinePauseBtt = JToggleButton("Pause") val pipelineRecordBtt = JToggleButton("Record") @@ -50,8 +46,9 @@ class PipelineSelectorButtonsPanel(eocvSim: EOCVSim) : JPanel(GridBagLayout()) { init { //listener for changing pause state pipelinePauseBtt.addActionListener { - eocvSim.onMainUpdate.once { eocvSim.pipelineManager.setPaused(pipelinePauseBtt.isSelected) } + onMainUpdate.once { pipelineManager.setPaused(pipelinePauseBtt.isSelected) } } + pipelinePauseBtt.addChangeListener { pipelinePauseBtt.text = if(pipelinePauseBtt.isSelected) "Resume" else "Pause" } @@ -61,14 +58,15 @@ class PipelineSelectorButtonsPanel(eocvSim: EOCVSim) : JPanel(GridBagLayout()) { }) pipelineRecordBtt.addActionListener { - eocvSim.onMainUpdate.once { + onMainUpdate.once { if (pipelineRecordBtt.isSelected) { - if (!eocvSim.isCurrentlyRecording()) eocvSim.startRecordingSession() + if (!recordingManager.isCurrentlyRecording()) recordingManager.startRecordingSession() } else { - if (eocvSim.isCurrentlyRecording()) eocvSim.stopRecordingSession() + if (recordingManager.isCurrentlyRecording()) recordingManager.stopRecordingSession() } } } + add(pipelineRecordBtt, GridBagConstraints().apply { gridx = 1 }) pipelineWorkspaceBtt.addActionListener { @@ -103,7 +101,7 @@ class PipelineSelectorButtonsPanel(eocvSim: EOCVSim) : JPanel(GridBagLayout()) { val selectWorkspBtt = JButton("Select Workspace") - selectWorkspBtt.addActionListener { DialogFactory.createWorkspace(eocvSim.visualizer) } + selectWorkspBtt.addActionListener { dialogFactory.createWorkspace() } workspaceButtonsPanel.add(selectWorkspBtt, GridBagConstraints().apply { gridx = 0 gridy = 0 @@ -114,7 +112,8 @@ class PipelineSelectorButtonsPanel(eocvSim: EOCVSim) : JPanel(GridBagLayout()) { gridy = 0 }) - pipelineCompileBtt.addActionListener { eocvSim.visualizer.asyncCompilePipelines() } + pipelineCompileBtt.addActionListener { pipelineManager.compiledPipelineManager.asyncBuild() } + workspaceButtonsPanel.add(pipelineCompileBtt, GridBagConstraints().apply { gridx = 2 gridy = 0 @@ -122,7 +121,7 @@ class PipelineSelectorButtonsPanel(eocvSim: EOCVSim) : JPanel(GridBagLayout()) { val outputBtt = JButton("Pipeline Output") - outputBtt.addActionListener { DialogFactory.createPipelineOutput(eocvSim) } + outputBtt.addActionListener { dialogFactory.createPipelineOutput() } workspaceButtonsPanel.add(outputBtt, GridBagConstraints().apply { gridy = 1 @@ -136,4 +135,4 @@ class PipelineSelectorButtonsPanel(eocvSim: EOCVSim) : JPanel(GridBagLayout()) { }) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorPanel.kt index 3a337755..b0ba2024 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorPanel.kt @@ -1,30 +1,13 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.Visualizer import com.github.serivesmejia.eocvsim.gui.util.icon.PipelineListIconRenderer + import com.github.serivesmejia.eocvsim.pipeline.PipelineData import com.github.serivesmejia.eocvsim.pipeline.PipelineManager import com.github.serivesmejia.eocvsim.util.ReflectUtil @@ -41,7 +24,15 @@ import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.* -class PipelineSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class PipelineSelectorPanel : JPanel(), KoinComponent { + + private val pipelineManager: PipelineManager by inject() + private val visualizer: Visualizer by inject() + + var selectedIndex: Int get() = indexMap[pipelineSelector.selectedIndex] ?: -1 @@ -61,7 +52,8 @@ class PipelineSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { // private val indexMap = mutableMapOf() - val buttonsPanel = PipelineSelectorButtonsPanel(eocvSim) + val buttonsPanel = PipelineSelectorButtonsPanel() + var allowPipelineSwitching = false @@ -77,7 +69,8 @@ class PipelineSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { pipelineSelectorLabel.horizontalAlignment = JLabel.CENTER - pipelineSelector.cellRenderer = PipelineListIconRenderer(eocvSim.pipelineManager) { indexMap } + pipelineSelector.cellRenderer = PipelineListIconRenderer(pipelineManager) { indexMap } + pipelineSelector.selectionMode = ListSelectionModel.SINGLE_SELECTION pipelineSelectorScroll.setViewportView(pipelineSelector) @@ -114,47 +107,49 @@ class PipelineSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { val pipeline = indexMap[index] ?: return if (pipeline != beforeSelectedPipeline) { - if (!eocvSim.pipelineManager.paused) { - eocvSim.pipelineManager.requestChangePipeline(pipeline) + if (!pipelineManager.paused) { + pipelineManager.requestChangePipeline(pipeline) beforeSelectedPipeline = pipeline } else { - if (eocvSim.pipelineManager.pauseReason !== PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS) { + if (pipelineManager.pauseReason !== PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS) { pipelineSelector.setSelectedIndex(beforeSelectedPipeline) } else { //handling pausing - eocvSim.pipelineManager.requestSetPaused(false) - eocvSim.pipelineManager.requestChangePipeline(pipeline) + pipelineManager.requestSetPaused(false) + pipelineManager.requestChangePipeline(pipeline) beforeSelectedPipeline = pipeline } } } + } else { pipelineSelector.setSelectedIndex(0) } } }) - eocvSim.pipelineManager.onPipelineChange { - selectedIndex = eocvSim.pipelineManager.currentPipelineIndex + pipelineManager.onPipelineChange { + selectedIndex = pipelineManager.currentPipelineIndex } - eocvSim.pipelineManager.onPipelineListRefresh { + pipelineManager.onPipelineListRefresh { updatePipelinesList() } - eocvSim.pipelineManager.onExternalSwitchingEnable { + pipelineManager.onExternalSwitchingEnable { allowPipelineSwitching = true } - eocvSim.pipelineManager.onExternalSwitchingDisable { + pipelineManager.onExternalSwitchingDisable { allowPipelineSwitching = false } val pauseListener: EventListener = { - eocvSim.visualizer.pipelineSelectorPanel.buttonsPanel.pipelinePauseBtt.isSelected = - eocvSim.pipelineManager.paused + visualizer.pipelineSelectorPanel.buttonsPanel.pipelinePauseBtt.isSelected = + pipelineManager.paused } - eocvSim.pipelineManager.onPause(pauseListener) - eocvSim.pipelineManager.onResume(pauseListener) + pipelineManager.onPause(pauseListener) + pipelineManager.onResume(pauseListener) + } fun updatePipelinesList() = SwingUtilities.invokeLater { @@ -163,9 +158,10 @@ class PipelineSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { indexMap.clear() - pipelinesData = eocvSim.pipelineManager.pipelines.toArray(arrayOf()) + pipelinesData = pipelineManager.pipelines.toArray(arrayOf()) + + for ((managerIndex, pipeline) in pipelineManager.pipelines.withIndex()) { - for ((managerIndex, pipeline) in eocvSim.pipelineManager.pipelines.withIndex()) { if (!ReflectUtil.hasSuperclass(pipeline.clazz, OpMode::class.java) && !pipeline.hidden) { listModel.addElement(pipeline.clazz.simpleName) indexMap[selectorIndex] = managerIndex @@ -188,3 +184,4 @@ class PipelineSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/SidebarPipelineTabPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/SidebarPipelineTabPanel.kt index ffb5e988..f2766bee 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/SidebarPipelineTabPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/SidebarPipelineTabPanel.kt @@ -1,29 +1,12 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager + import com.github.serivesmejia.eocvsim.gui.component.visualizer.SidebarPanel import com.github.serivesmejia.eocvsim.gui.component.visualizer.TelemetryPanel import java.awt.Font @@ -34,12 +17,20 @@ import javax.swing.JPanel import javax.swing.border.EmptyBorder import javax.swing.border.TitledBorder -class SidebarPipelineTabPanel(private val eocvSim: EOCVSim) : SidebarPanel.TabJPanel() { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class SidebarPipelineTabPanel : SidebarPanel.TabJPanel(), KoinComponent { + + private val pipelineManager: PipelineManager by inject() - val pipelineSelectorPanel = PipelineSelectorPanel(eocvSim) - val sourceSelectorPanel = SourceSelectorPanel(eocvSim) - val telemetryPanel = TelemetryPanel(eocvSim.pipelineManager) + val pipelineSelectorPanel = PipelineSelectorPanel() + val sourceSelectorPanel = SourceSelectorPanel() + + + val telemetryPanel = TelemetryPanel() + init { font = font.deriveFont(Font.PLAIN, 14f) @@ -93,13 +84,14 @@ class SidebarPipelineTabPanel(private val eocvSim: EOCVSim) : SidebarPanel.TabJP override fun onActivated() { pipelineSelectorPanel.isActive = true - eocvSim.pipelineManager.onUpdate.once { - eocvSim.pipelineManager.changePipeline(0) + pipelineManager.onUpdate.once { + pipelineManager.changePipeline(0) } } + override fun onDeactivated() { pipelineSelectorPanel.isActive = false } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/SourceSelectorPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/SourceSelectorPanel.kt index 4f5536db..c8be6a19 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/SourceSelectorPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/SourceSelectorPanel.kt @@ -1,45 +1,31 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline -import com.github.serivesmejia.eocvsim.EOCVSim import com.github.serivesmejia.eocvsim.gui.DialogFactory import com.github.serivesmejia.eocvsim.gui.util.icon.SourcesListIconRenderer +import com.github.serivesmejia.eocvsim.input.InputSourceManager import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.util.event.EventHandler import com.github.serivesmejia.eocvsim.util.extension.clipUpperZero -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.swing.Swing +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named import java.awt.FlowLayout import java.awt.GridBagConstraints import java.awt.GridBagLayout import java.awt.event.MouseAdapter import javax.swing.* -class SourceSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { +class SourceSelectorPanel : JPanel(), KoinComponent { + + private val inputSourceManager: InputSourceManager by inject() + private val pipelineManager: PipelineManager by inject() + private val onMainUpdate: EventHandler by inject(named("onMainLoop")) + private val dialogFactory: DialogFactory by inject() val sourceSelector = JList() val sourceSelectorScroll = JScrollPane() @@ -83,10 +69,11 @@ class SourceSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { }) //different icons - sourceSelector.cellRenderer = SourcesListIconRenderer(eocvSim.inputSourceManager) + sourceSelector.cellRenderer = SourcesListIconRenderer(inputSourceManager) + sourceSelectorCreateBtt.addActionListener { - DialogFactory.createSourceExDialog(eocvSim) + dialogFactory.createSourceExDialog() } sourceSelectorButtonsContainer = JPanel(FlowLayout(FlowLayout.CENTER)) @@ -118,24 +105,26 @@ class SourceSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { //enable or disable source delete button depending if source is default or not sourceSelectorDeleteBtt.isEnabled = - eocvSim.inputSourceManager.sources[source]?.isDefault == false + inputSourceManager.sources[source]?.isDefault == false if (source != beforeSelectedSource) { - if (!eocvSim.pipelineManager.paused) { - eocvSim.inputSourceManager.requestSetInputSource(source) + if (!pipelineManager.paused) { + inputSourceManager.requestSetInputSource(source) beforeSelectedSource = source beforeSelectedSourceIndex = sourceSelector.selectedIndex + } else { //check if the user requested the pause or if it was due to one shoot analysis when selecting images - if (eocvSim.pipelineManager.pauseReason !== PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS) { + if (pipelineManager.pauseReason !== PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS) { sourceSelector.setSelectedIndex(beforeSelectedSourceIndex) } else { //handling pausing - eocvSim.pipelineManager.requestSetPaused(false) - eocvSim.inputSourceManager.requestSetInputSource(source) + pipelineManager.requestSetPaused(false) + inputSourceManager.requestSetInputSource(source) beforeSelectedSource = source beforeSelectedSourceIndex = sourceSelector.selectedIndex } } + } } else { sourceSelector.setSelectedIndex(1) @@ -152,20 +141,53 @@ class SourceSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { val index = sourceSelector.selectedIndex val source = sourceSelector.model.getElementAt(index) - eocvSim.onMainUpdate.once { - eocvSim.inputSourceManager.deleteInputSource(source) + onMainUpdate.once { + inputSourceManager.deleteInputSource(source) updateSourcesList() sourceSelector.selectedIndex = (index - 1).clipUpperZero() } + + } + + inputSourceManager.onInputSourceRemoved { + updateSourcesList() + } + + inputSourceManager.onInputSourceAdded { + val name = inputSourceManager.lastAddedSourceName + val dispatchedByUser = inputSourceManager.wasLastSourceAddedByUser + + updateSourcesList() + + SwingUtilities.invokeLater { + val currentIndex = sourceSelector.selectedIndex + + if (dispatchedByUser) { + val index = getIndexOf(name) + sourceSelector.selectedIndex = index + + inputSourceManager.requestSetInputSource(name) + + onMainUpdate.once { + pipelineManager.requestSetPaused(false) + inputSourceManager.pauseIfImageTwoFrames() + } + } else { + sourceSelector.selectedIndex = currentIndex + } + + allowSourceSwitching = true + } } + } - fun updateSourcesList(): Job { + fun updateSourcesList() { SwingUtilities.invokeLater { val listModel = DefaultListModel() - for (source in eocvSim.inputSourceManager.sortedInputSources) { + inputSourceManager.sortedInputSources.forEach { source -> listModel.addElement(source.name) } @@ -177,12 +199,10 @@ class SourceSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { sourceSelector.selectedIndex = 0 } - - return Job() // we can't break ABI here, so we return a dummy Job } fun getIndexOf(name: String): Int { - for (i in 0..sourceSelector.model.size) { + for (i in 0 until sourceSelector.model.size) { if (sourceSelector.model.getElementAt(i) == name) return i } @@ -191,3 +211,4 @@ class SourceSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/About.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/About.java deleted file mode 100644 index 6f0b1846..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/About.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary; -import com.github.serivesmejia.eocvsim.gui.Icons; -import io.github.deltacv.vision.external.gui.component.ImageX; -import com.github.serivesmejia.eocvsim.gui.util.GuiUtil; -import com.github.serivesmejia.eocvsim.util.StrUtil; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.nio.charset.StandardCharsets; - -public class About { - - public JDialog about = null; - - public static ListModel CONTRIBS_LIST_MODEL; - public static ListModel OSL_LIST_MODEL; - - static { - try { - CONTRIBS_LIST_MODEL = GuiUtil.isToListModel(About.class.getResourceAsStream("/contributors.txt"), StandardCharsets.UTF_8); - OSL_LIST_MODEL = GuiUtil.isToListModel(About.class.getResourceAsStream("/opensourcelibs.txt"), StandardCharsets.UTF_8); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - } - - public About(JFrame parent, EOCVSim eocvSim) { - - about = new JDialog(parent); - - eocvSim.visualizer.childDialogs.add(about); - initAbout(); - - } - - private void initAbout() { - - about.setModal(true); - - about.setTitle("About"); - - JPanel contents = new JPanel(new GridLayout(2, 1)); - contents.setAlignmentX(Component.CENTER_ALIGNMENT); - - ImageX icon = new ImageX(EOCVSimIconLibrary.INSTANCE.getIcoEOCVSim64()); - icon.setSize(50, 50); - icon.setAlignmentX(Component.CENTER_ALIGNMENT); - - icon.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - - JLabel appInfo = new JLabel("EasyOpenCV Simulator v" + EOCVSim.VERSION); - appInfo.setFont(appInfo.getFont().deriveFont(appInfo.getFont().getStyle() | Font.BOLD)); //set font to bold - - JPanel appInfoLogo = new JPanel(new FlowLayout()); - - appInfoLogo.add(icon); - appInfoLogo.add(appInfo); - - appInfoLogo.setBorder(BorderFactory.createEmptyBorder(10, 10, -30, 10)); - - contents.add(appInfoLogo); - - JTabbedPane tabbedPane = new JTabbedPane(); - - JPanel contributors = new JPanel(new FlowLayout(FlowLayout.CENTER)); - - JList contribsList = new JList<>(); - contribsList.setModel(CONTRIBS_LIST_MODEL); - contribsList.setSelectionModel(new GuiUtil.NoSelectionModel()); - contribsList.setLayout(new FlowLayout(FlowLayout.CENTER)); - contribsList.setAlignmentY(Component.TOP_ALIGNMENT); - - contribsList.setVisibleRowCount(4); - - JPanel contributorsList = new JPanel(new FlowLayout(FlowLayout.CENTER)); - contributorsList.setAlignmentY(Component.TOP_ALIGNMENT); - - JScrollPane contribsListScroll = new JScrollPane(); - contribsListScroll.setBorder(new EmptyBorder(0,0,20,10)); - contribsListScroll.setAlignmentX(Component.CENTER_ALIGNMENT); - contribsListScroll.setAlignmentY(Component.TOP_ALIGNMENT); - contribsListScroll.setViewportView(contribsList); - - contributors.setAlignmentY(Component.TOP_ALIGNMENT); - contents.setAlignmentY(Component.TOP_ALIGNMENT); - - contributorsList.add(contribsListScroll); - contributors.add(contributorsList); - - tabbedPane.addTab("Contributors", contributors); - - JPanel osLibs = new JPanel(new FlowLayout(FlowLayout.CENTER)); - - JList osLibsList = new JList<>(); - osLibsList.setModel(OSL_LIST_MODEL); - osLibsList.setLayout(new FlowLayout(FlowLayout.CENTER)); - osLibsList.setAlignmentY(Component.TOP_ALIGNMENT); - - osLibsList.setVisibleRowCount(4); - - osLibsList.addListSelectionListener(e -> { - if(!e.getValueIsAdjusting()) { - - String text = osLibsList.getSelectedValue(); - String[] urls = StrUtil.findUrlsInString(text); - - if(urls.length > 0) { - try { - Desktop.getDesktop().browse(new URI(urls[0])); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - osLibsList.clearSelection(); - - } - }); - - JPanel osLibsListPane = new JPanel(new FlowLayout(FlowLayout.CENTER)); - osLibsList.setAlignmentY(Component.TOP_ALIGNMENT); - - JScrollPane osLibsListScroll = new JScrollPane(); - osLibsListScroll.setBorder(new EmptyBorder(0,0,20,10)); - osLibsListScroll.setAlignmentX(Component.CENTER_ALIGNMENT); - osLibsListScroll.setAlignmentY(Component.TOP_ALIGNMENT); - osLibsListScroll.setViewportView(osLibsList); - - osLibs.setAlignmentY(Component.TOP_ALIGNMENT); - - osLibsListPane.add(osLibsListScroll); - osLibs.add(osLibsListPane); - - tabbedPane.addTab("Open Source Libraries", osLibs); - - contents.add(tabbedPane); - - contents.setBorder(new EmptyBorder(10,10,10,10)); - - about.add(contents); - - about.pack(); - about.setLocationRelativeTo(null); - about.setResizable(false); - about.setVisible(true); - } - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/About.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/About.kt new file mode 100644 index 00000000..3f129111 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/About.kt @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.gui.dialog + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.gui.util.GuiUtil +import com.github.serivesmejia.eocvsim.util.StrUtil +import org.deltacv.vision.external.gui.component.ImageX +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.awt.* +import java.net.URI +import java.nio.charset.StandardCharsets +import javax.swing.* +import javax.swing.border.EmptyBorder + +class About : KoinComponent { + + private val visualizer: Visualizer by inject() + + val about = JDialog(visualizer.frame) + + companion object { + var CONTRIBS_LIST_MODEL: ListModel? = null + var OSL_LIST_MODEL: ListModel? = null + + init { + try { + CONTRIBS_LIST_MODEL = GuiUtil.isToListModel(About::class.java.getResourceAsStream("/contributors.txt"), StandardCharsets.UTF_8) + OSL_LIST_MODEL = GuiUtil.isToListModel(About::class.java.getResourceAsStream("/opensourcelibs.txt"), StandardCharsets.UTF_8) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + init { + initAbout() + } + + private fun initAbout() { + about.isModal = true + about.title = "About" + + val contents = JPanel(GridLayout(2, 1)) + contents.alignmentX = Component.CENTER_ALIGNMENT + + val icon = ImageX(EOCVSimIconLibrary.icoEOCVSim64) + icon.setSize(50, 50) + icon.alignmentX = Component.CENTER_ALIGNMENT + icon.border = BorderFactory.createEmptyBorder(10, 10, 10, 10) + + val appInfo = JLabel("EasyOpenCV Simulator v" + EOCVSim.VERSION) + appInfo.font = appInfo.font.deriveFont(appInfo.font.style or Font.BOLD) // set font to bold + + val appInfoLogo = JPanel(FlowLayout()) + appInfoLogo.add(icon) + appInfoLogo.add(appInfo) + appInfoLogo.border = BorderFactory.createEmptyBorder(10, 10, -30, 10) + + contents.add(appInfoLogo) + + val tabbedPane = JTabbedPane() + + val contributors = JPanel(FlowLayout(FlowLayout.CENTER)) + val contribsList = JList() + contribsList.model = CONTRIBS_LIST_MODEL + contribsList.selectionModel = GuiUtil.NoSelectionModel() + contribsList.layout = FlowLayout(FlowLayout.CENTER) + contribsList.alignmentY = Component.TOP_ALIGNMENT + contribsList.visibleRowCount = 4 + + val contributorsList = JPanel(FlowLayout(FlowLayout.CENTER)) + contributorsList.alignmentY = Component.TOP_ALIGNMENT + + val contribsListScroll = JScrollPane() + contribsListScroll.border = EmptyBorder(0, 0, 20, 10) + contribsListScroll.alignmentX = Component.CENTER_ALIGNMENT + contribsListScroll.alignmentY = Component.TOP_ALIGNMENT + contribsListScroll.setViewportView(contribsList) + + contributors.alignmentY = Component.TOP_ALIGNMENT + contents.alignmentY = Component.TOP_ALIGNMENT + + contributorsList.add(contribsListScroll) + contributors.add(contributorsList) + + tabbedPane.addTab("Contributors", contributors) + + val osLibs = JPanel(FlowLayout(FlowLayout.CENTER)) + val osLibsList = JList() + osLibsList.model = OSL_LIST_MODEL + osLibsList.layout = FlowLayout(FlowLayout.CENTER) + osLibsList.alignmentY = Component.TOP_ALIGNMENT + osLibsList.visibleRowCount = 4 + + osLibsList.addListSelectionListener { e -> + if (!e.valueIsAdjusting) { + val text = osLibsList.selectedValue + if (text != null) { + val urls = StrUtil.findUrlsInString(text) + if (urls.isNotEmpty()) { + try { + Desktop.getDesktop().browse(URI(urls[0])) + } catch (ex: Exception) { + ex.printStackTrace() + } + } + } + osLibsList.clearSelection() + } + } + + val osLibsListPane = JPanel(FlowLayout(FlowLayout.CENTER)) + osLibsList.alignmentY = Component.TOP_ALIGNMENT + + val osLibsListScroll = JScrollPane() + osLibsListScroll.border = EmptyBorder(0, 0, 20, 10) + osLibsListScroll.alignmentX = Component.CENTER_ALIGNMENT + osLibsListScroll.alignmentY = Component.TOP_ALIGNMENT + osLibsListScroll.setViewportView(osLibsList) + + osLibs.alignmentY = Component.TOP_ALIGNMENT + osLibsListPane.add(osLibsListScroll) + osLibs.add(osLibsListPane) + + tabbedPane.addTab("Open Source Libraries", osLibs) + + contents.add(tabbedPane) + contents.border = EmptyBorder(10, 10, 10, 10) + + about.add(contents) + about.pack() + about.setLocationRelativeTo(null) + about.isResizable = false + about.isVisible = true + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.kt index a7470c0b..5221e52f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.kt @@ -1,62 +1,55 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.config.Config +import com.github.serivesmejia.eocvsim.LifecycleSignal +import com.github.serivesmejia.eocvsim.config.ConfigManager +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.gui.Visualizer import com.github.serivesmejia.eocvsim.gui.component.input.EnumComboBox import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields import com.github.serivesmejia.eocvsim.gui.theme.Theme -import com.github.serivesmejia.eocvsim.gui.util.WebcamDriver import com.github.serivesmejia.eocvsim.pipeline.PipelineFps import com.github.serivesmejia.eocvsim.pipeline.PipelineTimeout +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import kotlinx.coroutines.channels.Channel +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named import java.awt.FlowLayout import java.awt.GridBagConstraints import java.awt.GridBagLayout import java.awt.GridLayout import javax.swing.* -class Configuration(parent: JFrame, private val eocvSim: EOCVSim) { +class Configuration : KoinComponent { + + private val visualizer: Visualizer by inject() + private val lifecycle: Channel by inject(named("lifecycle")) + private val dialogFactory: DialogFactory by inject() + private val configManager: ConfigManager by inject() + private val onMainLoop: EventHandler by inject(named("onMainLoop")) + + private val config get() = configManager.config - private val dialog = JDialog(parent) - private val config: Config = eocvSim.configManager.config + private val dialog = JDialog(visualizer.frame) - // Declaring the components as 'val' private val themeComboBox: JComboBox private val superAccessCheckBox: JCheckBox private val prefersPaperVisionCheckbox: JCheckBox private val pauseOnImageCheckBox: JCheckBox + private val webcamOpenTimeoutSpinner: JSpinner + private val webcamNewFrameTimeoutSpinner: JSpinner private val pipelineTimeoutComboBox: EnumComboBox private val pipelineFpsComboBox: EnumComboBox - private val preferredWebcamDriver: EnumComboBox private val videoRecordingSize: SizeFields private val videoRecordingFpsComboBox: EnumComboBox private val acceptButton: JButton init { - eocvSim.visualizer.childDialogs.add(dialog) - // --- Interface Tab --- themeComboBox = JComboBox().apply { Theme.entries.forEach { addItem(it.name.replace("_", " ")) } @@ -81,17 +74,18 @@ class Configuration(parent: JFrame, private val eocvSim: EOCVSim) { pauseOnImageCheckBox = JCheckBox("Pause with Image Sources").apply { isSelected = config.pauseOnImages } - preferredWebcamDriver = EnumComboBox( - "Preferred Webcam Driver: ", - WebcamDriver::class.java, - WebcamDriver.entries.toTypedArray() - ).apply { - removeEnumOption(WebcamDriver.OpenIMAJ) - selectedEnum = config.preferredWebcamDriver - } - val inputSourcesPanel = JPanel(GridLayout(2, 1, 1, 8)).apply { + webcamOpenTimeoutSpinner = JSpinner(SpinnerNumberModel(config.webcamOpenTimeoutSec, 0.1, 60.0, 0.1)) + webcamNewFrameTimeoutSpinner = JSpinner(SpinnerNumberModel(config.webcamNewFrameTimeoutSec, 0.1, 60.0, 0.1)) + val inputSourcesPanel = JPanel(GridLayout(3, 1, 1, 8)).apply { add(JPanel(FlowLayout()).apply { add(pauseOnImageCheckBox) }) - add(preferredWebcamDriver) + add(JPanel(FlowLayout()).apply { + add(JLabel("Timeout to start camera stream (seconds): ")) + add(webcamOpenTimeoutSpinner) + }) + add(JPanel(FlowLayout()).apply { + add(JLabel("Timeout for new camera frame (seconds): ")) + add(webcamNewFrameTimeoutSpinner) + }) } // --- Processing Tab --- @@ -139,7 +133,7 @@ class Configuration(parent: JFrame, private val eocvSim: EOCVSim) { } acceptButton.addActionListener { - eocvSim.onMainUpdate.once { + onMainLoop.once { applyChanges() } @@ -175,7 +169,8 @@ class Configuration(parent: JFrame, private val eocvSim: EOCVSim) { config.simTheme = userSelectedTheme config.pauseOnImages = pauseOnImageCheckBox.isSelected - config.preferredWebcamDriver = preferredWebcamDriver.selectedEnum + config.webcamOpenTimeoutSec = (webcamOpenTimeoutSpinner.value as Number).toDouble() + config.webcamNewFrameTimeoutSec = (webcamNewFrameTimeoutSpinner.value as Number).toDouble() config.pipelineTimeout = pipelineTimeoutComboBox.selectedEnum config.pipelineMaxFps = pipelineFpsComboBox.selectedEnum config.videoRecordingSize = videoRecordingSize.currentSize @@ -183,10 +178,12 @@ class Configuration(parent: JFrame, private val eocvSim: EOCVSim) { config.autoAcceptSuperAccessOnTrusted = superAccessCheckBox.isSelected config.flags["prefersPaperVision"] = prefersPaperVisionCheckbox.isSelected - eocvSim.configManager.saveToFile() + configManager.saveToFile() if (userSelectedTheme != previousTheme) { - eocvSim.restart() + dialogFactory.createYesOrNo(dialog, "Applying a new interface theme requires restarting.", "Do you wish to restart now?") { + lifecycle.trySend(LifecycleSignal.Restart) + } } } @@ -194,4 +191,4 @@ class Configuration(parent: JFrame, private val eocvSim: EOCVSim) { dialog.isVisible = false dialog.dispose() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/CrashReportOutput.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/CrashReportOutput.kt index b477e1a8..3824e1b7 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/CrashReportOutput.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/CrashReportOutput.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog import com.formdev.flatlaf.intellijthemes.FlatArcDarkIJTheme @@ -39,14 +21,15 @@ import kotlin.system.exitProcess class CrashReportOutput( parent: JFrame?, - crashReport: String + crashReport: String, + headerText: String? = null ){ val output by lazy { JDialog(parent) } private val reportPanel by lazy { - OutputPanel(DefaultBottomButtonsPanel { exitProcess(0) }) + OutputPanel(DefaultBottomButtonsPanel(hasClearButton = false) { exitProcess(0) }) } init { @@ -60,7 +43,7 @@ class CrashReportOutput( reportPanel.resetScroll() } - output.add(JLabel("An unexpected fatal error occurred, please report this to the developers.").apply { + output.add(JLabel(headerText ?: "An unexpected fatal error occurred, please report this to the developers.").apply { font = font.deriveFont(Font.BOLD, 18f) alignmentX = JLabel.CENTER_ALIGNMENT }) @@ -80,4 +63,4 @@ class CrashReportOutput( output.setLocationRelativeTo(null) output.isVisible = true } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/CreateWorkspace.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/CreateWorkspace.kt index d519d3f6..fc07a147 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/CreateWorkspace.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/CreateWorkspace.kt @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary @@ -13,12 +18,15 @@ import javax.swing.JLabel import javax.swing.JOptionPane import javax.swing.JPanel -class CreateWorkspace( - val parent: JFrame, - val visualizer: Visualizer -) { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class CreateWorkspace : KoinComponent { + + val visualizer: Visualizer by inject() + + val dialog = JDialog(visualizer.frame) - val dialog = JDialog(parent) init { dialog.isModal = true @@ -57,7 +65,8 @@ class CreateWorkspace( dialog.dispose() JOptionPane.showConfirmDialog( - this@CreateWorkspace.parent, + visualizer.frame, + "This feature prefers that you have Visual Studio Code already installed. You can opt to use IntelliJ IDEA instead, but you will have to do so manually.\n\n", "VS Code Workspace", JOptionPane.DEFAULT_OPTION, @@ -97,4 +106,4 @@ class CreateWorkspace( dialog.isVisible = true } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/FileAlreadyExists.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/FileAlreadyExists.java deleted file mode 100644 index 8f6468bf..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/FileAlreadyExists.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog; - -import com.github.serivesmejia.eocvsim.EOCVSim; - -import javax.swing.*; -import java.awt.*; - -public class FileAlreadyExists { - - public static volatile boolean alreadyOpened = false; - public volatile JDialog fileAlreadyExists = null; - public volatile JPanel contentsPanel = new JPanel(); - public volatile UserChoice userChoice; - EOCVSim eocvSim = null; - - public FileAlreadyExists(JFrame parent, EOCVSim eocvSim) { - - fileAlreadyExists = new JDialog(parent); - - this.eocvSim = eocvSim; - - eocvSim.visualizer.childDialogs.add(fileAlreadyExists); - - } - - public UserChoice run() { - fileAlreadyExists.setModal(true); - - fileAlreadyExists.setTitle("Warning"); - - JPanel alreadyExistsPanel = new JPanel(new FlowLayout()); - - JLabel alreadyExistsLabel = new JLabel("File already exists in the selected directory"); - alreadyExistsPanel.add(alreadyExistsLabel); - - contentsPanel.add(alreadyExistsPanel); - - JPanel replaceCancelPanel = new JPanel(new FlowLayout()); - - JButton replaceButton = new JButton("Replace"); - replaceCancelPanel.add(replaceButton); - - replaceButton.addActionListener((e) -> { - userChoice = UserChoice.REPLACE; - fileAlreadyExists.setVisible(false); - }); - - JButton cancelButton = new JButton("Cancel"); - replaceCancelPanel.add(cancelButton); - - cancelButton.addActionListener((e) -> { - userChoice = UserChoice.CANCEL; - fileAlreadyExists.setVisible(false); - }); - - contentsPanel.add(replaceCancelPanel); - - fileAlreadyExists.add(contentsPanel); - - fileAlreadyExists.setResizable(false); - fileAlreadyExists.setLocationRelativeTo(null); - fileAlreadyExists.setVisible(true); - - while (userChoice == UserChoice.NA); - - return userChoice; - } - - public enum UserChoice {NA, REPLACE, CANCEL} - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/FileAlreadyExists.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/FileAlreadyExists.kt new file mode 100644 index 00000000..547f3c52 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/FileAlreadyExists.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.gui.dialog + +import com.github.serivesmejia.eocvsim.gui.Visualizer +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.awt.FlowLayout +import javax.swing.* + +class FileAlreadyExists : KoinComponent { + + private val visualizer: Visualizer by inject() + + var fileAlreadyExists = JDialog(visualizer.frame) + var contentsPanel = JPanel() + var userChoice: UserChoice = UserChoice.NA + + fun run(): UserChoice { + fileAlreadyExists.isModal = true + fileAlreadyExists.title = "Warning" + + contentsPanel.layout = BoxLayout(contentsPanel, BoxLayout.Y_AXIS) + + val alreadyExistsPanel = JPanel(FlowLayout()) + val alreadyExistsLabel = JLabel("File already exists in the selected directory") + alreadyExistsPanel.add(alreadyExistsLabel) + + contentsPanel.add(alreadyExistsPanel) + + val replaceCancelPanel = JPanel(FlowLayout()) + + val replaceButton = JButton("Replace") + replaceCancelPanel.add(replaceButton) + + replaceButton.addActionListener { + userChoice = UserChoice.REPLACE + fileAlreadyExists.isVisible = false + } + + val cancelButton = JButton("Cancel") + replaceCancelPanel.add(cancelButton) + + cancelButton.addActionListener { + userChoice = UserChoice.CANCEL + fileAlreadyExists.isVisible = false + } + + contentsPanel.add(replaceCancelPanel) + + fileAlreadyExists.add(contentsPanel) + fileAlreadyExists.pack() + fileAlreadyExists.isResizable = false + fileAlreadyExists.setLocationRelativeTo(null) + fileAlreadyExists.isVisible = true + + return userChoice + } + + enum class UserChoice { NA, REPLACE, CANCEL } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Output.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Output.kt index b25bc677..cbfeda6f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Output.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Output.kt @@ -1,34 +1,18 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.Visualizer import com.github.serivesmejia.eocvsim.gui.dialog.component.OutputPanel -import com.github.serivesmejia.eocvsim.pipeline.compiler.PipelineCompileStatus +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.pipeline.compiled.CompiledPipelineManager +import com.github.serivesmejia.eocvsim.pipeline.compiled.PipelineCompileStatus +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.swing.Swing import java.awt.Dimension @@ -36,12 +20,15 @@ import java.awt.event.WindowAdapter import java.awt.event.WindowEvent import javax.swing.* +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + class Output @JvmOverloads constructor( - parent: JFrame, - private val eocvSim: EOCVSim, private val tabbedPaneIndex: Int = latestIndex, private val wasManuallyOpened: Boolean = false -) { +) : KoinComponent { + + private val visualizer: Visualizer by inject() companion object { var isAlreadyOpened = false @@ -51,7 +38,7 @@ class Output @JvmOverloads constructor( private set } - private val output = JDialog(parent) + private val output = JDialog(visualizer.frame) private val buildBottomButtonsPanel = BuildOutputBottomButtonsPanel(::close) private val buildOutputPanel = OutputPanel(buildBottomButtonsPanel) @@ -61,14 +48,13 @@ class Output @JvmOverloads constructor( private val tabbedPane = JTabbedPane() - private val compiledPipelineManager = eocvSim.pipelineManager.compiledPipelineManager - private val pipelineExceptionTracker = eocvSim.pipelineManager.pipelineExceptionTracker + private val pipelineManager: PipelineManager by inject() + private val compiledPipelineManager: CompiledPipelineManager by inject() + private val scope: CoroutineScope by inject() init { isAlreadyOpened = true - eocvSim.visualizer.childDialogs.add(output) - output.isModal = true output.title = "Output" @@ -81,7 +67,7 @@ class Output @JvmOverloads constructor( updatePipelineOutput() - if(eocvSim.pipelineManager.paused) { + if(pipelineManager.paused) { pipelinePaused() } else { pipelineResumed() @@ -102,14 +88,14 @@ class Output @JvmOverloads constructor( } @OptIn(DelicateCoroutinesApi::class) - private fun registerListeners() = GlobalScope.launch(Dispatchers.Swing) { + private fun registerListeners() = scope.launch(Dispatchers.Swing) { output.addWindowListener(object: WindowAdapter() { override fun windowClosing(e: WindowEvent) { close() } }) - pipelineExceptionTracker.onUpdate { + pipelineManager.pipelineExceptionTracker.onUpdate { if(!output.isVisible) { removeListener() } else { @@ -134,7 +120,7 @@ class Output @JvmOverloads constructor( } } - eocvSim.pipelineManager.onPause { + pipelineManager.onPause { if(!output.isVisible) { removeListener() } else { @@ -142,7 +128,7 @@ class Output @JvmOverloads constructor( } } - eocvSim.pipelineManager.onResume { + pipelineManager.onResume { if(!output.isVisible) { removeListener() } else { @@ -151,7 +137,7 @@ class Output @JvmOverloads constructor( } pipelineBottomButtonsPanel.pauseButton.addActionListener { - eocvSim.pipelineManager.setPaused(pipelineBottomButtonsPanel.pauseButton.isSelected) + pipelineManager.setPaused(pipelineBottomButtonsPanel.pauseButton.isSelected) if(pipelineBottomButtonsPanel.pauseButton.isSelected) { pipelinePaused() @@ -161,16 +147,16 @@ class Output @JvmOverloads constructor( } pipelineBottomButtonsPanel.clearButton.addActionListener { - eocvSim.pipelineManager.pipelineExceptionTracker.clear() + pipelineManager.pipelineExceptionTracker.clear() } buildBottomButtonsPanel.buildAgainButton.addActionListener { - eocvSim.visualizer.asyncCompilePipelines() + pipelineManager.compiledPipelineManager.asyncBuild() } } private fun updatePipelineOutput() { - pipelineOutputPanel.outputArea.text = pipelineExceptionTracker.message + pipelineOutputPanel.outputArea.text = pipelineManager.pipelineExceptionTracker.message } private fun buildRunning() { @@ -218,7 +204,7 @@ class Output @JvmOverloads constructor( class PipelineBottomButtonsPanel( closeCallback: () -> Unit - ) : OutputPanel.DefaultBottomButtonsPanel(closeCallback) { + ) : OutputPanel.DefaultBottomButtonsPanel(closeCallback = closeCallback) { val pauseButton = JToggleButton("Pause") override fun create(panel: OutputPanel) { @@ -230,7 +216,7 @@ class Output @JvmOverloads constructor( class BuildOutputBottomButtonsPanel( closeCallback: () -> Unit, - ) : OutputPanel.DefaultBottomButtonsPanel(closeCallback) { + ) : OutputPanel.DefaultBottomButtonsPanel(closeCallback = closeCallback) { val buildAgainButton = JButton("Build again") override fun create(panel: OutputPanel) { @@ -240,3 +226,4 @@ class Output @JvmOverloads constructor( } } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/PluginOutput.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/PluginOutput.kt index 570e3d1b..94e8814f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/PluginOutput.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/PluginOutput.kt @@ -1,78 +1,38 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.gui.dialog -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.LifecycleSignal +import com.github.serivesmejia.eocvsim.config.ConfigManager import com.github.serivesmejia.eocvsim.gui.dialog.component.BottomButtonsPanel import com.github.serivesmejia.eocvsim.gui.dialog.component.OutputPanel -import io.github.deltacv.common.util.loggerForThis -import io.github.deltacv.eocvsim.plugin.loader.FilePluginLoaderImpl -import io.github.deltacv.eocvsim.plugin.loader.PluginManager -import io.github.deltacv.eocvsim.plugin.loader.PluginSource -import io.github.deltacv.eocvsim.plugin.repository.PluginRepositoryManager -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.swing.Swing -import java.awt.Dimension -import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import java.awt.Toolkit +import com.github.serivesmejia.eocvsim.plugin.output.PluginDialogSignal +import com.github.serivesmejia.eocvsim.plugin.output.PluginOutputHandler +import com.github.serivesmejia.eocvsim.plugin.output.VisualPluginOutputHandler +import org.deltacv.eocvsim.plugin.loader.FilePluginLoaderImpl +import org.deltacv.eocvsim.plugin.loader.PluginManager +import org.deltacv.eocvsim.plugin.loader.PluginSource +import org.deltacv.eocvsim.plugin.repository.PluginRepositoryManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named +import java.awt.* import java.awt.datatransfer.StringSelection import javax.swing.* -import java.awt.Desktop -import javax.swing.event.ChangeEvent -import javax.swing.event.ChangeListener class PluginOutput( - appendDelegate: AppendDelegate, - val pluginManager: PluginManager, - val eocvSim: EOCVSim? = null, - val onContinue: Runnable -) : Appendable { - - companion object { - const val SPECIAL = "[13mck]" - - const val SPECIAL_OPEN = "$SPECIAL[OPEN]" - const val SPECIAL_OPEN_MGR = "$SPECIAL[OPEN_MGR]" - const val SPECIAL_CLOSE = "$SPECIAL[CLOSE]" - const val SPECIAL_CONTINUE = "$SPECIAL[CONTINUE]" - const val SPECIAL_FREE = "$SPECIAL[FREE]" - const val SPECIAL_SILENT = "$SPECIAL[SILENT]" - - fun String.trimSpecials(): String { - return this - .replace(SPECIAL_OPEN, "") - .replace(SPECIAL_OPEN_MGR, "") - .replace(SPECIAL_CLOSE, "") - .replace(SPECIAL_CONTINUE, "") - .replace(SPECIAL_SILENT, "") - .replace(SPECIAL_FREE, "") - } - } + val outputHandler: PluginOutputHandler, + val pluginManager: org.deltacv.eocvsim.plugin.loader.PluginManager, + val configManager: ConfigManager, + val scope: CoroutineScope +) : Appendable, KoinComponent { + + private val lifecycleChannel: Channel by inject(named("lifecycle")) private val output = JDialog() private val tabbedPane: JTabbedPane @@ -85,10 +45,8 @@ class PluginOutput( private val mavenOutputPanel = OutputPanel(mavenBottomButtonsPanel) - private val logger by loggerForThis() - init { - output.isModal = true + output.isModal = false output.isAlwaysOnTop = true output.title = "Plugin Manager" @@ -104,14 +62,48 @@ class PluginOutput( output.pack() output.setSize(500, 365) - appendDelegate.subscribe(this) + // Subscribe to output messages + outputHandler.onOutput.attachPayload { message -> + SwingUtilities.invokeLater { + mavenOutputPanel.outputArea.text += message + mavenOutputPanel.outputArea.revalidate() + mavenOutputPanel.outputArea.repaint() + } + } + + // Subscribe to dialog signals + outputHandler.onDialogSignal.attachPayload { signal -> + SwingUtilities.invokeLater { + when (signal) { + PluginDialogSignal.ShowOutput -> { + output.isVisible = true + tabbedPane.selectedIndex = tabbedPane.indexOfTab("Output") + } + PluginDialogSignal.ShowPlugins -> { + output.isVisible = true + tabbedPane.selectedIndex = tabbedPane.indexOfTab("Plugins") + tabbedPane.setComponentAt(0, makePluginManagerPanel()) + } + PluginDialogSignal.Hide -> { + output.isVisible = false + } + PluginDialogSignal.EnableContinue -> { + mavenBottomButtonsPanel.continueButton.isEnabled = true + mavenBottomButtonsPanel.closeButton.isEnabled = false + } + PluginDialogSignal.DisableContinue -> { + mavenBottomButtonsPanel.continueButton.isEnabled = false + mavenBottomButtonsPanel.closeButton.isEnabled = true + } + } + } + } output.setLocationRelativeTo(null) output.defaultCloseOperation = WindowConstants.HIDE_ON_CLOSE } - @OptIn(DelicateCoroutinesApi::class) - private fun registerListeners() = GlobalScope.launch(Dispatchers.Swing) { + private fun registerListeners() { output.addWindowListener(object : java.awt.event.WindowAdapter() { override fun windowClosing(e: java.awt.event.WindowEvent?) { if(mavenBottomButtonsPanel.continueButton.isEnabled) { @@ -124,35 +116,35 @@ class PluginOutput( mavenBottomButtonsPanel.continueButton.addActionListener { close() - onContinue.run() + // Signal the outputHandler that user clicked Continue + if (outputHandler is VisualPluginOutputHandler) { + outputHandler.signalContinuation() + } mavenBottomButtonsPanel.continueButton.isEnabled = false mavenBottomButtonsPanel.closeButton.isEnabled = true } - tabbedPane.addChangeListener(object: ChangeListener { - override fun stateChanged(e: ChangeEvent?) { - // remake plugin manager panel - if(tabbedPane.selectedIndex == tabbedPane.indexOfTab("Plugins")) { - tabbedPane.setComponentAt(0, makePluginManagerPanel()) - } + tabbedPane.addChangeListener { // remake plugin manager panel + if (tabbedPane.selectedIndex == tabbedPane.indexOfTab("Plugins")) { + tabbedPane.setComponentAt(0, makePluginManagerPanel()) } - }) + } } private fun checkShouldAskForRestart() { - if(shouldAskForRestart && eocvSim != null) { + if(shouldAskForRestart) { val dialogResult = JOptionPane.showConfirmDialog( output, "You need to restart the application to apply the changes. Do you want to restart now?", "Restart required", JOptionPane.YES_NO_OPTION ) - + if(dialogResult == JOptionPane.YES_OPTION) { output.isVisible = false - eocvSim.restart() + lifecycleChannel.trySend(LifecycleSignal.Restart) } - + shouldAskForRestart = false } } @@ -186,7 +178,7 @@ class PluginOutput( pluginPanel.layout = GridBagLayout() val pluginNameLabel = JLabel( - "

${loader.pluginInfo.nameWithAuthorVersion}

", + "

${loader.pluginInfo.nameWithVersionAndAuthor}

", SwingConstants.CENTER ) @@ -212,7 +204,7 @@ class PluginOutput( PluginSource.EMBEDDED -> "as an embedded plugin" } - val sourceEnabled = if(loader.shouldEnable) "It was LOADED $source." else "It is DISABLED, it comes $source." + val sourceEnabled = if(loader.shouldEnable) "It was loaded $source." else "It is disabled, it comes $source." val superAccess = if(loader.hasSuperAccess) "It has super access." @@ -222,7 +214,7 @@ class PluginOutput( is FilePluginLoaderImpl -> { if(loader.pluginToml.getBoolean("super-access", false)) "It requests super access in its manifest." - else "It does not request super access in its manifest." + else "" } else -> "" } @@ -360,14 +352,15 @@ class PluginOutput( ) if(dialogResult == JOptionPane.YES_OPTION) { - eocvSim?.config?.flags?.set("startFresh", true) - + configManager.config.flags["startFreshPlugins"] = true + PluginRepositoryManager.REPOSITORY_FILE.delete() PluginRepositoryManager.CACHE_FILE.delete() - + shouldAskForRestart = true checkShouldAskForRestart() } + } val closeButton = JButton("Close") @@ -412,72 +405,18 @@ class PluginOutput( output.isVisible = false } - private fun handleSpecials(text: String): Boolean { - when(text) { - SPECIAL_FREE -> { - mavenBottomButtonsPanel.continueButton.isEnabled = false - mavenBottomButtonsPanel.closeButton.isEnabled = true - } - SPECIAL_CLOSE -> close() - SPECIAL_CONTINUE -> { - mavenBottomButtonsPanel.continueButton.isEnabled = true - mavenBottomButtonsPanel.closeButton.isEnabled = false - } - } - - if(!text.startsWith(SPECIAL_SILENT) && text != SPECIAL_CLOSE && text != SPECIAL_FREE) { - SwingUtilities.invokeLater { - SwingUtilities.invokeLater { - if(text == SPECIAL_OPEN_MGR) { - tabbedPane.selectedIndex = tabbedPane.indexOfTab("Plugins") // focus on plugins tab - } else { - tabbedPane.selectedIndex = tabbedPane.indexOfTab("Output") // focus on output tab - } - } - - tabbedPane.setComponentAt(0, makePluginManagerPanel()) - logger.info("Displaying plugin manager dialog") - - output.isVisible = true - } - } - - return text == SPECIAL_OPEN || text == SPECIAL_CLOSE || text == SPECIAL_CONTINUE || text == SPECIAL_FREE - } - override fun append(csq: CharSequence?): java.lang.Appendable { - val text = csq.toString() - - SwingUtilities.invokeLater { - if(handleSpecials(text)) return@invokeLater - - mavenOutputPanel.outputArea.text += text.trimSpecials() - mavenOutputPanel.outputArea.revalidate() - mavenOutputPanel.outputArea.repaint() - } + // ...existing code... return this } override fun append(csq: CharSequence?, start: Int, end: Int): java.lang.Appendable { - val text = csq.toString().substring(start, end) - - SwingUtilities.invokeLater { - if(handleSpecials(text)) return@invokeLater - - mavenOutputPanel.outputArea.text += text.trimSpecials() - mavenOutputPanel.outputArea.revalidate() - mavenOutputPanel.outputArea.repaint() - } + // ...existing code... return this } override fun append(c: Char): java.lang.Appendable { - SwingUtilities.invokeLater { - mavenOutputPanel.outputArea.text += c - mavenOutputPanel.outputArea.revalidate() - mavenOutputPanel.outputArea.repaint() - } - + // ...existing code... return this } @@ -521,42 +460,3 @@ class PluginOutput( } } } - - -class AppendDelegate { - private val appendables = mutableListOf() - - @Synchronized - fun subscribe(appendable: Appendable) { - appendables.add(appendable) - } - - fun subscribe(appendable: (String) -> Unit) { - appendables.add(object : Appendable { - override fun append(csq: CharSequence?): java.lang.Appendable { - appendable(csq.toString()) - return this - } - - override fun append(csq: CharSequence?, start: Int, end: Int): java.lang.Appendable { - appendable(csq.toString().substring(start, end)) - return this - } - - override fun append(c: Char): java.lang.Appendable { - appendable(c.toString()) - return this - } - }) - } - - @Synchronized - fun append(text: String) { - appendables.forEach { it.append(text) } - } - - @Synchronized - fun appendln(text: String) { - appendables.forEach { it.appendLine(text) } - } -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/SplashScreen.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/SplashScreen.kt index 2ac13134..42bc9bec 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/SplashScreen.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/SplashScreen.kt @@ -1,40 +1,23 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary import com.github.serivesmejia.eocvsim.gui.Icons import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.event.ParamEventHandler import java.awt.* import javax.swing.JDialog import javax.swing.JPanel import javax.swing.SwingUtilities -class SplashScreen(closeHandler: EventHandler? = null) : JDialog() { +class SplashScreen(vararg closeHandlers: EventHandler) : JDialog() { init { - closeHandler?.once { + EventHandler.batchOnce(*closeHandlers) { SwingUtilities.invokeLater { isVisible = false dispose() @@ -76,4 +59,4 @@ class SplashScreen(closeHandler: EventHandler? = null) : JDialog() { override fun getPreferredSize() = Dimension(img.iconWidth / 6, img.iconHeight / 6) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/component/OutputPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/component/OutputPanel.kt index a59eb110..134c2203 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/component/OutputPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/component/OutputPanel.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog.component import com.formdev.flatlaf.FlatLaf @@ -100,6 +82,7 @@ class OutputPanel( } open class DefaultBottomButtonsPanel( + val hasClearButton: Boolean = true, override val closeCallback: () -> Unit ) : BottomButtonsPanel() { val copyButton = JButton("Copy") @@ -119,10 +102,12 @@ class OutputPanel( add(copyButton) add(Box.createRigidArea(Dimension(4, 0))) - clearButton.addActionListener { panel.outputArea.text = "" } + if(hasClearButton) { + clearButton.addActionListener { panel.outputArea.text = "" } - add(clearButton) - add(Box.createRigidArea(Dimension(4, 0))) + add(clearButton) + add(Box.createRigidArea(Dimension(4, 0))) + } closeButton.addActionListener { closeCallback() } @@ -139,3 +124,4 @@ abstract class BottomButtonsPanel : JPanel() { abstract fun create(panel: OutputPanel) } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmA.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmA.kt index 19eb4da7..659980cf 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmA.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmA.kt @@ -1,30 +1,14 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog.iama +import com.github.serivesmejia.eocvsim.gui.DialogFactory import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.config.ConfigManager import java.awt.Dimension import java.awt.GridBagConstraints import java.awt.GridBagLayout @@ -36,12 +20,18 @@ import javax.swing.JFrame import javax.swing.JLabel import javax.swing.JPanel -class IAmA( - val parent: JFrame, - val visualizer: Visualizer -) { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class IAmA : KoinComponent { + + private val visualizer: Visualizer by inject() + private val configManager: ConfigManager by inject() + + private val dialogFactory: DialogFactory by inject() + + val dialog = JDialog(visualizer.frame) - val dialog = JDialog(parent) init { dialog.isModal = true @@ -78,7 +68,7 @@ class IAmA( addActionListener { dialog.dispose() - IAmAFirstRobotics(this@IAmA.parent, visualizer) + IAmAFirstRobotics() } }) @@ -92,7 +82,7 @@ class IAmA( addActionListener { dialog.dispose() - IAmAGeneralPublic(this@IAmA.parent, visualizer) + IAmAGeneralPublic() } }) @@ -106,7 +96,7 @@ class IAmA( addActionListener { dialog.dispose() - IAmAPaperVision(this@IAmA.parent, visualizer, specificallyInterested = true) + IAmAPaperVision(specificallyInterested = true) } }) @@ -125,8 +115,8 @@ class IAmA( dialog.defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE dialog.setLocationRelativeTo(null) - visualizer.eocvSim.config.flags["hasShownIamA"] = true + configManager.config.flags["hasShownIamA"] = true dialog.isVisible = true } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAFirstRobotics.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAFirstRobotics.kt index fd5b4331..0e0280a8 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAFirstRobotics.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAFirstRobotics.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog.iama import com.github.serivesmejia.eocvsim.gui.Visualizer @@ -37,12 +19,15 @@ import javax.swing.JFrame import javax.swing.JLabel import javax.swing.JPanel -class IAmAFirstRobotics( - parent: JFrame, - visualizer: Visualizer -) { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class IAmAFirstRobotics : KoinComponent { + + private val visualizer: Visualizer by inject() + + private val dialog = JDialog(visualizer.frame).apply { - private val dialog = JDialog(parent).apply { isModal = true title = "Welcome !" contentPane.layout = GridBagLayout() @@ -100,9 +85,10 @@ class IAmAFirstRobotics( add(JButton("Next").apply { addActionListener { - dialog.dispose() // Close the dialog on click - IAmAPaperVision(parent, visualizer) + dialog.dispose() + IAmAPaperVision() } + }) border = BorderFactory.createEmptyBorder(0, 10, 10, 10) @@ -135,4 +121,4 @@ class IAmAFirstRobotics( // Make the dialog visible dialog.isVisible = true } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAGeneralPublic.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAGeneralPublic.kt index 35a08de7..a84a9c0d 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAGeneralPublic.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAGeneralPublic.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog.iama import com.github.serivesmejia.eocvsim.gui.Visualizer @@ -37,12 +19,15 @@ import javax.swing.JFrame import javax.swing.JLabel import javax.swing.JPanel -class IAmAGeneralPublic( - parent: JFrame, - visualizer: Visualizer -) { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class IAmAGeneralPublic : KoinComponent { + + private val visualizer: Visualizer by inject() + + private val dialog = JDialog(visualizer.frame).apply { - private val dialog = JDialog(parent).apply { isModal = true title = "Welcome !" contentPane.layout = GridBagLayout() @@ -96,15 +81,14 @@ class IAmAGeneralPublic( } }) - add(Box.createHorizontalStrut(10)) // Add some space between the buttons - add(JButton("Next").apply { addActionListener { - dialog.dispose() // Close the dialog on click - IAmAPaperVision(parent, visualizer) + dialog.dispose() + IAmAPaperVision() } }) + border = BorderFactory.createEmptyBorder(0, 10, 10, 10) } @@ -135,4 +119,4 @@ class IAmAGeneralPublic( // Make the dialog visible dialog.isVisible = true } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAPaperVision.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAPaperVision.kt index fa62de54..5b7b1609 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAPaperVision.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/iama/IAmAPaperVision.kt @@ -1,30 +1,15 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog.iama import com.github.serivesmejia.eocvsim.gui.DialogFactory import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.config.ConfigManager +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.awt.BorderLayout import java.awt.Dimension import javax.swing.BorderFactory @@ -40,17 +25,21 @@ import javax.swing.JPanel import javax.swing.SwingConstants class IAmAPaperVision( - parent: JFrame, - visualizer: Visualizer, - specificallyInterested: Boolean = false, - showWorkspacesButton: Boolean = true -) { + private val specificallyInterested: Boolean = false, + private val showWorkspacesButton: Boolean = true +) : KoinComponent { companion object { val papervisionGif = ImageIcon(this::class.java.getResource("/images/papervision.gif")) } - val dialog = JDialog(parent).apply { + private val visualizer: Visualizer by inject() + private val configManager: ConfigManager by inject() + + private val dialogFactory: DialogFactory by inject() + + val dialog = JDialog(visualizer.frame).apply { + isModal = true title = "Welcome !" @@ -115,7 +104,7 @@ class IAmAPaperVision( add(JButton("Use Workspaces Instead").apply { addActionListener { dialog.dispose() // Close the dialog on click - DialogFactory.createWorkspace(visualizer) + dialogFactory.createWorkspace() } }) @@ -131,7 +120,8 @@ class IAmAPaperVision( visualizer.sidebarPanel.selectedIndex = indexOfTab } else { JOptionPane.showMessageDialog( - parent, + visualizer.frame, + "PaperVision is not currently available, please check your plugin settings.", "Warning", JOptionPane.ERROR_MESSAGE @@ -140,14 +130,15 @@ class IAmAPaperVision( } fun openPaperVisionByDefault() { - visualizer.eocvSim.config.flags["prefersPaperVision"] = true + configManager.config.flags["prefersPaperVision"] = true } if(specificallyInterested) { openPaperVisionByDefault() JOptionPane.showConfirmDialog( - parent, + visualizer.frame, + "From now on, EOCV-Sim will focus on PaperVision upon startup.\nYou can change this in the settings.", "PaperVision", JOptionPane.DEFAULT_OPTION, @@ -155,7 +146,8 @@ class IAmAPaperVision( ) } else { JOptionPane.showOptionDialog( - parent, + visualizer.frame, + "Would you like to focus on PaperVision by default?\nThis is useful if you're not interested on the other tools.\nYou can change this in the settings.", "PaperVision", JOptionPane.YES_NO_OPTION, @@ -167,7 +159,7 @@ class IAmAPaperVision( if(it == JOptionPane.YES_OPTION) { openPaperVisionByDefault() } else { - visualizer.eocvSim.config.flags["prefersPaperVision"] = false + configManager.config.flags["prefersPaperVision"] = false } } } @@ -181,8 +173,8 @@ class IAmAPaperVision( dialog.contentPane.add(buttonsPanel) - visualizer.eocvSim.config.flags["hasShownIamPaperVision"] = true + configManager.config.flags["hasShownIamPaperVision"] = true dialog.isVisible = true } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateCameraSource.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateCameraSource.kt index ee72d18c..25c0e25b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateCameraSource.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateCameraSource.kt @@ -1,347 +1,356 @@ /* - * Copyright (c) 2021 Sebastian Erives - * - * 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. + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.gui.dialog.source -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.component.input.EnumComboBox -import com.github.serivesmejia.eocvsim.gui.util.WebcamDriver +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.input.InputSourceManager import com.github.serivesmejia.eocvsim.input.source.CameraSource -import io.github.deltacv.steve.Webcam -import io.github.deltacv.steve.WebcamRotation -import io.github.deltacv.steve.commonResolutions -import io.github.deltacv.steve.opencv.OpenCvWebcam -import io.github.deltacv.steve.opencv.OpenCvWebcamBackend -import io.github.deltacv.steve.openpnp.OpenPnpBackend -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.opencv.core.Mat -import org.opencv.core.Size +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named +import org.wpilib.vision.camera.UsbCamera +import org.wpilib.vision.camera.UsbCameraInfo +import org.wpilib.vision.camera.VideoMode import java.awt.* import javax.swing.* -class CreateCameraSource( - parent: JFrame, - private val eocvSim: EOCVSim -) { +class CreateCameraSource : KoinComponent { - companion object { - const val VISIBLE_CHARACTERS_COMBO_BOX = 22 - private val sizes = mutableMapOf>() - } - - val createCameraSource = JDialog(parent) - private val statusLabel = JLabel("", SwingConstants.CENTER) + private val inputSourceManager: InputSourceManager by inject() + private val onMainUpdate: EventHandler by inject(named("onMainLoop")) + private val visualizer: Visualizer by inject() private val camerasComboBox = JComboBox() - private val dimensionsComboBox = JComboBox() - private val rotationComboBox = EnumComboBox( - "", - WebcamRotation::class.java, - WebcamRotation.entries.toTypedArray(), // Correction: Using .entries - WebcamRotation::displayName - ) { WebcamRotation.fromDisplayName(it) ?: WebcamRotation.UPRIGHT } // Correction: Lambda moved out - private val nameTextField = JTextField(15) - private val createButton = JButton() - private var wasCancelled = false - - private var webcams = listOf() - private val indexes = mutableMapOf() - private var usingOpenCvDiscovery = false - - private var state = State.INITIAL - private enum class State { INITIAL, CLICKED_TEST, TEST_SUCCESSFUL, TEST_FAILED, NO_WEBCAMS, UNSUPPORTED } + private val resolutionComboBox = JComboBox() + private val fpsComboBox = JComboBox() + private val pixelFormatComboBox = JComboBox() + private val nameTextField = JTextField(20) + private val sameCameraCheckBox = JCheckBox("Match to exact port") - init { - eocvSim.visualizer.childDialogs.add(createCameraSource) - - // Force preferred driver fallback - if (eocvSim.config.preferredWebcamDriver == WebcamDriver.OpenIMAJ) - eocvSim.config.preferredWebcamDriver = WebcamDriver.OpenPnp - - val preferredDriver = eocvSim.config.preferredWebcamDriver - - when (preferredDriver) { - WebcamDriver.OpenPnp -> { - Webcam.backend = OpenPnpBackend - webcams = try { - Webcam.availableWebcams - } catch (t: Throwable) { - t.printStackTrace() - emptyList() - } - } + private val createButton = JButton("Create") - WebcamDriver.OpenCV -> { - webcams = emptyList() - Webcam.backend = OpenCvWebcamBackend - usingOpenCvDiscovery = true - } + private val dialog = JDialog(visualizer.frame) - else -> webcams = emptyList() + private var cameraInfos: Array = emptyArray() + private var modes: Array = emptyArray() + + init { + cameraInfos = try { + UsbCamera.enumerateUsbCameras() + } catch (t: Throwable) { + t.printStackTrace() + emptyArray() } - createCameraSource.apply { + dialog.apply { isModal = true - title = "Create camera source" + title = "Create Camera Source" } - // Build UI - val contentsPanel = JPanel(GridBagLayout()) + val root = JPanel(GridBagLayout()) + val gbc = GridBagConstraints().apply { fill = GridBagConstraints.HORIZONTAL - insets = Insets(7, 0, 0, 7) + weightx = 1.0 + insets = Insets(6, 6, 6, 6) } - // Camera combo box - val idLabel = JLabel("Available cameras: ", JLabel.RIGHT) - if (webcams.isEmpty()) { - camerasComboBox.addItem("No Cameras Detected") - state = State.NO_WEBCAMS - } else { - webcams.forEachIndexed { index, webcam -> - val name = webcam.name.let { - if (it.length > VISIBLE_CHARACTERS_COMBO_BOX) it.take(VISIBLE_CHARACTERS_COMBO_BOX) + "..." - else it - } + // ---------------- CAMERA LIST ---------------- - camerasComboBox.addItem(name) - indexes[name] = index + if (cameraInfos.isEmpty()) { + camerasComboBox.addItem("No Cameras Found") + createButton.isEnabled = false + } else { + cameraInfos.forEach { + camerasComboBox.addItem(it.name) + } - if (!sizes.containsKey(name)) { - val resolutions = if (webcam is OpenCvWebcam) { - commonResolutions - } else webcam.supportedResolutions.ifEmpty { - println("Webcam $name has no resolutions, skipping") - return@forEachIndexed - } + camerasComboBox.selectedIndex = 0 - sizes[name] = resolutions - } - } + updateAutomaticName() + loadModes(0) + } - SwingUtilities.invokeLater { camerasComboBox.selectedIndex = 0 } + camerasComboBox.addActionListener { + updateAutomaticName() + loadModes(camerasComboBox.selectedIndex) } - val fieldsPanel = JPanel(GridBagLayout()).apply { - add(idLabel, gbc) - gbc.gridx = 1; add(camerasComboBox, gbc) - - // Name field - gbc.gridx = 0; gbc.gridy = 1 - add(JLabel("Source name: ", JLabel.RIGHT), gbc) - gbc.gridx = 1 - nameTextField.text = "CameraSource-${eocvSim.inputSourceManager.sources.size + 1}" - add(nameTextField, gbc) - - // Suggested resolution - gbc.gridx = 0; gbc.gridy = 2 - add(JLabel("Suggested resolutions: ", JLabel.RIGHT), gbc) - gbc.gridx = 1 - add(dimensionsComboBox, gbc) - - // Rotation - gbc.gridx = 0; gbc.gridy = 3 - add(JLabel("Camera rotation: ", JLabel.RIGHT), gbc) - gbc.gridx = 1 - add(rotationComboBox.comboBox, gbc) + // ---------------- MODE CONTROLS ---------------- + + // simple renderers (defaults are fine, but keep fps/pixfmt readable) + fpsComboBox.renderer = object : DefaultListCellRenderer() { + override fun getListCellRendererComponent(list: JList<*>, value: Any?, index: Int, isSelected: Boolean, cellHasFocus: Boolean): Component { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus) + text = (value as? Int)?.toString() ?: "-" + return this + } } - gbc.gridx = 0; gbc.gridy = 0 - contentsPanel.add(fieldsPanel, gbc) + // ---------------- FORM ---------------- + + gbc.gridx = 0 + gbc.gridy = 0 + gbc.weightx = 0.0 + root.add(JLabel("Camera:"), gbc) + + gbc.gridx = 1 + gbc.weightx = 1.0 + root.add(camerasComboBox, gbc) + // Resolution + gbc.gridx = 0 gbc.gridy = 1 - contentsPanel.add(statusLabel, gbc) + gbc.weightx = 0.0 + root.add(JLabel("Resolution:"), gbc) - // Bottom buttons - val bottomPanel = JPanel(GridBagLayout()) - val gbcBtts = GridBagConstraints().apply { insets = Insets(0, 0, 0, 10) } - bottomPanel.add(createButton, gbcBtts) - gbcBtts.gridx = 1; gbcBtts.insets = Insets(0, 0, 0, 0) - val cancelButton = JButton("Cancel") - bottomPanel.add(cancelButton, gbcBtts) + gbc.gridx = 1 + gbc.weightx = 1.0 + root.add(resolutionComboBox, gbc) + + // FPS + gbc.gridx = 0 + gbc.gridy = 2 + gbc.weightx = 0.0 + root.add(JLabel("FPS:"), gbc) - gbc.insets = Insets(10, 0, 0, 0) - gbc.gridx = 0; gbc.gridy = 2 - contentsPanel.add(bottomPanel, gbc) + gbc.gridx = 1 + gbc.weightx = 1.0 + root.add(fpsComboBox, gbc) - contentsPanel.border = BorderFactory.createEmptyBorder(8, 15, 15, 0) - createCameraSource.contentPane.add(contentsPanel, BorderLayout.CENTER) + // Pixel Format + gbc.gridx = 0 + gbc.gridy = 3 + gbc.weightx = 0.0 + root.add(JLabel("Pixel Format:"), gbc) - // Event listeners - createButton.addActionListener { onCreateButton() } + gbc.gridx = 1 + gbc.weightx = 1.0 + root.add(pixelFormatComboBox, gbc) - camerasComboBox.addActionListener { onCameraSelectionChanged() } + // Name + gbc.gridx = 0 + gbc.gridy = 4 + gbc.weightx = 0.0 + root.add(JLabel("Source Name:"), gbc) - nameTextField.document.addDocumentListener(SimpleDocumentListener { updateCreateButton() }) - cancelButton.addActionListener { wasCancelled = true; close() } + gbc.gridx = 1 + gbc.weightx = 1.0 + root.add(nameTextField, gbc) - updateState() + gbc.gridx = 1 + gbc.gridy = 5 + gbc.weightx = 1.0 + root.add(sameCameraCheckBox, gbc) - createCameraSource.apply { - pack() - isResizable = false - isAlwaysOnTop = true - setLocationRelativeTo(null) - isVisible = true + // ---------------- BUTTONS ---------------- + + val buttons = JPanel(FlowLayout(FlowLayout.RIGHT)) + + val cancelButton = JButton("Cancel") + + buttons.add(createButton) + buttons.add(cancelButton) + + gbc.gridx = 0 + gbc.gridy = 6 + gbc.gridwidth = 2 + + root.add(buttons, gbc) + + // ---------------- EVENTS ---------------- + + createButton.addActionListener { + create() } - } - private fun onCreateButton() { - if (state == State.TEST_SUCCESSFUL) { - val webcam = webcams[getSelectedIndex()] - val dim = sizes[camerasComboBox.selectedItem]!![dimensionsComboBox.selectedIndex] - val rotation = rotationComboBox.selectedEnum ?: WebcamRotation.UPRIGHT // Correction: Handle null case - - if (usingOpenCvDiscovery) { - val index = if (webcam is OpenCvWebcam) webcam.index else camerasComboBox.selectedIndex - createSource(nameTextField.text, index, dim, rotation) - } else { - createSource(nameTextField.text, webcam.name, dim, rotation) - } + cancelButton.addActionListener { close() - } else { - state = State.CLICKED_TEST - updateState() - CoroutineScope(Dispatchers.IO).launch { - val webcam = webcams[getSelectedIndex()] - val dim = sizes[camerasComboBox.selectedItem]!![dimensionsComboBox.selectedIndex] - - webcam.resolution = dim - val success = testCamera(webcam) - - SwingUtilities.invokeLater { - if (!wasCancelled) { - state = if (success) State.TEST_SUCCESSFUL else State.TEST_FAILED - updateState() - } - } - } } + + // ---------------- DIALOG ---------------- + + dialog.contentPane.add(root) + + dialog.pack() + dialog.minimumSize = dialog.size + + dialog.setLocationRelativeTo(null) + dialog.isVisible = true } - private fun onCameraSelectionChanged() { - val webcam = webcams.getOrNull(getSelectedIndex()) ?: run { - state = State.UNSUPPORTED - updateState() - return + private fun updateAutomaticName() { + val info = cameraInfos.getOrNull(camerasComboBox.selectedIndex) + ?: return + + nameTextField.text = inputSourceManager.tryName(info.name) + } + + private fun loadModes(index: Int) { + + val info = cameraInfos.getOrNull(index) ?: return + + try { + val cam = UsbCamera(info.name, info.dev) + + modes = cam.enumerateVideoModes() + .distinctBy { + "${it.width}x${it.height}_${it.fps}_${it.pixelFormat}" + } + .sortedWith( + compareByDescending { + it.width * it.height + }.thenByDescending { + it.fps + } + ) + .toTypedArray() + + cam.close() + + } catch (t: Throwable) { + + t.printStackTrace() + modes = emptyArray() } - nameTextField.text = eocvSim.inputSourceManager.tryName(webcam.name) - dimensionsComboBox.removeAllItems() - - CoroutineScope(Dispatchers.IO).launch { - val webcamSizes = sizes[camerasComboBox.selectedItem] ?: return@launch - SwingUtilities.invokeLater { - dimensionsComboBox.removeAllItems() - webcamSizes.forEach { dimensionsComboBox.addItem("${it.width.toInt()}x${it.height.toInt()}") } - state = State.INITIAL - updateCreateButton() - updateState() + // Populate resolution / fps / pixel format combo boxes based on available modes + resolutionComboBox.removeAllItems() + fpsComboBox.removeAllItems() + pixelFormatComboBox.removeAllItems() + + val resolutions = modes + .map { "${it.width}x${it.height}" } + .distinct() + .sortedByDescending { res -> + val (w, h) = parseResolution(res) ?: Pair(0, 0) + w * h } + + for (r in resolutions) resolutionComboBox.addItem(r) + + if (resolutions.isNotEmpty()) { + resolutionComboBox.selectedIndex = 0 } - } - private fun testCamera(webcam: Webcam): Boolean { - webcam.open() - var success = webcam.isOpen - if (success) { - val m = Mat() - try { webcam.read(m) } catch (_: Exception) { success = false } - m.release() - webcam.close() + // listeners (remove previous to avoid duplicates) + for (l in resolutionComboBox.actionListeners) resolutionComboBox.removeActionListener(l) + for (l in fpsComboBox.actionListeners) fpsComboBox.removeActionListener(l) + + resolutionComboBox.addActionListener { + updateFpsAndPixelFormats() } - return success - } - private fun updateState() { - when (state) { - State.INITIAL -> { - statusLabel.text = "Click \"test\" to test camera." - createButton.text = "Test" - setInteractables(true) - } - State.CLICKED_TEST -> { - statusLabel.text = "Trying to open camera, please wait..." - setInteractables(false) - } - State.TEST_SUCCESSFUL -> { - statusLabel.text = "Camera was opened successfully." - createButton.text = "Create" - setInteractables(true) - } - State.TEST_FAILED -> { - statusLabel.text = "Failed to open camera, try another one." - createButton.text = "Test" - setInteractables(true) - } - State.NO_WEBCAMS -> { - statusLabel.text = "No cameras detected." - createButton.text = "Test" - nameTextField.text = "" - setInteractables(false) - } - State.UNSUPPORTED -> { - statusLabel.text = "This camera is currently unavailable." - createButton.text = "Test" - nameTextField.text = "" - setInteractables(false) - camerasComboBox.isEnabled = true - } + fpsComboBox.addActionListener { + updatePixelFormatsForFps() } - } - private fun setInteractables(enabled: Boolean) { - createButton.isEnabled = enabled - nameTextField.isEnabled = enabled - camerasComboBox.isEnabled = enabled - dimensionsComboBox.isEnabled = enabled + // initialize dependent lists + if (resolutionComboBox.itemCount > 0) updateFpsAndPixelFormats() } - private fun close() { - createCameraSource.isVisible = false - createCameraSource.dispose() + private fun parseResolution(res: String?): Pair? { + if (res == null) return null + val parts = res.split('x') + if (parts.size != 2) return null + return try { + Pair(parts[0].toInt(), parts[1].toInt()) + } catch (_: NumberFormatException) { + null + } } - private fun createSource(name: String, camName: String, size: Size, rotation: WebcamRotation) { - eocvSim.onMainUpdate.once { eocvSim.inputSourceManager.addInputSource(name, CameraSource(camName, size, rotation), true) } - } + private fun updateFpsAndPixelFormats() { + val res = resolutionComboBox.selectedItem as? String ?: return + val (w, h) = parseResolution(res) ?: return + + val modesForRes = modes.filter { it.width == w && it.height == h } + + val fpsList = modesForRes.map { it.fps }.distinct().sortedDescending() - private fun createSource(name: String, camIndex: Int, size: Size, rotation: WebcamRotation) { - eocvSim.onMainUpdate.once { eocvSim.inputSourceManager.addInputSource(name, CameraSource(camIndex, size, rotation), true) } + fpsComboBox.removeAllItems() + for (f in fpsList) fpsComboBox.addItem(f) + if (fpsComboBox.itemCount > 0) fpsComboBox.selectedIndex = 0 + + // populate pixel formats for the initially selected fps + updatePixelFormatsForFps() } - private fun updateCreateButton() { - createButton.isEnabled = nameTextField.text.trim().isNotEmpty() && - !eocvSim.inputSourceManager.isNameOnUse(nameTextField.text) + private fun updatePixelFormatsForFps() { + val res = resolutionComboBox.selectedItem as? String ?: return + val (w, h) = parseResolution(res) ?: return + val selectedFps = fpsComboBox.selectedItem as? Int + + val modesForRes = modes.filter { it.width == w && it.height == h } + + val pfList = if (selectedFps != null) { + modesForRes.filter { it.fps == selectedFps }.map { it.pixelFormat } + } else { + modesForRes.map { it.pixelFormat } + } + + val distinctPf = pfList.distinct() + + pixelFormatComboBox.removeAllItems() + for (pf in distinctPf) pixelFormatComboBox.addItem(pf) + if (pixelFormatComboBox.itemCount > 0) pixelFormatComboBox.selectedIndex = 0 } - private fun getSelectedIndex() = indexes[camerasComboBox.selectedItem] ?: 0 + private fun create() { + + val camIndex = camerasComboBox.selectedIndex + + val info = cameraInfos.getOrNull(camIndex) + ?: return + + // Build desired VideoMode from selected resolution / fps / pixel format + val resStr = resolutionComboBox.selectedItem as? String ?: return + val (w, h) = parseResolution(resStr) ?: return + val fps = fpsComboBox.selectedItem as? Int + val pf = pixelFormatComboBox.selectedItem + + var mode: VideoMode? = null - // Simple helper for DocumentListener in Kotlin - private class SimpleDocumentListener(val onChange: () -> Unit) : javax.swing.event.DocumentListener { - override fun insertUpdate(e: javax.swing.event.DocumentEvent) = onChange() - override fun removeUpdate(e: javax.swing.event.DocumentEvent) = onChange() - override fun changedUpdate(e: javax.swing.event.DocumentEvent) = onChange() + if (fps != null && pf != null) { + mode = modes.find { it.width == w && it.height == h && it.fps == fps && it.pixelFormat == pf } + } + + // fallback: match by resolution + fps + if (mode == null && fps != null) { + mode = modes.find { it.width == w && it.height == h && it.fps == fps } + } + + // fallback: any mode with resolution + if (mode == null) { + mode = modes.find { it.width == w && it.height == h } + } + + mode ?: return + + onMainUpdate.once { + + inputSourceManager.addInputSource( + nameTextField.text.trim(), + CameraSource( + info.name, + info.dev, + sameCameraCheckBox.isSelected, + info.vendorId, + info.productId, + mode + ), + true + ) + } + + close() + } + + private fun close() { + dialog.dispose() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateHttpSource.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateHttpSource.kt index d38a1fd1..18bd0111 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateHttpSource.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateHttpSource.kt @@ -1,29 +1,15 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ + package com.github.serivesmejia.eocvsim.gui.dialog.source import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.input.InputSourceManager import com.github.serivesmejia.eocvsim.input.source.HttpSource +import com.github.serivesmejia.eocvsim.util.event.EventHandler import java.awt.BorderLayout import java.awt.FlowLayout import java.awt.GridBagConstraints @@ -33,16 +19,23 @@ import javax.swing.* import javax.swing.event.DocumentEvent import javax.swing.event.DocumentListener -class CreateHttpSource(parent: JFrame, private val eocvSim: EOCVSim) { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named + +class CreateHttpSource : KoinComponent { + + private val inputSourceManager: InputSourceManager by inject() + private val onMainLoop: EventHandler by inject(named("onMainLoop")) + private val visualizer: Visualizer by inject() + + private val dialog = JDialog(visualizer.frame) - private val dialog = JDialog(parent) private val nameTextField: JTextField private val urlField: JTextField private val createButton: JButton init { - eocvSim.visualizer.childDialogs.add(dialog) - // Panel for input fields val fieldsPanel = JPanel(GridBagLayout()).apply { border = BorderFactory.createEmptyBorder(0, 0, 10, 0) @@ -60,9 +53,9 @@ class CreateHttpSource(parent: JFrame, private val eocvSim: EOCVSim) { // Name field gbc.gridy = 1 gbc.gridx = 0 - add(JLabel("Source name: "), gbc) + add(JLabel("Source Name: "), gbc) gbc.gridx = 1 - val sourceCount = eocvSim.inputSourceManager.sources.size + 1 + val sourceCount = inputSourceManager.sources.size + 1 nameTextField = JTextField("HttpSource-$sourceCount", 15) add(nameTextField, gbc) } @@ -111,8 +104,8 @@ class CreateHttpSource(parent: JFrame, private val eocvSim: EOCVSim) { } private fun createSource(sourceName: String, url: String) { - eocvSim.onMainUpdate.once { - eocvSim.inputSourceManager.addInputSource( + onMainLoop.once { + inputSourceManager.addInputSource( sourceName, HttpSource(url), false @@ -121,8 +114,7 @@ class CreateHttpSource(parent: JFrame, private val eocvSim: EOCVSim) { } private fun updateCreateButton() { - val isNameValid = nameTextField.text.isNotBlank() && - !eocvSim.inputSourceManager.isNameOnUse(nameTextField.text) + val isNameValid = nameTextField.text.isNotBlank() && !inputSourceManager.isNameInUse(nameTextField.text) val isUrlValid = urlField.text.isNotBlank() createButton.isEnabled = isNameValid && isUrlValid } @@ -137,4 +129,4 @@ class CreateHttpSource(parent: JFrame, private val eocvSim: EOCVSim) { override fun removeUpdate(e: DocumentEvent?) = action() }) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateImageSource.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateImageSource.kt index 4a553522..0111b77d 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateImageSource.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateImageSource.kt @@ -1,34 +1,20 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.gui.dialog.source import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.Visualizer import com.github.serivesmejia.eocvsim.gui.component.input.FileSelector import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields +import com.github.serivesmejia.eocvsim.input.InputSourceManager import com.github.serivesmejia.eocvsim.input.source.ImageSource -import io.github.deltacv.vision.external.util.CvUtil +import org.deltacv.vision.external.util.CvUtil import com.github.serivesmejia.eocvsim.util.FileFilters import com.github.serivesmejia.eocvsim.util.StrUtil +import com.github.serivesmejia.eocvsim.util.event.EventHandler import org.opencv.core.Size import java.awt.* import java.io.File @@ -37,13 +23,19 @@ import javax.swing.event.DocumentEvent import javax.swing.event.DocumentListener import kotlin.math.roundToInt +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named + class CreateImageSource( - parent: JFrame, - private val eocvSim: EOCVSim, private val initialFile: File? -) { +) : KoinComponent { + + private val visualizer: Visualizer by inject() + private val inputSourceManager: InputSourceManager by inject() + private val onMainLoop: EventHandler by inject(named("onMainLoop")) - val createImageSource = JDialog(parent) + val createImageSource = JDialog(visualizer.frame) val nameTextField = JTextField() val sizeFieldsInput = SizeFields() @@ -53,7 +45,6 @@ class CreateImageSource( private var selectedValidImage = false init { - eocvSim.visualizer.childDialogs.add(createImageSource) initCreateImageSource() } @@ -82,9 +73,9 @@ class CreateImageSource( // Name part val namePanel = JPanel(FlowLayout()) - val nameLabel = JLabel("Source name: ").apply { horizontalAlignment = JLabel.LEFT } + val nameLabel = JLabel("Source Name: ").apply { horizontalAlignment = JLabel.LEFT } - nameTextField.text = "ImageSource-${eocvSim.inputSourceManager.sources.size + 1}" + nameTextField.text = "ImageSource-${inputSourceManager.sources.size + 1}" namePanel.add(nameLabel) namePanel.add(nameTextField) @@ -140,7 +131,7 @@ class CreateImageSource( if (CvUtil.checkImageValid(fileAbsPath)) { val fileName = StrUtil.getFileBaseName(f.name) if (fileName.isNotBlank()) { - nameTextField.text = eocvSim.inputSourceManager.tryName(fileName) + nameTextField.text = inputSourceManager.tryName(fileName) } val size = CvUtil.scaleToFit(CvUtil.getImageSize(fileAbsPath), EOCVSim.DEFAULT_EOCV_SIZE) @@ -162,8 +153,8 @@ class CreateImageSource( } private fun createSource(sourceName: String, imgPath: String, size: Size) { - eocvSim.onMainUpdate.once { - eocvSim.inputSourceManager.addInputSource( + onMainLoop.once { + inputSourceManager.addInputSource( sourceName, ImageSource(imgPath, size), false @@ -176,6 +167,7 @@ class CreateImageSource( nameTextField.text.trim().isNotEmpty() && sizeFieldsInput.valid && selectedValidImage && - !eocvSim.inputSourceManager.isNameOnUse(nameTextField.text) + !inputSourceManager.isNameInUse(nameTextField.text) } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSource.java deleted file mode 100644 index 1e811378..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSource.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog.source; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.DialogFactory; -import com.github.serivesmejia.eocvsim.input.SourceType; - -import javax.swing.*; -import java.awt.*; - -public class CreateSource { - - public static volatile boolean alreadyOpened = false; - public volatile JDialog chooseSource = null; - - EOCVSim eocvSim = null; - - private volatile JFrame parent = null; - - public CreateSource(JFrame parent, EOCVSim eocvSim) { - - chooseSource = new JDialog(parent); - - - this.parent = parent; - this.eocvSim = eocvSim; - - eocvSim.visualizer.childDialogs.add(chooseSource); - initChooseSource(); - - } - - private void initChooseSource() { - - alreadyOpened = true; - - chooseSource.setModal(false); - chooseSource.setFocusableWindowState(false); - chooseSource.setAlwaysOnTop(true); - - chooseSource.setTitle("Select source type"); - chooseSource.setSize(300, 150); - - JPanel contentsPane = new JPanel(new GridLayout(2, 1)); - - JPanel dropDownPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - - SourceType[] sourceTypes = SourceType.values(); - String[] sourceTypesStr = new String[sourceTypes.length - 1]; - - for (int i = 0; i < sourceTypes.length - 1; i++) { - sourceTypesStr[i] = sourceTypes[i].coolName; - } - - JComboBox dropDown = new JComboBox<>(sourceTypesStr); - dropDownPanel.add(dropDown); - contentsPane.add(dropDownPanel); - - JPanel buttonsPanel = new JPanel(new FlowLayout()); - JButton nextButton = new JButton("Next"); - - buttonsPanel.add(nextButton); - - JButton cancelButton = new JButton("Cancel"); - buttonsPanel.add(cancelButton); - - contentsPane.add(buttonsPanel); - - contentsPane.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); - - chooseSource.getContentPane().add(contentsPane, BorderLayout.CENTER); - - cancelButton.addActionListener(e -> close()); - - nextButton.addActionListener(e -> { - close(); - SourceType sourceType = SourceType.fromCoolName(dropDown.getSelectedItem().toString()); - DialogFactory.createSourceDialog(eocvSim, sourceType); - }); - - chooseSource.pack(); - chooseSource.setLocationRelativeTo(null); - chooseSource.setResizable(false); - chooseSource.setVisible(true); - } - - public void close() { - alreadyOpened = false; - chooseSource.setVisible(false); - chooseSource.dispose(); - } - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSourceEx.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSourceEx.kt index bf38b3be..a58e0673 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSourceEx.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSourceEx.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.dialog.source import com.github.serivesmejia.eocvsim.gui.DialogFactory @@ -41,12 +23,16 @@ import javax.swing.JFrame import javax.swing.JLabel import javax.swing.JPanel -class CreateSourceEx( - val parent: JFrame, - val visualizer: Visualizer -) { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class CreateSourceEx : KoinComponent { + + private val visualizer: Visualizer by inject() + private val dialogFactory: DialogFactory by inject() + + val dialog = JDialog(visualizer.frame) - val dialog = JDialog(parent) init { dialog.isModal = true @@ -67,7 +53,7 @@ class CreateSourceEx( addActionListener { dialog.dispose() - DialogFactory.createSourceDialog(visualizer.eocvSim, SourceType.CAMERA) + dialogFactory.createSourceDialog(SourceType.CAMERA) } }) @@ -81,7 +67,7 @@ class CreateSourceEx( addActionListener { dialog.dispose() - DialogFactory.createSourceDialog(visualizer.eocvSim, SourceType.IMAGE) + dialogFactory.createSourceDialog(SourceType.IMAGE) } }) @@ -95,7 +81,7 @@ class CreateSourceEx( addActionListener { dialog.dispose() - DialogFactory.createSourceDialog(visualizer.eocvSim, SourceType.VIDEO) + dialogFactory.createSourceDialog(SourceType.VIDEO) } }) @@ -109,7 +95,7 @@ class CreateSourceEx( addActionListener { dialog.dispose() - DialogFactory.createSourceDialog(visualizer.eocvSim, SourceType.HTTP) + dialogFactory.createSourceDialog(SourceType.HTTP) } }) @@ -131,4 +117,4 @@ class CreateSourceEx( dialog.isVisible = true } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateVideoSource.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateVideoSource.kt index 292882e5..9566e4af 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateVideoSource.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateVideoSource.kt @@ -1,33 +1,19 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ + package com.github.serivesmejia.eocvsim.gui.dialog.source import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.Visualizer import com.github.serivesmejia.eocvsim.gui.component.input.FileSelector import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields +import com.github.serivesmejia.eocvsim.input.InputSourceManager import com.github.serivesmejia.eocvsim.input.source.VideoSource import com.github.serivesmejia.eocvsim.util.FileFilters -import io.github.deltacv.vision.external.util.CvUtil +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.deltacv.vision.external.util.CvUtil import org.opencv.core.Size import java.awt.BorderLayout import java.awt.FlowLayout @@ -38,12 +24,20 @@ import javax.swing.event.DocumentEvent import javax.swing.event.DocumentListener import kotlin.math.roundToInt +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named + class CreateVideoSource( - parent: JFrame, - private val eocvsim: EOCVSim, - initialFile: File? = null -) { - private val dialog: JDialog = JDialog(parent) + private val initialFile: File? = null +) : KoinComponent { + + private val visualizer: Visualizer by inject() + private val inputSourceManager by inject() + private val onMainLoop: EventHandler by inject(named("onMainLoop")) + + private val dialog: JDialog = JDialog(visualizer.frame) + private var nameTextField: JTextField private var fileSelector: FileSelector @@ -53,8 +47,6 @@ class CreateVideoSource( private var selectedValidVideo = false init { - eocvsim.visualizer.childDialogs.add(dialog) - // Main content panel val contentsPanel = JPanel(GridLayout(4, 1)).apply { border = BorderFactory.createEmptyBorder(15, 0, 0, 0) @@ -79,9 +71,9 @@ class CreateVideoSource( // Name input panel val namePanel = JPanel(FlowLayout()).apply { - val sourceCount = eocvsim.inputSourceManager.sources.size + 1 + val sourceCount = inputSourceManager.sources.size + 1 nameTextField = JTextField("VideoSource-$sourceCount", 15) - add(JLabel("Source name: ")) + add(JLabel("Source Name: ")) add(nameTextField) } contentsPanel.add(namePanel) @@ -122,7 +114,7 @@ class CreateVideoSource( // Suggest a name from the filename if it's not blank val fileName = file.nameWithoutExtension if (fileName.isNotBlank()) { - nameTextField.text = eocvsim.inputSourceManager.tryName(fileName) + nameTextField.text = inputSourceManager.tryName(fileName) } // Calculate a fitted size and update the fields @@ -148,8 +140,8 @@ class CreateVideoSource( } private fun createSource(sourceName: String, videoPath: String, size: Size) { - eocvsim.onMainUpdate.once { - eocvsim.inputSourceManager.addInputSource( + onMainLoop.once { + inputSourceManager.addInputSource( sourceName, VideoSource(videoPath, size), true @@ -159,7 +151,7 @@ class CreateVideoSource( private fun updateCreateButton() { val isNameValid = nameTextField.text.isNotBlank() && - !eocvsim.inputSourceManager.isNameOnUse(nameTextField.text) + !inputSourceManager.isNameInUse(nameTextField.text) createButton.isEnabled = isNameValid && sizeFields.valid && selectedValidVideo } @@ -174,4 +166,4 @@ class CreateVideoSource( override fun removeUpdate(e: DocumentEvent?) = action() }) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/Theme.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/Theme.java index 1b061492..3835e92f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/Theme.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/Theme.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.theme; import com.formdev.flatlaf.*; @@ -57,4 +39,4 @@ public enum Theme { public void install() throws ClassNotFoundException, UnsupportedLookAndFeelException, InstantiationException, IllegalAccessException { installRunn.install(); } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/ThemeInstaller.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/ThemeInstaller.java index 0c0e9d82..96cfe574 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/ThemeInstaller.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/ThemeInstaller.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.theme; import javax.swing.*; @@ -28,3 +10,4 @@ public interface ThemeInstaller { void install() throws ClassNotFoundException, UnsupportedLookAndFeelException, InstantiationException, IllegalAccessException; } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/GuiUtil.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/GuiUtil.java deleted file mode 100644 index 73ea2b3b..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/GuiUtil.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.gui.util; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.DialogFactory; -import com.github.serivesmejia.eocvsim.gui.dialog.FileAlreadyExists; -import io.github.deltacv.vision.external.util.CvUtil; -import com.github.serivesmejia.eocvsim.util.SysUtil; -import org.opencv.core.Mat; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.imageio.ImageIO; -import javax.swing.*; -import javax.swing.filechooser.FileNameExtensionFilter; -import javax.swing.text.AbstractDocument; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.DocumentFilter; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class GuiUtil { - - static Logger logger = LoggerFactory.getLogger(GuiUtil.class); - - public static void jTextFieldOnlyNumbers(JTextField field, int minNumber, int onMinNumberChangeTo) { - - ((AbstractDocument) field.getDocument()).setDocumentFilter(new DocumentFilter() { - final Pattern regEx = Pattern.compile("\\d*"); - - @Override - public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { - Matcher matcher = regEx.matcher(text); - if (!matcher.matches()) { - return; - } - - if (field.getText().length() == 0) { - try { - int number = Integer.parseInt(text); - if (number <= minNumber) { - text = String.valueOf(onMinNumberChangeTo); - } - } catch (NumberFormatException ex) { - } - } - - super.replace(fb, offset, length, text, attrs); - } - }); - - } - - public static ImageIcon loadImageIcon(String path) throws IOException { - return new ImageIcon(loadBufferedImage(path)); - } - - public static BufferedImage loadBufferedImage(String path) throws IOException { - return ImageIO.read(GuiUtil.class.getResourceAsStream(path)); - } - - public static void saveBufferedImage(File file, BufferedImage bufferedImage, String format) throws IOException { - ImageIO.write(bufferedImage, format, file); - } - - public static void saveBufferedImage(File file, BufferedImage bufferedImage) throws IOException { - saveBufferedImage(file, bufferedImage, "jpg"); - } - - public static void catchSaveBufferedImage(File file, BufferedImage bufferedImage, String format) { - try { - saveBufferedImage(file, bufferedImage, format); - } catch (IOException e) { - logger.error("Failed to save buffered image", e); - } - } - - public static void catchSaveBufferedImage(File file, BufferedImage bufferedImage) { - catchSaveBufferedImage(file, bufferedImage, "jpg"); - } - - public static void invertBufferedImageColors(BufferedImage input) { - - for (int x = 0; x < input.getWidth(); x++) { - for (int y = 0; y < input.getHeight(); y++) { - - int rgba = input.getRGB(x, y); - Color col = new Color(rgba, true); - - if (col.getAlpha() <= 0) continue; - - col = new Color(255 - col.getRed(), - 255 - col.getGreen(), - 255 - col.getBlue()); - - input.setRGB(x, y, col.getRGB()); - - } - } - - } - - public static void saveBufferedImageFileChooser(Component parent, BufferedImage bufferedImage, EOCVSim eocvSim) { - - FileNameExtensionFilter jpegFilter = new FileNameExtensionFilter("JPEG (*.jpg)", "jpg", "jpeg"); - FileNameExtensionFilter pngFilter = new FileNameExtensionFilter("PNG (*.png)", "png"); - - String[] validExts = {"jpg", "jpeg", "png"}; - - DialogFactory.createFileChooser(parent, DialogFactory.FileChooser.Mode.SAVE_FILE_SELECT, jpegFilter, pngFilter) - - .addCloseListener((MODE, selectedFile, selectedFileFilter) -> { - if (MODE == JFileChooser.APPROVE_OPTION) { - - Optional extension = SysUtil.getExtensionByStringHandling(selectedFile.getName()); - - boolean saveImage; - - if (!selectedFile.exists()) { - saveImage = true; - } else { - FileAlreadyExists.UserChoice userChoice = DialogFactory.createFileAlreadyExistsDialog(eocvSim); //create confirm dialog - saveImage = userChoice == FileAlreadyExists.UserChoice.REPLACE; - } - - String ext = ""; - - if (saveImage) { - if (selectedFileFilter instanceof FileNameExtensionFilter) { //if user selected an extension - - //get selected extension - ext = ((FileNameExtensionFilter) selectedFileFilter).getExtensions()[0]; - - selectedFile = new File(selectedFile + "." + ext); //append extension to file - catchSaveBufferedImage(selectedFile, bufferedImage, ext); - - } else if (extension.isPresent() && Arrays.asList(validExts).contains(extension.get())) { //if user gave a extension to file and it's valid (jpg, jpeg or png) - - ext = extension.get(); //get the extension - catchSaveBufferedImage(selectedFile, bufferedImage, ext); - - } else { //default to jpg if the conditions are not met - - selectedFile = new File(selectedFile + ".jpg"); //append default extension to file - catchSaveBufferedImage(selectedFile, bufferedImage); - - } - } - - } - }); - - } - - public static void saveMatFileChooser(Component parent, Mat mat, EOCVSim eocvSim) { - Mat clonedMat = mat.clone(); - - BufferedImage img = CvUtil.matToBufferedImage(clonedMat); - clonedMat.release(); - - saveBufferedImageFileChooser(parent, img, eocvSim); - } - - public static ListModel isToListModel(InputStream is, Charset charset) throws UnsupportedEncodingException { - - DefaultListModel listModel = new DefaultListModel<>(); - String isStr = SysUtil.loadIsStr(is, charset); - - String[] lines = isStr.split("\n"); - - for(int i = 0 ; i < lines.length ; i++) { - listModel.add(i, lines[i]); - } - - return listModel; - - } - - public static class NoSelectionModel extends DefaultListSelectionModel { - - @Override - public void setAnchorSelectionIndex(final int anchorIndex) {} - - @Override - public void setLeadAnchorNotificationEnabled(final boolean flag) {} - - @Override - public void setLeadSelectionIndex(final int leadIndex) {} - - @Override - public void setSelectionInterval(final int index0, final int index1) { } - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/GuiUtil.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/GuiUtil.kt new file mode 100644 index 00000000..0b182038 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/GuiUtil.kt @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.gui.util + +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.gui.dialog.FileAlreadyExists +import com.github.serivesmejia.eocvsim.util.SysUtil +import org.deltacv.common.util.loggerForThis +import org.deltacv.vision.external.util.CvUtil +import org.opencv.core.Mat +import org.slf4j.LoggerFactory +import java.awt.Color +import java.awt.Component +import java.awt.image.BufferedImage +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.nio.charset.Charset +import javax.imageio.ImageIO +import javax.swing.* +import javax.swing.filechooser.FileNameExtensionFilter + +object GuiUtil { + + private val logger = LoggerFactory.getLogger(GuiUtil::class.java) + + @JvmStatic + fun loadBufferedImage(path: String): BufferedImage { + return ImageIO.read(javaClass.getResourceAsStream(path) ?: throw IOException("Resource not found: $path")) + } + + @JvmStatic + fun saveBufferedImage(file: File, bufferedImage: BufferedImage, format: String = "jpg") { + ImageIO.write(bufferedImage, format, file) + } + + @JvmStatic + fun catchSaveBufferedImage(file: File, bufferedImage: BufferedImage, format: String = "jpg") { + try { + saveBufferedImage(file, bufferedImage, format) + } catch (e: IOException) { + logger.error("Failed to save buffered image", e) + } + } + + @JvmStatic + fun invertBufferedImageColors(input: BufferedImage) { + for (x in 0 until input.width) { + for (y in 0 until input.height) { + val rgba = input.getRGB(x, y) + var col = Color(rgba, true) + + if (col.alpha <= 0) continue + + col = Color( + 255 - col.red, + 255 - col.green, + 255 - col.blue + ) + + input.setRGB(x, y, col.rgb) + } + } + } + + @JvmStatic + fun saveBufferedImageFileChooser(parent: Component?, bufferedImage: BufferedImage, dialogFactory: DialogFactory) { + val jpegFilter = FileNameExtensionFilter("JPEG (*.jpg)", "jpg", "jpeg") + val pngFilter = FileNameExtensionFilter("PNG (*.png)", "png") + + val validExts = arrayOf("jpg", "jpeg", "png") + + dialogFactory.createFileChooser(parent, DialogFactory.FileChooser.Mode.SAVE_FILE_SELECT, jpegFilter, pngFilter) + .addCloseListener { mode, selectedFile, selectedFileFilter -> + if (mode == JFileChooser.APPROVE_OPTION && selectedFile != null) { + val extension = SysUtil.getExtensionByStringHandling(selectedFile.name) + + var saveImage: Boolean + + if (!selectedFile.exists()) { + saveImage = true + } else { + val userChoice = dialogFactory.createFileAlreadyExistsDialog() + saveImage = userChoice == FileAlreadyExists.UserChoice.REPLACE + } + + if (saveImage) { + var fileToSave = selectedFile + var ext = "" + + if (selectedFileFilter is FileNameExtensionFilter) { + ext = selectedFileFilter.extensions[0] + fileToSave = File(selectedFile.absolutePath + "." + ext) + catchSaveBufferedImage(fileToSave, bufferedImage, ext) + } else if (extension.isPresent && validExts.contains(extension.get())) { + ext = extension.get() + catchSaveBufferedImage(fileToSave, bufferedImage, ext) + } else { + fileToSave = File(selectedFile.absolutePath + ".jpg") + catchSaveBufferedImage(fileToSave, bufferedImage) + } + } + } + } + } + + @JvmStatic + fun saveMatFileChooser(parent: Component?, mat: Mat, dialogFactory: DialogFactory) { + val clonedMat = mat.clone() + val img = CvUtil.matToBufferedImage(clonedMat) + clonedMat.release() + + saveBufferedImageFileChooser(parent, img, dialogFactory) + } + + class NoSelectionModel : DefaultListSelectionModel() { + override fun setAnchorSelectionIndex(anchorIndex: Int) {} + override fun setLeadAnchorNotificationEnabled(flag: Boolean) {} + override fun setLeadSelectionIndex(leadIndex: Int) {} + override fun setSelectionInterval(index0: Int, index1: Int) {} + } + + @JvmStatic + fun isToListModel(inputStream: InputStream, charset: Charset): ListModel { + val listModel = DefaultListModel() + val isStr = SysUtil.loadIsStr(inputStream, charset) + val lines = isStr.split("\n").toTypedArray() + + for (i in lines.indices) { + listModel.add(i, lines[i]) + } + + return listModel + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ThreadedMatPoster.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ThreadedMatPoster.java index 3c0e7327..5f83f825 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ThreadedMatPoster.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ThreadedMatPoster.java @@ -1,30 +1,12 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.util; import com.github.serivesmejia.eocvsim.util.fps.FpsCounter; -import io.github.deltacv.common.image.MatPoster; +import org.deltacv.common.image.MatPoster; import org.firstinspires.ftc.robotcore.internal.collections.EvictingBlockingQueue; import org.opencv.core.Mat; import org.openftc.easyopencv.MatRecycler; @@ -220,4 +202,4 @@ public void run() { } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/WebcamDriver.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/WebcamDriver.kt index 7ea6591a..62c6b811 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/WebcamDriver.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/WebcamDriver.kt @@ -1,28 +1,10 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.util enum class WebcamDriver { OpenPnp, OpenIMAJ, OpenCV -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/PipelineListIconRenderer.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/PipelineListIconRenderer.kt index 520fe0b6..21bb1fb6 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/PipelineListIconRenderer.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/PipelineListIconRenderer.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.util.icon import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary @@ -64,3 +46,4 @@ class PipelineListIconRenderer( } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/SourcesListIconRenderer.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/SourcesListIconRenderer.java index 1b408eff..4b8e23c2 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/SourcesListIconRenderer.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/SourcesListIconRenderer.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.gui.util.icon; import com.github.serivesmejia.eocvsim.gui.EOCVSimIconLibrary; @@ -73,3 +55,4 @@ public Component getListCellRendererComponent( } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSource.java deleted file mode 100644 index 1a320bd6..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSource.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.input; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.google.gson.annotations.Expose; -import org.opencv.core.Mat; -import org.opencv.core.Size; - -import javax.swing.filechooser.FileFilter; - -public abstract class InputSource implements Comparable { - - public transient boolean isDefault; - public transient EOCVSim eocvSim; - - protected transient String name = ""; - protected transient boolean isPaused; - private transient boolean beforeIsPaused; - - @Expose - protected long createdOn = -1L; - - public abstract boolean init(); - public abstract void reset(); - - public void cleanIfDirty() { } - - public abstract void close(); - - public abstract void onPause(); - public abstract void onResume(); - - public void setSize(Size size) {} - - public Size getSize() { return new Size(); } - - public Mat update() { - return null; - } - - public final InputSource cloneSource() { - final InputSource source = internalCloneSource(); - source.createdOn = createdOn; - return source; - } - - protected abstract InputSource internalCloneSource(); - - public final void setPaused(boolean paused) { - - isPaused = paused; - - if (beforeIsPaused != isPaused) { - if (isPaused) { - onPause(); - } else { - onResume(); - } - } - - beforeIsPaused = paused; - - } - - public boolean getPaused() { - return isPaused; - } - - public final String getName() { - return name; - } - - public abstract FileFilter getFileFilters(); - - public abstract long getCaptureTimeNanos(); - - public long getCreationTime() { - return createdOn; - } - - @Override - public final int compareTo(InputSource source) { - return createdOn > source.createdOn ? 1 : -1; - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSource.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSource.kt new file mode 100644 index 00000000..42e5beb0 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSource.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.input + +import com.fasterxml.jackson.annotation.JsonAutoDetect +import com.fasterxml.jackson.annotation.JsonProperty +import org.koin.core.component.KoinComponent +import org.opencv.core.Mat +import org.opencv.core.Size +import javax.swing.filechooser.FileFilter + +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.NONE, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE, + creatorVisibility = JsonAutoDetect.Visibility.NONE +) +abstract class InputSource : Comparable, KoinComponent { + + // Computed property (no backing field) so Jackson field-based persistence only + // stores the actual source state and does not capture derived values. + open val hasSlowInitialization: Boolean get() = false + + @Transient var isDefault = false + + @Transient open var name: String = "" + + @Transient private var beforeIsPaused = false + @Transient @set:JvmName("setPausedValue") var isPaused = false + set(value) { + field = value + if (beforeIsPaused != value) { + if (value) onPause() else onResume() + } + beforeIsPaused = value + } + + @JsonProperty + @JvmField + var createdOn = -1L + + abstract fun init(): Boolean + abstract fun reset() + + open fun cleanIfDirty() {} + + abstract fun close() + + abstract fun onPause() + abstract fun onResume() + + abstract val sourceSize: Size + + open fun update(): Mat? = null + + fun cloneSource(): InputSource { + val source = internalCloneSource() + source.createdOn = createdOn + return source + } + + protected abstract fun internalCloneSource(): InputSource + + abstract val fileFilters: FileFilter? + abstract val captureTimeNanos: Long + + val creationTime: Long get() = createdOn + + override fun compareTo(other: InputSource): Int { + return if (createdOn > other.createdOn) 1 else -1 + } + +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceInitializer.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceInitializer.kt index 6e5af4c8..a80d12bc 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceInitializer.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceInitializer.kt @@ -1,79 +1,173 @@ -package com.github.serivesmejia.eocvsim.input - -import io.github.deltacv.common.util.loggerForThis -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout - -object InputSourceInitializer { - - const val TIMEOUT = 10000L - - val logger by loggerForThis() - - @OptIn(DelicateCoroutinesApi::class) - fun initializeWithTimeout(inputSource: InputSource, manager: InputSourceManager? = null): Boolean { - var result = false - - val job = GlobalScope.launch { - try { - result = inputSource.init() - } catch (e: Exception) { - logger.error("Error initializing InputSource", e) - } - } - - val dialog = manager?.showApwdIfNeeded(inputSource.name, job) - - runBlocking { - try { - withTimeout(TIMEOUT) { - job.join() - } - } catch (e: CancellationException) { - logger.error("InputSource initialization timed out after $TIMEOUT ms", e) - } finally { - job.cancel() - dialog?.destroyDialog() - } - } - - return result - } - - - @OptIn(DelicateCoroutinesApi::class) - @JvmOverloads - fun runWithTimeout(sourceName: String, manager: InputSourceManager? = null, callback: () -> Boolean): Boolean { - var result = false - - val job = GlobalScope.launch { - try { - result = callback() - } catch (e: Exception) { - logger.error("Error running InputSource", e) - } - } - - val dialog = manager?.showApwdIfNeeded(sourceName, job) - - runBlocking { - try { - withTimeout(TIMEOUT) { - job.join() - } - } catch (e: CancellationException) { - logger.error("InputSource run timed out after $TIMEOUT ms", e) - } finally { - job.cancel() - dialog?.destroyDialog() - } - } - - return result - } - -} \ No newline at end of file +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.input + +import com.github.serivesmejia.eocvsim.util.event.ParamEventHandler +import org.deltacv.common.util.loggerForThis +import kotlinx.coroutines.* +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.context.GlobalContext +import java.util.concurrent.atomic.AtomicInteger + +class InputSourceInitializer : KoinComponent { + + enum class Result { SUCCESS, FAILED, CANCELED, TIMED_OUT } + + companion object { + const val TIMEOUT = 10000L + + fun runWithTimeout(sourceName: String, callback: () -> Boolean): Result { + val initializer = GlobalContext.get().get() + return initializer.runWithTimeout(sourceName, false, callback) + } + + fun runWithTimeout(inputSource: InputSource, callback: () -> Boolean): Result { + val initializer = GlobalContext.get().get() + return initializer.runWithTimeout(inputSource.name, inputSource.hasSlowInitialization, callback) + } + + fun initializeWithTimeout(inputSource: InputSource): Result { + val initializer = GlobalContext.get().get() + return initializer.initializeWithTimeout(inputSource) + } + } + + private val scope: CoroutineScope by inject() + + val logger by loggerForThis() + + // Event fired when an initialization that may need UI interaction starts. + // Listeners will receive the InitSession payload directly. + val onInitBegin = ParamEventHandler("InputSourceInitBegin") + + private val sessionIdCounter = AtomicInteger(0) + data class InitSession( + val id: Int, + val inputSource: InputSource?, + val sourceName: String?, + val cancelJob: Job, + val resultSignal: CompletableDeferred, + val hasSlowInitialization: Boolean = false + ) + + fun initializeWithTimeout(inputSource: InputSource): Result { + val resultSignal = CompletableDeferred() + val cancelSignal = Job().apply { + invokeOnCompletion { + if (!resultSignal.isCompleted) { + resultSignal.complete(Result.CANCELED) + } + } + } + + val sessionId = sessionIdCounter.getAndIncrement() + val session = InitSession(sessionId, inputSource, inputSource.name, cancelSignal, resultSignal, inputSource.hasSlowInitialization) + + scope.launch { + try { + val initialized = inputSource.init() + + if (cancelSignal.isCancelled) { + runCatching { inputSource.close() } + .onFailure { logger.error("Error while closing canceled InputSource", it) } + + if (!resultSignal.isCompleted) { + resultSignal.complete(Result.CANCELED) + } + return@launch + } + + resultSignal.complete(if (initialized) Result.SUCCESS else Result.FAILED) + } catch (e: Exception) { + logger.error("Error initializing InputSource", e) + + if (cancelSignal.isCancelled) { + runCatching { inputSource.close() } + .onFailure { logger.error("Error while closing canceled InputSource", it) } + + if (!resultSignal.isCompleted) { + resultSignal.complete(Result.CANCELED) + } + } else { + resultSignal.complete(Result.FAILED) + } + } + } + + onInitBegin.run(session) + + val result = runBlocking { + try { + withTimeout(TIMEOUT) { resultSignal.await() } + } catch (_: TimeoutCancellationException) { + Result.TIMED_OUT + } finally { + cancelSignal.cancel() + } + } + + return result + } + + @OptIn(DelicateCoroutinesApi::class) + @JvmOverloads + fun runWithTimeout(sourceName: String, showDialog: Boolean = false, callback: () -> Boolean): Result { + val resultSignal = CompletableDeferred() + val cancelSignal = Job().apply { + invokeOnCompletion { + if (!resultSignal.isCompleted) { + resultSignal.complete(Result.CANCELED) + } + } + } + + val sessionId = sessionIdCounter.getAndIncrement() + val session = InitSession(sessionId, null, sourceName, cancelSignal, resultSignal, showDialog) + + scope.launch { + try { + val ran = callback() + + if (cancelSignal.isCancelled) { + if (!resultSignal.isCompleted) { + resultSignal.complete(Result.CANCELED) + } + return@launch + } + + resultSignal.complete(if (ran) Result.SUCCESS else Result.FAILED) + } catch (e: Exception) { + logger.error("Error running InputSource", e) + + if (cancelSignal.isCancelled) { + if (!resultSignal.isCompleted) { + resultSignal.complete(Result.CANCELED) + } + } else { + resultSignal.complete(Result.FAILED) + } + } + } + + // always notify, listeners decide if they want to show UI + onInitBegin.run(session) + + val result = runBlocking { + try { + withTimeout(TIMEOUT) { resultSignal.await() } + } catch (_: TimeoutCancellationException) { + Result.TIMED_OUT + } finally { + cancelSignal.cancel() + } + } + + return result + } + + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceLoader.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceLoader.java deleted file mode 100644 index 9db0bff8..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceLoader.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.input; - -import com.github.serivesmejia.eocvsim.input.source.*; -import com.github.serivesmejia.eocvsim.util.SysUtil; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.annotations.Expose; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -public class InputSourceLoader { - - Logger logger = LoggerFactory.getLogger(getClass()); - - public static final Gson gson = new GsonBuilder() - .registerTypeAdapter(CameraSource.class, new CameraSourceAdapter()) - .setPrettyPrinting() - .create(); - - public static final String SOURCES_SAVEFILE_NAME = "eocvsim_sources.json"; - - public static final File SOURCES_SAVEFILE = new File(SysUtil.getEOCVSimFolder() + File.separator + SOURCES_SAVEFILE_NAME); - public static final File SOURCES_SAVEFILE_OLD = new File(SysUtil.getAppData() + File.separator + SOURCES_SAVEFILE_NAME); - - public static final InputSourcesContainer.SourcesFileVersion CURRENT_FILE_VERSION = InputSourcesContainer.SourcesFileVersion.SEIS; - - public HashMap loadedInputSources = new HashMap<>(); - - public InputSourcesContainer.SourcesFileVersion fileVersion = null; - - public void saveInputSource(String name, InputSource source) { - loadedInputSources.put(name, source); - } - - public void deleteInputSource(String name) { - loadedInputSources.remove(name); - } - - public void saveInputSourcesToFile() { - saveInputSourcesToFile(SOURCES_SAVEFILE); - } - - public void saveInputSourcesToFile(File f) { - InputSourcesContainer sourcesContainer = new InputSourcesContainer(); - - //updates file version to most recent since it will be regenerated at this point - if(fileVersion != null) - sourcesContainer.sourcesFileVersion = fileVersion.ordinal() < CURRENT_FILE_VERSION.ordinal() - ? CURRENT_FILE_VERSION : fileVersion; - - for (Map.Entry entry : loadedInputSources.entrySet()) { - if (!entry.getValue().isDefault) { - InputSource source = entry.getValue().cloneSource(); - sourcesContainer.classifySource(entry.getKey(), source); - } - } - - saveInputSourcesToFile(f, sourcesContainer); - } - - public void saveInputSourcesToFile(File file, InputSourcesContainer sourcesContainer) { - String jsonInputSources = gson.toJson(sourcesContainer); - SysUtil.saveFileStr(file, jsonInputSources); - } - - public void saveInputSourcesToFile(InputSourcesContainer sourcesContainer) { - saveInputSourcesToFile(SOURCES_SAVEFILE, sourcesContainer); - } - - public void loadInputSourcesFromFile() { - SysUtil.migrateFile(SOURCES_SAVEFILE_OLD, SOURCES_SAVEFILE); - loadInputSourcesFromFile(SOURCES_SAVEFILE); - } - - public void loadInputSourcesFromFile(File f) { - - if (!f.exists()) return; - - String jsonSources = SysUtil.loadFileStr(f); - if (jsonSources.trim().equals("")) return; - - InputSourcesContainer sources; - - try { - sources = gson.fromJson(jsonSources, InputSourcesContainer.class); - } catch (Exception ex) { - logger.error("Error while parsing sources file, it will be replaced and fixed later on, but the user created sources will be deleted.", ex); - return; - } - - sources.updateAllSources(); - fileVersion = sources.sourcesFileVersion; - - saveInputSourcesToFile(sources); //to make sure version gets declared in case it was an older file - - logger.info("InputSources file version is " + sources.sourcesFileVersion); - - loadedInputSources = sources.allSources; - - } - - static class InputSourcesContainer { - - public transient HashMap allSources = new HashMap<>(); - - public HashMap imageSources = new HashMap<>(); - public HashMap cameraSources = new HashMap<>(); - public HashMap videoSources = new HashMap<>(); - public HashMap httpSources = new HashMap<>(); - - @Expose - public SourcesFileVersion sourcesFileVersion = null; - - public enum SourcesFileVersion { DOS, SEIS, SIETE } - - public void updateAllSources() { - if (sourcesFileVersion == null) sourcesFileVersion = SourcesFileVersion.DOS; - - allSources.clear(); - - allSources.putAll(imageSources); - allSources.putAll(cameraSources); - allSources.putAll(httpSources); - - //check if file version is bigger than DOS, we should have video sources section - //declared in any file with a version greater than that - if (sourcesFileVersion.ordinal() >= 1) { - allSources.putAll(videoSources); - } - } - - public void classifySource(String sourceName, InputSource source) { - switch (SourceType.fromClass(source.getClass())) { - case IMAGE: - imageSources.put(sourceName, (ImageSource) source); - break; - case CAMERA: - cameraSources.put(sourceName, (CameraSource) source); - break; - case VIDEO: - videoSources.put(sourceName, (VideoSource) source); - break; - case HTTP: - httpSources.put(sourceName, (HttpSource) source); - break; - } - } - - } - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceLoader.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceLoader.kt new file mode 100644 index 00000000..33cce197 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceLoader.kt @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.input + +import com.fasterxml.jackson.annotation.JsonAutoDetect +import com.fasterxml.jackson.annotation.JsonProperty +import com.github.serivesmejia.eocvsim.input.source.* +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.serialization.JacksonJsonSupport +import org.slf4j.LoggerFactory +import java.io.File + +class InputSourceLoader { + + private val logger = LoggerFactory.getLogger(javaClass) + + companion object { + const val SOURCES_SAVEFILE_NAME = "eocvsim_sources.json" + + @JvmStatic + val SOURCES_SAVEFILE = File(SysUtil.getEOCVSimFolder().toString() + File.separator + SOURCES_SAVEFILE_NAME) + @JvmStatic + val SOURCES_SAVEFILE_OLD = File(SysUtil.getAppData().toString() + File.separator + SOURCES_SAVEFILE_NAME) + + @JvmStatic + val CURRENT_FILE_VERSION = InputSourcesContainer.SourcesFileVersion.SEIS + } + + var loadedInputSources = HashMap() + + var fileVersion: InputSourcesContainer.SourcesFileVersion? = null + + fun saveInputSource(name: String, source: InputSource) { + loadedInputSources[name] = source + } + + fun deleteInputSource(name: String) { + loadedInputSources.remove(name) + } + + fun saveInputSourcesToFile() { + saveInputSourcesToFile(SOURCES_SAVEFILE) + } + + fun saveInputSourcesToFile(f: File) { + val sourcesContainer = InputSourcesContainer() + + // updates file version to most recent since it will be regenerated at this point + fileVersion?.let { + sourcesContainer.sourcesFileVersion = if (it.ordinal < CURRENT_FILE_VERSION.ordinal) + CURRENT_FILE_VERSION else it + } + + for ((key, value) in loadedInputSources) { + if (!value.isDefault) { + val source = value.cloneSource() + sourcesContainer.classifySource(key, source) + } + } + + saveInputSourcesToFile(f, sourcesContainer) + } + + fun saveInputSourcesToFile(file: File, sourcesContainer: InputSourcesContainer) { + val jsonInputSources = JacksonJsonSupport.persistenceMapper.writeValueAsString(sourcesContainer) + SysUtil.saveFileStr(file, jsonInputSources) + } + + fun saveInputSourcesToFile(sourcesContainer: InputSourcesContainer) { + saveInputSourcesToFile(SOURCES_SAVEFILE, sourcesContainer) + } + + fun loadInputSourcesFromFile() { + SysUtil.migrateFile(SOURCES_SAVEFILE_OLD, SOURCES_SAVEFILE) + loadInputSourcesFromFile(SOURCES_SAVEFILE) + } + + fun loadInputSourcesFromFile(f: File) { + if (!f.exists()) return + + val jsonSources = SysUtil.loadFileStr(f) + if (jsonSources.trim() == "") return + + val sources: InputSourcesContainer + try { + sources = JacksonJsonSupport.persistenceMapper.readValue(jsonSources, InputSourcesContainer::class.java) + } catch (ex: Exception) { + logger.error("Error while parsing sources file, it will be replaced and fixed later on, but the user created sources will be deleted.", ex) + return + } + + sources.updateAllSources() + fileVersion = sources.sourcesFileVersion + + saveInputSourcesToFile(sources) // to make sure version gets declared in case it was an older file + + logger.info("InputSources file version is ${sources.sourcesFileVersion}") + + loadedInputSources = sources.allSources + } + + @JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.NONE, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE, + creatorVisibility = JsonAutoDetect.Visibility.NONE + ) + class InputSourcesContainer { + + @Transient var allSources = HashMap() + + @JsonProperty var imageSources = HashMap() + @JsonProperty var cameraSources = HashMap() + @JsonProperty var videoSources = HashMap() + @JsonProperty var httpSources = HashMap() + + @JsonProperty var sourcesFileVersion: SourcesFileVersion? = null + + enum class SourcesFileVersion { DOS, SEIS, SIETE } + + fun updateAllSources() { + if (sourcesFileVersion == null) sourcesFileVersion = SourcesFileVersion.DOS + + allSources.clear() + + allSources.putAll(imageSources) + allSources.putAll(cameraSources) + allSources.putAll(httpSources) + + // check if file version is bigger than DOS, we should have video sources section + // declared in any file with a version greater than that + if (sourcesFileVersion!!.ordinal >= 1) { + allSources.putAll(videoSources) + } + } + + fun classifySource(sourceName: String, source: InputSource) { + when (SourceType.fromClass(source.javaClass)) { + SourceType.IMAGE -> imageSources[sourceName] = source as ImageSource + SourceType.CAMERA -> cameraSources[sourceName] = source as CameraSource + SourceType.VIDEO -> videoSources[sourceName] = source as VideoSource + SourceType.HTTP -> httpSources[sourceName] = source as HttpSource + else -> {} + } + } + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceManager.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceManager.java deleted file mode 100644 index e9889b4b..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceManager.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.input; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.Visualizer; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.SourceSelectorPanel; -import com.github.serivesmejia.eocvsim.input.source.ImageSource; -import com.github.serivesmejia.eocvsim.input.source.NullSource; -import com.github.serivesmejia.eocvsim.pipeline.PipelineManager; -import com.github.serivesmejia.eocvsim.util.SysUtil; -import kotlinx.coroutines.Job; -import org.opencv.core.Mat; -import org.opencv.core.Scalar; -import org.opencv.core.Size; -import org.opencv.imgproc.Imgproc; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.swing.*; -import java.awt.*; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; -import java.util.concurrent.CancellationException; - -public class InputSourceManager { - - private static final Scalar BLACK = new Scalar(0, 0, 0, 255); - - private final EOCVSim eocvSim; - - public volatile Mat lastMatFromSource = null; - public volatile InputSource currentInputSource = null; - - public volatile HashMap sources = new HashMap<>(); - - public InputSourceLoader inputSourceLoader = new InputSourceLoader(); - public SourceSelectorPanel selectorPanel; - - private String defaultSource = ""; - - Logger logger = LoggerFactory.getLogger(getClass()); - - public InputSourceManager(EOCVSim eocvSim) { - this.eocvSim = eocvSim; - selectorPanel = eocvSim.visualizer.sourceSelectorPanel; - } - - public void init() { - logger.info("Initializing..."); - - if(lastMatFromSource == null) - lastMatFromSource = new Mat(); - - Size size = new Size(640, 480); - - createDefaultImgInputSource("/images/ug_4.jpg", "ug_eocvsim_4.jpg", "Ultimate Goal 4 Ring", size); - createDefaultImgInputSource("/images/ug_1.jpg", "ug_eocvsim_1.jpg", "Ultimate Goal 1 Ring", size); - createDefaultImgInputSource("/images/ug_0.jpg", "ug_eocvsim_0.jpg", "Ultimate Goal 0 Ring", size); - - if(sources.isEmpty()) { - logger.warn("No input sources found, creating default null source"); - - NullSource nullSource = new NullSource(); - nullSource.isDefault = true; - - addInputSource("Default", nullSource); - } else { - setInputSource("Ultimate Goal 4 Ring", true); - } - - inputSourceLoader.loadInputSourcesFromFile(); - - for (Map.Entry entry : inputSourceLoader.loadedInputSources.entrySet()) { - logger.info("Loaded input source " + entry.getKey()); - addInputSource(entry.getKey(), entry.getValue()); - } - } - - private void createDefaultImgInputSource(String resourcePath, String fileName, String sourceName, Size imgSize) { - try { - InputStream is = InputSource.class.getResourceAsStream(resourcePath); - File f = SysUtil.copyFileIsTemp(is, fileName, true).file; - - ImageSource src = new ImageSource(f.getAbsolutePath(), imgSize); - src.isDefault = true; - src.createdOn = sources.size(); - - addInputSource(sourceName, src); - } catch (IOException e) { - logger.error("Error while creating default image input source", e); - } - } - - public void update(boolean isPaused) { - if(currentInputSource == null) return; - - try { - currentInputSource.setPaused(isPaused); - - Mat m = currentInputSource.update(); - - if(m != null && !m.empty()) { - lastMatFromSource.setTo(BLACK); // clear previous mat - m.copyTo(lastMatFromSource); - // add an extra alpha channel because that's what eocv returns for some reason... (more realistic simulation lol) - Imgproc.cvtColor(lastMatFromSource, lastMatFromSource, Imgproc.COLOR_RGB2RGBA); - } - } catch(Exception ex) { - logger.error("Error while processing current source", ex); - logger.warn("Changing to default source"); - - setInputSource(defaultSource); - } - } - - - public void addInputSource(String name, InputSource inputSource) { - addInputSource(name, inputSource, false); - } - - public void addInputSource(String name, InputSource inputSource, boolean dispatchedByUser) { - if (inputSource == null) { - return; - } - - if (sources.containsKey(name)) return; - - inputSource.eocvSim = eocvSim; - - if(eocvSim.visualizer.sourceSelectorPanel != null) { - eocvSim.visualizer.sourceSelectorPanel.setAllowSourceSwitching(false); - } - inputSource.name = name; - - sources.put(name, inputSource); - - if(inputSource.createdOn == -1) - inputSource.createdOn = System.currentTimeMillis(); - - if(!inputSource.isDefault) { - inputSourceLoader.saveInputSource(name, inputSource); - inputSourceLoader.saveInputSourcesToFile(); - } - - if(eocvSim.visualizer.sourceSelectorPanel != null) { - SourceSelectorPanel selectorPanel = eocvSim.visualizer.sourceSelectorPanel; - - selectorPanel.updateSourcesList(); - - SwingUtilities.invokeLater(() -> { - JList sourceSelector = selectorPanel.getSourceSelector(); - - int currentSourceIndex = sourceSelector.getSelectedIndex(); - - if(dispatchedByUser) { - int index = selectorPanel.getIndexOf(name); - - sourceSelector.setSelectedIndex(index); - - requestSetInputSource(name); - - eocvSim.onMainUpdate.once(() -> { - eocvSim.pipelineManager.requestSetPaused(false); - pauseIfImageTwoFrames(); - }); - } else { - sourceSelector.setSelectedIndex(currentSourceIndex); - } - - selectorPanel.setAllowSourceSwitching(true); - }); - } - - logger.info("Adding InputSource " + inputSource + " (" + inputSource.getClass().getSimpleName() + ")"); - } - - public void deleteInputSource(String sourceName) { - InputSource src = sources.get(sourceName); - - if (src == null) return; - if (src.isDefault) return; - - sources.remove(sourceName); - - inputSourceLoader.deleteInputSource(sourceName); - inputSourceLoader.saveInputSourcesToFile(); - } - - public boolean setInputSource(String sourceName, boolean makeDefault) { - boolean result = setInputSource(sourceName); - - if(result && makeDefault) { - defaultSource = sourceName; - } - - return result; - } - - public boolean setInputSource(String sourceName) { - InputSource src = null; - - SysUtil.debugLogCalled("setInputSource"); - - if(sourceName == null) { - src = new NullSource(); - } else { - src = sources.get(sourceName); - } - - if (src != null) { - src.reset(); - } - - if (src != null) { - if (!InputSourceInitializer.INSTANCE.initializeWithTimeout(src, this)) { - eocvSim.visualizer.asyncPleaseWaitDialog("Error while loading requested source", "Falling back to previous source", - "Close", new Dimension(300, 150), true, true); - - logger.error("Error while loading requested source (" + sourceName + ") reported by itself (init method returned false)"); - - return false; - } - } - - if (currentInputSource != null) { - currentInputSource.reset(); - } - - currentInputSource = src; - - //if pause on images option is turned on by user - if (eocvSim.configManager.getConfig().pauseOnImages) - pauseIfImage(); - - logger.info("Set InputSource to " + currentInputSource.toString() + " (" + src.getClass().getSimpleName() + ")"); - - return true; - } - - public void cleanSourceIfDirty() { - if(currentInputSource != null) { - currentInputSource.cleanIfDirty(); - } - } - - public boolean isNameOnUse(String name) { - return sources.containsKey(name); - } - - public String tryName(String name) { - String sourceName = name; - int count = 0; - - while(eocvSim.inputSourceManager.isNameOnUse(sourceName)) { - count++; - sourceName = name + " (" + count + ")"; - } - - return sourceName; - } - - public void pauseIfImage() { - if(currentInputSource == null) return; - - //if the new input source is an image, we will pause the next frame - //to execute one shot analysis on images and save resources. - if (SourceType.fromClass(currentInputSource.getClass()) == SourceType.IMAGE) { - eocvSim.onMainUpdate.once(() -> - eocvSim.pipelineManager.setPaused( - true, - PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS - ) - ); - } - } - - public void pauseIfImageTwoFrames() { - //if the new input source is an image, we will pause the next frame - //to execute one shot analysis on images and save resources. - eocvSim.onMainUpdate.once(this::pauseIfImage); - } - - public void requestSetInputSource(String name) { - SysUtil.debugLogCalled("requestSetInputSource"); - - eocvSim.onMainUpdate.once(() -> setInputSource(name)); - } - - public Visualizer.AsyncPleaseWaitDialog showApwdIfNeeded(String sourceName, Job job) { - Visualizer.AsyncPleaseWaitDialog apwd = null; - - if (getSourceType(sourceName) == SourceType.CAMERA || getSourceType(sourceName) == SourceType.VIDEO || getSourceType(sourceName) == SourceType.HTTP) { - apwd = eocvSim.visualizer.asyncPleaseWaitDialog( - "Opening source...", null, "Cancel", - new Dimension(300, 150), true - ); - - apwd.onCancel(() -> { - if (job != null) { - job.cancel(new CancellationException()); - } - }); - } - - return apwd; - } - - public String getDefaultInputSource() { - return defaultSource; - } - - public SourceType getSourceType(String sourceName) { - if(sourceName == null) { - return SourceType.UNKNOWN; - } - - InputSource source = sources.get(sourceName); - - if(source == null) { - return SourceType.UNKNOWN; - } - return SourceType.fromClass(source.getClass()); - } - - public InputSource[] getSortedInputSources() { - ArrayList sources = new ArrayList<>(this.sources.values()); - Collections.sort(sources); - - return sources.toArray(new InputSource[0]); - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceManager.kt new file mode 100644 index 00000000..79e4c292 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceManager.kt @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.input + +import com.github.serivesmejia.eocvsim.config.ConfigManager +import com.github.serivesmejia.eocvsim.input.source.ImageSource +import com.github.serivesmejia.eocvsim.input.source.NullSource +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.orchestration.PhaseOrchestrableBase +import org.deltacv.common.util.loggerForThis +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named +import org.opencv.core.Mat +import org.opencv.core.Scalar +import org.opencv.core.Size +import org.opencv.imgproc.Imgproc +import org.openftc.easyopencv.MatRecycler +import java.io.IOException + +class InputSourceManager : PhaseOrchestrableBase(), KoinComponent { + + private val pipelineManager: PipelineManager by inject() + private val configManager: ConfigManager by inject() + + private val onMainLoop: EventHandler by inject(named("onMainLoop")) + + private val inputSourceInitializer: InputSourceInitializer by inject() + + companion object { + private val BLACK = Scalar(0.0, 0.0, 0.0, 255.0) + } + + @Volatile var lastMatFromSource: Mat? = null + @Volatile var currentInputSource: InputSource? = null + + val sources = mutableMapOf() + + val inputSourceLoader = InputSourceLoader() + private lateinit var matRecycler: MatRecycler + + val onInputSourceAdded = EventHandler("InputSourceManager-OnInputSourceAdded") + val onInputSourceRemoved = EventHandler("InputSourceManager-OnInputSourceRemoved") + + var lastAddedSourceName = "" + private set + var wasLastSourceAddedByUser = false + private set + + private var defaultSource = "" + + private val logger by loggerForThis() + + override suspend fun init() { + logger.info("Initializing...") + + matRecycler = MatRecycler(4) + + if (lastMatFromSource == null) { + lastMatFromSource = Mat(Size(640.0, 480.0), 24) // 24 is CV_8UC4 (RGBA) + lastMatFromSource!!.setTo(BLACK) + } + + val size = Size(640.0, 480.0) + + createDefaultImgInputSource("/images/ug_4.jpg", "ug_eocvsim_4.jpg", "Ultimate Goal 4 Ring", size) + createDefaultImgInputSource("/images/ug_1.jpg", "ug_eocvsim_1.jpg", "Ultimate Goal 1 Ring", size) + createDefaultImgInputSource("/images/ug_0.jpg", "ug_eocvsim_0.jpg", "Ultimate Goal 0 Ring", size) + + if (sources.isEmpty()) { + logger.warn("No input sources found, creating default null source") + + val nullSource = NullSource().apply { + isDefault = true + } + + addInputSource("Default", nullSource) + } else { + setInputSource("Ultimate Goal 4 Ring", true) + } + + inputSourceLoader.loadInputSourcesFromFile() + + for ((name, source) in inputSourceLoader.loadedInputSources) { + logger.info("Loaded input source $name") + addInputSource(name, source) + } + } + + private fun createDefaultImgInputSource(resourcePath: String, fileName: String, sourceName: String, imgSize: Size) { + try { + val `is` = InputSource::class.java.getResourceAsStream(resourcePath) + val f = SysUtil.copyFileIsTemp(`is`, fileName, false).file + + val src = ImageSource(f.absolutePath, imgSize).apply { + isDefault = true + createdOn = sources.size.toLong() + } + + addInputSource(sourceName, src) + } catch (e: IOException) { + logger.error("Error while creating default image input source", e) + } + } + + override suspend fun run() { + val isPaused = pipelineManager.paused + + val currentSource = currentInputSource ?: return + + try { + currentSource.isPaused = isPaused + + val m = currentSource.update() + + if (m != null && !m.empty()) { + val nextMat = matRecycler.takeMatOrNull() ?: matRecycler.takeMatOrInterrupt() + + // Directly convert from the source 'm' (RGB) into our properly sized 'nextMat' (RGBA) + // This avoids allocating native buffers once nextMat is initialized natively for the first few frames + Imgproc.cvtColor(m, nextMat, Imgproc.COLOR_RGB2RGBA) + + val prev = lastMatFromSource + if (prev is MatRecycler.RecyclableMat) { + prev.returnMat() + } else { + prev?.release() + } + + lastMatFromSource = nextMat + } + } catch (ex: Exception) { + logger.error("Error while processing current source", ex) + logger.warn("Changing to default source") + + setInputSource(defaultSource) + } + } + + override suspend fun destroy() { + currentInputSource?.close() + } + + @JvmOverloads + fun addInputSource(name: String, inputSource: InputSource?, dispatchedByUser: Boolean = false) { + if (inputSource == null) return + + if (sources.containsKey(name)) return + + inputSource.name = name + sources[name] = inputSource + + if (inputSource.createdOn == -1L) { + inputSource.createdOn = System.currentTimeMillis() + } + + if (!inputSource.isDefault) { + inputSourceLoader.saveInputSource(name, inputSource) + inputSourceLoader.saveInputSourcesToFile() + } + + lastAddedSourceName = name + wasLastSourceAddedByUser = dispatchedByUser + + onInputSourceAdded.run() + + logger.info("Adding InputSource $inputSource (${inputSource.javaClass.simpleName})") + } + + fun deleteInputSource(sourceName: String) { + val src = sources[sourceName] ?: return + if (src.isDefault) return + + sources.remove(sourceName) + + inputSourceLoader.deleteInputSource(sourceName) + inputSourceLoader.saveInputSourcesToFile() + + onInputSourceRemoved.run() + } + + fun setInputSource(sourceName: String?, makeDefault: Boolean): Boolean { + val result = setInputSource(sourceName) + + if (result && makeDefault) { + defaultSource = sourceName ?: "" + } + + return result + } + + val onInputSourceInitError = EventHandler("InputSourceManager-OnInputSourceInitError") + + fun setInputSource(sourceName: String?): Boolean { + val src = if (sourceName == null) { + NullSource() + } else { + sources[sourceName] + } + + src?.reset() + + if (src != null) { + when (val initResult = inputSourceInitializer.initializeWithTimeout(src)) { + InputSourceInitializer.Result.SUCCESS -> Unit + InputSourceInitializer.Result.CANCELED -> { + logger.info("Input source loading canceled by user ($sourceName)") + return false + } + InputSourceInitializer.Result.FAILED, + InputSourceInitializer.Result.TIMED_OUT -> { + onInputSourceInitError.run() + logger.error("Error while loading requested source ($sourceName), result=$initResult") + return false + } + } + } + + currentInputSource?.reset() + currentInputSource = src + + // if pause on images option is turned on by user + if (configManager.config.pauseOnImages) { + pauseIfImage() + } + + logger.info("Set InputSource to ${currentInputSource.toString()} (${src?.javaClass?.simpleName})") + + return true + } + + @Suppress("unused") + fun cleanSourceIfDirty() { + currentInputSource?.cleanIfDirty() + } + + fun isNameInUse(name: String) = sources.containsKey(name) + + fun tryName(name: String): String { + var sourceName = name + var count = 0 + + while (isNameInUse(sourceName)) { + count++ + sourceName = "$name ($count)" + } + + return sourceName + } + + fun pauseIfImage() { + val source = currentInputSource ?: return + + // if the new input source is an image, we will pause the next frame + // to execute one shot analysis on images and save resources. + if (SourceType.fromClass(source.javaClass) == SourceType.IMAGE) { + onMainLoop.once { + pipelineManager.setPaused( + true, + PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS + ) + } + } + } + + fun pauseIfImageTwoFrames() { + // if the new input source is an image, we will pause the next frame + // to execute one shot analysis on images and save resources. + onMainLoop.once { pauseIfImage() } + } + + fun requestSetInputSource(name: String?) { + onMainLoop.once { setInputSource(name) } + } + + + @Suppress("unused") + fun getDefaultInputSource() = defaultSource + + fun getSourceType(sourceName: String?): SourceType { + if (sourceName == null) return SourceType.UNKNOWN + + val source = sources[sourceName] ?: return SourceType.UNKNOWN + return SourceType.fromClass(source.javaClass) + } + + val sortedInputSources: List get() { + val sourcesList = ArrayList(sources.values) + sourcesList.sort() + + return sourcesList + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/SourceType.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/SourceType.java deleted file mode 100644 index eaf553c5..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/SourceType.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.input; - -import com.github.serivesmejia.eocvsim.input.source.*; - -import javax.swing.filechooser.FileFilter; -import java.io.File; - -public enum SourceType { - - IMAGE(new ImageSource(""), "Image"), - CAMERA(new CameraSource("", null), "Camera"), - VIDEO(new VideoSource("", null), "Video"), - HTTP(new HttpSource(""), "HTTP"), - UNKNOWN(null, "Unknown"); - - public final Class klazz; - public final String coolName; - public final InputSource stubInstance; - - SourceType(InputSource instance, String coolName) { - stubInstance = instance; - - if(instance != null) - this.klazz = instance.getClass(); - else - this.klazz = null; - - this.coolName = coolName; - } - - public static SourceType fromClass(Class clazz) { - for(SourceType sourceType : values()) { - if(sourceType.klazz == clazz) { - return sourceType; - } - } - return UNKNOWN; - } - - public static SourceType fromCoolName(String coolName) { - for(SourceType sourceType : values()) { - if(sourceType.coolName.equalsIgnoreCase(coolName)) { - return sourceType; - } - } - return UNKNOWN; - } - - public static SourceType isFileUsableForSource(File file) { - for(SourceType type : values()) { - if(type.stubInstance != null && type.stubInstance.getFileFilters() != null) - if(type.stubInstance.getFileFilters().accept(file)) - return type; - } - - return UNKNOWN; - } - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/SourceType.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/SourceType.kt new file mode 100644 index 00000000..b529c70c --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/SourceType.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.input + +import com.github.serivesmejia.eocvsim.input.source.* +import java.io.File + +enum class SourceType(val stubInstance: InputSource?, val coolName: String) { + + IMAGE(ImageSource(), "Image"), + CAMERA(CameraSource(), "Camera"), + VIDEO(VideoSource(), "Video"), + HTTP(HttpSource(), "HTTP"), + UNKNOWN(null, "Unknown"); + + val klazz: Class? = stubInstance?.javaClass + + companion object { + @JvmStatic + fun fromClass(clazz: Class): SourceType { + for (sourceType in entries) { + if (sourceType.klazz == clazz) { + return sourceType + } + } + return UNKNOWN + } + + @JvmStatic + fun fromCoolName(coolName: String): SourceType { + for (sourceType in entries) { + if (sourceType.coolName.equals(coolName, ignoreCase = true)) { + return sourceType + } + } + return UNKNOWN + } + + @JvmStatic + fun isFileUsableForSource(file: File): SourceType { + for (type in entries) { + val filters = type.stubInstance?.fileFilters + if (filters != null && filters.accept(file)) { + return type + } + } + return UNKNOWN + } + } + +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSource.java deleted file mode 100644 index cf0020cc..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSource.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.input.source; - -import com.github.serivesmejia.eocvsim.gui.Visualizer; -import com.github.serivesmejia.eocvsim.gui.util.WebcamDriver; -import com.github.serivesmejia.eocvsim.input.InputSource; -import com.github.serivesmejia.eocvsim.input.InputSourceInitializer; -import com.github.serivesmejia.eocvsim.util.StrUtil; -import com.google.gson.annotations.Expose; -import io.github.deltacv.steve.Webcam; -import io.github.deltacv.steve.WebcamPropertyControl; -import io.github.deltacv.steve.WebcamRotation; -import io.github.deltacv.steve.opencv.OpenCvWebcam; -import io.github.deltacv.steve.opencv.OpenCvWebcamBackend; -import io.github.deltacv.steve.openpnp.OpenPnpBackend; -import io.github.deltacv.steve.openpnp.OpenPnpWebcam; -import org.opencv.core.Mat; -import org.opencv.core.Size; -import org.opencv.imgproc.Imgproc; -import org.openftc.easyopencv.MatRecycler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.swing.filechooser.FileFilter; -import java.util.List; - -public class CameraSource extends InputSource { - - // for global use, -1 means no webcam currently in use - public static int currentWebcamIndex = -1; - - protected int webcamIndex; - - @Expose - protected String webcamName = null; - - private transient Webcam camera = null; - - private transient MatRecycler.RecyclableMat lastFramePaused = null; - private transient MatRecycler.RecyclableMat lastFrame = null; - - private transient boolean initialized = false; - - protected boolean isLegacyByIndex = false; - - @Expose - protected volatile Size size; - @Expose - protected volatile WebcamRotation rotation; - - private volatile transient MatRecycler matRecycler; - - private transient long capTimeNanos = 0; - - Logger logger = LoggerFactory.getLogger(getClass()); - - public CameraSource() {} - - public CameraSource(String webcamName, Size size, WebcamRotation rotation) { - this.webcamName = webcamName; - this.size = size; - this.rotation = rotation; - } - - public CameraSource(String webcamName, Size size) { - this.webcamName = webcamName; - this.size = size; - this.rotation = WebcamRotation.UPRIGHT; - } - - public CameraSource(int webcamIndex, Size size, WebcamRotation rotation) { - this.webcamIndex = webcamIndex; - this.size = size; - this.rotation = rotation; - isLegacyByIndex = true; - } - - public CameraSource(int webcamIndex, Size size) { - this.webcamIndex = webcamIndex; - this.size = size; - this.rotation = WebcamRotation.UPRIGHT; - isLegacyByIndex = true; - } - - public WebcamPropertyControl getWebcamPropertyControl() { - if(camera == null) return null; - return camera.getPropertyControl(); - } - - public String getWebcamName() { - return webcamName; - } - - @Override - public void setSize(Size size) { - this.size = size; - } - - @Override - public Size getSize() { - return size; - } - - @Override - public boolean init() { - if (initialized) return false; - initialized = true; - - if(rotation == null) rotation = WebcamRotation.UPRIGHT; - - if(webcamName != null) { - if(eocvSim.getConfig().preferredWebcamDriver == WebcamDriver.OpenPnp) { - Webcam.Companion.setBackend(OpenPnpBackend.INSTANCE); - } else if(eocvSim.getConfig().preferredWebcamDriver == WebcamDriver.OpenIMAJ) { - eocvSim.getConfig().preferredWebcamDriver = WebcamDriver.OpenPnp; - Webcam.Companion.setBackend(OpenPnpBackend.INSTANCE); - } - - List webcams = Webcam.Companion.getAvailableWebcams(); - - boolean foundWebcam = false; - - for(Webcam device : webcams) { - String name = device.getName(); - double similarity = StrUtil.similarity(name, webcamName); - - if(name.equals(webcamName) || similarity > 0.6) { - logger.info("\"" + name + "\" compared to \"" + webcamName + "\", similarity " + similarity); - - camera = device; - foundWebcam = true; - break; - } - } - - if(!foundWebcam) { - logger.error("Could not find webcam " + webcamName); - return false; - } - } else { - Webcam.Companion.setBackend(OpenCvWebcamBackend.INSTANCE); - camera = new OpenCvWebcam(webcamIndex, size, rotation); - } - - camera.setResolution(size); - camera.setRotation(rotation); - - try { - camera.open(); - } catch (Exception ex) { - logger.error("Error while opening camera " + webcamIndex, ex); - return false; - } - - if (!camera.isOpen()) { - logger.error("Unable to open camera " + webcamIndex + ", isOpen() returned false."); - return false; - } - - if (matRecycler == null) matRecycler = new MatRecycler(4); - MatRecycler.RecyclableMat newFrame = matRecycler.takeMatOrNull(); - - camera.read(newFrame); - - if (newFrame.empty()) { - logger.error("Unable to open camera " + webcamIndex + ", returned Mat was empty."); - newFrame.release(); - return false; - } - - matRecycler.returnMat(newFrame); - - currentWebcamIndex = webcamIndex; - - return true; - } - - @Override - public void reset() { - if (!initialized) return; - if (camera != null && camera.isOpen()) camera.close(); - - if(lastFrame != null && lastFrame.isCheckedOut()) - lastFrame.returnMat(); - if(lastFramePaused != null && lastFramePaused.isCheckedOut()) - lastFramePaused.returnMat(); - - initialized = false; - } - - @Override - public void close() { - if (camera != null && camera.isOpen()) camera.close(); - currentWebcamIndex = -1; - } - - private MatRecycler.RecyclableMat lastNewFrame = null; - - @Override - public Mat update() { - if(lastNewFrame != null) { - lastNewFrame.returnMat(); - lastNewFrame = null; - } - - if (isPaused) { - return lastFramePaused; - } else if (lastFramePaused != null) { - lastFramePaused.release(); - lastFramePaused.returnMat(); - lastFramePaused = null; - } - - if (lastFrame == null) lastFrame = matRecycler.takeMatOrNull(); - if (camera == null) return lastFrame; - - MatRecycler.RecyclableMat newFrame = matRecycler.takeMatOrNull(); - lastNewFrame = newFrame; - - camera.read(newFrame); - capTimeNanos = System.nanoTime(); - - if (newFrame.empty()) { - newFrame.returnMat(); - return lastFrame; - } - - if (size == null) size = lastFrame.size(); - - newFrame.copyTo(lastFrame); - - newFrame.release(); - newFrame.returnMat(); - - lastNewFrame = null; - - return lastFrame; - } - - @Override - public void onPause() { - if (lastFrame != null) lastFrame.release(); - if (lastFramePaused == null) lastFramePaused = matRecycler.takeMatOrNull(); - - camera.read(lastFramePaused); - Imgproc.cvtColor(lastFramePaused, lastFramePaused, Imgproc.COLOR_BGR2RGB); - - update(); - - camera.close(); - - currentWebcamIndex = -1; - } - - @Override - public void onResume() { - InputSourceInitializer.INSTANCE.runWithTimeout(name, eocvSim.inputSourceManager, () -> { - camera.open(); - return camera.isOpen(); - }); - } - - @Override - protected InputSource internalCloneSource() { - return new CameraSource(webcamName, size); - } - - @Override - public FileFilter getFileFilters() { - return null; - } - - @Override - public long getCaptureTimeNanos() { - return capTimeNanos; - } - - @Override - public String toString() { - if (size == null) size = new Size(); - return "CameraSource(" + webcamName + ", " + webcamIndex + ", " + (size != null ? size.toString() : "null") + ")"; - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSource.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSource.kt new file mode 100644 index 00000000..2ae9550f --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSource.kt @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.input.source + +import com.fasterxml.jackson.annotation.JsonAutoDetect +import com.fasterxml.jackson.annotation.JsonProperty +import com.github.serivesmejia.eocvsim.input.InputSource +import com.github.serivesmejia.eocvsim.input.InputSourceInitializer +import com.github.serivesmejia.eocvsim.config.ConfigManager +import org.deltacv.common.util.loggerForThis +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.opencv.core.Mat +import org.opencv.core.Size +import org.opencv.imgproc.Imgproc +import org.wpilib.util.PixelFormat +import org.wpilib.vision.camera.CvSink +import org.wpilib.vision.camera.UsbCamera +import org.wpilib.vision.camera.VideoMode +import javax.swing.filechooser.FileFilter + +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.NONE, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE, + creatorVisibility = JsonAutoDetect.Visibility.NONE +) +class CameraSource : InputSource, KoinComponent { + + companion object { + @JvmStatic var currentWebcamIndex = -1 + } + + override val hasSlowInitialization = true + + @JsonProperty @JvmField var cameraPortIndex = -1 + @JsonProperty @JvmField var exactPortMatch = false + @JsonProperty @JvmField var vendorId: Int? = null + @JsonProperty @JvmField var productId: Int? = null + @Transient var webcamName = "" + + @JsonProperty @JvmField var videoMode: VideoMode? = null + + @Transient var camera: UsbCamera? = null + private set + + @Transient private var cvSink: CvSink? = null + @Transient private var lastFrame = Mat() + @Transient private var initialized = false + @Transient var isLegacyByIndex = false + @Transient private var capTimeNanos = 0L + + private val configManager: ConfigManager by inject() + private val logger by loggerForThis() + + constructor() : super() + + constructor(webcamName: String?, videoMode: VideoMode?) : super() { + this.webcamName = webcamName ?: "" + this.videoMode = videoMode + } + + constructor(webcamName: String?, vendorId: Int?, productId: Int?, videoMode: VideoMode?) : super() { + this.webcamName = webcamName ?: "" + this.vendorId = vendorId + this.productId = productId + this.videoMode = videoMode + } + + constructor( + webcamName: String?, + cameraPortIndex: Int, + exactPortMatch: Boolean, + vendorId: Int?, + productId: Int?, + videoMode: VideoMode? + ) : super() { + this.webcamName = webcamName ?: "" + this.cameraPortIndex = cameraPortIndex + this.exactPortMatch = exactPortMatch + this.vendorId = vendorId + this.productId = productId + this.videoMode = videoMode + } + + constructor(cameraPortIndex: Int, videoMode: VideoMode?) : super() { + this.cameraPortIndex = cameraPortIndex + this.videoMode = videoMode + this.isLegacyByIndex = true + } + + override val sourceSize: Size + get() = videoMode?.let { Size(it.width.toDouble(), it.height.toDouble()) } ?: Size() + + override fun init(): Boolean { + if (initialized) return false + initialized = true + + val cameras by lazy { UsbCamera.enumerateUsbCameras() } + + val matchedInfo = when { + exactPortMatch -> cameras.firstOrNull { + cameraPortIndex >= 0 && + it.dev == cameraPortIndex && + vendorId != null && productId != null && + it.vendorId == vendorId && + it.productId == productId + } ?: run { + logger.error("Camera not found on the same connection: $cameraPortIndex") + return false + } + + cameraPortIndex >= 0 -> cameras.firstOrNull { it.dev == cameraPortIndex } + ?: run { + logger.error("Camera not found on port: $cameraPortIndex") + return false + } + + vendorId != null && productId != null -> cameras.firstOrNull { + it.vendorId == vendorId && it.productId == productId + } ?: run { + logger.error("Camera not found by VID/PID: $vendorId:$productId") + return false + } + + webcamName.isNotEmpty() -> cameras.firstOrNull { it.name == webcamName } + ?: run { + logger.error("Camera not found: $webcamName") + return false + } + + else -> null + } + + camera = if (matchedInfo != null) { + webcamName = matchedInfo.name + UsbCamera(matchedInfo.name, matchedInfo.dev) + } else { + UsbCamera("$cameraPortIndex", cameraPortIndex) + } + + camera!!.videoMode = videoMode ?: camera!!.videoMode + + logger.info("Camera started: ${matchedInfo?.name ?: webcamName.ifEmpty { "Camera $cameraPortIndex" }} ${camera!!.videoMode?.stringify()}") + + cvSink = CvSink("eocvsim_sink_$cameraPortIndex", PixelFormat.BGR).also { + it.source = camera + } + + if (cvSink!!.grabFrame(lastFrame, configManager.config.webcamOpenTimeoutSec) == 0L || lastFrame.empty()) { + logger.error("Failed to open camera: ${cvSink!!.error}") + return false + } + + currentWebcamIndex = cameraPortIndex + return true + } + + override fun reset() { + if (!initialized) return + teardown() + lastFrame.release() + initialized = false + } + + override fun close() = teardown() + + override fun update(): Mat { + if (isPaused) return lastFrame + + capTimeNanos = cvSink?.grabFrame(lastFrame, configManager.config.webcamNewFrameTimeoutSec) ?: 0L + + if (!lastFrame.empty()) { + Imgproc.cvtColor(lastFrame, lastFrame, Imgproc.COLOR_BGR2RGBA) + } + + return lastFrame + } + + override fun onPause() { + cvSink?.grabFrame(lastFrame, configManager.config.webcamNewFrameTimeoutSec) + teardown() + } + + override fun onResume() { + InputSourceInitializer.runWithTimeout(this) { init() } + } + + private fun teardown() { + cvSink?.close() + cvSink = null + camera?.close() + camera = null + currentWebcamIndex = -1 + } + + override fun internalCloneSource(): InputSource = + if (isLegacyByIndex) CameraSource(cameraPortIndex, videoMode) + else CameraSource(webcamName, cameraPortIndex, exactPortMatch, vendorId, productId, videoMode) + + override val fileFilters: FileFilter? = null + override val captureTimeNanos get() = capTimeNanos + + override fun toString() = + "CameraSource($webcamName, port=$cameraPortIndex, exactPortMatch=$exactPortMatch, vid=$vendorId, pid=$productId, ${videoMode?.stringify()})" + + private fun VideoMode.stringify() = "${width}x${height}@${fps} $pixelFormat" +} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSourceAdapter.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSourceAdapter.java deleted file mode 100644 index a3a2fd29..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSourceAdapter.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.serivesmejia.eocvsim.input.source; - -import com.github.serivesmejia.eocvsim.input.source.CameraSource; -import com.google.gson.*; - -import java.lang.reflect.Type; - -public class CameraSourceAdapter implements JsonSerializer { - - @Override - public JsonElement serialize(CameraSource src, Type typeOfSrc, JsonSerializationContext context) { - JsonObject obj = new JsonObject(); - - if(src.webcamName != null) { - obj.addProperty("webcamName", src.webcamName); - } else { - obj.addProperty("webcamIndex", src.webcamIndex); - } - - obj.add("size", context.serialize(src.size)); - obj.addProperty("createdOn", src.getCreationTime()); - - return obj; - } - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/HttpSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/HttpSource.java deleted file mode 100644 index 8420065b..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/HttpSource.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (c) 2025 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.input.source; - -import com.github.serivesmejia.eocvsim.input.InputSource; -import com.github.serivesmejia.eocvsim.input.InputSourceInitializer; -import com.google.gson.annotations.Expose; -import io.github.deltacv.visionloop.io.MjpegHttpReader; -import org.opencv.core.Mat; -import org.opencv.core.MatOfByte; -import org.opencv.imgcodecs.Imgcodecs; -import org.opencv.imgproc.Imgproc; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.swing.filechooser.FileFilter; -import java.util.Iterator; - -public class HttpSource extends InputSource { - - @Expose - protected String url; - - private transient MjpegHttpReader mjpegHttpReader; - - private static final Logger logger = LoggerFactory.getLogger(HttpSource.class); - - private transient MatOfByte buf; - private transient Mat img; - - private transient Iterator iterator; - - private transient long capTimeNanos = 0; - - public HttpSource(String url) { - this.url = url; - } - - @Override - public boolean init() { - buf = new MatOfByte(); - img = new Mat(); - - try { - mjpegHttpReader = new MjpegHttpReader(url); - mjpegHttpReader.start(); - } catch (Exception e) { - logger.error("Error while initializing MjpegHttpReader", e); - return false; - } - - try { - iterator = mjpegHttpReader.iterator(); - } catch (Exception e) { - logger.error("Error while getting MjpegHttpReader iterator", e); - return false; - } - - logger.info("HttpSource initialized"); - - return mjpegHttpReader != null && iterator != null; - } - - byte[] frame; - - @Override - public Mat update() { - if (mjpegHttpReader == null) return null; - - boolean result = InputSourceInitializer.INSTANCE.runWithTimeout(name, () -> { - frame = iterator.next(); - return frame != null; - }); - - if(!result) { - return null; - } - - if(!dataIsValidJPEG(frame)) { - logger.error("Received data is not a valid JPEG image"); - return null; - } - - buf.fromArray(frame); - - if(buf.empty()) { - return null; - } - - Mat mat = Imgcodecs.imdecode(buf, Imgcodecs.IMREAD_COLOR); - Imgproc.cvtColor(mat, img, Imgproc.COLOR_BGR2RGBA); - - mat.release(); - - capTimeNanos = System.nanoTime(); - - return img; - } - - @Override - public void reset() { - if (mjpegHttpReader != null) { - mjpegHttpReader.stop(); - mjpegHttpReader = null; - } - } - - @Override - public void close() { - reset(); - } - - @Override - public void onPause() { - if (mjpegHttpReader != null) { - reset(); - } - } - - @Override - public void onResume() { - InputSourceInitializer.INSTANCE.runWithTimeout(name, eocvSim.inputSourceManager, this::init); - } - - @Override - protected InputSource internalCloneSource() { - return new HttpSource(url); - } - - public String getUrl() { - return url; - } - - @Override - public FileFilter getFileFilters() { - return null; - } - - @Override - public long getCaptureTimeNanos() { - return capTimeNanos; - } - - @Override - public String toString() { - return "HttpSource(" + url + ")"; - } - - private static boolean dataIsValidJPEG(byte[] data) { - if (data == null || data.length < 2) { - return false; - } - - int totalBytes = getJPEGSize(data, data.length); - - if (totalBytes == -1) { - return false; - } - - return (data[0] == (byte) 0xFF && - data[1] == (byte) 0xD8 && - data[totalBytes - 2] == (byte) 0xFF && - data[totalBytes - 1] == (byte) 0xD9); - } - - private static int getJPEGSize(byte[] data, int maxLength) { - if (data == null || maxLength < 4) { - return -1; // Invalid or too small to be a JPEG - } - - // Check for SOI marker - if (data[0] != (byte) 0xFF || data[1] != (byte) 0xD8) { - return -1; // Not a JPEG - } - - int pos = 2; // Start after SOI - - while (pos < maxLength - 2) { - // Look for the next marker (0xFF xx) - if (data[pos] == (byte) 0xFF) { - byte marker = data[pos + 1]; - - // End of Image (EOI) found - if (marker == (byte) 0xD9) { - return pos + 2; // JPEG size - } - - // Skip padding bytes (some JPEGs use 0xFF 0x00) - if (marker == (byte) 0x00) { - pos++; - continue; - } - - // Most markers have a 2-byte length field - if ((marker >= (byte) 0xC0 && marker <= (byte) 0xFE) && marker != (byte) 0xD9) { - if (pos + 3 >= maxLength) { - return -1; // Incomplete JPEG - } - - // Read segment length (big-endian) - int segmentLength = ((data[pos + 2] & 0xFF) << 8) | (data[pos + 3] & 0xFF); - - if (segmentLength < 2 || pos + segmentLength >= maxLength) { - return -1; // Corrupt or incomplete JPEG - } - - pos += segmentLength; // Move to next marker - } else { - pos++; // Skip unknown byte - } - } else { - pos++; // Continue searching - } - } - - return -1; // No valid JPEG end found - } -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/HttpSource.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/HttpSource.kt new file mode 100644 index 00000000..2f8621fe --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/HttpSource.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2025 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.input.source + +import com.fasterxml.jackson.annotation.JsonAutoDetect +import com.fasterxml.jackson.annotation.JsonProperty +import com.github.serivesmejia.eocvsim.input.InputSource +import com.github.serivesmejia.eocvsim.input.InputSourceInitializer +import com.github.serivesmejia.eocvsim.config.ConfigManager +import org.deltacv.common.util.loggerForThis +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.opencv.core.Mat +import org.opencv.core.Size +import org.opencv.imgproc.Imgproc +import org.wpilib.vision.camera.CvSink +import org.wpilib.vision.camera.HttpCamera +import javax.swing.filechooser.FileFilter + +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.NONE, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE, + creatorVisibility = JsonAutoDetect.Visibility.NONE +) +class HttpSource @JvmOverloads constructor ( + @field:JsonProperty @JvmField var url: String = "" +) : InputSource(), KoinComponent { + + override val hasSlowInitialization: Boolean get() = true + + @Transient private var camera: HttpCamera? = null + + @Transient private var cvSink: CvSink? = null + + @Transient private var lastFrame = Mat() + @Transient private var initialized = false + @Transient private var capTimeNanos: Long = 0 + private val configManager: ConfigManager by inject() + private val logger by loggerForThis() + + override fun init(): Boolean { + try { + camera = HttpCamera("eocvsim_http_source", url) + } catch (e: Exception) { + logger.error("Error while initializing HTTP camera", e) + return false + } + + try { + cvSink = CvSink("eocvsim_http_sink").also { + it.source = camera + } + } catch (e: Exception) { + logger.error("Error while setting up HTTP camera sink", e) + return false + } + + val ok = cvSink!!.grabFrame(lastFrame, configManager.config.webcamOpenTimeoutSec) + if (ok == 0L || lastFrame.empty()) { + logger.error("Failed to grab frame from HTTP camera: ${cvSink!!.error}") + return false + } + + logger.info("HttpSource initialized") + initialized = true + return true + } + + + override fun update(): Mat? { + if (!initialized) return null + + val ok = cvSink?.grabFrame(lastFrame, configManager.config.webcamNewFrameTimeoutSec) ?: 0L + + if (ok == 0L || lastFrame.empty()) { + return null + } + capTimeNanos = System.nanoTime() + Imgproc.cvtColor(lastFrame, lastFrame, Imgproc.COLOR_BGR2RGBA) + return lastFrame + } + + override fun reset() { + if (!initialized) return + cvSink?.close() + cvSink = null + camera?.close() + camera = null + lastFrame.release() + initialized = false + } + + override fun close() { + cvSink?.close() + camera?.close() + } + + override fun onPause() { + cvSink?.close() + camera?.close() + } + + override fun onResume() { + InputSourceInitializer.runWithTimeout(this) { init() } + } + + override fun internalCloneSource(): InputSource = HttpSource(url) + + override val sourceSize: Size + get() = cvSink?.directMat?.size() ?: Size() + + override val fileFilters: FileFilter? get() = null + override val captureTimeNanos: Long get() = capTimeNanos + + override fun toString(): String { + return "HttpSource($url)" + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/ImageSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/ImageSource.java deleted file mode 100644 index 42c48f14..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/ImageSource.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.input.source; - -import com.github.serivesmejia.eocvsim.input.InputSource; -import com.github.serivesmejia.eocvsim.util.FileFilters; -import com.google.gson.annotations.Expose; -import org.opencv.core.Mat; -import org.opencv.core.Size; -import org.opencv.imgcodecs.Imgcodecs; -import org.opencv.imgproc.Imgproc; -import org.openftc.easyopencv.MatRecycler; - -import javax.swing.filechooser.FileFilter; - -public class ImageSource extends InputSource { - - @Expose - private final String imgPath; - @Expose - private volatile Size size; - - private volatile transient MatRecycler.RecyclableMat img; - private volatile transient MatRecycler.RecyclableMat lastCloneTo; - - private volatile transient boolean initialized = false; - - private volatile transient MatRecycler matRecycler = new MatRecycler(2); - - public ImageSource(String imgPath) { - this(imgPath, null); - } - - public ImageSource(String imgPath, Size size) { - this.imgPath = imgPath; - this.size = size; - } - - @Override - public boolean init() { - - if (initialized) return false; - initialized = true; - - if (matRecycler == null) matRecycler = new MatRecycler(2); - - readImage(); - - return img != null && !img.empty(); - - } - - @Override - public void onPause() { - //if(img != null) img.release(); - } - - @Override - public void onResume() { - } - - @Override - public void reset() { - - if (!initialized) return; - - if (lastCloneTo != null) { - lastCloneTo.returnMat(); - lastCloneTo = null; - } - - if (img != null) { - img.returnMat(); - img = null; - } - - matRecycler.releaseAll(); - - initialized = false; - - } - - public void close() { - - if (img != null) { - matRecycler.returnMat(img); - img = null; - } - - if (lastCloneTo != null) { - lastCloneTo.returnMat(); - lastCloneTo = null; - } - - matRecycler.releaseAll(); - - } - - public void readImage() { - - Mat readMat = Imgcodecs.imread(this.imgPath); - - if (img == null) img = matRecycler.takeMatOrNull(); - - if (readMat.empty()) { - return; - } - - readMat.copyTo(img); - readMat.release(); - - if (this.size != null) { - Imgproc.resize(img, img, this.size, 0.0, 0.0, Imgproc.INTER_AREA); - } else { - this.size = img.size(); - } - - Imgproc.cvtColor(img, img, Imgproc.COLOR_BGR2RGB); - - } - - @Override - public Mat update() { - if (lastCloneTo == null) lastCloneTo = matRecycler.takeMatOrNull(); - - if (img == null) return null; - - lastCloneTo.release(); - img.copyTo(lastCloneTo); - - return lastCloneTo; - } - - public String getImgPath() { - return imgPath; - } - - @Override - public void cleanIfDirty() { - readImage(); - } - - @Override - protected InputSource internalCloneSource() { - return new ImageSource(imgPath, size); - } - - @Override - public void setSize(Size size) { - this.size = size; - } - - @Override - public Size getSize() { - return size; - } - - @Override - public FileFilter getFileFilters() { - return FileFilters.imagesFilter; - } - - @Override - public long getCaptureTimeNanos() { - return System.nanoTime(); - } - - @Override - public String toString() { - if (size == null) size = new Size(); - return "ImageSource(\"" + imgPath + "\", " + (size != null ? size.toString() : "null") + ")"; - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/ImageSource.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/ImageSource.kt new file mode 100644 index 00000000..9bac42df --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/ImageSource.kt @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.input.source + +import com.fasterxml.jackson.annotation.JsonAutoDetect +import com.fasterxml.jackson.annotation.JsonProperty +import com.github.serivesmejia.eocvsim.input.InputSource +import com.github.serivesmejia.eocvsim.util.FileFilters +import org.opencv.core.Mat +import org.opencv.core.Size +import org.opencv.imgcodecs.Imgcodecs +import org.opencv.imgproc.Imgproc +import org.openftc.easyopencv.MatRecycler +import javax.swing.filechooser.FileFilter + +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.NONE, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE, + creatorVisibility = JsonAutoDetect.Visibility.NONE +) +class ImageSource @JvmOverloads constructor( + @field:JsonProperty @JvmField var imgPath: String = "", + @field:JsonProperty @JvmField var size: Size = Size() + +) : InputSource() { + + @Transient private var img: MatRecycler.RecyclableMat? = null + @Transient private var lastCloneTo: MatRecycler.RecyclableMat? = null + + @Transient private var initialized = false + + @Transient private var matRecycler = MatRecycler(2) + + override val sourceSize get() = size + + override fun init(): Boolean { + if (initialized) return false + initialized = true + + readImage() + + return img != null && !img!!.empty() + } + + override fun onPause() {} + + override fun onResume() {} + + override fun reset() { + if (!initialized) return + + lastCloneTo?.returnMat() + lastCloneTo = null + + img?.returnMat() + img = null + + matRecycler.releaseAll() + + initialized = false + } + + override fun close() { + img?.let { + matRecycler.returnMat(it) + img = null + } + + lastCloneTo?.let { + it.returnMat() + lastCloneTo = null + } + + matRecycler.releaseAll() + } + + fun readImage() { + val readMat = Imgcodecs.imread(imgPath) + + if (img == null) img = matRecycler.takeMatOrNull() + + if (readMat.empty()) { + return + } + + readMat.copyTo(img) + readMat.release() + + if (this.sourceSize.area() != 0.0) { + Imgproc.resize(img, img, this.sourceSize, 0.0, 0.0, Imgproc.INTER_AREA) + } else { + this.size = img!!.size() + } + + Imgproc.cvtColor(img, img, Imgproc.COLOR_BGR2RGB) + } + + override fun update(): Mat? { + if (lastCloneTo == null) lastCloneTo = matRecycler.takeMatOrNull() + + if (img == null || lastCloneTo == null) return null + + lastCloneTo!!.release() + img!!.copyTo(lastCloneTo) + + return lastCloneTo + } + + override fun cleanIfDirty() { + readImage() + } + + override fun internalCloneSource() = ImageSource(imgPath, sourceSize) + + override val fileFilters: FileFilter get() = FileFilters.imagesFilter + override val captureTimeNanos: Long get() = System.nanoTime() + + + override fun toString(): String { + return "ImageSource(\"$imgPath\", $sourceSize)" + + } + +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/NullSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/NullSource.java deleted file mode 100644 index 76380b88..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/NullSource.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.input.source; - -import com.github.serivesmejia.eocvsim.input.InputSource; -import org.opencv.core.Mat; - -import javax.swing.filechooser.FileFilter; - -public class NullSource extends InputSource { - @Override - public boolean init() { - return true; - } - - @Override - public void reset() { - - } - - @Override - public void close() { - - } - - Mat emptyMat = new Mat(); - - @Override - public Mat update() { - return emptyMat; - } - - @Override - public void onPause() { - - } - - @Override - public void onResume() { - - } - - @Override - protected InputSource internalCloneSource() { - return new NullSource(); - } - - @Override - public FileFilter getFileFilters() { - return null; - } - - @Override - public long getCaptureTimeNanos() { - return System.nanoTime(); - } -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/NullSource.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/NullSource.kt new file mode 100644 index 00000000..e94c3e61 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/NullSource.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.input.source + +import com.github.serivesmejia.eocvsim.input.InputSource +import org.opencv.core.Mat +import org.opencv.core.Size +import javax.swing.filechooser.FileFilter + +class NullSource : InputSource() { + + override fun init(): Boolean = true + + override fun reset() {} + + override fun close() {} + + @Transient private val emptyMat = Mat() + + override fun update(): Mat = emptyMat + + override fun onPause() {} + + override fun onResume() {} + + override val sourceSize = Size() + + override fun internalCloneSource(): InputSource = NullSource() + + override val fileFilters: FileFilter? = null + override val captureTimeNanos: Long get() = System.nanoTime() + + +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/VideoSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/VideoSource.java deleted file mode 100644 index a02d7617..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/VideoSource.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.input.source; - -import com.github.serivesmejia.eocvsim.input.InputSource; -import com.github.serivesmejia.eocvsim.util.FileFilters; -import com.github.serivesmejia.eocvsim.util.fps.FpsLimiter; -import com.google.gson.annotations.Expose; -import org.opencv.core.Mat; -import org.opencv.core.Size; -import org.opencv.imgproc.Imgproc; -import org.opencv.videoio.VideoCapture; -import org.opencv.videoio.Videoio; -import org.openftc.easyopencv.MatRecycler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.swing.filechooser.FileFilter; - -public class VideoSource extends InputSource { - - @Expose - private String videoPath = null; - - private transient VideoCapture video; - - private transient FpsLimiter fpsLimiter = new FpsLimiter(30); - - private transient MatRecycler.RecyclableMat lastFramePaused; - private transient MatRecycler.RecyclableMat lastFrame; - - private transient boolean initialized; - - @Expose - private volatile Size size; - - private volatile transient MatRecycler matRecycler = null; - - private transient double lastFramePosition = 0; - - private transient long capTimeNanos = 0; - - private transient Logger logger = LoggerFactory.getLogger(getClass()); - - public VideoSource() { - } - - public VideoSource(String videoPath, Size size) { - this.videoPath = videoPath; - this.size = size; - } - - @Override - public boolean init() { - if (initialized) return false; - initialized = true; - - video = new VideoCapture(); - video.open(videoPath); - - if (!video.isOpened()) { - logger.error("Unable to open video " + videoPath); - return false; - } - - if (matRecycler == null) matRecycler = new MatRecycler(4); - - MatRecycler.RecyclableMat newFrame = matRecycler.takeMatOrNull(); - newFrame.release(); - - video.read(newFrame); - - if (newFrame.empty()) { - logger.error("Unable to open video " + videoPath + ", returned Mat was empty."); - return false; - } - - fpsLimiter.setMaxFPS(video.get(Videoio.CAP_PROP_FPS)); - - newFrame.release(); - matRecycler.returnMat(newFrame); - - return true; - } - - @Override - public void reset() { - if (!initialized) return; - - if (video != null && video.isOpened()) video.release(); - - if (lastFrame != null && lastFrame.isCheckedOut()) - lastFrame.returnMat(); - if (lastFramePaused != null && lastFramePaused.isCheckedOut()) - lastFramePaused.returnMat(); - - matRecycler.releaseAll(); - - video = null; - initialized = false; - } - - @Override - public void close() { - if (video != null && video.isOpened()) video.release(); - if (lastFrame != null && lastFrame.isCheckedOut()) lastFrame.returnMat(); - - if (lastFramePaused != null) { - lastFramePaused.returnMat(); - lastFramePaused = null; - } - } - - @Override - public Mat update() { - if (isPaused) { - return lastFramePaused; - } else if (lastFramePaused != null) { - lastFramePaused.returnMat(); - lastFramePaused = null; - } - - try { - fpsLimiter.sync(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - if (lastFrame == null) lastFrame = matRecycler.takeMatOrNull(); - if (video == null) return lastFrame; - - MatRecycler.RecyclableMat newFrame = matRecycler.takeMatOrNull(); - - video.read(newFrame); - capTimeNanos = System.nanoTime(); - - //with videocapture for video files, when an empty mat is returned - //the most likely reason is that the video ended, so we set the - //playback position back to 0 for looping in here and start over - //in next update - if (newFrame.empty()) { - newFrame.returnMat(); - - this.reset(); - this.init(); - return lastFrame; - } - - if (size == null) size = lastFrame.size(); - - Imgproc.cvtColor(newFrame, lastFrame, Imgproc.COLOR_BGR2RGB); - Imgproc.resize(lastFrame, lastFrame, size, 0.0, 0.0, Imgproc.INTER_AREA); - - matRecycler.returnMat(newFrame); - - return lastFrame; - } - - @Override - public void onPause() { - if (lastFrame != null) lastFrame.release(); - if (lastFramePaused == null) lastFramePaused = matRecycler.takeMatOrNull(); - - video.read(lastFramePaused); - - Imgproc.cvtColor(lastFramePaused, lastFramePaused, Imgproc.COLOR_BGR2RGB); - Imgproc.resize(lastFramePaused, lastFramePaused, size, 0.0, 0.0, Imgproc.INTER_AREA); - - update(); - - lastFramePosition = video.get(Videoio.CAP_PROP_POS_FRAMES); - - video.release(); - video = null; - } - - @Override - public void onResume() { - video = new VideoCapture(); - video.open(videoPath); - video.set(Videoio.CAP_PROP_POS_FRAMES, lastFramePosition); - } - - public String getVideoPath() { - return videoPath; - } - - @Override - protected InputSource internalCloneSource() { - return new VideoSource(videoPath, size); - } - - @Override - public Size getSize() { return size; } - @Override - public void setSize(Size size) { - this.size = size; - } - - @Override - public FileFilter getFileFilters() { - return FileFilters.videoMediaFilter; - } - - @Override - public long getCaptureTimeNanos() { - return capTimeNanos; - } - - @Override - public String toString() { - return "VideoSource(" + videoPath + ", " + (size != null ? size.toString() : "null") + ")"; - } - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/VideoSource.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/VideoSource.kt new file mode 100644 index 00000000..25119513 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/VideoSource.kt @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.input.source + +import com.fasterxml.jackson.annotation.JsonAutoDetect +import com.fasterxml.jackson.annotation.JsonProperty +import com.github.serivesmejia.eocvsim.input.InputSource +import com.github.serivesmejia.eocvsim.util.FileFilters +import com.github.serivesmejia.eocvsim.util.fps.FpsLimiter +import org.opencv.core.Mat +import org.opencv.core.Size +import org.opencv.imgproc.Imgproc +import org.opencv.videoio.VideoCapture +import org.opencv.videoio.Videoio +import org.openftc.easyopencv.MatRecycler +import org.slf4j.LoggerFactory +import javax.swing.filechooser.FileFilter + +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.NONE, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE, + creatorVisibility = JsonAutoDetect.Visibility.NONE +) +class VideoSource @JvmOverloads constructor( + @field:JsonProperty @JvmField var videoPath: String = "", + @field:JsonProperty @JvmField var size: Size = Size() +) : InputSource() { + + override val hasSlowInitialization: Boolean get() = true + + @Transient private var video: VideoCapture? = null + + @Transient private val fpsLimiter = FpsLimiter(30.0) + + @Transient private var lastFramePaused: MatRecycler.RecyclableMat? = null + @Transient private var lastFrame: MatRecycler.RecyclableMat? = null + + @Transient private var initialized = false + + @Transient private var matRecycler: MatRecycler? = null + + @Transient private var lastFramePosition = 0.0 + + @Transient private var capTimeNanos: Long = 0 + + @Transient private val logger = LoggerFactory.getLogger(javaClass) + + override val sourceSize get() = size + + override fun init(): Boolean { + if (initialized) return false + initialized = true + + video = VideoCapture() + video!!.open(videoPath) + + if (!video!!.isOpened) { + logger.error("Unable to open video $videoPath") + return false + } + + if (matRecycler == null) matRecycler = MatRecycler(4) + + val newFrame = matRecycler!!.takeMatOrNull() + newFrame!!.release() + + video!!.read(newFrame) + + if (newFrame.empty()) { + logger.error("Unable to open video $videoPath, returned Mat was empty.") + return false + } + + fpsLimiter.maxFPS = video!!.get(Videoio.CAP_PROP_FPS) + + newFrame.release() + matRecycler!!.returnMat(newFrame) + + return true + } + + override fun reset() { + if (!initialized) return + + if (video?.isOpened == true) video!!.release() + + if (lastFrame?.isCheckedOut == true) lastFrame!!.returnMat() + if (lastFramePaused?.isCheckedOut == true) lastFramePaused!!.returnMat() + + matRecycler?.releaseAll() + + video = null + initialized = false + } + + override fun close() { + if (video?.isOpened == true) video!!.release() + if (lastFrame?.isCheckedOut == true) lastFrame!!.returnMat() + + lastFramePaused?.let { + it.returnMat() + lastFramePaused = null + } + } + + override fun update(): Mat? { + if (isPaused) { + return lastFramePaused + } else if (lastFramePaused != null) { + lastFramePaused!!.returnMat() + lastFramePaused = null + } + + try { + fpsLimiter.sync() + } catch (_: InterruptedException) { + Thread.currentThread().interrupt() + } + + if (lastFrame == null) lastFrame = matRecycler?.takeMatOrNull() + if (video == null) return lastFrame + + val newFrame = matRecycler?.takeMatOrNull() ?: return lastFrame + + video!!.read(newFrame) + capTimeNanos = System.nanoTime() + + if (newFrame.empty()) { + newFrame.returnMat() + + reset() + init() + return lastFrame + } + + if (sourceSize.area() == 0.0) size = lastFrame!!.size() + + Imgproc.cvtColor(newFrame, lastFrame, Imgproc.COLOR_BGR2RGB) + Imgproc.resize(lastFrame, lastFrame, sourceSize, 0.0, 0.0, Imgproc.INTER_AREA) + + matRecycler?.returnMat(newFrame) + + return lastFrame + } + + override fun onPause() { + lastFrame?.release() + + if (lastFramePaused == null) lastFramePaused = matRecycler?.takeMatOrNull() + + video?.read(lastFramePaused) + + lastFramePaused?.let { + Imgproc.cvtColor(it, it, Imgproc.COLOR_BGR2RGB) + Imgproc.resize(it, it, sourceSize, 0.0, 0.0, Imgproc.INTER_AREA) + } + + update() + + lastFramePosition = video?.get(Videoio.CAP_PROP_POS_FRAMES) ?: 0.0 + + video?.release() + video = null + } + + override fun onResume() { + video = VideoCapture() + video!!.open(videoPath) + video!!.set(Videoio.CAP_PROP_POS_FRAMES, lastFramePosition) + } + + override fun internalCloneSource(): InputSource = VideoSource(videoPath, sourceSize) + + override val fileFilters: FileFilter get() = FileFilters.videoMediaFilter + override val captureTimeNanos: Long get() = capTimeNanos + + override fun toString() = "VideoSource($videoPath, $sourceSize)" +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/RecordingManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/RecordingManager.kt new file mode 100644 index 00000000..bb49187f --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/RecordingManager.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.output + +import com.github.serivesmejia.eocvsim.config.ConfigManager +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.util.FileFilters +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.deltacv.common.util.loggerForThis +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named +import java.io.File +import javax.swing.filechooser.FileFilter + +class RecordingManager : KoinComponent { + + val configManager: ConfigManager by inject() + val pipelineManager: PipelineManager by inject() + val visualizer: Visualizer by inject() + val dialogFactory: DialogFactory by inject() + val onMainUpdate: EventHandler by inject(named("onMainLoop")) + + private val logger by loggerForThis() + + var currentRecordingSession: VideoRecordingSession? = null + private set + + fun isCurrentlyRecording() = currentRecordingSession != null + + fun startRecordingSession() { + if (currentRecordingSession == null) { + currentRecordingSession = VideoRecordingSession( + configManager.config.videoRecordingFps.fps.toDouble(), configManager.config.videoRecordingSize + ) + + currentRecordingSession!!.startRecordingSession() + + logger.info("Recording session started") + + pipelineManager.pipelineOutputPosters.add(currentRecordingSession!!.matPoster) + } + } + + fun stopRecordingSession() { + currentRecordingSession?.let { itVideo -> + visualizer.pipelineSelectorPanel.buttonsPanel.pipelineRecordBtt.isEnabled = false + + itVideo.stopRecordingSession() + pipelineManager.pipelineOutputPosters.remove(itVideo.matPoster) + + logger.info("Recording session stopped") + + dialogFactory.createFileChooser( + visualizer.frame, DialogFactory.FileChooser.Mode.SAVE_FILE_SELECT, "", FileFilters.recordedVideoFilter + ).addCloseListener { _: Int, file: File?, _: FileFilter? -> + onMainUpdate.once { + if (file != null) { + var correctedFile = file + val extension = SysUtil.getExtensionByStringHandling(file.name) + if (!extension.isPresent || extension.get() != "avi") { + correctedFile = File(file.absolutePath + ".avi") + } + + + itVideo.saveTo(correctedFile) + } + + visualizer.pipelineSelectorPanel.buttonsPanel.pipelineRecordBtt.isEnabled = true + currentRecordingSession = null + } + } + } + } + +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/VideoRecordingSession.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/VideoRecordingSession.kt index d972600f..bcbc1731 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/VideoRecordingSession.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/VideoRecordingSession.kt @@ -1,34 +1,16 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.output import com.github.serivesmejia.eocvsim.gui.util.ThreadedMatPoster import com.github.serivesmejia.eocvsim.util.StrUtil -import io.github.deltacv.vision.external.util.extension.aspectRatio -import io.github.deltacv.vision.external.util.extension.clipTo +import org.deltacv.vision.external.util.extension.aspectRatio +import org.deltacv.vision.external.util.extension.clipTo import com.github.serivesmejia.eocvsim.util.fps.FpsCounter -import io.github.deltacv.common.image.MatPoster +import org.deltacv.common.image.MatPoster import org.opencv.core.* import org.opencv.imgproc.Imgproc import org.opencv.videoio.VideoWriter @@ -158,4 +140,4 @@ class VideoRecordingSession( } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java index 56046e24..6f598b7b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.pipeline; import android.graphics.Color; @@ -82,3 +64,4 @@ public void onDrawFrame(android.graphics.Canvas canvas, int onscreenWidth, int o } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt index 5585acfd..8e6d7707 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt @@ -1,68 +1,70 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.pipeline import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.config.ConfigManager import com.github.serivesmejia.eocvsim.gui.DialogFactory -import com.github.serivesmejia.eocvsim.pipeline.compiler.CompiledPipelineManager +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.input.InputSourceManager +import com.github.serivesmejia.eocvsim.pipeline.compiled.CompiledPipelineManager import com.github.serivesmejia.eocvsim.pipeline.handler.PipelineHandler import com.github.serivesmejia.eocvsim.pipeline.instantiator.DefaultPipelineInstantiator import com.github.serivesmejia.eocvsim.pipeline.instantiator.PipelineInstantiator import com.github.serivesmejia.eocvsim.pipeline.instantiator.processor.ProcessorInstantiator import com.github.serivesmejia.eocvsim.pipeline.util.PipelineExceptionTracker import com.github.serivesmejia.eocvsim.pipeline.util.PipelineSnapshot +import com.github.serivesmejia.eocvsim.tuner.TunableFieldRegistry +import com.github.serivesmejia.eocvsim.util.InitClasspathScan import com.github.serivesmejia.eocvsim.util.ReflectUtil import com.github.serivesmejia.eocvsim.util.StrUtil import com.github.serivesmejia.eocvsim.util.SysUtil import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.orchestration.initDependency +import com.github.serivesmejia.eocvsim.util.orchestration.runDependency +import com.github.serivesmejia.eocvsim.util.orchestration.PhaseOrchestrableBase import com.github.serivesmejia.eocvsim.util.fps.FpsCounter -import io.github.deltacv.common.util.loggerForThis -import io.github.deltacv.common.image.MatPoster -import io.github.deltacv.common.pipeline.util.PipelineStatisticsCalculator -import io.github.deltacv.eocvsim.virtualreflect.VirtualField -import io.github.deltacv.eocvsim.virtualreflect.VirtualReflection -import io.github.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection +import org.deltacv.common.image.MatPoster +import org.deltacv.common.pipeline.PipelineStatisticsCalculator +import org.deltacv.common.util.loggerForThis +import org.deltacv.eocvsim.virtualreflect.VirtualField +import org.deltacv.eocvsim.virtualreflect.VirtualReflection +import org.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection import kotlinx.coroutines.* import org.firstinspires.ftc.robotcore.external.Telemetry import org.firstinspires.ftc.robotcore.internal.opmode.EOCVSimTelemetryImpl import org.firstinspires.ftc.vision.VisionProcessor -import org.opencv.core.Mat +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named import org.openftc.easyopencv.OpenCvPipeline import org.openftc.easyopencv.OpenCvViewport import org.openftc.easyopencv.processFrameInternal -import java.util.* import kotlin.coroutines.EmptyCoroutineContext import kotlin.math.roundToLong @OptIn(DelicateCoroutinesApi::class) -class PipelineManager( - var eocvSim: EOCVSim, - val pipelineStatisticsCalculator: PipelineStatisticsCalculator -) { +class PipelineManager : PhaseOrchestrableBase(), KoinComponent { - companion object { + private val params: EOCVSim.Parameters by inject() + + private val classpathScan: InitClasspathScan by initDependency(inject()) + + private val dialogFactory: DialogFactory by inject() + private val configManager: ConfigManager by inject() + private val inputSourceManager: InputSourceManager by runDependency(inject()) + val pipelineStatisticsCalculator: PipelineStatisticsCalculator by inject() + private val visualizer: Visualizer by inject() + + private val onMainUpdate: EventHandler by inject(named("onMainLoop")) + private val scope: CoroutineScope by inject() + + val compiledPipelineManager: CompiledPipelineManager by initDependency(inject()) + companion object { var staticSnapshot: PipelineSnapshot? = null private set } @@ -72,17 +74,22 @@ class PipelineManager( @JvmField val onExternalSwitchingEnable = EventHandler("OnEnableExternalPipelineSwitching") + @JvmField val onExternalSwitchingDisable = EventHandler("OnDisableExternalPipelineSwitching") @JvmField val onUpdate = EventHandler("OnPipelineUpdate") + @JvmField val onPipelineChange = EventHandler("OnPipelineChange") + @JvmField val onPipelineTimeout = EventHandler("OnPipelineTimeout") + @JvmField val onPause = EventHandler("OnPipelinePause") + @JvmField val onResume = EventHandler("OnPipelineResume") @@ -91,7 +98,8 @@ class PipelineManager( val pipelineOutputPosters = ArrayList() val pipelineFpsCounter = FpsCounter() - private var hasInitCurrentPipeline = false + var hasInitCurrentPipeline = false + private set var lastPipelineAction = "processFrame" private set @@ -100,6 +108,7 @@ class PipelineManager( @Volatile var currentPipeline: OpenCvPipeline? = null private set + @Volatile var currentPipelineData: PipelineData? = null private set @@ -110,10 +119,6 @@ class PipelineManager( var previousPipelineIndex = 0 var virtualReflect: VirtualReflection = JvmVirtualReflection - set(value) { - eocvSim.tunerManager.setVirtualReflection(value) - field = value - } var reflectTarget: Any? = null private set @@ -159,13 +164,9 @@ class PipelineManager( // when getTunableFieldOf returns null, it means that // it wasn't able to find a suitable TunableField for // the passed Field type. - eocvSim.tunerManager.getTunableFieldClassOf(it) != null + TunableFieldRegistry.hasTunableFieldFor(it.type) } - //manages and builds pipelines in runtime - @JvmField - val compiledPipelineManager = CompiledPipelineManager(this) - private val pipelineHandlers = mutableListOf() private val pipelineInstantiators = mutableMapOf, PipelineInstantiator>() @@ -178,18 +179,14 @@ class PipelineManager( USER_REQUESTED, IMAGE_ONE_ANALYSIS, NOT_PAUSED } - fun init() { + override suspend fun init() { logger.info("Initializing...") //add default pipeline addPipelineClass(DefaultPipeline::class.java) - compiledPipelineManager.init() - - eocvSim.classpathScan.join() - //scan for pipelines - for (pipelineClass in eocvSim.classpathScan.scanResult.pipelineClasses) { + for (pipelineClass in classpathScan.scanResult!!.pipelineClasses) { addPipelineClass(pipelineClass) } @@ -214,7 +211,7 @@ class PipelineManager( val telemetry = currentTelemetry if (openedPipelineOutputCount <= 3) { - DialogFactory.createPipelineOutput(eocvSim) + dialogFactory.createPipelineOutput() openedPipelineOutputCount++ } @@ -238,7 +235,7 @@ class PipelineManager( onUpdate { if (currentPipeline != null) { for (pipelineHandler in pipelineHandlers) { - pipelineHandler.processFrame(eocvSim.inputSourceManager.currentInputSource) + pipelineHandler.processFrame(inputSourceManager.currentInputSource) } } } @@ -265,7 +262,7 @@ class PipelineManager( private fun applyStaticSnapOrDef() { onUpdate.once { if (!applyStaticSnapshot()) { - val params = eocvSim.params + val params = this.params // changing to the initial pipeline, defined by the eocv sim parameters or the default pipeline if (params.initialPipelineName != null) { @@ -284,7 +281,7 @@ class PipelineManager( private var wasBuildRunning = false - fun update(inputMat: Mat?) { + override suspend fun run() { val telemetry = currentTelemetry onUpdate.run() @@ -322,10 +319,12 @@ class PipelineManager( "processFrame" } + val inputMat = inputSourceManager.lastMatFromSource + pipelineStatisticsCalculator.newPipelineFrameStart() //run our pipeline in the background until it finishes or gets cancelled - val pipelineJob = GlobalScope.launch(currentPipelineContext!!) { + val pipelineJob = scope.launch(currentPipelineContext!!) { try { //if we have a pipeline, we run it right here, passing the input mat //given to us. we'll post the frame the pipeline returns as long @@ -407,39 +406,39 @@ class PipelineManager( pipelineStatisticsCalculator.endFrame() } - runBlocking { - val configTimeout = eocvSim.config.pipelineTimeout + val configTimeout = configManager.config.pipelineTimeout - //allow double timeout if we haven't initialized the pipeline - val timeout = if (hasInitCurrentPipeline) { - configTimeout.ms - } else { - (configTimeout.ms * 1.8).roundToLong() - } + //allow double timeout if we haven't initialized the pipeline + val timeout = if (hasInitCurrentPipeline) { + configTimeout.ms + } else { + (configTimeout.ms * 1.8).roundToLong() + } - try { - //ok! this is the part in which we'll wait for the pipeline with a timeout - withTimeout(timeout) { - pipelineJob.join() - } - } catch (_: TimeoutCancellationException) { - //oops, pipeline ran out of time! we'll fall back - //to default pipeline to avoid further issues. - requestForceChangePipeline(0) - //also call the event listeners in case - //someone wants to do something here - onPipelineTimeout.run() - - logger.warn("User pipeline $currentPipelineName took too long to $lastPipelineAction (more than $timeout ms), falling back to DefaultPipeline.") - } finally { - //we cancel our pipeline job so that it - //doesn't post the output mat from the - //pipeline if it ever returns. - pipelineJob.cancel() + try { + //ok! this is the part in which we'll wait for the pipeline with a timeout + withTimeout(timeout) { + pipelineJob.join() } + } catch (_: TimeoutCancellationException) { + //oops, pipeline ran out of time! we'll fall back + //to default pipeline to avoid further issues. + requestForceChangePipeline(0) + //also call the event listeners in case + //someone wants to do something here + onPipelineTimeout.run() + + logger.warn("User pipeline $currentPipelineName took too long to $lastPipelineAction (more than $timeout ms), falling back to DefaultPipeline.") + } finally { + //we cancel our pipeline job so that it + //doesn't post the output mat from the + //pipeline if it ever returns. + pipelineJob.cancel() } } + override suspend fun destroy() { } + private fun updateExceptionTracker(ex: Throwable? = null) { pipelineExceptionTracker.update( currentPipelineData ?: return, ex @@ -451,11 +450,11 @@ class PipelineManager( //similar to pipeline processFrame, call the user function in the background //and wait for some X timeout for the user to finisih doing what it has to do. - val viewportTappedJob = GlobalScope.launch(currentPipelineContext ?: EmptyCoroutineContext) { + val viewportTappedJob = scope.launch(currentPipelineContext ?: EmptyCoroutineContext) { pipeline.onViewportTapped() } - val configTimeoutMs = eocvSim.config.pipelineTimeout.ms + val configTimeoutMs = configManager.config.pipelineTimeout.ms try { //perform the timeout here (we'll block for a bit @@ -504,7 +503,7 @@ class PipelineManager( for ((instantiatorFor, instantiator) in pipelineInstantiators) { logger.debug( - " - Checking against instantiator for {} {}", + " - Checking against instantiator for {} loaded by {}", instantiatorFor.name, instantiatorFor.classLoader ) @@ -606,6 +605,7 @@ class PipelineManager( previousPipelineIndex = currentPipelineIndex previousPipeline = currentPipeline + hasInitCurrentPipeline = false currentPipeline = nextPipeline currentTelemetry = nextTelemetry currentPipelineName = clazz.simpleName @@ -627,16 +627,14 @@ class PipelineManager( if (applyStaticSnapshot) staticSnapshot?.transferTo(currentPipeline!!) - hasInitCurrentPipeline = false - currentPipelineContext?.close() currentPipelineContext = newSingleThreadContext("Pipeline-$currentPipelineName") activePipelineContexts.add(currentPipelineContext!!) setPaused(false) - if (eocvSim.configManager.config.pauseOnImages && pauseOnImages) { - eocvSim.inputSourceManager.pauseIfImageTwoFrames() + if (configManager.config.pauseOnImages && pauseOnImages) { + inputSourceManager.pauseIfImageTwoFrames() } onPipelineChange.run() @@ -694,7 +692,7 @@ class PipelineManager( applyStaticSnapshot = applyStaticSnapshot ) - eocvSim.visualizer.pipelineSelectorPanel.selectedIndex = index + visualizer.pipelineSelectorPanel.selectedIndex = index } /** @@ -800,7 +798,8 @@ class PipelineManager( @JvmOverloads fun requestSetPaused(paused: Boolean, pauseReason: PauseReason = PauseReason.USER_REQUESTED) { - eocvSim.onMainUpdate.once { setPaused(paused, pauseReason) } + onMainUpdate.once { setPaused(paused, pauseReason) } + } fun reloadPipelineByName() { @@ -856,3 +855,4 @@ enum class PipelineFps(val fps: Int, val coolName: String) { data class PipelineData(val source: PipelineSource, val clazz: Class<*>, val hidden: Boolean) enum class PipelineSource { CLASSPATH, COMPILED_ON_RUNTIME, ANONYMOUS } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/CompiledPipelineManager.kt similarity index 74% rename from EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt rename to EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/CompiledPipelineManager.kt index 9f8819f6..5cc96957 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/CompiledPipelineManager.kt @@ -1,27 +1,9 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline.compiler +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.pipeline.compiled import com.github.serivesmejia.eocvsim.Build import com.github.serivesmejia.eocvsim.gui.DialogFactory @@ -31,14 +13,30 @@ import com.github.serivesmejia.eocvsim.pipeline.PipelineSource import com.github.serivesmejia.eocvsim.util.StrUtil import com.github.serivesmejia.eocvsim.util.SysUtil import com.github.serivesmejia.eocvsim.util.event.EventHandler -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import com.github.serivesmejia.eocvsim.workspace.config.WorkspaceConfigLoader import com.github.serivesmejia.eocvsim.workspace.util.template.DefaultWorkspaceTemplate import com.qualcomm.robotcore.util.ElapsedTime import kotlinx.coroutines.* import java.io.File -class CompiledPipelineManager(private val pipelineManager: PipelineManager) { +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.util.orchestration.initDependency +import com.github.serivesmejia.eocvsim.util.orchestration.PhaseOrchestrableBase +import com.github.serivesmejia.eocvsim.workspace.WorkspaceManager +import org.koin.core.qualifier.named + +class CompiledPipelineManager : PhaseOrchestrableBase(), KoinComponent { + + private val onMainLoop: EventHandler by inject(named("onMainLoop")) + private val scope: CoroutineScope by inject() + + private val pipelineManager: PipelineManager by inject() + val workspaceManager: WorkspaceManager by initDependency(inject()) + private val visualizer: Visualizer by inject() + private val dialogFactory: DialogFactory by inject() companion object { val logger by loggerForThis() @@ -84,10 +82,23 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { var isBuildRunning = false private set - val workspaceManager get() = pipelineManager.eocvSim.workspaceManager - - fun init() { + override suspend fun init() { logger.info("Initializing...") + + onBuildStart { + onMainLoop.once { + visualizer.menuBar.workspCompile.isEnabled = false + visualizer.pipelineSelectorPanel.buttonsPanel.pipelineCompileBtt.isEnabled = false + } + } + + onBuildEnd { + onMainLoop.once { + visualizer.menuBar.workspCompile.isEnabled = true + visualizer.pipelineSelectorPanel.buttonsPanel.pipelineCompileBtt.isEnabled = true + } + } + asyncBuild() workspaceManager.onWorkspaceChange { @@ -95,6 +106,10 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { } } + override suspend fun run() { } + + override suspend fun destroy() { } + @OptIn(DelicateCoroutinesApi::class) suspend fun uncheckedBuild(): PipelineCompileResult { if(isBuildRunning) return PipelineCompileResult( @@ -151,8 +166,7 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { PipelineCompileStatus.NO_SOURCE -> { //delete jar if we had no sources, the most logical outcome in this case deleteJarFile() - if(pipelineManager.eocvSim.visualizer.hasFinishedInit()) - pipelineManager.onPipelineListRefresh.run() + pipelineManager.onPipelineListRefresh.run() "Build cancelled, no source files to compile $messageEnd" } @@ -173,15 +187,17 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { logger.warn("$lastBuildOutputMessage\n") if(result.status == PipelineCompileStatus.FAILED && !Output.isAlreadyOpened) - DialogFactory.createBuildOutput(pipelineManager.eocvSim) + withContext(Dispatchers.Main) { + dialogFactory.createBuildOutput() + } } - onBuildEnd.callRightAway = true + onBuildEnd.callRightAway = EventHandler.CallRightAway.InPlace onBuildEnd.run() - GlobalScope.launch { + scope.launch { delay(1000) - onBuildEnd.callRightAway = false + onBuildEnd.callRightAway = EventHandler.CallRightAway.Disabled } isBuildRunning = false @@ -209,7 +225,7 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { lastBuildResult = PipelineCompileResult(PipelineCompileStatus.FAILED, lastBuildOutputMessage!!) if(!Output.isAlreadyOpened) - DialogFactory.createBuildOutput(pipelineManager.eocvSim) + dialogFactory.createBuildOutput() lastBuildResult!! } @@ -218,10 +234,18 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { @OptIn(DelicateCoroutinesApi::class) fun asyncBuild( endCallback: (PipelineCompileResult) -> Unit = {} - ) = GlobalScope.launch(Dispatchers.IO) { - endCallback(build()) + ) = scope.launch(Dispatchers.IO) { + if(PipelineCompiler.IS_USABLE) { + endCallback(build()) + } else { + onMainLoop.once { + visualizer.compilerUnsupported() + } + } } + val isCompilerSupported get() = PipelineCompiler.IS_USABLE + private fun deleteJarFile() { if(PIPELINES_OUTPUT_JAR.exists()) PIPELINES_OUTPUT_JAR.delete() currentPipelineClassLoader = null @@ -251,3 +275,4 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { } private fun File.mkdirLazy() = apply { mkdir() } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineClassLoader.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/PipelineClassLoader.kt similarity index 87% rename from EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineClassLoader.kt rename to EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/PipelineClassLoader.kt index e4137f28..297857b2 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineClassLoader.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/PipelineClassLoader.kt @@ -1,11 +1,15 @@ -package com.github.serivesmejia.eocvsim.pipeline.compiler +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.pipeline.compiled import com.github.serivesmejia.eocvsim.util.ClasspathScan import com.github.serivesmejia.eocvsim.util.SysUtil import com.github.serivesmejia.eocvsim.util.extension.removeFromEnd -import io.github.deltacv.eocvsim.sandbox.restrictions.MethodCallByteCodeChecker -import io.github.deltacv.eocvsim.sandbox.restrictions.dynamicCodeMethodBlacklist -import io.github.deltacv.eocvsim.sandbox.restrictions.dynamicCodePackageBlacklist +import org.deltacv.eocvsim.sandbox.restrictions.MethodCallByteCodeChecker +import org.deltacv.eocvsim.sandbox.restrictions.dynamicCodeMethodBlacklist import org.openftc.easyopencv.OpenCvPipeline import java.io.ByteArrayOutputStream import java.io.File @@ -82,3 +86,4 @@ class PipelineClassLoader(pipelinesJar: File) : ClassLoader() { val OpenCvPipeline.isFromRuntimeCompilation: Boolean get() = this::class.java.classLoader is PipelineClassLoader + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineCompiler.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/PipelineCompiler.kt similarity index 80% rename from EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineCompiler.kt rename to EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/PipelineCompiler.kt index d5ade4e5..f80315e4 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineCompiler.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/PipelineCompiler.kt @@ -1,32 +1,14 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline.compiler +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.pipeline.compiled import com.github.serivesmejia.eocvsim.util.* import com.github.serivesmejia.eocvsim.util.compiler.JarPacker import com.github.serivesmejia.eocvsim.util.compiler.compiler -import io.github.deltacv.common.util.loggerFor +import org.deltacv.common.util.loggerFor import java.io.File import java.io.PrintWriter import java.nio.charset.Charset @@ -177,4 +159,4 @@ enum class PipelineCompileStatus { NO_SOURCE } -data class PipelineCompileResult(val status: PipelineCompileStatus, val message: String) \ No newline at end of file +data class PipelineCompileResult(val status: PipelineCompileStatus, val message: String) diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineStandardFileManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/PipelineStandardFileManager.kt similarity index 59% rename from EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineStandardFileManager.kt rename to EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/PipelineStandardFileManager.kt index f15c0904..7bdf6be0 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineStandardFileManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiled/PipelineStandardFileManager.kt @@ -1,31 +1,13 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline.compiler +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.pipeline.compiled import com.github.serivesmejia.eocvsim.util.SysUtil import com.github.serivesmejia.eocvsim.util.compiler.DelegatingStandardFileManager -import io.github.deltacv.common.util.loggerFor +import org.deltacv.common.util.loggerFor import java.io.File import java.util.* import javax.tools.StandardJavaFileManager @@ -71,3 +53,4 @@ class PipelineStandardFileManager(delegate: StandardJavaFileManager) : Delegatin } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/handler/PipelineHandler.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/handler/PipelineHandler.kt index 81c33913..6307976a 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/handler/PipelineHandler.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/handler/PipelineHandler.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.pipeline.handler import com.github.serivesmejia.eocvsim.input.InputSource @@ -39,4 +21,4 @@ interface PipelineHandler { fun onChange(beforePipeline: OpenCvPipeline?, newPipeline: OpenCvPipeline, telemetry: Telemetry) -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/handler/SpecificPipelineHandler.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/handler/SpecificPipelineHandler.kt index a446e210..fa992695 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/handler/SpecificPipelineHandler.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/handler/SpecificPipelineHandler.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.pipeline.handler import org.firstinspires.ftc.robotcore.external.Telemetry @@ -48,4 +30,4 @@ abstract class SpecificPipelineHandler( } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/processor/ProcessorInstantiator.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/processor/ProcessorInstantiator.kt index ca591cb4..649e894d 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/processor/ProcessorInstantiator.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/processor/ProcessorInstantiator.kt @@ -1,31 +1,13 @@ /* * Copyright (c) 2023 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.pipeline.instantiator.processor import com.github.serivesmejia.eocvsim.pipeline.instantiator.PipelineInstantiator import com.github.serivesmejia.eocvsim.util.ReflectUtil -import io.github.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection +import org.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection import org.firstinspires.ftc.robotcore.external.Telemetry import org.firstinspires.ftc.vision.VisionProcessor import org.openftc.easyopencv.OpenCvPipeline @@ -52,4 +34,4 @@ object ProcessorInstantiator : PipelineInstantiator { override fun variableTunerTarget(pipeline: OpenCvPipeline): VisionProcessor = (pipeline as ProcessorPipeline).processor -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/processor/ProcessorPipeline.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/processor/ProcessorPipeline.java index f3b184f6..96fd54d3 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/processor/ProcessorPipeline.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/instantiator/processor/ProcessorPipeline.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.pipeline.instantiator.processor; import android.graphics.Canvas; @@ -55,3 +37,4 @@ public void onDrawFrame(Canvas canvas, int onscreenWidth, int onscreenHeight, fl } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt index 67e359f9..6d620529 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt @@ -1,32 +1,15 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.pipeline.util import com.github.serivesmejia.eocvsim.pipeline.PipelineData import com.github.serivesmejia.eocvsim.pipeline.PipelineManager import com.github.serivesmejia.eocvsim.util.event.EventHandler import com.github.serivesmejia.eocvsim.util.StrUtil -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis class PipelineExceptionTracker(private val pipelineManager: PipelineManager) { @@ -161,3 +144,4 @@ class PipelineExceptionTracker(private val pipelineManager: PipelineManager) { var millisThrown: Long) } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineSnapshot.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineSnapshot.kt index 78bb310d..bbc4b74a 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineSnapshot.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineSnapshot.kt @@ -1,33 +1,15 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.pipeline.util -import io.github.deltacv.common.util.loggerForThis -import io.github.deltacv.eocvsim.virtualreflect.VirtualField -import io.github.deltacv.eocvsim.virtualreflect.VirtualReflectContext -import io.github.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflectContext -import io.github.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection +import org.deltacv.common.util.loggerForThis +import org.deltacv.eocvsim.virtualreflect.VirtualField +import org.deltacv.eocvsim.virtualreflect.VirtualReflectContext +import org.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflectContext +import org.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection import org.openftc.easyopencv.OpenCvPipeline import java.util.* @@ -131,3 +113,4 @@ class PipelineSnapshot(val virtualReflectContext: VirtualReflectContext, filter: } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/ConfigApiImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/ConfigApiImpl.kt index 51e9e704..887859de 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/ConfigApiImpl.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/ConfigApiImpl.kt @@ -1,31 +1,13 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.plugin.api.impl import com.github.serivesmejia.eocvsim.config.ConfigManager -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.ConfigApi +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.ConfigApi class ConfigApiImpl(owner: EOCVSimPlugin, val internalConfigManager: ConfigManager) : ConfigApi(owner) { override fun putFlag(flag: String) = apiImpl { @@ -43,4 +25,4 @@ class ConfigApiImpl(owner: EOCVSimPlugin, val internalConfigManager: ConfigManag override fun disableApi() { internalConfigManager.saveToFile() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/DialogFactoryApiImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/DialogFactoryApiImpl.kt index b59f1f30..9bf6e0e0 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/DialogFactoryApiImpl.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/DialogFactoryApiImpl.kt @@ -1,47 +1,34 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.plugin.api.impl import com.github.serivesmejia.eocvsim.gui.DialogFactory import com.github.serivesmejia.eocvsim.gui.Visualizer import com.github.serivesmejia.eocvsim.input.SourceType import com.github.serivesmejia.eocvsim.util.event.EventHandler -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.DialogFactoryApi -import io.github.deltacv.eocvsim.plugin.api.HookApi -import io.github.deltacv.eocvsim.plugin.api.InputSourceApi -import io.github.deltacv.eocvsim.plugin.api.JFileChooserApi +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.DialogFactoryApi +import org.deltacv.eocvsim.plugin.api.HookApi +import org.deltacv.eocvsim.plugin.api.InputSourceApi +import org.deltacv.eocvsim.plugin.api.JFileChooserApi +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.io.File import javax.swing.filechooser.FileFilter -class DialogFactoryApiImpl(owner: EOCVSimPlugin, val internalVisualizer: Visualizer) : DialogFactoryApi(owner) { +class DialogFactoryApiImpl(owner: EOCVSimPlugin, val internalVisualizer: Visualizer) : DialogFactoryApi(owner), KoinComponent { + + val dialogFactory: DialogFactory by inject() + override fun createYesOrNo( message: String, subMessage: String, result: (Boolean) -> Unit ) = apiImpl { - DialogFactory.createYesOrNo(internalVisualizer.frame, message, subMessage) { + dialogFactory.createYesOrNo(internalVisualizer.frame, message, subMessage) { result(it == 0) } } @@ -57,7 +44,7 @@ class DialogFactoryApiImpl(owner: EOCVSimPlugin, val internalVisualizer: Visuali JFileChooserApi.Mode.SAVE_FILE -> DialogFactory.FileChooser.Mode.SAVE_FILE_SELECT } - val fileChooser = DialogFactory.createFileChooser( + val fileChooser = dialogFactory.createFileChooser( internalVisualizer.frame, internalMode, initialFileName, @@ -78,31 +65,31 @@ class DialogFactoryApiImpl(owner: EOCVSimPlugin, val internalVisualizer: Visuali InputSourceApi.Type.HTTP -> SourceType.HTTP } - DialogFactory.createSourceDialog(internalVisualizer.eocvSim, type, initialFile) + dialogFactory.createSourceDialog(type, initialFile) } override fun createSourceDialog() = apiImpl { - DialogFactory.createSourceExDialog(internalVisualizer.eocvSim) + dialogFactory.createSourceExDialog() } override fun createConfigDialog() = apiImpl { - DialogFactory.createConfigDialog(internalVisualizer.eocvSim) + dialogFactory.createConfigDialog() } override fun createAboutDialog() = apiImpl { - DialogFactory.createAboutDialog(internalVisualizer.eocvSim) + dialogFactory.createAboutDialog() } override fun createOutputDialog(wasManuallyOpened: Boolean) = apiImpl { - DialogFactory.createOutput(internalVisualizer.eocvSim, wasManuallyOpened) + dialogFactory.createOutput(wasManuallyOpened) } override fun createBuildOutputDialog() = apiImpl { - DialogFactory.createBuildOutput(internalVisualizer.eocvSim) + dialogFactory.createBuildOutput() } override fun createPipelineOutputDialog() = apiImpl { - DialogFactory.createPipelineOutput(internalVisualizer.eocvSim) + dialogFactory.createPipelineOutput() } override fun createSplashScreen(closeHook: HookApi) = apiImpl { @@ -111,24 +98,24 @@ class DialogFactoryApiImpl(owner: EOCVSimPlugin, val internalVisualizer: Visuali internalCloseHandler.run() } - DialogFactory.createSplashScreen(internalCloseHandler) + dialogFactory.createSplashScreen(internalCloseHandler) } override fun createIAmADialog() = apiImpl { - DialogFactory.createIAmA(internalVisualizer) + dialogFactory.createIAmA() } override fun createIAmAPaperVisionDialog(showWorkspacesButton: Boolean) = apiImpl { - DialogFactory.createIAmAPaperVision(internalVisualizer, showWorkspacesButton) + dialogFactory.createIAmAPaperVision(showWorkspacesButton) } override fun createWorkspaceDialog() = apiImpl { - DialogFactory.createWorkspace(internalVisualizer) + dialogFactory.createWorkspace() } override fun createCrashReportDialog(report: String) = apiImpl { - DialogFactory.createCrashReport(internalVisualizer, report) + dialogFactory.createCrashReport(report) } override fun disableApi() { } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/EOCVSimApiImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/EOCVSimApiImpl.kt index d5035a4f..53e9b691 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/EOCVSimApiImpl.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/EOCVSimApiImpl.kt @@ -1,50 +1,51 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.plugin.api.impl import com.github.serivesmejia.eocvsim.EOCVSim -import io.github.deltacv.common.util.loggerForThis -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.ConfigApi -import io.github.deltacv.eocvsim.plugin.api.EOCVSimApi -import io.github.deltacv.eocvsim.plugin.api.InputSourceManagerApi -import io.github.deltacv.eocvsim.plugin.api.PipelineManagerApi -import io.github.deltacv.eocvsim.plugin.api.VariableTunerApi -import io.github.deltacv.eocvsim.plugin.api.VisualizerApi - -class EOCVSimApiImpl(owner: EOCVSimPlugin, val internalEOCVSim: EOCVSim) : EOCVSimApi(owner) { +import org.deltacv.common.util.loggerForThis +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.ConfigApi +import org.deltacv.eocvsim.plugin.api.EOCVSimApi +import org.deltacv.eocvsim.plugin.api.InputSourceManagerApi +import org.deltacv.eocvsim.plugin.api.PipelineManagerApi +import org.deltacv.eocvsim.plugin.api.VariableTunerApi +import org.deltacv.eocvsim.plugin.api.VisualizerApi + +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.input.InputSourceManager +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.tuner.TunerManager +import com.github.serivesmejia.eocvsim.config.ConfigManager + +class EOCVSimApiImpl(owner: EOCVSimPlugin) : EOCVSimApi(owner), KoinComponent { private val logger by loggerForThis() - override val mainLoopHook by apiField { EventHandlerHookApiImpl(owner, internalEOCVSim.onMainUpdate) } + private val onMainUpdate: EventHandler by inject(named("onMainLoop")) + private val visualizer: Visualizer by inject() + private val inputSourceManager: InputSourceManager by inject() + private val pipelineManager: PipelineManager by inject() + private val tunerManager: TunerManager by inject() + private val configManager: ConfigManager by inject() + + val internalEOCVSim: EOCVSim by inject() + + override val mainLoopHook by apiField { EventHandlerHookApiImpl(owner, onMainUpdate) } - override val visualizerApi: VisualizerApi by apiField(VisualizerApiImpl(owner, internalEOCVSim.visualizer)) - override val inputSourceManagerApi: InputSourceManagerApi by apiField(InputSourceManagerApiImpl(owner, internalEOCVSim.inputSourceManager)) - override val pipelineManagerApi: PipelineManagerApi by apiField(PipelineManagerApiImpl(owner, internalEOCVSim.pipelineManager)) - override val variableTunerApi: VariableTunerApi by apiField(VariableTunerApiImpl(owner, internalEOCVSim.tunerManager)) - override val configApi: ConfigApi by apiField(ConfigApiImpl(owner, internalEOCVSim.configManager)) + override val visualizerApi: VisualizerApi by apiField(VisualizerApiImpl(owner, visualizer)) + override val inputSourceManagerApi: InputSourceManagerApi by apiField(InputSourceManagerApiImpl(owner, inputSourceManager)) + override val pipelineManagerApi: PipelineManagerApi by apiField(PipelineManagerApiImpl(owner, pipelineManager)) + override val variableTunerApi: VariableTunerApi by apiField(VariableTunerApiImpl(owner, tunerManager)) + override val configApi: ConfigApi by apiField(ConfigApiImpl(owner, configManager)) override fun disableApi() { - logger.info("EOCV-Sim API for {} says: \"aight, time to check out\"", ownerName) + logger.info("API for {} says: \"ight, imma head out\"", ownerName) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/EventHandlerHookApiImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/EventHandlerHookApiImpl.kt index d4e4c8fa..3d85f119 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/EventHandlerHookApiImpl.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/EventHandlerHookApiImpl.kt @@ -1,34 +1,14 @@ /* * Copyright (c) 2026 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.plugin.api.impl import com.github.serivesmejia.eocvsim.util.event.EventHandler -import com.github.serivesmejia.eocvsim.util.event.EventListener import com.github.serivesmejia.eocvsim.util.event.EventListenerId -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.HookApi -import java.lang.ref.WeakReference +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.HookApi class EventHandlerHookApiImpl(owner: EOCVSimPlugin, val eventHandler: EventHandler) : HookApi(owner) { private var listeners = mutableListOf() @@ -57,4 +37,4 @@ class EventHandlerHookApiImpl(owner: EOCVSimPlugin, val eventHandler: EventHandl eventHandler.removeListener(id) } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/InputSourceApisImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/InputSourceApisImpl.kt index a05a30ab..ffda12a8 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/InputSourceApisImpl.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/InputSourceApisImpl.kt @@ -1,24 +1,6 @@ /* * Copyright (c) 2026 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.plugin.api.impl @@ -28,18 +10,24 @@ import com.github.serivesmejia.eocvsim.input.source.CameraSource import com.github.serivesmejia.eocvsim.input.source.HttpSource import com.github.serivesmejia.eocvsim.input.source.ImageSource import com.github.serivesmejia.eocvsim.input.source.VideoSource -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.InputSourceApi -import io.github.deltacv.eocvsim.plugin.api.InputSourceManagerApi +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.InputSourceApi +import org.deltacv.eocvsim.plugin.api.InputSourceManagerApi +import org.opencv.core.Size class InputSourceApiImpl(owner: EOCVSimPlugin, val internalInputSource: com.github.serivesmejia.eocvsim.input.InputSource) : InputSourceApi(owner) { - override val isPaused by liveApiField { internalInputSource.paused } + override val isPaused by liveApiField { internalInputSource.isPaused } override val data by apiField { when(internalInputSource) { - is CameraSource -> New.Camera(internalInputSource.webcamName, internalInputSource.size) - is ImageSource -> New.Image(internalInputSource.imgPath, internalInputSource.size) - is VideoSource -> New.Video(internalInputSource.videoPath, internalInputSource.size) + is CameraSource -> New.Camera( + internalInputSource.camera?.name ?: "", + Size(internalInputSource.videoMode?.width?.toDouble() ?: 0.0, + internalInputSource.videoMode?.height?.toDouble() ?: 0.0 + ) + ) + is ImageSource -> New.Image(internalInputSource.imgPath, internalInputSource.sourceSize) + is VideoSource -> New.Video(internalInputSource.videoPath, internalInputSource.sourceSize) is HttpSource -> New.Http(internalInputSource.url) else -> throw IllegalStateException("Unknown input source type: ${internalInputSource::class.java}") } @@ -48,16 +36,18 @@ class InputSourceApiImpl(owner: EOCVSimPlugin, val internalInputSource: com.gith override val name: String by apiField { internalInputSource.name } override val creationTime by apiField { internalInputSource.creationTime } + + override fun disableApi() { } } class InputSourceManagerApiImpl(owner: EOCVSimPlugin, val internalInputSourceManager: InputSourceManager) : InputSourceManagerApi(owner) { - override val allSources: List by liveApiField { + override val allSources: List by liveApiField> { internalInputSourceManager.sources.values.map { InputSourceApiImpl(owner, it) } } - override val currentSource: InputSourceApi? by liveNullableApiField { + override val currentSource: InputSourceApi? by liveNullableApiField { internalInputSourceManager.currentInputSource?.let { InputSourceApiImpl(owner, it) } } @@ -67,7 +57,7 @@ class InputSourceManagerApiImpl(owner: EOCVSimPlugin, val internalInputSourceMan override fun addInputSource(name: String, aNew: InputSourceApi.New) = apiImpl { val source = when(aNew) { - is InputSourceApi.New.Camera -> CameraSource(aNew.cameraName, aNew.size) + is InputSourceApi.New.Camera -> TODO() // CameraSource(aNew.cameraName, aNew.size) is InputSourceApi.New.Image -> ImageSource(aNew.filePath, aNew.size) is InputSourceApi.New.Video -> VideoSource(aNew.filePath, aNew.size) is InputSourceApi.New.Http -> HttpSource(aNew.url) @@ -84,4 +74,4 @@ class InputSourceManagerApiImpl(owner: EOCVSimPlugin, val internalInputSourceMan } override fun disableApi() { } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/PipelineManagerApiImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/PipelineManagerApiImpl.kt index 98fcf9f1..5c98cbf1 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/PipelineManagerApiImpl.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/PipelineManagerApiImpl.kt @@ -1,34 +1,16 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.plugin.api.impl import com.github.serivesmejia.eocvsim.pipeline.PipelineManager import com.github.serivesmejia.eocvsim.pipeline.instantiator.PipelineInstantiator -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.PipelineInstantiatorApi -import io.github.deltacv.eocvsim.plugin.api.PipelineManagerApi -import io.github.deltacv.eocvsim.plugin.api.PipelineManagerApi.PipelineSource +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.PipelineInstantiatorApi +import org.deltacv.eocvsim.plugin.api.PipelineManagerApi +import org.deltacv.eocvsim.plugin.api.PipelineManagerApi.PipelineSource import org.firstinspires.ftc.robotcore.external.Telemetry import org.openftc.easyopencv.OpenCvPipeline @@ -195,4 +177,4 @@ private fun PipelineManager.PauseReason.mapToApi() = when(this) { PipelineManager.PauseReason.NOT_PAUSED -> PipelineManagerApi.PipelinePauseReason.NOT_PAUSED PipelineManager.PauseReason.USER_REQUESTED -> PipelineManagerApi.PipelinePauseReason.USER_REQUESTED PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS -> PipelineManagerApi.PipelinePauseReason.IMAGE_SINGLE_SHOT -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/SimpleHookApiImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/SimpleHookApiImpl.kt index ed1943bb..83190cbe 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/SimpleHookApiImpl.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/SimpleHookApiImpl.kt @@ -1,30 +1,12 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.plugin.api.impl -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.HookApi +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.HookApi class SimpleHookApiImpl(owner: EOCVSimPlugin) : HookApi(owner) { @@ -60,3 +42,4 @@ class SimpleHookApiImpl(owner: EOCVSimPlugin) : HookApi(owner) { persistentHooks.clear() } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/SwingApisImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/SwingApisImpl.kt index 0f467db4..ed88c0a6 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/SwingApisImpl.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/SwingApisImpl.kt @@ -1,33 +1,15 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.plugin.api.impl import com.github.serivesmejia.eocvsim.gui.DialogFactory -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.JFileChooserApi -import io.github.deltacv.eocvsim.plugin.api.JMenuApi -import io.github.deltacv.eocvsim.plugin.api.JMenuItemApi +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.JFileChooserApi +import org.deltacv.eocvsim.plugin.api.JMenuApi +import org.deltacv.eocvsim.plugin.api.JMenuItemApi import java.io.File import javax.swing.JFileChooser import javax.swing.JMenu @@ -124,13 +106,17 @@ class JMenuItemApiImpl(owner: EOCVSimPlugin, val internalMenuItem: JMenuItem) : class JFileChooserApiImpl(owner: EOCVSimPlugin, val internalFileChooser: DialogFactory.FileChooser) : JFileChooserApi(owner) { override fun addCloseListener(listener: (Result, File, FileFilter) -> Unit) { internalFileChooser.addCloseListener { i, file, filter -> + val dummyFilter = javax.swing.filechooser.FileNameExtensionFilter("", "") + val finalFile = file ?: File("") + val finalFilter = filter ?: dummyFilter + when(i) { - JFileChooser.APPROVE_OPTION -> listener(Result.APPROVE, file, filter) - JFileChooser.CANCEL_OPTION -> listener(Result.CANCEL, file, filter) - else -> listener(Result.ERROR, file, filter) + JFileChooser.APPROVE_OPTION -> listener(Result.APPROVE, finalFile, finalFilter) + JFileChooser.CANCEL_OPTION -> listener(Result.CANCEL, finalFile, finalFilter) + else -> listener(Result.ERROR, finalFile, finalFilter) } } } override fun disableApi() { } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VariableTunerApiImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VariableTunerApiImpl.kt index 38fd0c8d..a1add672 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VariableTunerApiImpl.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VariableTunerApiImpl.kt @@ -1,40 +1,22 @@ /* * Copyright (c) 2026 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.plugin.api.impl import com.github.serivesmejia.eocvsim.tuner.TunableField import com.github.serivesmejia.eocvsim.tuner.TunerManager -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.TunableFieldApi -import io.github.deltacv.eocvsim.plugin.api.VariableTunerApi -import io.github.deltacv.eocvsim.virtualreflect.VirtualField +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.TunableFieldApi +import org.deltacv.eocvsim.plugin.api.VariableTunerApi +import org.deltacv.eocvsim.virtualreflect.VirtualField class TunableFieldApiImpl(owner: EOCVSimPlugin, val internalTunableField: TunableField<*>) : TunableFieldApi(owner) { override val field: VirtualField by liveApiField { internalTunableField.reflectionField } - override fun setFieldValue(index: Int, value: Any) = apiImpl { - internalTunableField.setFieldValue(index, value) + override fun setFieldValue(index: Int, value: Any) = apiImpl { + internalTunableField.tunableValues.getOrNull(index)?.setAnyFromGui(value) } override fun disableApi() { } @@ -45,7 +27,8 @@ class VariableTunerApiImpl(owner: EOCVSimPlugin, val internalTunerManager: Tuner virtualField: VirtualField, pipeline: Any ) = apiImpl { - val tunableField = internalTunerManager.newTunableFieldInstanceFor(virtualField, pipeline) + val tunableField = internalTunerManager.newTunableFieldInstanceFor(virtualField, pipeline) + if (tunableField == null) return@apiImpl null TunableFieldApiImpl(owner, tunableField) } @@ -56,4 +39,4 @@ class VariableTunerApiImpl(owner: EOCVSimPlugin, val internalTunerManager: Tuner } override fun disableApi() { } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VisualizerApiImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VisualizerApiImpl.kt index b9156e42..e959f4a4 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VisualizerApiImpl.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VisualizerApiImpl.kt @@ -1,36 +1,16 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.plugin.api.impl import com.github.serivesmejia.eocvsim.gui.Visualizer import com.github.serivesmejia.eocvsim.gui.component.visualizer.SidebarPanel import com.github.serivesmejia.eocvsim.gui.component.visualizer.TopMenuBar -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.DialogFactoryApi -import io.github.deltacv.eocvsim.plugin.api.VisualizerApi -import io.github.deltacv.eocvsim.plugin.api.VisualizerSidebarApi -import io.github.deltacv.eocvsim.plugin.api.VisualizerTopMenuBarApi +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.* +import org.deltacv.vision.external.gui.SwingOpenCvViewport class VisualizerApiImpl(owner: EOCVSimPlugin, val internalVisualizer: Visualizer) : VisualizerApi(owner) { override val frame by liveNullableApiField { internalVisualizer.frame } @@ -39,6 +19,8 @@ class VisualizerApiImpl(owner: EOCVSimPlugin, val internalVisualizer: Visualizer override val topMenuBarApi: VisualizerTopMenuBarApi by apiField { VisualizerTopMenuBarApiImpl(owner, internalVisualizer.menuBar) } override val sidebarApi: VisualizerSidebarApi by apiField { VisualizerSidebarApiImpl(owner, internalVisualizer.sidebarPanel) } + override val viewportApi: VisualizerViewportApi by apiField { VisualizerViewportApiImpl(owner, internalVisualizer.viewport) } + override val visualizerComponentsFactoryApi: VisualizerComponentsFactoryApi by apiField { VisualizerComponentsFactoryApiImpl(owner) } override val dialogFactoryApi: DialogFactoryApi by apiField { DialogFactoryApiImpl(owner, internalVisualizer) } override fun disableApi() { } @@ -94,4 +76,20 @@ private class VisualizerSidebarApiImpl(owner: EOCVSimPlugin, val internalSidebar tabs.keys.forEach { removeTab(it) } tabs.clear() } -} \ No newline at end of file +} + +class VisualizerViewportApiImpl(owner: EOCVSimPlugin, val internalViewport: SwingOpenCvViewport) : VisualizerViewportApi(owner) { + override fun activate() = apiImpl { + internalViewport.activate() + } + + override fun deactivate() = apiImpl { + internalViewport.deactivate() + } + + override fun setFpsMeterEnabled(enabled: Boolean) = apiImpl { + internalViewport.renderer.setFpsMeterEnabled(enabled) + } + + override fun disableApi() { } +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VisualizerComponentsApiImpl.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VisualizerComponentsApiImpl.kt new file mode 100644 index 00000000..be975275 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/api/impl/VisualizerComponentsApiImpl.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.plugin.api.impl + +import com.github.serivesmejia.eocvsim.gui.component.visualizer.TelemetryPanel +import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.PipelineSelectorPanel +import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.SourceSelectorPanel +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.PipelineSelectorPanelApi +import org.deltacv.eocvsim.plugin.api.SourceSelectorPanelApi +import org.deltacv.eocvsim.plugin.api.TelemetryPanelApi +import org.deltacv.eocvsim.plugin.api.VisualizerComponentsFactoryApi + +class VisualizerComponentsFactoryApiImpl(owner: EOCVSimPlugin) : VisualizerComponentsFactoryApi(owner) { + override fun createPipelineSelectorPanel() = apiImpl { PipelineSelectorPanelApiImpl(owner, PipelineSelectorPanel()) } + override fun createSourceSelectorPanel() = apiImpl { SourceSelectorPanelApiImpl(owner, SourceSelectorPanel()) } + override fun createTelemetryPanel() = apiImpl { TelemetryPanelApiImpl(owner, TelemetryPanel()) } + + override fun disableApi() { } +} + +class PipelineSelectorPanelApiImpl(owner: EOCVSimPlugin, val internalPanel: PipelineSelectorPanel) : PipelineSelectorPanelApi(owner) { + + override val jPanel by apiField(internalPanel) + + override var isInteractionEnabled: Boolean + get() = apiImpl { internalPanel.pipelineSelectorScroll.isEnabled } + set(value) = apiImpl { internalPanel.pipelineSelectorScroll.isEnabled = value } + + override var allowSwitching: Boolean + get() = apiImpl { internalPanel.allowPipelineSwitching } + set(value) = apiImpl { internalPanel.allowPipelineSwitching = value } + + override val selectedPipelineName: String? by liveApiField { internalPanel.pipelineSelector.selectedValue } + override val selectedPipelineIndex by liveApiField { internalPanel.pipelineSelector.selectedIndex } + + override fun refresh() = apiImpl { + internalPanel.updatePipelinesList() + } + + override fun disableApi() { } + +} + +class SourceSelectorPanelApiImpl(owner: EOCVSimPlugin, val internalPanel: SourceSelectorPanel) : SourceSelectorPanelApi(owner) { + + override val jPanel by apiField(internalPanel) + + override var isInteractionEnabled: Boolean + get() = apiImpl { internalPanel.sourceSelectorScroll.isEnabled } + set(value) = apiImpl { internalPanel.sourceSelectorScroll.isEnabled = value } + + override var allowSwitching: Boolean + get() = apiImpl { internalPanel.allowSourceSwitching } + set(value) = apiImpl { internalPanel.allowSourceSwitching = value } + + override val selectedSourceName: String? by liveApiField { internalPanel.sourceSelector.selectedValue } + override val selectedSourceIndex by liveApiField { internalPanel.sourceSelector.selectedIndex } + + override fun refresh() = apiImpl { + internalPanel.updateSourcesList() + } + + override fun disableApi() { } + +} + +class TelemetryPanelApiImpl(owner: EOCVSimPlugin, val internalPanel: TelemetryPanel) : TelemetryPanelApi(owner) { + override val jPanel by apiField(internalPanel) + + override fun update(text: String, captionSeparator: String, itemSeparator: String) = apiImpl { + internalPanel.updateTelemetry(text, captionSeparator, itemSeparator) + } + + override fun clear() = apiImpl { + internalPanel.telemetryList.removeAll() + } + + override fun disableApi() { } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/output/VisualPluginOutputHandler.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/output/VisualPluginOutputHandler.kt new file mode 100644 index 00000000..304d79c6 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/plugin/output/VisualPluginOutputHandler.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2024 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.plugin.output + +import com.github.serivesmejia.eocvsim.util.event.ParamEventHandler +import org.deltacv.common.util.loggerOf +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.withTimeoutOrNull +import kotlin.time.Duration.Companion.milliseconds + +/** + * Concrete implementation of PluginOutputHandler. + * + * Proper coroutine-based design: + * - Uses [withTimeoutOrNull] for timeout handling (not thread hacks) + * - Emits structured events, not magic strings + * - Manages continuation via [CompletableDeferred] suspension + */ +class VisualPluginOutputHandler : PluginOutputHandler { + + private val logger by loggerOf("PluginOutputHandler") + + // Continuation signal: plugin code awaits this, UI completes it + private var continuationDeferred: CompletableDeferred = CompletableDeferred() + + override val onOutput = ParamEventHandler("PluginOutput") + override val onDialogSignal = ParamEventHandler("PluginDialogSignal") + + /** + * Sends an output message. Emitted via [onOutput] event and logged. + */ + override fun sendOutput(message: String) { + // Log (trim leading newlines/spaces) + val trimmed = message.trim() + if (trimmed.isNotEmpty()) { + logger.info(trimmed) + } + + // Emit event + onOutput.run(message) + } + + /** + * Sends a dialog control signal (structured, not magic codes). + */ + override fun sendDialogSignal(signal: PluginDialogSignal) { + onDialogSignal.run(signal) + } + + /** + * Waits for continuation from the UI. + * + * Uses [withTimeoutOrNull] for coroutine-native timeout handling: + * - No daemon threads + * - Proper suspension (not sleep) + * - Clean timeout semantics + * + * @param timeoutMillis timeout in milliseconds (0 = wait indefinitely) + * @return true if completed by UI, false if timeout expired + */ + override suspend fun waitForContinuation(timeoutMillis: Long): Boolean { + if (timeoutMillis > 0L) { + sendOutputLine("Waiting for confirmation for ${timeoutMillis / 1000} seconds...") + } else { + sendOutputLine("Waiting for confirmation...") + } + + val deferred = continuationDeferred + + return if (timeoutMillis > 0L) { + // Use withTimeoutOrNull for coroutine-native timeout + val result = withTimeoutOrNull(timeoutMillis.milliseconds) { + deferred.await() + true + } + + if (result == null) { + // Timeout expired + logger.warn("Plugin output continuation timed out after ${timeoutMillis}ms") + false + } else { + // Completed by UI + true + } + } else { + // No timeout, wait indefinitely + deferred.await() + true + }.also { + // Reset for next cycle + continuationDeferred = CompletableDeferred() + } + } + + /** + * UI calls this to signal that continuation is complete (e.g., user clicked "Continue"). + * This allows plugin code waiting in [waitForContinuation] to resume. + */ + override fun signalContinuation() { + if (!continuationDeferred.isCompleted) { + continuationDeferred.complete(Unit) + } + } +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableField.java deleted file mode 100644 index d5389c7f..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableField.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanelConfig; -import com.github.serivesmejia.eocvsim.util.event.EventHandler; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; - -public abstract class TunableField { - - protected VirtualField reflectionField; - protected TunableFieldPanel fieldPanel; - - protected Object target; - protected AllowMode allowMode; - protected EOCVSim eocvSim; - - protected Object initialFieldValue; - - private int guiFieldAmount = 1; - private int guiComboBoxAmount = 0; - - private boolean ignoreGuiUpdates = false; - - public final EventHandler onValueChange = new EventHandler("TunableField-ValueChange"); - - private TunableFieldPanel.Mode recommendedMode = null; - - public TunableField(Object target, VirtualField reflectionField, EOCVSim eocvSim, AllowMode allowMode) throws IllegalAccessException { - this.reflectionField = reflectionField; - this.target = target; - this.allowMode = allowMode; - this.eocvSim = eocvSim; - - initialFieldValue = reflectionField.get(); - } - - public TunableField(Object target, VirtualField reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - this(target, reflectionField, eocvSim, AllowMode.TEXT); - } - - public abstract void init(); - - public abstract void update(); - - public abstract void updateGuiFieldValues(); - - public void setPipelineFieldValue(T newValue) throws IllegalAccessException { - if (hasChanged()) { //execute if value is not the same to save resources - reflectionField.set(newValue); - onValueChange.run(); - } - } - - public void setIgnoreGuiUpdates(boolean ignore) { - ignoreGuiUpdates = ignore; - } - - public abstract void setFieldValue(int index, Object newValue) throws IllegalAccessException; - public abstract void setFieldValueFromGui(int index, String newValue) throws IllegalAccessException; - - public void setComboBoxValueFromGui(int index, String newValue) throws IllegalAccessException { } - - public final void setTunableFieldPanel(TunableFieldPanel fieldPanel) { - this.fieldPanel = fieldPanel; - } - - protected final void setRecommendedPanelMode(TunableFieldPanel.Mode mode) { - recommendedMode = mode; - } - - public final void evalRecommendedPanelMode() { - TunableFieldPanelConfig configPanel = fieldPanel.panelOptions.getConfigPanel(); - TunableFieldPanelConfig.ConfigSource configSource = configPanel.getLocalConfig().getSource(); - //only apply the recommendation if user hasn't - //configured a global or specific field config - if(recommendedMode != null && fieldPanel != null && configSource == TunableFieldPanelConfig.ConfigSource.GLOBAL_DEFAULT) { - fieldPanel.setMode(recommendedMode); - } - } - - public abstract T getValue(); - - public abstract Object getGuiFieldValue(int index); - - public Object[] getGuiComboBoxValues(int index) { - return new Object[0]; - } - - public final int getGuiFieldAmount() { - return guiFieldAmount; - } - public final void setGuiFieldAmount(int amount) { - this.guiFieldAmount = amount; - } - - public final int getGuiComboBoxAmount() { - return guiComboBoxAmount; - } - public final void setGuiComboBoxAmount(int amount) { - this.guiComboBoxAmount = amount; - } - - public final String getFieldName() { - return reflectionField.getName(); - } - public final String getFieldTypeName() { - return reflectionField.getType().getSimpleName(); - } - - public final AllowMode getAllowMode() { - return allowMode; - } - - public final VirtualField getReflectionField() { - return reflectionField; - } - - public final boolean isOnlyNumbers() { - return getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS || - getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL; - } - - public boolean shouldIgnoreGuiUpdates() { - return ignoreGuiUpdates; - } - - public abstract boolean hasChanged(); - - public enum AllowMode {ONLY_NUMBERS, ONLY_NUMBERS_DECIMAL, TEXT} - -} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableField.kt new file mode 100644 index 00000000..966a4e41 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableField.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanelConfig +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.deltacv.eocvsim.virtualreflect.VirtualField + +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import javax.swing.SwingUtilities + +abstract class TunableField( + protected val target: Any, + val reflectionField: VirtualField, + val allowMode: AllowMode = AllowMode.TEXT +) : KoinComponent { + + protected val eocvSim: EOCVSim by inject() + + var fieldPanel: TunableFieldPanel? = null + private set + + protected val initialFieldValue: Any? = reflectionField.get() + + var isIgnoreGuiUpdates: Boolean = false + + @JvmField + val onValueChange = EventHandler("TunableField-ValueChange") + + abstract fun init() + + open fun update() { + refreshPipelineObject() + for (tunableValue in tunableValues) { + tunableValue.update() + } + } + + open fun refreshPipelineObject() {} + + open fun setPipelineFieldValue(newValue: T) { + val current = reflectionField.get() + if (current != newValue) { + reflectionField.set(newValue) + onValueChange.run() + } + } + + abstract val tunableValues: List> + + fun setTunableFieldPanel(fieldPanel: TunableFieldPanel) { + this.fieldPanel = fieldPanel + + for ((index, tunableValue) in tunableValues.withIndex()) { + tunableValue.onPipelineUpdate.attach { + if (!isIgnoreGuiUpdates) { + SwingUtilities.invokeLater { + fieldPanel.setFieldValue(index, tunableValue.value) + } + } + } + } + } + + abstract val value: T + + val fieldName: String + get() = reflectionField.name + + val fieldTypeName: String + get() = reflectionField.type.simpleName + + val isOnlyNumbers: Boolean + get() = allowMode == AllowMode.ONLY_NUMBERS || allowMode == AllowMode.ONLY_NUMBERS_DECIMAL + + fun shouldIgnoreGuiUpdates(): Boolean = isIgnoreGuiUpdates + + enum class AllowMode { + ONLY_NUMBERS, ONLY_NUMBERS_DECIMAL, TEXT + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptor.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptor.kt index 787e8b8f..c9022b22 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptor.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptor.kt @@ -1,28 +1,10 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.tuner interface TunableFieldAcceptor { fun accept(clazz: Class<*>): Boolean -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptorManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptorManager.kt deleted file mode 100644 index e887b5f0..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptorManager.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.serivesmejia.eocvsim.tuner - -import io.github.deltacv.common.util.loggerForThis -import java.util.HashMap - -class TunableFieldAcceptorManager(private val acceptors: HashMap>, Class>) { - - val logger by loggerForThis() - - fun accept(clazz: Class<*>): Class>? { - for((fieldClass, acceptorClass) in acceptors) { - //try getting a constructor for this acceptor - val acceptorConstructor = try { - acceptorClass.getConstructor() //get constructor with no params - } catch(ex: NoSuchMethodException) { - logger.error("TunableFieldAcceptor ${acceptorClass.typeName} doesn't implement a constructor with zero parameters", ex) - continue - } - - val acceptor = acceptorConstructor.newInstance() //create an instance of this acceptor - - if(acceptor.accept(clazz)) { //try accepting the given clazz type - return fieldClass //wooo someone accepted our type! return to tell who did. - } - } - - return null //no one accepted our type... poor clazz :( - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldRegistry.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldRegistry.kt new file mode 100644 index 00000000..030e123d --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldRegistry.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.util.ReflectUtil +import org.deltacv.eocvsim.virtualreflect.VirtualField +import com.github.serivesmejia.eocvsim.tuner.field.numeric.* +import com.github.serivesmejia.eocvsim.tuner.field.* +import com.github.serivesmejia.eocvsim.tuner.field.cv.* + +/** + * A manual registry for tunable fields, eliminating the need for classpath scanning + * and complex reflection constructor invocation. + */ +object TunableFieldRegistry { + + private val exactFields = mutableMapOf, (Any, VirtualField) -> TunableField<*>>() + private val acceptors = mutableListOf TunableField<*>>>() + + init { + registerField(Int::class.javaObjectType) { target, f -> IntegerField(target, f) } + registerField(Double::class.javaObjectType) { target, f -> DoubleField(target, f) } + registerField(Float::class.javaObjectType) { target, f -> FloatField(target, f) } + registerField(Long::class.javaObjectType) { target, f -> LongField(target, f) } + + registerField(String::class.java) { target, f -> StringField(target, f) } + registerField(Boolean::class.javaObjectType) { target, f -> BooleanField(target, f) } + + registerField(org.opencv.core.Scalar::class.java) { target, f -> ScalarField(target, f) } + registerField(org.opencv.core.Point::class.java) { target, f -> PointField(target, f) } + registerField(org.opencv.core.Rect::class.java) { target, f -> RectField(target, f) } + + registerAcceptor(EnumField.Acceptor()) { target, f -> EnumField(target, f) } + } + + fun registerField(type: Class<*>, constructor: (Any, VirtualField) -> TunableField<*>) { + exactFields[type] = constructor + } + + fun registerAcceptor(acceptor: TunableFieldAcceptor, constructor: (Any, VirtualField) -> TunableField<*>) { + acceptors.add(acceptor to constructor) + } + + fun getTunableFieldFor(field: VirtualField, pipeline: Any): TunableField<*>? { + if (field.isFinal) return null + + var type = field.type + if (type.isPrimitive) { + type = ReflectUtil.wrap(type) + } + + var constructor = exactFields[type] + if (constructor == null) { + constructor = acceptors.find { it.first.accept(type) }?.second + } + + return constructor?.invoke(pipeline, field) + } + + + fun hasTunableFieldFor(type: Class<*>): Boolean { + var t = type + if (t.isPrimitive) { + t = ReflectUtil.wrap(t) + } + if (exactFields.containsKey(t)) return true + return acceptors.any { it.first.accept(t) } + } + + fun reset() { + exactFields.clear() + acceptors.clear() + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableValue.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableValue.kt new file mode 100644 index 00000000..e7040e93 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableValue.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner + +import com.github.serivesmejia.eocvsim.util.event.EventHandler + +sealed class TunableValue( + initialValue: T, + val supplier: () -> T, + val consumer: (T) -> Unit +) { + private var _value: T = initialValue + + var value: T + get() = _value + set(newValue) { + if (_value != newValue) { + _value = newValue + onValueChange.run() + } + } + + val onValueChange = EventHandler("TunableValue-ValueChange") + val onPipelineUpdate = EventHandler("TunableValue-PipelineUpdate") + + fun setFromPipeline(newValue: T) { + if (_value != newValue) { + _value = newValue + onPipelineUpdate.run() + } + } + + fun update() { + setFromPipeline(supplier()) + } + + fun setFromGui(guiValue: T) { + consumer(guiValue) + value = guiValue + } + + abstract fun setAnyFromGui(guiValue: Any) +} + +class TunableNumber(initialValue: Double, supplier: () -> Double, consumer: (Double) -> Unit, val isOnlyNumbers: Boolean = false) : TunableValue(initialValue, supplier, consumer) { + override fun setAnyFromGui(guiValue: Any) { + if (guiValue is Number) setFromGui(guiValue.toDouble()) + else throw IllegalArgumentException("Expected Number but got ${guiValue::class.java.name}") + } +} +class TunableString(initialValue: String, supplier: () -> String, consumer: (String) -> Unit) : TunableValue(initialValue, supplier, consumer) { + override fun setAnyFromGui(guiValue: Any) { + if (guiValue is String) setFromGui(guiValue) + else throw IllegalArgumentException("Expected String but got ${guiValue::class.java.name}") + } +} +class TunableBoolean(initialValue: Boolean, supplier: () -> Boolean, consumer: (Boolean) -> Unit) : TunableValue(initialValue, supplier, consumer) { + override fun setAnyFromGui(guiValue: Any) { + if (guiValue is Boolean) setFromGui(guiValue) + else throw IllegalArgumentException("Expected Boolean but got ${guiValue::class.java.name}") + } +} +class TunableEnum>(initialValue: T, val enumValues: Array, supplier: () -> T, consumer: (T) -> Unit) : TunableValue(initialValue, supplier, consumer) { + override fun setAnyFromGui(guiValue: Any) { + if (value::class.java.isInstance(guiValue)) { + @Suppress("UNCHECKED_CAST") + setFromGui(guiValue as T) + } else { + throw IllegalArgumentException("Expected ${value::class.qualifiedName} but got ${guiValue::class.qualifiedName}") + } + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunerManager.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunerManager.java deleted file mode 100644 index bfe4340d..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunerManager.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.tuner.exception.CancelTunableFieldAddingException; -import com.github.serivesmejia.eocvsim.util.ReflectUtil; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; -import io.github.deltacv.eocvsim.virtualreflect.VirtualReflectContext; -import io.github.deltacv.eocvsim.virtualreflect.VirtualReflection; -import io.github.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -@SuppressWarnings("rawtypes") -public class TunerManager { - - Logger logger = LoggerFactory.getLogger(getClass()); - - private final EOCVSim eocvSim; - - private final List fields = new ArrayList<>(); - - private TunableFieldAcceptorManager acceptorManager = null; - - private static HashMap>> tunableFieldsTypes = null; - private static HashMap>, Class> tunableFieldAcceptors = null; - - private VirtualReflection reflect = JvmVirtualReflection.INSTANCE; - - private boolean firstInit = true; - - public TunerManager(EOCVSim eocvSim) { - this.eocvSim = eocvSim; - } - - public void init() { - if(tunableFieldsTypes == null) { - tunableFieldsTypes = new HashMap<>(); - // ... - for(Class> clazz : eocvSim.getClasspathScan().getScanResult().getTunableFieldClasses()) { - tunableFieldsTypes.put(ReflectUtil.getTypeArgumentsFrom(clazz)[0], clazz); - } - } - - if(tunableFieldAcceptors == null) { - tunableFieldAcceptors = new HashMap<>(); - // oh god... - tunableFieldAcceptors.putAll(eocvSim.getClasspathScan().getScanResult().getTunableFieldAcceptorClasses()); - } - - // for some reason, acceptorManager becomes null after a certain time passes - // (maybe garbage collected? i don't know for sure...), but we can simply recover - // from this by creating a new one with the found acceptors by the scanner, no problem. - if(acceptorManager == null) - acceptorManager = new TunableFieldAcceptorManager(tunableFieldAcceptors); - - if (firstInit) { - eocvSim.pipelineManager.onPipelineChange.attach(this::reset); - firstInit = false; - } - - if (eocvSim.pipelineManager.getReflectTarget() != null) { - addFieldsFrom(eocvSim.pipelineManager.getReflectTarget()); - eocvSim.visualizer.updateTunerFields(createTunableFieldPanels()); - - for(TunableField field : fields.toArray(new TunableField[0])) { - try { - field.init(); - } catch(CancelTunableFieldAddingException e) { - logger.info("Field " + field.getFieldName() + " was removed due to \"" + e.getMessage() + "\""); - fields.remove(field); - } - } - } - } - - public void update() { - //update all fields - for(TunableField field : fields.toArray(new TunableField[0])) { - try { - field.update(); - } catch(Exception ex) { - logger.error("Error while updating field " + field.getFieldName(), ex); - } - - //check if this field has requested to reevaluate config for all panels - if(field.fieldPanel.hasRequestedAllConfigReeval()) { - //if so, iterate through all fields to reevaluate - for(TunableField f : fields.toArray(new TunableField[0])) { - f.fieldPanel.panelOptions.reevaluateConfig(); - } - } - } - } - - public void reset() { - fields.clear(); - init(); - } - - @SuppressWarnings("unchecked") - public TunableField newTunableFieldInstanceFor(VirtualField field, Object pipeline) { - Class tunableFieldClass = getTunableFieldClassOf(field); - - if(tunableFieldClass != null) { - //yay we have a registered TunableField which handles this. - //now, lets do some more reflection to instantiate this TunableField - try { - Constructor constructor = tunableFieldClass.getConstructor(Object.class, VirtualField.class, EOCVSim.class); - return (TunableField) constructor.newInstance(pipeline, field, eocvSim); - } catch(InvocationTargetException e) { - if(e.getCause() instanceof CancelTunableFieldAddingException) { - String message = e.getCause().getMessage(); - logger.info("Field {} wasn't added due to \"{}\"", field.getName(), message); - } else { - logger.error("Reflection error while processing field: {}", field.getName(), e); - } - } catch (Exception ex) { - //oops rip - logger.error("Reflection error while processing field: {}", field.getName(), ex); - } - } - - return null; - } - - public Class getTunableFieldClassOf(VirtualField field) { - //we only accept non-final fields - if (field.isFinal()) return null; - - Class type = field.getType(); - if (field.getType().isPrimitive()) { //wrap to java object equivalent if field type is primitive - type = ReflectUtil.wrap(type); - } - - Class tunableFieldClass = null; - - if(tunableFieldsTypes.containsKey(type)) { - tunableFieldClass = tunableFieldsTypes.get(type); - } else { - //if we don't have a class yet, use our acceptors - if(acceptorManager != null) tunableFieldClass = acceptorManager.accept(type); - } - - return tunableFieldClass; - } - - public void addFieldsFrom(Object pipeline) { - if (pipeline == null) return; - - VirtualReflectContext reflectContext = reflect.contextOf(pipeline); - if(reflectContext == null) return; - - VirtualField[] fields = reflect.contextOf(pipeline).getFields(); - - for (VirtualField field : fields) { - // ...add it to the list - TunableField tunableField = newTunableFieldInstanceFor(field, pipeline); - - if(tunableField != null) { - this.fields.add(tunableField); - } - } - } - - @Nullable public TunableField getCurrentTunableFieldWithLabel(String label) { - TunableField labeledField = null; - - for(TunableField field : fields) { - String fieldLabel = field.reflectionField.getLabel(); - - if(fieldLabel != null && fieldLabel.equals(label)) { - labeledField = field; - break; - } - } - - if(labeledField != null) { - labeledField.setIgnoreGuiUpdates(true); - } - return labeledField; - } - - public void setVirtualReflection(VirtualReflection reflect) { - this.reflect = reflect; - } - - public void reevaluateConfigs() { - for(TunableField field : fields) { - field.fieldPanel.panelOptions.reevaluateConfig(); - } - } - - private List createTunableFieldPanels() { - List panels = new ArrayList<>(); - - for (TunableField field : fields) { - panels.add(new TunableFieldPanel(field, eocvSim)); - } - - return panels; - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunerManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunerManager.kt new file mode 100644 index 00000000..6ebd4b9d --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunerManager.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner + +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel +import com.github.serivesmejia.eocvsim.tuner.exception.CancelTunableFieldAddingException +import org.deltacv.common.util.loggerForThis +import org.deltacv.eocvsim.virtualreflect.VirtualField +import org.deltacv.eocvsim.virtualreflect.VirtualReflection +import org.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection + +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.util.orchestration.dependency +import com.github.serivesmejia.eocvsim.util.orchestration.PhaseOrchestrableBase + +class TunerManager : PhaseOrchestrableBase(), KoinComponent { + + private val pipelineManager: PipelineManager by dependency(inject()) + private val visualizer: Visualizer by inject() + + val logger by loggerForThis() + + val fields = mutableListOf>() + + var reflect: VirtualReflection = JvmVirtualReflection + + private var firstInit = true + + override suspend fun init() { + pipelineManager.onPipelineChange.attach { reset() } + refreshFields() + } + + private fun refreshFields() { + pipelineManager.reflectTarget?.let { target -> + addFieldsFrom(target) + visualizer.updateTunerFields(createTunableFieldPanels()) + + val iterator = fields.iterator() + while (iterator.hasNext()) { + val field = iterator.next() + try { + field.init() + } catch (e: CancelTunableFieldAddingException) { + logger.info("Field ${field.fieldName} was removed due to \"${e.message}\"") + iterator.remove() + } + } + } + } + + override suspend fun run() { + for (field in fields.toList()) { // toList to avoid concurrent modification issues + try { + field.update() + } catch (ex: Exception) { + logger.error("Error while updating field ${field.fieldName}", ex) + } + + if (field.fieldPanel?.hasRequestedAllConfigReeval() == true) { + for (f in fields) { + f.fieldPanel?.panelOptions?.reevaluateConfig() + } + } + } + } + + override suspend fun destroy() { + reset() + } + + fun reset() { + fields.clear() + refreshFields() + } + + fun newTunableFieldInstanceFor(field: VirtualField, pipeline: Any): TunableField<*>? { + return TunableFieldRegistry.getTunableFieldFor(field, pipeline) + } + + fun addFieldsFrom(pipeline: Any) { + val reflectContext = reflect.contextOf(pipeline) ?: return + val virtualFields = reflectContext.fields + + for (field in virtualFields) { + val tunableField = newTunableFieldInstanceFor(field, pipeline) + if (tunableField != null) { + fields.add(tunableField) + } + } + } + + fun getCurrentTunableFieldWithLabel(label: String): TunableField<*>? { + val labeledField = fields.find { it.reflectionField.label == label } + labeledField?.isIgnoreGuiUpdates = true + return labeledField + } + + fun reevaluateConfigs() { + for (field in fields) { + field.fieldPanel?.panelOptions?.reevaluateConfig() + } + } + + private fun createTunableFieldPanels(): List { + return fields.map { TunableFieldPanel(it) } + } + +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/exception/CancelTunableFieldAddingException.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/exception/CancelTunableFieldAddingException.kt index e2ddb339..e309be20 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/exception/CancelTunableFieldAddingException.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/exception/CancelTunableFieldAddingException.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.tuner.exception -class CancelTunableFieldAddingException(message: String) : RuntimeException(message) \ No newline at end of file +class CancelTunableFieldAddingException(message: String) : RuntimeException(message) diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/BooleanField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/BooleanField.java deleted file mode 100644 index bef2ee71..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/BooleanField.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.TunableField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; - -import javax.swing.*; - -@RegisterTunableField -public class BooleanField extends TunableField { - - boolean value; - - boolean lastVal; - volatile boolean hasChanged = false; - - public BooleanField(Object instance, VirtualField reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.TEXT); - - setGuiFieldAmount(0); - setGuiComboBoxAmount(1); - - value = (boolean) initialFieldValue; - } - - @Override - public void init() {} - - @Override - public void update() { - hasChanged = value != lastVal; - - if (hasChanged) { //update values in GUI if they changed since last check - updateGuiFieldValues(); - } - - lastVal = value; - } - - @Override - public void updateGuiFieldValues() { - SwingUtilities.invokeLater(() -> fieldPanel.setComboBoxSelection(0, value)); - } - - @Override - public void setFieldValue(int index, Object newValue) throws IllegalAccessException { - value = (boolean) newValue; - setPipelineFieldValue((boolean)newValue); - } - - @Override - public void setFieldValueFromGui(int index, String newValue) throws IllegalAccessException { - setComboBoxValueFromGui(index, newValue); - } - - @Override - public void setComboBoxValueFromGui(int index, String newValue) throws IllegalAccessException { - value = Boolean.parseBoolean(newValue); - setFieldValue(index, value); - lastVal = value; - } - - @Override - public Boolean getValue() { - return value; - } - - @Override - public Object getGuiFieldValue(int index) { - return value; - } - - @Override - public Object[] getGuiComboBoxValues(int index) { - return new Boolean[]{value, !value}; - } - - @Override - public boolean hasChanged() { - hasChanged = value != lastVal; - return hasChanged; - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/BooleanField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/BooleanField.kt new file mode 100644 index 00000000..9c8e6dab --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/BooleanField.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner.field + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.tuner.TunableBoolean +import com.github.serivesmejia.eocvsim.tuner.TunableField +import org.deltacv.eocvsim.virtualreflect.VirtualField + +class BooleanField( + instance: Any, + reflectionField: VirtualField +) : TunableField(instance, reflectionField, AllowMode.TEXT) { + + + private var _value: Boolean = initialFieldValue as? Boolean ?: false + + private val tunableValue by lazy { TunableBoolean(_value, { _value }, { updateBoolean(it) }) } + + override val tunableValues by lazy { listOf(tunableValue) } + + private fun updateBoolean(newValue: Boolean) { + _value = newValue + setPipelineFieldValue(_value) + } + + override fun init() { + reflectionField.set(_value) + } + + override fun refreshPipelineObject() { + val current = reflectionField.get() as? Boolean ?: return + _value = current + } + + override val value: Boolean + get() = _value +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/EnumField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/EnumField.kt index 1648a4a1..9d59a577 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/EnumField.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/EnumField.kt @@ -1,70 +1,49 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.tuner.field import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.tuner.TunableEnum import com.github.serivesmejia.eocvsim.tuner.TunableField import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField -import io.github.deltacv.eocvsim.virtualreflect.VirtualField -import javax.swing.SwingUtilities +import org.deltacv.eocvsim.virtualreflect.VirtualField -@RegisterTunableField class EnumField(instance: Any, - reflectionField: VirtualField, - eocvSim: EOCVSim) : TunableField>(instance, reflectionField, eocvSim, AllowMode.TEXT) { + reflectionField: VirtualField) : TunableField>(instance, reflectionField, AllowMode.TEXT) { + val values = reflectionField.type.enumConstants private val initialValue = initialFieldValue as Enum<*> private var currentValue = initialValue - private var beforeValue: Any? = null - - init { - guiComboBoxAmount = 1 - guiFieldAmount = 0 - } - - override fun init() { - fieldPanel.setComboBoxSelection(0, currentValue) - } - override fun update() { - if(hasChanged()) { - currentValue = value - updateGuiFieldValues() - } - beforeValue = currentValue - } + private val tunableValue by lazy { TunableEnum(currentValue, values as Array, { currentValue }, { updateEnum(it) }) } - override fun updateGuiFieldValues() { - SwingUtilities.invokeLater { - fieldPanel.setComboBoxSelection(0, currentValue) - } - } + override val tunableValues by lazy { listOf(tunableValue) } - override fun setFieldValue(index: Int, newValue: Any) { - reflectionField.set(newValue) + private fun updateEnum(newValue: Enum<*>) { + currentValue = newValue + setPipelineFieldValue(newValue) } - override fun setComboBoxValueFromGui(index: Int, newValue: String) = setFieldValueFromGui(index, newValue) - - override fun setFieldValueFromGui(index: Int, newValue: String) { - currentValue = java.lang.Enum.valueOf(initialValue::class.java, newValue) - setFieldValue(index, currentValue) + override fun init() { + reflectionField.set(currentValue) } - override fun getValue() = currentValue - - override fun getGuiFieldValue(index: Int) = currentValue.name - - override fun getGuiComboBoxValues(index: Int): Array { - return values + override fun refreshPipelineObject() { + val curr = reflectionField.get() as? Enum<*> ?: return + currentValue = curr } - override fun hasChanged() = reflectionField.get() != beforeValue + override val value: Enum<*> + get() = currentValue - class EnumFieldAcceptor : TunableFieldAcceptor { + class Acceptor : TunableFieldAcceptor { override fun accept(clazz: Class<*>) = clazz.isEnum } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/NumericField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/NumericField.java deleted file mode 100644 index 3ddaf419..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/NumericField.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.tuner.TunableField; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; - -import javax.swing.*; - -abstract public class NumericField extends TunableField { - - protected T value; - protected T beforeValue; - - protected volatile boolean hasChanged = false; - - public NumericField(Object instance, VirtualField reflectionField, EOCVSim eocvSim, AllowMode allowMode) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, allowMode); - } - - @Override - public void init() { - setRecommendedPanelMode(TunableFieldPanel.Mode.TEXTBOXES); - } - - @Override - @SuppressWarnings("unchecked") - public void update() { - if (value == null) return; - value = (T) reflectionField.get(); - - hasChanged = hasChanged(); - - if (hasChanged) { - updateGuiFieldValues(); - } - } - - @Override - public void updateGuiFieldValues() { - SwingUtilities.invokeLater(() -> fieldPanel.setFieldValue(0, value)); - } - - @Override - public T getValue() { - return value; - } - - @Override - public Object getGuiFieldValue(int index) { - return value; - } - - @Override - public boolean hasChanged() { - boolean hasChanged = value != beforeValue; - beforeValue = value; - return hasChanged; - } - - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/NumericField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/NumericField.kt new file mode 100644 index 00000000..cfa3ee7e --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/NumericField.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner.field + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel +import com.github.serivesmejia.eocvsim.tuner.TunableField +import com.github.serivesmejia.eocvsim.tuner.TunableNumber +import org.deltacv.eocvsim.virtualreflect.VirtualField + +abstract class NumericField( + target: Any, + reflectionField: VirtualField, + allowMode: AllowMode, + initialValue: T +) : TunableField(target, reflectionField, allowMode) { + + + protected var _value: T = initialValue + + protected val tunableValue by lazy { TunableNumber(_value.toDouble(), { _value.toDouble() }, { updateNumber(it) }) } + + override val tunableValues by lazy { listOf(tunableValue) } + + abstract fun createNumber(value: Double): T + + private fun updateNumber(newValue: Double) { + _value = createNumber(newValue) + setPipelineFieldValue(_value) + } + + override fun init() { + reflectionField.set(_value) + } + + override fun refreshPipelineObject() { + _value = createNumber((reflectionField.get() as? Number)?.toDouble() ?: 0.0) + } + + override val value: T + get() = _value +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/StringField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/StringField.java deleted file mode 100644 index 6bf3d6dc..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/StringField.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.tuner.TunableField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; - -import javax.swing.*; - -@RegisterTunableField -public class StringField extends TunableField { - - String value; - - String lastVal = ""; - - volatile boolean hasChanged = false; - - public StringField(Object instance, VirtualField reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.TEXT); - - if(initialFieldValue != null) { - value = (String) initialFieldValue; - } else { - value = ""; - } - } - - @Override - public void init() { - setRecommendedPanelMode(TunableFieldPanel.Mode.TEXTBOXES); - } - - @Override - public void update() { - hasChanged = !value.equals(lastVal); - - if (hasChanged) { //update values in GUI if they changed since last check - updateGuiFieldValues(); - } - - lastVal = value; - } - - @Override - public void updateGuiFieldValues() { - SwingUtilities.invokeLater(() -> fieldPanel.setFieldValue(0, value)); - } - - @Override - public void setFieldValue(int index, Object newValue) throws IllegalAccessException { - setPipelineFieldValue((String)newValue); - } - - @Override - public void setFieldValueFromGui(int index, String newValue) throws IllegalAccessException { - value = newValue; - setFieldValue(index, value); - - lastVal = value; - } - - @Override - public String getValue() { - return value; - } - - @Override - public Object getGuiFieldValue(int index) { - return value; - } - - @Override - public boolean hasChanged() { - hasChanged = !value.equals(lastVal); - return hasChanged; - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/StringField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/StringField.kt new file mode 100644 index 00000000..7f0c89e4 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/StringField.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner.field + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel +import com.github.serivesmejia.eocvsim.tuner.TunableField +import com.github.serivesmejia.eocvsim.tuner.TunableString +import org.deltacv.eocvsim.virtualreflect.VirtualField + +class StringField( + instance: Any, + reflectionField: VirtualField +) : TunableField(instance, reflectionField, AllowMode.TEXT) { + + + private var _value: String = initialFieldValue as? String ?: "" + + private val tunableValue by lazy { TunableString(_value, { _value }, { updateString(it) }) } + + override val tunableValues by lazy { listOf(tunableValue) } + + private fun updateString(newValue: String) { + _value = newValue + setPipelineFieldValue(_value) + } + + override fun init() { + reflectionField.set(_value) + } + + override fun refreshPipelineObject() { + val current = reflectionField.get() as? String ?: return + _value = current + } + + override val value: String + get() = _value +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/PointField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/PointField.java deleted file mode 100644 index 31262ad9..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/PointField.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.cv; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.TunableField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; -import org.opencv.core.Point; - -import javax.swing.*; - -@RegisterTunableField -public class PointField extends TunableField { - - Point point; - - double lastX = 0; - double lastY = 0; - - volatile boolean hasChanged = false; - - public PointField(Object instance, VirtualField reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); - - if(initialFieldValue != null) { - Point p = (Point) initialFieldValue; - point = new Point(p.x, p.y); - } else { - point = new Point(0, 0); - } - - setGuiFieldAmount(2); - } - - @Override - public void init() { - reflectionField.set(point); - } - - @Override - public void update() { - hasChanged = point.x != lastX || point.y != lastY; - - if (hasChanged) { //update values in GUI if they changed since last check - updateGuiFieldValues(); - } - - lastX = point.x; - lastY = point.y; - } - - @Override - public void updateGuiFieldValues() { - SwingUtilities.invokeLater(() -> { - fieldPanel.setFieldValue(0, point.x); - fieldPanel.setFieldValue(1, point.y); - }); - } - - @Override - public void setFieldValue(int index, Object newValue) throws IllegalAccessException { - try { - double value = 0; - if(newValue instanceof String) { - value = Double.parseDouble((String)newValue); - } else { - value = (double)newValue; - } - - if (index == 0) { - point.x = value; - } else { - point.y = value; - } - } catch (Exception ex) { - throw new IllegalArgumentException("Parameter should be a valid number", ex); - } - - setPipelineFieldValue(point); - - lastX = point.x; - lastY = point.y; - } - - @Override - public void setFieldValueFromGui(int index, String newValue) throws IllegalAccessException { - setFieldValue(index, point); - } - - @Override - public Point getValue() { - return point; - } - - @Override - public Object getGuiFieldValue(int index) { - return index == 0 ? point.x : point.y; - } - - @Override - public boolean hasChanged() { - hasChanged = point.x != lastX || point.y != lastY; - return hasChanged; - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/PointField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/PointField.kt new file mode 100644 index 00000000..c4d74c8d --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/PointField.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner.field.cv + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.tuner.TunableField +import com.github.serivesmejia.eocvsim.tuner.TunableNumber +import org.deltacv.eocvsim.virtualreflect.VirtualField +import org.opencv.core.Point + +class PointField( + instance: Any, + reflectionField: VirtualField +) : TunableField(instance, reflectionField, AllowMode.ONLY_NUMBERS_DECIMAL) { + + + private var point: Point = if (initialFieldValue != null) { + val p = initialFieldValue as Point + Point(p.x, p.y) + } else { + Point(0.0, 0.0) + } + + private val xValue by lazy { TunableNumber(point.x, { point.x }, { updatePoint(0, it) }) } + private val yValue by lazy { TunableNumber(point.y, { point.y }, { updatePoint(1, it) }) } + + override val tunableValues by lazy { listOf(xValue, yValue) } + + private fun updatePoint(index: Int, newValue: Double) { + if (index == 0) point.x = newValue else point.y = newValue + setPipelineFieldValue(point) + } + + override fun init() { + reflectionField.set(point) + } + + override fun refreshPipelineObject() { + val current = reflectionField.get() as Point + point.x = current.x + point.y = current.y + } + + override val value: Point + get() = point +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/RectField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/RectField.kt index 83c1ed12..dc885fbf 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/RectField.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/RectField.kt @@ -1,112 +1,59 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.tuner.field.cv import com.github.serivesmejia.eocvsim.EOCVSim import com.github.serivesmejia.eocvsim.tuner.TunableField -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField -import io.github.deltacv.eocvsim.virtualreflect.VirtualField +import com.github.serivesmejia.eocvsim.tuner.TunableNumber import org.opencv.core.Rect -import javax.swing.SwingUtilities +import org.deltacv.eocvsim.virtualreflect.VirtualField -@RegisterTunableField -class RectField(instance: Any, reflectionField: VirtualField, eocvSim: EOCVSim) : - TunableField(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL) { +class RectField(instance: Any, reflectionField: VirtualField) : + TunableField(instance, reflectionField, AllowMode.ONLY_NUMBERS_DECIMAL) { - private var rect = arrayOf(0.0, 0.0, 0.0, 0.0) - private var lastRect = arrayOf(0.0, 0.0, 0.0, 0.0) - @Volatile private var hasChanged = false + private var rect = arrayOf(0.0, 0.0, 0.0, 0.0) private var initialRect = if(initialFieldValue != null) (initialFieldValue as Rect).clone() else Rect(0, 0, 0, 0) + private val xValue by lazy { TunableNumber(rect[0], { rect[0] }, { updateRect(0, it) }) } + private val yValue by lazy { TunableNumber(rect[1], { rect[1] }, { updateRect(1, it) }) } + private val wValue by lazy { TunableNumber(rect[2], { rect[2] }, { updateRect(2, it) }) } + private val hValue by lazy { TunableNumber(rect[3], { rect[3] }, { updateRect(3, it) }) } + + private fun updateRect(index: Int, newValue: Double) { + rect[index] = newValue + initialRect.set(rect.toDoubleArray()) + setPipelineFieldValue(initialRect) + } + + override val tunableValues by lazy { listOf(xValue, yValue, wValue, hValue) } + init { rect[0] = initialRect.x.toDouble() rect[1] = initialRect.y.toDouble() rect[2] = initialRect.width.toDouble() rect[3] = initialRect.height.toDouble() - - guiFieldAmount = 4 } override fun init() { reflectionField.set(initialRect) } - override fun update() { - if(hasChanged()){ - initialRect = reflectionField.get() as Rect + override fun refreshPipelineObject() { + initialRect = reflectionField.get() as Rect - rect[0] = initialRect.x.toDouble() - rect[1] = initialRect.y.toDouble() - rect[2] = initialRect.width.toDouble() - rect[3] = initialRect.height.toDouble() - - updateGuiFieldValues() - } - } - - override fun setFieldValue(index: Int, newValue: Any) { - try { - rect[index] = if(newValue is String) - newValue.toDouble() - else (newValue as Number).toDouble() - } catch (e: Exception) { - throw IllegalArgumentException("Parameter should be a valid numeric value", e) - } - - initialRect.set(rect.toDoubleArray()) - setPipelineFieldValue(initialRect) - - lastRect[0] = initialRect.x.toDouble() - lastRect[1] = initialRect.y.toDouble() - lastRect[2] = initialRect.width.toDouble() - lastRect[3] = initialRect.height.toDouble() - } - - override fun updateGuiFieldValues() { - SwingUtilities.invokeLater { - for((i, value) in rect.withIndex()) { - fieldPanel.setFieldValue(i, value) - } - } - } - - override fun setFieldValueFromGui(index: Int, newValue: String) { - setFieldValue(index, newValue) - } - - override fun getValue(): Rect = Rect(rect.toDoubleArray()) - - override fun getGuiFieldValue(index: Int): Any = rect[index] - - override fun hasChanged(): Boolean { - hasChanged = rect[0] != lastRect[0] || rect[1] != lastRect[1] - || rect[2] != lastRect[2] || rect[3] != lastRect[3] - return hasChanged + rect[0] = initialRect.x.toDouble() + rect[1] = initialRect.y.toDouble() + rect[2] = initialRect.width.toDouble() + rect[3] = initialRect.height.toDouble() } -} \ No newline at end of file + override val value: Rect + get() = Rect(rect.toDoubleArray()) +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/ScalarField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/ScalarField.java deleted file mode 100644 index 6d2a7b4a..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/ScalarField.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.cv; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.tuner.TunableField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; -import org.opencv.core.Scalar; - -import javax.swing.*; -import java.util.Arrays; - -@RegisterTunableField -public class ScalarField extends TunableField { - - int scalarSize; - Scalar scalar; - - double[] lastVal = {0, 0, 0, 0}; - - volatile boolean hasChanged = false; - - public ScalarField(Object instance, VirtualField reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); - - if(initialFieldValue == null) { - scalar = new Scalar(0, 0, 0); - } else { - scalar = ((Scalar) initialFieldValue).clone(); - } - - scalarSize = scalar.val.length; - - setGuiFieldAmount(4); - setRecommendedPanelMode(TunableFieldPanel.Mode.SLIDERS); - } - - @Override - public void init() { - reflectionField.set(scalar); - } - - @Override - public void update() { - scalar = (Scalar) reflectionField.get(); - - hasChanged = !Arrays.equals(scalar.val, lastVal); - - if (hasChanged) { //update values in GUI if they changed since last check - updateGuiFieldValues(); - } - - lastVal[0] = scalar.val[0]; - lastVal[1] = scalar.val[1]; - lastVal[2] = scalar.val[2]; - lastVal[3] = scalar.val[3]; - } - - @Override - public void updateGuiFieldValues() { - SwingUtilities.invokeLater(() -> { - for (int i = 0; i < scalar.val.length; i++) { - fieldPanel.setFieldValue(i, scalar.val[i]); - } - }); - } - - @Override - public void setFieldValue(int index, Object newValue) throws IllegalAccessException { - try { - double value; - - if(newValue instanceof String) { - value = Double.parseDouble((String) newValue); - } else { - value = (double)newValue; - } - - scalar.val[index] = value; - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid number", ex); - } - - setPipelineFieldValue(scalar); - - lastVal[0] = scalar.val[0]; - lastVal[1] = scalar.val[1]; - lastVal[2] = scalar.val[2]; - lastVal[3] = scalar.val[3]; - } - - @Override - public void setFieldValueFromGui(int index, String newValue) throws IllegalAccessException { - setFieldValue(index, newValue); - } - - @Override - public Scalar getValue() { - return scalar; - } - - @Override - public Object getGuiFieldValue(int index) { - return scalar.val[index]; - } - - @Override - public boolean hasChanged() { - hasChanged = !Arrays.equals(scalar.val, lastVal); - return hasChanged; - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/ScalarField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/ScalarField.kt new file mode 100644 index 00000000..48a50752 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/ScalarField.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner.field.cv + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel +import com.github.serivesmejia.eocvsim.tuner.TunableField +import com.github.serivesmejia.eocvsim.tuner.TunableNumber +import org.deltacv.eocvsim.virtualreflect.VirtualField +import org.opencv.core.Scalar + +class ScalarField( + instance: Any, + reflectionField: VirtualField +) : TunableField(instance, reflectionField, AllowMode.ONLY_NUMBERS_DECIMAL) { + + + private var scalar: Scalar = if (initialFieldValue == null) { + Scalar(0.0, 0.0, 0.0) + } else { + (initialFieldValue as Scalar).clone() + } + + private val val0 by lazy { TunableNumber(scalar.`val`[0], { scalar.`val`[0] }, { updateScalar(0, it) }) } + private val val1 by lazy { TunableNumber(scalar.`val`[1], { scalar.`val`[1] }, { updateScalar(1, it) }) } + private val val2 by lazy { TunableNumber(scalar.`val`[2], { scalar.`val`[2] }, { updateScalar(2, it) }) } + private val val3 by lazy { TunableNumber(scalar.`val`[3], { scalar.`val`[3] }, { updateScalar(3, it) }) } + + override val tunableValues by lazy { listOf(val0, val1, val2, val3) } + + private fun updateScalar(index: Int, newValue: Double) { + scalar.`val`[index] = newValue + setPipelineFieldValue(scalar) + } + + override fun init() { + reflectionField.set(scalar) + } + + override fun refreshPipelineObject() { + val current = reflectionField.get() as Scalar + scalar = current + } + + override val value: Scalar + get() = scalar +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/DoubleField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/DoubleField.java deleted file mode 100644 index e06c6e37..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/DoubleField.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.numeric; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.field.NumericField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; - -@RegisterTunableField -public class DoubleField extends NumericField { - - public DoubleField(Object instance, VirtualField reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); - value = (double) initialFieldValue; - } - - @Override - public void setFieldValueFromGui(int index, String newValue) throws IllegalAccessException { - try { - value = Double.valueOf(newValue); - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid numeric String"); - } - - setFieldValue(index, value); - beforeValue = value; - } - - @Override - public void setFieldValue(int index, Object value) throws IllegalAccessException { - if(value instanceof Number) { - this.value = ((Number) value).doubleValue(); - } else { - this.value = (double)value; - } - setPipelineFieldValue(this.value); - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/DoubleField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/DoubleField.kt new file mode 100644 index 00000000..2554c896 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/DoubleField.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner.field.numeric + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.tuner.field.NumericField +import org.deltacv.eocvsim.virtualreflect.VirtualField +import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor + +class DoubleField( + instance: Any, + reflectionField: VirtualField +) : NumericField(instance, reflectionField, AllowMode.ONLY_NUMBERS_DECIMAL, reflectionField.get() as? Double ?: 0.0) { + + override fun createNumber(value: Double): Double = value + + class Acceptor : TunableFieldAcceptor { + override fun accept(clazz: Class<*>) = + clazz == Double::class.java || clazz == java.lang.Double::class.java + } +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/FloatField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/FloatField.java deleted file mode 100644 index c995a0b7..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/FloatField.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.numeric; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.field.NumericField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; - -@RegisterTunableField -public class FloatField extends NumericField { - - public FloatField(Object instance, VirtualField reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); - value = (float) initialFieldValue; - } - - @Override - public void setFieldValueFromGui(int index, String newValue) throws IllegalAccessException { - try { - value = Float.parseFloat(newValue); - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid numeric String"); - } - - setFieldValue(index, value); - beforeValue = value; - } - - @Override - public void setFieldValue(int index, Object value) throws IllegalAccessException { - if(value instanceof Number) { - this.value = ((Number) value).floatValue(); - } else { - this.value = (float)value; - } - setPipelineFieldValue(this.value); - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/FloatField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/FloatField.kt new file mode 100644 index 00000000..31107e30 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/FloatField.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner.field.numeric + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.tuner.field.NumericField +import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor +import org.deltacv.eocvsim.virtualreflect.VirtualField + +class FloatField( + instance: Any, + reflectionField: VirtualField +) : NumericField(instance, reflectionField, AllowMode.ONLY_NUMBERS_DECIMAL, reflectionField.get() as? Float ?: 0.0f) { + + + override fun createNumber(value: Double): Float = value.toFloat() + + class Acceptor : TunableFieldAcceptor { + override fun accept(clazz: Class<*>) = + clazz == Float::class.java || clazz == java.lang.Float::class.java + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/IntegerField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/IntegerField.java deleted file mode 100644 index f260cb2c..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/IntegerField.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.numeric; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.field.NumericField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; - -@RegisterTunableField -public class IntegerField extends NumericField { - - public IntegerField(Object instance, VirtualField reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS); - value = (int) initialFieldValue; - } - - @Override - public void setFieldValueFromGui(int index, String newValue) throws IllegalAccessException { - try { - value = (int) Math.round(Double.parseDouble(newValue)); - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid numeric String"); - } - - setFieldValue(index, value); - beforeValue = value; - } - - @Override - public void setFieldValue(int index, Object value) throws IllegalAccessException { - if(value instanceof Number) { - this.value = ((Number) value).intValue(); - } else { - this.value = (int)value; - } - setPipelineFieldValue(this.value); - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/IntegerField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/IntegerField.kt new file mode 100644 index 00000000..25807576 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/IntegerField.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner.field.numeric + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.tuner.field.NumericField +import org.deltacv.eocvsim.virtualreflect.VirtualField +import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor + +class IntegerField( + instance: Any, + reflectionField: VirtualField +) : NumericField(instance, reflectionField, AllowMode.ONLY_NUMBERS, reflectionField.get() as? Int ?: 0) { + + + override fun createNumber(value: Double): Int = value.toInt() + + class Acceptor : TunableFieldAcceptor { + override fun accept(clazz: Class<*>) = + clazz == Int::class.java || clazz == java.lang.Integer::class.java + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/LongField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/LongField.java deleted file mode 100644 index b5fcde37..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/LongField.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.numeric; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.field.NumericField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import io.github.deltacv.eocvsim.virtualreflect.VirtualField; - -@RegisterTunableField -public class LongField extends NumericField { - - public LongField(Object instance, VirtualField reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS); - value = (long) initialFieldValue; - } - - @Override - public void setFieldValueFromGui(int index, String newValue) throws IllegalAccessException { - try { - value = Math.round(Double.parseDouble(newValue)); - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid numeric String"); - } - - setFieldValue(index, value); - beforeValue = value; - } - - @Override - public void setFieldValue(int index, Object value) throws IllegalAccessException { - if(value instanceof Number) { - this.value = ((Number) value).longValue(); - } else { - this.value = (long)value; - } - setPipelineFieldValue(this.value); - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/LongField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/LongField.kt new file mode 100644 index 00000000..45999af4 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/LongField.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.tuner.field.numeric + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.tuner.field.NumericField +import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor +import org.deltacv.eocvsim.virtualreflect.VirtualField + +class LongField( + instance: Any, + reflectionField: VirtualField +) : NumericField(instance, reflectionField, AllowMode.ONLY_NUMBERS, reflectionField.get() as? Long ?: 0L) { + + + override fun createNumber(value: Double): Long = value.toLong() + + class Acceptor : TunableFieldAcceptor { + override fun accept(clazz: Class<*>) = + clazz == Long::class.java || clazz == java.lang.Long::class.java + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableField.java deleted file mode 100644 index 48e2bfd0..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableField.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.scanner; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.TYPE; - -@Target({TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface RegisterTunableField { } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableFieldAcceptor.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableFieldAcceptor.java deleted file mode 100644 index d692c53e..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableFieldAcceptor.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.scanner; - -import com.github.serivesmejia.eocvsim.tuner.TunableField; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.TYPE; - -@Target({TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface RegisterTunableFieldAcceptor { - Class> tunableFieldType(); -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt index 937df9c1..4c6b742b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt @@ -1,47 +1,37 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.util -import com.github.serivesmejia.eocvsim.tuner.TunableField -import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField +import com.github.serivesmejia.eocvsim.util.orchestration.Orchestrable +import com.github.serivesmejia.eocvsim.util.orchestration.Orchestrator import com.qualcomm.robotcore.eventloop.opmode.Disabled import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode import com.qualcomm.robotcore.eventloop.opmode.OpMode import com.qualcomm.robotcore.util.ElapsedTime import io.github.classgraph.ClassGraph -import io.github.deltacv.common.util.loggerForThis -import kotlinx.coroutines.* +import org.deltacv.common.util.loggerForThis import org.firstinspires.ftc.vision.VisionProcessor import org.openftc.easyopencv.OpenCvPipeline +class InitClasspathScan : ClasspathScan(), Orchestrable { + override fun wire(orchestrator: Orchestrator) { + orchestrator.register(this) { + phase(Orchestrator.Phase.INIT) { + target { scan() } + } + } + } +} + /** * Classpath scanner using ClassGraph. * * It scans for OpenCvPipelines, OpModes, VisionProcessors and TunableFields */ -class ClasspathScan { +open class ClasspathScan { companion object { val ignoredPackages = arrayOf( @@ -50,7 +40,7 @@ class ClasspathScan { "org.opencv", "imgui", "io.github.classgraph", - "io.github.deltacv", + "org.deltacv", "com.github.serivesmejia.eocvsim.pipeline", "org.firstinspires.ftc.vision", "org.lwjgl", @@ -62,10 +52,11 @@ class ClasspathScan { val logger by loggerForThis() - lateinit var scanResult: ScanResult + var scanResult: ScanResult? = null private set - private lateinit var scanResultJob: Job + var hasScanned = false + private set /** * Perform the classpath scan using ClassGraph, surprisingly fast due to @@ -77,7 +68,11 @@ class ClasspathScan { * @param addProcessorsAsPipelines if true, VisionProcessors will be wrapped as pipelines */ @Suppress("UNCHECKED_CAST") - fun scan(jarFile: String? = null, classLoader: ClassLoader? = null, addProcessorsAsPipelines: Boolean = true): ScanResult { + fun scan( + jarFile: String? = null, + classLoader: ClassLoader? = null, + addProcessorsAsPipelines: Boolean = true + ): ScanResult { val timer = ElapsedTime() val classGraph = ClassGraph() .enableClassInfo() @@ -85,22 +80,21 @@ class ClasspathScan { .enableAnnotationInfo() .rejectPackages(*ignoredPackages) - if(jarFile != null) { + if (jarFile != null) { classGraph.overrideClasspath("$jarFile!/") logger.info("Starting to scan for classes in $jarFile...") } else { logger.info("Starting to scan classpath...") } - if(classLoader != null) { + if (classLoader != null) { classGraph.overrideClassLoaders(classLoader) } val scanResult = classGraph.scan() logger.info("ClassGraph finished scanning (took ${timer.seconds()}s)") - - val tunableFieldClassesInfo = scanResult.getClassesWithAnnotation(RegisterTunableField::class.java.name) + val pipelineClasses = mutableListOf>() @@ -109,33 +103,33 @@ class ClasspathScan { fun searchPipelinesOfSuperclass(superclass: String) { logger.trace("searchPipelinesOfSuperclass: {}", superclass) - val superclassClazz = if(classLoader != null) { + val superclassClazz = if (classLoader != null) { classLoader.loadClass(superclass) } else Class.forName(superclass) - val pipelineClassesInfo = if(superclassClazz.isInterface) + val pipelineClassesInfo = if (superclassClazz.isInterface) scanResult.getClassesImplementing(superclass) - else scanResult.getSubclasses(superclass) + else scanResult.getSubclasses(superclass) - for(pipelineClassInfo in pipelineClassesInfo) { + for (pipelineClassInfo in pipelineClassesInfo) { logger.trace("pipelineClassInfo: {}", pipelineClassInfo.name) - for(pipelineSubclassInfo in pipelineClassInfo.subclasses) { + for (pipelineSubclassInfo in pipelineClassInfo.subclasses) { searchPipelinesOfSuperclass(pipelineSubclassInfo.name) // naming is my passion } - if(pipelineClassInfo.isAbstract || pipelineClassInfo.isInterface) { + if (pipelineClassInfo.isAbstract || pipelineClassInfo.isInterface) { continue // nope'd outta here } - val clazz = if(classLoader != null) { + val clazz = if (classLoader != null) { classLoader.loadClass(pipelineClassInfo.name) } else Class.forName(pipelineClassInfo.name) logger.trace("class {} super {}", clazz.typeName, clazz.superclass.typeName) - if(!pipelineClasses.contains(clazz) && ReflectUtil.hasSuperclass(clazz, superclassClazz)) { - if(clazz.isAnnotationPresent(Disabled::class.java)) { + if (!pipelineClasses.contains(clazz) && ReflectUtil.hasSuperclass(clazz, superclassClazz)) { + if (clazz.isAnnotationPresent(Disabled::class.java)) { logger.info("Found @Disabled pipeline ${clazz.typeName}") } else { logger.info("Found pipeline ${clazz.typeName}") @@ -148,7 +142,7 @@ class ClasspathScan { // start recursive hell searchPipelinesOfSuperclass(OpenCvPipeline::class.java.name) - if(jarFile != null) { + if (jarFile != null) { // Since we removed EOCV-Sim from the scan classpath, // ClassGraph does not know that OpMode and LinearOpMode // are subclasses of OpenCvPipeline, so we have to scan them @@ -157,67 +151,20 @@ class ClasspathScan { searchPipelinesOfSuperclass(LinearOpMode::class.java.name) } - if(addProcessorsAsPipelines) { + if (addProcessorsAsPipelines) { logger.info("Searching for VisionProcessors...") searchPipelinesOfSuperclass(VisionProcessor::class.java.name) } logger.info("Found ${pipelineClasses.size} pipelines") - val tunableFieldClasses = mutableListOf>>() - val tunableFieldAcceptorClasses = mutableMapOf>, Class>() - - for(tunableFieldClassInfo in tunableFieldClassesInfo) { - val clazz = if(classLoader != null) { - classLoader.loadClass(tunableFieldClassInfo.name) - } else Class.forName(tunableFieldClassInfo.name) - - if(ReflectUtil.hasSuperclass(clazz, TunableField::class.java)) { - val tunableFieldClass = clazz as Class> - - tunableFieldClasses.add(tunableFieldClass) - logger.trace("Found tunable field ${clazz.typeName}") - - for(subclass in clazz.declaredClasses) { - if(ReflectUtil.hasSuperclass(subclass, TunableFieldAcceptor::class.java)) { - tunableFieldAcceptorClasses[tunableFieldClass] = subclass as Class - logger.trace("Found acceptor for this tunable field, ${clazz.typeName}") - break - } - } - } - } - - logger.trace("Found ${tunableFieldClasses.size} tunable fields and ${tunableFieldAcceptorClasses.size} acceptors") - logger.info("Finished scanning (took ${timer.seconds()}s)") this.scanResult = ScanResult( - pipelineClasses.toTypedArray(), - tunableFieldClasses.toTypedArray(), - tunableFieldAcceptorClasses.toMap() + pipelineClasses ) - return this.scanResult - } - - /** - * Asynchronously perform the classpath scan - * and store the result in [scanResult] - */ - @OptIn(DelicateCoroutinesApi::class) - fun asyncScan() { - scanResultJob = GlobalScope.launch(Dispatchers.IO) { - scan() - } - } - - /** - * Join the async scan coroutine - * @see asyncScan - */ - fun join() = runBlocking { - scanResultJob.join() + return this.scanResult!! } } @@ -225,11 +172,7 @@ class ClasspathScan { /** * Result of the classpath scan * @param pipelineClasses the found OpenCvPipelines - * @param tunableFieldClasses the found TunableFields - * @param tunableFieldAcceptorClasses the found TunableFieldAcceptors */ data class ScanResult( - val pipelineClasses: Array>, - val tunableFieldClasses: Array>>, - val tunableFieldAcceptorClasses: Map>, Class> -) \ No newline at end of file + val pipelineClasses: List> +) diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/CombinedRuntimeLoader.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/CombinedRuntimeLoader.java new file mode 100644 index 00000000..56f62cc6 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/CombinedRuntimeLoader.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) FIRST and other WPILib contributors. + * Open Source Software; you can modify and/or share it under the terms of + * the WPILib BSD license below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of FIRST, WPILib, nor the names of other WPILib + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + */ + +package com.github.serivesmejia.eocvsim.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; + +/** Loads dynamic libraries for all platforms. */ +public final class CombinedRuntimeLoader { + private CombinedRuntimeLoader() {} + + private static String extractionDirectory; + + private static final Object extractCompleteLock = new Object(); + private static boolean extractAndVerifyComplete = false; + private static List filesAlreadyExtracted = new CopyOnWriteArrayList<>(); + + /** + * Returns library extraction directory. + * + * @return Library extraction directory. + */ + public static synchronized String getExtractionDirectory() { + return extractionDirectory; + } + + private static synchronized void setExtractionDirectory(String directory) { + extractionDirectory = directory; + } + + private static String defaultExtractionRoot; + + /** + * Gets the default extraction root location (~/.wpilib/nativecache) for use if + * setExtractionDirectory is not set. + * + * @return The default extraction root location. + */ + public static synchronized String getDefaultExtractionRoot() { + if (defaultExtractionRoot != null) { + return defaultExtractionRoot; + } + String home = System.getProperty("user.home"); + defaultExtractionRoot = Paths.get(home, ".wpilib", "nativecache").toString(); + return defaultExtractionRoot; + } + + /** + * Returns platform path. + * + * @return The current platform path. + * @throws IllegalStateException Thrown if the operating system is unknown. + */ + public static String getPlatformPath() { + String filePath; + String arch = System.getProperty("os.arch"); + + boolean intel32 = "x86".equals(arch) || "i386".equals(arch); + boolean intel64 = "amd64".equals(arch) || "x86_64".equals(arch); + + if (System.getProperty("os.name").startsWith("Windows")) { + if (intel32) { + filePath = "/windows/x86/"; + } else { + filePath = "/windows/x86-64/"; + } + } else if (System.getProperty("os.name").startsWith("Mac")) { + filePath = "/osx/universal/"; + } else if (System.getProperty("os.name").startsWith("Linux")) { + if (intel32) { + filePath = "/linux/x86/"; + } else if (intel64) { + filePath = "/linux/x86-64/"; + } else if ("arm".equals(arch) || "arm32".equals(arch)) { + filePath = "/linux/arm32/"; + } else if ("aarch64".equals(arch) || "arm64".equals(arch)) { + filePath = "/linux/arm64/"; + } else { + filePath = "/linux/nativearm/"; + } + } else { + throw new IllegalStateException(); + } + + return filePath; + } + + private static String getLoadErrorMessage(String libraryName, UnsatisfiedLinkError ule) { + StringBuilder msg = new StringBuilder(512); + msg.append(libraryName) + .append(" could not be loaded from path\n" + "\tattempted to load for platform ") + .append(getPlatformPath()) + .append("\nLast Load Error: \n") + .append(ule.getMessage()) + .append('\n'); + if (System.getProperty("os.name").startsWith("Windows")) { + msg.append( + "A common cause of this error is missing the C++ runtime.\n" + + "Download the latest at https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170\n"); + } + return msg.toString(); + } + + /** + * Architecture-specific information containing file hashes for a specific CPU architecture (e.g., + * x86-64, arm64). + */ + public record ArchInfo(Map fileHashes) {} + + /** + * Platform-specific information containing architectures for a specific OS platform (e.g., linux, + * windows). + */ + public record PlatformInfo(Map architectures) {} + + /** Overall resource information to be serialized */ + public record ResourceInformation( + // Combined MD5 hash of all native resource files + String hash, + // Platform-specific native libraries organized by platform then architecture + Map platforms, + // List of supported versions for these native resources + List versions) {} + + /** + * Extract a list of native libraries. + * + * @param The class where the resources would be located + * @param clazz The actual class object + * @param resourceName The resource name on the classpath to use for file lookup + * @return List of all libraries that were extracted + * @throws IOException Thrown if resource not found or file could not be extracted + */ + public static List extractLibraries(Class clazz, String resourceName) + throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ResourceInformation resourceInfo; + try (var stream = clazz.getResourceAsStream(resourceName)) { + resourceInfo = mapper.readValue(stream, ResourceInformation.class); + } + + var platformPath = Paths.get(getPlatformPath()); + var platform = platformPath.getName(0).toString(); + var arch = platformPath.getName(1).toString(); + + var platformInfo = resourceInfo.platforms().get(platform); + if (platformInfo == null) { + throw new IOException("Platform " + platform + " not found in resource information"); + } + + var archInfo = platformInfo.architectures().get(arch); + if (archInfo == null) { + throw new IOException("Architecture " + arch + " not found for platform " + platform); + } + + // Map of to + Map filenameToHash = archInfo.fileHashes(); + + var extractionPathString = getExtractionDirectory(); + + if (extractionPathString == null) { + // Folder to extract to derived from overall hash + String combinedHash = resourceInfo.hash(); + + var defaultExtractionRoot = getDefaultExtractionRoot(); + var extractionPath = Paths.get(defaultExtractionRoot, platform, arch, combinedHash); + extractionPathString = extractionPath.toString(); + + setExtractionDirectory(extractionPathString); + } + + List extractedFiles = new ArrayList<>(); + + for (String file : filenameToHash.keySet()) { + try (var stream = clazz.getResourceAsStream(file)) { + Objects.requireNonNull(stream); + + var outputFile = Paths.get(extractionPathString, new File(file).getName()); + + String fileHash = filenameToHash.get(file); + + extractedFiles.add(outputFile.toString()); + if (outputFile.toFile().exists()) { + if (hashEm(outputFile.toFile()).equals(fileHash)) { + continue; + } else { + // Hashes don't match, delete and re-extract + System.err.println( + outputFile.toAbsolutePath().toString() + + " failed validation - deleting and re-extracting"); + outputFile.toFile().delete(); + } + } + var parent = outputFile.getParent(); + if (parent == null) { + throw new IOException("Output file has no parent"); + } + parent.toFile().mkdirs(); + + try (var os = Files.newOutputStream(outputFile)) { + Files.copy(stream, outputFile, StandardCopyOption.REPLACE_EXISTING); + } + + if (!hashEm(outputFile.toFile()).equals(fileHash)) { + throw new IOException("Hash of extracted file does not match expected hash"); + } + } + } + + return extractedFiles; + } + + private static String hashEm(File f) throws IOException { + try { + MessageDigest fileHash = MessageDigest.getInstance("MD5"); + try (var dis = + new DigestInputStream(new BufferedInputStream(new FileInputStream(f)), fileHash)) { + dis.readAllBytes(); + } + var ret = HexFormat.of().formatHex(fileHash.digest()); + return ret; + } catch (NoSuchAlgorithmException e) { + throw new IOException("Unable to verify extracted native files", e); + } + } + + /** + * Load a single library from a list of extracted files. + * + * @param libraryName The library name to load + * @param extractedFiles The extracted files to search + * @throws IOException If library was not found + */ + public static void loadLibrary(String libraryName, List extractedFiles) + throws IOException { + String currentPath = null; + try { + for (var extractedFile : extractedFiles) { + if (extractedFile.contains(libraryName)) { + // Load it + currentPath = extractedFile; + System.load(extractedFile); + return; + } + } + throw new IOException("Could not find library " + libraryName); + } catch (UnsatisfiedLinkError ule) { + throw new IOException(getLoadErrorMessage(currentPath, ule)); + } + } + + /** + * Load a list of native libraries out of a single directory. + * + * @param The class where the resources would be located + * @param clazz The actual class object + * @param librariesToLoad List of libraries to load + * @throws IOException Throws an IOException if not found + */ + public static void loadLibraries(Class clazz, String... librariesToLoad) + throws IOException { + synchronized (extractCompleteLock) { + if (!extractAndVerifyComplete) { + // Extract everything + filesAlreadyExtracted = extractLibraries(clazz, "/ResourceInformation.json"); + extractAndVerifyComplete = true; + } + + for (var library : librariesToLoad) { + loadLibrary(library, filesAlreadyExtracted); + } + } + } +} + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/FileFilters.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/FileFilters.kt index 05f0d8d0..5e518e8d 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/FileFilters.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/FileFilters.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util import javax.swing.filechooser.FileNameExtensionFilter @@ -52,4 +34,4 @@ object FileFilters { */ @JvmField val logFileFilter = FileNameExtensionFilter("Log File (*.log)", "log") -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/LibraryLoader.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/LibraryLoader.java new file mode 100644 index 00000000..0f349199 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/LibraryLoader.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.util; + +import java.io.IOException; + +import org.opencv.core.Core; +import org.wpilib.math.jni.WPIMathJNI; +import org.wpilib.net.WPINetJNI; +import org.wpilib.util.WPIUtilJNI; +import org.wpilib.vision.apriltag.jni.AprilTagJNI; +import org.wpilib.vision.camera.CameraServerJNI; +import org.wpilib.vision.camera.OpenCvLoader; + +public class LibraryLoader { + public record Result(boolean success, Throwable error) {} + + public static Result loadLibraries() { + WPIUtilJNI.Helper.setExtractOnStaticLoad(false); + AprilTagJNI.Helper.setExtractOnStaticLoad(false); + WPIMathJNI.Helper.setExtractOnStaticLoad(false); + WPINetJNI.Helper.setExtractOnStaticLoad(false); + CameraServerJNI.Helper.setExtractOnStaticLoad(false); + OpenCvLoader.Helper.setExtractOnStaticLoad(false); + + try { + CombinedRuntimeLoader.loadLibraries(LibraryLoader.class, "wpiutiljni"); + WPIUtilJNI.checkMsvcRuntime(); + + CombinedRuntimeLoader.loadLibraries(LibraryLoader.class, + "apriltagjni", "wpimathjni", "wpinetjni", "cscorejni"); + + CombinedRuntimeLoader.loadLibraries(LibraryLoader.class, Core.NATIVE_LIBRARY_NAME); + } catch(IOException | UnsatisfiedLinkError e) { + return new Result(false, e); + } + + return new Result(true, null); + } +} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/CompilerProvider.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/CompilerProvider.kt index 6f8c2bd1..5e471ab9 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/CompilerProvider.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/CompilerProvider.kt @@ -1,29 +1,11 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.compiler -import io.github.deltacv.common.util.loggerOf +import org.deltacv.common.util.loggerOf import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler import javax.tools.JavaCompiler import javax.tools.ToolProvider @@ -50,4 +32,4 @@ val compiler by lazy { } } -data class Compiler(val name: String, val javaCompiler: JavaCompiler) \ No newline at end of file +data class Compiler(val name: String, val javaCompiler: JavaCompiler) diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/DelegatingStandardFileManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/DelegatingStandardFileManager.kt index 28787dfe..6858a6f4 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/DelegatingStandardFileManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/DelegatingStandardFileManager.kt @@ -1,28 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives & (c) 2017 Robert Atkinson - * - * Based from the FTC SDK's org.firstinspires.ftc.onbotjava.OnBotJavaDelegatingStandardFileManager - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.compiler import java.io.File @@ -54,4 +34,4 @@ open class DelegatingStandardFileManager( override fun getLocation(location: JavaFileManager.Location): MutableIterable = delegate.getLocation(location) ?: Collections.emptyList() -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/JarPacker.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/JarPacker.kt index 1cd9616a..f0b13ccc 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/JarPacker.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/JarPacker.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.compiler import com.github.serivesmejia.eocvsim.util.SysUtil @@ -76,4 +58,4 @@ object JarPacker { jar.closeEntry() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/MaxActiveContextsException.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/MaxActiveContextsException.kt index b725aff0..27418f5c 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/MaxActiveContextsException.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/MaxActiveContextsException.kt @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.exception /** @@ -6,4 +11,4 @@ package com.github.serivesmejia.eocvsim.util.exception * from user code (like running a pipeline) in a time-expiring way. * @param message the message of the exception */ -class MaxActiveContextsException(message: String = "") : Exception(message) \ No newline at end of file +class MaxActiveContextsException(message: String = "") : Exception(message) diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReport.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReport.kt index f3d56485..cd3a92b3 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReport.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReport.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.exception.handling import com.github.serivesmejia.eocvsim.EOCVSim @@ -29,7 +11,7 @@ import com.github.serivesmejia.eocvsim.util.StrUtil import com.github.serivesmejia.eocvsim.util.SysUtil import com.github.serivesmejia.eocvsim.util.extension.plus import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import java.io.File import java.time.LocalDateTime import java.time.format.DateTimeFormatter @@ -65,15 +47,13 @@ class CrashReport(causedByException: Throwable, isDummy: Boolean = false) { "Oops.", "Uh... Did I do that?", "This is fine.", - "I'm not even angry. I'm being so sincere right now.", "I feel sad now :(", "I let you down. Sorry :(", "On the bright side, I bought you a teddy bear!", "Daisy, daisy...", "Oh - I know what I did wrong!", - "Hey, that tickles! Hehehe!", - "I blame Dean Kamen.", - "You should try our sister simulator!", + "I blame ESD.", + "You should try PaperVision!", "Don't be sad. I'll do better next time, I promise!", "Don't be sad, have a hug! <3", "I just don't know what went wrong :(", @@ -86,15 +66,7 @@ class CrashReport(causedByException: Throwable, isDummy: Boolean = false) { "This doesn't make any sense!", "Why is it breaking :(", "Don't do that.", - "Ouch. That hurt :(", - "This is a token for 1 free hug. Redeem at your nearest local team: [~~HUG~~]", - "But it works on my machine!", - "Y a través de las estrellas ella viaja...", - "Tras el extraño silencio del sol ausente", - "no lo pienses demasiado, todo estará bien,", - "encontrarás otro desastre que hacer,", - "¿qué será de sentarse bajo la tumba del sol sin nunca hacer un desastre?", - "y por una ultima noche juntos," + "But it works on my machine!" ) @JvmStatic val defaultCrashFileName get() = "crashreport-$defaultFileName" @@ -106,9 +78,14 @@ class CrashReport(causedByException: Throwable, isDummy: Boolean = false) { private val sb = StringBuilder() init { - sb.appendLine("/--------------------------------\\").appendLine() - sb.appendLine(" EOCV-Sim v${EOCVSim.VERSION} crash report").appendLine() - sb.appendLine("\\--------------------------------/").appendLine() + val title = " EOCV-Sim v${EOCVSim.VERSION} crash report" + val dashes = "-".repeat(title.length) + val topBorder = "/$dashes\\" + val bottomBorder = "\\$dashes/" + + sb.appendLine(topBorder).appendLine() + sb.appendLine(title).appendLine() + sb.appendLine(bottomBorder).appendLine() sb.appendLine("! ${wittyComments.random()}").appendLine() @@ -119,23 +96,39 @@ class CrashReport(causedByException: Throwable, isDummy: Boolean = false) { sb.appendLine(causedByException.message).appendLine() } - sb.appendLine("==========================================").appendLine() + val infoSectionLines = listOf( + ": EOCV-Sim info", + " Version: ${EOCVSim.VERSION}", + " Built on: ${Build.buildDate}", + ": System specs", + " OS name: $OS_NAME", + " OS version: $OS_VERSION", + " Detected OS: $SYSUTIL_DETECTED_OS", + " Arch: $OS_ARCH", + " Detected Arch: ${SysUtil.ARCH}", + " Java version: $JAVA_VERSION", + " Java vendor: $JAVA_VENDOR", + " Last memory usage: ${SysUtil.getMemoryUsageMB()} MB" + ) + val infoSectionDivider = "=".repeat(infoSectionLines.maxOf { it.length }) + + sb.appendLine(infoSectionDivider).appendLine() - sb.appendLine(": EOCV-Sim info") - sb.appendLine(" Version: ${EOCVSim.VERSION}") - sb.appendLine(" Built on: ${Build.buildDate}").appendLine() + sb.appendLine(infoSectionLines[0]) + sb.appendLine(infoSectionLines[1]) + sb.appendLine(infoSectionLines[2]).appendLine() - sb.appendLine(": System specs") - sb.appendLine(" OS name: $OS_NAME") - sb.appendLine(" OS version: $OS_VERSION") - sb.appendLine(" Detected OS: $SYSUTIL_DETECTED_OS") - sb.appendLine(" Arch: $OS_ARCH") - sb.appendLine(" Detected Arch: ${SysUtil.ARCH}") - sb.appendLine(" Java version: $JAVA_VERSION") - sb.appendLine(" Java vendor: $JAVA_VENDOR") - sb.appendLine(" Last memory usage: ${SysUtil.getMemoryUsageMB()} MB").appendLine() + sb.appendLine(infoSectionLines[3]) + sb.appendLine(infoSectionLines[4]) + sb.appendLine(infoSectionLines[5]) + sb.appendLine(infoSectionLines[6]) + sb.appendLine(infoSectionLines[7]) + sb.appendLine(infoSectionLines[8]) + sb.appendLine(infoSectionLines[9]) + sb.appendLine(infoSectionLines[10]) + sb.appendLine(infoSectionLines[11]).appendLine() - sb.appendLine("==========================================").appendLine() + sb.appendLine(infoSectionDivider).appendLine() sb.appendLine(": Full thread dump").appendLine() @@ -148,10 +141,10 @@ class CrashReport(causedByException: Throwable, isDummy: Boolean = false) { } sb.appendLine() - sb.appendLine("==================================").appendLine() + sb.appendLine(infoSectionDivider).appendLine() - sb.appendLine(": Full logs").appendLine() + sb.appendLine(": Full log").appendLine() val lastLogFile = EOCVSimFolder.lastLogFile if(lastLogFile != null) { @@ -203,3 +196,4 @@ class CrashReport(causedByException: Throwable, isDummy: Boolean = false) { override fun toString() = sb.toString() } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReportOutputMain.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReportOutputMain.kt index 0f92e917..48209679 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReportOutputMain.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReportOutputMain.kt @@ -1,13 +1,25 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.exception.handling import com.formdev.flatlaf.intellijthemes.FlatArcDarkIJTheme import com.github.serivesmejia.eocvsim.Build import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.gui.dialog.CrashReportOutput import com.github.serivesmejia.eocvsim.util.SysUtil -import kotlinx.coroutines.Runnable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import org.koin.core.context.GlobalContext +import org.koin.dsl.module import picocli.CommandLine import java.io.File import javax.swing.SwingUtilities +import kotlin.system.exitProcess object CrashReportOutputMain { @CommandLine.Command(name = "report", mixinStandardHelpOptions = true, version = [Build.versionString]) @@ -16,13 +28,33 @@ object CrashReportOutputMain { @JvmField var crashReportPath: String? = null override fun run() { + if (crashReportPath == null || crashReportPath!!.isEmpty()) { + System.err.println("Crash report path is required (-p or --path)") + exitProcess(1) + } + + val file = File(crashReportPath!!) + if (!file.exists()) { + System.err.println("Crash report file not found: $crashReportPath") + exitProcess(1) + } + SwingUtilities.invokeLater(FlatArcDarkIJTheme::setup) - DialogFactory.createCrashReport(null, SysUtil.loadFileStr(File(crashReportPath ?: ""))) + + try { + val crashContent = SysUtil.loadFileStr(file) + CrashReportOutput(null, crashContent ?: "") + } catch (e: Exception) { + System.err.println("Failed to display crash report: $ {e.message}") + e.printStackTrace() + exitProcess(1) + } } } @JvmStatic fun main(args: Array) { - CommandLine(CrashReportOutputCommandInterface()).execute(*args) + val exitCode = CommandLine(CrashReportOutputCommandInterface()).execute(*args) + exitProcess(exitCode) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/EOCVSimUncaughtExceptionHandler.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/EOCVSimUncaughtExceptionHandler.kt index 86fc47ec..3b3e36c2 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/EOCVSimUncaughtExceptionHandler.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/EOCVSimUncaughtExceptionHandler.kt @@ -1,13 +1,27 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.exception.handling import com.github.serivesmejia.eocvsim.currentMainThread +import com.github.serivesmejia.eocvsim.gui.DialogFactory import com.github.serivesmejia.eocvsim.util.JavaProcess -import io.github.deltacv.common.util.loggerForThis +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.deltacv.common.util.loggerForThis +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.swing.Swing +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named import java.lang.Thread.sleep import javax.swing.SwingUtilities import kotlin.system.exitProcess -class EOCVSimUncaughtExceptionHandler private constructor() : Thread.UncaughtExceptionHandler { +class EOCVSimUncaughtExceptionHandler private constructor() : Thread.UncaughtExceptionHandler, KoinComponent{ companion object { const val MAX_UNCAUGHT_EXCEPTIONS_BEFORE_CRASH = 3 @@ -19,6 +33,9 @@ class EOCVSimUncaughtExceptionHandler private constructor() : Thread.UncaughtExc val logger by loggerForThis() + private val dialogFactory: DialogFactory by inject() + private val onCrash: EventHandler by inject(named("onCrash")) + private var uncaughtExceptionsCount = 0 override fun uncaughtException(t: Thread, e: Throwable) { @@ -33,21 +50,21 @@ class EOCVSimUncaughtExceptionHandler private constructor() : Thread.UncaughtExc logger.error("Uncaught exception thrown in \"${t.name}\" thread", e) - //Exit if uncaught exception happened in the main thread - //since we would be basically in a deadlock state if that happened - //or if we have a lotta uncaught exceptions. + // Exit if uncaught exception happened in the main thread + // since we would be basically in an invalid state if that happened if(t == currentMainThread || SwingUtilities.isEventDispatchThread() || e !is Exception || uncaughtExceptionsCount > MAX_UNCAUGHT_EXCEPTIONS_BEFORE_CRASH) { val file = CrashReport(e).saveCrashReport() logger.warn("If this error persists, open an issue on EOCV-Sim's GitHub attaching the crash report file.") logger.warn("The application will exit now (exit code 1)") - Thread { - JavaProcess.killSubprocessesOnExit = false - JavaProcess.exec(CrashReportOutputMain::class.java, listOf(), listOf("-p=${file.absolutePath}")) - }.start() + onCrash.run() - sleep(3000) // wait enough for the subprocess to start + runBlocking { + launch(Dispatchers.Swing) { + dialogFactory.instantiateCrashReport(file.readText()) + } + } exitProcess(1) } else { @@ -61,4 +78,4 @@ class EOCVSimUncaughtExceptionHandler private constructor() : Thread.UncaughtExc } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/FileWatcher.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/FileWatcher.kt index d2a711ea..5d44cd57 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/FileWatcher.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/FileWatcher.kt @@ -1,7 +1,12 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.util.io import com.github.serivesmejia.eocvsim.util.event.EventHandler -import io.github.deltacv.common.util.loggerOf +import org.deltacv.common.util.loggerOf import org.slf4j.Logger import java.io.File import java.nio.file.* @@ -170,4 +175,4 @@ class FileWatcher( return name.substring(dot + 1).lowercase() in set } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/serialization/JacksonJsonSupport.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/serialization/JacksonJsonSupport.kt new file mode 100644 index 00000000..ef6e64e2 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/serialization/JacksonJsonSupport.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.util.serialization + +import com.fasterxml.jackson.annotation.JsonAutoDetect +import com.fasterxml.jackson.annotation.PropertyAccessor +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.module.kotlin.registerKotlinModule + +/** + * Shared Jackson mappers for the simulator. + * + * `persistenceMapper` is configured for the config/input-source files and only + * serializes fields, avoiding getter-based schema drift from computed properties. + * `ipcMapper` keeps the same Kotlin support but is meant for internal message payloads. + */ +object JacksonJsonSupport { + + @JvmField + val persistenceMapper: ObjectMapper = ObjectMapper() + .registerKotlinModule() + .registerModule(VideoModeJackson.module()) + .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .enable(SerializationFeature.INDENT_OUTPUT) + + @JvmField + val ipcMapper: ObjectMapper = ObjectMapper() + .registerKotlinModule() + .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .enable(SerializationFeature.INDENT_OUTPUT) +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/serialization/PolymorphicAdapter.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/serialization/PolymorphicAdapter.kt deleted file mode 100644 index be150491..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/serialization/PolymorphicAdapter.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2024 Sebastian Erives - * - * 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. - * - */ - -package com.github.serivesmejia.eocvsim.util.serialization - -import com.google.gson.* -import java.lang.reflect.Type -private val gson = Gson() - -open class PolymorphicAdapter( - val name: String, - val classloader: ClassLoader = PolymorphicAdapter::class.java.classLoader -) : JsonSerializer, JsonDeserializer { - - override fun serialize(src: T, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - val obj = JsonObject() - - obj.addProperty("${name}Class", src!!::class.java.name) - obj.add(name, gson.toJsonTree(src)) - - return obj - } - - @Suppress("UNCHECKED_CAST") - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): T { - val className = json.asJsonObject.get("${name}Class").asString - - val clazz = classloader.loadClass(className) - - return gson.fromJson(json.asJsonObject.get(name), clazz) as T - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/serialization/VideoModeJackson.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/serialization/VideoModeJackson.kt new file mode 100644 index 00000000..c4bc7092 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/serialization/VideoModeJackson.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package com.github.serivesmejia.eocvsim.util.serialization + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.module.SimpleModule +import org.wpilib.util.PixelFormat +import org.wpilib.vision.camera.VideoMode + +/** + * Keeps persisted CameraSource.videoMode JSON stable while allowing reconstruction + * of WPILib VideoMode, which has no Jackson-friendly default constructor. + */ +object VideoModeJackson { + + fun module(): SimpleModule = SimpleModule("VideoModeModule") + .addSerializer(VideoMode::class.java, VideoModeSerializer) + .addDeserializer(VideoMode::class.java, VideoModeDeserializer) + + private object VideoModeSerializer : JsonSerializer() { + override fun serialize(value: VideoMode, gen: JsonGenerator, serializers: SerializerProvider) { + gen.writeStartObject() + gen.writeStringField("pixelFormat", value.pixelFormat.toString()) + gen.writeNumberField("width", value.width) + gen.writeNumberField("height", value.height) + gen.writeNumberField("fps", value.fps) + gen.writeEndObject() + } + } + + private object VideoModeDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): VideoMode { + val node = parser.codec.readTree(parser) + + val pixelFormatValue = parsePixelFormat(node.get("pixelFormat")) + val width = node.get("width")?.asInt() ?: 640 + val height = node.get("height")?.asInt() ?: 480 + val fps = node.get("fps")?.asInt() ?: 30 + + return VideoMode(pixelFormatValue, width, height, fps) + } + + private fun parsePixelFormat(node: com.fasterxml.jackson.databind.JsonNode?): Int { + if (node == null || node.isNull) return 0 + if (node.isInt) return node.asInt() + + if (node.isTextual) { + val text = node.asText().trim() + if (text.isEmpty()) return 0 + + val enumConstant = PixelFormat.entries.firstOrNull { + it.name.equals(text, ignoreCase = true) || + it.toString().equals(text, ignoreCase = true) + } ?: return 0 + + return enumConstantToInt(enumConstant) + } + + return 0 + } + + private fun enumConstantToInt(value: PixelFormat): Int { + return try { + val method = value.javaClass.getMethod("getValue") + (method.invoke(value) as Number).toInt() + } catch (_: Exception) { + (value as Enum<*>).ordinal + } + } + } +} + + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/WorkspaceManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/WorkspaceManager.kt index 4f0c756d..16da7664 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/WorkspaceManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/WorkspaceManager.kt @@ -1,42 +1,32 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.workspace import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.pipeline.compiler.CompiledPipelineManager +import com.github.serivesmejia.eocvsim.config.ConfigManager +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.pipeline.compiled.CompiledPipelineManager +import com.github.serivesmejia.eocvsim.util.SysUtil import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.orchestration.initDependency +import com.github.serivesmejia.eocvsim.util.orchestration.PhaseOrchestrableBase import com.github.serivesmejia.eocvsim.util.io.FileWatcher -import com.github.serivesmejia.eocvsim.util.SysUtil -import io.github.deltacv.common.util.loggerForThis import com.github.serivesmejia.eocvsim.workspace.config.WorkspaceConfig import com.github.serivesmejia.eocvsim.workspace.config.WorkspaceConfigLoader import com.github.serivesmejia.eocvsim.workspace.util.WorkspaceTemplate +import org.deltacv.common.util.loggerForThis +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named import java.io.File +import java.nio.file.Path import java.nio.file.Paths /** @@ -52,11 +42,17 @@ import java.nio.file.Paths * excluded paths and file extensions, and the source files themselves * which are built using an embedded compiler and loaded with a custom * classloader. - * - * @param eocvSim the EOCVSim instance to manage the workspace for */ @OptIn(DelicateCoroutinesApi::class) -class WorkspaceManager(val eocvSim: EOCVSim) { +class WorkspaceManager : PhaseOrchestrableBase(), KoinComponent { + + private val pipelineManager: PipelineManager by inject() + private val compiledPipelineManager: CompiledPipelineManager by inject() + private val configManager: ConfigManager by initDependency(inject()) + private val params: EOCVSim.Parameters by inject() + private val scope: CoroutineScope by inject() + + private val onMainLoop: EventHandler by inject(named("onMainLoop")) val logger by loggerForThis() @@ -70,17 +66,17 @@ class WorkspaceManager(val eocvSim: EOCVSim) { */ var workspaceFile = File(".") set(value) { - if(value != workspaceFile) { + if (value != workspaceFile) { workspaceConfigLoader.workspaceFile = value - eocvSim.config.workspacePath = value.absolutePath - eocvSim.configManager.saveToFile() + configManager.config.workspacePath = value.absolutePath + configManager.saveToFile() field = value logger.info("Set current workspace to ${value.absolutePath}") - if(::fileWatcher.isInitialized) + if (::fileWatcher.isInitialized) fileWatcher.stop() fileWatcher = FileWatcher( @@ -96,10 +92,10 @@ class WorkspaceManager(val eocvSim: EOCVSim) { cachedWorkspConfig = workspaceConfigLoader.loadWorkspaceConfig() - if(cachedWorkspConfig == null) { + if (cachedWorkspConfig == null) { cachedWorkspConfig = WorkspaceConfig() - if(workspaceConfigLoader.workspaceConfigFile.exists()) + if (workspaceConfigLoader.workspaceConfigFile.exists()) logger.warn("Recreating workspace config file, old one failed to parse") else logger.info("Creating workspace config file...") @@ -122,7 +118,7 @@ class WorkspaceManager(val eocvSim: EOCVSim) { cachedWorkspConfig = value } get() { - if(cachedWorkspConfig == null) + if (cachedWorkspConfig == null) ::workspaceFile.set(workspaceFile) return cachedWorkspConfig!! @@ -132,57 +128,63 @@ class WorkspaceManager(val eocvSim: EOCVSim) { * The relative path to the sources folder specified in the workspace configuration */ val sourcesRelativePath get() = workspaceConfig.sourcesPath!! + /** * The absolute path to the sources folder specified in the workspace configuration */ - val sourcesAbsolutePath get() = Paths.get(workspaceFile.absolutePath, sourcesRelativePath).normalize()!! + val sourcesAbsolutePath: Path get() = Paths.get(workspaceFile.absolutePath, sourcesRelativePath).normalize() /** * The relative path to the resources folder specified in the workspace configuration */ val resourcesRelativePath get() = workspaceConfig.resourcesPath!! + /** * The absolute path to the resources folder specified in the workspace configuration */ - val resourcesAbsolutePath get() = Paths.get(workspaceFile.absolutePath, resourcesRelativePath).normalize()!! + val resourcesAbsolutePath: Path get() = Paths.get(workspaceFile.absolutePath, resourcesRelativePath).normalize() /** * The relative paths to the excluded paths specified in the workspace configuration */ - val excludedRelativePaths get() = workspaceConfig.excludedPaths + val excludedRelativePaths: List get() = workspaceConfig.excludedPaths + /** * The absolute paths to the excluded paths specified in the workspace configuration */ - val excludedAbsolutePaths get() = excludedRelativePaths.map { - Paths.get(workspaceFile.absolutePath, it).normalize()!! - } + val excludedAbsolutePaths + get() = excludedRelativePaths.map { + Paths.get(workspaceFile.absolutePath, it).normalize() + } /** * The file extensions to exclude from the workspace */ - val excludedFileExtensions get() = workspaceConfig.excludedFileExtensions + val excludedFileExtensions: ArrayList get() = workspaceConfig.excludedFileExtensions /** * The source files in the workspace, excluding the excluded paths and file extensions */ - val sourceFiles get() = SysUtil.filesUnder(sourcesAbsolutePath.toFile()) { file -> - file.name.endsWith(".java") && excludedAbsolutePaths.stream().noneMatch { - file.startsWith(it.toFile().absolutePath) + val sourceFiles: List + get() = SysUtil.filesUnder(sourcesAbsolutePath.toFile()) { file -> + file.name.endsWith(".java") && excludedAbsolutePaths.stream().noneMatch { + file.startsWith(it.toFile().absolutePath) + } } - } /** * The resource files in the workspace, excluding the excluded paths and file extensions */ - val resourceFiles get() = SysUtil.filesUnder(resourcesAbsolutePath.toFile()) { file -> - file.name.run { - !endsWith(".java") && !endsWith(".class") && this != "eocvsim_workspace.json" - } && excludedAbsolutePaths.stream().noneMatch { - file.startsWith(it.toFile().absolutePath) - } && excludedFileExtensions.stream().noneMatch { - file.name.endsWith(".$it") + val resourceFiles: List + get() = SysUtil.filesUnder(resourcesAbsolutePath.toFile()) { file -> + file.name.run { + !endsWith(".java") && !endsWith(".class") && this != "eocvsim_workspace.json" + } && excludedAbsolutePaths.stream().noneMatch { + file.startsWith(it.toFile().absolutePath) + } && excludedFileExtensions.stream().noneMatch { + file.name.endsWith(".$it") + } } - } /** * Event handler to run code when the workspace changes @@ -195,11 +197,38 @@ class WorkspaceManager(val eocvSim: EOCVSim) { lateinit var fileWatcher: FileWatcher private set + /** + * Initializes the workspace manager + * To be called by the EOCVSim instance + * Initializes the file watcher and the workspace configuration loader + * and sets the workspace file to the initial workspace or the default one + */ + override suspend fun init() { + onWorkspaceChange { + fileWatcher.onChange { + pipelineManager.compiledPipelineManager.asyncBuild() + } + } + + val file = params.initialWorkspace ?: File(configManager.config.workspacePath) + + workspaceFile = if (file.exists()) + file + else + CompiledPipelineManager.DEF_WORKSPACE_FOLDER + } + + override suspend fun run() { } + + override suspend fun destroy() { + stopFileWatcher() + } + /** * Stops the current file watcher, if initialized */ fun stopFileWatcher() { - if(::fileWatcher.isInitialized) { + if (::fileWatcher.isInitialized) { fileWatcher.stop() } } @@ -213,8 +242,8 @@ class WorkspaceManager(val eocvSim: EOCVSim) { * @return true if the workspace was created successfully, false otherwise */ fun createWorkspaceWithTemplate(folder: File, template: WorkspaceTemplate): Boolean { - if(!folder.isDirectory) return false - if(!template.extractToIfEmpty(folder)) return false + if (!folder.isDirectory) return false + if (!template.extractToIfEmpty(folder)) return false workspaceFile = folder return true @@ -226,41 +255,21 @@ class WorkspaceManager(val eocvSim: EOCVSim) { * Runs asynchronously on a coroutine * @param folder the folder to create the workspace in */ - @JvmOverloads fun createWorkspaceWithTemplateAsync( + @JvmOverloads + fun createWorkspaceWithTemplateAsync( folder: File, template: WorkspaceTemplate, finishCallback: (() -> Unit)? = null - ) = GlobalScope.launch(Dispatchers.IO) { - if(!folder.isDirectory) return@launch - if(!template.extractToIfEmpty(folder)) return@launch + ) = scope.launch(Dispatchers.IO) { + if (!folder.isDirectory) return@launch + if (!template.extractToIfEmpty(folder)) return@launch - eocvSim.onMainUpdate.once { + onMainLoop.once { workspaceFile = folder - if(finishCallback != null) finishCallback() - - eocvSim.visualizer.asyncCompilePipelines() - } - } + if (finishCallback != null) finishCallback() - /** - * Initializes the workspace manager - * To be called by the EOCVSim instance - * Initializes the file watcher and the workspace configuration loader - * and sets the workspace file to the initial workspace or the default one - */ - fun init() { - onWorkspaceChange { - fileWatcher.onChange { - eocvSim.pipelineManager.compiledPipelineManager.asyncBuild() - } + compiledPipelineManager.asyncBuild() } - - val file = eocvSim.params.initialWorkspace ?: File(eocvSim.config.workspacePath) - - workspaceFile = if(file.exists()) - file - else - CompiledPipelineManager.DEF_WORKSPACE_FOLDER } /** @@ -280,3 +289,4 @@ class WorkspaceManager(val eocvSim: EOCVSim) { } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfig.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfig.java index 332b4a9a..b227cf91 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfig.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfig.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.workspace.config; import java.util.ArrayList; @@ -40,4 +22,4 @@ public class WorkspaceConfig { public String eocvSimVersion = ""; -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfigLoader.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfigLoader.kt index 859730d1..e14c30fc 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfigLoader.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfigLoader.kt @@ -1,9 +1,14 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.workspace.config import com.github.serivesmejia.eocvsim.Build import com.github.serivesmejia.eocvsim.util.SysUtil -import io.github.deltacv.common.util.loggerForThis -import com.google.gson.GsonBuilder +import com.github.serivesmejia.eocvsim.util.serialization.JacksonJsonSupport +import org.deltacv.common.util.loggerForThis import java.io.File /** @@ -13,10 +18,6 @@ import java.io.File */ class WorkspaceConfigLoader(var workspaceFile: File) { - companion object { - private val gson = GsonBuilder().setPrettyPrinting().create() - } - /** * The workspace configuration file */ @@ -34,7 +35,7 @@ class WorkspaceConfigLoader(var workspaceFile: File) { val configStr = SysUtil.loadFileStr(workspaceConfigFile) return try { - gson.fromJson(configStr, WorkspaceConfig::class.java) + JacksonJsonSupport.persistenceMapper.readValue(configStr, WorkspaceConfig::class.java) } catch(e: Exception) { logger.error("Failed to load workspace config", e) null @@ -47,8 +48,8 @@ class WorkspaceConfigLoader(var workspaceFile: File) { */ fun saveWorkspaceConfig(config: WorkspaceConfig) { config.eocvSimVersion = Build.standardVersionString - val configStr = gson.toJson(config) + val configStr = JacksonJsonSupport.persistenceMapper.writeValueAsString(config) SysUtil.saveFileStr(workspaceConfigFile, configStr) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/VSCodeLauncher.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/VSCodeLauncher.kt index e877b274..3002151b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/VSCodeLauncher.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/VSCodeLauncher.kt @@ -1,34 +1,16 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.workspace.util import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import com.github.serivesmejia.eocvsim.util.SysUtil -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import kotlinx.coroutines.DelicateCoroutinesApi import java.io.File @@ -64,6 +46,6 @@ object VSCodeLauncher { * Runs in a coroutine in the IO dispatcher context */ @OptIn(DelicateCoroutinesApi::class) - fun asyncLaunch(workspace: File) = GlobalScope.launch(Dispatchers.IO) { launch(workspace) } + fun asyncLaunch(workspace: File, scope: CoroutineScope) = scope.launch(Dispatchers.IO) { launch(workspace) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/WorkspaceTemplate.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/WorkspaceTemplate.kt index 6420d5a5..f3099bf5 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/WorkspaceTemplate.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/WorkspaceTemplate.kt @@ -1,29 +1,11 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package com.github.serivesmejia.eocvsim.workspace.util -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import java.io.File abstract class WorkspaceTemplate { @@ -45,4 +27,4 @@ abstract class WorkspaceTemplate { abstract fun extractTo(folder: File): Boolean -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt index d47f59bd..e87a6112 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt @@ -1,30 +1,12 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.workspace.util.template import com.github.serivesmejia.eocvsim.util.SysUtil -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import com.github.serivesmejia.eocvsim.workspace.util.WorkspaceTemplate import net.lingala.zip4j.ZipFile import java.io.File @@ -57,3 +39,4 @@ object DefaultWorkspaceTemplate : WorkspaceTemplate() { } } + diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt index 096c5dc6..df7d9f64 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt @@ -1,31 +1,13 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package com.github.serivesmejia.eocvsim.workspace.util.template import com.github.serivesmejia.eocvsim.Build import com.github.serivesmejia.eocvsim.util.SysUtil -import io.github.deltacv.common.util.loggerForThis +import org.deltacv.common.util.loggerForThis import com.github.serivesmejia.eocvsim.workspace.util.WorkspaceTemplate import net.lingala.zip4j.ZipFile import java.io.File @@ -33,8 +15,6 @@ import java.io.IOException object GradleWorkspaceTemplate : WorkspaceTemplate() { - private val TAG = "GradleWorkspaceTemplate" - val logger by loggerForThis() val templateZipResource = javaClass.getResourceAsStream("/templates/gradle_workspace.zip") @@ -72,9 +52,9 @@ object GradleWorkspaceTemplate : WorkspaceTemplate() { val fileContents = SysUtil.loadFileStr(buildGradleFile) .replace("\$eocvsim_version", Build.standardVersionString) .replace("\$opencv_version", Build.opencvVersion) - .replace("\$apriltag_version", Build.apriltagPluginVersion) SysUtil.saveFileStr(buildGradleFile, fileContents) } } + diff --git a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpModePipelineHandler.kt b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpModePipelineHandler.kt index f2fdcdca..c65febb5 100644 --- a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpModePipelineHandler.kt +++ b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpModePipelineHandler.kt @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.qualcomm.robotcore.eventloop.opmode import com.github.serivesmejia.eocvsim.input.InputSource @@ -5,8 +10,8 @@ import com.github.serivesmejia.eocvsim.input.InputSourceManager import com.github.serivesmejia.eocvsim.pipeline.handler.SpecificPipelineHandler import com.github.serivesmejia.eocvsim.util.event.EventHandler import com.qualcomm.robotcore.hardware.HardwareMap -import io.github.deltacv.eocvsim.input.VisionInputSourceProvider -import io.github.deltacv.vision.external.source.ThreadVisionSourceProvider +import org.deltacv.eocvsim.input.VisionInputSourceProvider +import org.deltacv.vision.external.source.ThreadVisionSourceProvider import org.firstinspires.ftc.robotcore.external.Telemetry import org.openftc.easyopencv.OpenCvPipeline import org.openftc.easyopencv.OpenCvViewport @@ -118,4 +123,4 @@ val OpMode.autonomousAnnotation get() = this.javaClass.autonomousAnnotation /** * Extension function to get the TeleOp annotation of an OpMode */ -val OpMode.teleopAnnotation get() = this.javaClass.teleopAnnotation \ No newline at end of file +val OpMode.teleopAnnotation get() = this.javaClass.teleopAnnotation diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/VisionInputSource.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/VisionInputSource.kt deleted file mode 100644 index dd31747b..00000000 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/VisionInputSource.kt +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.deltacv.eocvsim.input - -import com.github.serivesmejia.eocvsim.input.InputSource -import com.github.serivesmejia.eocvsim.input.source.CameraSource -import io.github.deltacv.common.util.loggerForThis -import io.github.deltacv.eocvsim.input.control.CameraSourceControlMap -import io.github.deltacv.vision.external.source.VisionSourceBase -import io.github.deltacv.vision.external.util.ThrowableHandler -import io.github.deltacv.vision.external.util.Timestamped -import org.opencv.core.Mat -import org.opencv.core.Size - -class VisionInputSource( - private val inputSource: InputSource, - throwableHandler: ThrowableHandler? = null -) : VisionSourceBase(throwableHandler) { - - val logger by loggerForThis() - - override fun init(): Int { - return 0 - } - - override fun getControlMap() = if(inputSource is CameraSource) { - CameraSourceControlMap(inputSource) - } else throw IllegalStateException("Controls are not available for source ${inputSource.name}") - - override fun close(): Boolean { - inputSource.close() - inputSource.reset() - return true - } - - override fun startSource(size: Size?): Boolean { - inputSource.setSize(size) - inputSource.init() - return true - } - - override fun stopSource(): Boolean { - inputSource.close() - return true; - } - - private val emptyMat = Mat() - - override fun pullFrame(): Timestamped { - return try { - val frame = inputSource.update(); - Timestamped(frame, inputSource.captureTimeNanos) - } catch(e: Exception) { - Timestamped(emptyMat, 0) - } - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/VisionInputSourceProvider.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/VisionInputSourceProvider.kt deleted file mode 100644 index 95dafc14..00000000 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/VisionInputSourceProvider.kt +++ /dev/null @@ -1,75 +0,0 @@ -package io.github.deltacv.eocvsim.input - -import com.github.serivesmejia.eocvsim.input.InputSourceManager -import com.github.serivesmejia.eocvsim.input.source.CameraSource -import com.github.serivesmejia.eocvsim.input.source.ImageSource -import com.github.serivesmejia.eocvsim.input.source.VideoSource -import io.github.deltacv.vision.external.source.ViewportVisionSourceProvider -import io.github.deltacv.vision.external.source.VisionSource -import io.github.deltacv.vision.internal.opmode.OpModeNotifier -import io.github.deltacv.vision.internal.opmode.OpModeState -import io.github.deltacv.vision.internal.opmode.RedirectToOpModeThrowableHandler -import org.opencv.core.Mat -import org.opencv.core.Size -import org.opencv.videoio.VideoCapture -import org.openftc.easyopencv.OpenCvViewport -import java.io.File -import java.io.IOException -import javax.imageio.ImageIO - -class VisionInputSourceProvider( - val notifier: OpModeNotifier, - val viewport: OpenCvViewport, - val inputSourceManager: InputSourceManager -) : ViewportVisionSourceProvider { - - private fun isImage(path: String) = try { - ImageIO.read(File(path)) != null - } catch(ex: IOException) { false } - - private fun isVideo(path: String): Boolean { - val capture = VideoCapture(path) - val mat = Mat() - - capture.read(mat) - - val isVideo = !mat.empty() - - capture.release() - - return isVideo - } - - override fun get(name: String): VisionSource { - val source = VisionInputSource(if(File(name).exists()) { - if(isImage(name)) { - ImageSource(name) - } else if(isVideo(name)) { - VideoSource(name, null) - } else throw IllegalArgumentException("File $name is neither an image nor a video") - } else { - val index = name.toIntOrNull() - ?: if(name == "default" || name == "Webcam 1") 0 - else null - - if(index == null) { - inputSourceManager.sources.get(name) ?: throw IllegalArgumentException("Input source $name not found") - } else CameraSource(index, Size(640.0, 480.0)) - }, RedirectToOpModeThrowableHandler(notifier)) - - notifier.onStateChange { - when(notifier.state) { - OpModeState.STOPPED -> { - source.stop() - removeListener() - } - else -> {} - } - } - - return source - } - - override fun viewport() = viewport - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/control/CameraSourceExposureControl.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/control/CameraSourceExposureControl.kt deleted file mode 100644 index 02a2dd0c..00000000 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/control/CameraSourceExposureControl.kt +++ /dev/null @@ -1,66 +0,0 @@ -package io.github.deltacv.eocvsim.input.control - -import com.github.serivesmejia.eocvsim.input.source.CameraSource -import io.github.deltacv.steve.WebcamProperty -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.ExposureControl -import org.firstinspires.ftc.robotcore.internal.collections.MutableReference -import java.util.concurrent.TimeUnit - -class CameraSourceExposureControl( - cameraSource: CameraSource -) : ExposureControl { - - val control = cameraSource.webcamPropertyControl - - override fun getMode() = if(control.getPropertyAuto(WebcamProperty.EXPOSURE)) { - ExposureControl.Mode.Auto - } else ExposureControl.Mode.Manual - - override fun setMode(mode: ExposureControl.Mode): Boolean { - control.setPropertyAuto(WebcamProperty.EXPOSURE, mode == ExposureControl.Mode.Auto) - return control.getPropertyAuto(WebcamProperty.EXPOSURE) - } - - override fun isModeSupported(mode: ExposureControl.Mode) = - mode == ExposureControl.Mode.Auto || mode == ExposureControl.Mode.Manual - - override fun getMinExposure(resultUnit: TimeUnit): Long { - val bounds = control.getPropertyBounds(WebcamProperty.EXPOSURE) - return TimeUnit.SECONDS.convert(bounds.min.toLong(), resultUnit) - } - - override fun getMaxExposure(resultUnit: TimeUnit): Long { - val bounds = control.getPropertyBounds(WebcamProperty.EXPOSURE) - return TimeUnit.SECONDS.convert(bounds.max.toLong(), resultUnit) - } - - override fun getExposure(resultUnit: TimeUnit): Long { - return TimeUnit.SECONDS.convert(control.getProperty(WebcamProperty.EXPOSURE).toLong(), resultUnit) - } - - @Deprecated("") - override fun getCachedExposure( - resultUnit: TimeUnit, - refreshed: MutableReference, - permittedStaleness: Long, - permittedStalenessUnit: TimeUnit - ) = getExposure(resultUnit) - - override fun setExposure(duration: Long, durationUnit: TimeUnit): Boolean { - val seconds = TimeUnit.SECONDS.convert(duration, durationUnit).toInt() - - control.setProperty(WebcamProperty.EXPOSURE, seconds) - return control.getProperty(WebcamProperty.EXPOSURE) == seconds - } - - override fun isExposureSupported() = control.isPropertySupported(WebcamProperty.EXPOSURE) - - override fun getAePriority(): Boolean { - throw UnsupportedOperationException("AE priority is not supported by EOCV-Sim") - } - - override fun setAePriority(priority: Boolean): Boolean { - throw UnsupportedOperationException("AE priority is not supported by EOCV-Sim") - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/sandbox/restrictions/MethodCallByteCodeChecker.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/sandbox/restrictions/MethodCallByteCodeChecker.kt deleted file mode 100644 index 22c881bd..00000000 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/sandbox/restrictions/MethodCallByteCodeChecker.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.sandbox.restrictions - -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassVisitor -import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Opcodes - -class MethodCallByteCodeChecker( - bytecode: ByteArray, - val methodBlacklist: Set -) { - - init { - // Create a ClassReader to read the bytecode - val classReader = ClassReader(bytecode) - // Accept the ClassVisitor to visit the class - classReader.accept(MethodCheckClassVisitor(), 0) - } - - private inner class MethodCheckClassVisitor : ClassVisitor(Opcodes.ASM9) { - lateinit var currentClassName: String - - override fun visit( - version: Int, - access: Int, - name: String?, - signature: String?, - superName: String?, - interfaces: Array? - ) { - super.visit(version, access, name, signature, superName, interfaces) - currentClassName = name!!.replace("/", ".") - } - - override fun visitMethod( - access: Int, - name: String?, - descriptor: String?, - signature: String?, - exceptions: Array? - ): MethodVisitor { - val mv = super.visitMethod(access, name, descriptor, signature, exceptions) - return MethodCheckMethodVisitor(mv, currentClassName, name!!) - } - } - - private inner class MethodCheckMethodVisitor(mv: MethodVisitor?, - val currentClassName: String, val currentMethodName: String) : MethodVisitor(Opcodes.ASM9, mv) { - override fun visitMethodInsn( - opcode: Int, - owner: String?, - name: String?, - descriptor: String?, - isInterface: Boolean - ) { - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) - val methodIdentifier = "${owner!!.replace("/", ".")}#$name" - - if (methodBlacklist.contains(methodIdentifier)) { - throw IllegalAccessError("Unauthorized method call of $methodIdentifier from dynamic code at $currentClassName#$currentMethodName") - } - } - } - -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt similarity index 81% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt index ec563cb6..3b476107 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt @@ -1,27 +1,9 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.gui.dialog +package org.deltacv.eocvsim.gui.dialog import com.formdev.flatlaf.FlatLightLaf import com.formdev.flatlaf.intellijthemes.FlatArcDarkIJTheme @@ -187,4 +169,4 @@ private fun wrapText(text: String, maxLineLength: Int): String { // Join all lines with
for HTML return wrappedLines.joinToString(breakTag) -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/VisionInputSource.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/VisionInputSource.kt new file mode 100644 index 00000000..15f9117b --- /dev/null +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/VisionInputSource.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.input + +import com.github.serivesmejia.eocvsim.input.InputSource +import com.github.serivesmejia.eocvsim.input.source.CameraSource +import org.deltacv.common.util.loggerForThis +import org.deltacv.eocvsim.input.control.CameraSourceControlMap +import org.deltacv.vision.external.source.VisionSourceBase +import org.deltacv.vision.external.util.ThrowableHandler +import org.deltacv.vision.external.util.Timestamped +import org.opencv.core.Mat +import org.opencv.core.Size + +class VisionInputSource( + throwableHandler: ThrowableHandler? = null, + private val inputSourceProvider: (Size) -> InputSource, +) : VisionSourceBase(throwableHandler) { + + val logger by loggerForThis() + + private var inputSource: InputSource? = null + + override fun check(): Int { + val testSource = inputSourceProvider(Size(0.0, 0.0)) + val state = testSource.init() + testSource.close() + return if(state) 0 else 1 + } + + override fun getControlMap() = if(inputSource is CameraSource) { + CameraSourceControlMap(inputSource!! as CameraSource) + } else throw IllegalStateException("Controls are not available for source ${inputSource?.name ?: ""}") + + override fun close(): Boolean { + inputSource?.close() + return true + } + + override fun startSource(size: Size): Boolean { + inputSource = inputSourceProvider(size) + return inputSource!!.init() + } + + override fun stopSource(): Boolean { + inputSource?.close() + return true; + } + + private val emptyMat = Mat() + + override fun pullFrame(): Timestamped { + return try { + val frame = inputSource!!.update(); + Timestamped(frame, inputSource!!.captureTimeNanos) + } catch(e: Exception) { + Timestamped(emptyMat, 0) + } + } + +} diff --git a/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/VisionInputSourceProvider.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/VisionInputSourceProvider.kt new file mode 100644 index 00000000..5ef85e79 --- /dev/null +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/VisionInputSourceProvider.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.input + +import com.github.serivesmejia.eocvsim.input.InputSourceManager +import com.github.serivesmejia.eocvsim.input.source.CameraSource +import com.github.serivesmejia.eocvsim.input.source.ImageSource +import com.github.serivesmejia.eocvsim.input.source.VideoSource +import org.deltacv.vision.external.source.ViewportVisionSourceProvider +import org.deltacv.vision.external.source.VisionSource +import org.deltacv.vision.internal.opmode.OpModeNotifier +import org.deltacv.vision.internal.opmode.OpModeState +import org.deltacv.vision.internal.opmode.RedirectToOpModeThrowableHandler +import org.opencv.core.Mat +import org.opencv.core.Size +import org.openftc.easyopencv.OpenCvViewport +import org.wpilib.vision.camera.UsbCamera +import org.wpilib.vision.camera.VideoMode +import java.io.File +import javax.imageio.ImageIO + +class VisionInputSourceProvider( + val notifier: OpModeNotifier, + val viewport: OpenCvViewport, + val inputSourceManager: InputSourceManager +) : ViewportVisionSourceProvider { + + private fun isImage(path: String) = try { + ImageIO.read(File(path)) != null + } catch (_: Exception) { + false + } + + private fun isVideo(path: String): Boolean { + val capture = org.opencv.videoio.VideoCapture(path) + val mat = Mat() + + capture.read(mat) + val ok = !mat.empty() + + capture.release() + return ok + } + + private fun defaultMode(index: Int, size: Size): VideoMode { + val cam = UsbCamera("$index", index) + + val mode = try { + val modes = cam.enumerateVideoModes() + if (size.width > 0 && size.height > 0) { + modes.firstOrNull { it.width == size.width.toInt() && it.height == size.height.toInt() } + ?: modes.firstOrNull() + ?: cam.videoMode + } else { + cam.videoMode + } + } catch (_: Exception) { + VideoMode(0, 640, 480, 30) + } finally { + cam.close() + } + + return mode + } + + override fun get(name: String): VisionSource { + val source = VisionInputSource( + RedirectToOpModeThrowableHandler(notifier) + ) { size -> + when { + File(name).exists() -> { + when { + isImage(name) -> ImageSource(name, size) + isVideo(name) -> VideoSource(name, size) + else -> throw IllegalArgumentException( + "File $name is neither image nor video" + ) + } + } + + else -> { + val index = name.toIntOrNull() + ?: if (name == "default" || name == "Webcam 1") 0 else null + + if (index == null) { + inputSourceManager.sources[name] + ?: throw IllegalArgumentException("Input source $name not found") + } else { + CameraSource(index, defaultMode(index, size)) + } + } + } + } + + notifier.onStateChange { + when (notifier.state) { + OpModeState.STOPPED -> { + source.stop() + removeListener() + } + else -> {} + } + } + + return source + } + + override fun viewport() = viewport +} diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/control/CameraSourceControlMap.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/control/CameraSourceControlMap.kt similarity index 73% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/control/CameraSourceControlMap.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/control/CameraSourceControlMap.kt index 27148794..1b45ba62 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/input/control/CameraSourceControlMap.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/control/CameraSourceControlMap.kt @@ -1,7 +1,12 @@ -package io.github.deltacv.eocvsim.input.control +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.input.control import com.github.serivesmejia.eocvsim.input.source.CameraSource -import io.github.deltacv.vision.external.source.CameraControlMap +import org.deltacv.vision.external.source.CameraControlMap import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.CameraControl import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.ExposureControl @@ -16,4 +21,4 @@ class CameraSourceControlMap( else -> null } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/control/CameraSourceExposureControl.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/control/CameraSourceExposureControl.kt new file mode 100644 index 00000000..1a99159c --- /dev/null +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/input/control/CameraSourceExposureControl.kt @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.input.control + +import com.github.serivesmejia.eocvsim.input.source.CameraSource +import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.ExposureControl +import org.firstinspires.ftc.robotcore.internal.collections.MutableReference +import org.wpilib.vision.camera.VideoProperty +import java.util.concurrent.TimeUnit + +class CameraSourceExposureControl( + private val cameraSource: CameraSource +) : ExposureControl { + + private val camera + get() = cameraSource.camera ?: error("Camera not initialized") + + /** + * CSCore exposure property. + * + * Usually: + * - exposure_absolute (Windows / DirectShow) + * - exposure (Linux / V4L) + * + * Try absolute first. + */ + private val exposureProperty: VideoProperty + get() { + val abs = camera.getProperty("exposure_absolute") + + return if (abs.kind != VideoProperty.Kind.kNone) { + abs + } else { + camera.getProperty("exposure") + } + } + + override fun getMode(): ExposureControl.Mode { + val prop = camera.getProperty("exposure_auto") + + if (prop.kind == VideoProperty.Kind.kNone) { + return ExposureControl.Mode.Unknown + } + + return if (prop.get() != 0) { + ExposureControl.Mode.Auto + } else { + ExposureControl.Mode.Manual + } + } + + override fun setMode(mode: ExposureControl.Mode): Boolean { + when (mode) { + ExposureControl.Mode.Auto -> { + camera.setExposureAuto() + } + + ExposureControl.Mode.Manual -> { + // Keep current exposure value when switching to manual + val current = exposureProperty.get() + camera.setExposureManual(current) + } + + else -> return false + } + + return getMode() == mode + } + + override fun isModeSupported(mode: ExposureControl.Mode): Boolean { + return mode == ExposureControl.Mode.Auto || + mode == ExposureControl.Mode.Manual + } + + override fun getMinExposure(resultUnit: TimeUnit): Long { + return convertExposure( + exposureProperty.min.toLong(), + resultUnit + ) + } + + override fun getMaxExposure(resultUnit: TimeUnit): Long { + return convertExposure( + exposureProperty.max.toLong(), + resultUnit + ) + } + + override fun getExposure(resultUnit: TimeUnit): Long { + return convertExposure( + exposureProperty.get().toLong(), + resultUnit + ) + } + + @Deprecated("") + override fun getCachedExposure( + resultUnit: TimeUnit, + refreshed: MutableReference, + permittedStaleness: Long, + permittedStalenessUnit: TimeUnit + ): Long { + refreshed.value = true + return getExposure(resultUnit) + } + + override fun setExposure( + duration: Long, + durationUnit: TimeUnit + ): Boolean { + + val value = convertFromTimeUnit(duration, durationUnit) + + camera.setExposureManual(value.toInt()) + + return exposureProperty.get() == value.toInt() + } + + override fun isExposureSupported(): Boolean { + return exposureProperty.kind != VideoProperty.Kind.kNone + } + + override fun getAePriority(): Boolean { + throw UnsupportedOperationException( + "AE priority is not supported by CSCore" + ) + } + + override fun setAePriority(priority: Boolean): Boolean { + throw UnsupportedOperationException( + "AE priority is not supported by CSCore" + ) + } + + /** + * CSCore exposure values are typically milliseconds-ish integers, + * not true nanosecond durations. + * + * FTC ExposureControl expects time units. + * + * You may need platform-specific scaling here. + */ + private fun convertExposure( + value: Long, + resultUnit: TimeUnit + ): Long { + return resultUnit.convert(value, TimeUnit.MILLISECONDS) + } + + private fun convertFromTimeUnit( + duration: Long, + unit: TimeUnit + ): Long { + return TimeUnit.MILLISECONDS.convert(duration, unit) + } +} diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/pipeline/StreamableOpenCvPipelineInstantiator.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/pipeline/StreamableOpenCvPipelineInstantiator.kt similarity index 77% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/pipeline/StreamableOpenCvPipelineInstantiator.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/pipeline/StreamableOpenCvPipelineInstantiator.kt index e5cffaa5..43cdbdfd 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/pipeline/StreamableOpenCvPipelineInstantiator.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/pipeline/StreamableOpenCvPipelineInstantiator.kt @@ -1,9 +1,14 @@ -package io.github.deltacv.eocvsim.pipeline +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.pipeline import com.github.serivesmejia.eocvsim.pipeline.instantiator.DefaultPipelineInstantiator import com.github.serivesmejia.eocvsim.pipeline.instantiator.PipelineInstantiator -import io.github.deltacv.eocvsim.stream.ImageStreamer -import io.github.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection +import org.deltacv.eocvsim.stream.ImageStreamer +import org.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection import org.firstinspires.ftc.robotcore.external.Telemetry import org.openftc.easyopencv.OpenCvPipeline @@ -22,4 +27,4 @@ class StreamableOpenCvPipelineInstantiator( override fun variableTunerTarget(pipeline: OpenCvPipeline) = pipeline -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/EmbeddedPluginLoader.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/EmbeddedPluginLoader.kt similarity index 72% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/EmbeddedPluginLoader.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/EmbeddedPluginLoader.kt index 313eae69..caaccf59 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/EmbeddedPluginLoader.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/EmbeddedPluginLoader.kt @@ -1,35 +1,17 @@ /* * Copyright (c) 2026 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.loader +package org.deltacv.eocvsim.plugin.loader import com.github.serivesmejia.eocvsim.config.ConfigLoader import com.github.serivesmejia.eocvsim.util.extension.plus -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.ApiDisabler -import io.github.deltacv.eocvsim.plugin.api.EOCVSimApi -import io.github.deltacv.eocvsim.plugin.security.PluginSignature -import io.github.deltacv.eocvsim.sandbox.nio.SandboxFileSystem +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.ApiDisabler +import org.deltacv.eocvsim.plugin.api.EOCVSimApi +import org.deltacv.eocvsim.plugin.security.PluginSignature +import org.deltacv.eocvsim.sandbox.nio.SandboxFileSystem import net.lingala.zip4j.ZipFile import java.io.File import java.nio.file.Path @@ -165,3 +147,4 @@ class EmbeddedPluginLoader( return hasSuperAccess } } + diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/FilePluginLoaderImpl.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/FilePluginLoaderImpl.kt similarity index 71% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/FilePluginLoaderImpl.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/FilePluginLoaderImpl.kt index 56ebad12..b674186e 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/FilePluginLoaderImpl.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/FilePluginLoaderImpl.kt @@ -1,45 +1,26 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.loader +package org.deltacv.eocvsim.plugin.loader import com.github.serivesmejia.eocvsim.EOCVSim import com.github.serivesmejia.eocvsim.config.ConfigLoader -import com.github.serivesmejia.eocvsim.gui.dialog.AppendDelegate -import com.github.serivesmejia.eocvsim.gui.dialog.PluginOutput +import com.github.serivesmejia.eocvsim.plugin.output.PluginOutputHandler import com.github.serivesmejia.eocvsim.util.SysUtil import com.github.serivesmejia.eocvsim.util.event.EventHandler import com.github.serivesmejia.eocvsim.util.extension.hashString import com.github.serivesmejia.eocvsim.util.extension.plus -import io.github.deltacv.common.util.loggerForThis -import com.moandjiezana.toml.Toml -import io.github.deltacv.common.util.ParsedVersion -import io.github.deltacv.eocvsim.plugin.EMBEDDED_PLUGIN_FOLDER -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.api.ApiDisabler -import io.github.deltacv.eocvsim.plugin.api.EOCVSimApi -import io.github.deltacv.eocvsim.plugin.security.PluginSignatureVerifier -import io.github.deltacv.eocvsim.sandbox.nio.SandboxFileSystem +import org.deltacv.common.util.loggerForThis +import org.deltacv.common.util.serialization.Toml +import org.deltacv.common.util.ParsedVersion +import org.deltacv.eocvsim.plugin.EMBEDDED_PLUGIN_FOLDER +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.api.ApiDisabler +import org.deltacv.eocvsim.plugin.api.EOCVSimApi +import org.deltacv.eocvsim.plugin.security.PluginSignatureVerifier +import org.deltacv.eocvsim.sandbox.nio.SandboxFileSystem import net.lingala.zip4j.ZipFile import java.io.File import java.nio.file.Path @@ -50,14 +31,14 @@ import java.nio.file.Path * @param classpath the classpath of the plugin * @param pluginSource the source of the plugin (file or repository) * @param pluginManager the plugin manager - * @param appender the appender to use for logging + * @param outputHandler the output handler for plugin logging */ open class FilePluginLoaderImpl( override val pluginFile: File, override val classpath: List, override val pluginSource: PluginSource, val pluginManager: PluginManager, - val appender: AppendDelegate + val outputHandler: PluginOutputHandler ) : FilePluginLoader() { val logger by loggerForThis() @@ -111,6 +92,7 @@ open class FilePluginLoaderImpl( /** * Fetch the plugin info from the plugin.toml file + * * Fills the pluginName, pluginVersion, pluginAuthor and pluginAuthorEmail fields */ fun fetchInfoFromToml() { @@ -120,7 +102,25 @@ open class FilePluginLoaderImpl( ?: throw InvalidPluginException("No plugin.toml in the jar file") ) - pluginInfo = PluginInfo.fromToml(pluginToml) + // Parse plugin info directly to avoid cross-module Toml type mismatch + val name = pluginToml.getString("name")?.trim() + ?: throw InvalidPluginException("No name in plugin.toml") + + val version = pluginToml.getString("version")?.trim() + ?: throw InvalidPluginException("No version in plugin.toml") + + val author = pluginToml.getString("author")?.trim() + ?: throw InvalidPluginException("No author in plugin.toml") + + val authorEmail = pluginToml.getString("author-email")?.trim() ?: "" + + val main = pluginToml.getString("main")?.trim() + ?: throw InvalidPluginException("No main in plugin.toml") + + val description = pluginToml.getString("description")?.trim() ?: "" + val superAccess = pluginToml.getBoolean("super-access") ?: false + + pluginInfo = PluginInfo(name, version, author, authorEmail, main, description, superAccess) } /** @@ -134,19 +134,19 @@ open class FilePluginLoaderImpl( fetchInfoFromToml() if(!shouldEnable) { - appender.appendln("${PluginOutput.SPECIAL_SILENT}Plugin ${pluginInfo.name} v${pluginInfo.version} is disabled") + outputHandler.sendOutputLine("Plugin ${pluginInfo.nameWithVersion} is disabled") return } - appender.appendln("${PluginOutput.SPECIAL_SILENT}Loading plugin ${pluginInfo.name} v${pluginInfo.version} by ${pluginInfo.version} from ${pluginSource.name}") + outputHandler.sendOutputLine("Loading plugin ${pluginInfo.nameWithVersionAndAuthor} from ${pluginSource.name}") signature setupFs() - if(pluginToml.contains("api-version") || pluginToml.contains("min-api-version")) { + if(pluginToml.getString("api-version") != null || pluginToml.getString("min-api-version") != null) { // default to api-version if min-api-version is not present - val apiVersionKey = if(pluginToml.contains("api-version")) "api-version" else "min-api-version" + val apiVersionKey = if(pluginToml.getString("api-version") != null) "api-version" else "min-api-version" val parsedVersion = ParsedVersion(pluginToml.getString(apiVersionKey)) if(parsedVersion > EOCVSim.PARSED_VERSION) @@ -155,7 +155,7 @@ open class FilePluginLoaderImpl( logger.info("Plugin ${pluginInfo.name} requests min api version of v${parsedVersion}") } - if(pluginToml.contains("max-api-version")) { + if(pluginToml.getString("max-api-version") != null) { val parsedVersion = ParsedVersion(pluginToml.getString("max-api-version")) if(parsedVersion < EOCVSim.PARSED_VERSION) @@ -164,7 +164,7 @@ open class FilePluginLoaderImpl( logger.info("Plugin ${pluginInfo.name} requests max api version of v${parsedVersion}") } - if(pluginToml.contains("exact-api-version")) { + if(pluginToml.getString("exact-api-version") != null) { val parsedVersion = ParsedVersion(pluginToml.getString("exact-api-version")) if(parsedVersion != EOCVSim.PARSED_VERSION) @@ -173,7 +173,7 @@ open class FilePluginLoaderImpl( logger.info("Plugin ${pluginInfo.name} requests exact api version of v${parsedVersion}") } - if(pluginToml.getBoolean("super-access", false)) { + if(pluginToml.getBoolean("super-access") != null && pluginToml.getBoolean("super-access")) { requestSuperAccess(pluginToml.getString("super-access-reason", "")) } @@ -214,7 +214,7 @@ open class FilePluginLoaderImpl( if(!shouldEnable) return - appender.appendln("${PluginOutput.SPECIAL_SILENT}Enabling plugin ${pluginInfo.name} v${pluginInfo.version}") + outputHandler.sendOutputLine("Enabling plugin ${pluginInfo.nameWithVersionAndAuthor}") plugin.onEnable() @@ -227,7 +227,7 @@ open class FilePluginLoaderImpl( override fun disable() { if(!enabled || !loaded) return - appender.appendln("${PluginOutput.SPECIAL_SILENT}Disabling plugin ${pluginInfo.name} v${pluginInfo.version}") + outputHandler.sendOutputLine("Disabling plugin ${pluginInfo.name} v${pluginInfo.version}") plugin.onDisable() @@ -262,7 +262,7 @@ class EmbeddedFilePluginLoader( resourcePath: String, classpath: List, pluginManager: PluginManager, - appender: AppendDelegate + outputHandler: PluginOutputHandler ) : FilePluginLoaderImpl( pluginFile = resourcePath.let { // extract to EMBEDDED_PLUGIN_FOLDER @@ -286,11 +286,11 @@ class EmbeddedFilePluginLoader( }, pluginSource = PluginSource.FILE, pluginManager = pluginManager, - appender = appender + outputHandler = outputHandler ) { override val hasSuperAccess = true // Embedded plugins always have super access init { fetchInfoFromToml() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginClassLoader.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginClassLoader.kt similarity index 81% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginClassLoader.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginClassLoader.kt index a0b8e482..f6f9247d 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginClassLoader.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginClassLoader.kt @@ -1,41 +1,24 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.loader +package org.deltacv.eocvsim.plugin.loader import com.github.serivesmejia.eocvsim.util.SysUtil import com.github.serivesmejia.eocvsim.util.extension.removeFromEnd -import io.github.deltacv.eocvsim.sandbox.restrictions.MethodCallByteCodeChecker -import io.github.deltacv.eocvsim.sandbox.restrictions.dynamicCodeExactMatchBlacklist -import io.github.deltacv.eocvsim.sandbox.restrictions.dynamicCodeMethodBlacklist -import io.github.deltacv.eocvsim.sandbox.restrictions.dynamicCodePackageAlwaysBlacklist -import io.github.deltacv.eocvsim.sandbox.restrictions.dynamicCodePackageBlacklist -import io.github.deltacv.eocvsim.sandbox.restrictions.dynamicCodePackageWhitelist +import org.deltacv.eocvsim.sandbox.restrictions.MethodCallByteCodeChecker +import org.deltacv.eocvsim.sandbox.restrictions.dynamicCodeExactMatchBlacklist +import org.deltacv.eocvsim.sandbox.restrictions.dynamicCodeMethodBlacklist +import org.deltacv.eocvsim.sandbox.restrictions.dynamicCodePackageAlwaysBlacklist +import org.deltacv.eocvsim.sandbox.restrictions.dynamicCodePackageBlacklist +import org.deltacv.eocvsim.sandbox.restrictions.dynamicCodePackageWhitelist import java.io.ByteArrayOutputStream import java.lang.ref.WeakReference import java.io.File import java.io.IOException import java.io.InputStream +import java.net.URI import java.net.URL import java.util.zip.ZipEntry import java.util.zip.ZipFile @@ -181,7 +164,7 @@ class PluginClassLoader( if (entry != null) { // Construct a URL for the resource inside the plugin JAR - return URL("jar:file:${pluginJar.absolutePath}!/$name") + return URI.create("jar:file:${pluginJar.absolutePath}!/$name").toURL() } else { resourceFromClasspath(name)?.let { return it } } @@ -223,7 +206,7 @@ class PluginClassLoader( } try { - return URL("jar:file:${file.absolutePath}!/$name") + return URI("jar:file:${file.absolutePath}!/$name").toURL() } catch (_: Exception) { } } catch (_: Exception) { @@ -321,4 +304,4 @@ class PluginClassLoader( override fun toString() = "PluginClassLoader@\"${pluginJar.name}\"" override val pluginContext by lazy { PluginContext(pluginLoader) } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginManager.kt similarity index 55% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginManager.kt index 16da71c7..09fab0d6 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/loader/PluginManager.kt @@ -1,56 +1,50 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.loader +package org.deltacv.eocvsim.plugin.loader import com.github.serivesmejia.eocvsim.Build -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.DialogFactory -import com.github.serivesmejia.eocvsim.gui.dialog.PluginOutput -import com.github.serivesmejia.eocvsim.gui.dialog.PluginOutput.Companion.trimSpecials +import com.github.serivesmejia.eocvsim.LifecycleSignal +import com.github.serivesmejia.eocvsim.config.ConfigManager +import com.github.serivesmejia.eocvsim.gui.Visualizer import com.github.serivesmejia.eocvsim.plugin.api.impl.EOCVSimApiImpl -import io.github.deltacv.common.util.loggerForThis -import io.github.deltacv.common.util.loggerOf -import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin -import io.github.deltacv.eocvsim.plugin.repository.PluginRepositoryManager -import io.github.deltacv.eocvsim.plugin.security.superaccess.SuperAccessDaemon -import io.github.deltacv.eocvsim.plugin.security.superaccess.SuperAccessDaemonClient -import io.github.deltacv.eocvsim.plugin.security.toMutable +import com.github.serivesmejia.eocvsim.plugin.output.PluginDialogSignal +import com.github.serivesmejia.eocvsim.plugin.output.PluginOutputHandler +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.orchestration.initDependency +import com.github.serivesmejia.eocvsim.util.orchestration.PhaseOrchestrableBase +import org.deltacv.common.util.loggerForThis +import org.deltacv.eocvsim.plugin.EOCVSimPlugin +import org.deltacv.eocvsim.plugin.repository.PluginRepositoryManager +import org.deltacv.eocvsim.plugin.security.superaccess.SuperAccessDaemon +import org.deltacv.eocvsim.plugin.security.superaccess.SuperAccessDaemonClient +import org.deltacv.eocvsim.plugin.security.toMutable +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.runBlocking +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named import java.io.File -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock import kotlin.properties.Delegates /** * Manages the loading, enabling and disabling of plugins - * @param eocvSim the EOCV-Sim instance */ -class PluginManager(val eocvSim: EOCVSim) { +class PluginManager : PhaseOrchestrableBase(), KoinComponent { + + private val configManager: ConfigManager by initDependency(inject()) + private val visualizer: Visualizer by inject() + private val outputHandler: PluginOutputHandler by inject() + + private val onMainUpdate: EventHandler by inject(named("onMainLoop")) + private val lifecycleChannel: Channel by inject(named("lifecycle")) companion object { - val PLUGIN_FOLDER = io.github.deltacv.eocvsim.plugin.PLUGIN_FOLDER - val PLUGIN_CACHING_FOLDER = io.github.deltacv.eocvsim.plugin.PLUGIN_CACHING_FOLDER - val FILESYSTEMS_FOLDER = io.github.deltacv.eocvsim.plugin.FILESYSTEMS_FOLDER + val PLUGIN_FOLDER = org.deltacv.eocvsim.plugin.PLUGIN_FOLDER + val PLUGIN_CACHING_FOLDER = org.deltacv.eocvsim.plugin.PLUGIN_CACHING_FOLDER + val FILESYSTEMS_FOLDER = org.deltacv.eocvsim.plugin.FILESYSTEMS_FOLDER const val GENERIC_SUPERACCESS_WARN = "Plugins run in a restricted environment by default. SuperAccess will grant full system access. Ensure you trust the authors before accepting." @@ -62,39 +56,14 @@ class PluginManager(val eocvSim: EOCVSim) { val superAccessDaemonClient by lazy { SuperAccessDaemonClient( - autoacceptOnTrusted = eocvSim.config.autoAcceptSuperAccessOnTrusted + autoacceptOnTrusted = configManager.config.autoAcceptSuperAccessOnTrusted ) } private val loadedPluginHashes = mutableListOf() - private val haltLock = ReentrantLock() - private val haltCondition = haltLock.newCondition() - - val appender by lazy { - val appender = DialogFactory.createMavenOutput(this) { - haltLock.withLock { - haltCondition.signalAll() - } - } - - val logger by loggerOf("PluginOutput") - - appender.subscribe { - if (!it.isBlank()) { - val message = it.trimSpecials() - - if (message.isNotBlank()) { - logger.info(message) - } - } - } - - appender!! - } - val repositoryManager by lazy { - PluginRepositoryManager(appender, eocvSim, haltLock, haltCondition) + PluginRepositoryManager(outputHandler, onMainUpdate) { lifecycleChannel.trySend(LifecycleSignal.Restart) } } private val _pluginFiles = mutableListOf() @@ -113,7 +82,7 @@ class PluginManager(val eocvSim: EOCVSim) { /** * Provides EOCV-Sim API instances for plugins */ - val eocvSimApiProvider = EOCVSimApiProvider { plugin -> EOCVSimApiImpl(plugin, eocvSim) } + val eocvSimApiProvider = EOCVSimApiProvider { plugin -> EOCVSimApiImpl(plugin) } /** * Initializes the plugin manager @@ -122,18 +91,16 @@ class PluginManager(val eocvSim: EOCVSim) { * and stores them in the loaders map * @see PluginLoader */ - fun init() { - eocvSim.visualizer.onInitFinished { - appender.append(PluginOutput.SPECIAL_FREE) + override suspend fun init() { + visualizer.onInitFinished { + outputHandler.sendDialogSignal(PluginDialogSignal.Hide) } - appender.appendln(PluginOutput.SPECIAL_SILENT + "Initializing PluginManager") - - superAccessDaemonClient.init() + outputHandler.sendOutputLine("Initializing PluginManager") // replace papervision line - if (!eocvSim.config.flags.getOrDefault("hasDiscardedPaperVisionRepository", false)) { + if (!configManager.config.flags.getOrDefault("hasDiscardedPaperVisionRepository", false)) { try { val repositoriesStr = PluginRepositoryManager.REPOSITORY_FILE.readText() for (line in repositoriesStr.lines()) { @@ -154,7 +121,7 @@ class PluginManager(val eocvSim: EOCVSim) { } catch (_: Exception) { } - eocvSim.config.flags["hasDiscardedPaperVisionRepository"] = true + configManager.config.flags["hasDiscardedPaperVisionRepository"] = true } repositoryManager.init() @@ -165,21 +132,21 @@ class PluginManager(val eocvSim: EOCVSim) { _pluginFiles.addAll(repositoryManager.resolveAll()) - if (eocvSim.config.flags.getOrDefault("startFresh", false)) { - logger.warn("startFresh = true, deleting all plugins in the plugins folder") + if (configManager.config.flags.getOrDefault("startFreshPlugins", false)) { + logger.warn("startFreshPlugins = true, deleting all plugins in the plugins folder") for (file in pluginFilesInFolder) { file.delete() } - eocvSim.config.flags["startFresh"] = false - eocvSim.configManager.saveToFile() + configManager.config.flags["startFreshPlugins"] = false + configManager.saveToFile() } else { _pluginFiles.addAll(pluginFilesInFolder) } if (pluginFiles.isEmpty()) { - appender.appendln(PluginOutput.SPECIAL_SILENT + "No plugin files to load") + outputHandler.sendOutputLine("No plugin files to load") } for (pluginFile in pluginFiles) { @@ -190,13 +157,13 @@ class PluginManager(val eocvSim: EOCVSim) { if (pluginFile in repositoryManager.resolvedFiles) PluginSource.REPOSITORY else PluginSource.FILE, this, - appender + outputHandler ) _loaders.add(loader) loader.fetchInfoFromToml() } catch (e: Throwable) { - appender.appendln("Failure creating PluginLoader for ${pluginFile.name}: ${e.message}") + outputHandler.sendOutputLine("Failure creating PluginLoader for ${pluginFile.name}: ${e.message}") logger.error("Failure creating PluginLoader for ${pluginFile.name}", e) } } @@ -211,7 +178,7 @@ class PluginManager(val eocvSim: EOCVSim) { "/embedded_plugins/PaperVisionPlugin.jar", listOf(), this, - appender + outputHandler ) ) @@ -223,7 +190,7 @@ class PluginManager(val eocvSim: EOCVSim) { Build.paperVisionVersion, "deltacv", "dev@deltacv.org", - "io.github.deltacv.papervision.plugin.PaperVisionEOCVSimPlugin", + "org.deltacv.papervision.plugin.PaperVisionEOCVSimPlugin", "Create your custom OpenCV algorithms using a user-friendly node editor interface", true ) @@ -231,7 +198,7 @@ class PluginManager(val eocvSim: EOCVSim) { @Suppress("UNCHECKED_CAST") addEmbeddedPlugin( pluginInfo, - Class.forName("io.github.deltacv.papervision.plugin.PaperVisionEOCVSimPlugin") as Class, + Class.forName("org.deltacv.papervision.plugin.PaperVisionEOCVSimPlugin") as Class, ) logger.info("Loaded embedded PaperVision from built-in class") @@ -240,8 +207,16 @@ class PluginManager(val eocvSim: EOCVSim) { } } } else { - appender.appendln(PluginOutput.SPECIAL_SILENT + "PaperVision plugin is already loaded, skipping embedded plugin.") + outputHandler.sendOutputLine("PaperVision plugin is already loaded, skipping embedded plugin.") } + + loadPlugins() + } + + override suspend fun run() { } + + override suspend fun destroy() { + disablePlugins() } private fun addEmbeddedPlugin( @@ -250,7 +225,7 @@ class PluginManager(val eocvSim: EOCVSim) { ) { val tempLoader = EmbeddedPluginLoader(pluginInfo, pluginClass, eocvSimApiProvider) - logger.info("Adding embedded plugin: ${pluginInfo.name} v${pluginInfo.version} by ${pluginInfo.author}") + logger.info("Adding embedded plugin: ${pluginInfo.nameWithVersionAndAuthor}") _loaders.add(tempLoader) } @@ -258,9 +233,8 @@ class PluginManager(val eocvSim: EOCVSim) { * Loads all plugins * @see PluginLoader.load */ - fun loadPlugins() { + private fun loadPlugins() { for (loader in _loaders.toTypedArray()) { - try { val hash = loader.hash() @@ -271,16 +245,25 @@ class PluginManager(val eocvSim: EOCVSim) { PluginSource.EMBEDDED -> "embedded plugin" } - appender.appendln("Plugin ${loader.pluginInfo.name} by ${loader.pluginInfo.author} is already loaded. Please delete the duplicate from the $source !") + outputHandler.sendDialogSignal(PluginDialogSignal.ShowOutput) + outputHandler.sendOutputLine("Plugin ${loader.pluginInfo.nameWithVersion} is already loaded. Please delete the duplicate from the $source !") return } loader.load() loadedPluginHashes.add(hash) } catch (e: Throwable) { - appender.appendln("Failure loading ${loader.pluginInfo.name} v${loader.pluginInfo.version}:") - appender.appendln(e.message ?: "Unknown error") - logger.error("Failure loading ${loader.pluginInfo.name} v${loader.pluginInfo.version}", e) + outputHandler.sendDialogSignal(PluginDialogSignal.ShowOutput) + outputHandler.sendOutputLine("-- Failure loading ${loader.pluginInfo.nameWithVersion} --") + outputHandler.sendOutputLine("'${e.toString()}'") + + logger.error("Failure loading ${loader.pluginInfo.nameWithVersion}", e) + + outputHandler.sendDialogSignal(PluginDialogSignal.EnableContinue) + runBlocking { + outputHandler.waitForContinuation(15000L) + } + outputHandler.sendDialogSignal(PluginDialogSignal.DisableContinue) _loaders.remove(loader) loader.kill() @@ -297,8 +280,8 @@ class PluginManager(val eocvSim: EOCVSim) { try { loader.enable() } catch (e: Throwable) { - appender.appendln("Failure enabling ${loader.pluginInfo.name} v${loader.pluginInfo.version}: ${e.message}") - logger.error("Failure enabling ${loader.pluginInfo.name} v${loader.pluginInfo.version}", e) + outputHandler.sendOutputLine("Failure enabling ${loader.pluginInfo.nameWithVersion}: ${e.message}") + logger.error("Failure enabling ${loader.pluginInfo.nameWithVersion}", e) loader.kill() } } @@ -316,8 +299,8 @@ class PluginManager(val eocvSim: EOCVSim) { try { loader.disable() } catch (e: Throwable) { - appender.appendln("Failure disabling ${loader.pluginInfo.name} v${loader.pluginInfo.version}: ${e.message}") - logger.error("Failure disabling ${loader.pluginInfo.name} v${loader.pluginInfo.version}", e) + outputHandler.sendOutputLine("Failure disabling ${loader.pluginInfo.nameWithVersion}: ${e.message}") + logger.error("Failure disabling ${loader.pluginInfo.nameWithVersion}", e) loader.kill() } } @@ -334,13 +317,13 @@ class PluginManager(val eocvSim: EOCVSim) { */ fun requestSuperAccessFor(loader: PluginLoader, reason: String): Boolean { if (loader.hasSuperAccess) { - appender.appendln(PluginOutput.SPECIAL_SILENT + "Plugin ${loader.pluginInfo.name} v${loader.pluginInfo.version} already has super access") + outputHandler.sendOutputLine("Plugin ${loader.pluginInfo.name} v${loader.pluginInfo.version} already has super access") return true } val signature = loader.signature - appender.appendln(PluginOutput.SPECIAL_SILENT + "Requesting super access for ${loader.pluginInfo.name} v${loader.pluginInfo.version}") + outputHandler.sendOutputLine("Requesting super access for ${loader.pluginInfo.name} v${loader.pluginInfo.version}") if (loader is FilePluginLoaderImpl) { var access = false @@ -352,34 +335,29 @@ class PluginManager(val eocvSim: EOCVSim) { reason ) ) { - if (it) { - access = true - } - - haltLock.withLock { - haltCondition.signalAll() - } + access = it + outputHandler.signalContinuation() } - haltLock.withLock { - haltCondition.await() + runBlocking { + outputHandler.waitForContinuation(0L) // Wait indefinitely. } - appender.appendln(PluginOutput.SPECIAL_SILENT + "Super access for ${loader.pluginInfo.nameWithVersion} was ${if (access) "granted" else "denied"}") + outputHandler.sendOutputLine("Super access for ${loader.pluginInfo.nameWithVersion} was ${if (access) "granted" else "denied"}") return access } else { - appender.appendln(PluginOutput.SPECIAL_SILENT + "Super access for ${loader.pluginInfo.nameWithVersion} is automatically determined, it was ${if (loader.hasSuperAccess) "granted" else "denied"}") + outputHandler.sendOutputLine("Super access for ${loader.pluginInfo.nameWithVersion} is automatically determined, it was ${if (loader.hasSuperAccess) "granted" else "denied"}") return loader.hasSuperAccess } } fun hasSuperAccess(pluginFile: File) = superAccessDaemonClient.checkAccess(pluginFile) - fun isPluginEnabledInConfig(loader: PluginLoader) = eocvSim.config.flags.getOrDefault(loader.hash(), true)!! + fun isPluginEnabledInConfig(loader: PluginLoader) = configManager.config.flags.getOrDefault(loader.hash(), true)!! fun setPluginEnabledInConfig(loader: PluginLoader, enabled: Boolean) { - eocvSim.config.flags[loader.hash()] = enabled - eocvSim.configManager.saveToFile() + configManager.config.flags[loader.hash()] = enabled + configManager.saveToFile() } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/repository/PluginRepositoryManager.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/repository/PluginRepositoryManager.kt similarity index 71% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/repository/PluginRepositoryManager.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/repository/PluginRepositoryManager.kt index b235b9be..0f587604 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/repository/PluginRepositoryManager.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/repository/PluginRepositoryManager.kt @@ -1,53 +1,30 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.repository +package org.deltacv.eocvsim.plugin.repository -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.dialog.AppendDelegate -import com.github.serivesmejia.eocvsim.gui.dialog.PluginOutput +import com.github.serivesmejia.eocvsim.plugin.output.PluginDialogSignal +import com.github.serivesmejia.eocvsim.plugin.output.PluginOutputHandler import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.event.EventHandler import com.github.serivesmejia.eocvsim.util.extension.hashString import com.github.serivesmejia.eocvsim.util.extension.plus -import io.github.deltacv.common.util.loggerForThis -import com.moandjiezana.toml.Toml -import io.github.deltacv.common.util.ParsedVersion -import io.github.deltacv.eocvsim.plugin.loader.PluginManager +import org.deltacv.common.util.serialization.Toml +import org.deltacv.common.util.ParsedVersion +import org.deltacv.common.util.loggerForThis +import org.deltacv.eocvsim.plugin.loader.PluginManager +import kotlinx.coroutines.runBlocking import org.jboss.shrinkwrap.resolver.api.maven.ConfigurableMavenResolverSystem import org.jboss.shrinkwrap.resolver.api.maven.Maven import java.io.File -import java.util.concurrent.TimeUnit -import java.util.concurrent.locks.Condition -import java.util.concurrent.locks.ReentrantLock import javax.swing.JOptionPane -import kotlin.concurrent.withLock - class PluginRepositoryManager( - val appender: AppendDelegate, - val eocvSim: EOCVSim, - val haltLock: ReentrantLock, - val haltCondition: Condition + val outputHandler: PluginOutputHandler, + val onMainUpdate: EventHandler, + val restart: () -> Unit ) { companion object { @@ -80,7 +57,6 @@ class PluginRepositoryManager( fun init() { logger.info("Initializing...") - appender // init appender SysUtil.copyFileIs(CACHE_TOML_RES, CACHE_FILE, false) cacheToml = Toml().read(CACHE_FILE) @@ -150,18 +126,16 @@ class PluginRepositoryManager( val latest = checkForUpdates(pluginDep, repositories.toTypedArray()) if(latest != null) { - appender.appendln( - PluginOutput.SPECIAL_SILENT + - "Plugin \"${plugin.key}\" is outdated. Latest version is ${latest.version}." + outputHandler.sendOutputLine( + "Plugin \"${plugin.key}\" is outdated. Latest version is ${latest.version}." ) - eocvSim.onMainUpdate.once { + onMainUpdate.once { promptUpdateAndRestart(plugin.key, pluginDep, latest) } } else { - appender.appendln( - PluginOutput.SPECIAL_SILENT + - "Plugin \"${plugin.key}\" is up to date." + outputHandler.sendOutputLine( + "Plugin \"${plugin.key}\" is up to date." ) } } @@ -175,7 +149,7 @@ class PluginRepositoryManager( private fun promptUpdateAndRestart(pluginName: String, pluginDep: String, latest: ParsedVersion) { if (promptUpdate(pluginName, latest)) { - appender.appendln(PluginOutput.SPECIAL_SILENT +"Updating plugin \"$pluginName\" to version ${latest.version}...") + outputHandler.sendOutputLine("Updating plugin \"$pluginName\" to version ${latest.version}...") val artifact = parseArtifact(pluginDep) @@ -187,7 +161,7 @@ class PluginRepositoryManager( // Locate the `[plugins]` section val indexOfPlugins = tomlLines.indexOfFirst { it.trim() == "[plugins]" } if (indexOfPlugins == -1) { - appender.appendln("Failed to find [plugins] section in the TOML file.") + outputHandler.sendOutputLine("Failed to find [plugins] section in the TOML file.") return } @@ -199,19 +173,18 @@ class PluginRepositoryManager( ?.let { it + indexOfPlugins + 1 } // Adjust the index relative to the full list if (pluginLineIndex == -1 || pluginLineIndex == null) { - appender.appendln("Failed to find plugin \"$pluginName\" in the TOML file.") + outputHandler.sendOutputLine("Failed to find plugin \"$pluginName\" in the TOML file.") return } // Update the version - val oldLine = tomlLines[pluginLineIndex] tomlLines[pluginLineIndex] = "$pluginName = \"${artifact.groupId}:${artifact.artifactId}:${latest.version}\"" // Write updated content back to the TOML file tomlFile.writeText(tomlLines.joinToString("\n")) - appender.appendln(PluginOutput.SPECIAL_SILENT +"Successfully updated \"$pluginName\" to version ${latest.version}. Restarting...") - eocvSim.restart() + outputHandler.sendOutputLine("Successfully updated \"$pluginName\" to version ${latest.version}. Restarting...") + restart() } } @@ -245,15 +218,13 @@ class PluginRepositoryManager( if (cachedFile.exists() && areAllTransitivesCached(pluginDep)) { addToResolvedFiles(cachedFile, pluginDep, newCache, newTransitiveCache) - appender.appendln( - PluginOutput.SPECIAL_SILENT + - "Found cached plugin \"$pluginDep\" (${pluginDep.hashString}). All transitive dependencies OK." + outputHandler.sendOutputLine( + "Found cached plugin \"$pluginDep\" (${pluginDep.hashString}). All transitive dependencies OK." ) return true } else { - appender.appendln( - PluginOutput.SPECIAL_SILENT + - "Dependency missing for plugin $pluginDep. Resolving..." + outputHandler.sendOutputLine( + "Dependency missing for plugin $pluginDep. Resolving..." ) } } @@ -271,14 +242,12 @@ class PluginRepositoryManager( val matchesDepsHash = depsHash == tomlHash if(!matchesDepsHash) { - appender.appendln( - PluginOutput.SPECIAL_SILENT + - "Mismatch, $depsHash != $tomlHash" + outputHandler.sendOutputLine( + "Mismatch, $depsHash != $tomlHash" ) - appender.appendln( - PluginOutput.SPECIAL_SILENT + - "Transitive dependencies hash mismatch for plugin $pluginDep. Resolving..." + outputHandler.sendOutputLine( + "Transitive dependencies hash mismatch for plugin $pluginDep. Resolving..." ) } @@ -289,9 +258,8 @@ class PluginRepositoryManager( val exists = File(it).exists() if(!exists) { - appender.appendln( - PluginOutput.SPECIAL_SILENT + - "Couldn't find file specified in cache for plugin $pluginDep, expected at \"$it\"." + outputHandler.sendOutputLine( + "Couldn't find file specified in cache for plugin $pluginDep, expected at \"$it\"." ) } @@ -305,7 +273,7 @@ class PluginRepositoryManager( newCache: MutableMap, newTransitiveCache: MutableMap> ): File { - appender.appendln("Resolving plugin \"$pluginDep\"...") + outputHandler.sendOutputLine("Resolving plugin \"$pluginDep\"...") var pluginJar: File? = null @@ -347,18 +315,23 @@ class PluginRepositoryManager( // Function to handle resolution errors private fun handleResolutionError(pluginDep: String, ex: Exception) { logger.warn("Failed to resolve plugin dependency \"$pluginDep\"", ex) - appender.appendln("Failed to resolve plugin \"$pluginDep\": ${ex.message}") + outputHandler.sendOutputLine("Failed to resolve plugin \"$pluginDep\": ${ex.message}") } // Function to handle the outcome of the resolution process private fun handleResolutionOutcome(shouldHalt: Boolean) { if (shouldHalt) { - appender.append(PluginOutput.SPECIAL_CONTINUE) - haltLock.withLock { - haltCondition.await(15, TimeUnit.SECONDS) // wait for user to read the error + // Show dialog and enable Continue button if errors occurred + outputHandler.sendDialogSignal(PluginDialogSignal.ShowOutput) + outputHandler.sendDialogSignal(PluginDialogSignal.EnableContinue) + + // Block for up to 15 seconds waiting for user to read the error + runBlocking { + outputHandler.waitForContinuation(15000L) } } else { - appender.append(PluginOutput.SPECIAL_CLOSE) + // Close dialog if everything resolved successfully + outputHandler.sendDialogSignal(PluginDialogSignal.Hide) } } @@ -399,4 +372,4 @@ class PluginRepositoryManager( deps.joinToString(File.pathSeparator).replace("\\", "/").trimEnd(File.pathSeparatorChar).hashString } -class InvalidFileException(msg: String) : RuntimeException(msg) \ No newline at end of file +class InvalidFileException(msg: String) : RuntimeException(msg) diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/repository/PluginRepositoryUpdateChecker.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/repository/PluginRepositoryUpdateChecker.kt similarity index 64% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/repository/PluginRepositoryUpdateChecker.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/repository/PluginRepositoryUpdateChecker.kt index ca76a702..f48d9306 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/repository/PluginRepositoryUpdateChecker.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/repository/PluginRepositoryUpdateChecker.kt @@ -1,30 +1,13 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.repository +package org.deltacv.eocvsim.plugin.repository -import io.github.deltacv.common.util.loggerOf -import io.github.deltacv.common.util.ParsedVersion +import org.deltacv.common.util.loggerOf +import org.deltacv.common.util.ParsedVersion +import java.net.URI import java.net.URL import javax.xml.parsers.DocumentBuilderFactory @@ -67,7 +50,7 @@ fun findArtifactLatest(repositoryUrl: String, artifactId: String): ParsedVersion return try { // Fetch and parse the maven-metadata.xml - val metadataXml = URL(mavenMetadataUrl).readText() + val metadataXml = URI(mavenMetadataUrl).toURL().readText() val factory = DocumentBuilderFactory.newInstance() val builder = factory.newDocumentBuilder() @@ -102,4 +85,4 @@ fun checkForUpdates( } return null -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemon.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemon.kt similarity index 71% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemon.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemon.kt index b6612f8b..7f937246 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemon.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemon.kt @@ -1,42 +1,24 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.security.superaccess +package org.deltacv.eocvsim.plugin.security.superaccess import com.github.serivesmejia.eocvsim.util.extension.fileHash import com.github.serivesmejia.eocvsim.util.extension.plus -import io.github.deltacv.common.util.loggerForThis -import com.github.serivesmejia.eocvsim.util.serialization.PolymorphicAdapter -import com.google.gson.GsonBuilder -import com.moandjiezana.toml.Toml -import io.github.deltacv.eocvsim.gui.dialog.SuperAccessRequest -import io.github.deltacv.eocvsim.plugin.loader.PluginInfo -import io.github.deltacv.eocvsim.plugin.loader.PluginManager -import io.github.deltacv.eocvsim.plugin.loader.PluginManager.Companion.GENERIC_LAWYER_YEET -import io.github.deltacv.eocvsim.plugin.loader.PluginManager.Companion.GENERIC_SUPERACCESS_WARN -import io.github.deltacv.eocvsim.plugin.security.Authority -import io.github.deltacv.eocvsim.plugin.security.AuthorityFetcher -import io.github.deltacv.eocvsim.plugin.security.MutablePluginSignature +import com.github.serivesmejia.eocvsim.util.serialization.JacksonJsonSupport +import org.deltacv.common.util.loggerForThis +import com.fasterxml.jackson.annotation.JsonTypeInfo +import org.deltacv.common.util.serialization.Toml +import org.deltacv.eocvsim.gui.dialog.SuperAccessRequest +import org.deltacv.eocvsim.plugin.loader.PluginInfo +import org.deltacv.eocvsim.plugin.loader.PluginManager +import org.deltacv.eocvsim.plugin.loader.PluginManager.Companion.GENERIC_LAWYER_YEET +import org.deltacv.eocvsim.plugin.loader.PluginManager.Companion.GENERIC_SUPERACCESS_WARN +import org.deltacv.eocvsim.plugin.security.Authority +import org.deltacv.eocvsim.plugin.security.AuthorityFetcher +import org.deltacv.eocvsim.plugin.security.MutablePluginSignature import org.java_websocket.client.WebSocketClient import org.java_websocket.handshake.ServerHandshake import java.io.File @@ -44,22 +26,22 @@ import java.lang.Exception import java.net.URI import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors +import java.util.concurrent.locks.ReentrantLock import java.util.zip.ZipFile import javax.swing.SwingUtilities +import kotlin.concurrent.withLock import kotlin.system.exitProcess object SuperAccessDaemon { val logger by loggerForThis() - val gson = GsonBuilder() - .registerTypeHierarchyAdapter(SuperAccessMessage::class.java, PolymorphicAdapter("message")) - .registerTypeHierarchyAdapter(SuperAccessResponse::class.java, PolymorphicAdapter("response")) - .create() + val mapper = JacksonJsonSupport.ipcMapper @get:Synchronized @set:Synchronized private var uniqueId = 0 + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class") sealed class SuperAccessMessage { data class Request(var pluginPath: String, var signature: MutablePluginSignature, var reason: String) : SuperAccessMessage() data class Check(var pluginPath: String) : SuperAccessMessage() @@ -67,6 +49,7 @@ object SuperAccessDaemon { var id = uniqueId++ } + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class") sealed class SuperAccessResponse(val id: Int) { class Success(id: Int) : SuperAccessResponse(id) class Failure(id: Int) : SuperAccessResponse(id) @@ -75,6 +58,7 @@ object SuperAccessDaemon { val SUPERACCESS_FILE = PluginManager.PLUGIN_CACHING_FOLDER + File.separator + "superaccess.txt" private val access = ConcurrentHashMap() + private val fileLock = ReentrantLock() @JvmStatic fun main(args: Array) { @@ -105,7 +89,7 @@ object SuperAccessDaemon { } override fun onMessage(msg: String) { - val message = gson.fromJson(msg, SuperAccessMessage::class.java) + val message = mapper.readValue(msg, SuperAccessMessage::class.java) executor.submit { when (message) { @@ -125,11 +109,15 @@ object SuperAccessDaemon { val parser = parsePlugin(pluginFile) ?: run { logger.error("Failed to parse plugin at ${message.pluginPath}") - (gson.toJson(SuperAccessResponse.Failure(message.id))) + send(mapper.writeValueAsString(SuperAccessResponse.Failure(message.id))) return@handleRequest } - if(SUPERACCESS_FILE.exists() && SUPERACCESS_FILE.readLines().contains(pluginFile.fileHash())) { + val hasAccess = fileLock.withLock { + SUPERACCESS_FILE.exists() && SUPERACCESS_FILE.readLines().contains(pluginFile.fileHash()) + } + + if(hasAccess) { accessGranted(message.id, message.pluginPath) return } @@ -163,7 +151,7 @@ object SuperAccessDaemon { val reason = message.reason - val name = parser.nameWithAuthorVersion + val name = parser.nameWithVersionAndAuthor var warning = "$GENERIC_SUPERACCESS_WARN" if(reason.trim().isNotBlank()) { @@ -172,7 +160,15 @@ object SuperAccessDaemon { // helper function to grant access, avoid code duplication fun grant() { - SUPERACCESS_FILE.appendText(pluginFile.fileHash() + "\n") + fileLock.withLock { + if (!SUPERACCESS_FILE.exists()) { + SUPERACCESS_FILE.createNewFile() + } + // Re-check in case another thread granted access in the meantime + if (!SUPERACCESS_FILE.readLines().contains(pluginFile.fileHash())) { + SUPERACCESS_FILE.appendText(pluginFile.fileHash() + "\n") + } + } accessGranted(message.id, message.pluginPath) } @@ -216,7 +212,11 @@ object SuperAccessDaemon { val pluginFile = File(message.pluginPath) - if(SUPERACCESS_FILE.exists() && SUPERACCESS_FILE.readLines().contains(pluginFile.fileHash())) { + val hasAccess = fileLock.withLock { + SUPERACCESS_FILE.exists() && SUPERACCESS_FILE.readLines().contains(pluginFile.fileHash()) + } + + if(hasAccess) { accessGranted(message.id, message.pluginPath) } else { accessDenied(message.id, message.pluginPath) @@ -225,12 +225,12 @@ object SuperAccessDaemon { private fun accessGranted(id: Int, pluginPath: String) { access[pluginPath] = true - send(gson.toJson(SuperAccessResponse.Success(id))) + send(mapper.writeValueAsString(SuperAccessResponse.Success(id))) } private fun accessDenied(id: Int, pluginPath: String) { access[pluginPath] = false - send(gson.toJson(SuperAccessResponse.Failure(id))) + send(mapper.writeValueAsString(SuperAccessResponse.Failure(id))) } override fun onClose(p0: Int, p1: String?, p2: Boolean) { @@ -253,4 +253,4 @@ object SuperAccessDaemon { return null } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemonClient.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemonClient.kt similarity index 76% rename from EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemonClient.kt rename to EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemonClient.kt index f2c64eec..7bf7294f 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemonClient.kt +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemonClient.kt @@ -1,30 +1,13 @@ /* * Copyright (c) 2024 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.plugin.security.superaccess +package org.deltacv.eocvsim.plugin.security.superaccess import com.github.serivesmejia.eocvsim.util.JavaProcess -import io.github.deltacv.common.util.loggerForThis +import com.github.serivesmejia.eocvsim.util.serialization.JacksonJsonSupport +import org.deltacv.common.util.loggerForThis import org.java_websocket.WebSocket import org.java_websocket.handshake.ClientHandshake import org.java_websocket.server.WebSocketServer @@ -47,7 +30,7 @@ private data class AccessCache( class SuperAccessDaemonClient( val cacheTTLMillis: Long = 3_000, - autoacceptOnTrusted: Boolean + val autoacceptOnTrusted: Boolean ) { val logger by loggerForThis() @@ -59,27 +42,35 @@ class SuperAccessDaemonClient( private val accessCache = mutableMapOf() // create a new WebSocket server - private val server = WsServer(startLock, startCondition, autoacceptOnTrusted) + private var server: WsServer? = null + + private var isInitialized = false - fun init() { - server.start() + fun initIfNeeded() { + if (isInitialized) return + + server = WsServer(startLock, startCondition, autoacceptOnTrusted) + server!!.start() startLock.withLock { startCondition.await() } logger.info("SuperAccessDaemonClient initialized") + isInitialized = true } fun sendRequest(request: SuperAccessDaemon.SuperAccessMessage.Request, onResponse: (Boolean) -> Unit) { - if(server.connections.isEmpty()) { + initIfNeeded() + + if(server!!.connections.isEmpty()) { onResponse(false) return } - server.broadcast(SuperAccessDaemon.gson.toJson(request)) + server!!.broadcast(JacksonJsonSupport.ipcMapper.writeValueAsString(request)) - server.addResponseReceiver(request.id) { response -> + server!!.addResponseReceiver(request.id) { response -> val result = when (response) { is SuperAccessDaemon.SuperAccessResponse.Success -> { onResponse(true) @@ -109,16 +100,18 @@ class SuperAccessDaemonClient( } } } + + initIfNeeded() val lock = ReentrantLock() val condition = lock.newCondition() val check = SuperAccessDaemon.SuperAccessMessage.Check(file.absolutePath) - server.broadcast(SuperAccessDaemon.gson.toJson(check)) + server!!.broadcast(JacksonJsonSupport.ipcMapper.writeValueAsString(check)) var hasAccess = false - server.addResponseReceiver(check.id) { response -> + server!!.addResponseReceiver(check.id) { response -> if(response is SuperAccessDaemon.SuperAccessResponse.Success) { hasAccess = true @@ -171,7 +164,7 @@ class SuperAccessDaemonClient( } override fun onOpen(conn: WebSocket, p1: ClientHandshake?) { - val hostString = conn.localSocketAddress.hostString + val hostString = conn.remoteSocketAddress.hostString if(hostString != "127.0.0.1" && hostString != "localhost" && hostString != "0.0.0.0") { logger.warn("Connection from ${conn.remoteSocketAddress} refused, only localhost connections are allowed") conn.close(1013, "Ipc does not allow connections incoming from non-localhost addresses") @@ -197,7 +190,7 @@ class SuperAccessDaemonClient( } override fun onMessage(ws: WebSocket, msg: String) { - val response = SuperAccessDaemon.gson.fromJson(msg, SuperAccessDaemon.SuperAccessResponse::class.java) + val response = JacksonJsonSupport.ipcMapper.readValue(msg, SuperAccessDaemon.SuperAccessResponse::class.java) synchronized(responseReceiverLock) { for ((condition, receiver) in responseReceiver.toMap()) { @@ -250,4 +243,4 @@ class SuperAccessDaemonClient( } } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/org/deltacv/eocvsim/sandbox/restrictions/MethodCallByteCodeChecker.kt b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/sandbox/restrictions/MethodCallByteCodeChecker.kt new file mode 100644 index 00000000..d1644491 --- /dev/null +++ b/EOCV-Sim/src/main/java/org/deltacv/eocvsim/sandbox/restrictions/MethodCallByteCodeChecker.kt @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.sandbox.restrictions + +import org.objectweb.asm.* + +class MethodCallByteCodeChecker( + bytecode: ByteArray, + private val methodBlacklist: Set +) { + + init { + val classReader = ClassReader(bytecode) + classReader.accept(MethodCheckClassVisitor(), 0) + } + + private inner class MethodCheckClassVisitor : ClassVisitor(Opcodes.ASM9) { + + private lateinit var currentClassName: String + + override fun visit( + version: Int, + access: Int, + name: String?, + signature: String?, + superName: String?, + interfaces: Array? + ) { + super.visit(version, access, name, signature, superName, interfaces) + + currentClassName = name!!.replace('/', '.') + } + + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array? + ): MethodVisitor { + + val mv = super.visitMethod( + access, + name, + descriptor, + signature, + exceptions + ) + + return MethodCheckMethodVisitor( + mv, + currentClassName, + name!! + ) + } + } + + private inner class MethodCheckMethodVisitor( + mv: MethodVisitor?, + private val currentClassName: String, + private val currentMethodName: String + ) : MethodVisitor(Opcodes.ASM9, mv) { + + private fun checkMethod(owner: String, name: String) { + val methodIdentifier = + "${owner.replace('/', '.')}#$name" + + if(methodBlacklist.contains(methodIdentifier)) { + throw IllegalAccessError( + "Unauthorized method call of $methodIdentifier " + + "from dynamic code at " + + "$currentClassName#$currentMethodName" + ) + } + } + + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean + ) { + super.visitMethodInsn( + opcode, + owner, + name, + descriptor, + isInterface + ) + + checkMethod(owner!!, name!!) + } + + override fun visitInvokeDynamicInsn( + name: String?, + descriptor: String?, + bootstrapMethodHandle: Handle?, + bootstrapMethodArguments: Array? + ) { + + super.visitInvokeDynamicInsn( + name, + descriptor, + bootstrapMethodHandle, + bootstrapMethodArguments + ) + + /* + * Check bootstrap method itself + */ + bootstrapMethodHandle?.let { + checkMethod(it.owner, it.name) + } + + /* + * Check bootstrap arguments + * + * Lambdas and method references commonly store + * target methods here as Handle instances. + */ + bootstrapMethodArguments?.forEach { arg -> + + when(arg) { + + is Handle -> { + checkMethod(arg.owner, arg.name) + } + + is ConstantDynamic -> { + + /* + * ConstantDynamic also has a bootstrap + * method and bootstrap args. + */ + + val bsm = arg.bootstrapMethod + + checkMethod( + bsm.owner, + bsm.name + ) + + for(i in 0 until arg.bootstrapMethodArgumentCount) { + + val nestedArg = + arg.getBootstrapMethodArgument(i) + + if(nestedArg is Handle) { + checkMethod( + nestedArg.owner, + nestedArg.name + ) + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/EOCVSimTelemetryImpl.java b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/EOCVSimTelemetryImpl.java index 1af5a2f2..e5c79b24 100644 --- a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/EOCVSimTelemetryImpl.java +++ b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/EOCVSimTelemetryImpl.java @@ -803,7 +803,7 @@ protected void saveToTransmitter(boolean recompose) String string = currentSb.toString().trim(); for(TelemetryTransmissionReceiver receiver : transmissionReceivers) { - receiver.onTelemetryTransmission(string, this); + receiver.consumeTelemetry(string, this); } } @@ -1013,3 +1013,6 @@ protected void onAddData() // no-op } } + + + diff --git a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryInternal.java b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryInternal.java index 4abbe605..9267d6c0 100644 --- a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryInternal.java +++ b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryInternal.java @@ -33,4 +33,6 @@ public interface TelemetryInternal { boolean tryUpdateIfDirty(); void resetTelemetryForOpMode(); -} \ No newline at end of file +} + + diff --git a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryTransmissionReceiver.kt b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryTransmissionReceiver.kt index bbf2c20c..9bc03507 100644 --- a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryTransmissionReceiver.kt +++ b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryTransmissionReceiver.kt @@ -1,7 +1,12 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package org.firstinspires.ftc.robotcore.internal.opmode import org.firstinspires.ftc.robotcore.external.Telemetry interface TelemetryTransmissionReceiver { - fun onTelemetryTransmission(text: String, srcTelemetry: Telemetry) -} \ No newline at end of file + fun consumeTelemetry(text: String, srcTelemetry: Telemetry) +} diff --git a/EOCV-Sim/src/main/java/org/openftc/easyopencv/ProcessFrameInternalAccessor.kt b/EOCV-Sim/src/main/java/org/openftc/easyopencv/ProcessFrameInternalAccessor.kt index 331a5afa..9ef1d253 100644 --- a/EOCV-Sim/src/main/java/org/openftc/easyopencv/ProcessFrameInternalAccessor.kt +++ b/EOCV-Sim/src/main/java/org/openftc/easyopencv/ProcessFrameInternalAccessor.kt @@ -1,28 +1,10 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package org.openftc.easyopencv import org.opencv.core.Mat -fun OpenCvPipeline.processFrameInternal(frame: Mat): Mat? = processFrameInternal(frame) \ No newline at end of file +fun OpenCvPipeline.processFrameInternal(frame: Mat): Mat? = processFrameInternal(frame) diff --git a/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedPipelineHandler.kt b/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedPipelineHandler.kt index afae8a04..8654f491 100644 --- a/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedPipelineHandler.kt +++ b/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedPipelineHandler.kt @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + package org.openftc.easyopencv import com.github.serivesmejia.eocvsim.input.InputSource @@ -44,4 +26,4 @@ class TimestampedPipelineHandler : SpecificPipelineHandlerNPE (Windwoes) - EasyOpenCV and AprilTag Plugin -serivesmejia - Main deltacv Dev +serivesmejia - Main Dev @ deltacv +NPE (Windwoes) - Original EasyOpenCV & AprilTag Plugin Purav - Contributor & Mac Tester Jaran - Kotlin & Coroutines Advisor Shaurya - Guide Contributor & Mac Tester diff --git a/EOCV-Sim/src/main/resources/opensourcelibs.txt b/EOCV-Sim/src/main/resources/opensourcelibs.txt index ce70c5a7..bc932b6d 100644 --- a/EOCV-Sim/src/main/resources/opensourcelibs.txt +++ b/EOCV-Sim/src/main/resources/opensourcelibs.txt @@ -1,17 +1,13 @@ -EOCV-Sim and its source code is distributed under the MIT License +EOCV-Sim and its source code is distributed under the MIT License OpenCV - Under Apache 2.0 License -OpenPnP OpenCV - Under Apache 2.0 License +WPILib - Under BSD 3-Clause License FTC SDK - Some source code under BSD License EasyOpenCV - Some source code under MIT License -EOCV-AprilTag-Plugin - Source code under MIT License -webcam-capture - Under MIT License +EOCV-AprilTag-Plugin - API under the MIT License Skiko - Under Apache 2.0 License Gson - Under Apache 2.0 License ClassGraph - Under MIT License FlatLaf - Under Apache 2.0 License Kotlin stdlib & coroutines - Under Apache 2.0 License -picocli - Under Apache 2.0 License -dear imgui - Under MIT License -imgui-java - Under Apache 2.0 License -LWJGL - Under BSD 3-Clause License \ No newline at end of file +picocli - Under Apache 2.0 License \ No newline at end of file diff --git a/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip b/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip index bcc1f1ea..00207963 100644 Binary files a/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip and b/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip differ diff --git a/EOCV-Sim/src/test/kotlin/com/github/serivesmejia/eocvsim/test/CoreTests.kt b/EOCV-Sim/src/test/kotlin/com/github/serivesmejia/eocvsim/test/CoreTests.kt index 2b911300..772b1c37 100644 --- a/EOCV-Sim/src/test/kotlin/com/github/serivesmejia/eocvsim/test/CoreTests.kt +++ b/EOCV-Sim/src/test/kotlin/com/github/serivesmejia/eocvsim/test/CoreTests.kt @@ -1,51 +1,28 @@ /* * Copyright (c) 2021 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ + @file:Suppress("UNUSED") package com.github.serivesmejia.eocvsim.test -import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.util.LibraryLoader import io.kotest.core.spec.style.StringSpec import org.opencv.core.Mat -import org.openftc.apriltag.AprilTagDetectorJNI +import org.wpilib.vision.apriltag.jni.AprilTagJNI -class OpenCvTest : StringSpec({ - "Loading native library" { - EOCVSim.loadOpenCvLib() +class LibrariesTest : StringSpec({ + "Loading Libraries" { + LibraryLoader.loadLibraries() } "Creating a Mat" { Mat() } -}) - -class AprilTagsTest : StringSpec({ - "Create AprilTag detector" { - val detector = AprilTagDetectorJNI.createApriltagDetector( - AprilTagDetectorJNI.TagFamily.TAG_36h11.string, - 0f, 3 - ) - println("Created detector $detector") + "Create AprilTag Detector" { + val handle = AprilTagJNI.createDetector() + assert(handle != 0.toLong()) } }) \ No newline at end of file diff --git a/README.md b/README.md index 2b939a35..8ba27e90 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,43 @@ Join the [deltacv discord server](https://discord.gg/A3RMYzf6DA) ! ### Formerly, EOCV-Sim was hosted on a [personal account repo](https://github.com/serivesmejia/EOCV-Sim/). Released prior to 3.0.0 can be found there for historic purposes. +## [v4.2.0 - Lifecycle & Platform Architecture Rework](https://github.com/deltacv/EOCV-Sim/releases/tag/v4.2.0) + +* This is the 37th release for EOCV-Sim + + * Major internal architecture rework focused on lifecycle management, startup orchestration and long-term platform maintainability + * Changelog + * **BREAKING**: Migrates package namespaces from io.github.deltacv to org.deltacv + * PaperVision is missing in this release and will be added in the next patch. + * Migrates OpenCV and webcam handling away from OpenPnP distributions into WPILib's OpenCV packaging and CSCore backend + * Adds support for Linux ARM64 and Intel macOS native distributions + * Replaces the legacy AprilTag plugin implementation with WPILib's AprilTag support while preserving backwards compatibility + * Adds multiplatform releases for: + * Windows x86-64 + * Linux x86-64 + * Linux ARM64 + * macOS Intel + * macOS Apple Silicon + * Adds a Bootstrap launcher that validates the Java runtime before startup instead of silently failing + * Bootstrap now scans common Java installation locations on Windows to help users locate compatible runtimes + * Adds timeout configuration controls for webcams + * Refactors the tunable field API into a cleaner and more maintainable implementation + * Implements individual APIs for accessing visualizer component controls + * Internal changes: + * Redesigns initialization and lifecycle handling around a centralized orchestrator with phased startup/shutdown + * Centralizes EOCV-Sim lifecycle scopes and improves cancellation handling across the application + * Adds support for non-blocking cancellation of input sources + * Migrates major internal systems and managers to Kotlin + * Migrates dependency injection to Koin + * Rewrites InputSourceManager and Visualizer into Kotlin + * Replaces Gson usage with Jackson serialization APIs + * Updates GitHub workflows to Java 25 + * Consolidates Maven publishing and multiplatform workflow handling + * Bugfixes: + * Fixes cases where onDrawFrame could be called before initialization completed + * Fixes continuation handling edge cases in the plugin manager + * Improves workflow artifact publishing reliability across platforms + ## [v4.1.2 - PaperVision Stable Release](https://github.com/deltacv/EOCV-Sim/releases/tag/v4.1.2) - This is the 36th release for EOCV-Sim - Adds [PaperVision v1.1.0](https://github.com/deltacv/PaperVision/releases/tag/v1.1.0) diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java index 145f3d2a..9c196949 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java @@ -183,4 +183,5 @@ private void telemetryAprilTag() { } // end method telemetryAprilTag() -} // end class \ No newline at end of file +} // end class + diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java index 523fa8f8..c8905238 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java @@ -138,4 +138,5 @@ private void telemetryAprilTag() { } // end method telemetryAprilTag() -} // end class \ No newline at end of file +} // end class + diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java index e0f65f3a..29ce90d1 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java @@ -237,4 +237,5 @@ private void telemetryAprilTag() { } // end method telemetryAprilTag() -} // end class \ No newline at end of file +} // end class + diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java index 312120c6..647516b8 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java @@ -188,4 +188,4 @@ public void runOpMode() sleep(50); } } -} \ No newline at end of file +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java index 33afccf1..6be2bc4e 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java @@ -133,4 +133,4 @@ public void runOpMode() sleep(20); } } -} \ No newline at end of file +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/AprilTagDetectionPipeline.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/AprilTagDetectionPipeline.java index 7302ad47..9f87f105 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/AprilTagDetectionPipeline.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/AprilTagDetectionPipeline.java @@ -312,4 +312,4 @@ public Pose(Mat rvec, Mat tvec) this.tvec = tvec; } } -} \ No newline at end of file +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SkystoneDeterminationPipeline.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SkystoneDeterminationPipeline.java index cc2ce895..66224f82 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SkystoneDeterminationPipeline.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SkystoneDeterminationPipeline.java @@ -307,4 +307,4 @@ public SkystonePosition getAnalysis() { return position; } -} \ No newline at end of file +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StageSwitchingPipeline.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StageSwitchingPipeline.java deleted file mode 100644 index ae69cdf8..00000000 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StageSwitchingPipeline.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2020 OpenFTC Team - * - * 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. - */ - -package org.firstinspires.ftc.teamcode; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.opencv.core.Core; -import org.opencv.core.Mat; -import org.opencv.core.MatOfPoint; -import org.opencv.core.Scalar; -import org.opencv.imgproc.Imgproc; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.util.ArrayList; -import java.util.List; - -@Disabled -public class StageSwitchingPipeline extends OpenCvPipeline -{ - Mat yCbCrChan2Mat = new Mat(); - Mat thresholdMat = new Mat(); - Mat contoursOnFrameMat = new Mat(); - List contoursList = new ArrayList<>(); - int numContoursFound; - - enum Stage - { - YCbCr_CHAN2, - THRESHOLD, - CONTOURS_OVERLAYED_ON_FRAME, - RAW_IMAGE, - } - - private Stage stageToRenderToViewport = Stage.YCbCr_CHAN2; - private Stage[] stages = Stage.values(); - - private Telemetry telemetry; - - public StageSwitchingPipeline(Telemetry telemetry) { - this.telemetry = telemetry; - } - - @Override - public void onViewportTapped() - { - /* - * Note that this method is invoked from the UI thread - * so whatever we do here, we must do quickly. - */ - - int currentStageNum = stageToRenderToViewport.ordinal(); - - int nextStageNum = currentStageNum + 1; - - if(nextStageNum >= stages.length) - { - nextStageNum = 0; - } - - stageToRenderToViewport = stages[nextStageNum]; - } - - @Override - public Mat processFrame(Mat input) - { - contoursList.clear(); - - /* - * This pipeline finds the contours of yellow blobs such as the Gold Mineral - * from the Rover Ruckus game. - */ - Imgproc.cvtColor(input, yCbCrChan2Mat, Imgproc.COLOR_RGB2YCrCb); - Core.extractChannel(yCbCrChan2Mat, yCbCrChan2Mat, 2); - Imgproc.threshold(yCbCrChan2Mat, thresholdMat, 102, 255, Imgproc.THRESH_BINARY_INV); - Imgproc.findContours(thresholdMat, contoursList, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE); - numContoursFound = contoursList.size(); - input.copyTo(contoursOnFrameMat); - Imgproc.drawContours(contoursOnFrameMat, contoursList, -1, new Scalar(0, 0, 255), 3, 8); - - telemetry.addData("[Stage]", stageToRenderToViewport); - telemetry.addData("[Found Contours]", "%d", numContoursFound); - telemetry.update(); - - switch (stageToRenderToViewport) - { - case YCbCr_CHAN2: - { - return yCbCrChan2Mat; - } - - case THRESHOLD: - { - return thresholdMat; - } - - case CONTOURS_OVERLAYED_ON_FRAME: - { - return contoursOnFrameMat; - } - - case RAW_IMAGE: - { - return input; - } - - default: - { - return input; - } - } - } - - public int getNumContoursFound() - { - return numContoursFound; - } -} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StoneOrientationAnalysisPipeline.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StoneOrientationAnalysisPipeline.java index b45ab52d..bb9f75a5 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StoneOrientationAnalysisPipeline.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StoneOrientationAnalysisPipeline.java @@ -466,4 +466,4 @@ static void drawRotatedRect(RotatedRect rect, Mat drawOn) Imgproc.line(drawOn, points[i], points[(i+1)%4], RED, 2); } } -} \ No newline at end of file +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/processor/SimpleThresholdProcessor.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/processor/SimpleThresholdProcessor.java index 0e5d7e7e..884f7516 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/processor/SimpleThresholdProcessor.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/processor/SimpleThresholdProcessor.java @@ -1,26 +1,8 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + package org.firstinspires.ftc.teamcode.processor; import android.graphics.Canvas; @@ -179,3 +161,4 @@ public Object processFrame(Mat frame, long captureTimeNanos) { public void onDrawFrame(Canvas canvas, int onscreenWidth, int onscreenHeight, float scaleBmpPxToCanvasPx, float scaleCanvasDensity, Object userContext) { } } + diff --git a/Vision/build.gradle b/Vision/build.gradle index 15eb033a..43266026 100644 --- a/Vision/build.gradle +++ b/Vision/build.gradle @@ -1,30 +1,27 @@ plugins { id 'kotlin' id 'signing' + id 'org.photonvision.tools.WpilibTools' id "com.vanniktech.maven.publish" version "0.30.0" } apply from: '../build.common.gradle' +wpilibTools.deps.wpilibVersion = wpilibVersion + dependencies { implementation project(':Common') - api("org.deltacv:AprilTagDesktop:$apriltag_plugin_version") { - transitive = false - } - - api "org.openpnp:opencv:$opencv_version" + compileOnly wpilibTools.deps.wpilibOpenCvJava(opencvVersion) + compileOnly wpilibTools.deps.wpilibJava("apriltag") + compileOnly wpilibTools.deps.wpilibJava("wpiutil") + compileOnly wpilibTools.deps.wpilibJava("wpimath") implementation "org.slf4j:slf4j-api:$slf4j_version" implementation 'org.jetbrains.kotlin:kotlin-stdlib' - - // Compatibility: Skiko supports many platforms but we will only be adding - // those that are supported by AprilTagDesktop as well - implementation("org.jetbrains.skiko:skiko-awt-runtime-windows-x64:$skiko_version") implementation("org.jetbrains.skiko:skiko-awt-runtime-linux-x64:$skiko_version") - implementation("org.jetbrains.skiko:skiko-awt-runtime-linux-x64:$skiko_version") implementation("org.jetbrains.skiko:skiko-awt-runtime-linux-arm64:$skiko_version") implementation("org.jetbrains.skiko:skiko-awt-runtime-macos-x64:$skiko_version") implementation("org.jetbrains.skiko:skiko-awt-runtime-macos-arm64:$skiko_version") diff --git a/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/LinearOpMode.java b/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/LinearOpMode.java index 866236f9..5703319c 100644 --- a/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/LinearOpMode.java +++ b/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/LinearOpMode.java @@ -31,8 +31,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE package com.qualcomm.robotcore.eventloop.opmode; -import io.github.deltacv.vision.external.source.ThreadVisionSourceProvider; -import io.github.deltacv.vision.external.source.VisionSourceProvider; +import org.deltacv.vision.external.source.ThreadVisionSourceProvider; +import org.deltacv.vision.external.source.VisionSourceProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -231,3 +231,6 @@ public void run() { } } + + + diff --git a/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpMode.java b/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpMode.java index ec861393..724ab948 100644 --- a/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpMode.java +++ b/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpMode.java @@ -33,10 +33,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import com.qualcomm.robotcore.hardware.Gamepad; import com.qualcomm.robotcore.hardware.HardwareMap; -import io.github.deltacv.vision.external.util.FrameQueue; -import io.github.deltacv.vision.internal.opmode.OpModeNotification; -import io.github.deltacv.vision.internal.opmode.OpModeNotifier; -import io.github.deltacv.vision.internal.opmode.OpModeState; +import org.deltacv.vision.external.util.FrameQueue; +import org.deltacv.vision.internal.opmode.OpModeNotification; +import org.deltacv.vision.internal.opmode.OpModeNotifier; +import org.deltacv.vision.internal.opmode.OpModeState; import org.openftc.easyopencv.TimestampedOpenCvPipeline; import org.firstinspires.ftc.robotcore.external.Telemetry; import org.opencv.core.Mat; @@ -211,3 +211,6 @@ protected void forceStop() { stopped = true; } } + + + diff --git a/Vision/src/main/java/com/qualcomm/robotcore/hardware/Gamepad.java b/Vision/src/main/java/com/qualcomm/robotcore/hardware/Gamepad.java index bfef1dab..01a5f88c 100644 --- a/Vision/src/main/java/com/qualcomm/robotcore/hardware/Gamepad.java +++ b/Vision/src/main/java/com/qualcomm/robotcore/hardware/Gamepad.java @@ -422,3 +422,5 @@ protected void updateButtonAliases(){ ps = guide; } } + + diff --git a/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareDevice.java b/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareDevice.java index 60cc1681..2122111e 100644 --- a/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareDevice.java +++ b/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareDevice.java @@ -80,4 +80,6 @@ enum Manufacturer { */ void close(); -} \ No newline at end of file +} + + diff --git a/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareMap.java b/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareMap.java index 6a1459c3..74aba1f5 100644 --- a/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareMap.java +++ b/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareMap.java @@ -1,7 +1,12 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + package com.qualcomm.robotcore.hardware; -import io.github.deltacv.vision.external.source.ThreadVisionSourceProvider; -import io.github.deltacv.vision.internal.source.ftc.SourcedCameraNameImpl; +import org.deltacv.vision.external.source.ThreadVisionSourceProvider; +import org.deltacv.vision.internal.source.ftc.SourcedCameraNameImpl; import org.firstinspires.ftc.robotcore.external.hardware.camera.CameraName; public class HardwareMap { @@ -24,4 +29,4 @@ public T get(Class classType, String deviceName) { throw new IllegalArgumentException("Unknown device type " + classType.getName()); } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/PipelineRenderHook.kt b/Vision/src/main/java/io/github/deltacv/vision/external/PipelineRenderHook.kt deleted file mode 100644 index 11171578..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/external/PipelineRenderHook.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.external - -import android.graphics.Canvas -import org.openftc.easyopencv.OpenCvViewport -import org.openftc.easyopencv.OpenCvViewport.RenderHook - -object PipelineRenderHook : RenderHook { - override fun onDrawFrame(canvas: Canvas, onscreenWidth: Int, onscreenHeight: Int, scaleBmpPxToCanvasPx: Float, canvasDensityScale: Float, userContext: Any) { - val frameContext = userContext as OpenCvViewport.FrameContext - - // We must make sure that we call onDrawFrame() for the same pipeline that set the - // context object when requesting a draw hook. (i.e. we can't just call onDrawFrame() - // for whatever pipeline happens to be currently attached; it might have an entirely - // different notion of what to expect in the context object) - if (frameContext.generatingPipeline != null) { - frameContext.generatingPipeline.onDrawFrame(canvas, onscreenWidth, onscreenHeight, scaleBmpPxToCanvasPx, canvasDensityScale, frameContext.userContext) - } - } -} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/source/FrameReceiver.java b/Vision/src/main/java/io/github/deltacv/vision/external/source/FrameReceiver.java deleted file mode 100644 index 576d37b3..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/external/source/FrameReceiver.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.external.source; - -import org.opencv.core.Mat; - -public interface FrameReceiver { - default void onFrameStart() {} - void consume(Mat frame, long timestamp); - - void stop(); -} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/source/ThreadVisionSourceProvider.java b/Vision/src/main/java/io/github/deltacv/vision/external/source/ThreadVisionSourceProvider.java deleted file mode 100644 index a92b2f48..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/external/source/ThreadVisionSourceProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.external.source; - -public final class ThreadVisionSourceProvider { - - private ThreadVisionSourceProvider() {} // No instantiation - - private static final ThreadLocal provider = new ThreadLocal<>(); - - public static void register(VisionSourceProvider provider) { - ThreadVisionSourceProvider.provider.set(provider); - } - - public static VisionSourceProvider getCurrentProvider() { - return provider.get(); - } - - public static VisionSource get(String name) { - return getCurrentProvider().get(name); - } - -} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/source/ViewportVisionSourceProvider.java b/Vision/src/main/java/io/github/deltacv/vision/external/source/ViewportVisionSourceProvider.java deleted file mode 100644 index 163bdf66..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/external/source/ViewportVisionSourceProvider.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.external.source; - -import org.openftc.easyopencv.OpenCvViewport; - -public interface ViewportVisionSourceProvider extends VisionSourceProvider { - OpenCvViewport viewport(); -} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/source/VisionSource.java b/Vision/src/main/java/io/github/deltacv/vision/external/source/VisionSource.java deleted file mode 100644 index e7a8afea..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/external/source/VisionSource.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.external.source; - -import org.opencv.core.Size; - -public interface VisionSource { - - int init(); - - CameraControlMap getControlMap(); - - boolean start(Size requestedSize); - - boolean attach(FrameReceiver sourced); - boolean remove(FrameReceiver sourced); - - boolean stop(); - - boolean close(); - -} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/source/VisionSourceProvider.java b/Vision/src/main/java/io/github/deltacv/vision/external/source/VisionSourceProvider.java deleted file mode 100644 index eb848f17..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/external/source/VisionSourceProvider.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.external.source; - -public interface VisionSourceProvider { - - VisionSource get(String name); - -} \ No newline at end of file diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/util/FrameQueue.java b/Vision/src/main/java/io/github/deltacv/vision/external/util/FrameQueue.java deleted file mode 100644 index b4ba3cd4..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/external/util/FrameQueue.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.external.util; - -import org.firstinspires.ftc.robotcore.internal.collections.EvictingBlockingQueue; -import org.opencv.core.Mat; -import org.openftc.easyopencv.MatRecycler; - -import java.util.concurrent.ArrayBlockingQueue; - -public class FrameQueue { - - private final EvictingBlockingQueue viewportQueue; - private final MatRecycler matRecycler; - - public FrameQueue(int maxQueueItems) { - viewportQueue = new EvictingBlockingQueue<>(new ArrayBlockingQueue<>(maxQueueItems)); - matRecycler = new MatRecycler(maxQueueItems + 2); - - viewportQueue.setEvictAction(this::evict); - } - - public Mat takeMatAndPost() { - Mat mat = matRecycler.takeMatOrNull(); - viewportQueue.add(mat); - - return mat; - } - - public Mat takeMat() { - return matRecycler.takeMatOrNull(); - } - - public Mat poll() { - Mat mat = viewportQueue.poll(); - - if(mat != null) { - evict(mat); - } - - return mat; - } - - private void evict(Mat mat) { - if(mat instanceof MatRecycler.RecyclableMat) { - matRecycler.returnMat((MatRecycler.RecyclableMat) mat); - } - } - -} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/util/ThrowableHandler.java b/Vision/src/main/java/io/github/deltacv/vision/external/util/ThrowableHandler.java deleted file mode 100644 index 6adf22c5..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/external/util/ThrowableHandler.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.deltacv.vision.external.util; - -public interface ThrowableHandler { - void handle(Throwable e); -} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/util/Timestamped.java b/Vision/src/main/java/io/github/deltacv/vision/external/util/Timestamped.java deleted file mode 100644 index 08230143..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/external/util/Timestamped.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.external.util; - -public class Timestamped { - - private final T value; - private final long timestamp; - - public Timestamped(T value, long timestamp) { - this.value = value; - this.timestamp = timestamp; - } - - public T getValue() { - return value; - } - - public long getTimestamp() { - return timestamp; - } - -} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/util/extension/CvExt.kt b/Vision/src/main/java/io/github/deltacv/vision/external/util/extension/CvExt.kt deleted file mode 100644 index cc04a96a..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/external/util/extension/CvExt.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -@file:JvmName("CvExt") - -package io.github.deltacv.vision.external.util.extension - -import com.qualcomm.robotcore.util.Range -import org.opencv.core.CvType -import org.opencv.core.Mat -import org.opencv.core.Scalar -import org.opencv.core.Size -import org.opencv.imgproc.Imgproc - -fun Scalar.cvtColor(code: Int): Scalar { - val mat = Mat(5, 5, CvType.CV_8UC3); - mat.setTo(this) - Imgproc.cvtColor(mat, mat, code); - - val newScalar = Scalar(mat.get(1, 1)) - mat.release() - - return newScalar -} - -fun Size.aspectRatio() = height / width -fun Mat.aspectRatio() = size().aspectRatio() - -fun Size.clipTo(size: Size): Size { - width = Range.clip(width, 0.0, size.width) - height = Range.clip(height, 0.0, size.height) - return this -} \ No newline at end of file diff --git a/Vision/src/main/java/io/github/deltacv/vision/internal/opmode/Enums.kt b/Vision/src/main/java/io/github/deltacv/vision/internal/opmode/Enums.kt deleted file mode 100644 index 9b6e20cf..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/internal/opmode/Enums.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.internal.opmode - -enum class OpModeNotification { INIT, START, STOP, NOTHING } -enum class OpModeState { SELECTED, INIT, START, STOP, STOPPED } \ No newline at end of file diff --git a/Vision/src/main/java/io/github/deltacv/vision/internal/opmode/RedirectToOpModeThrowableHandler.kt b/Vision/src/main/java/io/github/deltacv/vision/internal/opmode/RedirectToOpModeThrowableHandler.kt deleted file mode 100644 index 4de4ff6a..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/internal/opmode/RedirectToOpModeThrowableHandler.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.internal.opmode - -import io.github.deltacv.vision.external.util.ThrowableHandler - -class RedirectToOpModeThrowableHandler(private val notifier: OpModeNotifier) : ThrowableHandler { - override fun handle(e: Throwable) { - notifier.notify(e) - } -} \ No newline at end of file diff --git a/Vision/src/main/java/io/github/deltacv/vision/internal/source/ftc/SourcedCameraName.java b/Vision/src/main/java/io/github/deltacv/vision/internal/source/ftc/SourcedCameraName.java deleted file mode 100644 index 8c189743..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/internal/source/ftc/SourcedCameraName.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.internal.source.ftc; - -import io.github.deltacv.vision.external.source.VisionSource; -import org.firstinspires.ftc.robotcore.external.hardware.camera.CameraCharacteristics; - -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -public abstract class SourcedCameraName implements WebcamName { - - public abstract VisionSource getSource(); - - @Override - public boolean isWebcam() { - return false; - } - - @Override - public boolean isCameraDirection() { - return false; - } - - @Override - public boolean isSwitchable() { - return false; - } - - @Override - public boolean isUnknown() { - return false; - } - - @Override - public CameraCharacteristics getCameraCharacteristics() { - return null; - } - -} diff --git a/Vision/src/main/java/io/github/deltacv/vision/internal/source/ftc/SourcedCameraNameImpl.java b/Vision/src/main/java/io/github/deltacv/vision/internal/source/ftc/SourcedCameraNameImpl.java deleted file mode 100644 index 3479190c..00000000 --- a/Vision/src/main/java/io/github/deltacv/vision/internal/source/ftc/SourcedCameraNameImpl.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.internal.source.ftc; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.qualcomm.robotcore.util.SerialNumber; -import io.github.deltacv.vision.external.source.VisionSource; -import org.jetbrains.annotations.NotNull; - -public class SourcedCameraNameImpl extends SourcedCameraName { - - private VisionSource source; - - public SourcedCameraNameImpl(VisionSource source) { - this.source = source; - } - - @Override - public VisionSource getSource() { - return source; - } - - @Override - public Manufacturer getManufacturer() { - return null; - } - - @Override - public String getDeviceName() { - return null; - } - - @Override - public String getConnectionInfo() { - return null; - } - - @Override - public int getVersion() { - return 0; - } - - @Override - public void resetDeviceConfigurationForOpMode() { - - } - - @Override - public void close() { - - } - - @NonNull - @NotNull - @Override - public SerialNumber getSerialNumber() { - return null; - } - - @Nullable - @org.jetbrains.annotations.Nullable - @Override - public String getUsbDeviceNameIfAttached() { - return null; - } - - @Override - public boolean isAttached() { - return false; - } -} diff --git a/Vision/src/main/java/io/github/deltacv/eocvsim/pipeline/StreamableOpenCvPipeline.java b/Vision/src/main/java/org/deltacv/eocvsim/pipeline/StreamableOpenCvPipeline.java similarity index 54% rename from Vision/src/main/java/io/github/deltacv/eocvsim/pipeline/StreamableOpenCvPipeline.java rename to Vision/src/main/java/org/deltacv/eocvsim/pipeline/StreamableOpenCvPipeline.java index 8e385641..dc419198 100644 --- a/Vision/src/main/java/io/github/deltacv/eocvsim/pipeline/StreamableOpenCvPipeline.java +++ b/Vision/src/main/java/org/deltacv/eocvsim/pipeline/StreamableOpenCvPipeline.java @@ -1,29 +1,11 @@ /* * Copyright (c) 2026 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.eocvsim.pipeline; +package org.deltacv.eocvsim.pipeline; -import io.github.deltacv.eocvsim.stream.ImageStreamer; +import org.deltacv.eocvsim.stream.ImageStreamer; import org.opencv.core.Mat; import org.openftc.easyopencv.OpenCvPipeline; import org.slf4j.Logger; @@ -67,4 +49,4 @@ public ImageStreamer getStreamer() { } } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/deltacv/eocvsim/stream/ImageStreamer.kt b/Vision/src/main/java/org/deltacv/eocvsim/stream/ImageStreamer.kt new file mode 100644 index 00000000..5a23ab36 --- /dev/null +++ b/Vision/src/main/java/org/deltacv/eocvsim/stream/ImageStreamer.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.eocvsim.stream + +import org.opencv.core.Mat + +interface ImageStreamer { + fun sendFrame(id: Int, image: Mat, cvtCode: Int? = null) +} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/FrameReceiverOpenCvCamera.java b/Vision/src/main/java/org/deltacv/vision/external/external/FrameReceiverOpenCvCamera.java similarity index 77% rename from Vision/src/main/java/io/github/deltacv/vision/external/FrameReceiverOpenCvCamera.java rename to Vision/src/main/java/org/deltacv/vision/external/external/FrameReceiverOpenCvCamera.java index a449dd74..0e38d92c 100644 --- a/Vision/src/main/java/io/github/deltacv/vision/external/FrameReceiverOpenCvCamera.java +++ b/Vision/src/main/java/org/deltacv/vision/external/external/FrameReceiverOpenCvCamera.java @@ -1,30 +1,12 @@ /* - * Copyright (c) 2023 OpenFTC & EOCV-Sim implementation by Sebastian Erives - * - * 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. - * + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. */ -package io.github.deltacv.vision.external; +package org.deltacv.vision.external; -import io.github.deltacv.vision.external.source.VisionSource; -import io.github.deltacv.vision.external.source.FrameReceiver; +import org.deltacv.vision.external.source.VisionSource; +import org.deltacv.vision.external.source.FrameReceiver; import org.firstinspires.ftc.robotcore.external.hardware.camera.CameraControls; import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.*; import org.firstinspires.ftc.robotcore.internal.camera.calibration.CameraCalibrationIdentity; @@ -55,7 +37,7 @@ public FrameReceiverOpenCvCamera(VisionSource source, OpenCvViewport handedViewp public int openCameraDevice() { prepareForOpenCameraDevice(); - return source.init(); + return source.check(); } @Override @@ -226,4 +208,4 @@ public void consume(Mat frame, long timestamp) { public void stop() { closeCameraDevice(); } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/deltacv/vision/external/external/PipelineRenderHook.kt b/Vision/src/main/java/org/deltacv/vision/external/external/PipelineRenderHook.kt new file mode 100644 index 00000000..f5ab80b0 --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/external/external/PipelineRenderHook.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external + +import android.graphics.Canvas +import org.openftc.easyopencv.OpenCvViewport +import org.openftc.easyopencv.OpenCvViewport.RenderHook + +object PipelineRenderHook : RenderHook { + override fun onDrawFrame(canvas: Canvas, onscreenWidth: Int, onscreenHeight: Int, scaleBmpPxToCanvasPx: Float, canvasDensityScale: Float, userContext: Any) { + val frameContext = userContext as OpenCvViewport.FrameContext + + // We must make sure that we call onDrawFrame() for the same pipeline that set the + // context object when requesting a draw hook. (i.e. we can't just call onDrawFrame() + // for whatever pipeline happens to be currently attached; it might have an entirely + // different notion of what to expect in the context object) + if (frameContext.generatingPipeline != null) { + frameContext.generatingPipeline.onDrawFrame(canvas, onscreenWidth, onscreenHeight, scaleBmpPxToCanvasPx, canvasDensityScale, frameContext.userContext) + } + } +} + diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/gui/component/ImageX.java b/Vision/src/main/java/org/deltacv/vision/external/gui/component/ImageX.java similarity index 53% rename from Vision/src/main/java/io/github/deltacv/vision/external/gui/component/ImageX.java rename to Vision/src/main/java/org/deltacv/vision/external/gui/component/ImageX.java index 0eb4b7d9..dbca8153 100644 --- a/Vision/src/main/java/io/github/deltacv/vision/external/gui/component/ImageX.java +++ b/Vision/src/main/java/org/deltacv/vision/external/gui/component/ImageX.java @@ -1,30 +1,12 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.external.gui.component; - -import io.github.deltacv.vision.external.gui.util.ImgUtil; -import io.github.deltacv.vision.external.util.CvUtil; +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.gui.component; + +import org.deltacv.vision.external.gui.util.ImgUtil; +import org.deltacv.vision.external.util.CvUtil; import org.opencv.core.Mat; import javax.swing.*; @@ -85,3 +67,4 @@ public void setSize(Dimension dimension) { } } + diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/gui/SkiaPanel.kt b/Vision/src/main/java/org/deltacv/vision/external/gui/gui/SkiaPanel.kt similarity index 82% rename from Vision/src/main/java/io/github/deltacv/vision/external/gui/SkiaPanel.kt rename to Vision/src/main/java/org/deltacv/vision/external/gui/gui/SkiaPanel.kt index f5fad12d..e79fd114 100644 --- a/Vision/src/main/java/io/github/deltacv/vision/external/gui/SkiaPanel.kt +++ b/Vision/src/main/java/org/deltacv/vision/external/gui/gui/SkiaPanel.kt @@ -1,4 +1,9 @@ -package io.github.deltacv.vision.external.gui +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.gui import org.jetbrains.skiko.ClipComponent import org.jetbrains.skiko.SkiaLayer @@ -31,4 +36,4 @@ class SkiaPanel(private val layer: SkiaLayer) : JLayeredPane() { layer.dispose() super.removeNotify() } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/gui/SwingOpenCvViewport.kt b/Vision/src/main/java/org/deltacv/vision/external/gui/gui/SwingOpenCvViewport.kt similarity index 79% rename from Vision/src/main/java/io/github/deltacv/vision/external/gui/SwingOpenCvViewport.kt rename to Vision/src/main/java/org/deltacv/vision/external/gui/gui/SwingOpenCvViewport.kt index 300e15af..48e3b6cc 100644 --- a/Vision/src/main/java/io/github/deltacv/vision/external/gui/SwingOpenCvViewport.kt +++ b/Vision/src/main/java/org/deltacv/vision/external/gui/gui/SwingOpenCvViewport.kt @@ -1,30 +1,13 @@ /* - * Copyright (c) 2023 OpenFTC Team & EOCV-Sim implementation by Sebastian Erives - * - * 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. - * + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. */ -package io.github.deltacv.vision.external.gui + +package org.deltacv.vision.external.gui import android.graphics.Bitmap import android.graphics.Canvas -import io.github.deltacv.common.image.MatPoster +import org.deltacv.common.image.MatPoster import org.firstinspires.ftc.robotcore.internal.collections.EvictingBlockingQueue import org.jetbrains.skia.Color import org.jetbrains.skiko.SkiaLayer @@ -45,7 +28,10 @@ import java.util.concurrent.TimeUnit import javax.swing.JComponent import javax.swing.SwingUtilities -class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Vision") : OpenCvViewport, MatPoster { +class SwingOpenCvViewport( + private val size: Size, + fpsMeterDescriptor: String = "deltacv Vision" +) : OpenCvViewport, MatPoster { private val syncObj = Any() @@ -57,8 +43,7 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi private val needToDeactivateRegardlessOfUser = false private var surfaceExistsAndIsReady = false - @Volatile - private var useGpuCanvas = false + private var isInitialized = false var dark = false @@ -68,7 +53,8 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi PAUSED } - private val visionPreviewFrameQueue = EvictingBlockingQueue(ArrayBlockingQueue(VISION_PREVIEW_FRAME_QUEUE_CAPACITY + 1)) + private val visionPreviewFrameQueue = + EvictingBlockingQueue(ArrayBlockingQueue(VISION_PREVIEW_FRAME_QUEUE_CAPACITY + 1)) private var framebufferRecycler: MatRecycler? = null @Volatile @@ -84,7 +70,12 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi private var renderHook: RenderHook? = null - init { + fun init() { + if(isInitialized) { + logger.warn("init() called on SwingOpenCvViewport, but it was already initialized! Ignoring redundant call.") + return + } + visionPreviewFrameQueue.setEvictAction { value: MatRecycler.RecyclableMat? -> /* * If a Mat is evicted from the queue, we need @@ -93,11 +84,11 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi framebufferRecycler!!.returnMat(value) } - skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object: SkikoRenderDelegate { + skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate { override fun onRender(canvas: org.jetbrains.skia.Canvas, width: Int, height: Int, nanoTime: Long) { renderCanvas(Canvas(canvas, width, height)) - if(outputPosters.isNotEmpty()) { + if (outputPosters.isNotEmpty()) { synchronized(outputPosters) { skiaLayer.screenshot().use { bmp -> framebufferRecycler?.takeMatOrNull().let { mat -> @@ -115,6 +106,9 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi } }) + + isInitialized = true + setSize(size.width.toInt(), size.height.toInt()) } @@ -162,6 +156,7 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi } override fun setFpsMeterEnabled(enabled: Boolean) {} + override fun resume() { synchronized(syncObj) { userRequestedPause = false @@ -197,6 +192,11 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi } } + fun dispose() { + deactivate() + skiaLayer.dispose() + } + override fun setOptimizedViewRotation(rotation: OptimizedRotation) {} override fun notifyStatistics(fps: Float, pipelineMs: Int, overheadMs: Int) { renderer.notifyStatistics(fps, pipelineMs, overheadMs) @@ -206,12 +206,6 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi override fun post(mat: Mat, userContext: Any) { synchronized(syncObj) { - //did they give us null? - requireNotNull(mat) { - //ugh, they did - "cannot post null mat!" - } - //Are we actually rendering to the display right now? If not, //no need to waste time doing a memcpy if (internalRenderingState == RenderingState.ACTIVE) { @@ -240,6 +234,10 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi * Called with syncObj held */ fun checkState() { + if(!isInitialized) { + throw IllegalStateException("checkState() called before SwingOpenCvViewport was initialized! Call init() first.") + } + /* * If the surface isn't ready, don't do anything */ @@ -276,7 +274,8 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi /* * We only need to start the render thread if it's * stopped. - */if (internalRenderingState == RenderingState.STOPPED) { + */ + if (internalRenderingState == RenderingState.STOPPED) { logger.info("CheckState(): activating viewport") internalRenderingState = RenderingState.PAUSED internalRenderingState = if (userRequestedPause) { @@ -290,7 +289,8 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi } if (internalRenderingState != RenderingState.STOPPED) { if (userRequestedPause && internalRenderingState != RenderingState.PAUSED - || !userRequestedPause && internalRenderingState != RenderingState.ACTIVE) { + || !userRequestedPause && internalRenderingState != RenderingState.ACTIVE + ) { internalRenderingState = if (userRequestedPause) { logger.info("CheckState(): pausing viewport") RenderingState.PAUSED @@ -312,12 +312,12 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi private lateinit var lastFrame: MatRecycler.RecyclableMat private fun renderCanvas(canvas: Canvas) { - if(!::lastFrame.isInitialized) { + if (!::lastFrame.isInitialized) { lastFrame = framebufferRecycler!!.takeMatOrNull() } synchronized(canvasLock) { - if(dark) { + if (dark) { canvas.drawColor(Color.BLACK) } else { canvas.drawColor(Color.WHITE) @@ -355,11 +355,7 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi * destroyed, calls checkState(), which *SHOULD* block until we die. This * works most of the time, but not always? We don't yet understand... */ - if (canvas != null) { - renderer.render(mat, canvas, renderHook, mat.context) - } else { - logger.info("Canvas was null") - } + renderer.render(mat, canvas, renderHook, mat.context) //We're done with that Mat object; return it to the Mat recycler so it can be used again later if (mat !== lastFrame) { @@ -370,17 +366,7 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi RenderingState.PAUSED -> { if (shouldPaintOrange) { shouldPaintOrange = false - - /* - * For some reason, the canvas will very occasionally be null upon closing. - * Stack Overflow seems to suggest this means the canvas has been destroyed. - * However, surfaceDestroyed(), which is called right before the surface is - * destroyed, calls checkState(), which *SHOULD* block until we die. This - * works most of the time, but not always? We don't yet understand... - */ - if (canvas != null) { - renderer.renderPaused(canvas) - } + renderer.renderPaused(canvas) } } @@ -399,6 +385,7 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi override fun setRenderingPolicy(policy: ViewportRenderingPolicy) {} override fun setRenderHook(renderHook: RenderHook) { + logger.debug("setRenderHook(): ${renderHook::class.simpleName}") this.renderHook = renderHook } @@ -410,6 +397,7 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi companion object { private const val VISION_PREVIEW_FRAME_QUEUE_CAPACITY = 2 - private const val FRAMEBUFFER_RECYCLER_CAPACITY = VISION_PREVIEW_FRAME_QUEUE_CAPACITY + 4 //So that the evicting queue can be full, and the render thread has one checked out (+1) and post() can still take one (+1). + private const val FRAMEBUFFER_RECYCLER_CAPACITY = + VISION_PREVIEW_FRAME_QUEUE_CAPACITY + 4 //So that the evicting queue can be full, and the render thread has one checked out (+1) and post() can still take one (+1). } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/gui/util/ImgUtil.java b/Vision/src/main/java/org/deltacv/vision/external/gui/util/ImgUtil.java similarity index 79% rename from Vision/src/main/java/io/github/deltacv/vision/external/gui/util/ImgUtil.java rename to Vision/src/main/java/org/deltacv/vision/external/gui/util/ImgUtil.java index 9d146415..21053c61 100644 --- a/Vision/src/main/java/io/github/deltacv/vision/external/gui/util/ImgUtil.java +++ b/Vision/src/main/java/org/deltacv/vision/external/gui/util/ImgUtil.java @@ -1,4 +1,9 @@ -package io.github.deltacv.vision.external.gui.util; +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.gui.util; import javax.swing.*; import java.awt.*; @@ -26,3 +31,4 @@ public static ImageIcon scaleImage(ImageIcon icon, int w, int h) { } } + diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/source/CameraControlMap.java b/Vision/src/main/java/org/deltacv/vision/external/source/CameraControlMap.java similarity index 57% rename from Vision/src/main/java/io/github/deltacv/vision/external/source/CameraControlMap.java rename to Vision/src/main/java/org/deltacv/vision/external/source/CameraControlMap.java index c7cbdc3e..74e63a1d 100644 --- a/Vision/src/main/java/io/github/deltacv/vision/external/source/CameraControlMap.java +++ b/Vision/src/main/java/org/deltacv/vision/external/source/CameraControlMap.java @@ -1,4 +1,9 @@ -package io.github.deltacv.vision.external.source; +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.source; import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.CameraControl; @@ -6,4 +11,4 @@ public interface CameraControlMap { T get(Class classType); -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/deltacv/vision/external/source/FrameReceiver.java b/Vision/src/main/java/org/deltacv/vision/external/source/FrameReceiver.java new file mode 100644 index 00000000..8df08f2c --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/external/source/FrameReceiver.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.source; + +import org.opencv.core.Mat; + +public interface FrameReceiver { + default void onFrameStart() {} + void consume(Mat frame, long timestamp); + + void stop(); +} + diff --git a/Vision/src/main/java/org/deltacv/vision/external/source/ThreadVisionSourceProvider.java b/Vision/src/main/java/org/deltacv/vision/external/source/ThreadVisionSourceProvider.java new file mode 100644 index 00000000..20476f9e --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/external/source/ThreadVisionSourceProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.source; + +public final class ThreadVisionSourceProvider { + + private ThreadVisionSourceProvider() {} // No instantiation + + private static final ThreadLocal provider = new ThreadLocal<>(); + + public static void register(VisionSourceProvider provider) { + ThreadVisionSourceProvider.provider.set(provider); + } + + public static VisionSourceProvider getCurrentProvider() { + return provider.get(); + } + + public static VisionSource get(String name) { + return getCurrentProvider().get(name); + } + +} + diff --git a/Vision/src/main/java/org/deltacv/vision/external/source/ViewportVisionSourceProvider.java b/Vision/src/main/java/org/deltacv/vision/external/source/ViewportVisionSourceProvider.java new file mode 100644 index 00000000..77350f10 --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/external/source/ViewportVisionSourceProvider.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.source; + +import org.openftc.easyopencv.OpenCvViewport; + +public interface ViewportVisionSourceProvider extends VisionSourceProvider { + OpenCvViewport viewport(); +} + diff --git a/Vision/src/main/java/org/deltacv/vision/external/source/VisionSource.java b/Vision/src/main/java/org/deltacv/vision/external/source/VisionSource.java new file mode 100644 index 00000000..5ca329a0 --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/external/source/VisionSource.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.source; + +import org.opencv.core.Size; + +public interface VisionSource { + + int check(); + + CameraControlMap getControlMap(); + + boolean start(Size size); + + boolean attach(FrameReceiver sourced); + boolean remove(FrameReceiver sourced); + + boolean stop(); + + boolean close(); + +} + diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/source/VisionSourceBase.java b/Vision/src/main/java/org/deltacv/vision/external/source/VisionSourceBase.java similarity index 76% rename from Vision/src/main/java/io/github/deltacv/vision/external/source/VisionSourceBase.java rename to Vision/src/main/java/org/deltacv/vision/external/source/VisionSourceBase.java index b91af887..cae9310c 100644 --- a/Vision/src/main/java/io/github/deltacv/vision/external/source/VisionSourceBase.java +++ b/Vision/src/main/java/org/deltacv/vision/external/source/VisionSourceBase.java @@ -1,30 +1,12 @@ /* * Copyright (c) 2023 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ -package io.github.deltacv.vision.external.source; +package org.deltacv.vision.external.source; -import io.github.deltacv.vision.external.util.ThrowableHandler; -import io.github.deltacv.vision.external.util.Timestamped; +import org.deltacv.vision.external.util.ThrowableHandler; +import org.deltacv.vision.external.util.Timestamped; import org.opencv.core.Mat; import org.opencv.core.Size; import org.slf4j.Logger; @@ -162,3 +144,4 @@ public void run() { } } + diff --git a/Vision/src/main/java/org/deltacv/vision/external/source/VisionSourceProvider.java b/Vision/src/main/java/org/deltacv/vision/external/source/VisionSourceProvider.java new file mode 100644 index 00000000..cdca635c --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/external/source/VisionSourceProvider.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.source; + +public interface VisionSourceProvider { + + VisionSource get(String name); + +} diff --git a/Vision/src/main/java/org/deltacv/vision/external/util/extension/CvExt.kt b/Vision/src/main/java/org/deltacv/vision/external/util/extension/CvExt.kt new file mode 100644 index 00000000..4cd39d48 --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/external/util/extension/CvExt.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +@file:JvmName("CvExt") + +package org.deltacv.vision.external.util.extension + +import com.qualcomm.robotcore.util.Range +import org.opencv.core.CvType +import org.opencv.core.Mat +import org.opencv.core.Scalar +import org.opencv.core.Size +import org.opencv.imgproc.Imgproc + +fun Scalar.cvtColor(code: Int): Scalar { + val mat = Mat(5, 5, CvType.CV_8UC3); + mat.setTo(this) + Imgproc.cvtColor(mat, mat, code); + + val newScalar = Scalar(mat.get(1, 1)) + mat.release() + + return newScalar +} + +fun Size.aspectRatio() = height / width +fun Mat.aspectRatio() = size().aspectRatio() + +fun Size.clipTo(size: Size): Size { + width = Range.clip(width, 0.0, size.width) + height = Range.clip(height, 0.0, size.height) + return this +} diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/util/CvUtil.java b/Vision/src/main/java/org/deltacv/vision/external/util/util/CvUtil.java similarity index 78% rename from Vision/src/main/java/io/github/deltacv/vision/external/util/CvUtil.java rename to Vision/src/main/java/org/deltacv/vision/external/util/util/CvUtil.java index f0d11368..5e6be89e 100644 --- a/Vision/src/main/java/io/github/deltacv/vision/external/util/CvUtil.java +++ b/Vision/src/main/java/org/deltacv/vision/external/util/util/CvUtil.java @@ -1,29 +1,11 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.external.util; - -import io.github.deltacv.vision.external.util.extension.CvExt; +/* + * Copyright (c) 2021 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.util; + +import org.deltacv.vision.external.util.extension.CvExt; import org.opencv.core.Mat; import org.opencv.core.MatOfByte; import org.opencv.core.Size; @@ -202,3 +184,4 @@ public static Size scaleToFit(Size currentSize, Size targetSize) { } } + diff --git a/Vision/src/main/java/org/deltacv/vision/external/util/util/FrameQueue.java b/Vision/src/main/java/org/deltacv/vision/external/util/util/FrameQueue.java new file mode 100644 index 00000000..36631821 --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/external/util/util/FrameQueue.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.util; + +import org.firstinspires.ftc.robotcore.internal.collections.EvictingBlockingQueue; +import org.opencv.core.Mat; +import org.openftc.easyopencv.MatRecycler; + +import java.util.concurrent.ArrayBlockingQueue; + +public class FrameQueue { + + private final EvictingBlockingQueue viewportQueue; + private final MatRecycler matRecycler; + + public FrameQueue(int maxQueueItems) { + viewportQueue = new EvictingBlockingQueue<>(new ArrayBlockingQueue<>(maxQueueItems)); + matRecycler = new MatRecycler(maxQueueItems + 2); + + viewportQueue.setEvictAction(this::evict); + } + + public Mat takeMatAndPost() { + Mat mat = matRecycler.takeMatOrNull(); + viewportQueue.add(mat); + + return mat; + } + + public Mat takeMat() { + return matRecycler.takeMatOrNull(); + } + + public Mat poll() { + Mat mat = viewportQueue.poll(); + + if(mat != null) { + evict(mat); + } + + return mat; + } + + private void evict(Mat mat) { + if(mat instanceof MatRecycler.RecyclableMat) { + matRecycler.returnMat((MatRecycler.RecyclableMat) mat); + } + } + +} + diff --git a/Vision/src/main/java/org/deltacv/vision/external/util/util/ThrowableHandler.java b/Vision/src/main/java/org/deltacv/vision/external/util/util/ThrowableHandler.java new file mode 100644 index 00000000..811f114a --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/external/util/util/ThrowableHandler.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.util; + +public interface ThrowableHandler { + void handle(Throwable e); +} + diff --git a/Vision/src/main/java/org/deltacv/vision/external/util/util/Timestamped.java b/Vision/src/main/java/org/deltacv/vision/external/util/util/Timestamped.java new file mode 100644 index 00000000..c7f2e174 --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/external/util/util/Timestamped.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.external.util; + +public class Timestamped { + + private final T value; + private final long timestamp; + + public Timestamped(T value, long timestamp) { + this.value = value; + this.timestamp = timestamp; + } + + public T getValue() { + return value; + } + + public long getTimestamp() { + return timestamp; + } + +} + diff --git a/Vision/src/main/java/org/deltacv/vision/internal/opmode/Enums.kt b/Vision/src/main/java/org/deltacv/vision/internal/opmode/Enums.kt new file mode 100644 index 00000000..d21deeff --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/internal/opmode/Enums.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.internal.opmode + +enum class OpModeNotification { INIT, START, STOP, NOTHING } +enum class OpModeState { SELECTED, INIT, START, STOP, STOPPED } diff --git a/Vision/src/main/java/io/github/deltacv/vision/internal/opmode/OpModeNotifier.kt b/Vision/src/main/java/org/deltacv/vision/internal/opmode/OpModeNotifier.kt similarity index 59% rename from Vision/src/main/java/io/github/deltacv/vision/internal/opmode/OpModeNotifier.kt rename to Vision/src/main/java/org/deltacv/vision/internal/opmode/OpModeNotifier.kt index 9a13be42..cc30b7ea 100644 --- a/Vision/src/main/java/io/github/deltacv/vision/internal/opmode/OpModeNotifier.kt +++ b/Vision/src/main/java/org/deltacv/vision/internal/opmode/OpModeNotifier.kt @@ -1,27 +1,9 @@ -/* - * Copyright (c) 2023 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.vision.internal.opmode +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.internal.opmode import com.github.serivesmejia.eocvsim.util.event.EventHandler import org.firstinspires.ftc.robotcore.internal.collections.EvictingBlockingQueue @@ -88,4 +70,4 @@ class OpModeNotifier(maxNotificationsQueueSize: Int = 100) { fun pollException(): Throwable? = exceptionQueue.poll() -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/deltacv/vision/internal/opmode/RedirectToOpModeThrowableHandler.kt b/Vision/src/main/java/org/deltacv/vision/internal/opmode/RedirectToOpModeThrowableHandler.kt new file mode 100644 index 00000000..137af6c0 --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/internal/opmode/RedirectToOpModeThrowableHandler.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.internal.opmode + +import org.deltacv.vision.external.util.ThrowableHandler + +class RedirectToOpModeThrowableHandler(private val notifier: OpModeNotifier) : ThrowableHandler { + override fun handle(e: Throwable) { + notifier.notify(e) + } +} diff --git a/Vision/src/main/java/org/deltacv/vision/internal/source/ftc/SourcedCameraName.java b/Vision/src/main/java/org/deltacv/vision/internal/source/ftc/SourcedCameraName.java new file mode 100644 index 00000000..a6216271 --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/internal/source/ftc/SourcedCameraName.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.internal.source.ftc; + +import org.deltacv.vision.external.source.VisionSource; +import org.firstinspires.ftc.robotcore.external.hardware.camera.CameraCharacteristics; + +import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; +public abstract class SourcedCameraName implements WebcamName { + + public abstract VisionSource getSource(); + + @Override + public boolean isWebcam() { + return false; + } + + @Override + public boolean isCameraDirection() { + return false; + } + + @Override + public boolean isSwitchable() { + return false; + } + + @Override + public boolean isUnknown() { + return false; + } + + @Override + public CameraCharacteristics getCameraCharacteristics() { + return null; + } + +} + diff --git a/Vision/src/main/java/org/deltacv/vision/internal/source/ftc/SourcedCameraNameImpl.java b/Vision/src/main/java/org/deltacv/vision/internal/source/ftc/SourcedCameraNameImpl.java new file mode 100644 index 00000000..95970a68 --- /dev/null +++ b/Vision/src/main/java/org/deltacv/vision/internal/source/ftc/SourcedCameraNameImpl.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.deltacv.vision.internal.source.ftc; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.qualcomm.robotcore.util.SerialNumber; +import org.deltacv.vision.external.source.VisionSource; +import org.jetbrains.annotations.NotNull; + +public class SourcedCameraNameImpl extends SourcedCameraName { + + private VisionSource source; + + public SourcedCameraNameImpl(VisionSource source) { + this.source = source; + } + + @Override + public VisionSource getSource() { + return source; + } + + @Override + public Manufacturer getManufacturer() { + return null; + } + + @Override + public String getDeviceName() { + return null; + } + + @Override + public String getConnectionInfo() { + return null; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + public void resetDeviceConfigurationForOpMode() { + + } + + @Override + public void close() { + + } + + @NonNull + @NotNull + @Override + public SerialNumber getSerialNumber() { + return null; + } + + @Nullable + @org.jetbrains.annotations.Nullable + @Override + public String getUsbDeviceNameIfAttached() { + return null; + } + + @Override + public boolean isAttached() { + return false; + } +} + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/BuiltinCameraDirection.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/BuiltinCameraDirection.java index a4e85c55..3374b032 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/BuiltinCameraDirection.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/BuiltinCameraDirection.java @@ -1,7 +1,40 @@ +/* +Copyright (c) 2017 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ package org.firstinspires.ftc.robotcore.external.hardware.camera; public enum BuiltinCameraDirection { BACK, FRONT -} \ No newline at end of file +} + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraCharacteristics.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraCharacteristics.java index 3c9d5e8c..d893750d 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraCharacteristics.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraCharacteristics.java @@ -156,4 +156,6 @@ public CameraMode(int androidFormat, Size size, long nsFrameDuration, boolean is * @return all the combinatorial format, size, and fps camera modes supported */ List getAllCameraModes(); -} \ No newline at end of file +} + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraControls.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraControls.java index ebb010d7..471d62e8 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraControls.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraControls.java @@ -43,4 +43,6 @@ public interface CameraControls //---------------------------------------------------------------------------------------------- @Nullable T getControl(Class controlType); -} \ No newline at end of file +} + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraName.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraName.java index c0012ea5..a0a526f6 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraName.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/CameraName.java @@ -81,4 +81,6 @@ public interface CameraName * @return The properties of the given camera. A degenerate empty set of properties is returned on error. */ CameraCharacteristics getCameraCharacteristics(); -} \ No newline at end of file +} + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/WebcamName.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/WebcamName.java index 4bdd4ace..00a831db 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/WebcamName.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/WebcamName.java @@ -60,4 +60,6 @@ public interface WebcamName extends CameraName, HardwareDevice * @return whether this camera currently attached to the robot controller */ boolean isAttached(); -} \ No newline at end of file +} + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/CameraControl.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/CameraControl.java index 2201150a..bbaeea0d 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/CameraControl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/CameraControl.java @@ -39,4 +39,6 @@ are permitted (subject to the limitations in the disclaimer below) provided that @SuppressWarnings("WeakerAccess") public interface CameraControl { -} \ No newline at end of file +} + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/ExposureControl.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/ExposureControl.java index da28eb73..75069ce8 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/ExposureControl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/ExposureControl.java @@ -140,3 +140,6 @@ public static Mode fromId(int id) */ boolean setAePriority(boolean priority); } + + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/FocusControl.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/FocusControl.java index 87c56189..1a2c6e36 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/FocusControl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/FocusControl.java @@ -115,3 +115,6 @@ public static FocusControl.Mode fromId(int index) */ boolean isFocusLengthSupported(); } + + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/GainControl.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/GainControl.java index 2d0b08d3..f73f0131 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/GainControl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/GainControl.java @@ -66,3 +66,5 @@ public interface GainControl extends CameraControl */ boolean setGain(int gain); } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/PtzControl.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/PtzControl.java index d8da2cdc..d70717a5 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/PtzControl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/PtzControl.java @@ -99,4 +99,5 @@ class PanTiltHolder * @return the maximum virtual zoom level of the camera */ int getMaxZoom(); -} \ No newline at end of file +} + diff --git a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/WhiteBalanceControl.java b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/WhiteBalanceControl.java index 565a7210..d46e580c 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/WhiteBalanceControl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/robotcore/external/hardware/camera/controls/WhiteBalanceControl.java @@ -84,3 +84,5 @@ enum Mode /* If you change this order, remember to update jni_devicehandle.cpp! */ boolean setWhiteBalanceTemperature(int temperature); } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/VisionPortal.java b/Vision/src/main/java/org/firstinspires/ftc/vision/VisionPortal.java index 01b9ad4c..b4ee308f 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/VisionPortal.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/VisionPortal.java @@ -38,8 +38,8 @@ import java.util.ArrayList; import java.util.List; -import io.github.deltacv.vision.external.source.ThreadVisionSourceProvider; -import io.github.deltacv.vision.internal.source.ftc.SourcedCameraNameImpl; +import org.deltacv.vision.external.source.ThreadVisionSourceProvider; +import org.deltacv.vision.internal.source.ftc.SourcedCameraNameImpl; import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; import org.firstinspires.ftc.robotcore.external.hardware.camera.CameraName; import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; @@ -470,4 +470,5 @@ public enum CameraState * A closed portal may not be re-opened: if you wish to use the camera again, you must make a new portal */ public abstract void close(); -} \ No newline at end of file +} + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/VisionPortalImpl.java b/Vision/src/main/java/org/firstinspires/ftc/vision/VisionPortalImpl.java index ce1275e7..d50be844 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/VisionPortalImpl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/VisionPortalImpl.java @@ -38,7 +38,7 @@ import com.qualcomm.robotcore.util.RobotLog; -import io.github.deltacv.vision.internal.source.ftc.SourcedCameraName; +import org.deltacv.vision.internal.source.ftc.SourcedCameraName; import org.firstinspires.ftc.robotcore.external.hardware.camera.CameraName; import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.CameraControl; @@ -416,4 +416,5 @@ public void close() camera = null; } } -} \ No newline at end of file +} + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/VisionProcessor.java b/Vision/src/main/java/org/firstinspires/ftc/vision/VisionProcessor.java index 253733b1..56ffda87 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/VisionProcessor.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/VisionProcessor.java @@ -36,4 +36,4 @@ /** * May be attached to a {@link VisionPortal} to run image processing */ -public interface VisionProcessor extends VisionProcessorInternal {} +public interface VisionProcessor extends VisionProcessorInternal {} \ No newline at end of file diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/VisionProcessorInternal.java b/Vision/src/main/java/org/firstinspires/ftc/vision/VisionProcessorInternal.java index 3baf2bc6..10fcbee9 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/VisionProcessorInternal.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/VisionProcessorInternal.java @@ -46,4 +46,5 @@ interface VisionProcessorInternal void init(int width, int height, CameraCalibration calibration); Object processFrame(Mat frame, long captureTimeNanos); void onDrawFrame(Canvas canvas, int onscreenWidth, int onscreenHeight, float scaleBmpPxToCanvasPx, float scaleCanvasDensity, Object userContext); -} \ No newline at end of file +} + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagCanvasAnnotator.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagCanvasAnnotator.java index 8e159b15..8d4770c6 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagCanvasAnnotator.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagCanvasAnnotator.java @@ -292,3 +292,5 @@ void drawTagID(AprilTagDetection detection, Canvas canvas) canvas.restore(); } } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagDetection.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagDetection.java index 3f0bbb8c..464179d5 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagDetection.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagDetection.java @@ -104,3 +104,5 @@ public AprilTagDetection(int id, int hamming, float decisionMargin, Point center this.frameAcquisitionNanoTime = frameAcquisitionNanoTime; } } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagGameDatabase.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagGameDatabase.java index c5500a26..ec475cf2 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagGameDatabase.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagGameDatabase.java @@ -158,3 +158,5 @@ public static AprilTagLibrary getSampleTagLibrary() .build(); } } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagLibrary.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagLibrary.java index 29ef39bb..fcc645c6 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagLibrary.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagLibrary.java @@ -186,3 +186,5 @@ public AprilTagLibrary build() } } } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagMetadata.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagMetadata.java index c1a25bfe..94ce790e 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagMetadata.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagMetadata.java @@ -82,3 +82,5 @@ public AprilTagMetadata(int id, String name, double tagsize, DistanceUnit distan this.distanceUnit = distanceUnit; } } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagPoseFtc.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagPoseFtc.java index d9049246..008b3f97 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagPoseFtc.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagPoseFtc.java @@ -113,4 +113,5 @@ public AprilTagPoseFtc(double x, double y, double z, double yaw, double pitch, this.bearing = bearing; this.elevation = elevation; } -} \ No newline at end of file +} + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagPoseRaw.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagPoseRaw.java index f1fc3a94..101ff46b 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagPoseRaw.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagPoseRaw.java @@ -64,4 +64,5 @@ public AprilTagPoseRaw(double x, double y, double z, MatrixF R) this.z = z; this.R = R; } -} \ No newline at end of file +} + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessor.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessor.java index 9ed97a89..0404e80d 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessor.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessor.java @@ -325,3 +325,5 @@ public enum PoseSolver public abstract ArrayList getFreshDetections(); } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessorImpl.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessorImpl.java index 34f44c10..ab360334 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessorImpl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessorImpl.java @@ -608,4 +608,5 @@ public Pose(Mat rvec, Mat tvec) this.tvec = tvec; } } -} \ No newline at end of file +} + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/Circle.java b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/Circle.java index d02aa914..bcaeac4c 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/Circle.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/Circle.java @@ -97,3 +97,5 @@ public float getY() { return y; } } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorBlobLocatorProcessor.java b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorBlobLocatorProcessor.java index b4328196..6d8420ea 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorBlobLocatorProcessor.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorBlobLocatorProcessor.java @@ -642,3 +642,5 @@ public int compare(Blob c1, Blob c2) } } } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorBlobLocatorProcessorImpl.java b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorBlobLocatorProcessorImpl.java index 3466e20b..d4eb1ef1 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorBlobLocatorProcessorImpl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorBlobLocatorProcessorImpl.java @@ -514,3 +514,4 @@ public Circle getCircle() } } } + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorRange.java b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorRange.java index 5aa95fcc..c741f79f 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorRange.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorRange.java @@ -96,3 +96,5 @@ public ColorRange(ColorSpace colorSpace, Scalar min, Scalar max) this.max = max; } } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorSpace.java b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorSpace.java index e21301ca..38adaf56 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorSpace.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ColorSpace.java @@ -42,3 +42,5 @@ public enum ColorSpace HSV, RGB, } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ImageRegion.java b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ImageRegion.java index 83a65a13..11b6fc01 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ImageRegion.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/ImageRegion.java @@ -158,3 +158,5 @@ protected Rect asOpenCvRect(int imageWidth, int imageHeight) return rect; } } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/PredominantColorProcessor.java b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/PredominantColorProcessor.java index 6596d42a..229835da 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/PredominantColorProcessor.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/PredominantColorProcessor.java @@ -168,3 +168,5 @@ public static Swatch valueOf(int swatch) } } } + + diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/PredominantColorProcessorImpl.java b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/PredominantColorProcessorImpl.java index 01bf5568..c78904fd 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/PredominantColorProcessorImpl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/opencv/PredominantColorProcessorImpl.java @@ -260,3 +260,5 @@ byte[] yCrCb2Rgb(byte[] yCrCb) return rgb; } } + + diff --git a/Vision/src/main/java/org/opencv/android/Utils.java b/Vision/src/main/java/org/opencv/android/Utils.java index 991aae80..7993773b 100644 --- a/Vision/src/main/java/org/opencv/android/Utils.java +++ b/Vision/src/main/java/org/opencv/android/Utils.java @@ -95,7 +95,7 @@ private static void nBitmapToMat2(Bitmap b, Mat mat, boolean unPremultiplyAlpha) } } - private static ThreadLocal> threadLocalm2bReusableBuffers = ThreadLocal.withInitial(WeakHashMap::new); + private static final ThreadLocal> m2bReusableBuffers = ThreadLocal.withInitial(WeakHashMap::new); private static void nMatToBitmap2(Mat src, Bitmap b, boolean premultiplyAlpha) { Mat tmp; @@ -127,7 +127,7 @@ private static void nMatToBitmap2(Mat src, Bitmap b, boolean premultiplyAlpha) { int size = tmp.rows() * tmp.cols() * tmp.channels(); - WeakHashMap m2bArrays = threadLocalm2bReusableBuffers.get(); + WeakHashMap m2bArrays = m2bReusableBuffers.get(); byte[] m2bData = m2bArrays.get(size); @@ -146,3 +146,4 @@ private static void nMatToBitmap2(Mat src, Bitmap b, boolean premultiplyAlpha) { tmp.release(); } } + diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/VirtualReflectContext.kt b/Vision/src/main/java/org/openftc/apriltag/AprilTagDetection.java similarity index 77% rename from Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/VirtualReflectContext.kt rename to Vision/src/main/java/org/openftc/apriltag/AprilTagDetection.java index 7583c027..e19e7ffc 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/virtualreflect/VirtualReflectContext.kt +++ b/Vision/src/main/java/org/openftc/apriltag/AprilTagDetection.java @@ -1,37 +1,34 @@ -/* - * Copyright (c) 2022 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.virtualreflect - -interface VirtualReflectContext { - - val name: String - val simpleName: String - - val fields: Array - - fun getField(name: String): VirtualField? - - fun getLabeledField(label: String): VirtualField? - +/* + * Copyright (c) 2021 OpenFTC Team + * + * 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. + */ + +package org.openftc.apriltag; + +import org.opencv.core.Point; + +public class AprilTagDetection +{ + public int id; + public int hamming; + public float decisionMargin; + public Point center; + public Point[] corners; + public AprilTagPose pose; } \ No newline at end of file diff --git a/Vision/src/main/java/org/openftc/apriltag/AprilTagDetectionCache.java b/Vision/src/main/java/org/openftc/apriltag/AprilTagDetectionCache.java new file mode 100644 index 00000000..d1dc1cd2 --- /dev/null +++ b/Vision/src/main/java/org/openftc/apriltag/AprilTagDetectionCache.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2026 Sebastian Erives + * Licensed under the MIT License. + */ + +package org.openftc.apriltag; + +import org.wpilib.vision.apriltag.AprilTagDetection; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Internal cache for AprilTag detection arrays to simulate pointer-based returns + */ +class AprilTagDetectionCache +{ + private static final Map cache = new HashMap<>(); + private static final AtomicLong pointerCounter = new AtomicLong(1); + + /** + * Cache a detection array and return a fake pointer ID + */ + static long cacheDetections(AprilTagDetection[] detections) { + long fakePtr = pointerCounter.getAndIncrement(); + cache.put(fakePtr, detections); + return fakePtr; + } + + /** + * Retrieve cached detections by fake pointer ID + */ + static AprilTagDetection[] getDetections(long fakePtr) { + return cache.get(fakePtr); + } + + /** + * Remove cached detections by fake pointer ID + */ + static void freeDetections(long fakePtr) { + cache.remove(fakePtr); + } +} + diff --git a/Vision/src/main/java/org/openftc/apriltag/AprilTagDetectorJNI.java b/Vision/src/main/java/org/openftc/apriltag/AprilTagDetectorJNI.java new file mode 100644 index 00000000..ae3f5664 --- /dev/null +++ b/Vision/src/main/java/org/openftc/apriltag/AprilTagDetectorJNI.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2021 OpenFTC Team + * + * 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. + */ + +package org.openftc.apriltag; + +import org.opencv.core.Mat; +import org.wpilib.vision.apriltag.AprilTagDetector; +import org.wpilib.vision.apriltag.jni.AprilTagJNI; + +import java.util.ArrayList; + +public class AprilTagDetectorJNI +{ + public enum TagFamily + { + TAG_36h11("tag36h11"), + TAG_25h9("tag25h9"), + TAG_16h5("tag16h5"), + TAG_standard41h12("tagStandard41h12"); + + public final String string; + + TagFamily(String string) + { + this.string = string; + } + } + + /** + * Create a new AprilTag detector + * @param tagFamily the tag family the detector will use + * @param decimate the decimation factor + * @param threads the number of threads to use + * @return a native pointer to the newly created detector + */ + public static long createApriltagDetector(String tagFamily, float decimate, int threads) { + long handle = AprilTagJNI.createDetector(); + AprilTagJNI.addFamily(handle, tagFamily, 2); + + AprilTagDetector.Config config = new AprilTagDetector.Config(); + config.quadDecimate = decimate; + config.numThreads = threads; + AprilTagJNI.setDetectorConfig(handle, config); + + return handle; + } + + /** + * Set the decimation parameter of a previously created AprilTag detector + * @param ptrDetector native pointer to an AprilTag detector, obtained from {@link #createApriltagDetector(String, float, int)} + * @param decimate the new decimation value + */ + public static void setApriltagDetectorDecimation(long ptrDetector, float decimate) { + AprilTagDetector.Config config = AprilTagJNI.getDetectorConfig(ptrDetector); + config.quadDecimate = decimate; + AprilTagJNI.setDetectorConfig(ptrDetector, config); + } + + /** + * Run an AprilTag detector on a greyscale image + * @param ptrDetector native pointer to an AprilTag detector, obtained from {@link #createApriltagDetector(String, float, int)} + * @param ptrGreyscaleBuf native pointer to a greyscale image buffer + * @param width the width of the greyscale buffer + * @param height the height of the greyscale buffer + * @return a native pointer to a list of detections, or 0 if nothing was detected. + * If non-zero, must be freed with {@link ApriltagDetectionJNI#freeDetectionList(long)} + */ + public static long runApriltagDetector(long ptrDetector, long ptrGreyscaleBuf, int width, int height) { + org.wpilib.vision.apriltag.AprilTagDetection[] detections = AprilTagJNI.detect(ptrDetector, width, height, width, ptrGreyscaleBuf); + if (detections == null || detections.length == 0) { + return 0; + } + return AprilTagDetectionCache.cacheDetections(detections); + } + + /** + * Run an AprilTag detector on a greyscale image + * @param ptrDetector native pointer to an AprilTag detector, obtained from {@link #createApriltagDetector(String, float, int)} + * @param grey a greyscale OpenCV Mat + * @param tagSize size of the tag in meters + * @param fx lens intrinsics fx + * @param fy lens intrinsics fy + * @param cx lens intrinsics cx + * @param cy lens intrinsics cy + * @return an ArrayList of AprilTag detections. No care need be taken to release native detections. + */ + public static ArrayList runAprilTagDetectorSimple(long ptrDetector, Mat grey, double tagSize, double fx, double fy, double cx, double cy) + { + ArrayList detections = new ArrayList<>(); + long ptrDetectionArray = runApriltagDetector(ptrDetector, grey.dataAddr(), grey.width(), grey.height()); + if(ptrDetectionArray != 0) + { + detections = ApriltagDetectionJNI.getDetections(ptrDetectionArray, tagSize, fx, fy, cx, cy); + ApriltagDetectionJNI.freeDetectionList(ptrDetectionArray); + } + + return detections; + } + + /** + * Release an AprilTag detector + * @param ptr native pointer to an AprilTag detector, obtained from {@link #createApriltagDetector(String, float, int)} + */ + public static void releaseApriltagDetector(long ptr) { + AprilTagJNI.destroyDetector(ptr); + } +} \ No newline at end of file diff --git a/Vision/src/main/java/io/github/deltacv/eocvsim/stream/ImageStreamer.kt b/Vision/src/main/java/org/openftc/apriltag/AprilTagPose.java similarity index 79% rename from Vision/src/main/java/io/github/deltacv/eocvsim/stream/ImageStreamer.kt rename to Vision/src/main/java/org/openftc/apriltag/AprilTagPose.java index 74fd764a..de6be1cd 100644 --- a/Vision/src/main/java/io/github/deltacv/eocvsim/stream/ImageStreamer.kt +++ b/Vision/src/main/java/org/openftc/apriltag/AprilTagPose.java @@ -1,31 +1,32 @@ - -/* - * Copyright (c) 2026 Sebastian Erives - * - * 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. - * - */ - -package io.github.deltacv.eocvsim.stream - -import org.opencv.core.Mat - -interface ImageStreamer { - fun sendFrame(id: Int, image: Mat, cvtCode: Int? = null) +/* + * Copyright (c) 2021 OpenFTC Team + * + * 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. + */ + +package org.openftc.apriltag; + +import org.firstinspires.ftc.robotcore.external.matrices.MatrixF; + +public class AprilTagPose +{ + public double x; + public double y; + public double z; + public MatrixF R; } \ No newline at end of file diff --git a/Vision/src/main/java/org/openftc/apriltag/ApriltagDetectionJNI.java b/Vision/src/main/java/org/openftc/apriltag/ApriltagDetectionJNI.java new file mode 100644 index 00000000..56f6914c --- /dev/null +++ b/Vision/src/main/java/org/openftc/apriltag/ApriltagDetectionJNI.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2021 OpenFTC Team + * + * 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. + */ + +package org.openftc.apriltag; + +import org.wpilib.math.geometry.Rotation3d; +import org.wpilib.math.geometry.Transform3d; +import org.firstinspires.ftc.robotcore.external.matrices.GeneralMatrixF; +import org.opencv.core.Point; +import org.wpilib.vision.apriltag.AprilTagPoseEstimate; +import org.wpilib.vision.apriltag.AprilTagPoseEstimator; + +import java.util.ArrayList; + +public class ApriltagDetectionJNI +{ + /** + * Get the tag ID of a detection + * @param ptr pointer to a detection, obtained from {@link #getDetectionPointers(long)} + * @return the tag ID + */ + public static int getId(long ptr) + { + return getSingleDetection(ptr).getId(); + } + + /** + * Get the hamming of a detection + * @param ptr pointer to a detection, obtained from {@link #getDetectionPointers(long)} + * @return the hamming value + */ + public static int getHamming(long ptr) + { + return getSingleDetection(ptr).getHamming(); + } + + /** + * Get the decision margin of a detection + * @param ptr pointer to a detection, obtained from {@link #getDetectionPointers(long)} + * @return the decision margin of the detection + */ + public static float getDecisionMargin(long ptr) + { + return getSingleDetection(ptr).getDecisionMargin(); + } + + /** + * Get the centerpoint of a detection + * @param ptr pointer to a detection, obtained from {@link #getDetectionPointers(long)} + * @return the centerpoint of the detection + */ + public static double[] getCenterpoint(long ptr) + { + org.wpilib.vision.apriltag.AprilTagDetection det = getSingleDetection(ptr); + return new double[]{ det.getCenterX(), det.getCenterY() }; + } + + /** + * Get the corners of a detection + * @param ptr pointer to a detection, obtained from {@link #getDetectionPointers(long)} + * @return the corners of the detection + */ + public static double[][] getCorners(long ptr) + { + org.wpilib.vision.apriltag.AprilTagDetection det = getSingleDetection(ptr); + + // WPILib gives corners as a flat double[] of length 8: [x0,y0, x1,y1, x2,y2, x3,y3] + double[] flat = det.getCorners(); + + double[][] corners = new double[4][2]; + for (int i = 0; i < 4; i++) + { + corners[i][0] = flat[i * 2]; + corners[i][1] = flat[i * 2 + 1]; + } + return corners; + } + + /** + * Get the pose estimate for a detection + * @param ptr pointer to a detection, obtained from {@link #getDetectionPointers(long)} + * @param tagSize size of the tag in meters + * @param fx lens intrinsics fx + * @param fy lens intrinsics fy + * @param cx lens intrinsics cx + * @param cy lens intrinsics cy + * @return pose estimate for the detection. 0-2 are translation XYZ, 3-5 are yaw, pitch, roll + */ + public static double[] getPoseEstimate(long ptr, double tagSize, double fx, double fy, double cx, double cy) + { + org.wpilib.vision.apriltag.AprilTagDetection det = getSingleDetection(ptr); + + AprilTagPoseEstimator estimator = new AprilTagPoseEstimator( + new AprilTagPoseEstimator.Config(tagSize, fx, fy, cx, cy)); + + AprilTagPoseEstimate result = estimator.estimateOrthogonalIteration(det, 50); + + Transform3d pose = (result.error1 <= result.error2) ? result.pose1 : result.pose2; + + double tx = pose.getTranslation().getX(); + double ty = pose.getTranslation().getY(); + double tz = pose.getTranslation().getZ(); + + Rotation3d rot = pose.getRotation(); + double[] rotMatrix = quaternionToRotationMatrix( + rot.getQuaternion().getW(), + rot.getQuaternion().getX(), + rot.getQuaternion().getY(), + rot.getQuaternion().getZ() + ); + + double[] out = new double[12]; + out[0] = tx; + out[1] = ty; + out[2] = tz; + System.arraycopy(rotMatrix, 0, out, 3, 9); + return out; + } + + /** + * Get a pointer for each of the detections inside a list returned by {@link AprilTagDetectorJNI#runApriltagDetector(long, long, int, int)} + * @param ptrZarray native pointer from {@link AprilTagDetectorJNI#runApriltagDetector(long, long, int, int)} + * @return an array of native pointers to detections inside the list + */ + public static long[] getDetectionPointers(long ptrZarray) + { + org.wpilib.vision.apriltag.AprilTagDetection[] detections = AprilTagDetectionCache.getDetections(ptrZarray); + if (detections == null) return new long[0]; + + long[] ptrs = new long[detections.length]; + for (int i = 0; i < detections.length; i++) + { + ptrs[i] = AprilTagDetectionCache.cacheDetections(new org.wpilib.vision.apriltag.AprilTagDetection[]{ detections[i] }); + } + return ptrs; + } + + /** + * Frees a list of detections obtained from {@link AprilTagDetectorJNI#runApriltagDetector(long, long, int, int)} + * AND the detections themselves. (Thus, after calling this, any pointers you may have previously obtained + * from {@link #getDetectionPointers(long)} are INVALID) + * @param ptrDetections native pointer to a list of detections + */ + public static void freeDetectionList(long ptrDetections) + { + AprilTagDetectionCache.freeDetections(ptrDetections); + } + + /** + * Creates a nice decoupled java representation of the detections in the native detection list + * @param ptrDetections native pointer from {@link AprilTagDetectorJNI#runApriltagDetector(long, long, int, int)} + * @param tagSize size of the tag in meters + * @param fx lens intrinsics fx + * @param fy lens intrinsics fy + * @param cx lens intrinsics cx + * @param cy lens intrinsics cy + * @return a nice decoupled java representation of the detections in the native detection list + */ + public static ArrayList getDetections(long ptrDetections, double tagSize, double fx, double fy, double cx, double cy) + { + long[] detectionPointers = getDetectionPointers(ptrDetections); + ArrayList detections = new ArrayList<>(detectionPointers.length); + + for (long ptrDetection : detectionPointers) + { + AprilTagDetection detection = new AprilTagDetection(); + detection.id = getId(ptrDetection); + detection.hamming = getHamming(ptrDetection); + detection.decisionMargin = getDecisionMargin(ptrDetection); + double[] center = getCenterpoint(ptrDetection); + detection.center = new Point(center[0], center[1]); + double[][] corners = getCorners(ptrDetection); + + detection.corners = new Point[4]; + for (int p = 0; p < 4; p++) + { + detection.corners[p] = new Point(corners[p][0], corners[p][1]); + } + + detection.pose = new AprilTagPose(); + double[] pose = getPoseEstimate(ptrDetection, tagSize, fx, fy, cx, cy); + detection.pose.x = pose[0]; + detection.pose.y = pose[1]; + detection.pose.z = pose[2]; + + float[] rotMtxVals = new float[3*3]; + + for (int i = 0; i < 9; i++) + { + rotMtxVals[i] = (float) pose[3 + i]; + } + + detection.pose.R = new GeneralMatrixF(3, 3, rotMtxVals); + + // Free the individual detection's cache entry now that we're done with it + AprilTagDetectionCache.freeDetections(ptrDetection); + + detections.add(detection); + } + + return detections; + } + + // ------------------------------------------------------------------------- + // Private helpers + // ------------------------------------------------------------------------- + + /** + * Retrieves the single WPILib detection stored under a per-detection fake pointer. + */ + private static org.wpilib.vision.apriltag.AprilTagDetection getSingleDetection(long ptr) + { + org.wpilib.vision.apriltag.AprilTagDetection[] arr = AprilTagDetectionCache.getDetections(ptr); + if (arr == null || arr.length == 0) + throw new IllegalStateException("No detection found for pointer: " + ptr); + return arr[0]; + } + + /** + * Converts a unit quaternion (w, x, y, z) to a row-major 3x3 rotation matrix. + */ + private static double[] quaternionToRotationMatrix(double w, double x, double y, double z) + { + double[] m = new double[9]; + + m[0] = 1 - 2*(y*y + z*z); + m[1] = 2*(x*y - z*w); + m[2] = 2*(x*z + y*w); + + m[3] = 2*(x*y + z*w); + m[4] = 1 - 2*(x*x + z*z); + m[5] = 2*(y*z - x*w); + + m[6] = 2*(x*z - y*w); + m[7] = 2*(y*z + x*w); + m[8] = 1 - 2*(x*x + y*y); + + return m; + } +} \ No newline at end of file diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCamera.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCamera.java index 8fa95646..609226c7 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCamera.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCamera.java @@ -335,4 +335,4 @@ enum ViewportRenderer * if a recording session is currently active. */ void stopRecordingPipeline(); -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraBase.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraBase.java index 08ba161c..0f89c0f0 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraBase.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraBase.java @@ -21,8 +21,8 @@ package org.openftc.easyopencv; -import io.github.deltacv.common.pipeline.util.PipelineStatisticsCalculator; -import io.github.deltacv.vision.external.PipelineRenderHook; +import org.deltacv.common.pipeline.PipelineStatisticsCalculator; +import org.deltacv.vision.external.PipelineRenderHook; import org.opencv.core.*; import org.opencv.imgproc.Imgproc; @@ -352,3 +352,4 @@ protected Size getFrameSizeAfterRotation(int width, int height, OpenCvCameraRota } } + diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraException.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraException.java index 72538e80..2d502ed2 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraException.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraException.java @@ -28,3 +28,4 @@ public OpenCvCameraException(String msg) super(msg); } } + diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraFactory.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraFactory.java index 896881af..3f11ca3a 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraFactory.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraFactory.java @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2019 OpenFTC Team + * + * 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. + */ + package org.openftc.easyopencv; import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; @@ -35,3 +56,4 @@ public enum ViewportSplitMethod } } + diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraRotation.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraRotation.java index 4fd0d462..5bc4d400 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraRotation.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraRotation.java @@ -28,4 +28,4 @@ public enum OpenCvCameraRotation SIDEWAYS_LEFT, SIDEWAYS_RIGHT, SENSOR_NATIVE, -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvInternalCamera.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvInternalCamera.java index ac0b15fd..abf54c62 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvInternalCamera.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvInternalCamera.java @@ -19,6 +19,7 @@ * SOFTWARE. */ + package org.openftc.easyopencv; import android.hardware.Camera; @@ -38,3 +39,4 @@ enum CameraDirection } } } + diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvInternalCamera2.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvInternalCamera2.java index 5c8a69c9..12c5a7a0 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvInternalCamera2.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvInternalCamera2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 OpenFTC Team + * Copyright (c) 2020 OpenFTC Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -38,3 +38,4 @@ enum CameraDirection } } } + diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvTracker.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvTracker.java index a1422e4f..0277df8b 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvTracker.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvTracker.java @@ -32,4 +32,4 @@ protected final Mat processFrameInternal(Mat input) { input.copyTo(mat); return processFrame(mat); } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvTrackerApiPipeline.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvTrackerApiPipeline.java index 7f16da9a..1f3fd672 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvTrackerApiPipeline.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvTrackerApiPipeline.java @@ -70,4 +70,4 @@ public synchronized void onViewportTapped() { trackerDisplayIdx = 0; } } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewRenderer.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewRenderer.java index f46e34f6..feeaae5f 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewRenderer.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewRenderer.java @@ -18,7 +18,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - package org.openftc.easyopencv; import android.graphics.Bitmap; @@ -336,4 +335,4 @@ public void renderPaused(Canvas canvas) canvas.drawText("VIEWPORT PAUSED", statBoxLTxtStart, textLine2Y, fpsMeterTextPaint); //canvas.drawText("Hi", statBoxLTxtStart, textLine3Y, fpsMeterTextPaint); } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewport.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewport.java index f844ecd7..103b5976 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewport.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewport.java @@ -75,4 +75,4 @@ public FrameContext(OpenCvPipeline generatingPipeline, Object userContext) this.userContext = userContext; } } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvWebcam.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvWebcam.java index d4d73b01..2d040068 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvWebcam.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvWebcam.java @@ -121,4 +121,4 @@ enum StreamFormat * @return the CameraCalibrationIdentity for this webcam */ CameraCalibrationIdentity getCalibrationIdentity(); -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/openftc/easyopencv/PipelineRecordingParameters.java b/Vision/src/main/java/org/openftc/easyopencv/PipelineRecordingParameters.java index f8427a09..01f07ecc 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/PipelineRecordingParameters.java +++ b/Vision/src/main/java/org/openftc/easyopencv/PipelineRecordingParameters.java @@ -115,4 +115,4 @@ public PipelineRecordingParameters build() return new PipelineRecordingParameters(outputFormat, encoder, frameRate, bitrate, path); } } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/openftc/easyopencv/SourcedOpenCvCameraFactoryImpl.java b/Vision/src/main/java/org/openftc/easyopencv/SourcedOpenCvCameraFactoryImpl.java index 14612724..0584ee6a 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/SourcedOpenCvCameraFactoryImpl.java +++ b/Vision/src/main/java/org/openftc/easyopencv/SourcedOpenCvCameraFactoryImpl.java @@ -1,33 +1,15 @@ /* * Copyright (c) 2023 Sebastian Erives - * - * 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. - * + * Licensed under the MIT License. */ package org.openftc.easyopencv; -import io.github.deltacv.vision.external.FrameReceiverOpenCvCamera; -import io.github.deltacv.vision.external.source.VisionSource; -import io.github.deltacv.vision.external.source.ThreadVisionSourceProvider; -import io.github.deltacv.vision.external.source.ViewportVisionSourceProvider; -import io.github.deltacv.vision.internal.source.ftc.SourcedCameraName; +import org.deltacv.vision.external.FrameReceiverOpenCvCamera; +import org.deltacv.vision.external.source.VisionSource; +import org.deltacv.vision.external.source.ThreadVisionSourceProvider; +import org.deltacv.vision.external.source.ViewportVisionSourceProvider; +import org.deltacv.vision.internal.source.ftc.SourcedCameraName; import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; public class SourcedOpenCvCameraFactoryImpl extends OpenCvCameraFactory { @@ -99,3 +81,4 @@ public OpenCvWebcam createWebcam(WebcamName cameraName, int viewportContainerId) } } + diff --git a/Vision/src/main/java/org/openftc/easyopencv/TimestampedOpenCvPipeline.java b/Vision/src/main/java/org/openftc/easyopencv/TimestampedOpenCvPipeline.java index e2453bae..a5c93070 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/TimestampedOpenCvPipeline.java +++ b/Vision/src/main/java/org/openftc/easyopencv/TimestampedOpenCvPipeline.java @@ -1,24 +1,8 @@ -/* - * Copyright (c) 2020 OpenFTC Team - * - * 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. - */ - +/* + * Copyright (c) 2020 Sebastian Erives + * Licensed under the MIT License. + */ + package org.openftc.easyopencv; import org.opencv.core.Mat; @@ -39,4 +23,4 @@ protected void setTimestamp(long timestamp) { this.timestamp = timestamp; } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/openftc/easyopencv/Util.java b/Vision/src/main/java/org/openftc/easyopencv/Util.java index 88801c96..802b30c3 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/Util.java +++ b/Vision/src/main/java/org/openftc/easyopencv/Util.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 OpenFTC Team + * Copyright (c) 2020 OpenFTC Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -72,4 +72,4 @@ public static void acquireUninterruptibly(CountDownLatch latch) Thread.currentThread().interrupt(); } } -} \ No newline at end of file +} diff --git a/build.common.gradle b/build.common.gradle index bd22d10f..5fe2ac71 100644 --- a/build.common.gradle +++ b/build.common.gradle @@ -1,11 +1,9 @@ java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(25) } } -import org.gradle.plugins.signing.Sign - tasks.withType(Sign).configureEach { // Disable signing unless explicitly enabled AND not publishing to Maven Local def releaseSigningEnabled = diff --git a/build.gradle b/build.gradle index 47e49208..51cadaaa 100644 --- a/build.gradle +++ b/build.gradle @@ -3,53 +3,63 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter buildscript { - ext { - kotlin_version = "2.3.0" - kotlinx_coroutines_version = "1.10.2" - slf4j_version = "2.0.16" - log4j_version = "2.24.1" - opencv_version = "4.7.0-0" - apriltag_plugin_version = "2.1.0-D" - papervision_version = "1.1.0" - - flatlaf_version = "3.5.1" - miglayout_version = "11.4.2" - skiko_version = "0.8.15" - - classgraph_version = "4.8.112" - opencsv_version = "5.5.2" - toml4j_version = "0.7.2" - picocli_version = "4.6.1" - - Penv = findProperty('env') - if(Penv != null && (Penv != 'dev' && Penv != 'release')) { - throw new GradleException("Invalid env property, must be 'dev' or 'release'") - } - - env = Penv == 'release' ? 'release' : 'dev' - - println("Current build is: $env") - } - repositories { mavenCentral() - gradlePluginPortal() } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.3.1" classpath 'io.github.fvarrui:javapackager:1.7.6' } } plugins { id 'java' + id 'org.jetbrains.kotlin.jvm' version '2.3.21' apply false + id 'com.gradleup.shadow' version '9.3.1' apply false + id 'org.photonvision.tools.WpilibTools' version '4.1.1-photon' + id "org.wpilib.WPILibRepositoriesPlugin" version "2027.0.0" +} + +ext { + wpilibVersion = "2027.0.0-alpha-6" + opencvVersion = "2027-4.13.0-3" + quickbufVersion = "1.3.3"; + + kotlin_version = "2.3.21" + kotlinx_coroutines_version = "1.10.2" + koin_version = "4.2.0" + slf4j_version = "2.0.16" + log4j_version = "2.24.1" + jackson_version = "2.15.2"; + papervision_version = "1.1.0" + + flatlaf_version = "3.5.1" + miglayout_version = "11.4.2" + skiko_version = "0.8.15" + + classgraph_version = "4.8.112" + opencsv_version = "5.5.2" + toml4j_version = "0.7.2" + picocli_version = "4.6.1" + + Penv = findProperty('env') + if (Penv != null && (Penv != 'dev' && Penv != 'release')) { + throw new GradleException("Invalid env property, must be 'dev' or 'release'") + } + env = Penv == 'release' ? 'release' : 'dev' + + println("Current build is: $env") + + wpilibNativeName = wpilibTools.platformMapper.currentPlatform.platformName; + jniPlatform = wpilibTools.platformMapper.wpilibClassifier; + + println("Building for platform " + jniPlatform + " wpilib: " + wpilibNativeName) + println("Using WPILib: " + wpilibVersion) + println("Using OpenCV: " + opencvVersion) } allprojects { - group 'org.deltacv.EOCV-Sim' - version '4.1.2' + group 'org.deltacv.EOCV-Sim' + version '4.2.0' apply plugin: 'java' @@ -60,34 +70,25 @@ allprojects { repositories { mavenCentral() mavenLocal() - google() maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } maven { url 'https://artifacts.openmicroscopy.org/artifactory/ome.releases/' } + maven { url "https://maven.photonvision.org/releases" } } - tasks.withType(Jar).configureEach { - manifest { - attributes['Main-Class'] = 'com.github.serivesmejia.eocvsim.Main' - } - } - - if(env == 'dev') { - String date = DateTimeFormatter.ofPattern( - "yyMMdd-HHmm" - ).format(LocalDateTime.now()) + wpilibRepositories.use2027Repos() + wpilibRepositories.addAllReleaseRepositories(project) + if (env == 'dev') { + String date = DateTimeFormatter.ofPattern("yyMMdd-HHmm").format(LocalDateTime.now()) String hash = findProperty('hash') version += "-dev-${hash ?: date}" println("Final version of ${project} is $version") - File libsFolder = Paths.get( - projectDir.absolutePath, 'build', 'libs' - ).toFile() - - for(file in libsFolder.listFiles()) { - if(file.name.contains("dev") && file.name.endsWith(".jar")) + File libsFolder = Paths.get(projectDir.absolutePath, 'build', 'libs').toFile() + for (file in libsFolder.listFiles()) { + if (file.name.contains("dev") && file.name.endsWith(".jar")) file.delete() } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 731f0325..3afee195 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 6c492caf..547da593 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1,13 @@ pluginManagement { repositories { maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } + mavenCentral() - maven { url 'https://plugins.gradle.org/m2/' } + gradlePluginPortal() + + maven { url "https://frcmaven.wpi.edu/artifactory/release" } + maven { url "https://maven.photonvision.org/releases" } + maven { url "https://maven.photonvision.org/snapshots" } } } @@ -15,5 +20,4 @@ rootProject.name = 'EOCV-Sim' include 'TeamCode' include 'EOCV-Sim' include 'Common' -include 'Vision' -include 'PaperVisionShadow' \ No newline at end of file +include 'Vision' \ No newline at end of file