COMMUNITY

More Rosetta Code, and a question about scope and GC

haxe-neko

(Brian Tiffin) #1

Hello, Haxer world

Added a few more Neko entries to Rosetta Code. In particular for this post, Hello World/Graphical.

Wrote a small binding to libagar. Agar is an awesome GUI and all-sorts Core framework, written in C. The Hello World/Graphical sample was using a Button for the Goodbye, World! message, but I changed it to a Label at the last minute to avoid the issue I’m about to raise. rosettacode.org no longer allows file uploads, so there is no screen capture there, but the output is a simple:

hello-graphical-neko-agar

http://rosettacode.org/wiki/Hello_world/Graphical#Neko

The issue; for event callbacks, Agar uses C function pointers. I’d like nekoagar to use Neko function callbacks. That works, but marshaling from C types to Neko value requires a little hackety hack hack. The Neko primitive passes a Neko function value. That is converted to an abstract in the nekoagar marshaling layer, and then Agar can easily react to events using Neko functions. But… the big but…

These values are passed as arguments. The event callback needs the address of the function, the Neko function (which is in a C FFI value) which is passed around as an argument. I take the address of the Neko function just fine, and the code reacts as expected using the Neko C FFI and libagar. What that conversion layer needs though is a permanent allocation for the Neko value that is referenced by the Agar callback handler. I don’t know enough about the garbage collector, but what I did in the initial trials was the equivalent of returning the address of a call frame variable. A no-no.

For now I’ve just created a static array in the C marshaling layer to keep the Neko values permanent and pass the address of the C array element to libagar AG_SetEvent which is referenced to dispatch the C layer callback from a Neko function.

Is there a better way in Neko C FFI to allocate some RAM and keep it, possibly for the life of the program, or until rundown of the GUI. The C array of value works, but it’s not overly clean and requires guessing how many event callbacks may be active in an application. Another no-no.

You won’t see any of that code on the Rosetta Code page, as I pulled it out before posting the minimal sample using a Label. Not nice demonstrating code that can randomly break hard as memory regions change.

Just looking for a hint on the best way for C FFI layers to allocate RAM that isn’t at risk from the GC once a function that sets a callback goes out of scope.

And if that hint is Haxe source, that’s ok, as -D neko-source can be used to figure out how it is done in native Neko if needs be.

Have good, make well, and thanks

P.S. What is the slang term for Haxe programmers? Hello, Haxers? Hello, Haxenites? Hello, ??? :slight_smile:

And for completeness, if anyone takes up the question:

/*
  Author: Brian Tiffin
  MIT license

  Date started: October, 2018
  Modified: 2018-10-20/15:31-0400 btiffin

  Tectonics:
    gcc -shared -fPIC -o nekoagar.ndll nekoagar.c `agar-config --cflags --libs`
*/

/* Neko primitives for libAgar http://www.libagar.org */
#include <stdio.h>
#include <neko.h>
#include <agar/core.h>
#include <agar/gui.h>

#define val_widget(v)       ((AG_Widget *)val_data(v))
DEFINE_KIND(k_agar_widget);

/* Initialize Agar Core given appname and flags */
value agar_init_core(value appname, value flags) {
#ifdef DEBUG
  if (!val_is_null(appname))  val_check(appname, string);
  val_check(flags, int);
#endif
  if (AG_InitCore(val_string(appname), val_int(flags)) == -1)  return alloc_bool(0);
  return alloc_bool(1);
}
DEFINE_PRIM(agar_init_core, 2);

/* Initialize Agar GUI given graphic engine driver */
value agar_init_gui(value driver) {
#ifdef DEBUG
  if (!val_is_null(driver))  val_check(driver, string);
#endif
  if (AG_InitGraphics(val_string(driver)) == -1)  return alloc_bool(0);
  AG_BindStdGlobalKeys();
  return alloc_bool(1);
}
DEFINE_PRIM(agar_init_gui, 1);

/* Initialize Agar given appname, flags and GUI driver */
value agar_init(value appname, value flags, value driver) {
#ifdef DEBUG
  if (!val_is_null(appname))  val_check(appname, string);
  val_check(flags, int);
  if (!val_is_null(driver))  val_check(driver, string);
#endif
  if (!val_bool(agar_init_core(appname, flags)))  return alloc_bool(0);
  if (!val_bool(agar_init_gui(driver)))  return alloc_bool(0);
  return alloc_bool(1);
}
DEFINE_PRIM(agar_init, 3);


