0
votes

I saw a few other posts, but they were all in TCL and I'm looking to do this in my embedded interpreter using C++. The problem I'm having is that I need my own event loop so that I can read off the network, some where's where I can check a socket. I saw Tcl_SetMainLoop() but not sure how it works.

My app is structured like the following.

int Tcl_AppInit(Tcl_Interp *interp)
{

    if (Tcl_Init(interp) == TCL_ERROR) 
        return TCL_ERROR;

    
    if (MyTcl_Init(interp) == TCL_ERROR) 
        return TCL_ERROR;

    return TCL_OK;
}

int main(int argc, char* argv[])
{
    //...

    Tcl_Main(argc, argv, Tcl_AppInit);
    
    /* Replaced Tcl_Main for this, didn't work.
    Tcl_SetMainLoop([]() {
        Tcl_DoOneEvent(0);
    });
    */
}

I do not use Tk. Any thoughts on how to set a custom event loop?

1
See tcl.tk/man/tcl8.6/TclLib/Notifier.htm#M13 for relevant functions.Shawn
Or do your socket reading through tcl's event loop.Shawn
(I think Tcl_SetMainLoop() is supposed to be called from inside the AppInit function)Shawn

1 Answers

1
votes

The classic example of how to an event loop in Tcl is the vwait command. The core of that is this code:

    done = 0;
    foundEvent = 1;
    while (!done && foundEvent) {
        foundEvent = Tcl_DoOneEvent(TCL_ALL_EVENTS);
        if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) {
            break;
        }
        if (Tcl_LimitExceeded(interp)) {
            Tcl_ResetResult(interp);
            Tcl_SetObjResult(interp, Tcl_NewStringObj("limit exceeded", -1));
            break;
        }
    }

The done variable is set by a callback when the end-loop-triggering event happens (a write on a variable), and the clauses with cancellation and limit management can probably be omitted in your own code. The cut-down bare-bones version is:

    done = 0;
    foundEvent = 1;
    while (!done && foundEvent) {
        foundEvent = Tcl_DoOneEvent(TCL_ALL_EVENTS);
    }

Yes, it delegates most of its work to Tcl_DoOneEvent (which is part of the notifier layer). If you want to plug your fancy socket into that, the easiest way is to write your own event handler and install it with Tcl_CreateFileHandler or Tcl_CreateChannelHandler; probably the former, assuming you're not on Windows (as it relies on the POSIX concept of a file descriptor) and on Windows you need to do the work to make a channel type (because the underlying notifier system works a little differently on that platform under the hood). Once you do that, you can use the standard event loop; your custom handler (in C or C++) will be called at the right time. (The ClientData arguments are really just an arbitrary pointer that will be passed uninterpreted through Tcl to your callback; you'll probably cast that back to a pointer to the real object type as the first thing you do in the callback; that's what everyone else does.)


It's possible to install your own low-level event handling engine with Tcl_SetNotifier — call it very early if you want to do that — but it is usually a bad idea. In particular, you don't need a custom event loop to just handle a custom socket type. A better use for a custom event loop is integrating Tk with some other GUI toolkit, but that's a far more complex use case!