CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches

Macros

#define closureCreate(func, ...)    _closureCreate(func, count_macro_args(__VA_ARGS__), (stvar[]) { __VA_ARGS__ })
 
#define closureCall(cls, ...)    _closureCall(cls, count_macro_args(__VA_ARGS__), (stvar[]) { __VA_ARGS__ })
 
#define closureCreateAs(sigtype, func, ...)
 
#define closureCallAs(sigtype, cls, ...)    ((sigtype)_closureFuncAs((cls), #sigtype))(_closureCvars((cls), &(stvlist) { 0 }), __VA_ARGS__)
 

Typedefs

typedef struct closure_ref * closure
 Opaque handle to a closure.
 
typedef bool(* closureFunc) (stvlist *cvars, stvlist *args)
 
typedef void(* closureDestroyFunc) (stvlist *cvars)
 

Functions

void closureSetDestroy (closure cls, closureDestroyFunc destroy)
 
closure closureClone (closure cls)
 
void closureDestroy (closure *cls)
 

Detailed Description

Function closures that capture environment variables for deferred execution.

A closure packages a function pointer with a list of captured variables (cvars) that act as the function's environment. The captured variables are copied into the closure when it is created and destroyed along with it, so anything a callback needs – strings, object references, plain values – lives exactly as long as the callback does, with nothing to free by hand.

Use a closure when a callback needs state that must stay alive for as long as the callback is registered and be cleaned up with it. A plain function pointer and context pointer is still the better fit for a callback that only runs during the call that receives it (a sort comparator, a parse visitor), or for a table of several callbacks sharing one context.

Generic closures

A generic closure has the signature bool fn(stvlist *cvars, stvlist *args): captured variables and call-time arguments both arrive as variant lists. This suits callbacks where the caller and callee share no function type, and callbacks that run rarely enough that packing the arguments into variants does not matter.

Captures are easiest to read back by name. Tag them with stvark() and fetch them with stvlFindVal(), so their order does not matter:

static bool onDone(stvlist *cvars, stvlist *args) {
strref path = stvlFindVal(cvars, path, strref);
int32 tries = stvlFindVal(cvars, tries, int32);
// ...
return true;
}
closure cls = closureCreate(onDone, stvark(path, string, filename), stvark(tries, int32, 3));
void closureDestroy(closure *cls)
#define closureCall(cls,...)
Definition closure.h:147
#define closureCreate(func,...)
Definition closure.h:135
#define stvark(key, typen, val)
Definition stvar.h:204
#define stvNone
Definition stvar.h:178
#define stvlFindVal(list, key, type)
Definition stvar.h:1017

A closure that captures nothing, or a call that passes nothing, must say so with stvNone rather than leaving the argument list empty.

Typed closures

A typed closure is called through an ordinary C function type instead, so it can return any type and take raw pointers and sizes as arguments, with no variant packing on the call. Use one for a callback that runs often, or that needs to return something other than bool.

Its function type takes the captured variables first, followed by at least one argument of its own:

typedef size_t (*MyReadFunc)(stvlist *cvars, uint8 *buf, size_t sz);
static size_t readFile(stvlist *cvars, uint8 *buf, size_t sz) {
File *file = stvlAtObj(cvars, 0, File);
size_t didread = 0;
fileRead(file, buf, sz, &didread);
return didread;
}
closure cls = closureCreateAs(MyReadFunc, readFile, stvar(object, file));
size_t n = closureCallAs(MyReadFunc, cls, buf, sizeof(buf));
#define closureCallAs(sigtype, cls,...)
Definition closure.h:195
#define closureCreateAs(sigtype, func,...)
Definition closure.h:168
#define fileRead(self, buf, sz, bytesread)
Definition fileobj.h:116
#define stvar(typen, val)
Definition stvar.h:162
#define stvlAtObj(list, idx, class)
Definition stvar.h:885
Definition fileobj.h:59

The compiler checks that the function matches the type named in closureCreateAs(). The type named in closureCallAs() must be the same one; debug builds check this on every call. Call a typed closure only with closureCallAs(), and a generic one only with closureCall().

Captures can be read back by position with stvlAt(), stvlAtPtr() and stvlAtObj(), which is cheap enough for a callback that runs constantly, or by name with stvlFindVal(), stvlFindPtr() and stvlFindObj().

State that is not a capture

Captures are copied into the closure and read back as values, so they cannot hold state the callback changes as it runs, and copying a large value just to capture it can be wasteful. For that, allocate the state yourself, capture a pointer to it, and give the closure a destroy function to free it. The destroy function runs once when the closure is destroyed, with the captures still readable, much like a class destructor:

