Swi-prolog bindings to SDL

Hello all,
I have started to make swi-prolog bindings to the C multimedia library SDL: GitHub - kwon-young/sdlpl: SWI-Prolog bindings to SDL
For now, I have used the C Foreign Language Interface documented here: SWI-Prolog -- Foreign Language Interface
Here is my experience and a few questions for those of you that are more knowledgeable than me:

  • Should I be using the C++ interface instead ? @peter.ludemann mentionned to me privately that using C++ simplifies error handling. I did not have the time to read the C++ interface documentation yet…
  • How should I hande foreign data ? SDL functions can returns handles to things like surfaces or textures that should be freed in order. setup_call_cleanup is great when knowing the lifetime of the handle beforehand (like the handle for a window) but not great when dealing with textures that can be freed anytime. I tried to use Blobs but I don’t think we can impose freeing order on them (like all image textures should be freed before doing image_quit.
  • How should I handle C flags arguments ? For now, I’m converting prolog atoms to integers and ORing them in prolog for readability. Is there a better solution ?

As a more general question, is anyboy interested on colaborating on this project ?
How about we write small reference games with it like a pong or snake ?
Or a small immediate mode gui framework for visualising things like clpfd constraints labelling (sudoku or n-queens) ?
Or a gui framework like QML (in the C++ Qt) world where we can declaratively specifiy the gui in a dict ?

1 Like

Regarding cleanup, maybe you can use state blobs that carry everything, including the order of allocation, and then just define predicates that work with those state blobs, rather than have everything as separate blobs.
That way you retain full control over the cleanup.

I recommend using SWI-cpp2.h (as documented in SWI-Prolog -- A C++ interface to SWI-Prolog (Version 2)), even if you’re interfacing to C code. The following assumes that you’re writing foreign predicates, which is the most common use case (there are some more complex situations, such as calling Prolog from C/C++; SWI-cpp2.h is also better for this but some things require more care, especially C++ exceptions)

Off the top of my head, these are the advantages of using the C++ API (I’ll edit this list if more things occur to me in the future):

  • It’s efficient – the C++ wrapper classes (PlTerm, PlAtom, etc.) are the same size as the underlying term_t, atom_t, etc. types, which can be efficiently accessed using unwrap() if needed.
  • Most of the C functions that manipulate char* data have corresponding C++ methods that use std::string, which simplifies memory management.
  • Simplified error handling – most of the PL_*() functions (in SWI-Prolog.h) have corresponding Plx_*() functions that check for an error and throw a C++ exception if needed.
    • The PREDICATE declaration takes care of handling the C++ exception and turning it into a Prolog error.
  • Simplified unification failure handling – you can throw PlFail or wrap a call with PlCheckFail() to short-circuit when unification fails.
  • Convenience functions for creating an error term and raising an error in Prolog.
  • You can always get at the underling term_t, atom_t, etc. by using the unwrap() method, so everything in the SWI-Prolog.h API is available if needed.
  • It’s easy to create a “blob”, which is probably how you should wrap foreign data: SWI-Prolog -- Blobs (the C++ API for blobs isn’t completely general, but it should cover 99% of cases).
  • You can take advantage of C++ features such as “smart pointers” (e.g. std::unique_ptr) and std::string.
  • The code for non-deterministic foreign predicates and the “context” is simpler and less error-prone – there is sample code in the documentation and in the test cases.
  • If you’re handling connections to a file system or database, there is a utility module for mapping atoms to Prolog terms (including blobs).

There are some negatives:

  • In some situations, it’s slightly less efficient than the C API.
  • C++ compilation tends to be slower than C compilation and compilation error messages can sometimes be difficult to understand.
  • If you’re a C programmer and not familiar with C++, there can be some tricky bits. I can do code reviews or give advice. :slight_smile:
  • The C++ memory model doesn’t fit well with the Prolog memory model (this is also true with C, of course), so sometimes the code becomes more complicated because of that. However, there are convenience classes and functions that help deal with the mismatch (PlTermScoped, unify_blob(), PlControl).

Possibly this can be handled using C++ std::shared_ptr, which is essentially a reference-counted pointer, so you can “chain” the structures and their pointers. Depending on the situation, std::unique_ptr could also be used if there is “single ownership”.

But in general, blobs can be freed / garbage-collected at any time – would it make sense to put all your data into one large “blob” whose contents you free in proper order?

Perhaps you can use PL_scan_options() – there’s sample code in the documentation. Alternatively, there is ample code for handling a list of options. (If you’re talking about command-line options, there is also example code.)

You both proposed the same idea but I don’t understand how would this work.
With SDL, you can create resources like windows, renderers, surfaces or textures using function calls and SDL basically gives you an opaque pointer as handle to the resource.
These handles have to be freed in order.
Could one of you show an example of how I could pack all this into a single blob ?

My c is incredibly rusty, so I can’t just quickly write a n actual example.

But the idea would roughly be as follows.

create a state blob object

This would be your global entrypoint. You’d define predicates like

sdl_new_context(Ctx, ...)
sdl_free_context(Ctx, ...)

(though you can make garbage collection handle the free too, or do both).

Ctx would be your state object, returned as a blob in prolog.

create methods on this state blob object

things like

sdl_new_window(Ctx, WindowHandle, ...)
sdl_new_bitmap(Ctx, BitmapHandle, ...)

and

sdl_free_window(Ctx, WindowHandle, ...)
sdl_free_bitmap(Ctx, BitmapHandle, ...)

I don’t actually know what sdl does, so hopefully those are suggestive.

The handles could be anything, you could just return an atom, or a string, or whatever. it would be the responsibility of the ctx object to actually resolve them.

doing things with objects

sdl_window_size(Ctx, WindowHandle, 800,600)
sdl_window_show(Ctx, WindowHandle)

On the C/C++ side, you’d implement these predicates by keeping around a mapping between your handles and the actual objects, then doing the internal appropriate c/c++ function on that.

[edit]
I realize I left some things undiscussed. The point was making sure that freeing happens in order.
Essentially, when doing something like
sdl_free_window(Ctx, WindowHandle)
You’d have an opportunity to make sure that the window is actually in a position to be freed directly, or if other stuff needs to happen first. Then you could either error (making a prolog library fix their mistake), or cascade the free. You are able to do this for each free method cause you always have the entire context available to you through the context object.

Note that if there’s only one global context ever, you don’t even need a ctx object, and you could directly work with predicates passing around handles. This is how XPCE does it.

I noticed that SDL has Python bindings, so you might be better off just using those via SWI-Prolog’s Python interface. If not using Python, the question is: how does Python handle the problem of garbage collection? The Cpython implementation uses reference counting plus a garbage collector for circular references. This probably could be emulated by C++ shared_ptr, which is a reference counter (whether you could do this with a custom deleter or would need to implement something similar, I don’t know.

Another possibility is to have a blob for every SDL object, using PlBlob::register_ref() and PlBlob::unregister_ref (which use PL_register_atom() and PL_unregister_atom()) to maintain reference counts. You’d have to ask @jan if it’s guaranteed that the blob is deleted when the reference count goes to zero, and whether the deletions are done in order.

I don’t have time to learn the SDL API, so perhaps some sample code showing how the objects are created and destroyed would help me to suggest a solution.

This does not happen immediately when refcount becomes zero. There’s some conditions (such as use on stacks) where an atom still needs to be counted, so it is up to garbage collection to make the final call here.
That also means you won’t have guarantees about order of frees if you do it that way. The garbage collection will just see a bunch of atoms/blobs as ‘freeable’ and act accordingly.
Maybe some order can be forced here, where one type is always freed before the other, but that’s asking for trouble. Better to make the free order explicit in your code.

That said, returning individual objects as blobs can help you, cause it gives you a callback when the object is ‘freed’, which you are free to implement in any way you like, including doing some bookkeeping in a context object (and freeing/not freeing things according to that bookkeeping).

Thank you all for your examples and advices on using a state blob. I think I understand now.
For now, I’m just going to pass pointers around and let future me decide what to do
@peter.ludemann What is the canonical way to return a compound from C++ to prolog ?
I want to use compound term to represent input events from SDL, things like quit(Timestamp) or mousemotion(Timestamp, X, Y).
Using a dict here would be ideal, but again, don’t know if this is possible ?

If you don’t care about the contents, use a blob. You can also provide “access” predicates for getting at the values in the blob, but that can become tedious to write. (There isn’t a good example in test_cpp.cpp, but I can easily write an example if you wish.)

If you do care about the contents, a dict would probably be a good way of handling a large structure; however, the only public function for dicts is PL_get_dict_key() (which is also available in SWI-cpp2.h as Plx_get_dict_key(). Perhaps this is an oversight, @jan?

In the absence of direct manipulation of dicts, you can easily create a term with whatever arity you wish and return that to Prolog, which converts it to a dict, e.g.

SDL_complex_thing(c(A,B,C,D)),
Dict = c{a:A,b:B,c:C,d:D}

where SDL_complex_thing/1 is a foreign predicate that’s something like this (untested code):

PREDICATE(SDL_complex_thing,1)
{ auto result = call_SDL_thing(...);
  return A1.unify_term(PlCompound("c",
    PlTermv(PlTerm(PlTerm_integer(result.a),
                   PlTerm_string(result.b),
                   PlTerm_atom(PlAtom(result.c)),
                   PlTerm_float(result.d)))));
}

More complex structures can be built up, using Plterm_term().

One other way of returning a value is to build a list of pairs, which can be used as-is or converted to a dict – the sample code for predicate square_roots/2 (which generates the first n square routes) can get you started on that:

PREDICATE(square_roots, 2)
{ int end = A1.as_int();
  PlTerm_tail list(A2);
  for(int i=0; i<=end; i++)
    PlCheckFail(list.append(PlTerm_float(sqrt(double(i)))));
  return list.close();
}

There are more functions.
PL_put_dict can create a dict. PL_is_dict can tell you that something is a dict.

Edit: for vanity’s sake, this is how the rust bindings wrap dictionaries: swipl::dict - Rust and Term in swipl::term - Rust

Oops – I missed PL_put_dict(). There’s also PL_for_dict() (undocumented), for looping over keys.
I should probably create a C++ class for dicts; but for now, the Plx_*() wrappers will help a bit.

@peter.ludemann Thank you a lot for all your help and examples.

Is there a reason why the PlTermv function can only take 5 arguments ?
I needed to make compounds with 8/9 arguments which mean I had to do an ugly argument by argument initialisation…

There’s no reason for the limit, just a small matter of copy&paste&typing. :slight_smile:
[There might be some limitations with the underlying PL_new_term_refs(), but it’s certainly safe up to 10.]
I can quickly make a PR for that (but it’ll take a bit of time to get into the release).
There should be a more generic mechanism for PlTermv, but that’ll require more thought and possibly consultation with @jan.

Do you build your own version of swipl? If so, I can provide a patch.

The C interface has the variadic function PL_unify_term(). Is such a design at all an option for C++?

Well, there’s nothing pressing here :slight_smile: If you make a PR to build compound term up to 10 terms, I’ll be happy use that when that hits fedora 40.

I’ve use that in my C interface and it was quite nice indeed.

C++ is a superset of C, so a variadic form of PlTermv is possible. I’d have to study the code a bit to decide whether it can more-or-less copy PL_unify_term() (actually: PL_unify_termv() in pl-fli.c, which is nearly 250 lines of code) or requires additional work.

But a better alternative might be to use std::vector (and using {...} in the call), which would imply changing PlCompound and PlTermv. (Some additional magic can be done using overloaded constructors, to avoid the need for PL_ATOM, PL_INTEGER, etc.)

What you can do if blob A must be freed before blob B is to add the handle for blob A to the struct of blob B and use PL_register_atom(B) to let the system know. Now A will be freed first, which should call PL_unregister_atom(B). That should make B candidate for collection in a next run of the garbage collector.

The alternative is to set flags in the unregister() callbacks if you cannot free immediately. So, in the above, if A is not freed and we can an unregister for B, we just mark this in B. Now if A is freed we also check whether B is flagged and then free it.