Wrong binding for Gsk.PathForeachFunc #240

Closed
opened 2025-06-19 02:34:41 +02:00 by hamza-algohary · 7 comments
hamza-algohary commented 2025-06-19 02:34:41 +02:00 (Migrated from github.com)

In Gsk.Path.foreach() one of its arguments is the callback function to be called for each iteration which is Gsk.PathForeachFunc
The callback function has the following C signature:

gboolean
(* GskPathForeachFunc) (
  GskPathOperation op,
  const graphene_point_t* pts,
  gsize n_pts,
  float weight,
  gpointer user_data
)

Meanwhile the Java binding has the following signature:

boolean run(PathOperation op , Point pts, long nPts , float weight)

which is impossible to work with since we can't treat a Java reference as a pointer/array.

Thanks in advance.

In [Gsk.Path.foreach()](https://docs.gtk.org/gsk4/method.Path.foreach.html) one of its arguments is the callback function to be called for each iteration which is [Gsk.PathForeachFunc](https://docs.gtk.org/gsk4/callback.PathForeachFunc.html) The callback function has the following C signature: ```C gboolean (* GskPathForeachFunc) ( GskPathOperation op, const graphene_point_t* pts, gsize n_pts, float weight, gpointer user_data ) ``` Meanwhile the [Java binding](http://java-gi.org/javadoc/org/gnome/gsk/PathForeachFunc.html) has the following signature: ```java boolean run(PathOperation op , Point pts, long nPts , float weight) ``` which is impossible to work with since we can't treat a Java reference as a pointer/array. Thanks in advance.
jwharm commented 2025-06-20 22:05:32 +02:00 (Migrated from github.com)

Thanks for the issue report.

The pts argument has a missing array annotation in Gsk. I'll create an MR upstream.

Thanks for the issue report. The `pts` argument has a missing array annotation in Gsk. I'll create an MR upstream.
jwharm commented 2025-06-20 22:25:41 +02:00 (Migrated from github.com)

As a temporary workaround, save the following into PathForeachFunc_fixed.java and use the static foreach() function instead of Gsk's Path.foreach().

import java.lang.FunctionalInterface;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.Set;
import javax.annotation.processing.Generated;
import org.gnome.graphene.Point;
import org.javagi.base.FunctionPointer;
import org.javagi.interop.Interop;
import org.gnome.gsk.*;

/**
 * Functional interface declaration of the {@code PathForeachFunc} callback.
 * <p>
 * @see PathForeachFunc_fixed#run
 */
@FunctionalInterface
@Generated("io.github.jwharm.JavaGI")
public interface PathForeachFunc_fixed extends FunctionPointer {
    /**
     * Calls {@code func} for every operation of the path.
     * <p>
     * Note that this may only approximate this Path, because paths can contain
     * optimizations for various specialized contours, and depending on the
     * {@code flags}, the path may be decomposed into simpler curves than the ones
     * that it contained originally.
     * <p>
     * This function serves two purposes:
     * <ul>
     * <li>When the {@code flags} allow everything, it provides access to the raw,
     *   unmodified data of the path.
     * <li>When the {@code flags} disallow certain operations, it provides
     *   an approximation of the path using just the allowed operations.
     * </ul>
     *
     * @param flags flags to pass to the foreach function
     * @param func the function to call for operations
     * @return false if {@code func} returned false, true otherwise.
     * @since 4.14
     */
    public static boolean foreach(Path path, Set<PathForeachFlags> flags, PathForeachFunc_fixed func) {
        Gsk.javagi$ensureInitialized();
        try (var _arena = Arena.ofConfined()) {
            int _result;
            try {
                _result = (int) gsk_path_foreach.invokeExact(path.handle(),
                        Interop.enumSetToInt(flags),
                        (MemorySegment) (func == null ? MemorySegment.NULL : func.toCallback(_arena)),
                        MemorySegment.NULL);
            } catch (Throwable _err) {
                throw new AssertionError(_err);
            }
            boolean _returnValue = _result != 0;
            return _returnValue;
        }
    }

    /**
     * Type of the callback to iterate through the operations of a path.
     * <p>
     * For each operation, the callback is given the {@code op} itself, the points
     * that the operation is applied to in {@code pts}, and a {@code weight} for conic
     * curves. The {@code nPts} argument is somewhat redundant, since the number
     * of points can be inferred from the operation.
     * <p>
     * Each contour of the path starts with a {@code GSKPATHMOVE} operation.
     * Closed contours end with a {@code GSKPATHCLOSE} operation.
     */
    boolean run(PathOperation op, Point[] pts, float weight);

    /**
     * The {@code upcall} method is called from native code. The parameters
     * are marshaled and {@link #run} is executed.
     */
    default int upcall(int op, MemorySegment pts, long nPts, float weight, MemorySegment userData) {
        Arena _arena = Arena.ofAuto();
        var _result = run(PathOperation.of(op), Interop.getProxyArrayFrom(pts, (int) nPts, Point.class, Point::new), weight);
        return _result ? 1 : 0;
    }

    /**
     * Creates a native function pointer to the {@link #upcall} method.
     *
     * @return the native function pointer
     */
    default MemorySegment toCallback(Arena arena) {
        FunctionDescriptor _fdesc = FunctionDescriptor.of(ValueLayout.JAVA_INT,
                ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG,
                ValueLayout.JAVA_FLOAT, ValueLayout.ADDRESS);
        MethodHandle _handle = Interop.upcallHandle(MethodHandles.lookup(), PathForeachFunc_fixed.class, _fdesc);
        return Linker.nativeLinker().upcallStub(_handle.bindTo(this), _fdesc, arena);
    }

    static final MethodHandle gsk_path_foreach = Interop.downcallHandle("gsk_path_foreach",
            FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS,
                    ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS), false);
}
As a temporary workaround, save the following into `PathForeachFunc_fixed.java` and use the static `foreach()` function instead of Gsk's `Path.foreach()`. ```java import java.lang.FunctionalInterface; import java.lang.foreign.Arena; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.util.Set; import javax.annotation.processing.Generated; import org.gnome.graphene.Point; import org.javagi.base.FunctionPointer; import org.javagi.interop.Interop; import org.gnome.gsk.*; /** * Functional interface declaration of the {@code PathForeachFunc} callback. * <p> * @see PathForeachFunc_fixed#run */ @FunctionalInterface @Generated("io.github.jwharm.JavaGI") public interface PathForeachFunc_fixed extends FunctionPointer { /** * Calls {@code func} for every operation of the path. * <p> * Note that this may only approximate this Path, because paths can contain * optimizations for various specialized contours, and depending on the * {@code flags}, the path may be decomposed into simpler curves than the ones * that it contained originally. * <p> * This function serves two purposes: * <ul> * <li>When the {@code flags} allow everything, it provides access to the raw, * unmodified data of the path. * <li>When the {@code flags} disallow certain operations, it provides * an approximation of the path using just the allowed operations. * </ul> * * @param flags flags to pass to the foreach function * @param func the function to call for operations * @return false if {@code func} returned false, true otherwise. * @since 4.14 */ public static boolean foreach(Path path, Set<PathForeachFlags> flags, PathForeachFunc_fixed func) { Gsk.javagi$ensureInitialized(); try (var _arena = Arena.ofConfined()) { int _result; try { _result = (int) gsk_path_foreach.invokeExact(path.handle(), Interop.enumSetToInt(flags), (MemorySegment) (func == null ? MemorySegment.NULL : func.toCallback(_arena)), MemorySegment.NULL); } catch (Throwable _err) { throw new AssertionError(_err); } boolean _returnValue = _result != 0; return _returnValue; } } /** * Type of the callback to iterate through the operations of a path. * <p> * For each operation, the callback is given the {@code op} itself, the points * that the operation is applied to in {@code pts}, and a {@code weight} for conic * curves. The {@code nPts} argument is somewhat redundant, since the number * of points can be inferred from the operation. * <p> * Each contour of the path starts with a {@code GSKPATHMOVE} operation. * Closed contours end with a {@code GSKPATHCLOSE} operation. */ boolean run(PathOperation op, Point[] pts, float weight); /** * The {@code upcall} method is called from native code. The parameters * are marshaled and {@link #run} is executed. */ default int upcall(int op, MemorySegment pts, long nPts, float weight, MemorySegment userData) { Arena _arena = Arena.ofAuto(); var _result = run(PathOperation.of(op), Interop.getProxyArrayFrom(pts, (int) nPts, Point.class, Point::new), weight); return _result ? 1 : 0; } /** * Creates a native function pointer to the {@link #upcall} method. * * @return the native function pointer */ default MemorySegment toCallback(Arena arena) { FunctionDescriptor _fdesc = FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.JAVA_FLOAT, ValueLayout.ADDRESS); MethodHandle _handle = Interop.upcallHandle(MethodHandles.lookup(), PathForeachFunc_fixed.class, _fdesc); return Linker.nativeLinker().upcallStub(_handle.bindTo(this), _fdesc, arena); } static final MethodHandle gsk_path_foreach = Interop.downcallHandle("gsk_path_foreach", FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS), false); } ```
hamza-algohary commented 2025-06-21 15:28:45 +02:00 (Migrated from github.com)

