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")