How to properly call a callback in C?

197 views
Skip to first unread message

minirop

unread,
May 12, 2016, 3:44:46 PM5/12/16
to Wren
Hi Birdies,

I'm playing with the API to do an SDL module, but when I call the callback of "pollEvents" I segfault with the message "Null does not implement pollEvents(_)" (after printing "512", which means the callback is called only once).

here is the wren part:
var w = Window.new(800, 600, "Hello Wren")
var window_is_open = true
while (window_is_open) {
w.pollEvents {|event|
System.print(event)
}
w.clear()
w.render()
}

and the C side:
void windowPollEvents(WrenVM* vm)
{
WrenValue* fnCall = wrenMakeCallHandle(vm, "call(_)");
WrenValue* receiver = wrenGetSlotValue(vm, 1);
SDL_Event event;
while (SDL_PollEvent(&event))
{
wrenEnsureSlots(vm, 2);
wrenSetSlotValue(vm, 0, receiver);
wrenSetSlotDouble(vm, 1, event.type);
wrenCall(vm, fnCall);
}
wrenReleaseValue(vm, fnCall);
wrenReleaseValue(vm, receiver);
}

I must corrupt the stack since if I add "System.print(1)" before "pollEvents". I get "Null does not implement call(_)" (after printing "1" and "512").

Michel Hermier

unread,
May 12, 2016, 3:58:36 PM5/12/16
to wren-lang

Hi,
I didn't touched the code for a while but I think wrenCall can be called as this. It is not meant to be reentrant.

--
You received this message because you are subscribed to the Google Groups "Wren" group.
To unsubscribe from this group and stop receiving emails from it, send an email to wren-lang+...@googlegroups.com.
To post to this group, send email to wren...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/wren-lang/3dd6f320-6654-4b00-94be-24697f0fca99%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Michel Hermier

unread,
May 12, 2016, 4:00:46 PM5/12/16
to wren-lang


Le 12 mai 2016 21:58, "Michel Hermier" <michel....@gmail.com> a écrit :
>
> Hi,
> I didn't touched the code for a while but I think wrenCall can be called as this. It is not meant to be reentrant.

I meant "can't" not "can".

minirop

unread,
May 12, 2016, 4:17:26 PM5/12/16
to Wren
What happens is what I feared. After the call, the slot 0 get the "null" value since my callback doesn't return anything.
And then the program segfault in wrenEnsureSlots (but I still don't understand why, but probably a consequence of the previous thing)

How should I do then? :/

Thorbjørn Lindeijer

unread,
May 12, 2016, 4:25:05 PM5/12/16
to wren...@googlegroups.com
The error "Null does not implement pollEvents(_)" actually suggests a problem with Window.new. How does that implementation look?
 
Cheers,
Bjørn

Michel Hermier

unread,
May 12, 2016, 4:25:46 PM5/12/16
to wren-lang

I would say create a native event object and make pool event return a native event.

minirop

unread,
May 12, 2016, 4:59:25 PM5/12/16
to Wren
it simply calls "open" (which is the native function) :

void windowAllocate(WrenVM* vm)
{
 
InternalWindow * window = (InternalWindow*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(InternalWindow));
}

void windowOpen(WrenVM* vm)
{
 
InternalWindow * window = (InternalWindow*)wrenGetSlotForeign(vm, 0);
 
 
int w = (int)wrenGetSlotDouble(vm, 1);
 
int h = (int)wrenGetSlotDouble(vm, 2);
 
Uint32 flags = 0;
 
const char* title = wrenGetSlotString(vm, 3);
 
 SDL_CreateWindowAndRenderer
(w, h, flags, &window->window, &window->renderer);
 SDL_SetWindowTitle
(window->window, title);
}

As said before, wrenCall changes the slot 0 with the returned value of my callback, if I do :

w.pollEvents {|event|
 
return 42
}

the message becomes "Num does not implement pollEvents(_)".

Michel, if I don't manage to correct this, I'll simply copy the SDL function by function (and that could become ugly quite fast).

Michel Hermier

unread,
May 12, 2016, 5:11:53 PM5/12/16
to wren-lang

As I said afaik wrenCall is not meant to be used like this. From what I remember it should be used after a script was fully evaluated, as function callback.
I would say that for now do a simple implementation of SDL to have something that works. And later do something something more *powerful* either directly if fixable or by an abstraction.

--
You received this message because you are subscribed to the Google Groups "Wren" group.
To unsubscribe from this group and stop receiving emails from it, send an email to wren-lang+...@googlegroups.com.
To post to this group, send email to wren...@googlegroups.com.

Bob Nystrom

unread,
May 12, 2016, 6:07:45 PM5/12/16
to wren-lang
On Thu, May 12, 2016 at 2:11 PM, Michel Hermier <michel....@gmail.com> wrote:

As I said afaik wrenCall is not meant to be used like this. From what I remember it should be used after a script was fully evaluated, as function callback.

Yup, in general, the VM is not re-entrant. If you have Wren call your C code, your foreign function should not in turn tell Wren to execute more code by calling wrenCall() or wrenInterpret(). (Now that I think about it, Wren should probably assert() that you don't do this. If you want, file a bug, and I'll try to add that check. :) )

