|
CX Framework
Cross-platform C utility framework
|
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) |
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.
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:
A closure that captures nothing, or a call that passes nothing, must say so with stvNone rather than leaving the argument list empty.
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:
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().
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:
A closure with a destroy function cannot be copied with closureClone().
| #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.
| cls | Closure to call |
| ... | One or more stvar arguments to pass (stvNone for none) |
| #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.
| sigtype | The same function pointer type the closure was created with |
| cls | Closure to call |
| ... | Arguments after cvars, as declared by sigtype (at least one) |
Example:
| #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.
| func | Closure function to call |
| ... | One or more stvar arguments to capture (stvNone for none) |
| #define closureCreateAs | ( | sigtype, | |
| func, | |||
| ... | |||
| ) |
closure closureCreateAs(sigtype, func, ...)
Create a new typed closure with captured variables.
| sigtype | Function pointer type the closure is called through; its first parameter must be stvlist *cvars |
| func | Function to call; must match sigtype exactly |
| ... | One or more stvar arguments to capture (stvNone for none) |
Example:
| typedef void(* closureDestroyFunc) (stvlist *cvars) |
Generic closure function signature
Generic closure functions receive two argument lists:
| cvars | List of captured variables from closureCreate() |
| args | List of arguments from closureCall() |
| 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.
| cls | Closure to clone (NULL returns NULL) |
| 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.
| cls | Pointer to closure to destroy (may be NULL or point to NULL) |
| 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.
| cls | Closure to attach the function to |
| destroy | Function to run, or NULL for none |