static void readerDestroy(stvlist *cvars) {
ReaderState *st = stvlAtPtr(cvars, 0);
bufDestroy(&st->buf);
xaFree(st);
}
ReaderState *st = xaAllocStruct(ReaderState, XA_Zero);
closure cls = closureCreateAs(MyReadFunc, readChunk, stvar(ptr, st));
closureSetDestroy(cls, readerDestroy);
void bufDestroy(Buffer *buf)
void closureSetDestroy(closure cls, closureDestroyFunc destroy)
#define stvlAtPtr(list, idx)
Definition stvar.h:874
void xaFree(void *ptr)
#define xaAllocStruct(typn,...)
Definition xalloc.h:213
#define XA_Zero
Zero-fills returned memory.
Definition xalloc.h:91

A closure with a destroy function cannot be copied with closureClone().

Macro Definition Documentation

◆ closureCall

#define closureCall (   cls,
  ... 
)     _closureCall(cls, count_macro_args(__VA_ARGS__), (stvar[]) { __VA_ARGS__ })

bool closureCall(closure cls, ...)

Call a generic closure with the given arguments.

Parameters
clsClosure to call
...One or more stvar arguments to pass (stvNone for none)
Returns
Return value from the closure function

Definition at line 147 of file closure.h.

◆ closureCallAs

#define closureCallAs (   sigtype,
  cls,
  ... 
)     ((sigtype)_closureFuncAs((cls), #sigtype))(_closureCvars((cls), &(stvlist) { 0 }), __VA_ARGS__)

rettype closureCallAs(sigtype, closure cls, ...)

Call a typed closure.

cls is evaluated more than once, so pass a plain variable or field rather than an expression with side effects.

Parameters
sigtypeThe same function pointer type the closure was created with
clsClosure to call
...Arguments after cvars, as declared by sigtype (at least one)
Returns
Whatever the closure function returns

Example:

closureCallAs(ProgressFunc, cls, done, total);

Definition at line 195 of file closure.h.

◆ closureCreate

#define closureCreate (   func,
  ... 
)     _closureCreate(func, count_macro_args(__VA_ARGS__), (stvar[]) { __VA_ARGS__ })

closure closureCreate(closureFunc func, ...)

Create a new generic closure with captured variables.

The variables are copied, so the originals can be safely destroyed.

Parameters
funcClosure function to call
...One or more stvar arguments to capture (stvNone for none)
Returns
New closure object (must be freed with closureDestroy())

Definition at line 135 of file closure.h.

◆ closureCreateAs

#define closureCreateAs (   sigtype,
  func,
  ... 
)
Value:
_closureCreateAs((void (*)(void))(1 ? (func) : (sigtype)0), \
#sigtype, \
count_macro_args(__VA_ARGS__), \
(stvar[]) { __VA_ARGS__ })
#define count_macro_args(...)
Definition args.h:81

closure closureCreateAs(sigtype, func, ...)

Create a new typed closure with captured variables.

Parameters
sigtypeFunction pointer type the closure is called through; its first parameter must be stvlist *cvars
funcFunction to call; must match sigtype exactly
...One or more stvar arguments to capture (stvNone for none)
Returns
New closure object (must be freed with closureDestroy())

Example:

typedef void (*ProgressFunc)(stvlist *cvars, int64 done, int64 total);
closure cls = closureCreateAs(ProgressFunc, onProgress, stvar(object, window));

Definition at line 168 of file closure.h.

Typedef Documentation

◆ closureDestroyFunc

typedef void(* closureDestroyFunc) (stvlist *cvars)

Destroy function signature

Parameters
cvarsCaptured variables of the closure being destroyed

Definition at line 201 of file closure.h.

◆ closureFunc

typedef bool(* closureFunc) (stvlist *cvars, stvlist *args)

Generic closure function signature

Generic closure functions receive two argument lists:

  • cvars: Captured variables provided when the closure was created
  • args: Arguments provided when the closure is called
Parameters
cvarsList of captured variables from closureCreate()
argsList of arguments from closureCall()
Returns
true on success, false on failure

Definition at line 122 of file closure.h.

Function Documentation

◆ closureClone()

closure closureClone ( closure  cls)

Create a copy of a closure

Creates a new closure with the same function and captured variables. The captured variables are deep-copied, so modifications to the original won't affect the clone.

Parameters
clsClosure to clone (NULL returns NULL)
Returns
New independent closure (must be freed with closureDestroy()), or NULL for a closure with a destroy function, which cannot be copied

◆ closureDestroy()

void closureDestroy ( closure *  cls)

Destroy a closure and release its resources

Frees the closure and all captured variables. Sets the closure pointer to NULL. Does nothing if the closure was never created.

Parameters
clsPointer to closure to destroy (may be NULL or point to NULL)

◆ closureSetDestroy()

void closureSetDestroy ( closure  cls,
closureDestroyFunc  destroy 
)

Sets a function to run when the closure is destroyed

Use it to free state the closure owns that is not one of its captures. It runs once, from closureDestroy(), before the captured variables are destroyed.

Parameters
clsClosure to attach the function to
destroyFunction to run, or NULL for none