What you want to do is a little tricky, but, fortunately, I think fibers are powerful enough to handle this.

You can actually get the pollEvents() API you describe here to work by doing a little shenanigans with fibers, but I'd like to suggest a different API.

The pattern of doing work later by passing a callback to something which then invokes it for you makes a lot of sense in single-threaded platforms like JS. But Wren has real fibers, so a more natural API, I think, is one that is imperative and blocks, like this:

var w = Window.new(800, 600, "Hello Wren")
var window_is_open = true
while (window_is_open) {
  var event = w.waitForEvent()
  System.print(event)
  w.clear()
  w.render()
}

To make the above API work, you can do something like this:

class Window {
  waitForEvent() {
    // TODO: Handle case where user calls waitForEvent() from more than one
    // fiber or window.
    // Capture the current fiber.
    __waitingFiber = Fiber.current

    // TODO: Run other fibers...

    // Suspend the Wren VM and return control to C.
    return Fiber.suspend() // 1
  }

  foreign static receiveEvent_(event) {
    // Discard event if no one wants it, I guess...
    if (__waitingFiber == null) return

    // Now resume the fiber we suspended earlier and pass it the event. This
    // causes the above call to Fiber.suspend() to magically resume and return
    // event.
    __waitingFiber.transfer(event)
  }
}

Then in your C code, you do something like:

    void windowPollEvents(WrenVM* vm)
    {
      wrenEnsureSlots(1);
      WrenValue* windowClass = wrenGetVariable(vm, "Window", 0);
      WrenValue* receiveEvent = wrenMakeCallHandle(vm, "receiveEvent_(_)");
      
      SDL_Event event;
      while (SDL_PollEvent(&event))
      {
        wrenEnsureSlots(vm, 2);
        wrenSetSlotValue(vm, 0, windowClass);
        wrenSetSlotDouble(vm, 1, event.type);
        wrenCall(vm, receiveEvent);
      }
      
      wrenReleaseValue(vm, fnCall);
      wrenReleaseValue(vm, receiver);
    }

This should work. The only limitation with the above code is that you can't really do anything else while you're waiting for an event to come in. When you call waitForEvent(), you switch back to C and the VM just sits there motionless until an event comes in.

If you want to be able to run other fibers concurrently while you're waiting for an event, you can do that by adding a little sauce where it says "TODO: Run other fibers...". The basic idea is you provide an API to park or sleep another fiber. When you're about to wait for events, you give those sleeping fibers a chance to run.

This is basically how the scheduler in the Wren CLI works. It sits on top of libuv, which is single-threaded and callback-based, but provides a blocking fiber-based API on top of that. Any time you do an IO operation, that blocks the current fiber and then resumes the next fiber that's able to run. If none are, it waits until libuv invokes a callback which then resumes the pending fiber in the VM.

I know it's a lot to wrap your head around, but I think it's pretty powerful what you can express.

Cheers!

– bob

minirop

unread,
May 13, 2016, 3:34:25 PM5/13/16
to Wren
Thanks, I'll try to read and understand the concepts but for now, I'm hacking inside the VM and doing this (ugly, but it's not production code, so OK for now) :

#include <wren_vm.h>

void windowPollEvents(WrenVM* vm)
{

 
WrenValue* receiver = wrenGetSlotValue(vm, 1);

 
ObjInstance* inst = AS_INSTANCE(receiver->value);
 
 SDL_Event
event;
 
if (SDL_PollEvent(&event))
 
{
  inst
->fields[0] = NUM_VAL(42);
 
  wrenSetSlotBool
(vm, 0, true);
 
}
 
else
 
{
  wrenSetSlotBool
(vm, 0, false);
 
}
 
 wrenReleaseValue
(vm, receiver);
}
Reply all
Reply to author
Forward
Message has been deleted
0 new messages