/* end the Agar event loop on window-close */
void rundown(AG_Event *event) {
  AG_Terminate(0);
}

/*
 Event middleware, C handler that will callback to Neko
 Event expects an Agar %p event argument that points to the Neko function value

 Early development stub - assume no arguments to Neko function
*/
void interstitial(AG_Event *event) {
  value *fp = AG_PTR(1);
  value cb = *fp;
  val_call0(cb);
}

/* Set an event */
value agar_set_event(value obj, value name, value handler) {
#ifdef DEBUG
  val_check_kind(obj, k_agar_widget);
  val_check(name, string);
  val_check_function(handler, 0);
#endif
  AG_SetEvent(val_widget(obj), val_string(name), interstitial, "%p", &handler);
}


/* Create an Agar window, given UInt flags (which might use 32 bits...) */
value agar_window(value flags) {
#ifdef DEBUG
  val_check(flags, int);
#endif
  AG_Window *win;
  win = AG_WindowNew(val_int(flags));
  AG_SetEvent(win, "window-close", rundown, "%p", win);

  if ( win == NULL) return alloc_bool(0);
  return alloc_abstract(k_agar_widget, win);
}
DEFINE_PRIM(agar_window, 1);


/* Show a window */
value agar_window_show(value w) {
  AG_Window *win;

#ifdef DEBUG
  val_check_kind(w, k_agar_widget);
#endif
  win = (AG_Window *)val_widget(w);
  AG_WindowShow(win);
  return alloc_bool(1);
}
DEFINE_PRIM(agar_window_show, 1);


/* Hide a window */
value agar_window_hide(value w) {
  AG_Window *win;

#ifdef DEBUG
  val_check_kind(w, k_agar_widget);
#endif
  win = (AG_Window *)val_widget(w);
  AG_WindowHide(win);
  return alloc_bool(0);
}
DEFINE_PRIM(agar_window_hide, 1);


/* New box */
value agar_box(value parent, value type, value flags) {
  AG_Box *b;

#ifdef DEBUG
  val_check_kind(parent, k_agar_widget);
#endif
  b = AG_BoxNew(val_widget(parent), val_int(type), val_int(flags));
  return alloc_abstract(k_agar_widget, b);
} 
DEFINE_PRIM(agar_box, 3);

/* New label */
value agar_label(value parent, value flags, value text) {
  AG_Label *lw;

#ifdef DEBUG
  val_check_kind(parent, k_agar_widget);
#endif
  lw = AG_LabelNewS(val_widget(parent), val_int(flags), val_string(text));
  return alloc_abstract(k_agar_widget, lw);
} 
DEFINE_PRIM(agar_label, 3);


/* an_copy_value to allow safe references */
#define AN_MALLOC_SLOT_MAX 32
static int an_malloc_slot = 0;
static value an_mallocs[AN_MALLOC_SLOT_MAX];

value an_copy_value(value v) {
  if (an_malloc_slot >= AN_MALLOC_SLOT_MAX - 1) {
    fprintf(stderr, "No available an_malloc_slots\n");
    neko_error();
  }
  an_mallocs[an_malloc_slot] = v;
  
  return alloc_int(an_malloc_slot++);
}

/* New button with parent, flags, a string label and a Neko callback */
value agar_button_news(value parent, value flags, value label, value f) {
  AG_Button *b;
  value tmp;

#ifdef DEBUG
  val_check_kind(parent, k_agar_widget);
#endif
  b = AG_ButtonNewS(val_widget(parent), val_int(flags), val_string(label));

  /* a pointer to a Neko value of function */
  /* Using an array of globals to keep the function values */
  /* There is a reference made here, need a keep forver allocator... */
  tmp = an_copy_value(f);
  AG_SetEvent(b, "button-pushed", interstitial, "%p", &an_mallocs[val_int(tmp)]);

  return alloc_abstract(k_agar_widget, b);
}
DEFINE_PRIM(agar_button_news, 4);


/* Event Loop */
value agar_eventloop(void) {
  int rc;
  rc = AG_EventLoop();
  return alloc_int(rc);
}
DEFINE_PRIM(agar_eventloop, 0);

The trickery is in interstitial, using pointers in the Agar event callback to keep the Neko Function value, and dispatch the actual Neko code when the event is triggered from C code. Need those Neko argument values to not move, or be reaped.