diff --git a/.github/workflows/build_and_upload.yml b/.github/workflows/build_and_upload.yml index 614cce03..00ca8e7f 100644 --- a/.github/workflows/build_and_upload.yml +++ b/.github/workflows/build_and_upload.yml @@ -30,11 +30,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: "corretto" - java-version: 17 + java-version: 24 - name: Set up MinGW uses: msys2/setup-msys2@v2 @@ -51,16 +51,28 @@ jobs: value: $env:PATH;C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build - name: Build idl-helper project - run: ./gradlew idl_helper_build_project_windows64 + run: ./gradlew idl_helper_build_project_jni_windows64 + + - name: Build idl-helper FFM project + run: ./gradlew idl_helper_build_project_ffm_windows64 - name: Build TestLib project - run: ./gradlew TestLib_build_project_windows64 + run: ./gradlew TestLib_build_project_jni_windows64 + + - name: Build TestLib FFM project + run: ./gradlew TestLib_build_project_ffm_windows64 - name: Build Shared LibA project - run: ./gradlew LibA_build_project_windows64 + run: ./gradlew LibA_build_project_jni_windows64 + + - name: Build Shared LibA FFM project + run: ./gradlew LibA_build_project_ffm_windows64 - name: Build Shared LibB project - run: ./gradlew LibB_build_project_windows64 + run: ./gradlew LibB_build_project_jni_windows64 + + - name: Build Shared LibB FFM project + run: ./gradlew LibB_build_project_ffm_windows64 # - name: Test Shared Lib # run: ./gradlew :examples:SharedLib:app:core:test @@ -84,11 +96,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: "corretto" - java-version: 17 + java-version: 24 - name: Set up MinGW run: | @@ -98,16 +110,28 @@ jobs: run: chmod +x ./gradlew - name: Build idl-helper project - run: ./gradlew idl_helper_build_project_linux64 + run: ./gradlew idl_helper_build_project_jni_linux64 + + - name: Build idl-helper FFM project + run: ./gradlew idl_helper_build_project_ffm_linux64 - name: Build TestLib project - run: ./gradlew TestLib_build_project_linux64 + run: ./gradlew TestLib_build_project_jni_linux64 + + - name: Build TestLib FFM project + run: ./gradlew TestLib_build_project_ffm_linux64 - name: Build Shared LibA project - run: ./gradlew LibA_build_project_linux64 + run: ./gradlew LibA_build_project_jni_linux64 + + - name: Build Shared LibA FFM project + run: ./gradlew LibA_build_project_ffm_linux64 - name: Build Shared LibB project - run: ./gradlew LibB_build_project_linux64 + run: ./gradlew LibB_build_project_jni_linux64 + + - name: Build Shared LibB FFM project + run: ./gradlew LibB_build_project_ffm_linux64 # - name: Test Shared Lib # run: ./gradlew :examples:SharedLib:app:core:test @@ -131,29 +155,44 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: "corretto" - java-version: 17 + java-version: 24 - name: Change wrapper permissions run: chmod +x ./gradlew - name: Build project idl-helper - run: ./gradlew idl_helper_build_project_mac64 + run: ./gradlew idl_helper_build_project_jni_mac64 + + - name: Build idl-helper FFM project + run: ./gradlew idl_helper_build_project_ffm_mac64 - name: Build TestLib project run: | - ./gradlew TestLib_build_project_mac64 + ./gradlew TestLib_build_project_jni_mac64 + + - name: Build TestLib FFM project + run: | + ./gradlew TestLib_build_project_ffm_mac64 # - name: Build Shared LibA project # run: | -# ./gradlew LibA_build_project_mac64 +# ./gradlew LibA_build_project_jni_mac64 +# +# - name: Build Shared LibA FFM project +# run: | +# ./gradlew LibA_build_project_ffm_mac64 # # - name: Build Shared LibB project # run: | -# ./gradlew LibB_build_project_mac64 +# ./gradlew LibB_build_project_jni_mac64 +# +# - name: Build Shared LibB FFM project +# run: | +# ./gradlew LibB_build_project_ffm_mac64 # - name: Test Shared Lib # run: ./gradlew :examples:SharedLib:app:core:test @@ -177,29 +216,44 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: "corretto" - java-version: 17 + java-version: 24 - name: Change wrapper permissions run: chmod +x ./gradlew - name: Build project idl-helper - run: ./gradlew idl_helper_build_project_macArm + run: ./gradlew idl_helper_build_project_jni_macArm + + - name: Build idl-helper FFM project + run: ./gradlew idl_helper_build_project_ffm_macArm - name: Build TestLib project run: | - ./gradlew TestLib_build_project_macArm + ./gradlew TestLib_build_project_jni_macArm + + - name: Build TestLib FFM project + run: | + ./gradlew TestLib_build_project_ffm_macArm # - name: Build Shared LibA project # run: | -# ./gradlew LibA_build_project_macArm +# ./gradlew LibA_build_project_jni_macArm +# +# - name: Build Shared LibA FFM project +# run: | +# ./gradlew LibA_build_project_ffm_macArm # # - name: Build Shared LibB project # run: | -# ./gradlew LibB_build_project_macArm +# ./gradlew LibB_build_project_jni_macArm +# +# - name: Build Shared LibB FFM project +# run: | +# ./gradlew LibB_build_project_ffm_macArm # - name: Test Shared Lib # run: ./gradlew :examples:SharedLib:app:core:test @@ -223,11 +277,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: "corretto" - java-version: 17 + java-version: 24 - name: Set up MinGW run: | @@ -266,11 +320,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: "corretto" - java-version: 17 + java-version: 24 - name: Set up MinGW run: | @@ -289,12 +343,12 @@ jobs: run: chmod +x ./gradlew - name: Build project idl-helper - run: ./gradlew idl_helper_build_project_android + run: ./gradlew idl_helper_build_project_jni_android env: NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} - name: Build project - run: ./gradlew TestLib_build_project_android + run: ./gradlew TestLib_build_project_jni_android env: NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} @@ -326,11 +380,11 @@ jobs: property: version default: 0.0.1 - - name: Set up JDK 17 + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: "corretto" - java-version: 17 + java-version: 24 - name: Set up MinGW run: | diff --git a/.gitignore b/.gitignore index fdc75723..85d00731 100644 --- a/.gitignore +++ b/.gitignore @@ -31,16 +31,33 @@ out/ **/idl-helper/idl-helper-teavm/src/main/java/** **/idl-helper/idl-helper-core/src/main/java/** +**/idl-helper/idl-helper-desktop-ffm/src/main/java/** +**/idl-helper/idl-helper-desktop-jni/src/main/java/** +**/idl-helper/idl-helper-android/src/main/java/** **/webapp/** **/lib/core/src/** **/lib/desktop/src/main/** + **/lib/lib-teavm/src/main/java/** **/lib/lib-core/src/main/java/** +**/lib/lib-desktop-ffm/src/main/java/** +**/lib/lib-desktop-jni/src/main/java/** +**/lib/lib-android/src/main/java/** + **/libA/lib-teavm/src/main/java/** **/libA/lib-core/src/main/java/** +**/libA/lib-desktop-ffm/src/main/java/** +**/libA/lib-desktop-jni/src/main/java/** +**/libA/lib-android/src/main/java/** **/libB/lib-teavm/src/main/java/** **/libB/lib-core/src/main/java/** +**/libB/lib-desktop-ffm/src/main/java/** +**/libB/lib-desktop-jni/src/main/java/** +**/libB/lib-android/src/main/java/** + **/app/android/libs/ .codiumai -.kotlin/ \ No newline at end of file +.kotlin/ + +LOCAL_AGENT.MD \ No newline at end of file diff --git a/.run/LibA_build_project_android.run.xml b/.run/LibA_build_project_android.run.xml deleted file mode 100644 index 85112d31..00000000 --- a/.run/LibA_build_project_android.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/LibA_build_project_linux64.run.xml b/.run/LibA_build_project_linux64.run.xml deleted file mode 100644 index c25a15ca..00000000 --- a/.run/LibA_build_project_linux64.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/LibA_build_project_mac64.run.xml b/.run/LibA_build_project_mac64.run.xml deleted file mode 100644 index a26f8244..00000000 --- a/.run/LibA_build_project_mac64.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/LibA_build_project_macArm.run.xml b/.run/LibA_build_project_macArm.run.xml deleted file mode 100644 index ed9c455e..00000000 --- a/.run/LibA_build_project_macArm.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/LibA_build_project_teavm.run.xml b/.run/LibA_build_project_teavm.run.xml deleted file mode 100644 index 70e435e9..00000000 --- a/.run/LibA_build_project_teavm.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/LibA_build_project_windows64.run.xml b/.run/LibA_build_project_windows64.run.xml deleted file mode 100644 index 87adfae8..00000000 --- a/.run/LibA_build_project_windows64.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/LibB_build_project_android.run.xml b/.run/LibB_build_project_android.run.xml deleted file mode 100644 index a7a5d88e..00000000 --- a/.run/LibB_build_project_android.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/LibB_build_project_linux64.run.xml b/.run/LibB_build_project_linux64.run.xml deleted file mode 100644 index 1ecff02a..00000000 --- a/.run/LibB_build_project_linux64.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/LibB_build_project_mac64.run.xml b/.run/LibB_build_project_mac64.run.xml deleted file mode 100644 index f9ae1454..00000000 --- a/.run/LibB_build_project_mac64.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/LibB_build_project_macArm.run.xml b/.run/LibB_build_project_macArm.run.xml deleted file mode 100644 index 8d908a64..00000000 --- a/.run/LibB_build_project_macArm.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/LibB_build_project_teavm.run.xml b/.run/LibB_build_project_teavm.run.xml deleted file mode 100644 index b55adb25..00000000 --- a/.run/LibB_build_project_teavm.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/LibB_build_project_windows64.run.xml b/.run/LibB_build_project_windows64.run.xml deleted file mode 100644 index 985cbb5f..00000000 --- a/.run/LibB_build_project_windows64.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/SharedLib_run_app_desktop.run.xml b/.run/SharedLib_run_app_desktop.run.xml deleted file mode 100644 index 86d16a4c..00000000 --- a/.run/SharedLib_run_app_desktop.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/SharedLib_run_app_teavm.run.xml b/.run/SharedLib_run_app_teavm.run.xml deleted file mode 100644 index 978c4ef1..00000000 --- a/.run/SharedLib_run_app_teavm.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_build_project.run.xml b/.run/TestLib_build_project.run.xml deleted file mode 100644 index e080f31c..00000000 --- a/.run/TestLib_build_project.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_build_project_all.run.xml b/.run/TestLib_build_project_all.run.xml deleted file mode 100644 index ab9e25c0..00000000 --- a/.run/TestLib_build_project_all.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_build_project_android.run.xml b/.run/TestLib_build_project_android.run.xml deleted file mode 100644 index 2c0e1684..00000000 --- a/.run/TestLib_build_project_android.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_build_project_linux64.run.xml b/.run/TestLib_build_project_linux64.run.xml deleted file mode 100644 index a91e46b9..00000000 --- a/.run/TestLib_build_project_linux64.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_build_project_mac64.run.xml b/.run/TestLib_build_project_mac64.run.xml deleted file mode 100644 index a0340b03..00000000 --- a/.run/TestLib_build_project_mac64.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_build_project_macArm.run.xml b/.run/TestLib_build_project_macArm.run.xml deleted file mode 100644 index 54520984..00000000 --- a/.run/TestLib_build_project_macArm.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_build_project_teavm.run.xml b/.run/TestLib_build_project_teavm.run.xml deleted file mode 100644 index f5a96be2..00000000 --- a/.run/TestLib_build_project_teavm.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_build_project_windows64.run.xml b/.run/TestLib_build_project_windows64.run.xml deleted file mode 100644 index f070ff5c..00000000 --- a/.run/TestLib_build_project_windows64.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_run_app_desktop.run.xml b/.run/TestLib_run_app_desktop.run.xml deleted file mode 100644 index a5a2c447..00000000 --- a/.run/TestLib_run_app_desktop.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_run_app_teavm.run.xml b/.run/TestLib_run_app_teavm.run.xml deleted file mode 100644 index a366253e..00000000 --- a/.run/TestLib_run_app_teavm.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_run_benchmark_desktop.run.xml b/.run/TestLib_run_benchmark_desktop.run.xml deleted file mode 100644 index 713b2cec..00000000 --- a/.run/TestLib_run_benchmark_desktop.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/TestLib_run_benchmark_teavm.run.xml b/.run/TestLib_run_benchmark_teavm.run.xml deleted file mode 100644 index a2b45a52..00000000 --- a/.run/TestLib_run_benchmark_teavm.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/idl_helper_build_project_android.run.xml b/.run/idl_helper_build_project_android.run.xml deleted file mode 100644 index 72d40b06..00000000 --- a/.run/idl_helper_build_project_android.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/idl_helper_build_project_linux64.run.xml b/.run/idl_helper_build_project_linux64.run.xml deleted file mode 100644 index dbfe1b66..00000000 --- a/.run/idl_helper_build_project_linux64.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/idl_helper_build_project_mac64.run.xml b/.run/idl_helper_build_project_mac64.run.xml deleted file mode 100644 index cad8c216..00000000 --- a/.run/idl_helper_build_project_mac64.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/idl_helper_build_project_macArm.run.xml b/.run/idl_helper_build_project_macArm.run.xml deleted file mode 100644 index 3ea5a4bd..00000000 --- a/.run/idl_helper_build_project_macArm.run.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - false - false - false - false - - - \ No newline at end of file diff --git a/.run/idl_helper_build_project_teavm.run.xml b/.run/idl_helper_build_project_teavm.run.xml deleted file mode 100644 index 73e640d1..00000000 --- a/.run/idl_helper_build_project_teavm.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/idl_helper_build_project_windows64.run.xml b/.run/idl_helper_build_project_windows64.run.xml deleted file mode 100644 index 974ef0d3..00000000 --- a/.run/idl_helper_build_project_windows64.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..f097ddf6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,239 @@ +# AGENTS.md — jParser + +## Project Overview + +jParser is a Java code-generation and C/C++ compilation library that bridges native code to JVM platforms (desktop, mobile, web). It reads Java source files containing embedded native code blocks, then generates platform-specific Java source for **JNI** (desktop/mobile), **FFM** (desktop, Java 22+), and **TeaVM** (web/WASM) targets. It also supports **WebIDL**-driven automatic binding generation. + +### Context Resumption & State Persistence + +If you are asked to continue a task or if this chat has been restored from a backup, **always check for `LOCAL_AGENT.md`** in the project root. + +**This file is the single source of truth for the current state machine.** It must be updated **BEFORE** every significant action. + +### Agent Workflow for Persistence: +1. **Analyze**: Understand the user request and existing codebase. **NEVER guess code structures or property names.** You must verify the existence of classes, methods, and fields by reading the source files. (e.g., Do not assume a variable or a method exists; check the class structure first). +2. **Sync State**: Read `LOCAL_AGENT.md` to understand where the previous session left off. +3. **Plan & Commit**: Document any new architectural decisions, sub-tasks, or file modifications in `LOCAL_AGENT.md` **before** writing code. +4. **After planning:**: Always ask the user if can execute the plan. User may ask questions about the plan. If user approves, proceed to execute. If user asks for changes, update the plan and repeat step 3. +5. **Execute**: Perform file modifications, terminal commands, etc. +6. **Update Progress**: After a task (or significant sub-task) is completed or verified, update the "Current Progress" and "Next Task" sections in `LOCAL_AGENT.md`. +7. **Verify**: Always run compilation to ensure the state is stable before ending the turn. + +This ensures that if the session is interrupted, the next agent has a perfect "snapshot" of the state machine, including why decisions were made and which files were touched. + +## Architecture + +### Core Pipeline (`BuilderTool.build()` in `jParser/jParser-build-tool`) +1. **IDL Parsing** — `IDLReader` reads `.idl` files from `lib-build/src/main/cpp/` +2. **Code Generation (Core API)** — `IDLDefaultCodeParser` generates bridge-agnostic API classes into `lib-core/src/main/java`. +3. **Code Generation (JNI)** — `CppCodeParser` (extends `IDLDefaultCodeParser`) reads `lib-base/src/main/java` source, generates JNI Java into platform modules (`lib-desktop-jni/src/main/java` and `lib-android/src/main/java`) based on build target routing. Controlled by `BuildToolOptions.generateCPP` (default: `true`). +4. **Code Generation (TeaVM)** — `TeaVMCodeParser` generates TeaVM/JS Java into `lib-teavm/src/main/java`. Controlled by `BuildToolOptions.generateTeaVM` (default: `true`). +5. **Code Generation (FFM)** — `FFMCodeParser` generates FFM Java (using `java.lang.foreign` MethodHandle downcalls) into `lib-desktop-ffm/src/main/java`. Controlled by `BuildToolOptions.generateFFM` (default: `false`). +6. **Native Compilation** — `JBuilder.build()` compiles C/C++ for each platform target via `BuildMultiTarget` + +### Module Layout (follows a strict `-base/-build/-core/-teavm` convention) + +| Suffix | Purpose | Java target | +|---|---|---| +| `lib-base` | Hand-written Java source with embedded `/*[-JNI;-NATIVE]*/`, `/*[-FFM;-NATIVE]*/`, and `/*[-TEAVM;-REPLACE]*/` code blocks | Java 8 | +| `lib-build` | `BuildLib.main()` entry point — configures IDL, targets, runs generation + compilation | Java 11 | +| `lib-core` | **Generated** bridge-agnostic API classes (do not hand-edit) | Java 11 | +| `lib-teavm` | **Generated** TeaVM Java output (do not hand-edit) | Java 11 | +| `lib-desktop-jni` | **Generated** desktop JNI Java output + desktop JNI native DLLs/shared-libs (do not hand-edit) | Java 11 | +| `lib-desktop-ffm` | **Generated** FFM Java output + FFM-compiled native DLLs/shared-libs (do not hand-edit) | Java 22+ | +| `lib-android` | **Generated** Android JNI Java output + Android JNI packaging/native libs (do not hand-edit) | Java 8 | + +This pattern repeats across `jParser/`, `idl-helper/`, `loader/`, and `examples/`. + +### Key Modules + +- **`jParser/jParser-core`** — `JParser.generate()` entry point; uses JavaParser to parse/transform Java ASTs. `CodeParser` interface → `DefaultCodeParser` → `IDLDefaultCodeParser`. +- **`jParser/jParser-jni`** — `CppCodeParser` (header `"JNI"`) generates JNI glue code + `NativeCPPGenerator` emits C++ `.cpp` files with JNI calling convention (`jlong`, `jint`, etc.). +- **`jParser/jParser-ffm`** — `FFMCodeParser` (header `"FFM"`) generates Java classes using `java.lang.foreign` MethodHandle downcalls instead of JNI native methods. `FFMCppGenerator` emits C++ `.cpp` files with `extern "C"` and standard C types (`int64_t`, `int32_t`, etc.). Also includes `FFMMethodHandleRegistry`, `FFMNativeCodeGenerator`, and `FFMTypeMapper`. +- **`jParser/jParser-teavm`** — `TeaVMCodeParser` (header `"TEAVM"`) generates `@JSBody`-annotated methods for TeaVM. +- **`jParser/jParser-idl`** — IDL file parser, class model (`IDLClass`, `IDLMethod`, `IDLAttribute`), and code generation parsers. +- **`jParser/jParser-build`** — `JBuilder`, `BuildConfig`, `BuildToolOptions`, platform targets (`EmscriptenTarget`, `WindowsMSVCTarget`, `WindowsTarget`, `LinuxTarget`, `MacTarget`, `AndroidTarget`, `IOSTarget`). +- **`jParser/jParser-build-tool`** — `BuilderTool.build()` orchestrates the full pipeline: IDL parsing → JNI/TeaVM/FFM code generation → native compilation. +- **`jParser/jParser-base`** — Shared base classes used by all targets (e.g., `IDLUtils`, `IDLString`, `IDLArray`). +- **`idl/idl-core`** — `IDLBase` parent class for all native objects (memory management, ownership, dispose). +- **`idl/idl-teavm`** — TeaVM-specific IDL runtime support. +- **`loader/loader-core`** — `JParserLibraryLoader` handles native library loading for desktop and mobile platforms. +- **`loader/loader-teavm`** — TeaVM-specific library loader (asynchronous JS/WASM script loading). + +## Code Block Convention + +In `lib-base` Java source, native code is embedded via block comments with headers and set before the method. Each target uses its own header prefix and. A single source file can contain blocks for all three targets: + +```java +/*[-TEAVM;-REPLACE] + @org.teavm.jso.JSBody(params = {"this_addr"}, script = "...") + private static native void internal_native_doSomething(int this_addr); +*/ +/*[-JNI;-NATIVE] + MyType* obj = (MyType*)this_addr; + obj->doSomething(); +*/ +/*[-FFM;-NATIVE] + MyType* obj = (MyType*)this_addr; + obj->doSomething(); +*/ +private static native void internal_native_doSomething(long this_addr); + +``` + +**Headers**: `JNI`, `FFM`, `TEAVM`. + +**Commands**: `-ADD`, `-ADD_RAW`, `-REMOVE`, `-REPLACE`, `-REPLACE_BLOCK`, `-NATIVE`. Use `-IDL_SKIP` on a class comment to prevent IDL generation for that class. + +**How it works**: `DefaultCodeParser` matches the header prefix (e.g., `JNI`, `FFM`, `TEAVM`) in each block comment. Blocks whose header does not match the active parser are automatically removed from the generated output. The `-NATIVE` command associates C/C++ code with the following `native` method declaration. + +## Requirements + +- **JDK 11+** for building the jParser tool modules +- **JDK 22+** (25 recommended) for FFM modules and running FFM-based apps +- **Gradle** — wrapper included (`./gradlew` / `gradlew.bat`) +- **Windows native builds**: MinGW64 **and/or** Visual Studio C++ (`vcvarsall.bat` must be on PATH) +- **Linux native builds**: GCC / G++ toolchain +- **macOS native builds**: Xcode command-line tools +- **Web builds**: Emscripten SDK (`EMSDK` env var) + +## Build & Run + +All commands are run from the **project root**. Use `./gradlew` on Linux/macOS, `gradlew.bat` on Windows. + +### 1. Build the IDL Helper library (required before examples) + +The idl-helper provides the `IDLBase` runtime for all native-bound objects. It must be code-generated and native-compiled before examples can run. + +```sh +# Step 1 — Generate JNI/TeaVM/FFM Java code + compile native library for your platform +# JNI (pick your platform): +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_jni_windows64 +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_jni_linux64 +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_jni_mac64 +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_jni_macArm + +# FFM (pick your platform): +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_ffm_windows64 +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_ffm_linux64 +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_ffm_mac64 +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_ffm_macArm + +# All JNI platforms at once: +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_all +``` + +### 2. Build example: TestLib + +TestLib is the primary test/example library. Building it generates Java source code into `lib-core`, `lib-desktop-jni`, `lib-android`, `lib-teavm`, and `lib-desktop-ffm`, then compiles native C/C++ into platform DLLs/shared-libs. + +```sh +# Step 1 — Generate Java code + compile native for your platform (JNI): +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_jni_windows64 +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_jni_linux64 +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_jni_mac64 +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_jni_macArm + +# All JNI platforms: +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_all + +# FFM (generates lib-desktop-ffm Java code + compiles FFM native): +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_ffm_windows64 +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_ffm_linux64 +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_ffm_mac64 +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_ffm_macArm + +# Generate FFM Java code only (no native compilation): +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_ffm + +# Step 2 — Run the desktop app (JNI or FFM): +./gradlew :examples:TestLib:app:desktop-jni:TestLib_run_app_jni_desktop +./gradlew :examples:TestLib:app:desktop-ffm:TestLib_run_app_ffm_desktop + +``` + +### 3. Build example: SharedLib (multi-library) + +SharedLib demonstrates two libraries (libA + libB) where libB depends on libA. **Build libA first**, then libB. + +```sh +# libA — JNI: +./gradlew :examples:SharedLib:libA:lib-build:LibA_build_project_jni_windows64 +# libA — FFM: +./gradlew :examples:SharedLib:libA:lib-build:LibA_build_project_ffm_windows64 + +# libB — JNI (after libA): +./gradlew :examples:SharedLib:libB:lib-build:LibB_build_project_jni_windows64 +# libB — FFM (after libA): +./gradlew :examples:SharedLib:libB:lib-build:LibB_build_project_ffm_windows64 + +# Run SharedLib desktop app (JNI or FFM): +./gradlew :examples:SharedLib:app:desktop-jni:SharedLib_run_app_jni_desktop +./gradlew :examples:SharedLib:app:desktop-ffm:SharedLib_run_app_ffm_desktop +``` + +Replace `windows64` with `linux64`, `mac64`, or `macArm` for other platforms. + +### 4. JNI vs FFM Benchmarks + +Run micro-benchmarks comparing JNI and FFM bridge overhead. **Requires both JNI and FFM native DLLs to be built first** (see steps 2–3 above). + +```sh +# Run both benchmarks and print a comparison table +./gradlew :examples:TestLib:app:desktop-ffm:TestLib_throughput_benchmark_compare + +# Run only JNI benchmark (saves CSV to build/testlib-benchmark/benchmark_jni.csv) +./gradlew :examples:TestLib:app:desktop-jni:TestLib_throughput_benchmark_jni + +# Run only FFM benchmark (saves CSV to build/testlib-benchmark/benchmark_ffm.csv) +./gradlew :examples:TestLib:app:desktop-ffm:TestLib_throughput_benchmark_ffm +``` + +### 5. JNI vs FFM FPS Benchmarks + +Measures how native bridge overhead affects frame rate. Each frame executes a fixed number of native calls, then returns to let GDX render. Reports average and minimum FPS per scenario. + +```sh +# Run both FPS benchmarks and print a comparison table +./gradlew :examples:TestLib:app:desktop-ffm:TestLib_fps_benchmark_compare + +# Run only JNI FPS benchmark (saves CSV to build/testlib-benchmark/fps_benchmark_jni.csv) +./gradlew :examples:TestLib:app:desktop-jni:TestLib_fps_benchmark_jni + +# Run only FFM FPS benchmark (saves CSV to build/testlib-benchmark/fps_benchmark_ffm.csv) +./gradlew :examples:TestLib:app:desktop-ffm:TestLib_fps_benchmark_ffm +``` + +### Build order summary (from scratch on Windows) + +```sh +# 1. Build idl-helper native (both JNI + FFM) +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_jni_windows64 +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_ffm_windows64 + +# 2. Build TestLib native (both JNI + FFM) +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_jni_windows64 +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_ffm_windows64 + +# 3. Run the desktop app (JNI or FFM) +./gradlew :examples:TestLib:app:desktop-jni:TestLib_run_app_jni_desktop +./gradlew :examples:TestLib:app:desktop-ffm:TestLib_run_app_ffm_desktop + +# 4. Run throughput benchmarks +./gradlew :examples:TestLib:app:desktop-ffm:TestLib_throughput_benchmark_compare + +# 5. Run FPS benchmarks +./gradlew :examples:TestLib:app:desktop-ffm:TestLib_fps_benchmark_compare +``` + +## Conventions + +- **Version management**: `gradle.properties` holds the version; `LibExt.kt` in `buildSrc/` resolves it. Snapshots use `"-SNAPSHOT"`, releases use the property value. +- **Publishing**: The `publish.gradle.kts` plugin configures all library modules listed in `libProjects`. Use `publishRelease` or `publishTestRelease` tasks. +- **Generated code is not hand-edited**: `lib-core/`, `lib-desktop-jni/`, `lib-android/`, `lib-teavm/`, and `lib-desktop-ffm/` directories contain generated output with a "Do not make changes" header. +- **IDL files** live at `lib-build/src/main/cpp/.idl`. Custom C++ glue code goes in `lib-build/src/main/cpp/custom/`. +- **IDLBase** is the parent of all native-bound classes. Memory must be manually managed via `dispose()`. Use `ClassName.NULL` instead of Java `null` for native parameters. +- **Dependencies**: JavaParser (`3.26.1`) for AST manipulation, TeaVM (`0.13.1`) for web target, JUnit 4 for tests. +- **Native bridge selection**: TestLib and SharedLib desktop tasks are split into `app/desktop-jni` and `app/desktop-ffm` modules. +- **FFM desktop launcher toolchain**: Desktop `..._run_app_ffm_desktop` tasks currently configure Java toolchain 24 in app desktop-ffm Gradle scripts. +- **JNI vs FFM C++ differences**: JNI glue uses JNI types (`jlong`, `jint`, `JNIEnv*`). FFM glue uses `extern "C"` with standard C types (`int64_t`, `int32_t`) and no JNI environment — calls go through `java.lang.foreign` MethodHandle downcalls. diff --git a/README.md b/README.md index 486cfc4b..86cfb3aa 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,216 @@ -# jParser +

+

jParser

+

+ A Java code-generation library that bridges C/C++ native code to JVM platforms — desktop, mobile, and web. +

+

+ +

+ Build + Maven Central Version + Snapshot + License +

+ +--- + +## Table of Contents + +- [Overview](#overview) +- [How It Works](#how-it-works) +- [Supported Targets](#supported-targets) +- [Code Block Convention](#code-block-convention) +- [WebIDL Bindings](#webidl-bindings) +- [IDLBase API](#idlbase-api) +- [Requirements](#requirements) +- [Getting Started](#getting-started) +- [Libraries Using jParser](#libraries-using-jparser) +- [License](#license) + +--- + +## Overview + +Inspired by [gdx-jnigen](https://github.com/libgdx/gdx-jnigen), jParser lets you embed native C/C++ code directly inside Java source files using annotated comment blocks. Each block is translated into target-specific Java source code, enabling a single `lib-base` module to produce a bridge-agnostic `lib-core` API plus platform bridge outputs for **JNI** (desktop/mobile), **FFM** (desktop, Java 22+), and **TeaVM** (web via JS/WASM). -![Build](https://github.com/xpenatan/jParser/actions/workflows/snapshot.yml/badge.svg) -[![Maven Central Version](https://img.shields.io/maven-central/v/com.github.xpenatan.jParser/jParser-core)](https://central.sonatype.com/artifact/com.github.xpenatan.jParser/jParser-core) -[![Static Badge](https://img.shields.io/badge/snapshot---SNAPSHOT-red)](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/com/github/xpenatan/jParser/) +For web targets, jParser uses [Emscripten](https://emscripten.org/) to compile C/C++ into JS/WASM and [TeaVM](https://github.com/konsoletyper/teavm) to generate the corresponding Java-to-JavaScript bridge via `@JSBody` annotations. +## How It Works -jParser is a compact Java library designed to facilitate the integration of C/C++ code with desktop, mobile, and web platforms, enabling inline writing within Java source code. +jParser consists of two main stages: + +### 1. Code Generation + +Reads the hand-written Java source in the `lib-base` module — which contains embedded native code blocks — and generates platform-specific Java source for each target: + +| Output Module | Target | Description | +|---|---|---| +| `lib-core` | Core API | Generated bridge-agnostic API classes | +| `lib-desktop-jni` | JNI (Desktop) | Generated desktop JNI Java + native jars | +| `lib-android` | JNI (Android) | Generated Android JNI Java + Android packaging | +| `lib-teavm` | TeaVM | Generated `@JSBody`-annotated Java for web | +| `lib-desktop-ffm` | FFM | Generated FFM Java for desktop (Java 22+) | + +### 2. Native Compilation + +Compiles the C/C++ source into platform-specific native libraries: + +| Platform | Toolchain | +|---|---| +| Windows | MinGW64 or MSVC | +| Linux | GCC / G++ | +| macOS | Xcode CLI tools | +| Android | Android NDK | +| Web | Emscripten SDK | + +## Supported Targets + +| Target | Bridge | Platforms | Java Version | +|---|---|---|--------------| +| **JNI** | Java Native Interface | Windows, Linux, macOS, Android | 8+ | +| **FFM** | Foreign Function & Memory API | Windows, Linux, macOS | 22+ | +| **TeaVM** | JavaScript / WASM | Web browsers | 11+ | + +## Code Block Convention -Inspired by [gdx-jnigen](https://github.com/libgdx/gdx-jnigen), jParser allows you to embed native code within a code block. This block is then translated into the appropriate target-specific code. You can define multiple code block targets within the same Java source file, and for each target, jParser generates a corresponding Java source file. +In `lib-base` Java source files, native code is embedded via annotated comment blocks. jParser reads these blocks and generates the appropriate code for each target. + +```java +public class MyLib extends IDLBase { + + // TeaVM replacement — generates @JSBody-annotated method for web + /*[-TEAVM;-REPLACE] + @org.teavm.jso.JSBody(params = {"this_addr"}, + script = "var jsObj = [MODULE].wrapPointer(this_addr, [MODULE].MyType);" + + "return jsObj.getValue();") + private static native int internal_native_getValue(int this_addr); + */ + // JNI native code block — compiled into C++ for desktop & mobile + /*[-JNI;-NATIVE] + MyType* obj = (MyType*)this_addr; + return obj->getValue(); + */ + private static native int internal_native_getValue(long this_addr); +} +``` + +### Available Commands + +| Command | Description | +|---|---| +| `-NATIVE` | Inline C/C++ code compiled for the target | +| `-ADD` | Adds code to the generated output | +| `-ADD_RAW` | Adds raw code without processing | +| `-REMOVE` | Removes code from the generated output | +| `-REPLACE` | Replaces the following method with the block content | +| `-REPLACE_BLOCK` | Replaces the following code block | +| `-IDL_SKIP` | Placed on a class comment to skip IDL generation for that class | + +## WebIDL Bindings + +To reduce the effort of manually porting each method, jParser supports **Emscripten WebIDL**. Define a `.idl` file and jParser automatically generates binding code for all targets. + +```idl +interface NormalClass { + void NormalClass(); + long addIntValue(long value1, long value2); + static long subIntValue(long value1, long value2); + attribute long intValue; + attribute float floatValue; +}; +``` -For web applications, jParser requires Emscripten to produce JS/WASM files and utilizes [TeaVM](https://github.com/konsoletyper/teavm). The classes generated in the TeaVM module use `JSBody` annotation solution to interact with JavaScript. +This generates fully working Java classes with native bindings for JNI, FFM, and TeaVM — no manual glue code required. -Currently, jParser supports only `JNI` and `TEAVM` code targets. There are plans to support the Java Foreign Function and Memory API (FFM). +### WebIDL Notes -## How it Works -jParser consists of two main components: +- **IDL helper classes** (`IDLInt`, `IDLIntArray`, etc.) let you pass primitive pointers to C++. They work across Emscripten, desktop, and mobile. +- **C++ enums** are converted into Java enums, each carrying the integer value from native code. +- **`[Value]` methods** return a cached copy of the object. The cache is overwritten on each call — do not retain references. +- **`[NoDelete]` classes** should not have `dispose()` called. All other classes require explicit disposal. -1. **Code Generation**: It reads the Java source code containing the jParser solution and generates new Java source code for each target platform. The `base` module is used for this purpose. For desktop and mobile platforms, the generated JNI code is located in the `core` module, while the web-specific code is placed in the `teavm` module. +## IDLBase API -2. **C/C++ Compilation**: It compiles the C/C++ code for various platforms, including Windows, Linux, macOS, Android, iOS, and the Web. +Every native class extends `IDLBase`, which provides common memory-management functionality. -## WebIDL -To further streamline the lengthy process of manually porting each method, jParser includes support for Emscripten WebIDL. By creating a WebIDL file, you can automatically generate binding code for both JNI and TeaVM. While this feature may not cover every scenario, it significantly reduces the effort required to bind large libraries. For a comprehensive example, refer to the `examples:TestLib` module. +> **Important:** jParser does not automatically dispose C++ objects. You must call `dispose()` when you're done with an object to free native memory. Only objects you create or explicitly own require disposal. Creating and disposing native objects is expensive — avoid doing it every frame. + +| Method | Description | +|---|---| +| `ClassName.native_new()` | Creates an empty instance without native data | +| `ClassName.NULL` | Returns a NULL instance — use instead of Java `null` for native parameters | +| `dispose()` | Deletes the native instance (only if owned) | +| `isDisposed()` | Checks whether the native instance has been disposed | +| `native_setVoid(...)` | Sets an integer or long memory address | +| `native_reset()` | Resets the instance to default state | +| `native_takeOwnership()` | Takes ownership, enabling `dispose()` to delete the object | +| `native_releaseOwnership()` | Releases ownership, preventing `dispose()` from deleting | +| `native_hasOwnership()` | Checks whether you own the native instance | +| `native_copy(...)` | Copies memory address and native data from another instance | + +> The `native_` prefix is used to avoid naming conflicts with C/C++ methods. + +## Requirements + +| Requirement | Purpose | +|---|---| +| **JDK 11+** | Building jParser tool modules | +| **JDK 22+** (25 recommended) | FFM modules and FFM-based apps | +| [MinGW64](https://github.com/niXman/mingw-builds-binaries/releases) or [Visual Studio C++](https://visualstudio.microsoft.com/vs/community/) | Windows native builds | +| GCC / G++ | Linux native builds | +| Xcode CLI tools | macOS native builds | +| [Emscripten SDK](https://emscripten.org/) | Web builds (JS/WASM) | + +> **Windows (MSVC):** Ensure `vcvarsall.bat` is on your system `PATH`. It is typically located at: +> `C:\Program Files\Microsoft Visual Studio\[Year]\[Edition]\VC\Auxiliary\Build\` + +## Getting Started + +For a complete working example, refer to the [`examples/TestLib`](examples/TestLib) module. + +### Module Layout + +jParser projects follow a strict `-base / -build / -core / -teavm` convention: + +| Module Suffix | Purpose | +|---|---| +| `lib-base` | Hand-written Java source with embedded native code blocks | +| `lib-build` | Build entry point — configures IDL, targets, runs generation + compilation | +| `lib-core` | **Generated** bridge-agnostic API output _(do not hand-edit)_ | +| `lib-desktop-jni` | **Generated** desktop JNI Java output + JNI native libs _(do not hand-edit)_ | +| `lib-android` | **Generated** Android JNI Java output + Android JNI packaging _(do not hand-edit)_ | +| `lib-teavm` | **Generated** TeaVM Java output _(do not hand-edit)_ | +| `lib-desktop-ffm` | **Generated** FFM Java output _(do not hand-edit)_ | + +### Build Example: TestLib + +```bash +# 1. Build idl-helper (required once) +./gradlew :idl-helper:idl-helper-build:idl_helper_build_project_jni_windows64 + +# 2. Generate code + compile native library +./gradlew :examples:TestLib:lib:lib-build:TestLib_build_project_jni_windows64 + +# 3. Run the desktop app +./gradlew :examples:TestLib:app:desktop-jni:TestLib_run_app_jni_desktop +./gradlew :examples:TestLib:app:desktop-ffm:TestLib_run_app_ffm_desktop +``` -The generated methods will match those defined in the WebIDL file. If the C++ code is case-sensitive, as seen in ImGui, the corresponding Java methods will also maintain case sensitivity. Additionally, C/C++ attributes are converted into methods prefixed with `set_` or `get_`. +> Replace `windows64` with `linux64`, `mac64`, or `macArm` for other platforms. +> Current desktop FFM app tasks configure Java toolchain `24`; ensure a Java 24 toolchain is available when running `..._run_app_ffm_desktop` tasks. + +## Libraries Using jParser -## WebIDL Notes -* IDL classes, such as IDLInt or IDLIntArray, provide a way to pass primitive pointers to C++ code. These classes are compatible with Emscripten, desktop, and mobile platforms. Use them when you need to pass a pointer array or a primitive that the C++ code will modify. -* C++ enums are converted into Java Enums, where each enum name contains the integer value from the native code. -* Methods annotated with [Value] return a copy of the object. The object is cached in both C++ and Java. Each time you call the same method, it overwrites the previous data, so avoid retaining references to the returned object. -* JParser does not automatically dispose C++ objects. It's your responsibility to call dispose to free the memory. For classes marked with [NoDelete], there is no need to call dispose. +| Library | Description | Status | +|---|---|---| +| [jWebGPU](https://github.com/xpenatan/jWebGPU) | WebGPU bindings for Java | Active | +| [xImGui](https://github.com/xpenatan/xImGui) | Dear ImGui bindings for Java | Active | +| [xJolt](https://github.com/xpenatan/xJolt) | Jolt Physics bindings for Java | Active | +| [xLua](https://github.com/xpenatan/XLua) | Lua bindings for Java | Active | +| [xBullet](https://github.com/xpenatan/xBullet) | Bullet Physics bindings for Java | Active | +| [gdx-box2d](https://github.com/xpenatan/gdx-box2d) | Box2D bindings for libGDX | Inactive | +| [gdx-physx](https://github.com/xpenatan/gdx-physx) | PhysX bindings for libGDX | Inactive | + +## License -## IDLBase methods -Every native class extends IDLBase, a parent class that provides common functionality. One commonly used method is dispose, which frees the memory allocated for a native object to prevent memory leaks. -You must call this method when you're done using it. However, not all classes require calling dispose. Only objects you create manually, or those created by a library and explicitly owned by you, need to have their dispose method called. -Creating a native class and disposing is expensive, so avoid calling these operations every frame.

-Here is a list of all IDLBase methods: -* **static [ClassName].native_new()**: Creates a new empty instance without any associated native data. -* **static [ClassName].NULL**: Returns a NULL instance. Every method parameter must not be null. Use this when a native methods needs a null parameter. -* **dispose()**: Deletes the native instance, but only if you own it. -* **isDisposed()**: Checks whether the native instance has been disposed. -* **native_setVoid(...)**: Sets an integer or long memory address. In TeaVM, the long is cast to an integer. -* **native_reset()**: Resets the Java instance to its default state, removing any associated native data. -* **native_takeOwnership()**: Takes ownership of the native data, enabling dispose() to delete the object. -* **native_releaseOwnership()**: Releases ownership of the native data, preventing dispose() from deleting the object. -* **native_hasOwnership()**: Checks whether you own the native instance. -* **native_copy(...)**: Copies the memory address and all other native data from another Java instance to this Java instance. - -The **native** method keyword is primarily used to avoid conflicts with C/C++ methods. - -## Libraries using jParser:
-- [jWebGPU](https://github.com/xpenatan/jWebGPU)¹ -- [xImGui](https://github.com/xpenatan/xImGui)¹ -- [xJolt](https://github.com/xpenatan/xJolt)¹ -- [xLua](https://github.com/xpenatan/XLua)¹ -- [xBullet](https://github.com/xpenatan/xBullet)¹ -- [gdx-box2d](https://github.com/xpenatan/gdx-box2d)² -- [gdx-physx](https://github.com/xpenatan/gdx-physx)² - -¹: The focus is on maintaining this project.
-²: This project is currently inactive and may only be used to test the generator. - -## Requirements: -#### [Mingw64](https://github.com/niXman/mingw-builds-binaries/releases) or [Visual Studio C++](https://visualstudio.microsoft.com/vs/community/) -#### [Emscripten](https://emscripten.org/) -For Windows builds using WindowsMSVCTarget, ensure `vcvarsall.bat` is accessible via the system PATH. It is typically located in `C:\Program Files\Microsoft Visual Studio\[Year]\[Edition]\VC\Auxiliary\Build\`. +jParser is licensed under the [Apache License 2.0](LICENSE). diff --git a/buildSrc/src/main/kotlin/LibExt.kt b/buildSrc/src/main/kotlin/LibExt.kt index 57c0c260..2edebb33 100644 --- a/buildSrc/src/main/kotlin/LibExt.kt +++ b/buildSrc/src/main/kotlin/LibExt.kt @@ -12,17 +12,17 @@ object LibExt { const val java8Target = "1.8" const val java11Target = "11" + const val java24Target = "24" // Lib Dependencies const val jniGenVersion = "2.5.1" - const val teaVMVersion = "0.13.0" + const val teaVMVersion = "0.13.1" const val javaparserVersion = "3.26.1" const val jMultiplatform = "0.1.3" // Example Dependencies - const val exampleUseRepoLibs = false const val gdxVersion = "1.14.0" - const val gdxTeaVMVersion = "1.4.0" + const val gdxTeaVMVersion = "1.5.4" const val jUnitVersion = "4.13.2" } diff --git a/buildSrc/src/main/kotlin/publish.gradle.kts b/buildSrc/src/main/kotlin/publish.gradle.kts index fa09c4dc..33bdc8b1 100644 --- a/buildSrc/src/main/kotlin/publish.gradle.kts +++ b/buildSrc/src/main/kotlin/publish.gradle.kts @@ -8,14 +8,16 @@ var libProjects = mutableSetOf( project(":jParser:jParser-build-tool"), project(":jParser:jParser-base"), project(":jParser:jParser-idl"), - project(":jParser:jParser-cpp"), + project(":jParser:jParser-jni"), + project(":jParser:jParser-ffm"), project(":jParser:jParser-teavm"), project(":idl:idl-core"), project(":idl:idl-teavm"), project(":idl-helper:idl-helper-base"), project(":idl-helper:idl-helper-core"), project(":idl-helper:idl-helper-teavm"), - project(":idl-helper:idl-helper-desktop"), + project(":idl-helper:idl-helper-desktop-jni"), + project(":idl-helper:idl-helper-desktop-ffm"), project(":idl-helper:idl-helper-android"), project(":loader:loader-core"), project(":loader:loader-teavm"), @@ -64,9 +66,9 @@ configure(libProjects) { } } scm { - connection.set("scm:git:git://github.com/xpenatan/jParser.git") - developerConnection.set("scm:git:ssh://github.com/xpenatan/jParser.git") - url.set("http://github.com/xpenatan/jParser/tree/master") + connection.set("scm:git@github.com:xpenatan/jParser.git") + developerConnection.set("scm:git@github.com:xpenatan/jParser.git") + url.set("http://github.com/xpenatan/jParser") } licenses { license { diff --git a/examples/SharedLib/app/core/build.gradle.kts b/examples/SharedLib/app/core/build.gradle.kts index d0c13e5a..36fcb787 100644 --- a/examples/SharedLib/app/core/build.gradle.kts +++ b/examples/SharedLib/app/core/build.gradle.kts @@ -8,14 +8,17 @@ java { } dependencies { - implementation(project(":examples:SharedLib:libA:lib-core")) - implementation(project(":examples:SharedLib:libB:lib-core")) + // compileOnly: app/core compiles against lib-core's API, but does NOT + // propagate it transitively. Each platform module (desktop-jni, desktop-ffm, + // android, teavm) provides the actual native bridge implementation. + compileOnly(project(":examples:SharedLib:libA:lib-core")) + compileOnly(project(":examples:SharedLib:libB:lib-core")) implementation("com.badlogicgames.gdx:gdx:${LibExt.gdxVersion}") - testImplementation(project(":examples:SharedLib:libA:lib-desktop")) - testImplementation(project(":examples:SharedLib:libB:lib-desktop")) - testImplementation(project(":idl-helper:idl-helper-desktop")) + testImplementation(project(":examples:SharedLib:libA:lib-desktop-jni")) + testImplementation(project(":examples:SharedLib:libB:lib-desktop-jni")) + testImplementation(project(":idl-helper:idl-helper-desktop-jni")) testImplementation("junit:junit:${LibExt.jUnitVersion}") } diff --git a/examples/SharedLib/app/desktop-ffm/build.gradle.kts b/examples/SharedLib/app/desktop-ffm/build.gradle.kts new file mode 100644 index 00000000..69774343 --- /dev/null +++ b/examples/SharedLib/app/desktop-ffm/build.gradle.kts @@ -0,0 +1,40 @@ +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform + +plugins { + id("java") +} + +java { + sourceCompatibility = JavaVersion.toVersion(24) + targetCompatibility = JavaVersion.toVersion(24) + toolchain { + languageVersion.set(JavaLanguageVersion.of(24)) + } +} + +val isMacOs = DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX + +dependencies { + implementation(project(":examples:SharedLib:app:core")) + + implementation("com.badlogicgames.gdx:gdx-platform:${LibExt.gdxVersion}:natives-desktop") + implementation("com.badlogicgames.gdx:gdx-backend-lwjgl3:${LibExt.gdxVersion}") + + implementation(project(":examples:SharedLib:libA:lib-desktop-ffm")) + implementation(project(":examples:SharedLib:libB:lib-desktop-ffm")) +} + +tasks.register("SharedLib_run_app_ffm_desktop") { + group = "example-desktop" + description = "Run desktop app with FFM bridge" + mainClass.set("com.github.xpenatan.jParser.example.app.Main") + classpath = sourceSets["main"].runtimeClasspath + javaLauncher.set(javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(24)) + }) + jvmArgs("--enable-native-access=ALL-UNNAMED") + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } +} \ No newline at end of file diff --git a/examples/SharedLib/app/desktop/src/main/java/com/github/xpenatan/jParser/example/app/Main.java b/examples/SharedLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/Main.java similarity index 100% rename from examples/SharedLib/app/desktop/src/main/java/com/github/xpenatan/jParser/example/app/Main.java rename to examples/SharedLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/Main.java diff --git a/examples/SharedLib/app/desktop/build.gradle.kts b/examples/SharedLib/app/desktop-jni/build.gradle.kts similarity index 78% rename from examples/SharedLib/app/desktop/build.gradle.kts rename to examples/SharedLib/app/desktop-jni/build.gradle.kts index d234ee39..5634c73c 100644 --- a/examples/SharedLib/app/desktop/build.gradle.kts +++ b/examples/SharedLib/app/desktop-jni/build.gradle.kts @@ -9,23 +9,25 @@ java { targetCompatibility = JavaVersion.toVersion(LibExt.java8Target) } +val isMacOs = DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX + dependencies { implementation(project(":examples:SharedLib:app:core")) - implementation(project(":examples:SharedLib:libA:lib-desktop")) - implementation(project(":examples:SharedLib:libB:lib-desktop")) - implementation(project(":idl-helper:idl-helper-desktop")) implementation("com.badlogicgames.gdx:gdx-platform:${LibExt.gdxVersion}:natives-desktop") implementation("com.badlogicgames.gdx:gdx-backend-lwjgl3:${LibExt.gdxVersion}") + + implementation(project(":examples:SharedLib:libA:lib-desktop-jni")) + implementation(project(":examples:SharedLib:libB:lib-desktop-jni")) } -tasks.register("SharedLib_run_app_desktop") { +tasks.register("SharedLib_run_app_jni_desktop") { group = "example-desktop" - description = "Run desktop app" + description = "Run desktop app with JNI bridge" mainClass.set("com.github.xpenatan.jParser.example.app.Main") classpath = sourceSets["main"].runtimeClasspath - - if(DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX) { + if(isMacOs) { jvmArgs("-XstartOnFirstThread") } -} \ No newline at end of file +} + diff --git a/examples/TestLib/app/desktop/src/main/java/com/github/xpenatan/jParser/example/app/Main.java b/examples/SharedLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/Main.java similarity index 84% rename from examples/TestLib/app/desktop/src/main/java/com/github/xpenatan/jParser/example/app/Main.java rename to examples/SharedLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/Main.java index aba2748d..3d12fa14 100644 --- a/examples/TestLib/app/desktop/src/main/java/com/github/xpenatan/jParser/example/app/Main.java +++ b/examples/SharedLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/Main.java @@ -7,6 +7,7 @@ public class Main { public static void main(String[] args) { Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); - new Lwjgl3Application(new AppTest(), config); + new Lwjgl3Application(new SharedLibApp(), config); } -} \ No newline at end of file +} + diff --git a/examples/SharedLib/app/teavm/build.gradle.kts b/examples/SharedLib/app/teavm/build.gradle.kts index 712a5f9d..7399a870 100644 --- a/examples/SharedLib/app/teavm/build.gradle.kts +++ b/examples/SharedLib/app/teavm/build.gradle.kts @@ -1,12 +1,5 @@ plugins { id("java") - id("org.gretty") version("4.1.10") -} - - -project.extra["webAppDir"] = File(projectDir, "build/dist/webapp") -gretty { - contextPath = "/" } java { @@ -18,24 +11,14 @@ dependencies { implementation(project(":examples:SharedLib:app:core")) implementation(project(":examples:SharedLib:libA:lib-teavm")) implementation(project(":examples:SharedLib:libB:lib-teavm")) - implementation(project(":idl-helper:idl-helper-teavm")) implementation("com.badlogicgames.gdx:gdx:${LibExt.gdxVersion}") - implementation("com.github.xpenatan.gdx-teavm:backend-teavm:${LibExt.gdxTeaVMVersion}") + implementation("com.github.xpenatan.gdx-teavm:backend-web:${LibExt.gdxTeaVMVersion}") } -tasks.register("SharedLib_build_app_teavm") { +tasks.register("SharedLib_run_app_teavm") { group = "example-teavm" description = "Build teavm app" mainClass.set("Build") classpath = sourceSets["main"].runtimeClasspath -} - -tasks.register("SharedLib_run_app_teavm") { - group = "example-teavm" - description = "Run teavm app" - val list = listOf("SharedLib_build_app_teavm", "jettyRun") - dependsOn(list) - - tasks.findByName("jettyRun")?.mustRunAfter("SharedLib_build_app_teavm") } \ No newline at end of file diff --git a/examples/SharedLib/app/teavm/src/main/java/Build.java b/examples/SharedLib/app/teavm/src/main/java/Build.java index 2b87e183..08c46b84 100644 --- a/examples/SharedLib/app/teavm/src/main/java/Build.java +++ b/examples/SharedLib/app/teavm/src/main/java/Build.java @@ -1,32 +1,21 @@ -import com.github.xpenatan.gdx.backends.teavm.config.AssetFileHandle; -import com.github.xpenatan.gdx.backends.teavm.config.TeaBuildConfiguration; -import com.github.xpenatan.gdx.backends.teavm.config.TeaBuilder; +import com.github.xpenatan.gdx.teavm.backends.shared.config.AssetFileHandle; +import com.github.xpenatan.gdx.teavm.backends.shared.config.compiler.TeaCompiler; +import com.github.xpenatan.gdx.teavm.backends.web.config.backend.WebBackend; import java.io.File; import java.io.IOException; -import org.teavm.tooling.TeaVMSourceFilePolicy; -import org.teavm.tooling.TeaVMTargetType; -import org.teavm.tooling.TeaVMTool; -import org.teavm.tooling.sources.DirectorySourceFileProvider; import org.teavm.vm.TeaVMOptimizationLevel; public class Build { public static void main(String[] args) throws IOException { - TeaBuildConfiguration teaBuildConfiguration = new TeaBuildConfiguration(); - teaBuildConfiguration.assetsPath.add(new AssetFileHandle("../desktop/assets")); - teaBuildConfiguration.webappPath = new File("build/dist").getCanonicalPath(); - teaBuildConfiguration.targetType = TeaVMTargetType.JAVASCRIPT; - TeaBuilder.config(teaBuildConfiguration); - - TeaVMTool tool = new TeaVMTool(); - tool.setObfuscated(false); - tool.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); - tool.setMainClass(TeaVMLauncher.class.getName()); - -// tool.setDebugInformationGenerated(true); -// tool.setSourceMapsFileGenerated(true); -// tool.setSourceFilePolicy(TeaVMSourceFilePolicy.COPY); - - TeaBuilder.build(tool); + AssetFileHandle assetsPath = new AssetFileHandle("../assets"); + WebBackend webBackend = new WebBackend(); + webBackend.setStartJettyAfterBuild(true); + new TeaCompiler(webBackend) + .addAssets(assetsPath) + .setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE) + .setMainClass(TeaVMLauncher.class.getName()) + .setObfuscated(false) + .build(new File("build/dist")); } } diff --git a/examples/SharedLib/app/teavm/src/main/java/TeaVMLauncher.java b/examples/SharedLib/app/teavm/src/main/java/TeaVMLauncher.java index 086a643f..1355899f 100644 --- a/examples/SharedLib/app/teavm/src/main/java/TeaVMLauncher.java +++ b/examples/SharedLib/app/teavm/src/main/java/TeaVMLauncher.java @@ -1,13 +1,13 @@ -import com.github.xpenatan.gdx.backends.teavm.TeaApplication; -import com.github.xpenatan.gdx.backends.teavm.TeaApplicationConfiguration; +import com.github.xpenatan.gdx.teavm.backends.web.WebApplication; +import com.github.xpenatan.gdx.teavm.backends.web.WebApplicationConfiguration; import com.github.xpenatan.jParser.example.app.SharedLibApp; public class TeaVMLauncher { public static void main(String[] args) { - TeaApplicationConfiguration config = new TeaApplicationConfiguration("canvas"); + WebApplicationConfiguration config = new WebApplicationConfiguration("canvas"); config.width = 0; config.height = 0; config.showDownloadLogs = true; - new TeaApplication(new SharedLibApp(), config); + new WebApplication(new SharedLibApp(), config); } } \ No newline at end of file diff --git a/examples/SharedLib/libA/lib-android/build.gradle.kts b/examples/SharedLib/libA/lib-android/build.gradle.kts index 5ae4435c..b1a4e149 100644 --- a/examples/SharedLib/libA/lib-android/build.gradle.kts +++ b/examples/SharedLib/libA/lib-android/build.gradle.kts @@ -33,4 +33,14 @@ android { } dependencies { + implementation(project(":loader:loader-core")) + implementation(project(":idl:idl-core")) + implementation(project(":idl-helper:idl-helper-android")) +} + +tasks.named("clean") { + doFirst { + val srcPath = "$projectDir/src/main/" + project.delete(files(srcPath)) + } } \ No newline at end of file diff --git a/examples/SharedLib/libA/lib-base/build.gradle.kts b/examples/SharedLib/libA/lib-base/build.gradle.kts index 8b0c9138..10ce2aa7 100644 --- a/examples/SharedLib/libA/lib-base/build.gradle.kts +++ b/examples/SharedLib/libA/lib-base/build.gradle.kts @@ -8,12 +8,6 @@ java { } dependencies { - if(LibExt.exampleUseRepoLibs) { - implementation("com.github.xpenatan.jParser:jParser-base:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:loader-core:-SNAPSHOT") - } - else { - implementation(project(":loader:loader-core")) - implementation(project(":idl:idl-core")) - } + implementation(project(":loader:loader-core")) + implementation(project(":idl:idl-core")) } \ No newline at end of file diff --git a/examples/SharedLib/libA/lib-base/src/main/java/libA/LibALoader.java b/examples/SharedLib/libA/lib-base/src/main/java/libA/LibALoader.java index eb2f0d44..47f864b2 100644 --- a/examples/SharedLib/libA/lib-base/src/main/java/libA/LibALoader.java +++ b/examples/SharedLib/libA/lib-base/src/main/java/libA/LibALoader.java @@ -11,6 +11,10 @@ public class LibALoader { #include "LibACustomCode.h" */ + /*[-FFM;-NATIVE] + #include "LibACustomCode.h" + */ + public static void init(JParserLibraryLoaderListener listener) { JParserLibraryLoader.load(LIB_NAME, listener); } diff --git a/examples/SharedLib/libA/lib-build/build.gradle.kts b/examples/SharedLib/libA/lib-build/build.gradle.kts index bbbd310e..68e53021 100644 --- a/examples/SharedLib/libA/lib-build/build.gradle.kts +++ b/examples/SharedLib/libA/lib-build/build.gradle.kts @@ -12,22 +12,13 @@ val mainClassName = "BuildLibA" dependencies { implementation(project(":examples:SharedLib:libA:lib-base")) - if(LibExt.exampleUseRepoLibs) { - implementation("com.github.xpenatan.jParser:jParser-core:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-idl:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-teavm:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-cpp:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-build:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-build-tool:-SNAPSHOT") - } - else { - implementation(project(":jParser:jParser-core")) - implementation(project(":jParser:jParser-idl")) - implementation(project(":jParser:jParser-teavm")) - implementation(project(":jParser:jParser-cpp")) - implementation(project(":jParser:jParser-build")) - implementation(project(":jParser:jParser-build-tool")) - } + implementation(project(":jParser:jParser-core")) + implementation(project(":jParser:jParser-idl")) + implementation(project(":jParser:jParser-teavm")) + implementation(project(":jParser:jParser-jni")) + implementation(project(":jParser:jParser-build")) + implementation(project(":jParser:jParser-build-tool")) + implementation(project(":jParser:jParser-ffm")) implementation(project(":idl-helper:idl-helper-core")) } @@ -40,66 +31,92 @@ tasks.register("LibA_build_project") { classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibA_build_project_all") { +tasks.register("LibA_build_project_teavm") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("teavm", "windows64", "linux64", "mac64", "macArm", "android", "ios") + args = mutableListOf("teavm") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibA_build_project_teavm") { +tasks.register("LibA_build_project_jni_windows64") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("teavm") + args = mutableListOf("jni_windows64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibA_build_project_windows64") { +tasks.register("LibA_build_project_jni_linux64") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("windows64") + args = mutableListOf("jni_linux64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibA_build_project_linux64") { +tasks.register("LibA_build_project_jni_mac64") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("linux64") + args = mutableListOf("jni_mac64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibA_build_project_mac64") { +tasks.register("LibA_build_project_jni_macArm") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("mac64") + args = mutableListOf("jni_macArm") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibA_build_project_macArm") { +tasks.register("LibA_build_project_jni_android") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("macArm") + args = mutableListOf("jni_android") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibA_build_project_android") { +tasks.register("LibA_build_project_jni_ios") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("android") + args = mutableListOf("jni_ios") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibA_build_project_ios") { +// FFM tasks — generate FFM Java code and compile native libs with FFMGlue + +tasks.register("LibA_build_project_ffm_windows64") { group = "lib" - description = "Generate native project" + description = "Generate FFM Java code and compile for Windows with FFMGlue" + mainClass.set(mainClassName) + args = mutableListOf("ffm_windows64") + classpath = sourceSets["main"].runtimeClasspath +} + +tasks.register("LibA_build_project_ffm_linux64") { + group = "lib" + description = "Generate FFM Java code and compile for Linux with FFMGlue" mainClass.set(mainClassName) - args = mutableListOf("ios") + args = mutableListOf("ffm_linux64") classpath = sourceSets["main"].runtimeClasspath -} \ No newline at end of file +} + +tasks.register("LibA_build_project_ffm_mac64") { + group = "lib" + description = "Generate FFM Java code and compile for Mac with FFMGlue" + mainClass.set(mainClassName) + args = mutableListOf("ffm_mac64") + classpath = sourceSets["main"].runtimeClasspath +} + +tasks.register("LibA_build_project_ffm_macArm") { + group = "lib" + description = "Generate FFM Java code and compile for Mac ARM with FFMGlue" + mainClass.set(mainClassName) + args = mutableListOf("ffm_macArm") + classpath = sourceSets["main"].runtimeClasspath +} diff --git a/examples/SharedLib/libA/lib-build/src/main/java/BuildLibA.java b/examples/SharedLib/libA/lib-build/src/main/java/BuildLibA.java index 50856cf4..c9105b6b 100644 --- a/examples/SharedLib/libA/lib-build/src/main/java/BuildLibA.java +++ b/examples/SharedLib/libA/lib-build/src/main/java/BuildLibA.java @@ -10,7 +10,6 @@ import com.github.xpenatan.jParser.builder.tool.BuildToolListener; import com.github.xpenatan.jParser.builder.tool.BuildToolOptions; import com.github.xpenatan.jParser.builder.tool.BuilderTool; -import com.github.xpenatan.jParser.core.JParser; import com.github.xpenatan.jParser.idl.IDLReader; import java.util.ArrayList; @@ -23,7 +22,6 @@ public static void main(String[] args) throws Exception { String sourceDir = "/src/main/cpp/source"; WindowsMSVCTarget.DEBUG_BUILD = true; - JParser.CREATE_IDL_HELPER = false; // NativeCPPGenerator.SKIP_GLUE_CODE = true; BuildToolOptions.BuildToolParams data = new BuildToolOptions.BuildToolParams(); @@ -36,65 +34,50 @@ public static void main(String[] args) throws Exception { BuildToolOptions op = new BuildToolOptions(data, args); op.addAdditionalIDLRefPath(IDLReader.getIDLHelperFile()); + BuilderTool.build(op, new BuildToolListener() { @Override public void onAddTarget(BuildToolOptions op, IDLReader idlReader, ArrayList targets) { if(op.containsArg("teavm")) { targets.add(getTeavmTarget(op, idlReader)); } - if(op.containsArg("windows64")) { - targets.add(getWindowVCTarget(op)); + if(op.containsArg("jni_windows64")) { + targets.add(getJNIWindowVCTarget(op)); // targets.add(getWindowTarget(op)); } - if(op.containsArg("linux64")) { - targets.add(getLinuxTarget(op)); + if(op.containsArg("jni_linux64")) { + targets.add(getJNILinuxTarget(op)); + } + if(op.containsArg("jni_mac64")) { + targets.add(getJNIMacTarget(op, false)); + } + if(op.containsArg("jni_macArm") ) { + targets.add(getJNIMacTarget(op, true)); + } + if(op.containsArg("jni_android")) { + targets.add(getJNIAndroidTarget(op)); } - if(op.containsArg("mac64")) { - targets.add(getMacTarget(op, false)); + if(op.containsArg("jni_ios")) { + targets.add(getJNIIOSTarget(op)); } - if(op.containsArg("macArm")) { - targets.add(getMacTarget(op, true)); + + if(op.containsArg("ffm_windows64")) { + targets.add(getFFMWindowVCTarget(op)); + } + if(op.containsArg("ffm_linux64")) { + targets.add(getFFMLinuxTarget(op)); } - if(op.containsArg("android")) { - targets.add(getAndroidTarget(op)); + if(op.containsArg("ffm_mac64")) { + targets.add(getFFMMacTarget(op, false)); } - if(op.containsArg("ios")) { - targets.add(getIOSTarget(op)); + if(op.containsArg("ffm_macArm")) { + targets.add(getFFMMacTarget(op, true)); } } }); } - private static BuildMultiTarget getWindowTarget(BuildToolOptions op) { - BuildMultiTarget multiTarget = new BuildMultiTarget(); - String sourceDir = op.getSourceDir(); - String libBuildCPPPath = op.getModuleBuildCPPPath(); - - // Make a static library - WindowsTarget compileStaticTarget = new WindowsTarget(); - compileStaticTarget.isStatic = true; - compileStaticTarget.cppFlags.add("-std=c++11"); - compileStaticTarget.headerDirs.add("-I" + sourceDir); - compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); - compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); - compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); - multiTarget.add(compileStaticTarget); - - WindowsTarget linkTarget = new WindowsTarget(); - linkTarget.addJNIHeaders(); - linkTarget.cppFlags.add("-std=c++11"); - linkTarget.headerDirs.add("-I" + sourceDir); - linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); - linkTarget.linkerFlags.add("-Wl,--whole-archive"); - linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/windows/" + op.libName + "64_.a"); - linkTarget.linkerFlags.add("-Wl,--no-whole-archive"); - linkTarget.cppInclude.add(libBuildCPPPath + "/src/jniglue/JNIGlue.cpp"); - multiTarget.add(linkTarget); - - return multiTarget; - } - - private static BuildMultiTarget getWindowVCTarget(BuildToolOptions op) { + private static BuildMultiTarget getJNIWindowVCTarget(BuildToolOptions op) { BuildMultiTarget multiTarget = new BuildMultiTarget(); String sourceDir = op.getSourceDir(); String libBuildCPPPath = op.getModuleBuildCPPPath(); @@ -103,6 +86,7 @@ private static BuildMultiTarget getWindowVCTarget(BuildToolOptions op) { // Make a static library WindowsMSVCTarget compileStaticTarget = new WindowsMSVCTarget(); + compileStaticTarget.libDirSuffix = "windows/vc/jni"; compileStaticTarget.isStatic = true; compileStaticTarget.cppFlags.add("/std:c++11"); compileStaticTarget.cppFlags.add("/DLIBA_EXPORTS"); @@ -114,12 +98,13 @@ private static BuildMultiTarget getWindowVCTarget(BuildToolOptions op) { multiTarget.add(compileStaticTarget); WindowsMSVCTarget linkTarget = new WindowsMSVCTarget(); + linkTarget.libDirSuffix = "windows/vc/jni"; linkTarget.addJNIHeaders(); linkTarget.cppFlags.add("/std:c++11"); linkTarget.headerDirs.add("-I" + sourceDir); linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); linkTarget.headerDirs.add("-I" + libBuildCPPPath + "/src/jniglue"); - linkTarget.linkerFlags.add("/WHOLEARCHIVE:" + libBuildCPPPath + "/libs/windows/vc/" + op.libName + "64_.lib"); + linkTarget.linkerFlags.add("/WHOLEARCHIVE:" + libBuildCPPPath + "/libs/windows/vc/jni/" + op.libName + "64_.lib"); linkTarget.linkerFlags.add("-DLL"); linkTarget.cppInclude.add(libBuildCPPPath + "/src/jniglue/JNIGlue.cpp"); multiTarget.add(linkTarget); @@ -127,7 +112,7 @@ private static BuildMultiTarget getWindowVCTarget(BuildToolOptions op) { return multiTarget; } - private static BuildMultiTarget getLinuxTarget(BuildToolOptions op) { + private static BuildMultiTarget getJNILinuxTarget(BuildToolOptions op) { BuildMultiTarget multiTarget = new BuildMultiTarget(); String sourceDir = op.getSourceDir(); String libBuildCPPPath = op.getModuleBuildCPPPath(); @@ -136,6 +121,7 @@ private static BuildMultiTarget getLinuxTarget(BuildToolOptions op) { // Make a static library LinuxTarget compileStaticTarget = new LinuxTarget(); + compileStaticTarget.libDirSuffix = "linux/jni"; compileStaticTarget.isStatic = true; compileStaticTarget.cppFlags.add("-std=c++11"); compileStaticTarget.cppFlags.add(config); @@ -148,6 +134,7 @@ private static BuildMultiTarget getLinuxTarget(BuildToolOptions op) { multiTarget.add(compileStaticTarget); LinuxTarget linkTarget = new LinuxTarget(); + linkTarget.libDirSuffix = "linux/jni"; linkTarget.addJNIHeaders(); linkTarget.cppFlags.add("-std=c++11"); linkTarget.cppFlags.add("-fPIC"); @@ -157,7 +144,7 @@ private static BuildMultiTarget getLinuxTarget(BuildToolOptions op) { linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); linkTarget.headerDirs.add("-I" + libBuildCPPPath + "/src/jniglue"); linkTarget.linkerFlags.add("-Wl,-soname,libLibA64.so"); - linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/linux/lib" + op.libName + "64_.a"); + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/linux/jni/lib" + op.libName + "64_.a"); linkTarget.cppInclude.add(libBuildCPPPath + "/src/jniglue/JNIGlue.cpp"); multiTarget.add(linkTarget); @@ -165,15 +152,18 @@ private static BuildMultiTarget getLinuxTarget(BuildToolOptions op) { return multiTarget; } - private static BuildMultiTarget getMacTarget(BuildToolOptions op, boolean isArm) { + private static BuildMultiTarget getJNIMacTarget(BuildToolOptions op, boolean isArm) { BuildMultiTarget multiTarget = new BuildMultiTarget(); String sourceDir = op.getSourceDir(); String libBuildCPPPath = op.getModuleBuildCPPPath(); String config = "-DLIB_USER_CONFIG=\"LibACustomConfig.h\""; + String macSubDir = isArm ? "mac/arm/ffm" : "mac/ffm"; + // Make a static library MacTarget compileStaticTarget = new MacTarget(isArm); + compileStaticTarget.libDirSuffix = macSubDir; compileStaticTarget.isStatic = true; compileStaticTarget.cppFlags.add("-std=c++11"); compileStaticTarget.cppFlags.add(config); @@ -185,6 +175,7 @@ private static BuildMultiTarget getMacTarget(BuildToolOptions op, boolean isArm) multiTarget.add(compileStaticTarget); MacTarget linkTarget = new MacTarget(isArm); + compileStaticTarget.libDirSuffix = macSubDir; linkTarget.addJNIHeaders(); linkTarget.cppFlags.add("-std=c++11"); linkTarget.cppFlags.add(config); @@ -249,7 +240,7 @@ private static BuildMultiTarget getTeavmTarget(BuildToolOptions op, IDLReader id return multiTarget; } - private static BuildMultiTarget getAndroidTarget(BuildToolOptions op) { + private static BuildMultiTarget getJNIAndroidTarget(BuildToolOptions op) { BuildMultiTarget multiTarget = new BuildMultiTarget(); String sourceDir = op.getSourceDir(); String libBuildCPPPath = op.getModuleBuildCPPPath(); @@ -305,7 +296,7 @@ private static BuildMultiTarget getAndroidTarget(BuildToolOptions op) { return multiTarget; } - private static BuildMultiTarget getIOSTarget(BuildToolOptions op) { + private static BuildMultiTarget getJNIIOSTarget(BuildToolOptions op) { BuildMultiTarget multiTarget = new BuildMultiTarget(); String sourceDir = op.getSourceDir(); String libBuildCPPPath = op.getModuleBuildCPPPath(); @@ -335,4 +326,122 @@ private static BuildMultiTarget getIOSTarget(BuildToolOptions op) { return multiTarget; } + + // ==================== FFM Build Targets ==================== + // These compile the same native C++ source but link with FFMGlue.cpp instead of JNIGlue.cpp. + // No JNI headers are needed. Output goes to /ffm/ to avoid conflicts with JNI libs. + + private static BuildMultiTarget getFFMWindowVCTarget(BuildToolOptions op) { + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String sourceDir = op.getSourceDir(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + String config = "/DLIB_USER_CONFIG=\"\\\"LibACustomConfig.h\\\"\""; + + // Make a static library + WindowsMSVCTarget compileStaticTarget = new WindowsMSVCTarget(); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = "windows/vc/ffm"; + compileStaticTarget.cppFlags.add("/std:c++11"); + compileStaticTarget.cppFlags.add("/DLIBA_EXPORTS"); + compileStaticTarget.cppFlags.add(config); + compileStaticTarget.headerDirs.add("-I" + sourceDir); + compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); + compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); + multiTarget.add(compileStaticTarget); + + // Link with FFMGlue instead of JNIGlue — no JNI headers + WindowsMSVCTarget linkTarget = new WindowsMSVCTarget(); + linkTarget.libDirSuffix = "windows/vc/ffm"; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("/std:c++11"); + linkTarget.headerDirs.add("-I" + sourceDir); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.linkerFlags.add("/WHOLEARCHIVE:" + libBuildCPPPath + "/libs/windows/vc/ffm/" + op.libName + "64_.lib"); + linkTarget.linkerFlags.add("-DLL"); + multiTarget.add(linkTarget); + + return multiTarget; + } + + private static BuildMultiTarget getFFMLinuxTarget(BuildToolOptions op) { + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String sourceDir = op.getSourceDir(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + String config = "-DLIB_USER_CONFIG=\"LibACustomConfig.h\""; + + // Make a static library + LinuxTarget compileStaticTarget = new LinuxTarget(); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = "linux/ffm"; + compileStaticTarget.cppFlags.add("-std=c++11"); + compileStaticTarget.cppFlags.add(config); + compileStaticTarget.cppFlags.add("-fPIC"); + compileStaticTarget.cppFlags.add("-fvisibility=hidden"); + compileStaticTarget.headerDirs.add("-I" + sourceDir); + compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); + compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); + multiTarget.add(compileStaticTarget); + + // Link with FFMGlue instead of JNIGlue — no JNI headers + LinuxTarget linkTarget = new LinuxTarget(); + linkTarget.libDirSuffix = "linux/ffm"; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("-std=c++11"); + linkTarget.cppFlags.add(config); + linkTarget.cppFlags.add("-fPIC"); + linkTarget.cppFlags.add("-fvisibility=hidden"); + linkTarget.headerDirs.add("-I" + sourceDir); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.linkerFlags.add("-Wl,-soname,libLibA64.so"); + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/linux/ffm/lib" + op.libName + "64_.a"); + multiTarget.add(linkTarget); + + return multiTarget; + } + + private static BuildMultiTarget getFFMMacTarget(BuildToolOptions op, boolean isArm) { + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String sourceDir = op.getSourceDir(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + String config = "-DLIB_USER_CONFIG=\"LibACustomConfig.h\""; + String macSubDir = isArm ? "mac/arm/ffm" : "mac/ffm"; + + // Make a static library + MacTarget compileStaticTarget = new MacTarget(isArm); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = macSubDir; + compileStaticTarget.cppFlags.add("-std=c++11"); + compileStaticTarget.cppFlags.add(config); + compileStaticTarget.cppFlags.add("-fPIC"); + compileStaticTarget.headerDirs.add("-I" + sourceDir); + compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); + compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); + multiTarget.add(compileStaticTarget); + + // Link with FFMGlue instead of JNIGlue — no JNI headers + MacTarget linkTarget = new MacTarget(isArm); + linkTarget.libDirSuffix = macSubDir; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("-std=c++11"); + linkTarget.cppFlags.add(config); + linkTarget.cppFlags.add("-fPIC"); + linkTarget.headerDirs.add("-I" + sourceDir); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.linkerFlags.add("-Wl,-force_load"); + if(isArm) { + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/mac/arm/ffm/lib" + op.libName + "64_.a"); + } + else { + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/mac/ffm/lib" + op.libName + "64_.a"); + } + multiTarget.add(linkTarget); + + return multiTarget; + } } \ No newline at end of file diff --git a/examples/SharedLib/libA/lib-core/build.gradle.kts b/examples/SharedLib/libA/lib-core/build.gradle.kts index 965a77da..d4952f3d 100644 --- a/examples/SharedLib/libA/lib-core/build.gradle.kts +++ b/examples/SharedLib/libA/lib-core/build.gradle.kts @@ -9,14 +9,8 @@ java { } dependencies { - if(LibExt.exampleUseRepoLibs) { - api("com.github.xpenatan.jParser:loader-core:-SNAPSHOT") - api("com.github.xpenatan.jParser:idl-core:-SNAPSHOT") - } - else { - api(project(":loader:loader-core")) - api(project(":idl:idl-core")) - } + api(project(":loader:loader-core")) + api(project(":idl:idl-core")) api(project(":idl-helper:idl-helper-core")) } diff --git a/examples/SharedLib/libA/lib-desktop-ffm/build.gradle.kts b/examples/SharedLib/libA/lib-desktop-ffm/build.gradle.kts new file mode 100644 index 00000000..3c32e320 --- /dev/null +++ b/examples/SharedLib/libA/lib-desktop-ffm/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + id("java") + id("java-library") +} + +java { + sourceCompatibility = JavaVersion.toVersion(LibExt.java24Target) + targetCompatibility = JavaVersion.toVersion(LibExt.java24Target) +} + +dependencies { + api(project(":loader:loader-core")) + api(project(":idl:idl-core")) + api(project(":idl-helper:idl-helper-desktop-ffm")) +} + +// Bundle FFM-compiled native libraries into the JAR. +val libDir = "${projectDir}/../lib-build/build/c++/libs" +val windowsFile = "$libDir/windows/vc/ffm/LibA64.dll" +val linuxFile = "$libDir/linux/ffm/libLibA64.so" +val macFile = "$libDir/mac/ffm/libLibA64.dylib" +val macArmFile = "$libDir/mac/arm/ffm/libLibAarm64.dylib" + +tasks.jar { + from(windowsFile) + from(linuxFile) + from(macFile) + from(macArmFile) +} + +tasks.named("clean") { + doFirst { + val srcPath = "$projectDir/src/main/" + project.delete(files(srcPath)) + } +} diff --git a/examples/SharedLib/libA/lib-desktop/build.gradle.kts b/examples/SharedLib/libA/lib-desktop-jni/build.gradle.kts similarity index 56% rename from examples/SharedLib/libA/lib-desktop/build.gradle.kts rename to examples/SharedLib/libA/lib-desktop-jni/build.gradle.kts index 5e43f43b..c61b78bf 100644 --- a/examples/SharedLib/libA/lib-desktop/build.gradle.kts +++ b/examples/SharedLib/libA/lib-desktop-jni/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("java") + id("java-library") } java { @@ -8,11 +9,10 @@ java { } val libDir = "${projectDir}/../lib-build/build/c++/libs" -//val windowsFile = "$libDir/windows/LibA64.dll" -val windowsFile = "$libDir/windows/vc/LibA64.dll" -val linuxFile = "$libDir/linux/libLibA64.so" -val macFile = "$libDir/mac/libLibA64.dylib" -val macArmFile = "$libDir/mac/arm/libLibAarm64.dylib" +val windowsFile = "$libDir/windows/vc/jni/LibA64.dll" +val linuxFile = "$libDir/linux/jni/libLibA64.so" +val macFile = "$libDir/mac/jni/libLibA64.dylib" +val macArmFile = "$libDir/mac/arm/jni/libLibAarm64.dylib" tasks.jar { from(windowsFile) @@ -22,6 +22,9 @@ tasks.jar { } dependencies { + api(project(":loader:loader-core")) + api(project(":idl:idl-core")) + api(project(":idl-helper:idl-helper-desktop-jni")) } tasks.named("clean") { diff --git a/examples/SharedLib/libA/lib-teavm/build.gradle.kts b/examples/SharedLib/libA/lib-teavm/build.gradle.kts index 1ec8eed3..b22dd18f 100644 --- a/examples/SharedLib/libA/lib-teavm/build.gradle.kts +++ b/examples/SharedLib/libA/lib-teavm/build.gradle.kts @@ -18,18 +18,10 @@ dependencies { implementation("org.teavm:teavm-jso:${LibExt.teaVMVersion}") implementation("org.teavm:teavm-classlib:${LibExt.teaVMVersion}") - if(LibExt.exampleUseRepoLibs) { - implementation("com.github.xpenatan.jParser:loader-teavm:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:loader-core:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:idl-teavm:-SNAPSHOT") - } - else { - implementation(project(":loader:loader-teavm")) - implementation(project(":loader:loader-core")) - implementation(project(":idl:idl-teavm")) - } - - implementation(project(":idl-helper:idl-helper-teavm")) + implementation(project(":loader:loader-teavm")) + implementation(project(":loader:loader-core")) + implementation(project(":idl:idl-teavm")) + api(project(":idl-helper:idl-helper-teavm")) } tasks.named("clean") { diff --git a/examples/SharedLib/libB/lib-android/build.gradle.kts b/examples/SharedLib/libB/lib-android/build.gradle.kts index a5988b39..8d1c4c54 100644 --- a/examples/SharedLib/libB/lib-android/build.gradle.kts +++ b/examples/SharedLib/libB/lib-android/build.gradle.kts @@ -33,4 +33,15 @@ android { } dependencies { + implementation(project(":loader:loader-core")) + implementation(project(":idl:idl-core")) + implementation(project(":idl-helper:idl-helper-android")) + implementation(project(":examples:SharedLib:libA:lib-android")) +} + +tasks.named("clean") { + doFirst { + val srcPath = "$projectDir/src/main/" + project.delete(files(srcPath)) + } } \ No newline at end of file diff --git a/examples/SharedLib/libB/lib-base/build.gradle.kts b/examples/SharedLib/libB/lib-base/build.gradle.kts index 8b0c9138..10ce2aa7 100644 --- a/examples/SharedLib/libB/lib-base/build.gradle.kts +++ b/examples/SharedLib/libB/lib-base/build.gradle.kts @@ -8,12 +8,6 @@ java { } dependencies { - if(LibExt.exampleUseRepoLibs) { - implementation("com.github.xpenatan.jParser:jParser-base:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:loader-core:-SNAPSHOT") - } - else { - implementation(project(":loader:loader-core")) - implementation(project(":idl:idl-core")) - } + implementation(project(":loader:loader-core")) + implementation(project(":idl:idl-core")) } \ No newline at end of file diff --git a/examples/SharedLib/libB/lib-base/src/main/java/libB/LibBLoader.java b/examples/SharedLib/libB/lib-base/src/main/java/libB/LibBLoader.java index faeb11e1..eb40c732 100644 --- a/examples/SharedLib/libB/lib-base/src/main/java/libB/LibBLoader.java +++ b/examples/SharedLib/libB/lib-base/src/main/java/libB/LibBLoader.java @@ -9,6 +9,10 @@ public class LibBLoader { #include "LibBCustomCode.h" */ + /*[-FFM;-NATIVE] + #include "LibBCustomCode.h" + */ + public static final String LIB_NAME = "LibB"; public static void init(JParserLibraryLoaderListener listener) { diff --git a/examples/SharedLib/libB/lib-build/build.gradle.kts b/examples/SharedLib/libB/lib-build/build.gradle.kts index 7470e797..83092c85 100644 --- a/examples/SharedLib/libB/lib-build/build.gradle.kts +++ b/examples/SharedLib/libB/lib-build/build.gradle.kts @@ -13,23 +13,13 @@ dependencies { implementation(project(":examples:SharedLib:libB:lib-base")) implementation(project(":examples:SharedLib:libA:lib-core")) - - if(LibExt.exampleUseRepoLibs) { - implementation("com.github.xpenatan.jParser:jParser-core:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-idl:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-teavm:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-cpp:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-build:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-build-tool:-SNAPSHOT") - } - else { - implementation(project(":jParser:jParser-core")) - implementation(project(":jParser:jParser-idl")) - implementation(project(":jParser:jParser-teavm")) - implementation(project(":jParser:jParser-cpp")) - implementation(project(":jParser:jParser-build")) - implementation(project(":jParser:jParser-build-tool")) - } + implementation(project(":jParser:jParser-core")) + implementation(project(":jParser:jParser-idl")) + implementation(project(":jParser:jParser-teavm")) + implementation(project(":jParser:jParser-jni")) + implementation(project(":jParser:jParser-build")) + implementation(project(":jParser:jParser-build-tool")) + implementation(project(":jParser:jParser-ffm")) implementation(project(":idl-helper:idl-helper-core")) } @@ -42,66 +32,92 @@ tasks.register("LibB_build_project") { classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibB_build_project_all") { +tasks.register("LibB_build_project_teavm") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("teavm", "windows64", "linux64", "mac64", "macArm", "android", "ios") + args = mutableListOf("teavm") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibB_build_project_teavm") { +tasks.register("LibB_build_project_jni_windows64") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("teavm") + args = mutableListOf("jni_windows64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibB_build_project_windows64") { +tasks.register("LibB_build_project_jni_linux64") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("windows64") + args = mutableListOf("jni_linux64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibB_build_project_linux64") { +tasks.register("LibB_build_project_jni_mac64") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("linux64") + args = mutableListOf("jni_mac64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibB_build_project_mac64") { +tasks.register("LibB_build_project_jni_macArm") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("mac64") + args = mutableListOf("jni_macArm") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibB_build_project_macArm") { +tasks.register("LibB_build_project_jni_android") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("macArm") + args = mutableListOf("jni_android") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibB_build_project_android") { +tasks.register("LibB_build_project_jni_ios") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("android") + args = mutableListOf("jni_ios") + classpath = sourceSets["main"].runtimeClasspath +} + +// FFM tasks — generate FFM Java code and/or compile native libs with FFMGlue + +tasks.register("LibB_build_project_ffm_windows64") { + group = "lib" + description = "Generate FFM Java code and compile for Windows with FFMGlue" + mainClass.set(mainClassName) + args = mutableListOf("ffm_windows64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("LibB_build_project_ios") { +tasks.register("LibB_build_project_ffm_linux64") { group = "lib" - description = "Generate native project" + description = "Generate FFM Java code and compile for Linux with FFMGlue" mainClass.set(mainClassName) - args = mutableListOf("ios") + args = mutableListOf("ffm_linux64") classpath = sourceSets["main"].runtimeClasspath -} \ No newline at end of file +} + +tasks.register("LibB_build_project_ffm_mac64") { + group = "lib" + description = "Generate FFM Java code and compile for Mac with FFMGlue" + mainClass.set(mainClassName) + args = mutableListOf("ffm_mac64") + classpath = sourceSets["main"].runtimeClasspath +} + +tasks.register("LibB_build_project_ffm_macArm") { + group = "lib" + description = "Generate FFM Java code and compile for Mac ARM with FFMGlue" + mainClass.set(mainClassName) + args = mutableListOf("ffm_macArm") + classpath = sourceSets["main"].runtimeClasspath +} diff --git a/examples/SharedLib/libB/lib-build/src/main/java/BuildLibB.java b/examples/SharedLib/libB/lib-build/src/main/java/BuildLibB.java index 989ac21d..ec26dc60 100644 --- a/examples/SharedLib/libB/lib-build/src/main/java/BuildLibB.java +++ b/examples/SharedLib/libB/lib-build/src/main/java/BuildLibB.java @@ -10,7 +10,6 @@ import com.github.xpenatan.jParser.builder.tool.BuildToolListener; import com.github.xpenatan.jParser.builder.tool.BuildToolOptions; import com.github.xpenatan.jParser.builder.tool.BuilderTool; -import com.github.xpenatan.jParser.core.JParser; import com.github.xpenatan.jParser.idl.IDLReader; import java.io.File; import java.util.ArrayList; @@ -24,7 +23,6 @@ public static void main(String[] args) throws Exception { String sourceDir = "/src/main/cpp/source"; WindowsMSVCTarget.DEBUG_BUILD = false; - JParser.CREATE_IDL_HELPER = false; // NativeCPPGenerator.SKIP_GLUE_CODE = true; String libAPath = new File("./../../libA/").getCanonicalPath().replace("\\", "/"); @@ -48,63 +46,44 @@ public void onAddTarget(BuildToolOptions op, IDLReader idlReader, ArrayList/ffm/ to avoid conflicts with JNI libs. + + private static BuildMultiTarget getFFMWindowVCTarget(BuildToolOptions op, String libAPath) { + String libALibPath = libAPath + "/lib-build/build/c++/libs/windows/vc/ffm"; + String libACPPPath = libAPath + "/lib-build/src/main/cpp"; + String libASourcePath = libACPPPath + "/source"; + String libACustomPath = libACPPPath + "/custom"; + + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String sourceDir = op.getSourceDir(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + String config = "/DLIB_USER_CONFIG=\"\\\"LibACustomConfig.h\\\"\""; + + // Make a static library + WindowsMSVCTarget compileStaticTarget = new WindowsMSVCTarget(); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = "windows/vc/ffm"; + compileStaticTarget.cppFlags.add("/std:c++11"); + compileStaticTarget.headerDirs.add("-I" + sourceDir); + compileStaticTarget.headerDirs.add("-I" + libASourcePath); + compileStaticTarget.headerDirs.add("-I" + libACustomPath); + compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); + multiTarget.add(compileStaticTarget); + + // Link with FFMGlue instead of JNIGlue — no JNI headers + WindowsMSVCTarget linkTarget = new WindowsMSVCTarget(); + linkTarget.libDirSuffix = "windows/vc/ffm"; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("/std:c++11"); + linkTarget.cppFlags.add(config); + linkTarget.headerDirs.add("-I" + sourceDir); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.headerDirs.add("-I" + libASourcePath); + linkTarget.headerDirs.add("-I" + libACustomPath); + linkTarget.linkerFlags.add("/WHOLEARCHIVE:" + libALibPath + "/LibA64.lib"); + linkTarget.linkerFlags.add("/WHOLEARCHIVE:" + libBuildCPPPath + "/libs/windows/vc/ffm/" + op.libName + "64_.lib"); + linkTarget.linkerFlags.add("-DLL"); + multiTarget.add(linkTarget); + + return multiTarget; + } + + private static BuildMultiTarget getFFMLinuxTarget(BuildToolOptions op, String libAPath) { + String libALibPath = libAPath + "/lib-build/build/c++/libs/linux/ffm"; + String libACPPPath = libAPath + "/lib-build/src/main/cpp"; + String libASourcePath = libACPPPath + "/source"; + String libACustomPath = libACPPPath + "/custom"; + + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String sourceDir = op.getSourceDir(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + String config = "-DLIB_USER_CONFIG=\"LibACustomConfig.h\""; + + // Make a static library + LinuxTarget compileStaticTarget = new LinuxTarget(); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = "linux/ffm"; + compileStaticTarget.cppFlags.add("-std=c++11"); + compileStaticTarget.cppFlags.add(config); + compileStaticTarget.cppFlags.add("-fPIC"); + compileStaticTarget.cppFlags.add("-fvisibility=hidden"); + compileStaticTarget.headerDirs.add("-I" + sourceDir); + compileStaticTarget.headerDirs.add("-I" + libASourcePath); + compileStaticTarget.headerDirs.add("-I" + libACustomPath); + compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); + multiTarget.add(compileStaticTarget); + + // Link with FFMGlue instead of JNIGlue — no JNI headers + LinuxTarget linkTarget = new LinuxTarget(); + linkTarget.libDirSuffix = "linux/ffm"; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("-std=c++11"); + linkTarget.cppFlags.add(config); + linkTarget.cppFlags.add("-fPIC"); + linkTarget.cppFlags.add("-fvisibility=hidden"); + linkTarget.headerDirs.add("-I" + sourceDir); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.headerDirs.add("-I" + libASourcePath); + linkTarget.headerDirs.add("-I" + libACustomPath); + linkTarget.linkerFlags.add(libALibPath + "/libLibA64.so"); + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/linux/ffm/lib" + op.libName + "64_.a"); + multiTarget.add(linkTarget); + + return multiTarget; + } + + private static BuildMultiTarget getFFMMacTarget(BuildToolOptions op, boolean isArm, String libAPath) { + String libALibPath = libAPath + "/lib-build/build/c++/libs/mac/ffm"; + String libALibArmPath = libAPath + "/lib-build/build/c++/libs/mac/arm/ffm"; + String libACPPPath = libAPath + "/lib-build/src/main/cpp"; + String libASourcePath = libACPPPath + "/source"; + String libACustomPath = libACPPPath + "/custom"; + + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String sourceDir = op.getSourceDir(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + String config = "-DLIB_USER_CONFIG=\"LibACustomConfig.h\""; + String macSubDir = isArm ? "mac/arm/ffm" : "mac/ffm"; + + // Make a static library + MacTarget compileStaticTarget = new MacTarget(isArm); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = macSubDir; + compileStaticTarget.cppFlags.add("-std=c++11"); + compileStaticTarget.cppFlags.add(config); + compileStaticTarget.cppFlags.add("-fPIC"); + compileStaticTarget.headerDirs.add("-I" + sourceDir); + compileStaticTarget.headerDirs.add("-I" + libASourcePath); + compileStaticTarget.headerDirs.add("-I" + libACustomPath); + compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); + multiTarget.add(compileStaticTarget); + + // Link with FFMGlue instead of JNIGlue — no JNI headers + MacTarget linkTarget = new MacTarget(isArm); + linkTarget.libDirSuffix = macSubDir; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("-std=c++11"); + linkTarget.cppFlags.add(config); + linkTarget.cppFlags.add("-fPIC"); + linkTarget.headerDirs.add("-I" + sourceDir); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.headerDirs.add("-I" + libASourcePath); + linkTarget.headerDirs.add("-I" + libACustomPath); + + if(isArm) { + linkTarget.linkerFlags.add(libALibArmPath + "/libLibA64.dylib"); + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/mac/arm/ffm/lib" + op.libName + "64_.a"); + } + else { + linkTarget.linkerFlags.add(libALibPath + "/libLibA64.dylib"); + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/mac/ffm/lib" + op.libName + "64_.a"); + } + multiTarget.add(linkTarget); + + return multiTarget; + } } diff --git a/examples/SharedLib/libB/lib-core/build.gradle.kts b/examples/SharedLib/libB/lib-core/build.gradle.kts index 2ed194d5..b89932be 100644 --- a/examples/SharedLib/libB/lib-core/build.gradle.kts +++ b/examples/SharedLib/libB/lib-core/build.gradle.kts @@ -12,14 +12,8 @@ dependencies { implementation(project(":examples:SharedLib:libA:lib-core")) - if(LibExt.exampleUseRepoLibs) { - api("com.github.xpenatan.jParser:loader-core:-SNAPSHOT") - api("com.github.xpenatan.jParser:idl-core:-SNAPSHOT") - } - else { - api(project(":loader:loader-core")) - api(project(":idl:idl-core")) - } + api(project(":loader:loader-core")) + api(project(":idl:idl-core")) implementation(project(":idl-helper:idl-helper-core")) } diff --git a/examples/SharedLib/libB/lib-desktop-ffm/build.gradle.kts b/examples/SharedLib/libB/lib-desktop-ffm/build.gradle.kts new file mode 100644 index 00000000..33232a58 --- /dev/null +++ b/examples/SharedLib/libB/lib-desktop-ffm/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("java") + id("java-library") +} + +java { + sourceCompatibility = JavaVersion.toVersion(LibExt.java24Target) + targetCompatibility = JavaVersion.toVersion(LibExt.java24Target) +} + +dependencies { + implementation(project(":examples:SharedLib:libA:lib-desktop-ffm")) + api(project(":loader:loader-core")) + api(project(":idl:idl-core")) + api(project(":idl-helper:idl-helper-desktop-ffm")) +} + +// Bundle FFM-compiled native libraries into the JAR. +val libDir = "${projectDir}/../lib-build/build/c++/libs" +val windowsFile = "$libDir/windows/vc/ffm/LibB64.dll" +val linuxFile = "$libDir/linux/ffm/libLibB64.so" +val macFile = "$libDir/mac/ffm/libLibB64.dylib" +val macArmFile = "$libDir/mac/arm/ffm/libLibBarm64.dylib" + +tasks.jar { + from(windowsFile) + from(linuxFile) + from(macFile) + from(macArmFile) +} + +tasks.named("clean") { + doFirst { + val srcPath = "$projectDir/src/main/" + project.delete(files(srcPath)) + } +} diff --git a/examples/SharedLib/libB/lib-desktop/build.gradle.kts b/examples/SharedLib/libB/lib-desktop-jni/build.gradle.kts similarity index 52% rename from examples/SharedLib/libB/lib-desktop/build.gradle.kts rename to examples/SharedLib/libB/lib-desktop-jni/build.gradle.kts index e9480b55..415e8128 100644 --- a/examples/SharedLib/libB/lib-desktop/build.gradle.kts +++ b/examples/SharedLib/libB/lib-desktop-jni/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("java") + id("java-library") } java { @@ -8,11 +9,10 @@ java { } val libDir = "${projectDir}/../lib-build/build/c++/libs" -//val windowsFile = "$libDir/windows/LibB64.dll" -val windowsFile = "$libDir/windows/vc/LibB64.dll" -val linuxFile = "$libDir/linux/libLibB64.so" -val macFile = "$libDir/mac/libLibB64.dylib" -val macArmFile = "$libDir/mac/arm/libLibBarm64.dylib" +val windowsFile = "$libDir/windows/vc/jni/LibB64.dll" +val linuxFile = "$libDir/linux/jni/libLibB64.so" +val macFile = "$libDir/mac/jni/libLibB64.dylib" +val macArmFile = "$libDir/mac/arm/jni/libLibBarm64.dylib" tasks.jar { from(windowsFile) @@ -22,6 +22,10 @@ tasks.jar { } dependencies { + implementation(project(":examples:SharedLib:libA:lib-desktop-jni")) + api(project(":loader:loader-core")) + api(project(":idl:idl-core")) + api(project(":idl-helper:idl-helper-desktop-jni")) } tasks.named("clean") { diff --git a/examples/SharedLib/libB/lib-teavm/build.gradle.kts b/examples/SharedLib/libB/lib-teavm/build.gradle.kts index ce310a54..4bd5ac31 100644 --- a/examples/SharedLib/libB/lib-teavm/build.gradle.kts +++ b/examples/SharedLib/libB/lib-teavm/build.gradle.kts @@ -20,18 +20,11 @@ dependencies { implementation(project(":examples:SharedLib:libA:lib-teavm")) - if(LibExt.exampleUseRepoLibs) { - implementation("com.github.xpenatan.jParser:loader-teavm:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:loader-core:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:idl-teavm:-SNAPSHOT") - } - else { - implementation(project(":loader:loader-teavm")) - implementation(project(":loader:loader-core")) - implementation(project(":idl:idl-teavm")) - } + implementation(project(":loader:loader-teavm")) + implementation(project(":loader:loader-core")) + implementation(project(":idl:idl-teavm")) - implementation(project(":idl-helper:idl-helper-teavm")) + api(project(":idl-helper:idl-helper-teavm")) } tasks.named("clean") { diff --git a/examples/TestLib/app/desktop/assets/data/badlogic.jpg b/examples/TestLib/app/assets/data/badlogic.jpg similarity index 100% rename from examples/TestLib/app/desktop/assets/data/badlogic.jpg rename to examples/TestLib/app/assets/data/badlogic.jpg diff --git a/examples/TestLib/app/core/build.gradle.kts b/examples/TestLib/app/core/build.gradle.kts index 157ae626..54bcd086 100644 --- a/examples/TestLib/app/core/build.gradle.kts +++ b/examples/TestLib/app/core/build.gradle.kts @@ -8,7 +8,10 @@ java { } dependencies { - implementation(project(":examples:TestLib:lib:lib-core")) + // compileOnly: app/core compiles against lib-core's API, but does NOT + // propagate it transitively. Each platform module (desktop-jni, desktop-ffm, + // android, teavm) provides the actual native bridge implementation. + compileOnly(project(":examples:TestLib:lib:lib-core")) implementation("com.badlogicgames.gdx:gdx:${LibExt.gdxVersion}") } \ No newline at end of file diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnumBenchmark.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnumBenchmark.java index 2e58824b..4c82447e 100644 --- a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnumBenchmark.java +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnumBenchmark.java @@ -2,30 +2,57 @@ import com.badlogic.gdx.Application; import com.badlogic.gdx.Gdx; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; import java.util.Arrays; +import java.util.ArrayList; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; public class EnumBenchmark { - static long size = 3_000_000_000L; + private static final int WARMUP_ITERATIONS = 1; + private static final int TIMED_ITERATIONS = 5; + private static long size = 3_000_000_000L; + private static final ArrayList results = new ArrayList<>(); + + public static class Result { + public final String label; + public final double medianMs; + public final double mopsPerSec; + + public Result(String label, double medianMs, double mopsPerSec) { + this.label = label; + this.medianMs = medianMs; + this.mopsPerSec = mopsPerSec; + } + } static void test() { if(Gdx.app.getType() == Application.ApplicationType.WebGL) { size = 200_000_000L; } - System.out.println("=========== TEST size: " + size); - for (int i = 0; i < 3; i++) { - System.out.println("=========== TEST: " + i); - benchmarkBitflags(); - benchmarkTestEnumLib(); - benchmarkTestEnumLibCustom_1(); - benchmarkTestEnumLibCustom_2(); -// benchmarkHashSet(); -// benchmarkHashSetCache(); -// benchmarkEnumSet(); + System.out.println("======================================================="); + System.out.println(" Enum Benchmark"); + System.out.println(" Warm-up rounds : " + WARMUP_ITERATIONS); + System.out.println(" Timed rounds : " + TIMED_ITERATIONS); + System.out.println(" Iterations : " + size); + System.out.println("======================================================="); + + results.clear(); + benchmark("bitfield", EnumBenchmark::runBitflags); + + // Keep existing placeholders to preserve source compatibility. + benchmarkTestEnumLib(); + benchmarkTestEnumLibCustom_1(); + benchmarkTestEnumLibCustom_2(); + + String outputPath = System.getProperty("benchmark.enum.output"); + if(outputPath != null && !outputPath.isEmpty()) { + writeCsv(outputPath); } } @@ -79,16 +106,50 @@ static void benchmarkHashSetCache() { static final int F = 1 << 5; static final int G = 1 << 6; - static void benchmarkBitflags() { + private static void runBitflags() { System.gc(); - long beg = System.nanoTime(); long a = A | B | G; for (long i = 0; i < size; i++) { long b = A | B | G; assert a == b; } - long end = System.nanoTime(); - System.out.println((end - beg)/1e9 + "\t\tbitfield"); + } + + private static void benchmark(String label, Runnable body) { + for(int i = 0; i < WARMUP_ITERATIONS; i++) { + body.run(); + } + + long[] times = new long[TIMED_ITERATIONS]; + for(int i = 0; i < TIMED_ITERATIONS; i++) { + long t0 = System.nanoTime(); + body.run(); + long t1 = System.nanoTime(); + times[i] = t1 - t0; + } + + java.util.Arrays.sort(times); + long medianNs = times[TIMED_ITERATIONS / 2]; + double medianMs = medianNs / 1_000_000.0; + double mopsPerSec = (size / (medianNs / 1_000_000_000.0)) / 1_000_000.0; + + results.add(new Result(label, medianMs, mopsPerSec)); + System.out.printf(" %-28s %10.1f ms %8.2f Mops/s%n", label, medianMs, mopsPerSec); + } + + private static void writeCsv(String path) { + try(PrintWriter pw = new PrintWriter(new FileWriter(path))) { + pw.println("# warmup=" + WARMUP_ITERATIONS); + pw.println("# timed=" + TIMED_ITERATIONS); + pw.println("# iterations=" + size); + pw.println("label,medianMs,mopsPerSec"); + for(Result result : results) { + pw.printf("%s,%.4f,%.4f%n", result.label, result.medianMs, result.mopsPerSec); + } + System.out.println(" Results written to: " + path); + } catch(IOException e) { + System.err.println(" Failed to write CSV: " + e.getMessage()); + } } static void benchmarkTestEnumLib() { diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnumBenchmarkCompare.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnumBenchmarkCompare.java new file mode 100644 index 00000000..cda5fce1 --- /dev/null +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnumBenchmarkCompare.java @@ -0,0 +1,185 @@ +package com.github.xpenatan.jParser.example.app; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Reads two enum benchmark CSV files (JNI and FFM) and prints a side-by-side + * comparison table to stdout, and optionally writes it to a file. + *

+ * Usage: {@code java EnumBenchmarkCompare [output.txt]} + */ +public class EnumBenchmarkCompare { + + private static class Row { + final String label; + final double medianMs; + final double mopsPerSec; + + Row(String label, double medianMs, double mopsPerSec) { + this.label = label; + this.medianMs = medianMs; + this.mopsPerSec = mopsPerSec; + } + } + + private static class CsvData { + final Map rows = new LinkedHashMap<>(); + String warmup = "?"; + String timed = "?"; + String iterations = "?"; + } + + public static void main(String[] args) { + if(args.length < 2) { + System.err.println("Usage: EnumBenchmarkCompare [output.txt]"); + System.exit(1); + } + + CsvData jniData = readCsv(args[0]); + CsvData ffmData = readCsv(args[1]); + + if(jniData.rows.isEmpty() || ffmData.rows.isEmpty()) { + System.err.println("ERROR: One or both CSV files are empty or could not be read."); + System.exit(1); + } + + String outputPath = args.length >= 3 ? args[2] : null; + printComparison(jniData, ffmData, outputPath); + } + + private static CsvData readCsv(String path) { + CsvData data = new CsvData(); + try(BufferedReader br = new BufferedReader(new FileReader(path))) { + String line; + while((line = br.readLine()) != null) { + line = line.trim(); + if(line.isEmpty()) continue; + if(line.startsWith("#")) { + String meta = line.substring(1).trim(); + if(meta.startsWith("warmup=")) data.warmup = meta.substring(7); + else if(meta.startsWith("timed=")) data.timed = meta.substring(6); + else if(meta.startsWith("iterations=")) data.iterations = meta.substring(11); + continue; + } + if(line.startsWith("label,")) continue; + + int lastComma = line.lastIndexOf(','); + int secondLastComma = line.lastIndexOf(',', lastComma - 1); + if(secondLastComma < 0) continue; + + String label = line.substring(0, secondLastComma); + double medianMs = Double.parseDouble(line.substring(secondLastComma + 1, lastComma)); + double mopsPerSec = Double.parseDouble(line.substring(lastComma + 1)); + data.rows.put(label, new Row(label, medianMs, mopsPerSec)); + } + } catch(IOException e) { + System.err.println("Failed to read CSV: " + path + " -- " + e.getMessage()); + } + return data; + } + + private static void printComparison(CsvData jniData, CsvData ffmData, String outputPath) { + Map jniRows = jniData.rows; + Map ffmRows = ffmData.rows; + + ArrayList labels = new ArrayList<>(jniRows.keySet()); + for(String label : ffmRows.keySet()) { + if(!labels.contains(label)) labels.add(label); + } + + String sep = "+-" + pad(28) + "-+-" + + pad(10) + "-+-" + + pad(10) + "-+-" + + pad(10) + "-+-" + + pad(10) + "-+-" + + pad(9) + "-+-" + + pad(8) + "-+"; + + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("===================================================================\n"); + sb.append(" JNI vs FFM -- Enum Benchmark Comparison\n"); + sb.append("===================================================================\n"); + sb.append(" Warm-up rounds : ").append(jniData.warmup).append("\n"); + sb.append(" Timed rounds : ").append(jniData.timed).append("\n"); + sb.append(" Iterations : ").append(jniData.iterations).append("\n"); + sb.append("===================================================================\n"); + sb.append("\n"); + sb.append(sep).append("\n"); + sb.append(String.format("| %-28s | %10s | %10s | %10s | %10s | %9s | %-8s |%n", + "Benchmark", "JNI (ms)", "JNI Mops", "FFM (ms)", "FFM Mops", "Speedup", "Winner")); + sb.append(sep).append("\n"); + + int jniWins = 0; + int ffmWins = 0; + int ties = 0; + + for(String label : labels) { + Row jni = jniRows.get(label); + Row ffm = ffmRows.get(label); + + if(jni == null || ffm == null) { + sb.append(String.format("| %-28s | %10s | %10s | %10s | %10s | %9s | %-8s |%n", + label, + jni != null ? String.format("%.1f", jni.medianMs) : "N/A", + jni != null ? String.format("%.2f", jni.mopsPerSec) : "N/A", + ffm != null ? String.format("%.1f", ffm.medianMs) : "N/A", + ffm != null ? String.format("%.2f", ffm.mopsPerSec) : "N/A", + "--", "--")); + continue; + } + + double speedup; + String winner; + if(jni.medianMs < ffm.medianMs) { + speedup = ffm.medianMs / jni.medianMs; + winner = "JNI"; + jniWins++; + } else if(ffm.medianMs < jni.medianMs) { + speedup = jni.medianMs / ffm.medianMs; + winner = "FFM"; + ffmWins++; + } else { + speedup = 1.0; + winner = "TIE"; + ties++; + } + + sb.append(String.format("| %-28s | %10.1f | %10.2f | %10.1f | %10.2f | %8.2fx | %-8s |%n", + label, jni.medianMs, jni.mopsPerSec, + ffm.medianMs, ffm.mopsPerSec, speedup, winner)); + } + + sb.append(sep).append("\n"); + sb.append("\n"); + sb.append(String.format(" Summary: JNI wins %d, FFM wins %d, Ties %d (out of %d benchmarks)%n", + jniWins, ffmWins, ties, labels.size())); + sb.append("\n"); + + String table = sb.toString(); + System.out.print(table); + + if(outputPath != null && !outputPath.isEmpty()) { + try(PrintWriter pw = new PrintWriter(new FileWriter(outputPath))) { + pw.print(table); + System.out.println(" Comparison written to: " + outputPath); + } catch(IOException e) { + System.err.println(" Failed to write comparison file: " + e.getMessage()); + } + } + } + + private static String pad(int width) { + StringBuilder sb = new StringBuilder(); + while(sb.length() < width) sb.append('-'); + return sb.toString(); + } +} + diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnunBenchmarkAppTest.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnunBenchmarkAppTest.java index bc7e7912..bb87147d 100644 --- a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnunBenchmarkAppTest.java +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/EnunBenchmarkAppTest.java @@ -1,6 +1,7 @@ package com.github.xpenatan.jParser.example.app; import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.utils.ScreenUtils; import com.github.xpenatan.jParser.example.testlib.TestLibLoader; @@ -23,6 +24,7 @@ public void render() { if(init) { init = false; EnumBenchmark.test(); + Gdx.app.exit(); } ScreenUtils.clear(Color.GREEN); } diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmark.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmark.java new file mode 100644 index 00000000..7d04e95b --- /dev/null +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmark.java @@ -0,0 +1,165 @@ +package com.github.xpenatan.jParser.example.app; + +import com.badlogic.gdx.Application; +import com.badlogic.gdx.Gdx; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Comprehensive native bridge benchmark comparing JNI, FFM, and TeaVM performance. + *

+ * Each benchmark measures the cost of crossing the Java → native boundary for + * different parameter types. The native work is intentionally trivial so the + * measurement isolates bridge overhead, not native computation. + *

+ * Results are printed to stdout. Run this on desktop with either lib-core (JNI) + * or lib-ffm (FFM) on the classpath to compare. On TeaVM the iteration count + * is reduced automatically. + */ +public class NativeBridgeBenchmark { + + // --------------------------------------------------------------------------- + // Configuration + // --------------------------------------------------------------------------- + + /** Number of warm-up iterations (allows JIT to stabilise). */ + private static final int WARMUP_ITERATIONS = 2; + /** Number of timed iterations whose median is reported. */ + private static final int TIMED_ITERATIONS = 3; + + /** Calls per timed iteration – adjusted for TeaVM. */ + private static long CALLS_PER_ITERATION = 1_000_000L; + + /** Shared scenario definitions and native resources. */ + private static NativeBridgeWorkloads workloads; + + /** Collected results for CSV export. */ + private static final ArrayList results = new ArrayList<>(); + + /** Simple data holder for one benchmark measurement. */ + public static class BenchmarkResult { + public final String label; + public final double medianMs; + public final double mcallsPerSec; + + public BenchmarkResult(String label, double medianMs, double mcallsPerSec) { + this.label = label; + this.medianMs = medianMs; + this.mcallsPerSec = mcallsPerSec; + } + } + + // --------------------------------------------------------------------------- + // Public entry point + // --------------------------------------------------------------------------- + + public static void run() { + if(Gdx.app != null && Gdx.app.getType() == Application.ApplicationType.WebGL) { + CALLS_PER_ITERATION = 500_000L; + } + + System.out.println("======================================================="); + System.out.println(" Native Bridge Benchmark"); + System.out.println(" Warm-up rounds : " + WARMUP_ITERATIONS); + System.out.println(" Timed rounds : " + TIMED_ITERATIONS); + System.out.println(" Calls/round : " + CALLS_PER_ITERATION); + System.out.println("======================================================="); + + results.clear(); + + workloads = new NativeBridgeWorkloads(); + try { + runAllBenchmarks(); + } finally { + workloads.dispose(); + workloads = null; + } + + System.out.println("======================================================="); + System.out.println(" Benchmark complete"); + System.out.println("======================================================="); + + // Write CSV if output path is specified via system property + String outputPath = System.getProperty("benchmark.output"); + if(outputPath != null && !outputPath.isEmpty()) { + writeCsv(outputPath); + } + } + + /** + * Returns the collected results from the last {@link #run()} call. + */ + public static ArrayList getResults() { + return results; + } + + /** + * Writes collected results to a CSV file. + */ + private static void writeCsv(String path) { + try(PrintWriter pw = new PrintWriter(new FileWriter(path))) { + pw.println("# warmup=" + WARMUP_ITERATIONS); + pw.println("# timed=" + TIMED_ITERATIONS); + pw.println("# calls=" + CALLS_PER_ITERATION); + pw.println("label,medianMs,mcallsPerSec"); + for(BenchmarkResult r : results) { + pw.printf("%s,%.4f,%.4f%n", r.label, r.medianMs, r.mcallsPerSec); + } + System.out.println(" Results written to: " + path); + } catch(IOException e) { + System.err.println(" Failed to write CSV: " + e.getMessage()); + } + } + + // --------------------------------------------------------------------------- + // Benchmark runner + // --------------------------------------------------------------------------- + + private static void runAllBenchmarks() { + for(NativeBridgeWorkloads.Scenario scenario : workloads.getScenarios()) { + benchmark(scenario.label, () -> scenario.run(CALLS_PER_ITERATION)); + } + } + + /** + * Runs warm-up + timed iterations, then prints the median time and + * throughput (million calls/sec). + */ + private static void benchmark(String label, Runnable body) { + // Warm-up + for(int i = 0; i < WARMUP_ITERATIONS; i++) { + body.run(); + } + + // Timed + long[] times = new long[TIMED_ITERATIONS]; + for(int i = 0; i < TIMED_ITERATIONS; i++) { + long t0 = System.nanoTime(); + body.run(); + long t1 = System.nanoTime(); + times[i] = t1 - t0; + } + + // Sort for median + java.util.Arrays.sort(times); + long medianNs = times[TIMED_ITERATIONS / 2]; + double medianMs = medianNs / 1_000_000.0; + double mcallsPerSec = (CALLS_PER_ITERATION / (medianNs / 1_000_000_000.0)) / 1_000_000.0; + + // Trim label for display but keep original for CSV + String trimmed = label.trim(); + results.add(new BenchmarkResult(trimmed, medianMs, mcallsPerSec)); + + System.out.printf(" %-38s %8.1f ms %8.2f Mcalls/s%n", label, medianMs, mcallsPerSec); + } +} + + + + + + + + diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkApp.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkApp.java new file mode 100644 index 00000000..b9bfabdb --- /dev/null +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkApp.java @@ -0,0 +1,34 @@ +package com.github.xpenatan.jParser.example.app; +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.ScreenUtils; +import com.github.xpenatan.jParser.example.testlib.TestLibLoader; +import com.github.xpenatan.jparser.idl.IDLLoader; +public class NativeBridgeBenchmarkApp extends ApplicationAdapter { + private boolean init = false; + @Override + public void create() { + IDLLoader.init((idl_isSuccess, idl_e) -> { + if(idl_e != null) { + idl_e.printStackTrace(); + return; + } + TestLibLoader.init((isSuccess, e) -> { + if(e != null) { + e.printStackTrace(); + } + init = isSuccess; + }); + }); + } + @Override + public void render() { + if(init) { + init = false; + NativeBridgeBenchmark.run(); + Gdx.app.exit(); + } + ScreenUtils.clear(Color.GREEN); + } +} diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkCompare.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkCompare.java new file mode 100644 index 00000000..c06f332a --- /dev/null +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkCompare.java @@ -0,0 +1,191 @@ +package com.github.xpenatan.jParser.example.app; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Reads two benchmark CSV files (JNI and FFM) and prints a side-by-side + * comparison table to stdout, and optionally writes it to a file. + *

+ * Usage: {@code java NativeBridgeBenchmarkCompare [output.txt]} + */ +public class NativeBridgeBenchmarkCompare { + + private static class Row { + final String label; + final double medianMs; + final double mcallsPerSec; + + Row(String label, double medianMs, double mcallsPerSec) { + this.label = label; + this.medianMs = medianMs; + this.mcallsPerSec = mcallsPerSec; + } + } + + /** Holds parsed CSV rows together with optional metadata from comment lines. */ + private static class CsvData { + final Map rows = new LinkedHashMap<>(); + String warmup = "?"; + String timed = "?"; + String calls = "?"; + } + + public static void main(String[] args) { + if(args.length < 2) { + System.err.println("Usage: NativeBridgeBenchmarkCompare [output.txt]"); + System.exit(1); + } + + CsvData jniData = readCsv(args[0]); + CsvData ffmData = readCsv(args[1]); + + if(jniData.rows.isEmpty() || ffmData.rows.isEmpty()) { + System.err.println("ERROR: One or both CSV files are empty or could not be read."); + System.exit(1); + } + + String outputPath = args.length >= 3 ? args[2] : null; + printComparison(jniData, ffmData, outputPath); + } + + private static CsvData readCsv(String path) { + CsvData data = new CsvData(); + try(BufferedReader br = new BufferedReader(new FileReader(path))) { + String line; + while((line = br.readLine()) != null) { + line = line.trim(); + if(line.isEmpty()) continue; + // Parse metadata comments (e.g. "# warmup=3") + if(line.startsWith("#")) { + String meta = line.substring(1).trim(); + if(meta.startsWith("warmup=")) data.warmup = meta.substring(7); + else if(meta.startsWith("timed=")) data.timed = meta.substring(6); + else if(meta.startsWith("calls=")) data.calls = meta.substring(6); + continue; + } + // Skip CSV header + if(line.startsWith("label,")) continue; + // Format: label,medianMs,mcallsPerSec + int lastComma = line.lastIndexOf(','); + int secondLastComma = line.lastIndexOf(',', lastComma - 1); + if(secondLastComma < 0) continue; + String label = line.substring(0, secondLastComma); + double medianMs = Double.parseDouble(line.substring(secondLastComma + 1, lastComma)); + double mcalls = Double.parseDouble(line.substring(lastComma + 1)); + data.rows.put(label, new Row(label, medianMs, mcalls)); + } + } catch(IOException e) { + System.err.println("Failed to read CSV: " + path + " — " + e.getMessage()); + } + return data; + } + + private static void printComparison(CsvData jniData, CsvData ffmData, String outputPath) { + Map jniRows = jniData.rows; + Map ffmRows = ffmData.rows; + + // Collect all labels preserving order from JNI file + ArrayList labels = new ArrayList<>(jniRows.keySet()); + for(String l : ffmRows.keySet()) { + if(!labels.contains(l)) labels.add(l); + } + + String sep = "+-" + pad("", 34, '-') + "-+-" + + pad("", 10, '-') + "-+-" + + pad("", 12, '-') + "-+-" + + pad("", 10, '-') + "-+-" + + pad("", 12, '-') + "-+-" + + pad("", 9, '-') + "-+-" + + pad("", 8, '-') + "-+"; + + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("=======================================================================\n"); + sb.append(" JNI vs FFM -- Benchmark Comparison\n"); + sb.append("=======================================================================\n"); + sb.append(" Warm-up rounds : ").append(jniData.warmup).append("\n"); + sb.append(" Timed rounds : ").append(jniData.timed).append("\n"); + sb.append(" Calls/round : ").append(jniData.calls).append("\n"); + sb.append("=======================================================================\n"); + sb.append("\n"); + sb.append(sep).append("\n"); + sb.append(String.format("| %-34s | %10s | %12s | %10s | %12s | %9s | %-8s |%n", + "Benchmark", "JNI (ms)", "JNI Mcalls/s", "FFM (ms)", "FFM Mcalls/s", "Speedup", "Winner")); + sb.append(sep).append("\n"); + + int jniWins = 0; + int ffmWins = 0; + int ties = 0; + + for(String label : labels) { + Row jni = jniRows.get(label); + Row ffm = ffmRows.get(label); + + if(jni == null || ffm == null) { + sb.append(String.format("| %-34s | %10s | %12s | %10s | %12s | %9s | %-8s |%n", + label, + jni != null ? String.format("%.1f", jni.medianMs) : "N/A", + jni != null ? String.format("%.2f", jni.mcallsPerSec) : "N/A", + ffm != null ? String.format("%.1f", ffm.medianMs) : "N/A", + ffm != null ? String.format("%.2f", ffm.mcallsPerSec) : "N/A", + "--", "--")); + continue; + } + + // Speedup: how much faster is the winner (ratio of slower/faster) + double speedup; + String winner; + if(jni.medianMs < ffm.medianMs) { + speedup = ffm.medianMs / jni.medianMs; + winner = "JNI"; + jniWins++; + } else if(ffm.medianMs < jni.medianMs) { + speedup = jni.medianMs / ffm.medianMs; + winner = "FFM"; + ffmWins++; + } else { + speedup = 1.0; + winner = "TIE"; + ties++; + } + + sb.append(String.format("| %-34s | %10.1f | %12.2f | %10.1f | %12.2f | %8.2fx | %-8s |%n", + label, jni.medianMs, jni.mcallsPerSec, + ffm.medianMs, ffm.mcallsPerSec, speedup, winner)); + } + + sb.append(sep).append("\n"); + sb.append("\n"); + sb.append(String.format(" Summary: JNI wins %d, FFM wins %d, Ties %d (out of %d benchmarks)%n", + jniWins, ffmWins, ties, labels.size())); + sb.append("\n"); + + // Print to stdout + String table = sb.toString(); + System.out.print(table); + + // Write to file if output path was provided + if(outputPath != null && !outputPath.isEmpty()) { + try(PrintWriter pw = new PrintWriter(new FileWriter(outputPath))) { + pw.print(table); + System.out.println(" Comparison written to: " + outputPath); + } catch(IOException e) { + System.err.println(" Failed to write comparison file: " + e.getMessage()); + } + } + } + + private static String pad(String s, int width, char c) { + StringBuilder sb = new StringBuilder(s); + while(sb.length() < width) sb.append(c); + return sb.toString(); + } +} + diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmark.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmark.java new file mode 100644 index 00000000..9e6b8e75 --- /dev/null +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmark.java @@ -0,0 +1,283 @@ +package com.github.xpenatan.jParser.example.app; + +import com.badlogic.gdx.Gdx; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * FPS benchmark — measures how native bridge overhead affects frame rate. + *

+ * Each frame executes a fixed number of native calls for the current scenario, + * then returns to let GDX render. A state machine cycles through all scenarios, + * measuring average and minimum FPS for each. + *

+ * This complements {@link NativeBridgeBenchmark} which measures raw throughput + * in a tight loop. The FPS benchmark shows real-world frame rate impact. + */ +public class NativeBridgeFpsBenchmark { + + // --------------------------------------------------------------------------- + // Configuration + // --------------------------------------------------------------------------- + + /** Native calls executed per frame for every scenario. */ + private static final int CALLS_PER_FRAME = 1_000_000; + /** Seconds to warm up before measuring (lets JIT stabilise). */ + private static final float WARMUP_SECONDS = 5f; + /** Seconds to measure FPS after warm-up. */ + private static final float MEASURE_SECONDS = 5f; + /** Seconds between FPS log lines in interactive mode. */ + private static final float INTERACTIVE_LOG_INTERVAL_SECONDS = 1f; + + public enum Mode { SEQUENTIAL, INTERACTIVE_SINGLE } + + // --------------------------------------------------------------------------- + // State machine + // --------------------------------------------------------------------------- + + private enum State { IDLE, WARMUP, MEASURE, NEXT, DONE } + + private State state = State.IDLE; + private Mode mode = Mode.SEQUENTIAL; + private float elapsed; + private int frameCount; + private float minFrameTime; // worst (longest) frame during measurement + private int scenarioIndex; + private float interactiveLogElapsed; + private int interactiveLogFrames; + + // Shared scenario definitions/resources + private NativeBridgeWorkloads workloads; + private NativeBridgeWorkloads.Scenario[] scenarios; + + // Collected results + private final ArrayList results = new ArrayList<>(); + + /** Data holder for one scenario measurement. */ + public static class FpsResult { + public final String label; + public final float avgFps; + public final float minFps; + + public FpsResult(String label, float avgFps, float minFps) { + this.label = label; + this.avgFps = avgFps; + this.minFps = minFps; + } + } + + // --------------------------------------------------------------------------- + // Lifecycle + // --------------------------------------------------------------------------- + + /** Call once after native libraries are loaded. */ + public void start() { + workloads = new NativeBridgeWorkloads(); + scenarios = workloads.getScenarios(); + mode = readMode(); + results.clear(); + scenarioIndex = 0; + if(mode == Mode.SEQUENTIAL) { + beginScenario(); + } + else { + beginInteractiveScenario(); + } + + System.out.println("======================================================="); + System.out.println(" FPS Benchmark"); + System.out.println(" Mode : " + mode); + System.out.println(" Calls/frame : " + CALLS_PER_FRAME); + System.out.println(" Warm-up (sec) : " + (int) WARMUP_SECONDS); + System.out.println(" Measure (sec) : " + (int) MEASURE_SECONDS); + System.out.println(" Scenarios : " + scenarios.length); + if(mode == Mode.INTERACTIVE_SINGLE) { + System.out.println(" Controls : LEFT=previous, RIGHT=next"); + } + System.out.println("======================================================="); + } + + /** + * Call every frame from {@code render()}. Returns {@code true} when all + * scenarios are finished. + */ + public boolean update() { + if(mode == Mode.INTERACTIVE_SINGLE) { + return updateInteractive(); + } + return updateSequential(); + } + + public boolean isInteractiveMode() { + return mode == Mode.INTERACTIVE_SINGLE; + } + + public void nextScenario() { + if(scenarios == null || scenarios.length == 0) { + return; + } + scenarioIndex = (scenarioIndex + 1) % scenarios.length; + beginInteractiveScenario(); + } + + public void previousScenario() { + if(scenarios == null || scenarios.length == 0) { + return; + } + scenarioIndex = (scenarioIndex - 1 + scenarios.length) % scenarios.length; + beginInteractiveScenario(); + } + + public void dispose() { + if(workloads != null) { + workloads.dispose(); + workloads = null; + scenarios = null; + } + state = State.DONE; + } + + private boolean updateSequential() { + if(state == State.DONE) return true; + + float dt = Gdx.graphics.getDeltaTime(); + + // Execute the workload for this frame + if(state == State.WARMUP || state == State.MEASURE) { + scenarios[scenarioIndex].run(CALLS_PER_FRAME); + } + + switch(state) { + case WARMUP: + elapsed += dt; + if(elapsed >= WARMUP_SECONDS) { + // Transition to measurement + state = State.MEASURE; + elapsed = 0f; + frameCount = 0; + minFrameTime = 0f; + } + break; + + case MEASURE: + elapsed += dt; + frameCount++; + if(dt > minFrameTime) { + minFrameTime = dt; + } + if(elapsed >= MEASURE_SECONDS) { + // Record results + float avgFps = frameCount / elapsed; + float minFps = minFrameTime > 0 ? 1f / minFrameTime : 0f; + results.add(new FpsResult(scenarios[scenarioIndex].label, avgFps, minFps)); + + System.out.printf(" %-38s avg %6.1f FPS min %6.1f FPS%n", + scenarios[scenarioIndex].label, avgFps, minFps); + + state = State.NEXT; + } + break; + + case NEXT: + scenarioIndex++; + if(scenarioIndex >= scenarios.length) { + state = State.DONE; + finish(); + return true; + } + beginScenario(); + break; + + default: + break; + } + + return false; + } + + private boolean updateInteractive() { + if(scenarios == null || scenarios.length == 0) { + return false; + } + + float dt = Gdx.graphics.getDeltaTime(); + scenarios[scenarioIndex].run(CALLS_PER_FRAME); + + interactiveLogElapsed += dt; + interactiveLogFrames++; + + if(interactiveLogElapsed >= INTERACTIVE_LOG_INTERVAL_SECONDS) { + float avgFps = interactiveLogFrames / interactiveLogElapsed; + float currentFps = dt > 0f ? 1f / dt : 0f; + System.out.printf(" [Interactive] %-30s current %6.1f FPS avg %6.1f FPS%n", + scenarios[scenarioIndex].label, currentFps, avgFps); + interactiveLogElapsed = 0f; + interactiveLogFrames = 0; + } + + return false; + } + + /** Returns collected results after benchmark is done. */ + public ArrayList getResults() { + return results; + } + + // --------------------------------------------------------------------------- + // Internals + // --------------------------------------------------------------------------- + + private void beginScenario() { + state = State.WARMUP; + elapsed = 0f; + frameCount = 0; + minFrameTime = 0f; + } + + private void beginInteractiveScenario() { + interactiveLogElapsed = 0f; + interactiveLogFrames = 0; + if(scenarios != null && scenarios.length > 0) { + System.out.println(" [Interactive] Selected scenario: " + scenarios[scenarioIndex].label); + } + } + + private Mode readMode() { + String rawMode = System.getProperty("benchmark.fps.mode", "sequence"); + if(rawMode != null && rawMode.equalsIgnoreCase("interactive")) { + return Mode.INTERACTIVE_SINGLE; + } + return Mode.SEQUENTIAL; + } + + private void finish() { + System.out.println("======================================================="); + System.out.println(" FPS Benchmark complete"); + System.out.println("======================================================="); + + String outputPath = System.getProperty("benchmark.fps.output"); + if(outputPath != null && !outputPath.isEmpty()) { + writeCsv(outputPath); + } + + dispose(); + } + + private void writeCsv(String path) { + try(PrintWriter pw = new PrintWriter(new FileWriter(path))) { + pw.println("# calls_per_frame=" + CALLS_PER_FRAME); + pw.println("# warmup_sec=" + (int) WARMUP_SECONDS); + pw.println("# measure_sec=" + (int) MEASURE_SECONDS); + pw.println("label,avgFps,minFps"); + for(FpsResult r : results) { + pw.printf("%s,%.2f,%.2f%n", r.label, r.avgFps, r.minFps); + } + System.out.println(" Results written to: " + path); + } catch(IOException e) { + System.err.println(" Failed to write CSV: " + e.getMessage()); + } + } +} + diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkApp.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkApp.java new file mode 100644 index 00000000..6274d7ab --- /dev/null +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkApp.java @@ -0,0 +1,69 @@ +package com.github.xpenatan.jParser.example.app; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.ScreenUtils; +import com.github.xpenatan.jParser.example.testlib.TestLibLoader; +import com.github.xpenatan.jparser.idl.IDLLoader; + +public class NativeBridgeFpsBenchmarkApp extends ApplicationAdapter { + + private boolean init = false; + private boolean running = false; + private NativeBridgeFpsBenchmark benchmark; + + @Override + public void create() { + IDLLoader.init((idl_isSuccess, idl_e) -> { + if(idl_e != null) { + idl_e.printStackTrace(); + return; + } + TestLibLoader.init((isSuccess, e) -> { + if(e != null) { + e.printStackTrace(); + } + init = isSuccess; + }); + }); + } + + @Override + public void render() { + ScreenUtils.clear(Color.DARK_GRAY); + + if(init && !running) { + init = false; + running = true; + benchmark = new NativeBridgeFpsBenchmark(); + benchmark.start(); + } + + if(running) { + if(benchmark.isInteractiveMode()) { + if(Gdx.input.isKeyJustPressed(Input.Keys.RIGHT)) { + benchmark.nextScenario(); + } + if(Gdx.input.isKeyJustPressed(Input.Keys.LEFT)) { + benchmark.previousScenario(); + } + } + + boolean done = benchmark.update(); + if(done && !benchmark.isInteractiveMode()) { + Gdx.app.exit(); + } + } + } + + @Override + public void dispose() { + if(benchmark != null) { + benchmark.dispose(); + benchmark = null; + } + } +} + diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkCompare.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkCompare.java new file mode 100644 index 00000000..db0c6c28 --- /dev/null +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkCompare.java @@ -0,0 +1,184 @@ +package com.github.xpenatan.jParser.example.app; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Reads two FPS benchmark CSV files (JNI and FFM) and prints a side-by-side + * comparison table to stdout, and optionally writes it to a file. + *

+ * Usage: {@code java NativeBridgeFpsBenchmarkCompare [output.txt]} + */ +public class NativeBridgeFpsBenchmarkCompare { + + private static class Row { + final String label; + final double avgFps; + final double minFps; + + Row(String label, double avgFps, double minFps) { + this.label = label; + this.avgFps = avgFps; + this.minFps = minFps; + } + } + + private static class CsvData { + final Map rows = new LinkedHashMap<>(); + String callsPerFrame = "?"; + String warmupSec = "?"; + String measureSec = "?"; + } + + public static void main(String[] args) { + if(args.length < 2) { + System.err.println("Usage: NativeBridgeFpsBenchmarkCompare [output.txt]"); + System.exit(1); + } + + CsvData jniData = readCsv(args[0]); + CsvData ffmData = readCsv(args[1]); + + if(jniData.rows.isEmpty() || ffmData.rows.isEmpty()) { + System.err.println("ERROR: One or both CSV files are empty or could not be read."); + System.exit(1); + } + + String outputPath = args.length >= 3 ? args[2] : null; + printComparison(jniData, ffmData, outputPath); + } + + private static CsvData readCsv(String path) { + CsvData data = new CsvData(); + try(BufferedReader br = new BufferedReader(new FileReader(path))) { + String line; + while((line = br.readLine()) != null) { + line = line.trim(); + if(line.isEmpty()) continue; + if(line.startsWith("#")) { + String meta = line.substring(1).trim(); + if(meta.startsWith("calls_per_frame=")) data.callsPerFrame = meta.substring(16); + else if(meta.startsWith("warmup_sec=")) data.warmupSec = meta.substring(11); + else if(meta.startsWith("measure_sec=")) data.measureSec = meta.substring(12); + continue; + } + if(line.startsWith("label,")) continue; + // Format: label,avgFps,minFps + int lastComma = line.lastIndexOf(','); + int secondLastComma = line.lastIndexOf(',', lastComma - 1); + if(secondLastComma < 0) continue; + String label = line.substring(0, secondLastComma); + double avgFps = Double.parseDouble(line.substring(secondLastComma + 1, lastComma)); + double minFps = Double.parseDouble(line.substring(lastComma + 1)); + data.rows.put(label, new Row(label, avgFps, minFps)); + } + } catch(IOException e) { + System.err.println("Failed to read CSV: " + path + " -- " + e.getMessage()); + } + return data; + } + + private static void printComparison(CsvData jniData, CsvData ffmData, String outputPath) { + Map jniRows = jniData.rows; + Map ffmRows = ffmData.rows; + + ArrayList labels = new ArrayList<>(jniRows.keySet()); + for(String l : ffmRows.keySet()) { + if(!labels.contains(l)) labels.add(l); + } + + String sep = "+-" + pad(34) + "-+-" + + pad(10) + "-+-" + + pad(10) + "-+-" + + pad(10) + "-+-" + + pad(10) + "-+-" + + pad(9) + "-+-" + + pad(8) + "-+"; + + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("=======================================================================\n"); + sb.append(" JNI vs FFM -- FPS Benchmark Comparison\n"); + sb.append("=======================================================================\n"); + sb.append(" Calls/frame : ").append(jniData.callsPerFrame).append("\n"); + sb.append(" Warm-up (sec) : ").append(jniData.warmupSec).append("\n"); + sb.append(" Measure (sec) : ").append(jniData.measureSec).append("\n"); + sb.append("=======================================================================\n"); + sb.append("\n"); + sb.append(sep).append("\n"); + sb.append(String.format("| %-34s | %10s | %10s | %10s | %10s | %9s | %-8s |%n", + "Benchmark", "JNI avg", "JNI min", "FFM avg", "FFM min", "FPS gain", "Winner")); + sb.append(sep).append("\n"); + + int jniWins = 0; + int ffmWins = 0; + int ties = 0; + + for(String label : labels) { + Row jni = jniRows.get(label); + Row ffm = ffmRows.get(label); + + if(jni == null || ffm == null) { + sb.append(String.format("| %-34s | %10s | %10s | %10s | %10s | %9s | %-8s |%n", + label, + jni != null ? String.format("%.1f", jni.avgFps) : "N/A", + jni != null ? String.format("%.1f", jni.minFps) : "N/A", + ffm != null ? String.format("%.1f", ffm.avgFps) : "N/A", + ffm != null ? String.format("%.1f", ffm.minFps) : "N/A", + "--", "--")); + continue; + } + + double gain; + String winner; + if(ffm.avgFps > jni.avgFps) { + gain = ffm.avgFps - jni.avgFps; + winner = "FFM"; + ffmWins++; + } else if(jni.avgFps > ffm.avgFps) { + gain = jni.avgFps - ffm.avgFps; + winner = "JNI"; + jniWins++; + } else { + gain = 0; + winner = "TIE"; + ties++; + } + + sb.append(String.format("| %-34s | %10.1f | %10.1f | %10.1f | %10.1f | %+8.1f | %-8s |%n", + label, jni.avgFps, jni.minFps, + ffm.avgFps, ffm.minFps, winner.equals("FFM") ? gain : -gain, winner)); + } + + sb.append(sep).append("\n"); + sb.append("\n"); + sb.append(String.format(" Summary: JNI wins %d, FFM wins %d, Ties %d (out of %d benchmarks)%n", + jniWins, ffmWins, ties, labels.size())); + sb.append("\n"); + + String table = sb.toString(); + System.out.print(table); + + if(outputPath != null && !outputPath.isEmpty()) { + try(PrintWriter pw = new PrintWriter(new FileWriter(outputPath))) { + pw.print(table); + System.out.println(" Comparison written to: " + outputPath); + } catch(IOException e) { + System.err.println(" Failed to write comparison file: " + e.getMessage()); + } + } + } + + private static String pad(int width) { + StringBuilder sb = new StringBuilder(); + while(sb.length() < width) sb.append('-'); + return sb.toString(); + } +} + diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeWorkloads.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeWorkloads.java new file mode 100644 index 00000000..43f048ed --- /dev/null +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeWorkloads.java @@ -0,0 +1,256 @@ +package com.github.xpenatan.jParser.example.app; + +import com.github.xpenatan.jParser.example.testlib.TestAttributeClass; +import com.github.xpenatan.jParser.example.testlib.TestBufferManualClass; +import com.github.xpenatan.jParser.example.testlib.TestEnumLib; +import com.github.xpenatan.jParser.example.testlib.TestMethodClass; +import com.github.xpenatan.jParser.example.testlib.TestObjectClass; +import com.github.xpenatan.jparser.idl.helper.IDLFloat; +import com.github.xpenatan.jparser.idl.helper.IDLFloatArray; +import com.github.xpenatan.jparser.idl.helper.IDLInt; +import com.github.xpenatan.jparser.idl.helper.IDLIntArray; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Shared native bridge workloads used by both throughput and FPS benchmarks. + */ +public class NativeBridgeWorkloads { + + @FunctionalInterface + public interface IterationWorkload { + void run(long iterations); + } + + public static class Scenario { + public final String label; + private final IterationWorkload workload; + + public Scenario(String label, IterationWorkload workload) { + this.label = label; + this.workload = workload; + } + + public void run(long iterations) { + workload.run(iterations); + } + } + + private final TestMethodClass methodObj; + private final TestObjectClass objectA; + private final TestObjectClass objectB; + private final TestAttributeClass attrObj; + private final TestBufferManualClass bufferObj; + private final IDLIntArray intArray; + private final IDLFloatArray floatArray; + private final IDLInt idlInt; + private final IDLFloat idlFloat; + private final ByteBuffer byteBuffer; + + private final Scenario[] scenarios; + + private volatile int intSink; + private volatile float floatSink; + + public NativeBridgeWorkloads() { + methodObj = new TestMethodClass(); + objectA = new TestObjectClass(); + objectB = new TestObjectClass(); + attrObj = new TestAttributeClass(); + bufferObj = new TestBufferManualClass(); + intArray = new IDLIntArray(64); + floatArray = new IDLFloatArray(64); + idlInt = new IDLInt(); + idlFloat = new IDLFloat(); + + byteBuffer = ByteBuffer.allocateDirect(256); + byteBuffer.order(ByteOrder.nativeOrder()); + + objectA.set_intValue01(42); + objectA.set_floatValue01(3.14f); + objectB.set_intValue01(99); + objectB.set_floatValue01(2.71f); + methodObj.setMethod05("hello"); + + for(int i = 0; i < 64; i++) { + intArray.setValue(i, i); + floatArray.setValue(i, i); + } + for(int i = 0; i < 256; i++) { + byteBuffer.put(i, (byte)i); + } + + scenarios = new Scenario[] { + new Scenario("void(int)", this::setInt), + new Scenario("void(float, bool)", this::setFloatBool), + new Scenario("void(int,int,float,float,bool)", this::setManyPrimitives), + new Scenario("int getter", this::getInt), + new Scenario("float getter", this::getFloat), + new Scenario("void(String)", this::setString), + new Scenario("void(TestEnumLib)", this::setEnum), + new Scenario("String getter", this::getString), + new Scenario("void(Obj*,Obj*,Obj&,Obj&)", this::objectPassing), + new Scenario("Object* getter", this::objectReturn), + new Scenario("attribute set int", this::attrSetInt), + new Scenario("attribute get int", this::attrGetInt), + new Scenario("attribute set float", this::attrSetFloat), + new Scenario("attribute get float", this::attrGetFloat), + new Scenario("IDLIntArray set+get", this::idlIntArray), + new Scenario("IDLFloatArray set+get", this::idlFloatArray), + new Scenario("IDLInt set+getValue", this::idlIntValue), + new Scenario("IDLFloat set+getValue", this::idlFloatValue), + new Scenario("ByteBuffer update (256B)", this::byteBufferUpdate), + }; + } + + public Scenario[] getScenarios() { + return scenarios; + } + + public void dispose() { + idlFloat.dispose(); + idlInt.dispose(); + floatArray.dispose(); + intArray.dispose(); + attrObj.dispose(); + objectB.dispose(); + objectA.dispose(); + methodObj.dispose(); + } + + private void setInt(long iterations) { + for(long i = 0; i < iterations; i++) { + methodObj.setMethod01((int)i); + } + } + + private void setFloatBool(long iterations) { + for(long i = 0; i < iterations; i++) { + methodObj.setMethod02(1.5f, true); + } + } + + private void setManyPrimitives(long iterations) { + for(long i = 0; i < iterations; i++) { + methodObj.setMethod03(1, 2, 3.0f, 4.0f, true); + } + } + + private void getInt(long iterations) { + int sink = 0; + for(long i = 0; i < iterations; i++) { + sink += methodObj.getIntValue01(); + } + intSink = sink; + } + + private void getFloat(long iterations) { + float sink = 0f; + for(long i = 0; i < iterations; i++) { + sink += methodObj.getFloatValue01(); + } + floatSink = sink; + } + + private void setString(long iterations) { + for(long i = 0; i < iterations; i++) { + methodObj.setMethod05("benchmark"); + } + } + + private void setEnum(long iterations) { + for(long i = 0; i < iterations; i++) { + methodObj.setMethod10(TestEnumLib.TEST_SECOND); + } + } + + private void getString(long iterations) { + for(long i = 0; i < iterations; i++) { + methodObj.getStrValue01().data(); + } + } + + private void objectPassing(long iterations) { + for(long i = 0; i < iterations; i++) { + methodObj.setMethod06(objectA, objectB, objectA, objectB); + } + } + + private void objectReturn(long iterations) { + for(long i = 0; i < iterations; i++) { + methodObj.getPointerObject02(); + } + } + + private void attrSetInt(long iterations) { + for(long i = 0; i < iterations; i++) { + attrObj.set_intValue01((int)i); + } + } + + private void attrGetInt(long iterations) { + int sink = 0; + for(long i = 0; i < iterations; i++) { + sink += attrObj.get_intValue01(); + } + intSink = sink; + } + + private void attrSetFloat(long iterations) { + for(long i = 0; i < iterations; i++) { + attrObj.set_floatValue01((float)i); + } + } + + private void attrGetFloat(long iterations) { + float sink = 0f; + for(long i = 0; i < iterations; i++) { + sink += attrObj.get_floatValue01(); + } + floatSink = sink; + } + + private void idlIntArray(long iterations) { + int sink = 0; + for(long i = 0; i < iterations; i++) { + int idx = (int)(i & 63L); + intArray.setValue(idx, (int)i); + sink += intArray.getValue(idx); + } + intSink = sink; + } + + private void idlFloatArray(long iterations) { + float sink = 0f; + for(long i = 0; i < iterations; i++) { + int idx = (int)(i & 63L); + floatArray.setValue(idx, i); + sink += floatArray.getValue(idx); + } + floatSink = sink; + } + + private void idlIntValue(long iterations) { + int sink = 0; + for(long i = 0; i < iterations; i++) { + idlInt.set((int)i); + sink += idlInt.getValue(); + } + intSink = sink; + } + + private void idlFloatValue(long iterations) { + float sink = 0f; + for(long i = 0; i < iterations; i++) { + idlFloat.set(i); + sink += idlFloat.getValue(); + } + floatSink = sink; + } + + private void byteBufferUpdate(long iterations) { + for(long i = 0; i < iterations; i++) { + bufferObj.updateByteBuffer(byteBuffer, 256, (byte)0x42); + } + } +} diff --git a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/TestLib.java b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/TestLib.java index bc62f4dc..1bfa0bfb 100644 --- a/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/TestLib.java +++ b/examples/TestLib/app/core/src/main/java/com/github/xpenatan/jParser/example/app/TestLib.java @@ -22,8 +22,14 @@ public static boolean test() { ArrayList logs = new ArrayList<>(); boolean allTestsPassed = true; for(CodeTest setupTest : setupTests()) { - boolean result = setupTest.test(); String testName = setupTest.getClass().getSimpleName(); + boolean result; + try { + result = setupTest.test(); + } catch(Throwable e) { + e.printStackTrace(); + result = false; + } logs.add(testName + ": " + result); allTestsPassed = allTestsPassed && result; } diff --git a/examples/TestLib/app/desktop-ffm/build.gradle.kts b/examples/TestLib/app/desktop-ffm/build.gradle.kts new file mode 100644 index 00000000..c09a3af9 --- /dev/null +++ b/examples/TestLib/app/desktop-ffm/build.gradle.kts @@ -0,0 +1,167 @@ +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform + +plugins { + id("java") +} + +java { + sourceCompatibility = JavaVersion.toVersion(24) + targetCompatibility = JavaVersion.toVersion(24) + toolchain { + languageVersion.set(JavaLanguageVersion.of(24)) + } +} + +val benchmarkDir = rootProject.layout.buildDirectory.dir("testlib-benchmark") +val isMacOs = DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX + +dependencies { + implementation(project(":examples:TestLib:app:core")) + + implementation("com.badlogicgames.gdx:gdx-platform:${LibExt.gdxVersion}:natives-desktop") + implementation("com.badlogicgames.gdx:gdx-backend-lwjgl3:${LibExt.gdxVersion}") + + implementation(project(":examples:TestLib:lib:lib-desktop-ffm")) +} + +tasks.register("TestLib_run_app_ffm_desktop") { + group = "example-desktop" + description = "Run desktop app with FFM bridge" + mainClass.set("com.github.xpenatan.jParser.example.app.Main") + classpath = sourceSets["main"].runtimeClasspath + javaLauncher.set(javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(24)) + }) + jvmArgs("--enable-native-access=ALL-UNNAMED") + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } +} + +tasks.register("TestLib_enum_benchmark_ffm") { + group = "example-benchmark" + description = "Run enum benchmark with FFM bridge and write CSV output" + mainClass.set("com.github.xpenatan.jParser.example.app.BenchmarkMain") + systemProperty("benchmark.enum.output", benchmarkDir.get().file("enum_benchmark_ffm.csv").asFile.absolutePath) + classpath = sourceSets["main"].runtimeClasspath + javaLauncher.set(javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(24)) + }) + jvmArgs("--enable-native-access=ALL-UNNAMED") + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } + + doFirst { + benchmarkDir.get().asFile.mkdirs() + } +} + +tasks.register("TestLib_throughput_benchmark_ffm") { + group = "example-benchmark" + description = "Run native bridge throughput benchmark with FFM bridge" + mainClass.set("com.github.xpenatan.jParser.example.app.NativeBridgeBenchmarkMain") + systemProperty("benchmark.output", benchmarkDir.get().file("benchmark_ffm.csv").asFile.absolutePath) + classpath = sourceSets["main"].runtimeClasspath + javaLauncher.set(javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(24)) + }) + jvmArgs("--enable-native-access=ALL-UNNAMED") + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } + + doFirst { + benchmarkDir.get().asFile.mkdirs() + } +} + +tasks.register("TestLib_fps_benchmark_ffm") { + group = "example-benchmark" + description = "Run FPS benchmark with FFM bridge" + mainClass.set("com.github.xpenatan.jParser.example.app.NativeBridgeFpsBenchmarkMain") + systemProperty("benchmark.fps.output", benchmarkDir.get().file("fps_benchmark_ffm.csv").asFile.absolutePath) + classpath = sourceSets["main"].runtimeClasspath + javaLauncher.set(javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(24)) + }) + jvmArgs("--enable-native-access=ALL-UNNAMED") + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } + + doFirst { + benchmarkDir.get().asFile.mkdirs() + } +} + +tasks.register("TestLib_fps_benchmark_ffm_interactive") { + group = "example-benchmark" + description = "Run FPS benchmark with FFM bridge in interactive mode" + mainClass.set("com.github.xpenatan.jParser.example.app.NativeBridgeFpsBenchmarkMain") + systemProperty("benchmark.fps.mode", "interactive") + classpath = sourceSets["main"].runtimeClasspath + javaLauncher.set(javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(24)) + }) + jvmArgs("--enable-native-access=ALL-UNNAMED") + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } +} + +tasks.register("TestLib_throughput_benchmark_compare") { + group = "example-benchmark" + description = "Run JNI & FFM benchmarks then print a comparison table" + dependsOn(":examples:TestLib:app:desktop-jni:TestLib_throughput_benchmark_jni", "TestLib_throughput_benchmark_ffm") + + mainClass.set("com.github.xpenatan.jParser.example.app.NativeBridgeBenchmarkCompare") + classpath = sourceSets["main"].runtimeClasspath + args( + benchmarkDir.get().file("benchmark_jni.csv").asFile.absolutePath, + benchmarkDir.get().file("benchmark_ffm.csv").asFile.absolutePath, + benchmarkDir.get().file("benchmark_compare.txt").asFile.absolutePath + ) +} + +tasks.register("TestLib_fps_benchmark_compare") { + group = "example-benchmark" + description = "Run JNI & FFM FPS benchmarks then print a comparison table" + dependsOn(":examples:TestLib:app:desktop-jni:TestLib_fps_benchmark_jni", "TestLib_fps_benchmark_ffm") + + mainClass.set("com.github.xpenatan.jParser.example.app.NativeBridgeFpsBenchmarkCompare") + classpath = sourceSets["main"].runtimeClasspath + args( + benchmarkDir.get().file("fps_benchmark_jni.csv").asFile.absolutePath, + benchmarkDir.get().file("fps_benchmark_ffm.csv").asFile.absolutePath, + benchmarkDir.get().file("fps_benchmark_compare.txt").asFile.absolutePath + ) +} + +tasks.register("TestLib_enum_benchmark_compare") { + group = "example-benchmark" + description = "Run JNI & FFM enum benchmarks then print a comparison table" + dependsOn(":examples:TestLib:app:desktop-jni:TestLib_enum_benchmark_jni", "TestLib_enum_benchmark_ffm") + + mainClass.set("com.github.xpenatan.jParser.example.app.EnumBenchmarkCompare") + classpath = sourceSets["main"].runtimeClasspath + args( + benchmarkDir.get().file("enum_benchmark_jni.csv").asFile.absolutePath, + benchmarkDir.get().file("enum_benchmark_ffm.csv").asFile.absolutePath, + benchmarkDir.get().file("enum_benchmark_compare.txt").asFile.absolutePath + ) +} + +tasks.named("TestLib_throughput_benchmark_ffm") { + mustRunAfter(":examples:TestLib:app:desktop-jni:TestLib_throughput_benchmark_jni") +} + +tasks.named("TestLib_fps_benchmark_ffm") { + mustRunAfter(":examples:TestLib:app:desktop-jni:TestLib_fps_benchmark_jni") +} + +tasks.named("TestLib_enum_benchmark_ffm") { + mustRunAfter(":examples:TestLib:app:desktop-jni:TestLib_enum_benchmark_jni") +} + diff --git a/examples/TestLib/app/desktop/src/main/java/com/github/xpenatan/jParser/example/app/BenchmarkMain.java b/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/BenchmarkMain.java similarity index 99% rename from examples/TestLib/app/desktop/src/main/java/com/github/xpenatan/jParser/example/app/BenchmarkMain.java rename to examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/BenchmarkMain.java index b27228fb..ea0dd0e0 100644 --- a/examples/TestLib/app/desktop/src/main/java/com/github/xpenatan/jParser/example/app/BenchmarkMain.java +++ b/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/BenchmarkMain.java @@ -9,4 +9,4 @@ public static void main(String[] args) { Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); new Lwjgl3Application(new EnunBenchmarkAppTest(), config); } -} \ No newline at end of file +} diff --git a/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/Main.java b/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/Main.java new file mode 100644 index 00000000..fa3bfe37 --- /dev/null +++ b/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/Main.java @@ -0,0 +1,13 @@ +package com.github.xpenatan.jParser.example.app; + +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; + +public class Main { + + public static void main(String[] args) { + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + new Lwjgl3Application(new AppTest(), config); + } +} + diff --git a/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkMain.java b/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkMain.java new file mode 100644 index 00000000..d28d0cc1 --- /dev/null +++ b/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkMain.java @@ -0,0 +1,14 @@ +package com.github.xpenatan.jParser.example.app; + +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; + +public class NativeBridgeBenchmarkMain { + + public static void main(String[] args) { + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + config.setTitle("Native Bridge Benchmark"); + new Lwjgl3Application(new NativeBridgeBenchmarkApp(), config); + } +} + diff --git a/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkMain.java b/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkMain.java new file mode 100644 index 00000000..00051aaa --- /dev/null +++ b/examples/TestLib/app/desktop-ffm/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkMain.java @@ -0,0 +1,16 @@ +package com.github.xpenatan.jParser.example.app; + +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; + +public class NativeBridgeFpsBenchmarkMain { + + public static void main(String[] args) { + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + config.setTitle("FPS Benchmark"); + config.useVsync(false); + config.setForegroundFPS(0); + new Lwjgl3Application(new NativeBridgeFpsBenchmarkApp(), config); + } +} + diff --git a/examples/TestLib/app/desktop-jni/build.gradle.kts b/examples/TestLib/app/desktop-jni/build.gradle.kts new file mode 100644 index 00000000..dcbe4242 --- /dev/null +++ b/examples/TestLib/app/desktop-jni/build.gradle.kts @@ -0,0 +1,88 @@ +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform + +plugins { + id("java") +} + +java { + sourceCompatibility = JavaVersion.toVersion(LibExt.java8Target) + targetCompatibility = JavaVersion.toVersion(LibExt.java8Target) +} + +val benchmarkDir = rootProject.layout.buildDirectory.dir("testlib-benchmark") +val isMacOs = DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX + +dependencies { + implementation(project(":examples:TestLib:app:core")) + + implementation("com.badlogicgames.gdx:gdx-platform:${LibExt.gdxVersion}:natives-desktop") + implementation("com.badlogicgames.gdx:gdx-backend-lwjgl3:${LibExt.gdxVersion}") + + implementation(project(":examples:TestLib:lib:lib-desktop-jni")) +} + +tasks.register("TestLib_run_app_jni_desktop") { + group = "example-desktop" + description = "Run desktop app with JNI bridge" + mainClass.set("com.github.xpenatan.jParser.example.app.Main") + classpath = sourceSets["main"].runtimeClasspath + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } +} + +tasks.register("TestLib_enum_benchmark_jni") { + group = "example-benchmark" + description = "Run enum benchmark with JNI bridge and write CSV output" + mainClass.set("com.github.xpenatan.jParser.example.app.BenchmarkMain") + systemProperty("benchmark.enum.output", benchmarkDir.get().file("enum_benchmark_jni.csv").asFile.absolutePath) + classpath = sourceSets["main"].runtimeClasspath + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } + + doFirst { + benchmarkDir.get().asFile.mkdirs() + } +} + +tasks.register("TestLib_throughput_benchmark_jni") { + group = "example-benchmark" + description = "Run native bridge throughput benchmark with JNI bridge" + mainClass.set("com.github.xpenatan.jParser.example.app.NativeBridgeBenchmarkMain") + systemProperty("benchmark.output", benchmarkDir.get().file("benchmark_jni.csv").asFile.absolutePath) + classpath = sourceSets["main"].runtimeClasspath + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } + + doFirst { + benchmarkDir.get().asFile.mkdirs() + } +} + +tasks.register("TestLib_fps_benchmark_jni") { + group = "example-benchmark" + description = "Run FPS benchmark with JNI bridge" + mainClass.set("com.github.xpenatan.jParser.example.app.NativeBridgeFpsBenchmarkMain") + systemProperty("benchmark.fps.output", benchmarkDir.get().file("fps_benchmark_jni.csv").asFile.absolutePath) + classpath = sourceSets["main"].runtimeClasspath + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } + + doFirst { + benchmarkDir.get().asFile.mkdirs() + } +} + +tasks.register("TestLib_fps_benchmark_jni_interactive") { + group = "example-benchmark" + description = "Run FPS benchmark with JNI bridge in interactive mode" + mainClass.set("com.github.xpenatan.jParser.example.app.NativeBridgeFpsBenchmarkMain") + systemProperty("benchmark.fps.mode", "interactive") + classpath = sourceSets["main"].runtimeClasspath + if(isMacOs) { + jvmArgs("-XstartOnFirstThread") + } +} \ No newline at end of file diff --git a/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/BenchmarkMain.java b/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/BenchmarkMain.java new file mode 100644 index 00000000..ea0dd0e0 --- /dev/null +++ b/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/BenchmarkMain.java @@ -0,0 +1,12 @@ +package com.github.xpenatan.jParser.example.app; + +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; + +public class BenchmarkMain { + + public static void main(String[] args) { + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + new Lwjgl3Application(new EnunBenchmarkAppTest(), config); + } +} diff --git a/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/Main.java b/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/Main.java new file mode 100644 index 00000000..fa3bfe37 --- /dev/null +++ b/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/Main.java @@ -0,0 +1,13 @@ +package com.github.xpenatan.jParser.example.app; + +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; + +public class Main { + + public static void main(String[] args) { + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + new Lwjgl3Application(new AppTest(), config); + } +} + diff --git a/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkMain.java b/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkMain.java new file mode 100644 index 00000000..d28d0cc1 --- /dev/null +++ b/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeBenchmarkMain.java @@ -0,0 +1,14 @@ +package com.github.xpenatan.jParser.example.app; + +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; + +public class NativeBridgeBenchmarkMain { + + public static void main(String[] args) { + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + config.setTitle("Native Bridge Benchmark"); + new Lwjgl3Application(new NativeBridgeBenchmarkApp(), config); + } +} + diff --git a/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkMain.java b/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkMain.java new file mode 100644 index 00000000..00051aaa --- /dev/null +++ b/examples/TestLib/app/desktop-jni/src/main/java/com/github/xpenatan/jParser/example/app/NativeBridgeFpsBenchmarkMain.java @@ -0,0 +1,16 @@ +package com.github.xpenatan.jParser.example.app; + +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; + +public class NativeBridgeFpsBenchmarkMain { + + public static void main(String[] args) { + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + config.setTitle("FPS Benchmark"); + config.useVsync(false); + config.setForegroundFPS(0); + new Lwjgl3Application(new NativeBridgeFpsBenchmarkApp(), config); + } +} + diff --git a/examples/TestLib/app/desktop/build.gradle.kts b/examples/TestLib/app/desktop/build.gradle.kts deleted file mode 100644 index ecdfd82d..00000000 --- a/examples/TestLib/app/desktop/build.gradle.kts +++ /dev/null @@ -1,41 +0,0 @@ -import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform - -plugins { - id("java") -} - -java { - sourceCompatibility = JavaVersion.toVersion(LibExt.java8Target) - targetCompatibility = JavaVersion.toVersion(LibExt.java8Target) -} - -dependencies { - implementation(project(":examples:TestLib:app:core")) - implementation(project(":examples:TestLib:lib:lib-core")) - implementation(project(":examples:TestLib:lib:lib-desktop")) - - implementation("com.badlogicgames.gdx:gdx-platform:${LibExt.gdxVersion}:natives-desktop") - implementation("com.badlogicgames.gdx:gdx-backend-lwjgl3:${LibExt.gdxVersion}") -} - -tasks.register("TestLib_run_app_desktop") { - group = "example-desktop" - description = "Run desktop app" - mainClass.set("com.github.xpenatan.jParser.example.app.Main") - classpath = sourceSets["main"].runtimeClasspath - - if(DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX) { - jvmArgs("-XstartOnFirstThread") - } -} - -tasks.register("TestLib_run_benchmark_desktop") { - group = "example-desktop" - description = "Run desktop app" - mainClass.set("com.github.xpenatan.jParser.example.app.BenchmarkMain") - classpath = sourceSets["main"].runtimeClasspath - - if(DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX) { - jvmArgs("-XstartOnFirstThread") - } -} \ No newline at end of file diff --git a/examples/TestLib/app/teavm/build.gradle.kts b/examples/TestLib/app/teavm/build.gradle.kts index d34361e9..a877a3b9 100644 --- a/examples/TestLib/app/teavm/build.gradle.kts +++ b/examples/TestLib/app/teavm/build.gradle.kts @@ -1,12 +1,5 @@ plugins { id("java") - id("org.gretty") version("4.1.10") -} - - -project.extra["webAppDir"] = File(projectDir, "build/dist/webapp") -gretty { - contextPath = "/" } java { @@ -19,37 +12,19 @@ dependencies { implementation(project(":examples:TestLib:lib:lib-teavm")) implementation("com.badlogicgames.gdx:gdx:${LibExt.gdxVersion}") - implementation("com.github.xpenatan.gdx-teavm:backend-teavm:${LibExt.gdxTeaVMVersion}") + implementation("com.github.xpenatan.gdx-teavm:backend-web:${LibExt.gdxTeaVMVersion}") } -tasks.register("TestLib_build_app_teavm") { +tasks.register("TestLib_run_app_teavm") { group = "example-teavm" description = "Build teavm app" mainClass.set("Build") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("TestLib_run_app_teavm") { - group = "example-teavm" - description = "Run teavm app" - val list = listOf("TestLib_build_app_teavm", "jettyRun") - dependsOn(list) - - tasks.findByName("jettyRun")?.mustRunAfter("TestLib_build_app_teavm") -} - -tasks.register("TestLib_build_benchmark_teavm") { +tasks.register("TestLib_run_benchmark_teavm") { group = "example-teavm" description = "Build teavm benchmark" mainClass.set("BenchmarkBuild") classpath = sourceSets["main"].runtimeClasspath -} - -tasks.register("TestLib_run_benchmark_teavm") { - group = "example-teavm" - description = "Run teavm benchmark" - val list = listOf("TestLib_build_benchmark_teavm", "jettyRun") - dependsOn(list) - - tasks.findByName("jettyRun")?.mustRunAfter("TestLib_build_benchmark_teavm") } \ No newline at end of file diff --git a/examples/TestLib/app/teavm/src/main/java/BenchmarkBuild.java b/examples/TestLib/app/teavm/src/main/java/BenchmarkBuild.java index 058e4554..210b1926 100644 --- a/examples/TestLib/app/teavm/src/main/java/BenchmarkBuild.java +++ b/examples/TestLib/app/teavm/src/main/java/BenchmarkBuild.java @@ -1,24 +1,21 @@ -import com.github.xpenatan.gdx.backends.teavm.config.AssetFileHandle; -import com.github.xpenatan.gdx.backends.teavm.config.TeaBuildConfiguration; -import com.github.xpenatan.gdx.backends.teavm.config.TeaBuilder; +import com.github.xpenatan.gdx.teavm.backends.shared.config.AssetFileHandle; +import com.github.xpenatan.gdx.teavm.backends.shared.config.compiler.TeaCompiler; +import com.github.xpenatan.gdx.teavm.backends.web.config.backend.WebBackend; import java.io.File; import java.io.IOException; -import org.teavm.tooling.TeaVMTargetType; -import org.teavm.tooling.TeaVMTool; import org.teavm.vm.TeaVMOptimizationLevel; public class BenchmarkBuild { public static void main(String[] args) throws IOException { - TeaBuildConfiguration teaBuildConfiguration = new TeaBuildConfiguration(); - teaBuildConfiguration.assetsPath.add(new AssetFileHandle("../desktop/assets")); - teaBuildConfiguration.webappPath = new File("build/dist").getCanonicalPath(); - teaBuildConfiguration.targetType = TeaVMTargetType.WEBASSEMBLY_GC; - TeaBuilder.config(teaBuildConfiguration); - TeaVMTool tool = new TeaVMTool(); - tool.setOptimizationLevel(TeaVMOptimizationLevel.ADVANCED); - tool.setMainClass(BenchmarkLauncher.class.getName()); - tool.setObfuscated(false); - TeaBuilder.build(tool); + AssetFileHandle assetsPath = new AssetFileHandle("../assets"); + WebBackend webBackend = new WebBackend(); + webBackend.setStartJettyAfterBuild(true); + new TeaCompiler(webBackend) + .addAssets(assetsPath) + .setOptimizationLevel(TeaVMOptimizationLevel.ADVANCED) + .setMainClass(BenchmarkLauncher.class.getName()) + .setObfuscated(false) + .build(new File("build/dist")); } } diff --git a/examples/TestLib/app/teavm/src/main/java/BenchmarkLauncher.java b/examples/TestLib/app/teavm/src/main/java/BenchmarkLauncher.java index f76fc6b1..97bb9b98 100644 --- a/examples/TestLib/app/teavm/src/main/java/BenchmarkLauncher.java +++ b/examples/TestLib/app/teavm/src/main/java/BenchmarkLauncher.java @@ -1,13 +1,13 @@ -import com.github.xpenatan.gdx.backends.teavm.TeaApplication; -import com.github.xpenatan.gdx.backends.teavm.TeaApplicationConfiguration; +import com.github.xpenatan.gdx.teavm.backends.web.WebApplication; +import com.github.xpenatan.gdx.teavm.backends.web.WebApplicationConfiguration; import com.github.xpenatan.jParser.example.app.EnunBenchmarkAppTest; public class BenchmarkLauncher { public static void main(String[] args) { - TeaApplicationConfiguration config = new TeaApplicationConfiguration("canvas"); + WebApplicationConfiguration config = new WebApplicationConfiguration("canvas"); config.width = 0; config.height = 0; config.showDownloadLogs = true; - new TeaApplication(new EnunBenchmarkAppTest(), config); + new WebApplication(new EnunBenchmarkAppTest(), config); } } \ No newline at end of file diff --git a/examples/TestLib/app/teavm/src/main/java/Build.java b/examples/TestLib/app/teavm/src/main/java/Build.java index 2b87e183..08c46b84 100644 --- a/examples/TestLib/app/teavm/src/main/java/Build.java +++ b/examples/TestLib/app/teavm/src/main/java/Build.java @@ -1,32 +1,21 @@ -import com.github.xpenatan.gdx.backends.teavm.config.AssetFileHandle; -import com.github.xpenatan.gdx.backends.teavm.config.TeaBuildConfiguration; -import com.github.xpenatan.gdx.backends.teavm.config.TeaBuilder; +import com.github.xpenatan.gdx.teavm.backends.shared.config.AssetFileHandle; +import com.github.xpenatan.gdx.teavm.backends.shared.config.compiler.TeaCompiler; +import com.github.xpenatan.gdx.teavm.backends.web.config.backend.WebBackend; import java.io.File; import java.io.IOException; -import org.teavm.tooling.TeaVMSourceFilePolicy; -import org.teavm.tooling.TeaVMTargetType; -import org.teavm.tooling.TeaVMTool; -import org.teavm.tooling.sources.DirectorySourceFileProvider; import org.teavm.vm.TeaVMOptimizationLevel; public class Build { public static void main(String[] args) throws IOException { - TeaBuildConfiguration teaBuildConfiguration = new TeaBuildConfiguration(); - teaBuildConfiguration.assetsPath.add(new AssetFileHandle("../desktop/assets")); - teaBuildConfiguration.webappPath = new File("build/dist").getCanonicalPath(); - teaBuildConfiguration.targetType = TeaVMTargetType.JAVASCRIPT; - TeaBuilder.config(teaBuildConfiguration); - - TeaVMTool tool = new TeaVMTool(); - tool.setObfuscated(false); - tool.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); - tool.setMainClass(TeaVMLauncher.class.getName()); - -// tool.setDebugInformationGenerated(true); -// tool.setSourceMapsFileGenerated(true); -// tool.setSourceFilePolicy(TeaVMSourceFilePolicy.COPY); - - TeaBuilder.build(tool); + AssetFileHandle assetsPath = new AssetFileHandle("../assets"); + WebBackend webBackend = new WebBackend(); + webBackend.setStartJettyAfterBuild(true); + new TeaCompiler(webBackend) + .addAssets(assetsPath) + .setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE) + .setMainClass(TeaVMLauncher.class.getName()) + .setObfuscated(false) + .build(new File("build/dist")); } } diff --git a/examples/TestLib/app/teavm/src/main/java/TeaVMLauncher.java b/examples/TestLib/app/teavm/src/main/java/TeaVMLauncher.java index ebc3ff92..f08de6d4 100644 --- a/examples/TestLib/app/teavm/src/main/java/TeaVMLauncher.java +++ b/examples/TestLib/app/teavm/src/main/java/TeaVMLauncher.java @@ -1,13 +1,13 @@ -import com.github.xpenatan.gdx.backends.teavm.TeaApplication; -import com.github.xpenatan.gdx.backends.teavm.TeaApplicationConfiguration; +import com.github.xpenatan.gdx.teavm.backends.web.WebApplication; +import com.github.xpenatan.gdx.teavm.backends.web.WebApplicationConfiguration; import com.github.xpenatan.jParser.example.app.AppTest; public class TeaVMLauncher { public static void main(String[] args) { - TeaApplicationConfiguration config = new TeaApplicationConfiguration("canvas"); + WebApplicationConfiguration config = new WebApplicationConfiguration("canvas"); config.width = 0; config.height = 0; config.showDownloadLogs = true; - new TeaApplication(new AppTest(), config); + new WebApplication(new AppTest(), config); } } \ No newline at end of file diff --git a/examples/TestLib/lib/lib-android/build.gradle.kts b/examples/TestLib/lib/lib-android/build.gradle.kts index d9341c46..580c8427 100644 --- a/examples/TestLib/lib/lib-android/build.gradle.kts +++ b/examples/TestLib/lib/lib-android/build.gradle.kts @@ -33,10 +33,14 @@ android { } dependencies { - if(LibExt.exampleUseRepoLibs) { - api("com.github.xpenatan.jParser:idl--helper-android:-SNAPSHOT") - } - else { - api(project(":idl-helper:idl-helper-android")) + api(project(":loader:loader-core")) + api(project(":idl:idl-core")) + api(project(":idl-helper:idl-helper-android")) +} + +tasks.named("clean") { + doFirst { + val srcPath = "$projectDir/src/main/" + project.delete(files(srcPath)) } -} \ No newline at end of file +} diff --git a/examples/TestLib/lib/lib-base/build.gradle.kts b/examples/TestLib/lib/lib-base/build.gradle.kts index 640acba3..ec5029d0 100644 --- a/examples/TestLib/lib/lib-base/build.gradle.kts +++ b/examples/TestLib/lib/lib-base/build.gradle.kts @@ -8,14 +8,7 @@ java { } dependencies { - if(LibExt.exampleUseRepoLibs) { - implementation("com.github.xpenatan.jParser:loader-core:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:idl-core:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:idl-helper-core:-SNAPSHOT") - } - else { - implementation(project(":loader:loader-core")) - implementation(project(":idl:idl-core")) - implementation(project(":idl-helper:idl-helper-core")) - } + implementation(project(":loader:loader-core")) + implementation(project(":idl:idl-core")) + implementation(project(":idl-helper:idl-helper-core")) } \ No newline at end of file diff --git a/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/CallbackClassManual.java b/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/CallbackClassManual.java index 8afd6726..8080ef78 100644 --- a/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/CallbackClassManual.java +++ b/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/CallbackClassManual.java @@ -55,6 +55,81 @@ virtual void onStringCallback(const char* strValue01) const { }; */ + /*[-FFM;-NATIVE] + typedef void (*fp_CCMImpl_onVoidCallback)(int64_t, int64_t); + typedef int32_t (*fp_CCMImpl_onIntCallback)(int32_t, int32_t); + typedef float (*fp_CCMImpl_onFloatCallback)(float, float); + typedef int32_t (*fp_CCMImpl_onBoolCallback)(int32_t); + typedef void (*fp_CCMImpl_onStringCallback)(const char*); + class CallbackClassManualImpl : public CallbackClassManual { + private: + fp_CCMImpl_onVoidCallback onVoidCallback_ptr; + fp_CCMImpl_onIntCallback onIntCallback_ptr; + fp_CCMImpl_onFloatCallback onFloatCallback_ptr; + fp_CCMImpl_onBoolCallback onBoolCallback_ptr; + fp_CCMImpl_onStringCallback onStringCallback_ptr; + public: + void setupCallback(fp_CCMImpl_onVoidCallback a, fp_CCMImpl_onIntCallback b, fp_CCMImpl_onFloatCallback c, fp_CCMImpl_onBoolCallback d, fp_CCMImpl_onStringCallback e) { + this->onVoidCallback_ptr = a; + this->onIntCallback_ptr = b; + this->onFloatCallback_ptr = c; + this->onBoolCallback_ptr = d; + this->onStringCallback_ptr = e; + } + virtual void onVoidCallback(TestObjectClass& refData, TestObjectClass* pointerData) const { + onVoidCallback_ptr((int64_t)&refData, (int64_t)pointerData); + } + virtual int onIntCallback(int intValue01, int intValue02) const { + return (int)onIntCallback_ptr(intValue01, intValue02); + } + virtual float onFloatCallback(float floatValue01, float floatValue02) const { + return (float)onFloatCallback_ptr(floatValue01, floatValue02); + } + virtual bool onBoolCallback(bool boolValue01) const { + return (bool)onBoolCallback_ptr(boolValue01); + } + virtual void onStringCallback(const char* strValue01) const { + onStringCallback_ptr(strValue01); + } + }; + + extern "C" { + FFM_EXPORT int64_t jparser_com_github_xpenatan_jParser_example_testlib_CallbackClassManual_internal_1native_1create_1addr__(void) { + return (int64_t)new CallbackClassManualImpl(); + } + FFM_EXPORT int64_t jparser_com_github_xpenatan_jParser_example_testlib_CallbackClassManual_internal_1getAndroidCode__(void) { + long long myCode = 0; + myCode++; + #ifdef __ANDROID__ + return 1; + #else + return 0; + #endif + } + FFM_EXPORT void jparser_com_github_xpenatan_jParser_example_testlib_CallbackClassManual_internal_1native_1setupCallbacks__JJJJJJ(int64_t this_addr, int64_t onVoidCallback_fp, int64_t onIntCallback_fp, int64_t onFloatCallback_fp, int64_t onBoolCallback_fp, int64_t onStringCallback_fp) { + CallbackClassManualImpl* nativeObject = (CallbackClassManualImpl*)this_addr; + nativeObject->setupCallback((fp_CCMImpl_onVoidCallback)onVoidCallback_fp, (fp_CCMImpl_onIntCallback)onIntCallback_fp, (fp_CCMImpl_onFloatCallback)onFloatCallback_fp, (fp_CCMImpl_onBoolCallback)onBoolCallback_fp, (fp_CCMImpl_onStringCallback)onStringCallback_fp); + } + } + */ + + /*[-FFM;-ADD] + private void internal_ffm_onStringCallback(java.lang.foreign.MemorySegment seg) { + String str = seg.reinterpret(Long.MAX_VALUE).getString(0); + internal_onStringCallback(str); + } + */ + + /*[-FFM;-ADD] + private static final class FFMHandles { + private static final java.lang.foreign.SymbolLookup LOOKUP = java.lang.foreign.SymbolLookup.loaderLookup(); + private static final java.lang.foreign.Linker LINKER = java.lang.foreign.Linker.nativeLinker(); + static final java.lang.invoke.MethodHandle create_addr = LINKER.downcallHandle(LOOKUP.find("jparser_com_github_xpenatan_jParser_example_testlib_CallbackClassManual_internal_1native_1create_1addr__").orElseThrow(), java.lang.foreign.FunctionDescriptor.of(java.lang.foreign.ValueLayout.JAVA_LONG)); + static final java.lang.invoke.MethodHandle getAndroidCode = LINKER.downcallHandle(LOOKUP.find("jparser_com_github_xpenatan_jParser_example_testlib_CallbackClassManual_internal_1getAndroidCode__").orElseThrow(), java.lang.foreign.FunctionDescriptor.of(java.lang.foreign.ValueLayout.JAVA_LONG)); + static final java.lang.invoke.MethodHandle setupCallbacks = LINKER.downcallHandle(LOOKUP.find("jparser_com_github_xpenatan_jParser_example_testlib_CallbackClassManual_internal_1native_1setupCallbacks__JJJJJJ").orElseThrow(), java.lang.foreign.FunctionDescriptor.ofVoid(java.lang.foreign.ValueLayout.JAVA_LONG, java.lang.foreign.ValueLayout.JAVA_LONG, java.lang.foreign.ValueLayout.JAVA_LONG, java.lang.foreign.ValueLayout.JAVA_LONG, java.lang.foreign.ValueLayout.JAVA_LONG, java.lang.foreign.ValueLayout.JAVA_LONG)); + } + */ + /*[-TEAVM;-ADD] @org.teavm.jso.JSFunctor public interface onVoidCallback extends org.teavm.jso.JSObject { @@ -108,6 +183,12 @@ public static long GetAndroidCode() { return 0; #endif */ + /*[-FFM;-REPLACE] + private static long internal_getAndroidCode() { + try { return (long) FFMHandles.getAndroidCode.invokeExact(); } + catch(Throwable e) { throw new RuntimeException(e); } + } + */ private static native long internal_getAndroidCode(); /*[-JNI;-NATIVE] @@ -117,8 +198,33 @@ public static long GetAndroidCode() { var CallbackClassManualImpl = new [MODULE].CallbackClassManualImpl(); return [MODULE].getPointer(CallbackClassManualImpl); */ + /*[-FFM;-REPLACE] + private static long internal_native_create_addr() { + try { return (long) FFMHandles.create_addr.invokeExact(); } + catch(Throwable e) { throw new RuntimeException(e); } + } + */ private static native long internal_native_create_addr(); + /*[-FFM;-REPLACE_BLOCK] + { + try { + java.lang.invoke.MethodHandle mh_void = java.lang.invoke.MethodHandles.lookup().findVirtual(CallbackClassManual.class, "internal_onVoidCallback", java.lang.invoke.MethodType.methodType(void.class, long.class, long.class)).bindTo(this); + java.lang.foreign.MemorySegment stub_void = java.lang.foreign.Linker.nativeLinker().upcallStub(mh_void, java.lang.foreign.FunctionDescriptor.ofVoid(java.lang.foreign.ValueLayout.JAVA_LONG, java.lang.foreign.ValueLayout.JAVA_LONG), java.lang.foreign.Arena.ofAuto()); + java.lang.invoke.MethodHandle mh_int = java.lang.invoke.MethodHandles.lookup().findVirtual(CallbackClassManual.class, "internal_onIntCallback", java.lang.invoke.MethodType.methodType(int.class, int.class, int.class)).bindTo(this); + java.lang.foreign.MemorySegment stub_int = java.lang.foreign.Linker.nativeLinker().upcallStub(mh_int, java.lang.foreign.FunctionDescriptor.of(java.lang.foreign.ValueLayout.JAVA_INT, java.lang.foreign.ValueLayout.JAVA_INT, java.lang.foreign.ValueLayout.JAVA_INT), java.lang.foreign.Arena.ofAuto()); + java.lang.invoke.MethodHandle mh_float = java.lang.invoke.MethodHandles.lookup().findVirtual(CallbackClassManual.class, "internal_onFloatCallback", java.lang.invoke.MethodType.methodType(float.class, float.class, float.class)).bindTo(this); + java.lang.foreign.MemorySegment stub_float = java.lang.foreign.Linker.nativeLinker().upcallStub(mh_float, java.lang.foreign.FunctionDescriptor.of(java.lang.foreign.ValueLayout.JAVA_FLOAT, java.lang.foreign.ValueLayout.JAVA_FLOAT, java.lang.foreign.ValueLayout.JAVA_FLOAT), java.lang.foreign.Arena.ofAuto()); + java.lang.invoke.MethodHandle mh_bool = java.lang.invoke.MethodHandles.lookup().findVirtual(CallbackClassManual.class, "internal_onBoolCallback", java.lang.invoke.MethodType.methodType(boolean.class, boolean.class)).bindTo(this); + java.lang.foreign.MemorySegment stub_bool = java.lang.foreign.Linker.nativeLinker().upcallStub(mh_bool, java.lang.foreign.FunctionDescriptor.of(java.lang.foreign.ValueLayout.JAVA_BOOLEAN, java.lang.foreign.ValueLayout.JAVA_BOOLEAN), java.lang.foreign.Arena.ofAuto()); + java.lang.invoke.MethodHandle mh_string = java.lang.invoke.MethodHandles.lookup().findVirtual(CallbackClassManual.class, "internal_ffm_onStringCallback", java.lang.invoke.MethodType.methodType(void.class, java.lang.foreign.MemorySegment.class)).bindTo(this); + java.lang.foreign.MemorySegment stub_string = java.lang.foreign.Linker.nativeLinker().upcallStub(mh_string, java.lang.foreign.FunctionDescriptor.ofVoid(java.lang.foreign.ValueLayout.ADDRESS), java.lang.foreign.Arena.ofAuto()); + internal_native_setupCallbacks(native_address, stub_void.address(), stub_int.address(), stub_float.address(), stub_bool.address(), stub_string.address()); + } catch(Throwable e) { + throw new RuntimeException(e); + } + } + */ /*[-TEAVM;-REPLACE_BLOCK] { onVoidCallback onVoidCallback = new onVoidCallback() { @@ -166,6 +272,12 @@ private void setupCallbacks() { @org.teavm.jso.JSBody(params = { "this_addr", "onVoidCallback", "onIntCallback", "onFloatCallback", "onBoolCallback", "onStringCallback" }, script = "var CallbackClassManualImpl = [MODULE].wrapPointer(this_addr, [MODULE].CallbackClassManualImpl); CallbackClassManualImpl.onVoidCallback = onVoidCallback; CallbackClassManualImpl.onIntCallback = onIntCallback; CallbackClassManualImpl.onFloatCallback = onFloatCallback; CallbackClassManualImpl.onBoolCallback = onBoolCallback; CallbackClassManualImpl.onStringCallback = onStringCallback;") private static native void internal_native_setupCallbacks(int this_addr, onVoidCallback onVoidCallback, onIntCallback onIntCallback, onFloatCallback onFloatCallback, onBoolCallback onBoolCallback, onStringCallback onStringCallback); */ + /*[-FFM;-REPLACE] + private static void internal_native_setupCallbacks(long this_addr, long onVoidCallback_fp, long onIntCallback_fp, long onFloatCallback_fp, long onBoolCallback_fp, long onStringCallback_fp) { + try { FFMHandles.setupCallbacks.invokeExact(this_addr, onVoidCallback_fp, onIntCallback_fp, onFloatCallback_fp, onBoolCallback_fp, onStringCallback_fp); } + catch(Throwable e) { throw new RuntimeException(e); } + } + */ private native void internal_native_setupCallbacks(long this_addr); public void internal_onVoidCallback(long refData, long pointerData) { diff --git a/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/TestBufferManualClass.java b/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/TestBufferManualClass.java index adf0e2fd..eb0f0b02 100644 --- a/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/TestBufferManualClass.java +++ b/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/TestBufferManualClass.java @@ -5,12 +5,36 @@ public class TestBufferManualClass extends IDLBase { + /*[-FFM;-NATIVE] + extern "C" { + FFM_EXPORT void jparser_com_github_xpenatan_jParser_example_testlib_TestBufferManualClass_internal_1native_1updateByteBuffer__JJIB(int64_t this_addr, int64_t data_ptr, int32_t size, int8_t value) { + TestBufferManualClass* nativeObject = (TestBufferManualClass*)this_addr; + unsigned char* byteData = (unsigned char*)data_ptr; + nativeObject->updateByteBuffer(byteData, (int)size, (unsigned char)value); + } + } + */ + + /*[-FFM;-ADD] + private static final class FFMHandles { + private static final java.lang.foreign.SymbolLookup LOOKUP = java.lang.foreign.SymbolLookup.loaderLookup(); + private static final java.lang.foreign.Linker LINKER = java.lang.foreign.Linker.nativeLinker(); + static final java.lang.invoke.MethodHandle updateByteBuffer = LINKER.downcallHandle(LOOKUP.find("jparser_com_github_xpenatan_jParser_example_testlib_TestBufferManualClass_internal_1native_1updateByteBuffer__JJIB").orElseThrow(), java.lang.foreign.FunctionDescriptor.ofVoid(java.lang.foreign.ValueLayout.JAVA_LONG, java.lang.foreign.ValueLayout.JAVA_LONG, java.lang.foreign.ValueLayout.JAVA_INT, java.lang.foreign.ValueLayout.JAVA_BYTE)); + } + */ + /*[-TEAVM;-REPLACE_BLOCK] { org.teavm.jso.typedarrays.Uint8Array array = org.teavm.jso.typedarrays.Uint8Array.fromJavaBuffer(data); internal_native_updateByteBuffer(native_address, array, size, value); } */ + /*[-FFM;-REPLACE_BLOCK] + { + java.lang.foreign.MemorySegment seg = java.lang.foreign.MemorySegment.ofBuffer(data); + internal_native_updateByteBuffer(native_address, seg.address(), size, value); + } + */ public void updateByteBuffer(ByteBuffer data, int size, byte value) { internal_native_updateByteBuffer(native_address, data, size, value); } @@ -25,5 +49,11 @@ public void updateByteBuffer(ByteBuffer data, int size, byte value) { unsigned char* byteData = static_cast(dataAddress); nativeObject->updateByteBuffer(byteData, (int)size, (unsigned char)value); */ + /*[-FFM;-REPLACE] + private static void internal_native_updateByteBuffer(long this_addr, long data_ptr, int size, byte value) { + try { FFMHandles.updateByteBuffer.invokeExact(this_addr, data_ptr, size, value); } + catch(Throwable e) { throw new RuntimeException(e); } + } + */ private static native void internal_native_updateByteBuffer(long this_addr, ByteBuffer data, int size, byte value); } \ No newline at end of file diff --git a/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/TestLibLoader.java b/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/TestLibLoader.java index 15ec63a6..57e692ea 100644 --- a/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/TestLibLoader.java +++ b/examples/TestLib/lib/lib-base/src/main/java/com/github/xpenatan/jParser/example/testlib/TestLibLoader.java @@ -11,6 +11,10 @@ public class TestLibLoader { #include "CustomCode.h" */ + /*[-FFM;-NATIVE] + #include "CustomCode.h" + */ + public static void init(JParserLibraryLoaderListener listener) { JParserLibraryLoader.load(LIB_NAME, listener); } diff --git a/examples/TestLib/lib/lib-build/build.gradle.kts b/examples/TestLib/lib/lib-build/build.gradle.kts index 205fbbbb..68c136cc 100644 --- a/examples/TestLib/lib/lib-build/build.gradle.kts +++ b/examples/TestLib/lib/lib-build/build.gradle.kts @@ -11,25 +11,13 @@ val mainClassName = "BuildLib" dependencies { implementation(project(":examples:TestLib:lib:lib-base")) - - if(LibExt.exampleUseRepoLibs) { - implementation("com.github.xpenatan.jParser:jParser-core:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-idl:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-teavm:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-cpp:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-build:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:jParser-build-tool:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:idl-helper-core:-SNAPSHOT") - } - else { - implementation(project(":jParser:jParser-core")) - implementation(project(":jParser:jParser-idl")) - implementation(project(":jParser:jParser-teavm")) - implementation(project(":jParser:jParser-cpp")) - implementation(project(":jParser:jParser-build")) - implementation(project(":jParser:jParser-build-tool")) - implementation(project(":idl-helper:idl-helper-core")) - } + implementation(project(":jParser:jParser-core")) + implementation(project(":jParser:jParser-idl")) + implementation(project(":jParser:jParser-teavm")) + implementation(project(":jParser:jParser-jni")) + implementation(project(":jParser:jParser-build")) + implementation(project(":jParser:jParser-build-tool")) + implementation(project(":idl-helper:idl-helper-core")) } tasks.register("TestLib_build_project") { @@ -40,66 +28,91 @@ tasks.register("TestLib_build_project") { classpath = sourceSets["main"].runtimeClasspath } -tasks.register("TestLib_build_project_all") { +tasks.register("TestLib_build_project_teavm") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("teavm", "windows64", "linux64", "mac64", "macArm", "android", "ios") + args = mutableListOf("teavm") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("TestLib_build_project_teavm") { +tasks.register("TestLib_build_project_ffm_windows64") { group = "lib" - description = "Generate native project" + description = "Generate FFM Java code and compile for Windows with FFMGlue" mainClass.set(mainClassName) - args = mutableListOf("teavm") + args = mutableListOf("ffm_windows64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("TestLib_build_project_windows64") { +tasks.register("TestLib_build_project_ffm_linux64") { + group = "lib" + description = "Generate FFM Java code and compile for Linux with FFMGlue" + mainClass.set(mainClassName) + args = mutableListOf("ffm_linux64") + classpath = sourceSets["main"].runtimeClasspath +} + +tasks.register("TestLib_build_project_ffm_mac64") { + group = "lib" + description = "Generate FFM Java code and compile for Mac with FFMGlue" + mainClass.set(mainClassName) + args = mutableListOf("ffm_mac64") + classpath = sourceSets["main"].runtimeClasspath +} + +tasks.register("TestLib_build_project_ffm_macArm") { + group = "lib" + description = "Generate FFM Java code and compile for Mac ARM with FFMGlue" + mainClass.set(mainClassName) + args = mutableListOf("ffm_macArm") + classpath = sourceSets["main"].runtimeClasspath +} + +tasks.register("TestLib_build_project_jni_windows64") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("windows64") + args = mutableListOf("jni_windows64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("TestLib_build_project_linux64") { +tasks.register("TestLib_build_project_jni_linux64") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("linux64") + args = mutableListOf("jni_linux64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("TestLib_build_project_mac64") { +tasks.register("TestLib_build_project_jni_mac64") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("mac64") + args = mutableListOf("jni_mac64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("TestLib_build_project_macArm") { +tasks.register("TestLib_build_project_jni_macArm") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("macArm") + args = mutableListOf("jni_macArm") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("TestLib_build_project_android") { +tasks.register("TestLib_build_project_jni_android") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("android") + args = mutableListOf("jni_android") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("TestLib_build_project_ios") { +tasks.register("TestLib_build_project_jni_ios") { group = "lib" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("ios") + args = mutableListOf("jni_ios") classpath = sourceSets["main"].runtimeClasspath -} \ No newline at end of file +} + diff --git a/examples/TestLib/lib/lib-build/src/main/cpp/source/TestLib/src/TestLib.h b/examples/TestLib/lib/lib-build/src/main/cpp/source/TestLib/src/TestLib.h index 8325de3e..dedbd32d 100644 --- a/examples/TestLib/lib/lib-build/src/main/cpp/source/TestLib/src/TestLib.h +++ b/examples/TestLib/lib/lib-build/src/main/cpp/source/TestLib/src/TestLib.h @@ -165,36 +165,24 @@ class TestBufferManualClass { public: void updateByteBuffer(unsigned char* data, int size, unsigned char value) { - for(int i = 0; i < size; i++) { - std::cout << "[" << i << "]: " << static_cast(data[i]) << std::endl; - } for(int i = 0; i < size; i++) { data[i] = value; } } void updateIntBuffer(int* data, int size, int value) { - for(int i = 0; i < size; i++) { - std::cout << "[" << i << "]: " << data[i] << std::endl; - } for(int i = 0; i < size; i++) { data[i] = value; } } void updateShortBuffer(short* data, int size, short value) { - for(int i = 0; i < size; i++) { - std::cout << "[" << i << "]: " << data[i] << std::endl; - } for(int i = 0; i < size; i++) { data[i] = value; } } void updateFloatBuffer(float* data, int size, float value) { - for(int i = 0; i < size; i++) { - std::cout << "[" << i << "]: " << data[i] << std::endl; - } for(int i = 0; i < size; i++) { data[i] = value; } diff --git a/examples/TestLib/lib/lib-build/src/main/java/BuildLib.java b/examples/TestLib/lib/lib-build/src/main/java/BuildLib.java index e2664b13..84c95451 100644 --- a/examples/TestLib/lib/lib-build/src/main/java/BuildLib.java +++ b/examples/TestLib/lib/lib-build/src/main/java/BuildLib.java @@ -9,7 +9,6 @@ import com.github.xpenatan.jParser.builder.tool.BuildToolListener; import com.github.xpenatan.jParser.builder.tool.BuildToolOptions; import com.github.xpenatan.jParser.builder.tool.BuilderTool; -import com.github.xpenatan.jParser.core.JParser; import com.github.xpenatan.jParser.idl.IDLReader; import java.util.ArrayList; @@ -22,7 +21,6 @@ public static void main(String[] args) throws Exception { String sourceDir = "/src/main/cpp/source/TestLib/src"; WindowsMSVCTarget.DEBUG_BUILD = true; - JParser.CREATE_IDL_HELPER = false; // NativeCPPGenerator.SKIP_GLUE_CODE = true; BuildToolOptions.BuildToolParams data = new BuildToolOptions.BuildToolParams(); @@ -42,36 +40,52 @@ public void onAddTarget(BuildToolOptions op, IDLReader idlReader, ArrayList/ffm/ to avoid conflicts with JNI libs. + + private static BuildMultiTarget getFFMWindowMingwTarget(BuildToolOptions op) { + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String sourceDir = op.getSourceDir(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + // Make a static library (same as JNI — shared C++ source) + WindowsTarget compileStaticTarget = new WindowsTarget(); + compileStaticTarget.libDirSuffix = "windows/mingw/ffm"; + compileStaticTarget.isStatic = true; + compileStaticTarget.cppFlags.add("-std=c++11"); + compileStaticTarget.headerDirs.add("-I" + sourceDir); + compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); + compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); + multiTarget.add(compileStaticTarget); + + // Link with FFMGlue instead of JNIGlue — no JNI headers + WindowsTarget linkTarget = new WindowsTarget(); + linkTarget.libDirSuffix = "windows/mingw/ffm"; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("-std=c++11"); + linkTarget.headerDirs.add("-I" + sourceDir); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.linkerFlags.add("-Wl,--whole-archive"); + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/windows/mingw/ffm/" + op.libName + "64_.a"); + linkTarget.linkerFlags.add("-Wl,--no-whole-archive"); + multiTarget.add(linkTarget); + + return multiTarget; + } + + private static BuildMultiTarget getFFMWindowVCTarget(BuildToolOptions op) { + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String sourceDir = op.getSourceDir(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + // Make a static library + WindowsMSVCTarget compileStaticTarget = new WindowsMSVCTarget(); + compileStaticTarget.libDirSuffix = "windows/vc/ffm"; + compileStaticTarget.isStatic = true; + compileStaticTarget.cppFlags.add("/std:c++11"); + compileStaticTarget.headerDirs.add("-I" + sourceDir); + compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); + compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); + multiTarget.add(compileStaticTarget); + + // Link with FFMGlue instead of JNIGlue — no JNI headers + WindowsMSVCTarget linkTarget = new WindowsMSVCTarget(); + linkTarget.libDirSuffix = "windows/vc/ffm"; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("/std:c++11"); + linkTarget.headerDirs.add("-I" + sourceDir); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.linkerFlags.add("/WHOLEARCHIVE:" + libBuildCPPPath + "/libs/windows/vc/ffm/" + op.libName + "64_.lib"); + linkTarget.linkerFlags.add("-DLL"); + multiTarget.add(linkTarget); + + return multiTarget; + } + + private static BuildMultiTarget getFFMLinuxTarget(BuildToolOptions op) { + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String sourceDir = op.getSourceDir(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + // Make a static library + LinuxTarget compileStaticTarget = new LinuxTarget(); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = "linux/ffm"; + compileStaticTarget.cppFlags.add("-std=c++11"); + compileStaticTarget.cppFlags.add("-fPIC"); + compileStaticTarget.headerDirs.add("-I" + sourceDir); + compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); + compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); + multiTarget.add(compileStaticTarget); + + // Link with FFMGlue instead of JNIGlue — no JNI headers + LinuxTarget linkTarget = new LinuxTarget(); + linkTarget.libDirSuffix = "linux/ffm"; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("-std=c++11"); + linkTarget.cppFlags.add("-fPIC"); + linkTarget.headerDirs.add("-I" + sourceDir); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.linkerFlags.add("-Wl,--whole-archive"); + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/linux/ffm/lib" + op.libName + "64_.a"); + linkTarget.linkerFlags.add("-Wl,--no-whole-archive"); + multiTarget.add(linkTarget); + + return multiTarget; + } + + private static BuildMultiTarget getFFMMacTarget(BuildToolOptions op, boolean isArm) { + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String sourceDir = op.getSourceDir(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + String macSubDir = isArm ? "mac/arm/ffm" : "mac/ffm"; + + // Make a static library + MacTarget compileStaticTarget = new MacTarget(isArm); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = macSubDir; + compileStaticTarget.cppFlags.add("-std=c++11"); + compileStaticTarget.cppFlags.add("-fPIC"); + compileStaticTarget.headerDirs.add("-I" + sourceDir); + compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + compileStaticTarget.cppInclude.add(sourceDir + "**.cpp"); + compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); + multiTarget.add(compileStaticTarget); + + // Link with FFMGlue instead of JNIGlue — no JNI headers + MacTarget linkTarget = new MacTarget(isArm); + linkTarget.libDirSuffix = macSubDir; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("-std=c++11"); + linkTarget.cppFlags.add("-fPIC"); + linkTarget.headerDirs.add("-I" + sourceDir); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.linkerFlags.add("-Wl,-force_load"); + if(isArm) { + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/mac/arm/ffm/lib" + op.libName + "64_.a"); + } + else { + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/mac/ffm/lib" + op.libName + "64_.a"); + } + multiTarget.add(linkTarget); + + return multiTarget; + } } \ No newline at end of file diff --git a/examples/TestLib/lib/lib-core/build.gradle.kts b/examples/TestLib/lib/lib-core/build.gradle.kts index bafd1980..d4952f3d 100644 --- a/examples/TestLib/lib/lib-core/build.gradle.kts +++ b/examples/TestLib/lib/lib-core/build.gradle.kts @@ -9,16 +9,9 @@ java { } dependencies { - if(LibExt.exampleUseRepoLibs) { - api("com.github.xpenatan.jParser:loader-core:-SNAPSHOT") - api("com.github.xpenatan.jParser:idl-core:-SNAPSHOT") - api("com.github.xpenatan.jParser:idl-helper-core:-SNAPSHOT") - } - else { - api(project(":loader:loader-core")) - api(project(":idl:idl-core")) - api(project(":idl-helper:idl-helper-core")) - } + api(project(":loader:loader-core")) + api(project(":idl:idl-core")) + api(project(":idl-helper:idl-helper-core")) } tasks.named("clean") { diff --git a/examples/TestLib/lib/lib-desktop-ffm/build.gradle.kts b/examples/TestLib/lib/lib-desktop-ffm/build.gradle.kts new file mode 100644 index 00000000..011b5411 --- /dev/null +++ b/examples/TestLib/lib/lib-desktop-ffm/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + id("java") + id("java-library") +} + +java { + sourceCompatibility = JavaVersion.toVersion(LibExt.java24Target) + targetCompatibility = JavaVersion.toVersion(LibExt.java24Target) +} + +dependencies { + api(project(":loader:loader-core")) + api(project(":idl:idl-core")) + api(project(":idl-helper:idl-helper-desktop-ffm")) +} + +// Bundle FFM-compiled native libraries into the JAR. +val libDir = "${projectDir}/../lib-build/build/c++/libs" +val windowsFile = "$libDir/windows/vc/ffm/TestLib64.dll" +val linuxFile = "$libDir/linux/ffm/libTestLib64.so" +val macFile = "$libDir/mac/ffm/libTestLib64.dylib" +val macArmFile = "$libDir/mac/arm/ffm/libTestLibarm64.dylib" + +tasks.jar { + from(windowsFile) + from(linuxFile) + from(macFile) + from(macArmFile) +} + +tasks.named("clean") { + doFirst { + val srcPath = "$projectDir/src/main/" + project.delete(files(srcPath)) + } +} diff --git a/examples/TestLib/lib/lib-desktop/build.gradle.kts b/examples/TestLib/lib/lib-desktop-jni/build.gradle.kts similarity index 62% rename from examples/TestLib/lib/lib-desktop/build.gradle.kts rename to examples/TestLib/lib/lib-desktop-jni/build.gradle.kts index 04d89149..29d5ae26 100644 --- a/examples/TestLib/lib/lib-desktop/build.gradle.kts +++ b/examples/TestLib/lib/lib-desktop-jni/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("java") + id("java-library") } java { @@ -9,10 +10,10 @@ java { val libDir = "${projectDir}/../lib-build/build/c++/libs" //val windowsFile = "$libDir/windows/TestLib64.dll" -val windowsFile = "$libDir/windows/vc/TestLib64.dll" -val linuxFile = "$libDir/linux/libTestLib64.so" -val macFile = "$libDir/mac/libTestLib64.dylib" -val macArmFile = "$libDir/mac/arm/libTestLibarm64.dylib" +val windowsFile = "$libDir/windows/vc/jni/TestLib64.dll" +val linuxFile = "$libDir/linux/jni/libTestLib64.so" +val macFile = "$libDir/mac/jni/libTestLib64.dylib" +val macArmFile = "$libDir/mac/arm/jni/libTestLibarm64.dylib" tasks.jar { from(windowsFile) @@ -22,14 +23,11 @@ tasks.jar { } dependencies { - if(LibExt.exampleUseRepoLibs) { - implementation("com.github.xpenatan.jParser:idl-helper-desktop:-SNAPSHOT") - testImplementation("com.github.xpenatan.jParser:loader-core:-SNAPSHOT") - } - else { - implementation(project(":idl-helper:idl-helper-desktop")) - testImplementation(project(":loader:loader-core")) - } + api(project(":loader:loader-core")) + api(project(":idl:idl-core")) + api(project(":idl-helper:idl-helper-desktop-jni")) + + testImplementation(project(":loader:loader-core")) testImplementation(project(":examples:TestLib:lib:lib-core")) testImplementation("junit:junit:${LibExt.jUnitVersion}") } diff --git a/examples/TestLib/lib/lib-desktop/src/test/java/com/github/xpenatan/jParser/example/NormalClassTest.java b/examples/TestLib/lib/lib-desktop/src/test/java/com/github/xpenatan/jParser/example/NormalClassTest.java deleted file mode 100644 index 619b3949..00000000 --- a/examples/TestLib/lib/lib-desktop/src/test/java/com/github/xpenatan/jParser/example/NormalClassTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.github.xpenatan.jParser.example; - -import com.github.xpenatan.jParser.example.testlib.NormalClass; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class NormalClassTest { - -// @BeforeClass -// public static void setUp() throws Exception { -// String libDir = "build/libs/desktop.jar"; -// new JParserLibraryLoader(libDir).load("exampleLib"); -// } -// -// @Test -// public void test_add_int() { -// NormalClass normalClass = new NormalClass(); -// int ret = normalClass.addIntValue(10, 10); -// assertEquals(20, ret); -// } -// -// @Test -// public void test_static_sub_int() { -// int ret = NormalClass.subIntValue(11, 10); -// assertEquals(1, ret); -// } -// -// @Test -// public void test_static_sub_int_subValue() { -// int ret = NormalClass.subIntValue(11, 10, 1); -// assertEquals(0, ret); -// } -// -// @Test -// public void test_add_float() { -// NormalClass normalClass = new NormalClass(); -// float ret = normalClass.addFloatValue(10.3f, 10.3f); -// assertEquals(20.6, ret, 1.0f); -// } -// -// @Test -// public void test_invert_boolean_should_be_false() { -// NormalClass normalClass = new NormalClass(); -// boolean ret = normalClass.invertBoolean(true); -// assertFalse(ret); -// } -// -// @Test -// public void test_invert_boolean_should_be_true() { -// NormalClass normalClass = new NormalClass(); -// boolean ret = normalClass.invertBoolean(false); -// assertTrue(ret); -// } -// -// @Test -// public void test_attribute() { -// NormalClass normalClass = new NormalClass(); -// normalClass.set_hiddenInt(10); -// int retValue = normalClass.get_hiddenInt(); -// assertEquals(10, retValue); -// } - -} - diff --git a/examples/TestLib/lib/lib-teavm/build.gradle.kts b/examples/TestLib/lib/lib-teavm/build.gradle.kts index 2c591902..480bb131 100644 --- a/examples/TestLib/lib/lib-teavm/build.gradle.kts +++ b/examples/TestLib/lib/lib-teavm/build.gradle.kts @@ -18,20 +18,11 @@ dependencies { implementation("org.teavm:teavm-jso:${LibExt.teaVMVersion}") implementation("org.teavm:teavm-classlib:${LibExt.teaVMVersion}") - if(LibExt.exampleUseRepoLibs) { - implementation("com.github.xpenatan.jParser:loader-teavm:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:loader-core:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:idl-teavm:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:idl-core:-SNAPSHOT") - implementation("com.github.xpenatan.jParser:idl--helper-teavm:-SNAPSHOT") - } - else { - implementation(project(":loader:loader-teavm")) - implementation(project(":loader:loader-core")) - implementation(project(":idl:idl-teavm")) - implementation(project(":idl:idl-core")) - implementation(project(":idl-helper:idl-helper-teavm")) - } + implementation(project(":loader:loader-teavm")) + implementation(project(":loader:loader-core")) + implementation(project(":idl:idl-teavm")) + implementation(project(":idl:idl-core")) + implementation(project(":idl-helper:idl-helper-teavm")) // testImplementation(project(":example:lib:lib-core")) // testImplementation("junit:junit:${LibExt.jUnitVersion}") // testImplementation("org.teavm:teavm-core:${LibExt.teaVMVersion}") diff --git a/gradle.properties b/gradle.properties index efb638d6..deb1583b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ android.useAndroidX=true -version=1.0.0 \ No newline at end of file +version=1.1.0 \ No newline at end of file diff --git a/idl-helper/idl-helper-android/build.gradle.kts b/idl-helper/idl-helper-android/build.gradle.kts index 26559f46..1aa31a05 100644 --- a/idl-helper/idl-helper-android/build.gradle.kts +++ b/idl-helper/idl-helper-android/build.gradle.kts @@ -42,6 +42,8 @@ tasks.named("preBuild").configure { } dependencies { + implementation(project(":idl:idl-core")) + implementation(project(":loader:loader-core")) } afterEvaluate { @@ -55,4 +57,11 @@ afterEvaluate { } } } +} + +tasks.named("clean") { + doFirst { + val srcPath = "$projectDir/src/main/" + project.delete(files(srcPath)) + } } \ No newline at end of file diff --git a/idl-helper/idl-helper-base/src/main/java/idl/IDLLoader.java b/idl-helper/idl-helper-base/src/main/java/idl/IDLLoader.java index 8099c21a..df70fea7 100644 --- a/idl-helper/idl-helper-base/src/main/java/idl/IDLLoader.java +++ b/idl-helper/idl-helper-base/src/main/java/idl/IDLLoader.java @@ -11,6 +11,10 @@ public class IDLLoader { #include "IDLCustomCode.h" */ + /*[-FFM;-NATIVE] + #include "IDLCustomCode.h" + */ + public static void init(JParserLibraryLoaderListener listener) { JParserLibraryLoader.load(LIB_NAME, listener); } diff --git a/idl-helper/idl-helper-build/build.gradle.kts b/idl-helper/idl-helper-build/build.gradle.kts index 4c4a0349..8cb99be6 100644 --- a/idl-helper/idl-helper-build/build.gradle.kts +++ b/idl-helper/idl-helper-build/build.gradle.kts @@ -14,7 +14,8 @@ dependencies { implementation(project(":jParser:jParser-core")) implementation(project(":jParser:jParser-idl")) implementation(project(":jParser:jParser-teavm")) - implementation(project(":jParser:jParser-cpp")) + implementation(project(":jParser:jParser-jni")) + implementation(project(":jParser:jParser-ffm")) implementation(project(":jParser:jParser-build")) implementation(project(":jParser:jParser-build-tool")) } @@ -27,66 +28,90 @@ tasks.register("idl_helper_build_project") { classpath = sourceSets["main"].runtimeClasspath } -tasks.register("idl_helper_build_project_all") { +tasks.register("idl_helper_build_project_teavm") { group = "idl-helper" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("teavm", "windows64", "linux64", "mac64", "macArm", "android", "ios") + args = mutableListOf("teavm") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("idl_helper_build_project_teavm") { +tasks.register("idl_helper_build_project_jni_windows64") { group = "idl-helper" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("teavm") + args = mutableListOf("jni_windows64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("idl_helper_build_project_windows64") { +tasks.register("idl_helper_build_project_jni_linux64") { group = "idl-helper" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("windows64") + args = mutableListOf("jni_linux64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("idl_helper_build_project_linux64") { +tasks.register("idl_helper_build_project_jni_mac64") { group = "idl-helper" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("linux64") + args = mutableListOf("jni_mac64") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("idl_helper_build_project_mac64") { +tasks.register("idl_helper_build_project_jni_macArm") { group = "idl-helper" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("mac64") + args = mutableListOf("jni_macArm") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("idl_helper_build_project_macArm") { +tasks.register("idl_helper_build_project_jni_android") { group = "idl-helper" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("macArm") + args = mutableListOf("jni_android") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("idl_helper_build_project_android") { +tasks.register("idl_helper_build_project_jni_ios") { group = "idl-helper" description = "Generate native project" mainClass.set(mainClassName) - args = mutableListOf("android") + args = mutableListOf("jni_ios") classpath = sourceSets["main"].runtimeClasspath } -tasks.register("idl_helper_build_project_ios") { +tasks.register("idl_helper_build_project_ffm_windows64") { group = "idl-helper" - description = "Generate native project" + description = "Generate FFM code + compile FFM native for Windows" mainClass.set(mainClassName) - args = mutableListOf("ios") + args = mutableListOf("ffm_windows64") classpath = sourceSets["main"].runtimeClasspath -} \ No newline at end of file +} + +tasks.register("idl_helper_build_project_ffm_linux64") { + group = "idl-helper" + description = "Generate FFM code + compile FFM native for Linux" + mainClass.set(mainClassName) + args = mutableListOf("ffm_linux64") + classpath = sourceSets["main"].runtimeClasspath +} + +tasks.register("idl_helper_build_project_ffm_mac64") { + group = "idl-helper" + description = "Generate FFM code + compile FFM native for macOS" + mainClass.set(mainClassName) + args = mutableListOf("ffm_mac64") + classpath = sourceSets["main"].runtimeClasspath +} + +tasks.register("idl_helper_build_project_ffm_macArm") { + group = "idl-helper" + description = "Generate FFM code + compile FFM native for macOS ARM" + mainClass.set(mainClassName) + args = mutableListOf("ffm_macArm") + classpath = sourceSets["main"].runtimeClasspath +} diff --git a/idl-helper/idl-helper-build/src/main/java/BuildIDLHelper.java b/idl-helper/idl-helper-build/src/main/java/BuildIDLHelper.java index 7964e4ad..6c1b6ef3 100644 --- a/idl-helper/idl-helper-build/src/main/java/BuildIDLHelper.java +++ b/idl-helper/idl-helper-build/src/main/java/BuildIDLHelper.java @@ -8,6 +8,7 @@ import com.github.xpenatan.jParser.builder.tool.BuildToolListener; import com.github.xpenatan.jParser.builder.tool.BuildToolOptions; import com.github.xpenatan.jParser.builder.tool.BuilderTool; +import com.github.xpenatan.jParser.core.JParser; import com.github.xpenatan.jParser.idl.IDLReader; import java.util.ArrayList; @@ -19,7 +20,7 @@ public static void main(String[] args) throws Exception { String basePackage = "com.github.xpenatan.jparser.idl"; WindowsMSVCTarget.DEBUG_BUILD = false; -// JParser.CREATE_IDL_HELPER = false; + JParser.CREATE_IDL_HELPER = true; // NativeCPPGenerator.SKIP_GLUE_CODE = true; BuildToolOptions.BuildToolParams data = new BuildToolOptions.BuildToolParams(); @@ -37,34 +38,49 @@ public void onAddTarget(BuildToolOptions op, IDLReader idlReader, ArrayList/ffm/ to avoid conflicts with JNI libs. + + private static BuildMultiTarget getFFMWindowVCTarget(BuildToolOptions op) { + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + WindowsMSVCTarget compileStaticTarget = new WindowsMSVCTarget(); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = "windows/vc/ffm"; + compileStaticTarget.cppFlags.add("-std:c++11"); + compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + compileStaticTarget.cppInclude.add(libBuildCPPPath + "/src/idl/IDLHelper.cpp"); + compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); + multiTarget.add(compileStaticTarget); + + WindowsMSVCTarget linkTarget = new WindowsMSVCTarget(); + linkTarget.libDirSuffix = "windows/vc/ffm"; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("-std:c++11"); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.linkerFlags.add("/WHOLEARCHIVE:" + libBuildCPPPath + "/libs/windows/vc/ffm/" + op.libName + "64_.lib"); + linkTarget.linkerFlags.add("-DLL"); + multiTarget.add(linkTarget); + + return multiTarget; + } + + private static BuildMultiTarget getFFMLinuxTarget(BuildToolOptions op) { + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + LinuxTarget compileStaticTarget = new LinuxTarget(); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = "linux/ffm"; + compileStaticTarget.cppFlags.add("-std=c++11"); + compileStaticTarget.cppFlags.add("-fPIC"); + compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + compileStaticTarget.cppInclude.add(libBuildCPPPath + "/src/idl/IDLHelper.cpp"); + compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); + multiTarget.add(compileStaticTarget); + + LinuxTarget linkTarget = new LinuxTarget(); + linkTarget.libDirSuffix = "linux/ffm"; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("-std=c++11"); + linkTarget.cppFlags.add("-fPIC"); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.linkerFlags.add("-Wl,--whole-archive"); + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/linux/ffm/lib" + op.libName + "64_.a"); + linkTarget.linkerFlags.add("-Wl,--no-whole-archive"); + multiTarget.add(linkTarget); + + return multiTarget; + } + + private static BuildMultiTarget getFFMMacTarget(BuildToolOptions op, boolean isArm) { + BuildMultiTarget multiTarget = new BuildMultiTarget(); + String libBuildCPPPath = op.getModuleBuildCPPPath(); + + String macSubDir = isArm ? "mac/arm/ffm" : "mac/ffm"; + + MacTarget compileStaticTarget = new MacTarget(isArm); + compileStaticTarget.isStatic = true; + compileStaticTarget.libDirSuffix = macSubDir; + compileStaticTarget.cppFlags.add("-std=c++11"); + compileStaticTarget.cppFlags.add("-fPIC"); + compileStaticTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + compileStaticTarget.cppInclude.add(libBuildCPPPath + "/src/idl/IDLHelper.cpp"); + compileStaticTarget.cppInclude.add(op.getCustomSourceDir() + "*.cpp"); + multiTarget.add(compileStaticTarget); + + MacTarget linkTarget = new MacTarget(isArm); + linkTarget.libDirSuffix = macSubDir; + linkTarget.addFFMGlueCode(libBuildCPPPath); + linkTarget.cppFlags.add("-std=c++11"); + linkTarget.cppFlags.add("-fPIC"); + linkTarget.headerDirs.add("-I" + op.getCustomSourceDir()); + linkTarget.linkerFlags.add("-Wl,-force_load"); + if(isArm) { + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/mac/arm/ffm/lib" + op.libName + "64_.a"); + } + else { + linkTarget.linkerFlags.add(libBuildCPPPath + "/libs/mac/ffm/lib" + op.libName + "64_.a"); + } + multiTarget.add(linkTarget); + + return multiTarget; + } } \ No newline at end of file diff --git a/idl-helper/idl-helper-desktop-ffm/build.gradle.kts b/idl-helper/idl-helper-desktop-ffm/build.gradle.kts new file mode 100644 index 00000000..297483fa --- /dev/null +++ b/idl-helper/idl-helper-desktop-ffm/build.gradle.kts @@ -0,0 +1,52 @@ +plugins { + id("java") +} + +val moduleName = "idl-helper-desktop-ffm" + +dependencies { + implementation(project(":idl:idl-core")) + implementation(project(":loader:loader-core")) +} + +java { + sourceCompatibility = JavaVersion.toVersion(LibExt.java24Target) + targetCompatibility = JavaVersion.toVersion(LibExt.java24Target) +} + +// Bundle FFM-compiled native libraries into the JAR. +val libDir = "${projectDir}/../idl-helper-build/build/c++/libs" +val windowsFile = "$libDir/windows/vc/ffm/idl64.dll" +val linuxFile = "$libDir/linux/ffm/libidl64.so" +val macFile = "$libDir/mac/ffm/libidl64.dylib" +val macArmFile = "$libDir/mac/arm/ffm/libidlarm64.dylib" + +tasks.jar { + from(windowsFile) + from(linuxFile) + from(macFile) + from(macArmFile) +} + +java { + withJavadocJar() + withSourcesJar() +} + +tasks.named("clean") { + doFirst { + val srcPath = "$projectDir/src/main/java" + project.delete(files(srcPath)) + } +} + +publishing { + publications { + create("maven") { + artifactId = moduleName + group = LibExt.groupId + version = LibExt.libVersion + from(components["java"]) + } + } +} diff --git a/idl-helper/idl-helper-desktop/build.gradle.kts b/idl-helper/idl-helper-desktop-jni/build.gradle.kts similarity index 67% rename from idl-helper/idl-helper-desktop/build.gradle.kts rename to idl-helper/idl-helper-desktop-jni/build.gradle.kts index deaec68f..48220660 100644 --- a/idl-helper/idl-helper-desktop/build.gradle.kts +++ b/idl-helper/idl-helper-desktop-jni/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("java") } -val moduleName = "idl-helper-desktop" +val moduleName = "idl-helper-desktop-jni" java { sourceCompatibility = JavaVersion.toVersion(LibExt.java8Target) @@ -10,10 +10,10 @@ java { } val libDir = "${projectDir}/../idl-helper-build/build/c++/libs" -val windowsFile = "$libDir/windows/vc/idl64.dll" -val linuxFile = "$libDir/linux/libidl64.so" -val macFile = "$libDir/mac/libidl64.dylib" -val macArmFile = "$libDir/mac/arm/libidlarm64.dylib" +val windowsFile = "$libDir/windows/vc/jni/idl64.dll" +val linuxFile = "$libDir/linux/jni/libidl64.so" +val macFile = "$libDir/mac/jni/libidl64.dylib" +val macArmFile = "$libDir/mac/arm/jni/libidlarm64.dylib" tasks.jar { from(windowsFile) @@ -23,6 +23,8 @@ tasks.jar { } dependencies { + implementation(project(":idl:idl-core")) + implementation(project(":loader:loader-core")) } tasks.named("clean") { diff --git a/jParser/jParser-base/src/main/java/idl/helper/IDLArray.java b/jParser/jParser-base/src/main/java/idl/helper/IDLArray.java index df38a3e3..e13cd9cc 100644 --- a/jParser/jParser-base/src/main/java/idl/helper/IDLArray.java +++ b/jParser/jParser-base/src/main/java/idl/helper/IDLArray.java @@ -39,5 +39,9 @@ public void resize(int size) { IDL::IDLArray* nativeObject = (IDL::IDLArray*)this_addr; nativeObject->resize(size); */ + /*[-FFM;-NATIVE] + IDL::IDLArray* nativeObject = (IDL::IDLArray*)this_addr; + nativeObject->resize(size); + */ public static native void internal_native_resize(long this_addr, int size); } diff --git a/jParser/jParser-base/src/main/java/idl/helper/IDLString.java b/jParser/jParser-base/src/main/java/idl/helper/IDLString.java index 477a8abb..6c0ea13e 100644 --- a/jParser/jParser-base/src/main/java/idl/helper/IDLString.java +++ b/jParser/jParser-base/src/main/java/idl/helper/IDLString.java @@ -34,6 +34,10 @@ public String c_str() { var returnedJSObj = jsObj.c_str(); return returnedJSObj; */ + /*[-FFM;-NATIVE] + IDLString* nativeObject = (IDLString*)this_addr; + return nativeObject->c_str(); + */ private static native String internal_native_c_str(long this_addr); public String data() { @@ -52,5 +56,9 @@ public String data() { var returnedJSObj = jsObj.data(); return returnedJSObj; */ + /*[-FFM;-NATIVE] + IDLString* nativeObject = (IDLString*)this_addr; + return nativeObject->data(); + */ private static native String internal_native_data(long this_addr); } \ No newline at end of file diff --git a/jParser/jParser-base/src/main/java/idl/helper/IDLUtils.java b/jParser/jParser-base/src/main/java/idl/helper/IDLUtils.java index 64bad66f..28e73822 100644 --- a/jParser/jParser-base/src/main/java/idl/helper/IDLUtils.java +++ b/jParser/jParser-base/src/main/java/idl/helper/IDLUtils.java @@ -23,6 +23,12 @@ public static String getJSString(long addr) { internal_native_copyToByteBuffer((int)source.native_void_address, destinationArray, offset, sizeInBytes); } */ + /*[-FFM;-REPLACE_BLOCK] + { + java.lang.foreign.MemorySegment seg = java.lang.foreign.MemorySegment.ofBuffer(destination); + internal_native_copyToByteBuffer(source.native_void_address, seg.address(), offset, sizeInBytes); + } + */ public static void copyToByteBuffer(IDLBase source, ByteBuffer destination, int offset, int sizeInBytes) { internal_native_copyToByteBuffer(source.native_void_address, destination, offset, sizeInBytes); } @@ -39,5 +45,29 @@ public static void copyToByteBuffer(IDLBase source, ByteBuffer destination, int char* bufferAddress = (char*)env->GetDirectBufferAddress(destination); memcpy(bufferAddress + offset, data, sizeInBytes); */ + /*[-FFM;-NATIVE] + #include + extern "C" { + FFM_EXPORT void jparser_com_github_xpenatan_jparser_idl_helper_IDLUtils_internal_1native_1copyToByteBuffer__JJII(int64_t data_addr, int64_t destination_addr, int32_t offset, int32_t sizeInBytes) { + void* data = (void*)data_addr; + char* bufferAddress = (char*)destination_addr; + memcpy(bufferAddress + offset, data, sizeInBytes); + } + } + */ + /*[-FFM;-REPLACE] + public static void internal_native_copyToByteBuffer(long data_addr, long destination_addr, int offset, int sizeInBytes) { + try { + FFMHandles.internal_native_copyToByteBuffer.invokeExact(data_addr, destination_addr, offset, sizeInBytes); + } catch(Throwable e) { throw new RuntimeException(e); } + } + */ + /*[-FFM;-ADD] + private static final class FFMHandles { + private static final java.lang.foreign.SymbolLookup LOOKUP = java.lang.foreign.SymbolLookup.loaderLookup(); + private static final java.lang.foreign.Linker LINKER = java.lang.foreign.Linker.nativeLinker(); + static final java.lang.invoke.MethodHandle internal_native_copyToByteBuffer = LINKER.downcallHandle(LOOKUP.find("jparser_com_github_xpenatan_jparser_idl_helper_IDLUtils_internal_1native_1copyToByteBuffer__JJII").orElseThrow(), java.lang.foreign.FunctionDescriptor.ofVoid(java.lang.foreign.ValueLayout.JAVA_LONG, java.lang.foreign.ValueLayout.JAVA_LONG, java.lang.foreign.ValueLayout.JAVA_INT, java.lang.foreign.ValueLayout.JAVA_INT)); + } + */ public static native void internal_native_copyToByteBuffer(long data_addr, ByteBuffer destination, int offset, int sizeInBytes); } \ No newline at end of file diff --git a/jParser/jParser-build-tool/build.gradle.kts b/jParser/jParser-build-tool/build.gradle.kts index 359a9526..f068377b 100644 --- a/jParser/jParser-build-tool/build.gradle.kts +++ b/jParser/jParser-build-tool/build.gradle.kts @@ -8,7 +8,8 @@ dependencies { implementation(project(":jParser:jParser-core")) implementation(project(":jParser:jParser-idl")) implementation(project(":jParser:jParser-teavm")) - implementation(project(":jParser:jParser-cpp")) + implementation(project(":jParser:jParser-jni")) + implementation(project(":jParser:jParser-ffm")) implementation(project(":jParser:jParser-build")) } diff --git a/jParser/jParser-build-tool/src/main/java/com/github/xpenatan/jParser/builder/tool/BuilderTool.java b/jParser/jParser-build-tool/src/main/java/com/github/xpenatan/jParser/builder/tool/BuilderTool.java index f008b709..95b2aff6 100644 --- a/jParser/jParser-build-tool/src/main/java/com/github/xpenatan/jParser/builder/tool/BuilderTool.java +++ b/jParser/jParser-build-tool/src/main/java/com/github/xpenatan/jParser/builder/tool/BuilderTool.java @@ -7,9 +7,12 @@ import com.github.xpenatan.jParser.cpp.CppCodeParser; import com.github.xpenatan.jParser.cpp.CppGenerator; import com.github.xpenatan.jParser.cpp.NativeCPPGenerator; +import com.github.xpenatan.jParser.ffm.FFMCodeParser; +import com.github.xpenatan.jParser.ffm.FFMCppGenerator; import com.github.xpenatan.jParser.idl.IDLFile; import com.github.xpenatan.jParser.idl.IDLRenaming; import com.github.xpenatan.jParser.idl.IDLReader; +import com.github.xpenatan.jParser.idl.parser.IDLDefaultCodeParser; import com.github.xpenatan.jParser.teavm.TeaVMCodeParser; import java.util.ArrayList; @@ -28,6 +31,8 @@ public static void build(BuildToolOptions op, BuildToolListener listener, IDLRen } private static void generateAndBuild(BuildToolOptions op, BuildToolListener listener, IDLRenaming packageRenaming) throws Exception { + applyAutoGenerateFlags(op); + IDLReader idlReader = IDLReader.readIDL(op.getIDL()); IDLFile[] idlRefPath = op.getIDLRef(); @@ -40,23 +45,73 @@ private static void generateAndBuild(BuildToolOptions op, BuildToolListener list listener.onAddTarget(op, idlReader, targets); IDLReader.setupClasses(idlReader); - if(op.generateCPP) { + if(op.generateCore) { + IDLDefaultCodeParser coreParser = new IDLDefaultCodeParser(op.packageName, "CORE", idlReader, op.getSourceDir()); + coreParser.generateClass = true; + coreParser.generateNativeBindings = false; + coreParser.idlRenaming = packageRenaming; + JParser.generate(coreParser, op.getModuleBaseJavaDir(), op.getModuleCorePath() + "/src/main/java"); + } + + if(op.generateDesktopJNI || op.generateAndroid) { // NativeCPPGenerator.SKIP_GLUE_CODE = true; CppGenerator cppGenerator = new NativeCPPGenerator(op.getCPPDestinationPath()); - CppCodeParser cppParser = new CppCodeParser(cppGenerator, idlReader, op.packageName, op.getSourceDir()); - cppParser.generateClass = true; - cppParser.idlRenaming = packageRenaming; - JParser.generate(cppParser, op.getModuleBaseJavaDir(), op.getModuleCorePath() + "/src/main/java"); + String[] outputPaths = op.getJNIJavaOutputPaths(); + for(int i = 0; i < outputPaths.length; i++) { + String outputPath = outputPaths[i]; + CppCodeParser cppParser = new CppCodeParser(cppGenerator, idlReader, op.packageName, op.getSourceDir()); + cppParser.generateClass = true; + cppParser.idlRenaming = packageRenaming; + JParser.generate(cppParser, op.getModuleBaseJavaDir(), outputPath); + } } - if(op.generateTeaVM) { + if(op.generateTeaVMWeb) { // EmscriptenTarget.SKIP_GLUE_CODE = true; TeaVMCodeParser teavmParser = new TeaVMCodeParser(idlReader, op.webModuleName, op.packageName, op.getSourceDir()); teavmParser.idlRenaming = packageRenaming; JParser.generate(teavmParser, op.getModuleBaseJavaDir(), op.getModuleTeaVMPath() + "/src/main/java/"); } + if(op.generateDesktopFFM) { + FFMCppGenerator ffmGenerator = new FFMCppGenerator(op.getCPPDestinationPath()); + FFMCodeParser ffmParser = new FFMCodeParser(ffmGenerator, idlReader, op.packageName, op.getSourceDir()); + ffmParser.generateClass = true; + ffmParser.idlRenaming = packageRenaming; + JParser.generate(ffmParser, op.getModuleBaseJavaDir(), op.getModuleFFMPath() + "/src/main/java"); + } + BuildConfig buildConfig = new BuildConfig(op); JBuilder.build(buildConfig, targets); } + + private static void applyAutoGenerateFlags(BuildToolOptions op) { + if(op.containsArg("ffm") || + op.containsArg("ffm_windows64") || + op.containsArg("ffm_linux64") || + op.containsArg("ffm_mac64") || + op.containsArg("ffm_macArm")) { + op.generateDesktopFFM = true; + } + + if(op.containsArg("jni_windows64") || + op.containsArg("jni_linux64") || + op.containsArg("jni_mac64") || + op.containsArg("jni_macArm") || + op.containsArg("jni_ios")) { + op.generateDesktopJNI = true; + } + + if(op.containsArg("jni_android")) { + op.generateAndroid = true; + } + + if(op.containsArg("jni_ios")) { + op.generateIOS = true; + } + + if(op.containsArg("teavm")) { + op.generateTeaVMWeb = true; + } + } } \ No newline at end of file diff --git a/jParser/jParser-build/src/main/java/com/github/xpenatan/jParser/builder/DefaultBuildTarget.java b/jParser/jParser-build/src/main/java/com/github/xpenatan/jParser/builder/DefaultBuildTarget.java index 184831b5..919afc8e 100644 --- a/jParser/jParser-build/src/main/java/com/github/xpenatan/jParser/builder/DefaultBuildTarget.java +++ b/jParser/jParser-build/src/main/java/com/github/xpenatan/jParser/builder/DefaultBuildTarget.java @@ -273,6 +273,18 @@ else if(isMac()) { } } + /** + * Add FFM glue code for compilation. Unlike JNI, no JNI headers are needed. + * The FFMGlue.cpp/.h files are generated by FFMCppGenerator into the ffmglue/ subdirectory. + * + * @param libBuildCPPPath the module build C++ path (from BuildToolOptions.getModuleBuildCPPPath()) + */ + public void addFFMGlueCode(String libBuildCPPPath) { + String ffmGlueDir = libBuildCPPPath + "/src/ffmglue"; + headerDirs.add("-I" + ffmGlueDir); + cppInclude.add(ffmGlueDir + "/FFMGlue.cpp"); + } + public static ArrayList getCPPFiles(CustomFileDescriptor dir, ArrayList cppIncludes, ArrayList cppExcludes) { ArrayList files = new ArrayList<>(); getAllFiles(dir, files); diff --git a/jParser/jParser-build/src/main/java/com/github/xpenatan/jParser/builder/tool/BuildToolOptions.java b/jParser/jParser-build/src/main/java/com/github/xpenatan/jParser/builder/tool/BuildToolOptions.java index 12e81374..d0a0f321 100644 --- a/jParser/jParser-build/src/main/java/com/github/xpenatan/jParser/builder/tool/BuildToolOptions.java +++ b/jParser/jParser-build/src/main/java/com/github/xpenatan/jParser/builder/tool/BuildToolOptions.java @@ -12,8 +12,12 @@ public class BuildToolOptions { public final String libName; public final String webModuleName; public final String packageName; - public boolean generateTeaVM = true; - public boolean generateCPP = true; + public boolean generateTeaVMWeb = false; + public boolean generateDesktopJNI = false; + public boolean generateDesktopFFM = false; + public boolean generateAndroid = false; + public boolean generateIOS = false; // TODO + public boolean generateCore = true; /** Name of the idl file located in [Module Build Path] + src/main/cpp/myidl.idl. The default is libName but can be changed. */ public String idlName; @@ -23,7 +27,10 @@ public class BuildToolOptions { private String moduleBuildPath; private String moduleBuildCPPPath; private String moduleCorePath; + private String moduleDesktopJNIPath; + private String moduleAndroidPath; private String moduleTeavmPath; + private String moduleFFMPath; private ArrayList idlPath = new ArrayList<>(); private ArrayList idlPathRef = new ArrayList<>(); private ArrayList additionalSourceDirs = new ArrayList<>(); @@ -74,7 +81,10 @@ private void setup() { moduleBasePath = modulePath + "/" + modulePrefix + "-base"; moduleBuildPath = modulePath + "/" + modulePrefix + "-build"; moduleCorePath = modulePath + "/" + modulePrefix + "-core"; + moduleDesktopJNIPath = modulePath + "/" + modulePrefix + "-desktop-jni"; + moduleAndroidPath = modulePath + "/" + modulePrefix + "-android"; moduleTeavmPath = modulePath + "/" + modulePrefix + "-teavm"; + moduleFFMPath = modulePath + "/" + modulePrefix + "-desktop-ffm"; moduleBaseJavaDir = moduleBasePath + "/src/main/java"; cppPath = moduleBuildPath + "/src/main/cpp/"; @@ -133,10 +143,38 @@ public String getCPPPath() { return cppPath; } + public String getModuleDesktopJNIPath() { + return moduleDesktopJNIPath; + } + + public String getModuleAndroidPath() { + return moduleAndroidPath; + } + + public String[] getJNIJavaOutputPaths() { + ArrayList outputPaths = new ArrayList<>(); + if(generateAndroid) { + outputPaths.add(moduleAndroidPath + "/src/main/java"); + } + + if(generateDesktopJNI) { + outputPaths.add(moduleDesktopJNIPath + "/src/main/java"); + } + + String[] paths = new String[outputPaths.size()]; + outputPaths.toArray(paths); + return paths; + } + + public String getModuleTeaVMPath() { return moduleTeavmPath; } + public String getModuleFFMPath() { + return moduleFFMPath; + } + public IDLFile[] getIDL() { IDLFile [] path = new IDLFile[idlPath.size()]; idlPath.toArray(path); diff --git a/jParser/jParser-core/src/main/java/com/github/xpenatan/jParser/core/JParser.java b/jParser/jParser-core/src/main/java/com/github/xpenatan/jParser/core/JParser.java index 5f2a5e16..eb579c42 100644 --- a/jParser/jParser-core/src/main/java/com/github/xpenatan/jParser/core/JParser.java +++ b/jParser/jParser-core/src/main/java/com/github/xpenatan/jParser/core/JParser.java @@ -47,7 +47,7 @@ public class JParser { public ArrayList unitArray = new ArrayList<>(); - public static boolean CREATE_IDL_HELPER = true; + public static boolean CREATE_IDL_HELPER = false; private JParser(String sourceDir, String genDir) { this.sourceDir = sourceDir; diff --git a/jParser/jParser-ffm/build.gradle.kts b/jParser/jParser-ffm/build.gradle.kts new file mode 100644 index 00000000..db2e0863 --- /dev/null +++ b/jParser/jParser-ffm/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("java-library") +} + +val moduleName = "jParser-ffm" + +dependencies { + implementation(project(":jParser:jParser-idl")) + implementation(project(":jParser:jParser-core")) + implementation(project(":idl:idl-core")) + + testImplementation(project(":loader:loader-core")) + testImplementation("junit:junit:${LibExt.jUnitVersion}") +} + +java { + sourceCompatibility = JavaVersion.toVersion(LibExt.java11Target) + targetCompatibility = JavaVersion.toVersion(LibExt.java11Target) +} + +java { + withJavadocJar() + withSourcesJar() +} + +publishing { + publications { + create("maven") { + artifactId = moduleName + group = LibExt.groupId + version = LibExt.libVersion + from(components["java"]) + } + } +} + + diff --git a/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMCodeParser.java b/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMCodeParser.java new file mode 100644 index 00000000..71fa8922 --- /dev/null +++ b/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMCodeParser.java @@ -0,0 +1,1244 @@ +package com.github.xpenatan.jParser.ffm; + +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Modifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.ast.body.EnumDeclaration; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.InitializerDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.type.Type; +import com.github.javaparser.utils.Pair; +import com.github.xpenatan.jParser.core.JParser; +import com.github.xpenatan.jParser.core.JParserHelper; +import com.github.xpenatan.jParser.core.JParserItem; +import com.github.xpenatan.jParser.idl.IDLAttribute; +import com.github.xpenatan.jParser.idl.IDLClass; +import com.github.xpenatan.jParser.idl.IDLConstructor; +import com.github.xpenatan.jParser.idl.IDLEnumClass; +import com.github.xpenatan.jParser.idl.IDLEnumItem; +import com.github.xpenatan.jParser.idl.IDLFile; +import com.github.xpenatan.jParser.idl.IDLHelper; +import com.github.xpenatan.jParser.idl.IDLMethod; +import com.github.xpenatan.jParser.idl.IDLParameter; +import com.github.xpenatan.jParser.idl.IDLReader; +import com.github.xpenatan.jParser.idl.parser.IDLAttributeOperation; +import com.github.xpenatan.jParser.idl.parser.IDLDefaultCodeParser; +import com.github.xpenatan.jParser.idl.parser.IDLMethodOperation; +import com.github.xpenatan.jParser.idl.parser.IDLMethodParser; +import com.github.xpenatan.jParser.idl.parser.data.IDLParameterData; +import java.util.ArrayList; +import java.util.List; + +/** + * FFM code parser that generates Java classes using java.lang.foreign MethodHandle downcalls + * instead of JNI native methods. Parallel to CppCodeParser. + * + *

For each native method, generates: + *

    + *
  • A private static (non-native) bridge method that invokes a MethodHandle
  • + *
  • C++ glue code using extern "C" with standard C types (via FFMCppGenerator)
  • + *
+ */ +public class FFMCodeParser extends IDLDefaultCodeParser { + + private static final String HEADER_CMD = "FFM"; + + // Same template tags as CppCodeParser (the C++ code is largely the same) + protected static final String TEMPLATE_TAG_TYPE = "[TYPE]"; + protected static final String TEMPLATE_TAG_METHOD = "[METHOD]"; + protected static final String TEMPLATE_TAG_OPERATOR = "[OPERATOR]"; + protected static final String TEMPLATE_TAG_ATTRIBUTE = "[ATTRIBUTE]"; + protected static final String TEMPLATE_TAG_ENUM = "[ENUM]"; + protected static final String TEMPLATE_TAG_ATTRIBUTE_TYPE = "[ATTRIBUTE_TYPE]"; + protected static final String TEMPLATE_TAG_RETURN_TYPE = "[RETURN_TYPE]"; + protected static final String TEMPLATE_TAG_CONST = "[CONST]"; + protected static final String TEMPLATE_TAG_COPY_TYPE = "[COPY_TYPE]"; + protected static final String TEMPLATE_TAG_COPY_PARAM = "[COPY_PARAM]"; + protected static final String TEMPLATE_TAG_CONSTRUCTOR = "[CONSTRUCTOR]"; + protected static final String TEMPLATE_TAG_CAST = "[CAST]"; + + // C++ templates — identical to CppCodeParser but with int64_t casts instead of jlong + protected static final String GET_CONSTRUCTOR_OBJ_POINTER_TEMPLATE = + "\nreturn (int64_t)new [CONSTRUCTOR];\n"; + + protected static final String METHOD_DELETE_OBJ_POINTER_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "delete nativeObject;\n"; + + // --- Attribute templates (int64_t instead of jlong, int32_t instead of jint) --- + + protected static final String ATTRIBUTE_SET_PRIMITIVE_STATIC_TEMPLATE = + "\n[TYPE]::[ATTRIBUTE] = [ATTRIBUTE];\n"; + + protected static final String ATTRIBUTE_ARRAY_SET_PRIMITIVE_STATIC_TEMPLATE = + "\n[TYPE]::[ATTRIBUTE][index] = [ATTRIBUTE];\n"; + + protected static final String ATTRIBUTE_SET_PRIMITIVE_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "nativeObject->[ATTRIBUTE] = [CAST][ATTRIBUTE];\n"; + + protected static final String ATTRIBUTE_ARRAY_SET_PRIMITIVE_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "nativeObject->[ATTRIBUTE][index] = [CAST][ATTRIBUTE];\n"; + + protected static final String ATTRIBUTE_SET_OBJECT_POINTER_STATIC_TEMPLATE = + "\n[TYPE]::[ATTRIBUTE] = ([ATTRIBUTE_TYPE]*)[ATTRIBUTE]_addr;\n"; + + protected static final String ATTRIBUTE_ARRAY_SET_OBJECT_POINTER_STATIC_TEMPLATE = + "\n[TYPE]::[ATTRIBUTE][index] = ([ATTRIBUTE_TYPE]*)[ATTRIBUTE]_addr;\n"; + + protected static final String ATTRIBUTE_SET_OBJECT_POINTER_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "nativeObject->[ATTRIBUTE] = ([ATTRIBUTE_TYPE]*)[ATTRIBUTE]_addr;\n"; + + protected static final String ATTRIBUTE_ARRAY_SET_OBJECT_POINTER_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "nativeObject->[ATTRIBUTE][index] = ([ATTRIBUTE_TYPE]*)[ATTRIBUTE]_addr;\n"; + + protected static final String ATTRIBUTE_SET_OBJECT_VALUE_STATIC_TEMPLATE = + "\n[TYPE]::[ATTRIBUTE] = *(([ATTRIBUTE_TYPE]*)[ATTRIBUTE]_addr);\n"; + + protected static final String ATTRIBUTE_ARRAY_SET_OBJECT_VALUE_STATIC_TEMPLATE = + "\n[TYPE]::[ATTRIBUTE][index] = *(([ATTRIBUTE_TYPE]*)[ATTRIBUTE]_addr);\n"; + + protected static final String ATTRIBUTE_SET_OBJECT_VALUE_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "nativeObject->[ATTRIBUTE] = *(([ATTRIBUTE_TYPE]*)[ATTRIBUTE]_addr);\n"; + + protected static final String ATTRIBUTE_ARRAY_SET_OBJECT_VALUE_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "nativeObject->[ATTRIBUTE][index] = *(([ATTRIBUTE_TYPE]*)[ATTRIBUTE]_addr);\n"; + + protected static final String ATTRIBUTE_GET_OBJECT_VALUE_STATIC_TEMPLATE = + "\nreturn (int64_t)&[TYPE]::[ATTRIBUTE];\n"; + + protected static final String ATTRIBUTE_ARRAY_GET_OBJECT_VALUE_STATIC_TEMPLATE = + "\nreturn (int64_t)&[TYPE]::[ATTRIBUTE][index];\n"; + + protected static final String ATTRIBUTE_GET_OBJECT_VALUE_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "return (int64_t)&nativeObject->[ATTRIBUTE];\n"; + + protected static final String ATTRIBUTE_ARRAY_GET_OBJECT_VALUE_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "return (int64_t)&nativeObject->[ATTRIBUTE][index];\n"; + + protected static final String ATTRIBUTE_GET_OBJECT_POINTER_STATIC_TEMPLATE = + "\nreturn (int64_t)[TYPE]::[ATTRIBUTE];\n"; + + protected static final String ATTRIBUTE_ARRAY_GET_OBJECT_POINTER_STATIC_TEMPLATE = + "\nreturn (int64_t)([TYPE]::[ATTRIBUTE][index]);\n"; + + protected static final String ATTRIBUTE_GET_OBJECT_POINTER_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "[CONST][ATTRIBUTE_TYPE]* attr = nativeObject->[ATTRIBUTE];\n" + + "return (int64_t)attr;\n"; + + protected static final String ATTRIBUTE_ARRAY_GET_OBJECT_POINTER_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "[CONST][ATTRIBUTE_TYPE]* attr = (nativeObject->[ATTRIBUTE][index]);\n" + + "return (int64_t)attr;\n"; + + protected static final String ATTRIBUTE_GET_PRIMITIVE_STATIC_TEMPLATE = + "\nreturn [TYPE]::[ATTRIBUTE];\n"; + + protected static final String ATTRIBUTE_ARRAY_GET_PRIMITIVE_STATIC_TEMPLATE = + "\nreturn [TYPE]::[ATTRIBUTE][index];\n"; + + protected static final String ATTRIBUTE_GET_PRIMITIVE_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "return [CAST]nativeObject->[ATTRIBUTE];\n"; + + protected static final String ATTRIBUTE_ARRAY_GET_PRIMITIVE_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "return [CAST]nativeObject->[ATTRIBUTE][index];\n"; + + // --- Method templates --- + + protected static final String METHOD_GET_OBJ_VALUE_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "static [COPY_TYPE] [COPY_PARAM];\n" + + "[COPY_PARAM] = nativeObject->[METHOD];\n" + + "return (int64_t)&[COPY_PARAM];"; + + protected static final String METHOD_GET_OBJ_VALUE_ARITHMETIC_OPERATOR_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "static [COPY_TYPE] [COPY_PARAM];\n" + + "[COPY_PARAM] = [OPERATOR];\n" + + "return (int64_t)&[COPY_PARAM];"; + + protected static final String METHOD_GET_OBJ_VALUE_STATIC_TEMPLATE = + "\nstatic [COPY_TYPE] [COPY_PARAM];\n" + + "[COPY_PARAM] = [TYPE]::[METHOD];\n" + + "return (int64_t)&[COPY_PARAM];"; + + protected static final String METHOD_CALL_VOID_STATIC_TEMPLATE = + "\n[TYPE]::[METHOD];\n"; + + protected static final String METHOD_CALL_VOID_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "nativeObject->[METHOD];\n"; + + protected static final String METHOD_GET_OBJ_POINTER_STATIC_TEMPLATE = + "\nreturn (int64_t)[TYPE]::[METHOD];\n"; + + protected static final String METHOD_GET_OBJ_POINTER_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "[CONST][RETURN_TYPE]* obj = nativeObject->[METHOD];\n" + + "return (int64_t)obj;\n"; + + protected static final String METHOD_GET_OBJ_POINTER_OPERATOR_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "[CONST][RETURN_TYPE]* obj = [OPERATOR];\n" + + "return (int64_t)obj;\n"; + + protected static final String METHOD_GET_REF_OBJ_POINTER_STATIC_TEMPLATE = + "\nreturn (int64_t)&[TYPE]::[METHOD];\n"; + + protected static final String METHOD_GET_REF_OBJ_POINTER_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "return (int64_t)&nativeObject->[METHOD];\n"; + + protected static final String METHOD_GET_REF_OBJ_POINTER_OPERATOR_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "return (int64_t)&[OPERATOR];\n"; + + protected static final String METHOD_GET_PRIMITIVE_STATIC_TEMPLATE = + "\nreturn [CAST][TYPE]::[METHOD];\n"; + + protected static final String METHOD_GET_PRIMITIVE_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "return [CAST]nativeObject->[METHOD];\n"; + + protected static final String METHOD_GET_PRIMITIVE_OPERATOR_TEMPLATE = + "\n[TYPE]* nativeObject = ([TYPE]*)this_addr;\n" + + "return ([OPERATOR]);"; + + protected static final String ENUM_GET_INT_TEMPLATE = + "\nreturn (int64_t)[ENUM];\n"; + + private final FFMNativeCodeGenerator cppGenerator; + private final FFMMethodHandleRegistry registry = new FFMMethodHandleRegistry(); + + public FFMCodeParser(FFMNativeCodeGenerator cppGenerator, String cppDir) { + this(cppGenerator, null, "", cppDir); + } + + public FFMCodeParser(FFMNativeCodeGenerator cppGenerator, IDLReader idlReader, String basePackage, String cppDir) { + super(basePackage, HEADER_CMD, idlReader, cppDir); + this.cppGenerator = cppGenerator; + } + + // ==================== IDL Generation Hooks ==================== + + @Override + public void onIDLConstructorGenerated(JParser jParser, IDLConstructor idlConstructor, + ClassOrInterfaceDeclaration classDeclaration, + ConstructorDeclaration constructorDeclaration, + MethodDeclaration nativeMethodDeclaration) { + IDLClass idlClass = idlConstructor.idlClass; + String classTypeName = idlClass.getCPPName(); + + NodeList parameters = constructorDeclaration.getParameters(); + ArrayList idParameters = idlConstructor.parameters; + String params = getParams(parameters, idParameters); + + String constructor = classTypeName + "(" + params + ")"; + String content = GET_CONSTRUCTOR_OBJ_POINTER_TEMPLATE.replace(TEMPLATE_TAG_CONSTRUCTOR, constructor); + + String header = "[-" + HEADER_CMD + ";" + CMD_NATIVE + "]"; + String blockComment = header + content; + nativeMethodDeclaration.setBlockComment(blockComment); + } + + @Override + public void onIDLDeConstructorGenerated(JParser jParser, IDLClass idlClass, + ClassOrInterfaceDeclaration classDeclaration, + MethodDeclaration nativeMethodDeclaration) { + String classTypeName; + if(idlClass.callbackImpl == null) { + classTypeName = idlClass.getCPPName(); + } + else { + classTypeName = idlClass.callbackImpl.name; + } + + String content = METHOD_DELETE_OBJ_POINTER_TEMPLATE.replace(TEMPLATE_TAG_TYPE, classTypeName); + + String header = "[-" + HEADER_CMD + ";" + CMD_NATIVE + "]"; + String blockComment = header + content; + nativeMethodDeclaration.setBlockComment(blockComment); + } + + @Override + public void onIDLMethodGenerated(JParser jParser, IDLMethod idlMethod, + ClassOrInterfaceDeclaration classDeclaration, + MethodDeclaration methodDeclaration, + MethodDeclaration nativeMethodDeclaration) { + String param = getParams(idlMethod, methodDeclaration); + setupMethodGenerated(idlMethod, param, classDeclaration, methodDeclaration, nativeMethodDeclaration); + } + + @Override + public void onIDLAttributeGenerated(JParser jParser, IDLAttribute idlAttribute, boolean isSet, + ClassOrInterfaceDeclaration classDeclaration, + MethodDeclaration methodDeclaration, + MethodDeclaration nativeMethod) { + String attributeName = idlAttribute.name; + String classTypeName = classDeclaration.getNameAsString(); + IDLClass idlClass = idlAttribute.idlFile.getClass(classTypeName); + if(idlClass != null) { + classTypeName = idlClass.getCPPName(); + } + + String getPrimitiveCast = ""; + String attributeType = idlAttribute.getCPPType(); + String constTag = ""; + if(idlAttribute.isConst) { + constTag = "const "; + } + + IDLClass retTypeClass = idlAttribute.idlFile.getClass(attributeType); + if(retTypeClass != null) { + attributeType = retTypeClass.getCPPName(); + } + + if(idlAttribute.isAny) { + getPrimitiveCast = "(int64_t)"; + } + + String attributeReturnCast = ""; + + IDLEnumClass idlEnum = idlAttribute.idlFile.getEnum(attributeType); + if(idlEnum != null) { + if(idlEnum.typePrefix.equals(attributeType)) { + attributeReturnCast = "(" + attributeType + ")"; + } + else { + attributeReturnCast = "(" + idlEnum.typePrefix + "::" + attributeType + ")"; + } + getPrimitiveCast = "(int32_t)"; + } + + String content = null; + IDLAttributeOperation.Op op = IDLAttributeOperation.getEnum(isSet, idlAttribute, methodDeclaration, nativeMethod); + switch(op) { + case SET_OBJECT_VALUE: + content = ATTRIBUTE_SET_OBJECT_VALUE_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case SET_ARRAY_OBJECT_VALUE: + content = ATTRIBUTE_ARRAY_SET_OBJECT_VALUE_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case SET_OBJECT_VALUE_STATIC: + content = ATTRIBUTE_SET_OBJECT_VALUE_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case SET_ARRAY_OBJECT_VALUE_STATIC: + content = ATTRIBUTE_ARRAY_SET_OBJECT_VALUE_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case GET_OBJECT_VALUE: + content = ATTRIBUTE_GET_OBJECT_VALUE_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case GET_ARRAY_OBJECT_VALUE: + content = ATTRIBUTE_ARRAY_GET_OBJECT_VALUE_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case GET_OBJECT_VALUE_STATIC: + content = ATTRIBUTE_GET_OBJECT_VALUE_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case GET_ARRAY_OBJECT_VALUE_STATIC: + content = ATTRIBUTE_ARRAY_GET_OBJECT_VALUE_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case SET_OBJECT_POINTER: + content = ATTRIBUTE_SET_OBJECT_POINTER_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case SET_ARRAY_OBJECT_POINTER: + content = ATTRIBUTE_ARRAY_SET_OBJECT_POINTER_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case SET_OBJECT_POINTER_STATIC: + content = ATTRIBUTE_SET_OBJECT_POINTER_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case SET_ARRAY_OBJECT_POINTER_STATIC: + content = ATTRIBUTE_ARRAY_SET_OBJECT_POINTER_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case GET_OBJECT_POINTER: + content = ATTRIBUTE_GET_OBJECT_POINTER_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_CONST, constTag); + break; + case GET_ARRAY_OBJECT_POINTER: + content = ATTRIBUTE_ARRAY_GET_OBJECT_POINTER_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName).replace(TEMPLATE_TAG_ATTRIBUTE_TYPE, attributeType).replace(TEMPLATE_TAG_CONST, constTag); + break; + case GET_OBJECT_POINTER_STATIC: + content = ATTRIBUTE_GET_OBJECT_POINTER_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case GET_ARRAY_OBJECT_POINTER_STATIC: + content = ATTRIBUTE_ARRAY_GET_OBJECT_POINTER_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case SET_PRIMITIVE: + content = ATTRIBUTE_SET_PRIMITIVE_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName).replace(TEMPLATE_TAG_CAST, attributeReturnCast); + break; + case SET_PRIMITIVE_STATIC: + content = ATTRIBUTE_SET_PRIMITIVE_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case SET_ARRAY_PRIMITIVE_STATIC: + content = ATTRIBUTE_ARRAY_SET_PRIMITIVE_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case SET_ARRAY_PRIMITIVE: + content = ATTRIBUTE_ARRAY_SET_PRIMITIVE_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName).replace(TEMPLATE_TAG_CAST, attributeReturnCast); + break; + case GET_PRIMITIVE: + content = ATTRIBUTE_GET_PRIMITIVE_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName).replace(TEMPLATE_TAG_CAST, getPrimitiveCast); + break; + case GET_ARRAY_PRIMITIVE: + content = ATTRIBUTE_ARRAY_GET_PRIMITIVE_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName).replace(TEMPLATE_TAG_CAST, getPrimitiveCast); + break; + case GET_PRIMITIVE_STATIC: + content = ATTRIBUTE_GET_PRIMITIVE_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case GET_ARRAY_PRIMITIVE_STATIC: + content = ATTRIBUTE_ARRAY_GET_PRIMITIVE_STATIC_TEMPLATE.replace(TEMPLATE_TAG_ATTRIBUTE, attributeName).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + } + + if(content != null) { + String header = "[-" + HEADER_CMD + ";" + CMD_NATIVE + "]"; + String blockComment = header + content; + nativeMethod.setBlockComment(blockComment); + } + } + + @Override + public void onIDLEnumMethodGenerated(JParser jParser, IDLEnumClass idlEnum, + EnumDeclaration enumDeclaration, + IDLEnumItem enumItem, + MethodDeclaration nativeMethodDeclaration) { + String enumStr = enumItem.name; + String content = ENUM_GET_INT_TEMPLATE.replace(TEMPLATE_TAG_ENUM, enumStr); + String header = "[-" + HEADER_CMD + ";" + CMD_NATIVE + "]"; + String blockComment = header + content; + nativeMethodDeclaration.setBlockComment(blockComment); + } + + @Override + public void onIDLCallbackGenerated(JParser jParser, IDLClass idlClass, + ClassOrInterfaceDeclaration classDeclaration, + MethodDeclaration callbackDeclaration, + ArrayList>> methods) { + IDLClass idlCallbackClass = idlClass.callbackImpl; + + // 1. Build parameter list for native setupCallback: this_addr + one long per callback method (function pointer) + ArrayList parameterArray = new ArrayList<>(); + for(Pair> pair : methods) { + IDLMethod idlMethod = pair.a; + String fpParamName = idlMethod.getCPPName() + "_fp"; + Parameter fpParam = new Parameter(com.github.javaparser.ast.type.PrimitiveType.longType(), fpParamName); + IDLParameterData data = new IDLParameterData(); + data.parameter = fpParam; + parameterArray.add(data); + } + + Type methodReturnType = callbackDeclaration.getType(); + MethodDeclaration nativeMethodDeclaration = IDLMethodParser.generateNativeMethod( + idlReader, callbackDeclaration.getNameAsString(), parameterArray, methodReturnType, false); + + if(!JParserHelper.containsMethod(classDeclaration, nativeMethodDeclaration)) { + // Keep the method static (FFM uses explicit this_addr, no implicit JNI params) + classDeclaration.getMembers().add(nativeMethodDeclaration); + + // 2. Build setupCallback Java body with upcall stub creation + StringBuilder body = new StringBuilder(); + body.append("{\n"); + body.append(" try {\n"); + + for(Pair> pair : methods) { + IDLMethod idlMethod = pair.a; + MethodDeclaration internalMethod = pair.b.a; + String methodName = idlMethod.getCPPName(); + String internalMethodName = internalMethod.getNameAsString(); + + // FFM upcall stubs require MethodHandle types to exactly match the FunctionDescriptor. + // For String (const char*) parameters, the native side passes a pointer (ADDRESS layout), + // so the internal method must accept MemorySegment instead of String and convert it. + fixupCallbackStringParams(internalMethod); + + String methodTypeStr = buildMethodTypeStr(internalMethod); + String funcDescriptor = buildCallbackFunctionDescriptor(internalMethod); + + body.append(" java.lang.invoke.MethodHandle mh_").append(methodName) + .append(" = java.lang.invoke.MethodHandles.lookup().findVirtual(") + .append(classDeclaration.getNameAsString()).append(".class, \"") + .append(internalMethodName).append("\", ").append(methodTypeStr).append(").bindTo(this);\n"); + body.append(" java.lang.foreign.MemorySegment stub_").append(methodName) + .append(" = java.lang.foreign.Linker.nativeLinker().upcallStub(mh_").append(methodName) + .append(", ").append(funcDescriptor).append(", java.lang.foreign.Arena.ofAuto());\n"); + } + + // Call native setupCallback with native_address + stub addresses + body.append(" ").append(nativeMethodDeclaration.getNameAsString()).append("(native_address"); + for(Pair> pair : methods) { + IDLMethod idlMethod = pair.a; + body.append(", stub_").append(idlMethod.getCPPName()).append(".address()"); + } + body.append(");\n"); + + body.append(" } catch(Throwable e) {\n"); + body.append(" throw new RuntimeException(e);\n"); + body.append(" }\n"); + body.append("}"); + + BlockStmt blockStmt = StaticJavaParser.parseBlock(body.toString()); + callbackDeclaration.setBody(blockStmt); + + // 3. Set C++ code for the native setupCallback method + String cppSetupBody = generateFFMSetupCallbackCPPBody(idlCallbackClass, methods); + String header = "[-" + HEADER_CMD + ";" + CMD_NATIVE + "]"; + nativeMethodDeclaration.setBlockComment(header + cppSetupBody); + + // 4. Generate C++ callback class and emit it via the generator + generateFFMCPPClass(idlClass, classDeclaration, callbackDeclaration, methods); + } + } + + // ==================== Code Block Parsing ==================== + + @Override + public boolean parseCodeBlock(Node node, String headerCommands, String content) { + if(!super.parseCodeBlock(node, headerCommands, content)) { + if(headerCommands.contains(CMD_NATIVE)) { + cppGenerator.addNativeCode(node, content); + return true; + } + } + return false; + } + + @Override + protected void setJavaBodyNativeCMD(String content, MethodDeclaration methodDeclaration) { + // Collect C++ code for the FFM glue file + cppGenerator.addNativeCode(methodDeclaration, content); + + // Register the MethodHandle entry for this native method + String handleName = registerNativeMethod(methodDeclaration); + + // Transform the native method into an FFM bridge method + convertToFFMBridgeMethod(methodDeclaration, handleName); + } + + // ==================== Lifecycle Hooks ==================== + + @Override + public void onParseClassStart(JParser jParser, CompilationUnit unit, TypeDeclaration classOrEnum) { + String nameAsString = classOrEnum.getNameAsString(); + String include = classCppPath.get(nameAsString); + super.onParseClassStart(jParser, unit, classOrEnum); + } + + @Override + public void onParseFileEnd(JParser jParser, JParserItem parserItem) { + cppGenerator.addParseFile(jParser, parserItem); + } + + @Override + public void onParseEnd(JParser jParser) { + cppGenerator.generate(jParser); + } + + @Override + public void onParserComplete(JParser jParser, ArrayList parserItems) { + super.onParserComplete(jParser, parserItems); + + // For each class that has registered MethodHandle entries, inject the FFMHandles inner class + for(JParserItem parserItem : parserItems) { + if(parserItem.notAllowed) continue; + + ClassOrInterfaceDeclaration classDeclaration = parserItem.getClassDeclaration(); + if(classDeclaration != null) { + String className = classDeclaration.getNameAsString(); + if(registry.hasEntries(className)) { + injectFFMHandlesClass(parserItem.unit, classDeclaration, className); + } + continue; + } + + // Also handle enum declarations (they can have native methods too) + EnumDeclaration enumDeclaration = parserItem.getEnumDeclaration(); + if(enumDeclaration != null) { + String className = enumDeclaration.getNameAsString(); + if(registry.hasEntries(className)) { + injectFFMHandlesClassForEnum(parserItem.unit, enumDeclaration, className); + } + } + } + } + + // ==================== FFM Bridge Method Generation ==================== + + /** + * Register a native method in the MethodHandle registry. + * Returns the unique handle name (method name + overload suffix) for use in bridge method body. + */ + private String registerNativeMethod(MethodDeclaration methodDeclaration) { + TypeDeclaration classOrEnum = (TypeDeclaration) methodDeclaration.getParentNode().get(); + CompilationUnit compilationUnit = classOrEnum.findCompilationUnit().get(); + String packageName = compilationUnit.getPackageDeclaration().get().getNameAsString(); + String className = classOrEnum.getNameAsString(); + String methodName = methodDeclaration.getNameAsString(); + + // Build parameter info + List paramInfos = new ArrayList<>(); + ArrayList ffmArgs = new ArrayList<>(); + if(methodDeclaration.getParameters() != null) { + for(Parameter parameter : methodDeclaration.getParameters()) { + FFMMethodHandleRegistry.ParamInfo paramInfo = FFMMethodHandleRegistry.ParamInfo.fromParameter(parameter); + paramInfos.add(paramInfo); + + String[] typeTokens = parameter.getType().toString().split("\\."); + String type = typeTokens[typeTokens.length - 1]; + ffmArgs.add(new FFMCppGenerator.FFMArgument( + parameter.getNameAsString(), type, + FFMTypeMapper.getCType(type), + FFMTypeMapper.getOverloadSuffix(type))); + } + } + + // Build overload suffix for unique handle name + StringBuilder overloadSuffix = new StringBuilder(); + for(FFMCppGenerator.FFMArgument arg : ffmArgs) { + overloadSuffix.append(arg.overloadSuffix); + } + String handleName = methodName + "__" + overloadSuffix; + + String returnType = methodDeclaration.getType().toString(); + String symbolName = FFMCppGenerator.buildSymbolName(packageName, className, methodName, ffmArgs); + + registry.register(className, symbolName, methodName, handleName, returnType, paramInfos); + return handleName; + } + + /** + * Transform a JNI-style native method declaration into an FFM bridge method. + * Removes the 'native' modifier and adds a body that invokes the MethodHandle. + * + * @param handleName the unique field name in FFMHandles (includes overload suffix) + */ + private void convertToFFMBridgeMethod(MethodDeclaration methodDeclaration, String handleName) { + // Remove native modifier + methodDeclaration.removeModifier(Modifier.Keyword.NATIVE); + + String methodName = methodDeclaration.getNameAsString(); + Type returnType = methodDeclaration.getType(); + String returnTypeStr = returnType.asString(); + boolean isVoid = returnType.isVoidType(); + + // Build the invokeExact call arguments + StringBuilder invokeArgs = new StringBuilder(); + NodeList parameters = methodDeclaration.getParameters(); + for(int i = 0; i < parameters.size(); i++) { + Parameter parameter = parameters.get(i); + if(i > 0) invokeArgs.append(", "); + + String paramType = parameter.getType().asString(); + // For String parameters, we need to convert to MemorySegment + if(paramType.equals("String")) { + invokeArgs.append("(java.lang.foreign.MemorySegment)(").append(parameter.getNameAsString()) + .append(" != null ? java.lang.foreign.Arena.global().allocateFrom(") + .append(parameter.getNameAsString()).append(") : java.lang.foreign.MemorySegment.NULL)"); + } + else { + invokeArgs.append(parameter.getNameAsString()); + } + } + + // Build method body + StringBuilder bodyCode = new StringBuilder(); + bodyCode.append("{\n"); + bodyCode.append(" try {\n"); + + if(isVoid) { + bodyCode.append(" FFMHandles.").append(handleName) + .append(".invokeExact(").append(invokeArgs).append(");\n"); + } + else if(FFMTypeMapper.isString(returnTypeStr)) { + // String returns: native function returns const char* (ADDRESS). + // invokeExact returns MemorySegment — convert to Java String. + bodyCode.append(" java.lang.foreign.MemorySegment _retSeg = (java.lang.foreign.MemorySegment) FFMHandles.").append(handleName) + .append(".invokeExact(").append(invokeArgs).append(");\n"); + bodyCode.append(" return _retSeg.reinterpret(Long.MAX_VALUE).getString(0);\n"); + } + else { + String castType = FFMTypeMapper.getFFMCast(returnTypeStr); + bodyCode.append(" return (").append(castType).append(") FFMHandles.").append(handleName) + .append(".invokeExact(").append(invokeArgs).append(");\n"); + } + + bodyCode.append(" } catch(Throwable e) {\n"); + bodyCode.append(" throw new RuntimeException(e);\n"); + bodyCode.append(" }\n"); + + if(!isVoid) { + // Unreachable but makes the compiler happy + } + + bodyCode.append("}"); + + BlockStmt body = StaticJavaParser.parseBlock(bodyCode.toString()); + methodDeclaration.setBody(body); + } + + /** + * Inject the FFMHandles inner class into a Java class with all MethodHandle field declarations. + */ + private void injectFFMHandlesClass(CompilationUnit unit, ClassOrInterfaceDeclaration classDeclaration, String className) { + String innerClassSource = buildFFMHandlesSource(className); + if(innerClassSource == null) return; + + ClassOrInterfaceDeclaration innerClass = StaticJavaParser.parseBodyDeclaration(innerClassSource) + .asClassOrInterfaceDeclaration(); + classDeclaration.addMember(innerClass); + + addFFMImports(unit); + } + + /** + * Inject the FFMHandles inner class into an enum declaration. + */ + private void injectFFMHandlesClassForEnum(CompilationUnit unit, EnumDeclaration enumDeclaration, String className) { + String innerClassSource = buildFFMHandlesSource(className); + if(innerClassSource == null) return; + + ClassOrInterfaceDeclaration innerClass = StaticJavaParser.parseBodyDeclaration(innerClassSource) + .asClassOrInterfaceDeclaration(); + enumDeclaration.addMember(innerClass); + + addFFMImports(unit); + } + + /** + * Build the FFMHandles inner class source code for a given class name. + */ + private String buildFFMHandlesSource(String className) { + List entries = registry.getEntries(className); + if(entries.isEmpty()) return null; + + StringBuilder sb = new StringBuilder(); + sb.append("private static final class FFMHandles {\n"); + sb.append(" private static final java.lang.foreign.SymbolLookup LOOKUP;\n"); + sb.append(" private static final java.lang.foreign.Linker LINKER = java.lang.foreign.Linker.nativeLinker();\n"); + sb.append(" static {\n"); + sb.append(" LOOKUP = java.lang.foreign.SymbolLookup.loaderLookup();\n"); + sb.append(" }\n\n"); + + for(FFMMethodHandleRegistry.FFMEntry entry : entries) { + String descriptor = FFMMethodHandleRegistry.buildFunctionDescriptor(entry); + sb.append(" static final java.lang.invoke.MethodHandle ").append(entry.handleName) + .append(" = LINKER.downcallHandle(\n"); + sb.append(" LOOKUP.find(\"").append(entry.symbolName).append("\").orElseThrow(),\n"); + sb.append(" ").append(descriptor).append(");\n\n"); + } + + sb.append("}"); + return sb.toString(); + } + + private void addFFMImports(CompilationUnit unit) { + unit.addImport("java.lang.foreign.FunctionDescriptor"); + unit.addImport("java.lang.foreign.ValueLayout"); + unit.addImport("java.lang.foreign.Linker"); + unit.addImport("java.lang.foreign.SymbolLookup"); + unit.addImport("java.lang.foreign.Arena"); + unit.addImport("java.lang.foreign.MemorySegment"); + unit.addImport("java.lang.invoke.MethodHandle"); + } + + // ==================== FFM Callback C++ Generation ==================== + + /** + * Generate the full C++ callback class with function pointers and emit it. + * Attaches the class definition as a block comment on the constructor (same pattern as CppCodeParser). + */ + private void generateFFMCPPClass(IDLClass idlClass, ClassOrInterfaceDeclaration classDeclaration, + MethodDeclaration callbackDeclaration, + ArrayList>> methods) { + IDLClass callback = idlClass.callbackImpl; + StringBuilder cppClass = new StringBuilder(); + + // Generate function pointer typedefs + for(Pair> pair : methods) { + IDLMethod idlMethod = pair.a; + MethodDeclaration internalMethod = pair.b.a; + cppClass.append(buildFPTypedef(callback.name, idlMethod, internalMethod)).append("\n"); + } + cppClass.append("\n"); + + // Class definition + cppClass.append("class ").append(callback.getCPPName()).append(" : public ").append(idlClass.getCPPName()).append(" {\n"); + cppClass.append("private:\n"); + + // Function pointer fields + for(Pair> pair : methods) { + IDLMethod idlMethod = pair.a; + MethodDeclaration internalMethod = pair.b.a; + String fpTypeName = buildFPTypeName(callback.name, idlMethod, internalMethod); + cppClass.append("\t").append(fpTypeName).append(" ").append(idlMethod.getCPPName()).append("_ptr;\n"); + } + + cppClass.append("public:\n"); + + // setupCallback method — receives function pointers + cppClass.append("\tvoid ").append(callbackDeclaration.getNameAsString()).append("("); + for(int i = 0; i < methods.size(); i++) { + Pair> pair = methods.get(i); + IDLMethod idlMethod = pair.a; + MethodDeclaration internalMethod = pair.b.a; + String fpTypeName = buildFPTypeName(callback.name, idlMethod, internalMethod); + if(i > 0) cppClass.append(", "); + cppClass.append(fpTypeName).append(" ").append(idlMethod.getCPPName()); + } + cppClass.append(") {\n"); + for(Pair> pair : methods) { + IDLMethod idlMethod = pair.a; + cppClass.append("\t\tthis->").append(idlMethod.getCPPName()).append("_ptr = ").append(idlMethod.getCPPName()).append(";\n"); + } + cppClass.append("\t}\n"); + + // Virtual methods — call function pointers + cppClass.append(generateFFMMethodCallers(idlClass, methods)); + + cppClass.append("};\n"); + + // Attach to constructor block comment (same pattern as CppCodeParser). + // parseCodeBlock will emit the code via cppGenerator.addNativeCode(). + String header = "[-" + HEADER_CMD + ";" + CMD_NATIVE + "]\n"; + String code = header + cppClass.toString(); + classDeclaration.getConstructors().get(0).setBlockComment(code); + } + + /** + * Generate the C++ body for the native setupCallback method. + * Example: nativeObject->setupCallback((fp_type)fp1, (fp_type)fp2); + */ + private String generateFFMSetupCallbackCPPBody(IDLClass idlCallbackClass, + ArrayList>> methods) { + StringBuilder sb = new StringBuilder(); + sb.append("\n").append(idlCallbackClass.name).append("* nativeObject = (").append(idlCallbackClass.name).append("*)this_addr;\n"); + sb.append("nativeObject->setupCallback("); + for(int i = 0; i < methods.size(); i++) { + Pair> pair = methods.get(i); + IDLMethod idlMethod = pair.a; + MethodDeclaration internalMethod = pair.b.a; + String fpTypeName = buildFPTypeName(idlCallbackClass.name, idlMethod, internalMethod); + if(i > 0) sb.append(", "); + sb.append("(").append(fpTypeName).append(")").append(idlMethod.getCPPName()).append("_fp"); + } + sb.append(");\n"); + return sb.toString(); + } + + /** + * Generate virtual method implementations that call function pointers. + */ + private String generateFFMMethodCallers(IDLClass idlClass, + ArrayList>> methods) { + IDLClass callback = idlClass.callbackImpl; + StringBuilder cppMethods = new StringBuilder(); + + for(Pair> pair : methods) { + IDLMethod idlMethod = pair.a; + MethodDeclaration publicMethod = pair.b.b; + + Type type = publicMethod.getType(); + boolean isVoidType = type.isVoidType(); + String returnTypeStr = getFFMCPPType(idlMethod.getCPPReturnType()); + String constStr = idlMethod.isReturnConst ? " const" : ""; + String methodName = idlMethod.getCPPName(); + + // Build virtual method params and call params + StringBuilder methodParams = new StringBuilder(); + StringBuilder callParams = new StringBuilder(); + NodeList publicMethodParameters = publicMethod.getParameters(); + + for(int i = 0; i < idlMethod.parameters.size(); i++) { + IDLParameter idlParameter = idlMethod.parameters.get(i); + Parameter parameter = publicMethodParameters.get(i); + boolean isPrimitive = parameter.getType().isPrimitiveType() || idlParameter.isAny; + String paramName = idlParameter.name; + String paramType = idlParameter.getCPPType(); + boolean isString = idlParameter.idlType.equals("DOMString"); + String tag = " "; + String callParamCast = ""; + + if(!isString) { + if(idlParameter.isRef) { + tag = "& "; + callParamCast = "(int64_t)&"; + } + else if(idlParameter.isAny) { + // any type = void* in C++ virtual method; needs (int64_t) cast for function pointer + // Don't change tag — getCPPType() already returns "void*" + callParamCast = "(int64_t)"; + } + else if(!idlParameter.isEnum() && !isPrimitive && !idlParameter.isValue) { + tag = "* "; + callParamCast = "(int64_t)"; + } + } + + paramType = getFFMCPPType(paramType); + if(idlParameter.isConst) { + paramType = "const " + paramType; + } + + if(i > 0) { + callParams.append(", "); + methodParams.append(", "); + } + callParams.append(callParamCast).append(paramName); + methodParams.append(paramType).append(tag).append(paramName); + } + + String returnStr = isVoidType ? "" : "return (" + returnTypeStr + ")"; + if(returnTypeStr.contains("unsigned")) { + returnStr = "return (" + returnTypeStr + ")"; + } + + cppMethods.append("\tvirtual ").append(returnTypeStr).append(" ").append(methodName) + .append("(").append(methodParams).append(")").append(constStr).append(" {\n"); + cppMethods.append("\t\t").append(returnStr).append(methodName).append("_ptr(").append(callParams).append(");\n"); + cppMethods.append("\t}\n"); + } + return cppMethods.toString(); + } + + /** + * Build a function pointer typedef for a callback method. + * Example: typedef void (*fp_MyCallbackImpl_onEvent_JJ)(int64_t, int64_t); + */ + private String buildFPTypedef(String className, IDLMethod idlMethod, MethodDeclaration internalMethod) { + String fpTypeName = buildFPTypeName(className, idlMethod, internalMethod); + String returnCType = FFMTypeMapper.getCType(internalMethod.getType().asString()); + + StringBuilder sb = new StringBuilder(); + sb.append("typedef ").append(returnCType).append(" (*").append(fpTypeName).append(")("); + NodeList params = internalMethod.getParameters(); + for(int i = 0; i < params.size(); i++) { + if(i > 0) sb.append(", "); + // Use IDL parameter info to detect string (DOMString) types, since + // fixupCallbackStringParams may have changed the Java type to MemorySegment. + if(i < idlMethod.parameters.size() && idlMethod.parameters.get(i).idlType.equals("DOMString")) { + sb.append("const char*"); + } else { + String paramType = params.get(i).getType().asString(); + sb.append(FFMTypeMapper.getCType(paramType)); + } + } + sb.append(");"); + return sb.toString(); + } + + /** + * Build a unique function pointer type name for a callback method. + * Example: fp_MyCallbackImpl_onEvent_JJ + */ + private String buildFPTypeName(String className, IDLMethod idlMethod, MethodDeclaration internalMethod) { + StringBuilder suffix = new StringBuilder(); + NodeList params = internalMethod.getParameters(); + for(Parameter param : params) { + suffix.append(FFMTypeMapper.getOverloadSuffix(param.getType().asString())); + } + return "fp_" + className + "_" + idlMethod.getCPPName() + "_" + suffix; + } + + /** + * Fix up String parameters on a callback internal method for FFM upcall compatibility. + * Changes the parameter type from String to MemorySegment and inserts conversion code + * at the start of the method body (MemorySegment → String via getString(0)). + */ + private void fixupCallbackStringParams(MethodDeclaration internalMethod) { + NodeList params = internalMethod.getParameters(); + for(int i = 0; i < params.size(); i++) { + Parameter param = params.get(i); + if(param.getType().asString().equals("String")) { + String originalName = param.getNameAsString(); + String segmentName = originalName + "_seg"; + param.setName(segmentName); + param.setType(StaticJavaParser.parseType("java.lang.foreign.MemorySegment")); + // Insert conversion statement at the top of the method body + String convStmt = "String " + originalName + " = " + segmentName + + ".reinterpret(Long.MAX_VALUE).getString(0);"; + internalMethod.getBody().ifPresent(body -> + body.getStatements().add(0, StaticJavaParser.parseStatement(convStmt))); + } + } + } + + /** + * Build MethodType string for MethodHandles.lookup().findVirtual(). + * Example: java.lang.invoke.MethodType.methodType(void.class, long.class, long.class) + */ + private String buildMethodTypeStr(MethodDeclaration internalMethod) { + StringBuilder sb = new StringBuilder(); + sb.append("java.lang.invoke.MethodType.methodType("); + Type returnType = internalMethod.getType(); + if(returnType.isVoidType()) { + sb.append("void.class"); + } else { + sb.append(returnType.asString()).append(".class"); + } + for(Parameter param : internalMethod.getParameters()) { + sb.append(", ").append(param.getType().asString()).append(".class"); + } + sb.append(")"); + return sb.toString(); + } + + /** + * Build FunctionDescriptor for upcall stubs. + * Example: java.lang.foreign.FunctionDescriptor.ofVoid(java.lang.foreign.ValueLayout.JAVA_LONG) + */ + private String buildCallbackFunctionDescriptor(MethodDeclaration internalMethod) { + StringBuilder sb = new StringBuilder(); + Type returnType = internalMethod.getType(); + boolean isVoid = returnType.isVoidType(); + + if(isVoid) { + sb.append("java.lang.foreign.FunctionDescriptor.ofVoid("); + } else { + String retLayout = FFMTypeMapper.getValueLayout(returnType.asString()); + if(retLayout == null) retLayout = "java.lang.foreign.ValueLayout.JAVA_LONG"; + else retLayout = "java.lang.foreign." + retLayout; + sb.append("java.lang.foreign.FunctionDescriptor.of(").append(retLayout); + if(internalMethod.getParameters().size() > 0) sb.append(", "); + } + + NodeList params = internalMethod.getParameters(); + for(int i = 0; i < params.size(); i++) { + if(i > 0) sb.append(", "); + String paramType = params.get(i).getType().asString(); + String layout = FFMTypeMapper.getValueLayout(paramType); + if(layout == null) layout = "java.lang.foreign.ValueLayout.JAVA_LONG"; + else layout = "java.lang.foreign." + layout; + sb.append(layout); + } + sb.append(")"); + return sb.toString(); + } + + /** + * Map Java/IDL type to FFM-compatible C++ type. + */ + private String getFFMCPPType(String typeString) { + if(typeString.equals("boolean")) return "bool"; + if(typeString.equals("String")) return "char*"; + return typeString; + } + + // ==================== C++ Parameter Helpers (reused from CppCodeParser) ==================== + + private void setupMethodGenerated(IDLMethod idlMethod, String param, + ClassOrInterfaceDeclaration classDeclaration, + MethodDeclaration methodDeclaration, + MethodDeclaration nativeMethod) { + Type returnType = methodDeclaration.getType(); + String returnTypeStr = idlMethod.getJavaReturnType(); + String cppReturnType = idlMethod.getCPPReturnType(); + String methodName = idlMethod.getCPPName(); + String classTypeName = classDeclaration.getNameAsString(); + IDLClass idlClass = idlMethod.idlFile.getClass(classTypeName); + if(idlClass != null) { + classTypeName = idlClass.getCPPName(); + } + String returnCastStr = ""; + String methodCaller = methodName + "(" + param + ")"; + if(idlMethod.idlFile.getEnum(returnTypeStr) != null) { + returnCastStr = "(int)"; + } + if(idlMethod.isAny) { + returnCastStr = "(int64_t)"; + } + + String constTag = ""; + if(idlMethod.isReturnConst) { + constTag = "const "; + } + + String operator = getOperator(idlMethod.operator, param); + String content = null; + IDLMethodOperation.Op op = IDLMethodOperation.getEnum(idlMethod, methodDeclaration, nativeMethod); + switch(op) { + case CALL_VOID_STATIC: + content = METHOD_CALL_VOID_STATIC_TEMPLATE.replace(TEMPLATE_TAG_METHOD, methodCaller).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case CALL_VOID: + content = METHOD_CALL_VOID_TEMPLATE.replace(TEMPLATE_TAG_METHOD, methodCaller).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case GET_OBJ_REF_POINTER_STATIC: + content = METHOD_GET_REF_OBJ_POINTER_STATIC_TEMPLATE.replace(TEMPLATE_TAG_METHOD, methodCaller).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case GET_OBJ_REF_POINTER: + if(operator.isEmpty()) { + content = METHOD_GET_REF_OBJ_POINTER_TEMPLATE.replace(TEMPLATE_TAG_METHOD, methodCaller).replace(TEMPLATE_TAG_TYPE, classTypeName); + } else { + content = METHOD_GET_REF_OBJ_POINTER_OPERATOR_TEMPLATE.replace(TEMPLATE_TAG_OPERATOR, operator).replace(TEMPLATE_TAG_TYPE, classTypeName); + } + break; + case GET_OBJ_VALUE_STATIC: { + String returnTypeName = returnType.asClassOrInterfaceType().asClassOrInterfaceType().getNameAsString(); + IDLClass retTypeClass = idlMethod.idlFile.getClass(returnTypeName); + if(retTypeClass != null) returnTypeName = retTypeClass.getCPPName(); + String copyParam = "copy_addr"; + content = METHOD_GET_OBJ_VALUE_STATIC_TEMPLATE + .replace(TEMPLATE_TAG_METHOD, methodCaller).replace(TEMPLATE_TAG_TYPE, classTypeName) + .replace(TEMPLATE_TAG_COPY_TYPE, returnTypeName).replace(TEMPLATE_TAG_COPY_PARAM, copyParam); + break; + } + case GET_OBJ_VALUE: { + String returnTypeName = returnType.asClassOrInterfaceType().asClassOrInterfaceType().getNameAsString(); + IDLClass retTypeClass = idlMethod.idlFile.getClass(returnTypeName); + if(retTypeClass != null) returnTypeName = retTypeClass.getCPPName(); + String copyParam = "copy_addr"; + if(operator.isEmpty()) { + content = METHOD_GET_OBJ_VALUE_TEMPLATE + .replace(TEMPLATE_TAG_METHOD, methodCaller).replace(TEMPLATE_TAG_TYPE, classTypeName) + .replace(TEMPLATE_TAG_COPY_TYPE, returnTypeName).replace(TEMPLATE_TAG_COPY_PARAM, copyParam); + } else { + content = METHOD_GET_OBJ_VALUE_ARITHMETIC_OPERATOR_TEMPLATE + .replace(TEMPLATE_TAG_OPERATOR, operator).replace(TEMPLATE_TAG_TYPE, classTypeName) + .replace(TEMPLATE_TAG_COPY_TYPE, returnTypeName).replace(TEMPLATE_TAG_COPY_PARAM, copyParam); + } + break; + } + case GET_OBJ_POINTER_STATIC: + content = METHOD_GET_OBJ_POINTER_STATIC_TEMPLATE.replace(TEMPLATE_TAG_METHOD, methodCaller).replace(TEMPLATE_TAG_TYPE, classTypeName); + break; + case GET_OBJ_POINTER: + if(operator.isEmpty()) { + content = METHOD_GET_OBJ_POINTER_TEMPLATE + .replace(TEMPLATE_TAG_METHOD, methodCaller).replace(TEMPLATE_TAG_TYPE, classTypeName) + .replace(TEMPLATE_TAG_RETURN_TYPE, cppReturnType).replace(TEMPLATE_TAG_CONST, constTag); + } else { + content = METHOD_GET_OBJ_POINTER_OPERATOR_TEMPLATE + .replace(TEMPLATE_TAG_OPERATOR, operator).replace(TEMPLATE_TAG_TYPE, classTypeName) + .replace(TEMPLATE_TAG_RETURN_TYPE, cppReturnType).replace(TEMPLATE_TAG_CONST, constTag); + } + break; + case GET_PRIMITIVE_STATIC: + content = METHOD_GET_PRIMITIVE_STATIC_TEMPLATE.replace(TEMPLATE_TAG_METHOD, methodCaller).replace(TEMPLATE_TAG_TYPE, classTypeName).replace(TEMPLATE_TAG_CAST, returnCastStr); + break; + case GET_PRIMITIVE: + if(operator.isEmpty()) { + content = METHOD_GET_PRIMITIVE_TEMPLATE.replace(TEMPLATE_TAG_METHOD, methodCaller).replace(TEMPLATE_TAG_TYPE, classTypeName).replace(TEMPLATE_TAG_CAST, returnCastStr); + } else { + content = METHOD_GET_PRIMITIVE_OPERATOR_TEMPLATE + .replace(TEMPLATE_TAG_OPERATOR, operator).replace(TEMPLATE_TAG_TYPE, classTypeName); + } + break; + } + + String header = "[-" + HEADER_CMD + ";" + CMD_NATIVE + "]"; + String blockComment = header + content; + nativeMethod.setBlockComment(blockComment); + } + + private static String getOperator(String operatorCode, String param) { + String oper = ""; + if(!operatorCode.isEmpty()) { + if(operatorCode.equals("[]")) { + oper = "(*nativeObject)[" + param + "]"; + } else { + oper = "(*nativeObject " + operatorCode + " " + param + ")"; + } + } + return oper; + } + + private static String getParams(IDLMethod idlMethod, MethodDeclaration methodDeclaration) { + NodeList parameters = methodDeclaration.getParameters(); + ArrayList idParameters = idlMethod.parameters; + return getParams(parameters, idParameters); + } + + private static String getParams(NodeList parameters, ArrayList idParameters) { + String param = ""; + for(int i = 0; i < parameters.size(); i++) { + Parameter parameter = parameters.get(i); + IDLParameter idlParameter = idParameters.get(i); + Type type = parameter.getType(); + String paramName = getParam(idlParameter, type); + if(i > 0) param += ", "; + param += paramName; + } + return param; + } + + private static String getParam(IDLParameter idlParameter, Type type) { + IDLFile idlFile = idlParameter.idlFile; + String paramName = idlParameter.name; + String cppType = idlParameter.getCPPType(); + String classType = cppType; + boolean isEnum = idlParameter.isEnum(); + boolean isAny = idlParameter.isAny; + boolean isRef = idlParameter.isRef; + boolean isValue = idlParameter.isValue; + boolean isArray = idlParameter.isArray; + boolean isObject = type.isClassOrInterfaceType(); + + if(!isEnum && isObject && !classType.equals("char*")) { + paramName += IDLDefaultCodeParser.NATIVE_PARAM_ADDRESS; + if(isArray) { + String idlType = cppType.replace("[]", "*"); + if(idlParameter.idlClassOrEnum != null && !isRef) { + idlType += "*"; + } + paramName = "(" + idlType + ")" + paramName; + } else { + String idlArrayOrNull = IDLHelper.getIDLArrayClassOrNull(classType); + if(idlArrayOrNull != null) { + classType = idlArrayOrNull; + } + IDLClass paramClass = idlFile.getClass(classType); + if(paramClass != null) { + classType = paramClass.getCPPName(); + } + if(isRef || isValue) { + paramName = "*((" + classType + "* )" + paramName + ")"; + } else if(isAny) { + paramName = "(" + classType + ")" + paramName; + } else { + paramName = "(" + classType + "* )" + paramName; + } + } + } else if(isAny) { + paramName = "( void* )" + paramName; + } else { + if(classType.equals("int")) { + paramName = "(int)" + paramName; + } else if(classType.equals("float")) { + paramName = "(float)" + paramName; + } else if(classType.equals("double")) { + paramName = "(double)" + paramName; + } else if(classType.equals("boolean")) { + paramName = "(bool)" + paramName; + } + } + + IDLEnumClass anEnum = idlFile.getEnum(classType); + if(anEnum != null) { + if(anEnum.typePrefix.equals(classType)) { + paramName = "(" + classType + ")" + paramName; + } else { + paramName = "(" + anEnum.typePrefix + "::" + classType + ")" + paramName; + } + } + return paramName; + } +} + + + diff --git a/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMCppGenerator.java b/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMCppGenerator.java new file mode 100644 index 00000000..67fe2ac0 --- /dev/null +++ b/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMCppGenerator.java @@ -0,0 +1,247 @@ +package com.github.xpenatan.jParser.ffm; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.xpenatan.jParser.core.JParser; +import com.github.xpenatan.jParser.core.JParserItem; +import com.github.xpenatan.jParser.core.util.CustomFileDescriptor; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Generates FFMGlue.cpp/.h with extern "C" exported functions using standard C types. + * Parallel to NativeCPPGenerator but without any JNI dependencies. + */ +public class FFMCppGenerator implements FFMNativeCodeGenerator { + + public static boolean SKIP_GLUE_CODE = false; + + private String glueCppDestinationDir; + private String cppGlueName = "FFMGlue"; + + StringBuilder mainPrinter = new StringBuilder(); + StringBuilder headerPrinter = new StringBuilder(); + StringBuilder codePrinter = new StringBuilder(); + + private boolean init = true; + + public FFMCppGenerator(String cppDestinationDir) { + try { + this.glueCppDestinationDir = new File(cppDestinationDir, "ffmglue").getCanonicalPath() + File.separator; + } catch(IOException e) { + throw new RuntimeException(e); + } + } + + private void print(PrintType type, String text) { + if(init) { + init = false; + headerPrinter.append("#pragma once\n"); + headerPrinter.append("#include \n"); + headerPrinter.append("\n"); + headerPrinter.append("#ifdef _WIN32\n"); + headerPrinter.append(" #define FFM_EXPORT __declspec(dllexport)\n"); + headerPrinter.append("#else\n"); + headerPrinter.append(" #define FFM_EXPORT __attribute__((visibility(\"default\")))\n"); + headerPrinter.append("#endif\n"); + headerPrinter.append("\n"); + mainPrinter.append("\n"); + mainPrinter.append("extern \"C\" {\n"); + mainPrinter.append("\n"); + } + if(type == PrintType.HEADER) { + headerPrinter.append(text + "\n"); + } + else if(type == PrintType.MAIN) { + mainPrinter.append(text + "\n"); + } + else if(type == PrintType.CODE) { + codePrinter.append(text + "\n"); + } + } + + @Override + public void addNativeCode(Node node, String content) { + Scanner scanner = new Scanner(content); + boolean haveInclude = content.contains("#include"); + while(scanner.hasNextLine()) { + String line = scanner.nextLine().trim(); + if(haveInclude) { + print(PrintType.HEADER, line); + } + else { + print(PrintType.CODE, line); + } + } + scanner.close(); + } + + @Override + public void addCallbackClassCode(String cppClassCode) { + // Callback class code goes into the CODE section (before extern "C") + print(PrintType.CODE, cppClassCode); + } + + @Override + public void addNativeCode(MethodDeclaration nativeMethod, String content) { + String methodName = nativeMethod.getNameAsString(); + boolean isStatic = nativeMethod.isStatic(); + TypeDeclaration classOrEnum = (TypeDeclaration) nativeMethod.getParentNode().get(); + CompilationUnit compilationUnit = classOrEnum.findCompilationUnit().get(); + String packageName = compilationUnit.getPackageDeclaration().get().getNameAsString(); + String className = classOrEnum.getNameAsString(); + String packageNameCPP = packageName.replace(".", "_"); + String returnTypeStr = nativeMethod.getType().toString(); + String returnType = FFMTypeMapper.getCType(returnTypeStr); + + // Build parameter list — no JNIEnv*, no jclass/jobject + String params = "("; + ArrayList arguments = new ArrayList<>(); + if(nativeMethod.getParameters() != null) { + for(Parameter parameter : nativeMethod.getParameters()) { + FFMArgument argument = getArgument(parameter); + arguments.add(argument); + } + } + + String paramsType = ""; + String prefixCode = ""; + String suffixCode = ""; + + for(int i = 0; i < arguments.size(); i++) { + FFMArgument argument = arguments.get(i); + String paramName = argument.name; + String cType = argument.cType; + String valueType = argument.overloadSuffix; + paramsType += valueType; + + if(i > 0) { + params += ", "; + } + + // Strings arrive as const char* directly from FFM — no conversion needed + params += cType + " " + paramName; + } + + if(!paramsType.isEmpty()) { + paramsType = "__" + paramsType; + } + else { + paramsType = "__"; + } + + params += ")"; + + // Escape underscores in method/class names for symbol name + String escapedMethodName = methodName.replace("_", "_1"); + String escapedClassName = className.replace("_", "_1"); + + boolean haveReturn = content.lines().anyMatch(s -> s.trim().startsWith("return ")); + if(haveReturn) { + String wrappedLambda = "" + + returnType + " wrappedReturn = [&]() -> " + returnType + " {\n" + + content + + "\n }();"; + + content = wrappedLambda; + suffixCode += "return wrappedReturn;"; + } + + content = prefixCode + "\n" + content + "\n" + suffixCode; + + String fullMethodName = packageNameCPP + "_" + escapedClassName + "_" + escapedMethodName + paramsType + params; + + print(PrintType.MAIN, "FFM_EXPORT " + returnType + " jparser_" + fullMethodName + " {"); + content = "\t" + content.replace("\n", "\n\t"); + print(PrintType.MAIN, content); + print(PrintType.MAIN, "}"); + print(PrintType.MAIN, ""); + } + + /** + * Build the symbol name for a native method. + * Must match exactly with what FFMCodeParser generates for SymbolLookup.find(). + */ + public static String buildSymbolName(String packageName, String className, String methodName, ArrayList arguments) { + String packageNameCPP = packageName.replace(".", "_"); + String escapedClassName = className.replace("_", "_1"); + String escapedMethodName = methodName.replace("_", "_1"); + + String paramsType = ""; + for(FFMArgument argument : arguments) { + paramsType += argument.overloadSuffix; + } + if(!paramsType.isEmpty()) { + paramsType = "__" + paramsType; + } + else { + paramsType = "__"; + } + + return "jparser_" + packageNameCPP + "_" + escapedClassName + "_" + escapedMethodName + paramsType; + } + + @Override + public void addParseFile(JParser jParser, JParserItem parserItem) { + } + + @Override + public void generate(JParser jParser) { + headerPrinter.append("\n"); + + mainPrinter.insert(0, codePrinter); + mainPrinter.insert(0, headerPrinter); + print(PrintType.MAIN, "}"); + String code = mainPrinter.toString(); + + String gluePathStr = glueCppDestinationDir; + String cppGlueHPath = gluePathStr + cppGlueName + ".h"; + String cppGluePath = gluePathStr + cppGlueName + ".cpp"; + CustomFileDescriptor fileDescriptor = new CustomFileDescriptor(cppGlueHPath); + if(!SKIP_GLUE_CODE) { + fileDescriptor.writeString(code, false); + } + + CustomFileDescriptor cppFile = new CustomFileDescriptor(cppGluePath); + String include = "#include \"" + cppGlueName + ".h\""; + cppFile.writeString(include, false); + } + + private FFMArgument getArgument(Parameter parameter) { + String[] typeTokens = parameter.getType().toString().split("\\."); + String type = typeTokens[typeTokens.length - 1]; + String cType = FFMTypeMapper.getCType(type); + String overloadSuffix = FFMTypeMapper.getOverloadSuffix(type); + return new FFMArgument(parameter.getNameAsString(), type, cType, overloadSuffix); + } + + /** + * Represents a function argument with its FFM/C type info. + */ + public static class FFMArgument { + public final String name; + public final String javaType; + public final String cType; + public final String overloadSuffix; + + public FFMArgument(String name, String javaType, String cType, String overloadSuffix) { + this.name = name; + this.javaType = javaType; + this.cType = cType; + this.overloadSuffix = overloadSuffix; + } + } + + enum PrintType { + HEADER, + CODE, + MAIN + } +} + + diff --git a/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMMethodHandleRegistry.java b/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMMethodHandleRegistry.java new file mode 100644 index 00000000..566aecc9 --- /dev/null +++ b/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMMethodHandleRegistry.java @@ -0,0 +1,136 @@ +package com.github.xpenatan.jParser.ffm; + +import com.github.javaparser.ast.body.Parameter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Tracks MethodHandle entries per Java class during FFM code generation. + * After all methods are parsed, this registry is used to inject the FFMHandles inner class + * with static MethodHandle fields and FunctionDescriptor initialization. + */ +public class FFMMethodHandleRegistry { + + private final Map> classEntries = new HashMap<>(); + + /** + * Register a native method for a given class. + */ + public void register(String className, String symbolName, String javaMethodName, + String handleName, String returnType, List parameters) { + List entries = classEntries.computeIfAbsent(className, k -> new ArrayList<>()); + entries.add(new FFMEntry(symbolName, javaMethodName, handleName, returnType, parameters)); + } + + /** + * Get all entries for a given class. + */ + public List getEntries(String className) { + return classEntries.getOrDefault(className, new ArrayList<>()); + } + + /** + * Get all class names that have registered entries. + */ + public Iterable getClassNames() { + return classEntries.keySet(); + } + + /** + * Check if a class has any registered entries. + */ + public boolean hasEntries(String className) { + List entries = classEntries.get(className); + return entries != null && !entries.isEmpty(); + } + + /** + * Clear all entries (for reuse). + */ + public void clear() { + classEntries.clear(); + } + + /** + * Generate the FunctionDescriptor code for a single entry. + * Example output: "FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT)" + * Example output: "FunctionDescriptor.ofVoid(ValueLayout.JAVA_LONG)" + */ + public static String buildFunctionDescriptor(FFMEntry entry) { + StringBuilder sb = new StringBuilder(); + boolean isVoid = entry.returnType.equals("void"); + + if(isVoid) { + sb.append("FunctionDescriptor.ofVoid("); + } + else { + String retLayout = FFMTypeMapper.getValueLayout(entry.returnType); + if(retLayout == null) { + // Non-primitive return (object pointer → long) + retLayout = "ValueLayout.JAVA_LONG"; + } + sb.append("FunctionDescriptor.of(").append(retLayout); + if(!entry.parameters.isEmpty()) { + sb.append(", "); + } + } + + for(int i = 0; i < entry.parameters.size(); i++) { + ParamInfo param = entry.parameters.get(i); + String layout = FFMTypeMapper.getValueLayout(param.javaType); + if(layout == null) { + // Non-primitive parameter (object address → long) + layout = "ValueLayout.JAVA_LONG"; + } + if(i > 0) { + sb.append(", "); + } + sb.append(layout); + } + + sb.append(")"); + return sb.toString(); + } + + /** + * Represents a single MethodHandle entry to be generated. + */ + public static class FFMEntry { + public final String symbolName; + public final String javaMethodName; + /** Unique field name for the MethodHandle in FFMHandles (includes overload suffix). */ + public final String handleName; + public final String returnType; + public final List parameters; + + public FFMEntry(String symbolName, String javaMethodName, String handleName, String returnType, List parameters) { + this.symbolName = symbolName; + this.javaMethodName = javaMethodName; + this.handleName = handleName; + this.returnType = returnType; + this.parameters = parameters; + } + } + + /** + * Parameter info for building FunctionDescriptor. + */ + public static class ParamInfo { + public final String name; + public final String javaType; + + public ParamInfo(String name, String javaType) { + this.name = name; + this.javaType = javaType; + } + + public static ParamInfo fromParameter(Parameter parameter) { + String[] typeTokens = parameter.getType().toString().split("\\."); + String type = typeTokens[typeTokens.length - 1]; + return new ParamInfo(parameter.getNameAsString(), type); + } + } +} + diff --git a/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMNativeCodeGenerator.java b/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMNativeCodeGenerator.java new file mode 100644 index 00000000..11b631f8 --- /dev/null +++ b/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMNativeCodeGenerator.java @@ -0,0 +1,27 @@ +package com.github.xpenatan.jParser.ffm; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.xpenatan.jParser.core.JParser; +import com.github.xpenatan.jParser.core.JParserItem; + +/** + * Interface for generating native C/C++ glue code for FFM. + * Parallel to CppGenerator but decoupled from JNI dependencies. + */ +public interface FFMNativeCodeGenerator { + void addNativeCode(Node node, String content); + + void addNativeCode(MethodDeclaration nativeMethod, String content); + + /** + * Add raw C++ code for a callback class definition. + * This code is placed before the extern "C" block in the generated glue file. + */ + void addCallbackClassCode(String cppClassCode); + + void addParseFile(JParser jParser, JParserItem parserItem); + + void generate(JParser jParser); +} + diff --git a/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMTypeMapper.java b/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMTypeMapper.java new file mode 100644 index 00000000..29541210 --- /dev/null +++ b/jParser/jParser-ffm/src/main/java/com/github/xpenatan/jParser/ffm/FFMTypeMapper.java @@ -0,0 +1,176 @@ +package com.github.xpenatan.jParser.ffm; + +import java.util.HashMap; +import java.util.Map; + +/** + * Maps Java types to FFM ValueLayout constants and C types for the FFM code generator. + */ +public class FFMTypeMapper { + + private static final Map javaToValueLayout = new HashMap<>(); + private static final Map javaToCType = new HashMap<>(); + private static final Map javaToFFMCast = new HashMap<>(); + + static { + // Java primitive → ValueLayout constant name + javaToValueLayout.put("long", "ValueLayout.JAVA_LONG"); + javaToValueLayout.put("int", "ValueLayout.JAVA_INT"); + javaToValueLayout.put("float", "ValueLayout.JAVA_FLOAT"); + javaToValueLayout.put("double", "ValueLayout.JAVA_DOUBLE"); + javaToValueLayout.put("boolean", "ValueLayout.JAVA_BOOLEAN"); + javaToValueLayout.put("short", "ValueLayout.JAVA_SHORT"); + javaToValueLayout.put("byte", "ValueLayout.JAVA_BYTE"); + javaToValueLayout.put("char", "ValueLayout.JAVA_CHAR"); + javaToValueLayout.put("String", "ValueLayout.ADDRESS"); + javaToValueLayout.put("java.lang.foreign.MemorySegment", "ValueLayout.ADDRESS"); + + // Array types → ADDRESS layout (passed as MemorySegment pointers) + javaToValueLayout.put("int[]", "ValueLayout.ADDRESS"); + javaToValueLayout.put("long[]", "ValueLayout.ADDRESS"); + javaToValueLayout.put("float[]", "ValueLayout.ADDRESS"); + javaToValueLayout.put("double[]", "ValueLayout.ADDRESS"); + javaToValueLayout.put("byte[]", "ValueLayout.ADDRESS"); + javaToValueLayout.put("short[]", "ValueLayout.ADDRESS"); + javaToValueLayout.put("boolean[]", "ValueLayout.ADDRESS"); + javaToValueLayout.put("char[]", "ValueLayout.ADDRESS"); + + // Java primitive → C type for FFMGlue.cpp + javaToCType.put("long", "int64_t"); + javaToCType.put("int", "int32_t"); + javaToCType.put("float", "float"); + javaToCType.put("double", "double"); + javaToCType.put("boolean", "int32_t"); + javaToCType.put("short", "int16_t"); + javaToCType.put("byte", "int8_t"); + javaToCType.put("char", "uint16_t"); + javaToCType.put("void", "void"); + javaToCType.put("String", "const char*"); + + // Array types → C pointer types + javaToCType.put("int[]", "int32_t*"); + javaToCType.put("long[]", "int64_t*"); + javaToCType.put("float[]", "float*"); + javaToCType.put("double[]", "double*"); + javaToCType.put("byte[]", "int8_t*"); + javaToCType.put("short[]", "int16_t*"); + javaToCType.put("boolean[]", "int32_t*"); + javaToCType.put("char[]", "uint16_t*"); + + // Java primitive → cast needed in invokeExact return + javaToFFMCast.put("long", "long"); + javaToFFMCast.put("int", "int"); + javaToFFMCast.put("float", "float"); + javaToFFMCast.put("double", "double"); + javaToFFMCast.put("boolean", "boolean"); + javaToFFMCast.put("short", "short"); + javaToFFMCast.put("byte", "byte"); + javaToFFMCast.put("char", "char"); + } + + /** + * Returns the FFM ValueLayout constant for a Java type string. + * Returns null if the type is not a known primitive/String. + */ + public static String getValueLayout(String javaType) { + return javaToValueLayout.get(javaType); + } + + /** + * Returns the C type for a Java type string (used in FFMGlue.cpp). + */ + public static String getCType(String javaType) { + String cType = javaToCType.get(javaType); + return cType != null ? cType : "int64_t"; // default: object addresses are int64_t + } + + /** + * Returns the cast type for MethodHandle.invokeExact() return. + */ + public static String getFFMCast(String javaType) { + String cast = javaToFFMCast.get(javaType); + return cast != null ? cast : "long"; // default: object addresses are long + } + + /** + * Returns true if the type is a known primitive type (including void). + */ + public static boolean isPrimitive(String javaType) { + return javaToCType.containsKey(javaType) && !javaType.equals("String"); + } + + /** + * Returns true if the type is String. + */ + public static boolean isString(String javaType) { + return "String".equals(javaType); + } + + /** + * Gets the overload suffix character for a parameter type, similar to JNI mangling. + * Used to disambiguate overloaded native function names. + */ + public static String getOverloadSuffix(String javaType) { + switch(javaType) { + case "boolean": return "Z"; + case "byte": return "B"; + case "char": return "C"; + case "short": return "S"; + case "int": return "I"; + case "long": return "J"; + case "float": return "F"; + case "double": return "D"; + case "String": return "Ljava_lang_String_2"; + default: return "Ljava_lang_Object_2"; + } + } + + // ==================== Array/Buffer Optimization Helpers ==================== + + /** + * Returns true if the type is a Java array type. + */ + public static boolean isArrayType(String javaType) { + return javaType.endsWith("[]"); + } + + /** + * Returns FFM code to create a MemorySegment from a Java primitive array. + * Example: "java.lang.foreign.MemorySegment.ofArray(myArray)" + * + * @param paramName the Java variable name of the array + * @param javaType the Java array type (e.g., "int[]", "float[]") + * @return the FFM MemorySegment creation code + */ + public static String getArraySegmentCode(String paramName, String javaType) { + if(!isArrayType(javaType)) { + throw new IllegalArgumentException("Not an array type: " + javaType); + } + return "java.lang.foreign.MemorySegment.ofArray(" + paramName + ")"; + } + + /** + * Returns FFM code to create a MemorySegment from a direct ByteBuffer. + * Example: "java.lang.foreign.MemorySegment.ofBuffer(myBuffer)" + * + * @param paramName the Java variable name of the ByteBuffer + * @return the FFM MemorySegment creation code + */ + public static String getBufferSegmentCode(String paramName) { + return "java.lang.foreign.MemorySegment.ofBuffer(" + paramName + ")"; + } + + /** + * Returns the element ValueLayout for an array type. + * Example: "int[]" → "ValueLayout.JAVA_INT" + * + * @param arrayType the Java array type + * @return the element ValueLayout, or null if not a known array type + */ + public static String getArrayElementLayout(String arrayType) { + if(!isArrayType(arrayType)) return null; + String elementType = arrayType.replace("[]", ""); + return javaToValueLayout.get(elementType); + } +} + diff --git a/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLAttributeParser.java b/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLAttributeParser.java index 79053f47..9c01b4fd 100644 --- a/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLAttributeParser.java +++ b/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLAttributeParser.java @@ -117,7 +117,7 @@ public static void generateAttribute(IDLDefaultCodeParser idlParser, JParser jPa JParserHelper.addMissingImportType(jParser, unit, type); IDLDefaultCodeParser.setDefaultReturnValues(jParser, unit, type, getMethodDeclaration); - if(idlParser.generateClass) { + if(idlParser.generateNativeBindings) { setupAttributeMethod(idlParser, jParser, idlAttribute, false, classOrInterfaceDeclaration, getMethodDeclaration, getMethodName); } } @@ -136,7 +136,7 @@ public static void generateAttribute(IDLDefaultCodeParser idlParser, JParser jPa Type paramType = parameter.getType(); JParserHelper.addMissingImportType(jParser, unit, paramType); - if(idlParser.generateClass) { + if(idlParser.generateNativeBindings) { setupAttributeMethod(idlParser, jParser, idlAttribute, true, classOrInterfaceDeclaration, setMethodDeclaration, setMethodName); } } diff --git a/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLConstructorParser.java b/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLConstructorParser.java index 90a8ae8b..45314165 100644 --- a/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLConstructorParser.java +++ b/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLConstructorParser.java @@ -42,7 +42,7 @@ public static void generateConstructor(IDLDefaultCodeParser idlParser, JParser j IDLConstructor idlConstructor = constructors.get(i); ConstructorDeclaration constructorDeclaration = IDLConstructorParser.getOrCreateConstructorDeclaration(idlParser, jParser, unit, classOrInterfaceDeclaration, idlConstructor); - if(constructorDeclaration.getBody().isEmpty()) { + if(constructorDeclaration.getBody().isEmpty() && idlParser.generateNativeBindings) { MethodDeclaration nativeMethod = IDLConstructorParser.setupConstructor(idlParser.idlReader, idlConstructor, classOrInterfaceDeclaration, constructorDeclaration); idlParser.onIDLConstructorGenerated(jParser, idlConstructor, classOrInterfaceDeclaration, constructorDeclaration, nativeMethod); } diff --git a/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLDeConstructorParser.java b/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLDeConstructorParser.java index f4502433..1fcfc4f8 100644 --- a/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLDeConstructorParser.java +++ b/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLDeConstructorParser.java @@ -21,6 +21,10 @@ public class IDLDeConstructorParser { private static final String DELETE_NATIVE = "deleteNative"; public static void generateDeConstructor(IDLDefaultCodeParser idlParser, JParser jParser, CompilationUnit unit, ClassOrInterfaceDeclaration classOrInterfaceDeclaration, IDLClass idlClass) { + if(!idlParser.generateNativeBindings) { + return; + } + if(!idlClass.classHeader.isNoDelete) { List methodsBySignature = classOrInterfaceDeclaration.getMethodsBySignature(DELETE_NATIVE); int size = methodsBySignature.size(); diff --git a/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLDefaultCodeParser.java b/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLDefaultCodeParser.java index 55f59e36..d0754032 100644 --- a/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLDefaultCodeParser.java +++ b/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLDefaultCodeParser.java @@ -38,6 +38,7 @@ public class IDLDefaultCodeParser extends IDLClassGeneratorParser { public static final String CMD_IDL_SKIP = "-IDL_SKIP"; protected boolean enableAttributeParsing = true; + public boolean generateNativeBindings = true; protected static final String NATIVE_ADDRESS = "native_address"; protected static final String NATIVE_VOID_ADDRESS = "native_void_address"; @@ -138,14 +139,14 @@ public static void setDefaultReturnValues(JParser jParser, CompilationUnit unit, BlockStmt blockStmt = idlMethodDeclaration.getBody().get(); ReturnStmt returnStmt = new ReturnStmt(); if(returnType.isPrimitiveType()) { - if(JParserHelper.isLong(returnType) || JParserHelper.isInt(returnType) || JParserHelper.isFloat(returnType) || JParserHelper.isDouble(returnType) || JParserHelper.isShort(returnType)) { + if(JParserHelper.isBoolean(returnType)) { NameExpr returnNameExpr = new NameExpr(); - returnNameExpr.setName("0"); + returnNameExpr.setName("false"); returnStmt.setExpression(returnNameExpr); } - else if(JParserHelper.isBoolean(returnType)) { + else { NameExpr returnNameExpr = new NameExpr(); - returnNameExpr.setName("false"); + returnNameExpr.setName("0"); returnStmt.setExpression(returnNameExpr); } } diff --git a/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLMethodParser.java b/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLMethodParser.java index 41dc207a..7b07a904 100644 --- a/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLMethodParser.java +++ b/jParser/jParser-idl/src/main/java/com/github/xpenatan/jParser/idl/parser/IDLMethodParser.java @@ -117,7 +117,7 @@ public class IDLMethodParser { public static void generateMethod(IDLDefaultCodeParser idlParser, JParser jParser, CompilationUnit unit, ClassOrInterfaceDeclaration classOrInterfaceDeclaration, IDLClass idlClass, IDLMethod idlMethod) { MethodDeclaration methodDeclaration = generateAndAddMethodOnly(idlParser, jParser, unit, classOrInterfaceDeclaration, idlMethod); - if(methodDeclaration != null && idlParser.generateClass) { + if(methodDeclaration != null && idlParser.generateNativeBindings) { setupMethod(idlParser, jParser, idlMethod, classOrInterfaceDeclaration, methodDeclaration); } } diff --git a/jParser/jParser-cpp/build.gradle.kts b/jParser/jParser-jni/build.gradle.kts similarity index 94% rename from jParser/jParser-cpp/build.gradle.kts rename to jParser/jParser-jni/build.gradle.kts index ddf4d0d2..f97e51d9 100644 --- a/jParser/jParser-cpp/build.gradle.kts +++ b/jParser/jParser-jni/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("java-library") } -val moduleName = "${LibExt.libName}-cpp" +val moduleName = "${LibExt.libName}-jni" dependencies { implementation(project(":jParser:jParser-idl")) diff --git a/jParser/jParser-cpp/src/main/java/com/github/xpenatan/jParser/cpp/CppCodeParser.java b/jParser/jParser-jni/src/main/java/com/github/xpenatan/jParser/cpp/CppCodeParser.java similarity index 100% rename from jParser/jParser-cpp/src/main/java/com/github/xpenatan/jParser/cpp/CppCodeParser.java rename to jParser/jParser-jni/src/main/java/com/github/xpenatan/jParser/cpp/CppCodeParser.java diff --git a/jParser/jParser-cpp/src/main/java/com/github/xpenatan/jParser/cpp/CppGenerator.java b/jParser/jParser-jni/src/main/java/com/github/xpenatan/jParser/cpp/CppGenerator.java similarity index 100% rename from jParser/jParser-cpp/src/main/java/com/github/xpenatan/jParser/cpp/CppGenerator.java rename to jParser/jParser-jni/src/main/java/com/github/xpenatan/jParser/cpp/CppGenerator.java diff --git a/jParser/jParser-cpp/src/main/java/com/github/xpenatan/jParser/cpp/JNITypeSignature.java b/jParser/jParser-jni/src/main/java/com/github/xpenatan/jParser/cpp/JNITypeSignature.java similarity index 100% rename from jParser/jParser-cpp/src/main/java/com/github/xpenatan/jParser/cpp/JNITypeSignature.java rename to jParser/jParser-jni/src/main/java/com/github/xpenatan/jParser/cpp/JNITypeSignature.java diff --git a/jParser/jParser-cpp/src/main/java/com/github/xpenatan/jParser/cpp/NativeCPPGenerator.java b/jParser/jParser-jni/src/main/java/com/github/xpenatan/jParser/cpp/NativeCPPGenerator.java similarity index 100% rename from jParser/jParser-cpp/src/main/java/com/github/xpenatan/jParser/cpp/NativeCPPGenerator.java rename to jParser/jParser-jni/src/main/java/com/github/xpenatan/jParser/cpp/NativeCPPGenerator.java diff --git a/jParser/jParser-cpp/src/test/java/com/github/xpenatan/jParser/cpp/CppCodeParserTest.java b/jParser/jParser-jni/src/test/java/com/github/xpenatan/jParser/cpp/CppCodeParserTest.java similarity index 100% rename from jParser/jParser-cpp/src/test/java/com/github/xpenatan/jParser/cpp/CppCodeParserTest.java rename to jParser/jParser-jni/src/test/java/com/github/xpenatan/jParser/cpp/CppCodeParserTest.java diff --git a/jParser/jParser-cpp/src/test/java/com/github/xpenatan/jParser/cpp/tests/CppTestClass.java b/jParser/jParser-jni/src/test/java/com/github/xpenatan/jParser/cpp/tests/CppTestClass.java similarity index 100% rename from jParser/jParser-cpp/src/test/java/com/github/xpenatan/jParser/cpp/tests/CppTestClass.java rename to jParser/jParser-jni/src/test/java/com/github/xpenatan/jParser/cpp/tests/CppTestClass.java diff --git a/settings.gradle.kts b/settings.gradle.kts index 7fa2326a..b313de5b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,8 +3,9 @@ include(":jParser:jParser-build") include(":jParser:jParser-build-tool") include(":jParser:jParser-base") include(":jParser:jParser-idl") -include(":jParser:jParser-cpp") +include(":jParser:jParser-jni") include(":jParser:jParser-teavm") +include(":jParser:jParser-ffm") include(":idl:idl-core") include(":idl:idl-teavm") @@ -13,7 +14,8 @@ include(":idl-helper:idl-helper-base") include(":idl-helper:idl-helper-build") include(":idl-helper:idl-helper-core") include(":idl-helper:idl-helper-teavm") -include(":idl-helper:idl-helper-desktop") +include(":idl-helper:idl-helper-desktop-jni") +include(":idl-helper:idl-helper-desktop-ffm") include(":idl-helper:idl-helper-android") include(":loader:loader-core") @@ -22,31 +24,36 @@ include(":loader:loader-teavm") include(":examples:TestLib:lib:lib-build") include(":examples:TestLib:lib:lib-base") include(":examples:TestLib:lib:lib-core") -include(":examples:TestLib:lib:lib-desktop") +include(":examples:TestLib:lib:lib-desktop-jni") +include(":examples:TestLib:lib:lib-desktop-ffm") include(":examples:TestLib:lib:lib-teavm") include(":examples:TestLib:lib:lib-android") include(":examples:TestLib:app:core") -include(":examples:TestLib:app:desktop") +include(":examples:TestLib:app:desktop-jni") +include(":examples:TestLib:app:desktop-ffm") include(":examples:TestLib:app:teavm") include(":examples:TestLib:app:android") include(":examples:SharedLib:libA:lib-build") include(":examples:SharedLib:libA:lib-base") include(":examples:SharedLib:libA:lib-core") -include(":examples:SharedLib:libA:lib-desktop") +include(":examples:SharedLib:libA:lib-desktop-jni") +include(":examples:SharedLib:libA:lib-desktop-ffm") include(":examples:SharedLib:libA:lib-teavm") include(":examples:SharedLib:libA:lib-android") include(":examples:SharedLib:libB:lib-build") include(":examples:SharedLib:libB:lib-base") include(":examples:SharedLib:libB:lib-core") -include(":examples:SharedLib:libB:lib-desktop") +include(":examples:SharedLib:libB:lib-desktop-jni") +include(":examples:SharedLib:libB:lib-desktop-ffm") include(":examples:SharedLib:libB:lib-teavm") include(":examples:SharedLib:libB:lib-android") include(":examples:SharedLib:app:core") -include(":examples:SharedLib:app:desktop") +include(":examples:SharedLib:app:desktop-jni") +include(":examples:SharedLib:app:desktop-ffm") include(":examples:SharedLib:app:teavm") include(":examples:SharedLib:app:android")