unigd exposes a C API that allows other R packages to
interact with the graphics device from compiled code. This enables
building applications like plot viewers, web servers, or IDE
integrations on top of unigd.
The API has extern "C" linkage, so it can be used from
both C and C++ client code. It is a versioned vtable (struct of function
pointers) obtained at runtime through R’s R_GetCCallable
mechanism. All functions are thread-safe, so clients can call into
unigd from background threads.
The examples in this document use C++ for brevity, but the API itself is plain C.
Architecture overview
R session
|
| ugd() opens a unigd graphics device
v
unigd device stores plot history, manages renderers
|
| C API function pointer table (unigd_api_v1)
v
client package attaches via device_attach(), receives callbacks,
renders plots on demand
The API follows a client model: a client package
registers itself with an open unigd device and receives
lifecycle callbacks. It can then query device state, browse plot
history, and render plots to any supported format.
Package setup
DESCRIPTION
Your package needs to link against unigd:
Imports: unigd
LinkingTo: unigd
Imports ensures unigd is loaded (and its
shared library available) before your package. LinkingTo
gives your compiler access to the headers in
unigd/inst/include/.
Initialization
Obtain the API vtable once when your package is loaded. The
recommended pattern uses a RAII guard and the
[[cpp11::init]] attribute (which runs at
R_init_<pkg> time):
#include <cpp11/R.hpp>
#include <unigd_api.h>
namespace mypackage {
// Global API pointer and client ID
unigd_api_v1 *api = nullptr;
UNIGD_CLIENT_ID client_id = 0;
namespace {
class api_guard {
public:
~api_guard() { destroy(); }
void create() {
if (api == nullptr) unigd_api_v1_create(&api);
}
void destroy() {
if (api != nullptr) {
unigd_api_v1_destroy(api);
api = nullptr;
}
}
};
api_guard guard;
} // namespace
} // namespace mypackage
[[cpp11::init]] void import_unigd_api(DllInfo *dll) {
mypackage::guard.create();
mypackage::client_id = mypackage::api->register_client_id();
}After this runs, mypackage::api points to the vtable and
mypackage::client_id holds a unique identifier for your
package. The guard ensures unigd_api_v1_destroy is called
when the shared library unloads.
Types
Handle types
The API uses opaque void * handles for resource
management. Each *_create / *_find function
returns a handle that must be freed with the
corresponding *_destroy function.
| Type | Returned by | Freed by |
|---|---|---|
UNIGD_HANDLE |
device_attach |
device_destroy |
UNIGD_RENDER_HANDLE |
device_render_create |
device_render_destroy |
UNIGD_FIND_HANDLE |
device_plots_find |
device_plots_find_destroy |
UNIGD_RENDERERS_HANDLE |
renderers |
renderers_destroy |
UNIGD_RENDERERS_ENTRY_HANDLE |
renderers_find |
renderers_find_destroy |
ID types
| Type | Underlying | Description |
|---|---|---|
UNIGD_PLOT_ID |
uint32_t |
Stable identifier for a plot |
UNIGD_PLOT_INDEX |
uint32_t |
Positional index (oldest = 1) |
UNIGD_PLOT_RELATIVE |
int32_t |
Signed offset (0 = latest, -1 = previous) |
UNIGD_CLIENT_ID |
uint32_t |
Unique client identifier |
UNIGD_RENDERER_ID |
const char * |
Renderer string ID (e.g. "svg",
"png") |
Structs
unigd_graphics_client
Callback table provided by the client to device_attach.
All callbacks receive the client_data pointer that was
passed to device_attach.
struct unigd_graphics_client {
void (*start)(void *client_data); // Device activated
void (*close)(void *client_data); // Device closing
void (*state_change)(void *client_data); // Plot state changed
const char *(*info)(void *client_data); // Return client info string
};-
start: Called once afterdevice_attachsucceeds. Use this to launch background threads or initialize resources. -
close: Called when the device is closed (e.g.dev.off()in R). Clean up resources and calldevice_destroyon the handle here. After this returns, the device handle is no longer valid. -
state_change: Called whenever a plot is created, modified, or removed. Typically used to notify connected clients (e.g. push a WebSocket message). -
info: Should return a static string identifying the client (e.g."mypkg 1.0.0").
unigd_device_state
struct unigd_device_state {
int upid; // Update ID, increments on every state change
UNIGD_PLOT_INDEX hsize; // Number of plots in history
bool active; // Whether the device is active
};The upid field is useful for change detection: if the
upid hasn’t changed since the last check, no plots have
been added, removed, or redrawn.
unigd_render_args
struct unigd_render_args {
double width; // Plot width in inches (or pixels for raster)
double height; // Plot height in inches (or pixels for raster)
double scale; // Zoom/scale factor (1.0 = 100%)
};Pass width = -1 and height = -1 to use the
device’s current dimensions.
unigd_render_access
struct unigd_render_access {
const uint8_t *buffer; // Pointer to rendered data
uint64_t size; // Size in bytes
};The buffer is valid until the corresponding
UNIGD_RENDER_HANDLE is destroyed.
unigd_renderer_info
struct unigd_renderer_info {
UNIGD_RENDERER_ID id; // e.g. "svg", "png"
const char *mime; // e.g. "image/svg+xml"
const char *fileext; // e.g. "svg"
const char *name; // Human-readable name
const char *type; // Renderer category
const char *description; // Short description
bool text; // true if output is text, false if binary
};Attaching to a device
A client attaches to an open unigd device by providing a
callback struct, its client ID, and an arbitrary data pointer:
class MyClient {
unigd_api_v1 *m_api = nullptr;
UNIGD_HANDLE m_handle = nullptr;
unigd_graphics_client m_client;
public:
MyClient() {
m_client.start = [](void *d) {
static_cast<MyClient *>(d)->on_start();
};
m_client.close = [](void *d) {
static_cast<MyClient *>(d)->on_close();
};
m_client.state_change = [](void *d) {
static_cast<MyClient *>(d)->on_state_change();
};
m_client.info = [](void *) { return "mypkg 1.0.0"; };
}
bool attach(int devnum) {
m_api = mypackage::api;
m_handle = m_api->device_attach(
devnum, // R device number
&m_client, // callback table
mypackage::client_id, // from register_client_id()
this // client_data passed to callbacks
);
return m_handle != nullptr;
}
void on_start() {
// Called after attach succeeds.
// Launch background work here.
}
void on_state_change() {
auto state = m_api->device_state(m_handle);
// React to state.upid, state.hsize, etc.
}
void on_close() {
// Device is closing. Clean up.
if (m_api && m_handle) {
m_api->device_destroy(m_handle);
}
delete this;
}
};The devnum parameter is the R graphics device number,
typically obtained from dev.cur() after opening a
ugd() device.
Browsing plot history
Use device_plots_find to query plot IDs with
pagination:
unigd_find_results results;
UNIGD_FIND_HANDLE fh = api->device_plots_find(
handle,
0, // offset: 0 = start from latest, negative = relative
10, // limit: max number of results (0 = all)
&results
);
for (UNIGD_PLOT_INDEX i = 0; i < results.size; ++i) {
UNIGD_PLOT_ID id = results.ids[i];
// ... use the plot ID ...
}
// Always free the results
api->device_plots_find_destroy(fh);To remove a single plot or clear all history:
Rendering plots
Rendering is a two-step process: create the render (which may trigger a redraw), then access the buffer.
// 1. Look up the renderer (optional, useful to get MIME type)
unigd_renderer_info rinfo;
auto rinfo_h = api->renderers_find("svg", &rinfo);
// rinfo.mime == "image/svg+xml"
// rinfo.text == true
// 2. Render a specific plot
unigd_render_access render;
auto render_h = api->device_render_create(
handle,
"svg", // renderer ID
plot_id, // from device_plots_find
{720.0, 576.0, 1.0}, // {width, height, scale}
&render
);
if (render_h) {
// render.buffer contains the SVG data
// render.size is the byte length
std::string svg(render.buffer, render.buffer + render.size);
}
// 3. Free resources
api->device_render_destroy(render_h);
api->renderers_find_destroy(rinfo_h);To enumerate all available renderers:
Retrieving a client from a device
If your R code needs to look up a previously attached client (e.g. to
expose its state to R), use device_get:
void *data = api->device_get(devnum, mypackage::client_id);
if (data) {
auto *client = static_cast<MyClient *>(data);
// ... use client ...
}This returns the client_data pointer that was passed to
device_attach.
Logging
The API provides a thread-safe logging function that prints to the R console:
Safe to call from any thread.
Memory management
Every function that returns a handle allocates memory that must be freed by the corresponding destroy function. Failing to do so will leak memory.
| Allocator | Deallocator |
|---|---|
device_attach |
device_destroy |
device_render_create |
device_render_destroy |
device_plots_find |
device_plots_find_destroy |
renderers |
renderers_destroy |
renderers_find |
renderers_find_destroy |
The unigd_render_access buffer is owned by its
UNIGD_RENDER_HANDLE. Copy the data out if you need it after
calling device_render_destroy.
Similarly, the unigd_find_results.ids array is owned by
its UNIGD_FIND_HANDLE and becomes invalid after
device_plots_find_destroy.
Thread safety
All API functions are safe to call from any thread. Internally,
unigd uses a shared mutex to synchronize access to the plot
store.
The state_change callback is invoked from R’s main
thread (during graphics engine operations). If your callback needs to
communicate with background threads, use appropriate
synchronization.
API reference
General
| Function | Signature | Description |
|---|---|---|
log |
void (const char *) |
Print a message to the R console (thread-safe) |
info |
const char *() |
Returns "unigd <version>"
|
Client registration
| Function | Signature | Description |
|---|---|---|
register_client_id |
UNIGD_CLIENT_ID () |
Obtain a unique client ID |
Device operations
| Function | Signature | Description |
|---|---|---|
device_attach |
UNIGD_HANDLE (int devnum, unigd_graphics_client *, UNIGD_CLIENT_ID, void *) |
Attach client to device |
device_get |
void *(int devnum, UNIGD_CLIENT_ID) |
Retrieve client data |
device_destroy |
void (UNIGD_HANDLE) |
Free device handle |
device_state |
unigd_device_state (UNIGD_HANDLE) |
Query device state |
Plot history
| Function | Signature | Description |
|---|---|---|
device_plots_find |
UNIGD_FIND_HANDLE (UNIGD_HANDLE, UNIGD_PLOT_RELATIVE, UNIGD_PLOT_INDEX, unigd_find_results *) |
Query plot IDs |
device_plots_find_destroy |
void (UNIGD_FIND_HANDLE) |
Free find results |
device_plots_remove |
bool (UNIGD_HANDLE, UNIGD_PLOT_ID) |
Remove a plot |
device_plots_clear |
bool (UNIGD_HANDLE) |
Clear all plots |
Rendering
| Function | Signature | Description |
|---|---|---|
device_render_create |
UNIGD_RENDER_HANDLE (UNIGD_HANDLE, UNIGD_RENDERER_ID, UNIGD_PLOT_ID, unigd_render_args, unigd_render_access *) |
Render a plot |
device_render_destroy |
void (UNIGD_RENDER_HANDLE) |
Free render data |
Renderers
| Function | Signature | Description |
|---|---|---|
renderers |
UNIGD_RENDERERS_HANDLE (unigd_renderers_list *) |
List all renderers |
renderers_destroy |
void (UNIGD_RENDERERS_HANDLE) |
Free renderer list |
renderers_find |
UNIGD_RENDERERS_ENTRY_HANDLE (UNIGD_RENDERER_ID, unigd_renderer_info *) |
Look up a renderer |
renderers_find_destroy |
void (UNIGD_RENDERERS_ENTRY_HANDLE) |
Free renderer lookup |
