'How to export thousands of constants in an R package?

Goal

I want to expose built-in constants from a package I am developing that come originally from C source code, being defined with #define directives.

OpenGL constants defined as #define directives

I'm wrapping this C library GLFW using Rcpp. This C library, in turn, includes OpenGL declarations which includes many #defines, here's a short snippet from gl.h:

#define GL_T4F_C4F_N3F_V4F          0x2A2D
#define GL_MATRIX_MODE              0x0BA0
#define GL_MODELVIEW                0x1700
#define GL_PROJECTION               0x1701
#define GL_TEXTURE                  0x1702
#define GL_POINT_SMOOTH             0x0B10
#define GL_POINT_SIZE               0x0B11
#define GL_POINT_SIZE_GRANULARITY   0x0B13
#define GL_POINT_SIZE_RANGE         0x0B12
#define GL_LINE_SMOOTH              0x0B20

Wrapping of C macro definitions in C++ functions

Now, I've been exposing these C macro definitions by wrapping them in c++ functions, e.g.:

// [[Rcpp::export]]
Rcpp::IntegerVector macro_gl_matrix_mode() {return Rcpp::wrap((unsigned int) GL_MATRIX_MODE);}

Exporting the variables

And then, I have an R source file in data-raw/ that essentially calls those not exported functions and saves each object to disk: (abbreviated for clarity):

library(glfw)
library(tibble)
library(usethis)
library(fs)
library(dplyr)

#
# use_data2 accepts a list of strings with the names of the objects to be
# exported instead of the interface provided by usethis::use_data that expects
# multiple arguments passed in `...`.
#
use_data2 <- function(objs,
                      internal = FALSE,
                      overwrite = FALSE,
                      compress = "bzip2",
                      version = 2,
                      envir = parent.frame())
{
  usethis:::check_is_package("use_data()")
  if (internal) {
    usethis::use_directory("R")
    paths <- fs::path("R", "sysdata.rda")
    objs <- list(objs)
  }
  else {
    usethis::use_directory("data")
    paths <- fs::path("data", objs, ext = "rda")
  }
  usethis:::check_files_absent(proj_path(paths), overwrite = overwrite)
  usethis::ui_done("Saving {ui_value(unlist(objs))} to {ui_value(paths)}")
  mapply(save, list = objs,
         file = proj_path(paths),
         MoreArgs = list(envir = envir, compress = compress, version = version)
  )
  invisible()
}

gl <- new.env()

# Begin of loads of assign calls
# (...)
assign('GL_MATRIX_MODE', glfw:::macro_gl_matrix_mode(), envir = gl)
# (...)
# End

#
# Exporting
#

gl_lst <- as.list(gl)
gl_names <- names(gl_lst)

use_data2(gl_names, internal = FALSE, compress = "xz", overwrite = TRUE, version = 2, envir = gl)

This is working but I have 5727 of these constants to be exported. So when I load my package it just stays for more than 5 min loading at this stage:

*** moving datasets to lazyload DB

So there's got to be a better way, right? Not only this is very slow at package loading time as well as I'm guessing that having thousands of objects in my data/ folder is going to create trouble from the package standards or requirements point of view...

Let me just say that I was trying to avoid encapsulating all these constants in a list or dataframe because I wanted to keep the API interface similar to the C library in this respect, i.e., right now I think it is quite nice to be able to simply use the variables GL_MODELVIEW or GL_POINT_SIZE_GRANULARITY straight without any extra syntax.

Any help/pointers is greatly appreciated.

Note: This other question is similar to mine, but it has not an answer yet, and the scope might be slightly different because my constants are originally from C code so there might be a more some specific solution my to problem, for instance, using Rcpp in a way I haven't tried yet: Exporting an unwieldy set of constants in a package.

r


Solution 1:[1]

I had a similar problem. I inherited a project that had a large number of values defined in a file. The app sourced these files to load the data into the global environment. I am converting much of this to a package and wanted these as internal package data. So I did this simple script to create the R/sysdata.rda that is loaded when the package is loaded with "LazyData: true" in the Description file.

#Start with a clean environment
 rm(list=ls(all.names = T))

#Data to be saved  

strings <- c("a","b")
my_list <- list(first=c(1,2,3), second = seq(1,10))

#Get the names
data_names <- paste0(ls(),collapse =",")

#Create string for execution
command <- paste0("usethis::use_data(" , data_names ,",internal = 
TRUE,overwrite=TRUE)")

#execute
eval(parse(text = command))

#cleanup
rm(list=ls(all.names = T))


 

Solution 2:[2]

I am studying the following approach:

Step 1

Instead of exporting all those constants separately, I am going to export an environment that encapsulates all the constants: environment gl (similar to @ralf-stubner's suggestion). This makes the loading (rebuilding of the package) much faster.

So, in my data-raw/gl_macros.R (the data generating script) I am adding this last line to export the environment gl:

usethis::use_data(gl, internal = FALSE, compress = "xz", overwrite = TRUE, version = 2)

Step 2

And then, to have the convenience of accessing the OpenGL macros with their original names, I add a on-attach hook to my R/zzz.R:

.onAttach <- function(libname, pkgname) {
  for (n in ls(glfw::gl, all.names = TRUE)) assign(n, get(n, glfw::gl), .GlobalEnv)
} # .onAttach()

It seems to work! At least on an interactive session. This last step takes a few seconds but it's a lot quicker than the original approach. But I am thinking now that this won't work if my package is used by other packages, not sure though.

Alternative to step 2

Perhaps this will work best:

.onLoad <- function(libname, pkgname) {
  attach(glfw::gl)
} # .onLoad()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Craig Parman
Solution 2