/*
* notcurses_native_shim.c — high-throughput batched primitives that
* are too slow to express call-per-cell over the Raku NativeCall
* boundary.
*
* The headline primitive is `notcurses_native_copy_cells`, a direct
* port of Selkie::Widget::ViewportedCardList's per-cell read+write
* loop. ViewportedCardList copies a slice of one ncplane onto
* another (its visible self.plane) once per visible card-subtree
* widget per frame; with five visible cards, each composed of five
* widget planes averaging 30 rows × 100 cols, the Raku version
* makes ~75,000 NativeCall trips per render. ncplane_mergedown
* looks like a one-call replacement but composites at absolute
* pile coordinates rather than at the scroll-translated dst we
* need (see ViewportedCardList.rakumod's !copy-cells Pod6 for the
* full investigation), so the path is C with the loop in C.
*
* Symbols are prefixed `notcurses_native_` to stay out of
* notcurses's namespace.
*
* Build: see Build.rakumod's !try-compile-shim. Compiled with
* -undefined dynamic_lookup (macOS) or unresolved-at-link
* (Linux) so the shim has no link-time dependency on
* libnotcurses; the symbols resolve at runtime against whatever
* libnotcurses the host process has already loaded via
* Notcurses::Native's existing FFI bindings.
*/
/*
* notcurses.h's inline functions (NCCELL_INITIALIZER,
* ncplane_putwstr_aligned, etc.) call wcwidth() / wcswidth() from
* . glibc only exposes those when _GNU_SOURCE,
* _XOPEN_SOURCE >= 500, or _POSIX_C_SOURCE >= 200809L is defined
* before any system header is pulled in. Without one, gcc 14+ now
* errors on the implicit declaration (gcc 14 promoted
* -Wimplicit-function-declaration to error by default as part of
* its C23 conformance push). Define _GNU_SOURCE up-front so every
* consumer compile path (CI prebuilt + Build.rakumod's
* source-build fallback + manual user builds) sees wcwidth
* declared. No-op on musl / macOS libc / Windows MSVCRT-UCRT.
*/
#define _GNU_SOURCE
#include
#include
#include
#include
/*
* Copy `rows × cols` cells from `src` starting at (src_y, src_x)
* to `dst` starting at (dst_y, dst_x). Empty source cells take the
* source plane's base cell content (matching the per-cell behaviour
* of ncplane_at_yx), so e.g. a Border's interior empty cells carry
* the theme background through the copy.
*
* Returns 0 on success. Negative ncplane_at_yx_cell results (cells
* out of bounds) are skipped silently — same as the Raku loop.
*/
int notcurses_native_copy_cells(
struct ncplane* src,
struct ncplane* dst,
int src_y, int src_x,
int dst_y, int dst_x,
unsigned rows, unsigned cols
) {
nccell base = NCCELL_TRIVIAL_INITIALIZER;
ncplane_base(src, &base);
const char* base_egc = nccell_extended_gcluster(src, &base);
uint16_t base_styles = base.stylemask;
uint64_t base_channels = base.channels;
for (unsigned r = 0; r < rows; r++) {
for (unsigned c = 0; c < cols; c++) {
nccell cell = NCCELL_TRIVIAL_INITIALIZER;
int bytes = ncplane_at_yx_cell(
src,
src_y + (int)r,
src_x + (int)c,
&cell
);
if (bytes < 0) {
continue;
}
const char* egc = nccell_extended_gcluster(src, &cell);
const char* write_egc;
uint16_t write_styles;
uint64_t write_channels;
if (egc == NULL || egc[0] == '\0') {
write_egc = base_egc;
write_styles = base_styles;
write_channels = base_channels;
} else {
write_egc = egc;
write_styles = cell.stylemask;
write_channels = cell.channels;
}
if (write_egc != NULL && write_egc[0] != '\0') {
ncplane_set_styles(dst, write_styles);
ncplane_set_channels(dst, write_channels);
ncplane_putstr_yx(
dst,
dst_y + (int)r,
dst_x + (int)c,
write_egc
);
}
nccell_release(src, &cell);
}
}
nccell_release(src, &base);
return 0;
}