Thanks for the temporary solution.

Thanks for the temporary solution.
hamza-algohary commented 2025-06-21 16:50:21 +02:00 (Migrated from github.com)

The correct imports for FunctionPointer and Interop are:

import io.github.jwharm.javagi.base.FunctionPointer;
import io.github.jwharm.javagi.interop.Interop;
The correct imports for FunctionPointer and Interop are: ```java import io.github.jwharm.javagi.base.FunctionPointer; import io.github.jwharm.javagi.interop.Interop; ```
jwharm commented 2025-06-21 17:37:37 +02:00 (Migrated from github.com)

My apologies, I generated this code from java-gi’s main branch. (The io.github.jwharm.javagi package names will change in the upcoming release to org.javagi.)

My apologies, I generated this code from java-gi’s main branch. (The `io.github.jwharm.javagi` package names will change in the upcoming release to `org.javagi`.)
jwharm commented 2025-06-22 22:40:44 +02:00 (Migrated from github.com)
MR link: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8716
jwharm commented 2025-07-14 15:27:44 +02:00 (Migrated from github.com)

The MR was merged, it will be fixed in GNOME 49 (and in the subsequent java-gi release).

The MR was merged, it will be fixed in GNOME 49 (and in the subsequent java-gi release).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
java-gi/java-gi#240
No description provided.