19. State
Monolith has the ability to save and load data. We call this data state
.
19.1. SQLite Header Include
#include "sqlite3.h"
19.2. Monolith State in Top-level Struct
At any given time, exactly one state file is open. This state file is managed inside of the struct.
monolith_state state;
monolith_state_init(&m->state);
On cleanup, monolith will automatically close any open monolith state. Nothing will happen if there isn't anything open.
monolith_state_close(m);
State can be retrieved with monolith_state_get
.
monolith_state* monolith_state_get(monolith_d *m);
monolith_state* monolith_state_get(monolith_d *m)
{
return &m->state;
}
19.3. Monolith State Data
The monolith state is mostly powered by SQLite! SQLite doesn't need to be seen by other parts of monolith, so we wrap all the bits inside of monolith state and make SQLite calls via monolith state functions.
typedef struct monolith_state monolith_state;
struct monolith_state {
sqlite3 *db;
};
It is initialized with the funciton monolith_state_init
.
void monolith_state_init(monolith_state *s);
void monolith_state_init(monolith_state *s)
{
s->db = NULL;
}
The function monolith_state_empty
checks to see if the
database is empty.
int monolith_state_empty(monolith_state *s);
int monolith_state_empty(monolith_state *s)
{
return s->db == NULL;
}
19.4. Parameter and Schema State Data
While the monolith state does use SQLite, pages shouldn't be writing SQLite statements directly. Parameters can be saved and loaded using this parameter interface.
19.4.1. Parameter Data
A parameter is a single unit which stores a single data value. It is known
as a monolith_state_param
.
typedef struct monolith_state_param monolith_state_param;
struct monolith_state_param {
<<state_param_contents>>
};
19.4.1.1. Parameter Struct Initialization/Cleanup
The struct is initialized with monolith_state_param_init
.
void monolith_state_param_init(monolith_state_param *msp);
void monolith_state_param_init(monolith_state_param *msp)
{
<<monolith_state_param_init>>
}
Any thing allocated and bound to this particular param instance is freed
with monolith_state_param_cleanup
.
void monolith_state_param_cleanup(monolith_state_param *msp);
void monolith_state_param_cleanup(monolith_state_param *msp)
{
<<monolith_state_param_cleanup>>
}
19.4.1.2. Parameter Struct Contents
19.4.1.2.1. Param Name
The parameter name is the string used to identify what the parameter is. In addition to the name, the string length is stored as well.
char *name;
int len;
msp->name = NULL;
msp->len = 0;
if(msp->name != NULL) free(msp->name);
The name for a particular parameter can be set using the function
monolith_state_param_name_set
. It will allocate and set the name of the
string. The length of the string must also be known and explicitely provided.
int monolith_state_param_name_set(monolith_state_param *p,
const char *name,
int len);
The function will allocate and copy the string to the internal state. If the allocation fails, the function will return false (0), otherwise it will return true (1). The function can also fail if the length is non-zero or the name is non-null.
int monolith_state_param_name_set(monolith_state_param *p,
const char *name,
int len)
{
if(p->len != 0) return 0;
if(p->name != NULL) return 0;
p->name = calloc(1, len + 1);
if(p->name == NULL) return 0;
strncpy(p->name, name, len);
return 1;
}
The parameter name can be retrieved using the function
monolith_state_param_name_get
const char * monolith_state_param_name_get(monolith_state_param *p);
const char * monolith_state_param_name_get(monolith_state_param *p)
{
return p->name;
}
19.4.1.2.2. Param Type Flag
int type;
msp->type = 0;
The type can be set with the function monolith_state_param_type_set
,
and retrieved with the function monolith_state_param_type_get
.
void monolith_state_param_type_set(monolith_state_param *p, int type);
int monolith_state_param_type_get(monolith_state_param *p);
void monolith_state_param_type_set(monolith_state_param *p, int type)
{
p->type = type;
}
int monolith_state_param_type_get(monolith_state_param *p)
{
return p->type;
}
19.4.1.2.3. Param User Data
void *ud;
msp->ud = NULL;
Data assigned to the user data pointer ud
is freed by monolith_state_param
during cleanup. By default, the data is freed using the system free
function.
If the internal destructor function has been set with
monolith_state_param_dtor_set
.
if(msp->ud != NULL) {
if(msp->dtor != NULL) {
msp->dtor(msp->ud); /* use external destructor */
} else {
free(msp->ud); /* use system free */
}
}
The user data can be set/get with the functions
monolith_state_param_data_set
and
monolith_state_param_data_get
.
void monolith_state_param_data_set(monolith_state_param *p, void *ud);
void * monolith_state_param_data_get(monolith_state_param *p);
void monolith_state_param_data_set(monolith_state_param *p, void *ud)
{
p->ud = ud;
}
void * monolith_state_param_data_get(monolith_state_param *p)
{
return p->ud;
}
19.4.1.2.4. Param Destructor (Optional)
A destructor function pointer is used as an alternative to free
for
freeing the internal userdata. If it is non-null, it will call this function
when the function is being cleaned up. Otherwise, it will call
the default system free
.
void (*dtor)(void *);
msp->dtor = NULL;
The destructor can be set using the function monolith_state_param_dtor_set
.
void monolith_state_param_dtor_set(monolith_state_param *msp,
void (*dtor)(void *));
void monolith_state_param_dtor_set(monolith_state_param *msp,
void (*dtor)(void *))
{
msp->dtor = dtor;
}
19.4.1.3. Parameter State Types
19.4.1.3.1. Type Enum Declaration
All types are stored inside of an enum, which gets dynamically populated here.
enum {
PARAM_NONE = 0,
<<parameter_enum>>
PARAM_END /* so we don't have to worry about comma warning */
};
19.4.1.3.2. Floating Point Parameter
A floating point parameter uses the enum type PARAM_FLOAT
.
PARAM_FLOAT,
A float point parameter is created using the function
monolith_state_param_mkfloat
.
int monolith_state_param_mkfloat(monolith_state_param *p,
const char *name,
int len,
float ival);
int monolith_state_param_mkfloat(monolith_state_param *p,
const char *name,
int len,
float ival)
{
int rc;
float *x;
rc = monolith_state_param_name_set(p, name, len);
if(!rc) return 0;
x = calloc(1, sizeof(float));
*x = ival;
monolith_state_param_data_set(p, x);
monolith_state_param_type_set(p, PARAM_FLOAT);
return 1;
}
The value of a float can be retrieved using the function
monolith_param_float
. This will do type checking. If it's not a float,
it will return 0.
int monolith_state_param_float(monolith_state_param *p, float **f);
int monolith_state_param_float(monolith_state_param *p, float **f)
{
int t;
t = monolith_state_param_type_get(p);
if(t != PARAM_FLOAT) return 0;
*f = (float *)monolith_state_param_data_get(p);
return 1;
}
The value of a float can be set using the function
monolith_param_float_set
. If the parameter is not a float, it will
return 0.
int monolith_state_param_float_set(monolith_state_param *p, float f);
int monolith_state_param_float_set(monolith_state_param *p, float f)
{
int t;
float *fp;
t = monolith_state_param_type_get(p);
if(t != PARAM_FLOAT) return 0;
fp = (float *)monolith_state_param_data_get(p);
*fp = f;
return 1;
}
19.4.1.3.3. Integer Parameter
An integer parameter uses the enum type PARAM_INT
.
PARAM_INT,
An integer parameter is created using the function
monolith_state_param_mkint
.
int monolith_state_param_mkint(monolith_state_param *p,
const char *name,
int len,
int ival);
int monolith_state_param_mkint(monolith_state_param *p,
const char *name,
int len,
int ival)
{
int rc;
int *x;
rc = monolith_state_param_name_set(p, name, len);
if(!rc) return 0;
x = calloc(1, sizeof(int));
*x = ival;
monolith_state_param_data_set(p, x);
monolith_state_param_type_set(p, PARAM_INT);
return 1;
}
The value of an integer can be retrieved using the function
monolith_state_param_int
. This will do type checking. If it's not an integer,
it will return 0.
int monolith_state_param_int(monolith_state_param *p, int **i);
int monolith_state_param_int(monolith_state_param *p, int **i)
{
int t;
t = monolith_state_param_type_get(p);
if(t != PARAM_INT) return 0;
*i = (int *)monolith_state_param_data_get(p);
return 1;
}
The value of an integer can be set using the function
monolith_param_int_set
. If the parameter is not an integer, it will
return 0.
int monolith_state_param_int_set(monolith_state_param *p, int i);
int monolith_state_param_int_set(monolith_state_param *p, int i)
{
int t;
int *ip;
t = monolith_state_param_type_get(p);
if(t != PARAM_INT) return 0;
ip = (int *)monolith_state_param_data_get(p);
*ip = i;
return 1;
}
19.4.1.3.4. String Parameter
CLOSED: [2019-01-13 Sun 18:57]
A string parameter uses the enum type PARAM_STRING
.
PARAM_STRING,
An string parameter is created using the function
monolith_state_param_mkstring
. The size of the string must be supplied
as well.
int monolith_state_param_mkstring(monolith_state_param *p,
const char *name,
int len,
const char *str,
unsigned int strlen);
int monolith_state_param_mkstring(monolith_state_param *p,
const char *name,
int len,
const char *str,
unsigned int strlen)
{
int rc;
char *x;
rc = monolith_state_param_name_set(p, name, len);
if(!rc) return 0;
x = calloc(1, strlen + 1);
if(x == NULL) return 0;
strncpy(x, str, strlen);
monolith_state_param_data_set(p, x);
monolith_state_param_type_set(p, PARAM_STRING);
return 1;
}
The value of a string can be retrieved using the function
monolith_state_param_string
. This will do type checking.
If it's not a string, it will return 0.
int monolith_state_param_string(monolith_state_param *p, char **str);
int monolith_state_param_string(monolith_state_param *p, char **str)
{
int t;
t = monolith_state_param_type_get(p);
if(t != PARAM_STRING) return 0;
*str = (char *)monolith_state_param_data_get(p);
return 1;
}
19.4.1.3.5. Monome State Parameter
The monome LED state has it's own parameter. The parameter itself stores the current state of the LED grid as a string. More information can be found in the section How monome state data is stored.
A monome state parameter uses the enum type PARAM_MSTATE
.
PARAM_MSTATE,
When the monolith state is saved, it only saves the string. The data allocated is all the data needed for the string, plus the null terminator. With 3 bytes per row, and 8 rows, this totals to be 25 bytes.
int monolith_state_param_mkmstate(monolith_state_param *p,
const char *name,
int len,
monolith_page_mstate *ms);
int monolith_state_param_mkmstate(monolith_state_param *p,
const char *name,
int len,
monolith_page_mstate *ms)
{
int rc;
char *bytes;
rc = monolith_state_param_name_set(p, name, len);
if(!rc) return 0;
bytes = calloc(1, 25); /* (8 x 3) + 1 */
monolith_base64_grid_encode(ms, bytes);
bytes[24] = 0;
monolith_state_param_data_set(p, bytes);
monolith_state_param_type_set(p, PARAM_MSTATE);
return 1;
}
This function will read information from the state param and read it into the monolith monome state.
int monolith_state_param_mstate(monolith_state_param *p,
monolith_page_mstate *ms);
int monolith_state_param_mstate(monolith_state_param *p,
monolith_page_mstate *ms)
{
int t;
char *bytes;
t = monolith_state_param_type_get(p);
if(t != PARAM_MSTATE) return 0;
bytes = (char *)monolith_state_param_data_get(p);
monolith_base64_grid_decode(ms, bytes);
return 1;
}
The value of an integer can be set using the function
monolith_param_int_set
. If the parameter is not an integer, it will
return 0.
int monolith_state_param_mstate_set(monolith_state_param *p, const char *str);
int monolith_state_param_mstate_set(monolith_state_param *p, const char *str)
{
int t;
char *in;
int i;
t = monolith_state_param_type_get(p);
if(t != PARAM_MSTATE) return 0;
in = (char *)monolith_state_param_data_get(p);
for(i = 0; i < 24; i++) {
in[i] = str[i];
}
return 1;
}
19.4.1.3.6. DONE Blob Parameter [4/4]
CLOSED: [2019-04-05 Fri 21:36]
CLOSED: [2019-03-17 Sun 23:05] A blob is used to store binary data.
PARAM_BLOB,
CLOSED: [2019-04-05 Fri 21:26]
The function state_param_mkblob
creates a blob entry. If the blob needs to be
freed, a destructor callback dtor
should be passed in as an argument.
int monolith_state_param_mkblob(monolith_state_param *p,
const char *name,
int len,
void *blob,
unsigned int bloblen,
void (*dtor)(void*));
In order to keep track of both the blob data and the size, a special struct is created. The custom destructor callback is stored here as well.
typedef struct {
unsigned int blobsize;
void *ud;
void (*dtor)(void*);
} mkblob_struct;
<<mkblob_struct>>
static void blob_free(void *ud)
{
mkblob_struct *x;
x = ud;
if(x->dtor != NULL) x->dtor(x->ud);
free(ud);
}
int monolith_state_param_mkblob(monolith_state_param *p,
const char *name,
int len,
void *blob,
unsigned int bloblen,
void (*dtor)(void*))
{
int rc;
mkblob_struct *x;
rc = monolith_state_param_name_set(p, name, len);
if(!rc) return 0;
x = calloc(1, sizeof(mkblob_struct));
if(x == NULL) return 0;
x->ud = blob;
x->blobsize = bloblen;
x->dtor = dtor;
monolith_state_param_data_set(p, x);
monolith_state_param_type_set(p, PARAM_BLOB);
monolith_state_param_dtor_set(p, blob_free);
return 1;
}
CLOSED: [2019-04-05 Fri 21:32]
This function will read a state parameter, and store the pointer and size into
the variables blob
and blobsize
.
int monolith_state_param_blob(monolith_state_param *p,
void **blob,
unsigned int *blobsize);
int monolith_state_param_blob(monolith_state_param *p,
void **blob,
unsigned int *blobsize)
{
int t;
mkblob_struct *x;
t = monolith_state_param_type_get(p);
if(t != PARAM_BLOB) return 0;
x = monolith_state_param_data_get(p);
if(blob != NULL) *blob = x->ud;
if(blobsize != NULL) *blobsize = x->blobsize;
return 1;
}
CLOSED: [2019-05-12 Sun 10:42]
An initialized Blob parameter (initialized with empty values) can be set
to hold real values with the function state_param_blob_set
. This will
require the blob, the blob size, and an optional destructor callback.
int monolith_state_param_blob_set(monolith_state_param *p,
void *blob,
unsigned int blobsize,
void (*dtor)(void*));
int monolith_state_param_blob_set(monolith_state_param *p,
void *blob,
unsigned int blobsize,
void (*dtor)(void*))
{
int t;
mkblob_struct *x;
t = monolith_state_param_type_get(p);
if(t != PARAM_BLOB) return 0;
x = (mkblob_struct *)monolith_state_param_data_get(p);
x->ud = blob;
x->blobsize = blobsize;
x->dtor = dtor;
return 1;
}
19.4.2. Monolith Schema Data
A group of parameters (for say, a page) is known as a schema
. In practice,
one uses schemas to read and write data to a SQLite table.
The schema struct is an opaque pointer that must be manually allocated and destroyed when it is being used.
19.4.2.1. Schema Initialization/Cleanup
When schema is initialized, it pre-allocates a fixed number of parameters. The number of parameters must be node. From there, the schema can be populated with variable names and values.
void monolith_state_schema_init(monolith_state_schema **p_s, int nparams);
void monolith_state_schema_init(monolith_state_schema **p_s, int nparams)
{
int i;
monolith_state_schema *s;
s = calloc(1, sizeof(monolith_state_schema));
<<schema_init>>
*p_s = s;
}
When the schema no longer needs to be used, it must be freed using
monolith_state_schema_cleanup
.
void monolith_state_schema_cleanup(monolith_state_schema **p_s);
void monolith_state_schema_cleanup(monolith_state_schema **p_s)
{
int i;
monolith_state_schema *s;
s = *p_s;
<<schema_cleanup>>
free(s);
}
19.4.2.2. Schema Contents
19.4.2.2.1. Schema Struct Declaration
typedef struct monolith_state_schema monolith_state_schema;
struct monolith_state_schema {
<<schema_contents>>
};
19.4.2.2.2. Param Array
monolith_state_param *params;
On init, this gets allocated, then each parameter is initialized.
s->params = calloc(1, nparams * sizeof(monolith_state_param));
for(i = 0; i < nparams; i++) {
monolith_state_param_init(&s->params[i]);
}
for(i = 0; i < s->nparams; i++) {
monolith_state_param_cleanup(&s->params[i]);
}
if(s->params != NULL) free(s->params);
A single parameter can be retrieved using the function
monolith_state_schema_param
. This will do bounds checking, and will return
0 on error.
int monolith_state_schema_param(monolith_state_schema *s,
int pos,
monolith_state_param **p);
int monolith_state_schema_param(monolith_state_schema *s,
int pos,
monolith_state_param **p)
{
if(pos < 0) return 0;
if(pos >= s->nparams) return 0;
*p = &s->params[pos];
return 1;
}
19.4.2.2.3. Number of Parameters
The number of parameters is stored inside of a variable called nparams
.
int nparams;
s->nparams = nparams;
19.4.2.3. Schema Parameter Setters/Getters
Parameters inside of the schema can be indirectly set using schema functions.
Pages will uses these functions, so they get the shorter prefix
monolith_param
.
19.4.2.3.1. Schema Floating Point Parameter
A monolith parameter can be created using the function monolith_param_mkfloat
.
int monolith_param_mkfloat(monolith_state_schema *s,
int pos,
const char *name,
int len,
float ival);
int monolith_param_mkfloat(monolith_state_schema *s,
int pos,
const char *name,
int len,
float ival)
{
monolith_state_param *p;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
return monolith_state_param_mkfloat(p, name, len, ival);
}
A monolith parameter float can be retrieved using the function
monolith_param_float
#+NAME: function_declarations
int monolith_param_float(monolith_state_schema *s, int pos, float *val);
int monolith_param_float(monolith_state_schema *s, int pos, float *val)
{
monolith_state_param *p;
float *tmp;
int rc;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
rc = monolith_state_param_float(p, &tmp);
if(rc) *val = *tmp;
return rc;
}
19.4.2.3.2. Schema Integer Parameter
A monolith parameter can be created using the function monolith_param_mkfloat
.
int monolith_param_mkint(monolith_state_schema *s,
int pos,
const char *name,
int len,
int ival);
int monolith_param_mkint(monolith_state_schema *s,
int pos,
const char *name,
int len,
int ival)
{
monolith_state_param *p;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
return monolith_state_param_mkint(p, name, len, ival);
}
A monolith parameter integer can be retrieved using the function
monolith_param_int
** 19.4.2.3.2.2. Integer Param Getter
int monolith_param_int(monolith_state_schema *s, int pos, int *val);
int monolith_param_int(monolith_state_schema *s, int pos, int *val)
{
monolith_state_param *p;
int *tmp;
int rc;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
rc = monolith_state_param_int(p, &tmp);
if(rc) *val = *tmp;
return rc;
}
19.4.2.3.3. Schema String Parameter
A monolith parameter can be created using the function monolith_param_mkfloat
.
int monolith_param_mkstring(monolith_state_schema *s,
int pos,
const char *name,
int len,
const char *str,
unsigned int strlen);
int monolith_param_mkstring(monolith_state_schema *s,
int pos,
const char *name,
int len,
const char *str,
unsigned int strlen)
{
monolith_state_param *p;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
return monolith_state_param_mkstring(p, name, len, str, strlen);
}
A monolith parameter string can be retrieved using the function
monolith_param_string
** 19.4.2.3.3.2. String Param Getter
int monolith_param_string(monolith_state_schema *s, int pos, char **val);
int monolith_param_string(monolith_state_schema *s, int pos, char **val)
{
monolith_state_param *p;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
return monolith_state_param_string(p, val);
}
In many situations, const
strings are used more often, so a wrapper function
has been created called monolith_param_stringc
. On error, the function
will return NULL.
const char * monolith_param_stringc(monolith_state_schema *s, int pos);
const char * monolith_param_stringc(monolith_state_schema *s, int pos)
{
char *str;
if(!monolith_param_string(s, pos, &str)) return NULL;
return str;
}
19.4.2.3.4. Schema Monolith State Parameter
This refers to the LED state of the monome grid.
int monolith_param_mkmstate(monolith_state_schema *s,
int pos,
const char *name,
int len,
monolith_page_mstate *ms);
int monolith_param_mkmstate(monolith_state_schema *s,
int pos,
const char *name,
int len,
monolith_page_mstate *ms)
{
monolith_state_param *p;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
return monolith_state_param_mkmstate(p, name, len, ms);
}
int monolith_param_mstate(monolith_state_schema *s,
int pos,
monolith_page_mstate *ms);
int monolith_param_mstate(monolith_state_schema *s,
int pos,
monolith_page_mstate *ms)
{
monolith_state_param *p;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
return monolith_state_param_mstate(p, ms);
}
19.4.2.3.5. DONE Schema Blob Parameter
CLOSED: [2019-04-05 Fri 21:36]
CLOSED: [2019-04-05 Fri 21:34]
int monolith_param_mkblob(monolith_state_schema *s,
int pos,
const char *name,
int len,
void *blob,
unsigned int blobsize,
void (*dtor)(void *));
int monolith_param_mkblob(monolith_state_schema *s,
int pos,
const char *name,
int len,
void *blob,
unsigned int blobsize,
void (*dtor)(void *))
{
monolith_state_param *p;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
return monolith_state_param_mkblob(p, name, len, blob, blobsize, dtor);
}
CLOSED: [2019-04-05 Fri 21:36]
int monolith_param_blob(monolith_state_schema *s,
int pos,
void **blob,
unsigned int *blobsize);
int monolith_param_blob(monolith_state_schema *s,
int pos,
void **blob,
unsigned int *blobsize)
{
monolith_state_param *p;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
return monolith_state_param_blob(p, blob, blobsize);
}
CLOSED: [2019-05-09 Thu 08:20]
Unlike other parameter times, making a blob parameter and setting
a blob parameter are two separate things. The blob value, once
allocated is set with the function monolith_param_setblob
#+NAME: function_declarations
int monolith_param_setblob(monolith_state_schema *s,
int pos,
void *blob,
unsigned int blobsize,
void (*dtor)(void *));
int monolith_param_setblob(monolith_state_schema *s,
int pos,
void *blob,
unsigned int blobsize,
void (*dtor)(void *))
{
monolith_state_param *p;
if(!monolith_state_schema_param(s, pos, &p)) return 0;
return monolith_state_param_blob_set(p, blob, blobsize, dtor);
}
If the system mallocs
and frees
are used, then the function
monolith_param_setblob_default
function can be used.
int monolith_param_setblob_default(monolith_state_schema *s,
int pos,
void *blob,
unsigned int blobsize);
static void free_blob(void *ud) {
free(ud);
}
int monolith_param_setblob_default(monolith_state_schema *s,
int pos,
void *blob,
unsigned int blobsize)
{
return monolith_param_setblob(s, pos, blob, blobsize, free_blob);
}
19.4.3. Monolith Schema write test
This is a quick function to make sure the schema things work out okay. From there, this can be swapped out to write to an actual SQLite database.
void monolith_state_schema_test_write(monolith_state_schema *s,
const char *key,
unsigned int len);
void monolith_state_schema_test_write(monolith_state_schema *s,
const char *key,
unsigned int len)
{
unsigned int n;
monolith_state_param *param;
char buf[256];
int pos;
pos = 0;
strncpy(&buf[pos], "CREATE TABLE IF NOT EXISTS ", 28);
pos += 27;
strncpy(&buf[pos], key, len);
pos += len;
strncpy(&buf[pos], "(", 2);
pos += 1;
strncpy(&buf[pos], "key UNIQUE STRING, ", 20);
pos += 19;
for(n = 0; n < s->nparams; n++) {
param = &s->params[n];
switch(param->type) {
case PARAM_NONE:
printf("NONE");
break;
case PARAM_FLOAT:
pos += sprintf(&buf[pos], "%s FLOAT", param->name);
break;
case PARAM_INT:
pos += sprintf(&buf[pos], "%s INT", param->name);
break;
case PARAM_STRING:
pos += sprintf(&buf[pos], "%s STRING", param->name);
break;
case PARAM_MSTATE:
pos += sprintf(&buf[pos], "%s STRING", param->name);
break;
default:
break;
}
if(n < s->nparams - 1) {
pos += sprintf(&buf[pos], ", ");
}
}
pos += sprintf(&buf[pos], ");");
buf[pos] = 0;
printf("%s\n", buf);
}
19.5. How monome state data is stored
The Grid state data is encoded as in a non standard ascii base 64 string, which allows the data to be stored in a portable manner, while sacrificing some storage efficiency. Each character in a base64 string encodes a 6 bit value. A grid row (16 bits, 2 bytes) is stored amongst base64 ascii characters, with 2 extra bits to spare. To mimic how the monome stores data (with "1" being on the far left), the bitfield is encoded in little endian format.
In the monolith base64 system, numbers end at ascii position 122 ('z') and move down to ascii position 58 (':'). Between this range are all printable ascii characters that also contain the entire alphabet (capital and lowercase). This chosen range is an aesthetic choice by the author.
19.5.1. Encoding/Decoding 6 bit values
The atomic operation for the monolith base64 algorithm are 6 bit values, which get represented as printable ascii character.
A 6 bit value is encoded with the function monolith_base64_6bit_encode
, and
decoded with the function monolith_base64_6bit_decode
.
char monolith_base64_6bit_encode(unsigned char x);
unsigned char monolith_base64_6bit_decode(char x);
Converting a 6-bit value to an ascii is a matter of masking the 8 bit value and adding the starting bias of 58.
char monolith_base64_6bit_encode(unsigned char x)
{
return (x & 63) + 58;
}
Decoding an ascii value to a number is the reverse process of this.
unsigned char monolith_base64_6bit_decode(char x)
{
return x - 58;
}
19.5.2. Encoding/Decoding a monome row
A monome state is encoded/decoded one row at a time. Rows are represented as 2-byte unsigned short values (16 bits), which get mapped to the monome in a little endian sequence. The base 64 encoding also respects this little endian storage.
A monome row is encoded using the function mononolith_base64_16bit_encode
and decoded with the function monolith_base64_16bit_decode
.
void monolith_base64_16bit_encode(unsigned short row, char *a, char *b, char *c);
unsigned short monolith_base64_16bit_decode(char a, char b, char c);
When a 16 bit value is encoded, it maps the first 6 bits into the character a
,
the second set of 6-bits into character b
, and the remaining 4 bits into the
character c
.
void monolith_base64_16bit_encode(unsigned short row, char *a, char *b, char *c)
{
*a = monolith_base64_6bit_encode(row & 63);
*b = monolith_base64_6bit_encode((row >> 6) & 63);
*c = monolith_base64_6bit_encode((row >> 12) & 63);
}
The encoding process does the reverse of this, taking the and A, B, C values, and using bitshifting operations to create a row.
unsigned short monolith_base64_16bit_decode(char a, char b, char c)
{
unsigned short out;
out = monolith_base64_6bit_decode(a);
out |= monolith_base64_6bit_decode(b) << 6;
out |= monolith_base64_6bit_decode(c) << 12;
return out;
}
19.5.3. Encoding/Decoding a monome grid
The monome grid consists of 8 rows of 16 bits. The encode/decode functions
handle converting to and from the C representation of 8 16-bit values
to a ascii string buffer of 24 bytes (not including the null terminator).
These functions are monolith_base64_grid_encode
and
monolith_base64_grid_decode
, respectively.
void monolith_base64_grid_encode(monolith_page_mstate *mstate, char *bytes);
void monolith_base64_grid_decode(monolith_page_mstate *mstate, char *bytes);
void monolith_base64_grid_encode(monolith_page_mstate *mstate, char *bytes)
{
int row;
unsigned short *grid;
grid = (unsigned short *)mstate->data;
for(row = 0; row < 8; row++) {
monolith_base64_16bit_encode(grid[row],
&bytes[0],
&bytes[1],
&bytes[2]);
bytes+=3;
}
}
void monolith_base64_grid_decode(monolith_page_mstate *mstate, char *bytes)
{
int row;
unsigned short *grid;
grid = (unsigned short *)mstate->data;
for(row = 0; row < 8; row++) {
grid[row] = monolith_base64_16bit_decode(bytes[0],
bytes[1],
bytes[2]);
bytes+=3;
}
}
19.5.4. Base64 Scheme functions
Rows are encoded and decoded with monolith:base64-16bit-encode
and
monolith:base64-16bit-decode
.
{"monolith:base64-16bit-encode", pp_base64_16bit_encode, 1, 1, {INT,___,___}},
{"monolith:base64-16bit-decode", pp_base64_16bit_decode, 1, 1, {STR,___,___}},
static cell pp_base64_16bit_encode(cell p)
{
unsigned short x;
char name[] = "monolith:base64-16bit-encode";
char str[4];
x = (unsigned short) integer_value(name, car(p));
monolith_base64_16bit_encode(x, &str[0], &str[1], &str[2]);
str[3] = 0;
return s9_make_string(str, 3);
}
static cell pp_base64_16bit_decode(cell p)
{
unsigned short x;
char *str;
str = string(car(p));
x = monolith_base64_16bit_decode(str[0], str[1], str[2]);
return s9_make_integer(x);
}
19.6. Generating SQL commands from Schema data
The Schema, once populated with parameters, is passed of to the monolith state to be translated to a SQL database. In order to do this, the Schema needs to be coverted to a SQL command.
There are two major SQL commands: a create table command, and a row insert command.
The functions to run these commands are expected to run twice: first, to do a runthrough to get the number of bytes needed to allocate. second, to actually write to the buffer.
19.6.1. SQL Create Command from Schema
This will create the necessary table creation command. This SQL command will check if the table exists first before writing, so it is safe to call every time.
19.6.1.1. monolith_state_schema_sql_create
unsigned int monolith_state_schema_sql_create(monolith_state_schema *s,
const char *tabname,
unsigned int len,
char *buf,
int write);
unsigned int monolith_state_schema_sql_create(monolith_state_schema *s,
const char *tabname,
unsigned int len,
char *buf,
int write)
{
unsigned int pos;
unsigned int n;
monolith_state_param *param;
pos = 0;
if(write) strncpy(&buf[pos], "CREATE TABLE IF NOT EXISTS ", 28);
pos += 27;
if(write) strncpy(&buf[pos], tabname, len);
pos += len;
if(write) strncpy(&buf[pos], "(", 2);
pos += 1;
if(write) strncpy(&buf[pos], "key TEXT UNIQUE, ", 18);
pos += 17;
for(n = 0; n < s->nparams; n++) {
param = &s->params[n];
switch(param->type) {
<<schema_sql_create_types>>
default:
break;
}
if(n < s->nparams - 1) {
if(write) pos += sprintf(&buf[pos], ", ");
else pos += snprintf(NULL, 0, ", ");
}
}
if(write) pos += sprintf(&buf[pos], ");");
else pos += snprintf(NULL, 0, ");");
if(write) buf[pos] = 0;
return pos + 1; /* don't forget the NULL terminator :) */
}
19.6.1.2. Parameter types
19.6.1.2.1. No Parameter
case PARAM_NONE:
break;
19.6.1.2.2. SQLite Float
case PARAM_FLOAT:
if(write) pos += sprintf(&buf[pos], "%s FLOAT", param->name);
else pos += snprintf(NULL, 0, "%s FLOAT", param->name);
break;
19.6.1.2.3. SQLite Integer
case PARAM_INT:
if(write) pos += sprintf(&buf[pos], "%s INT", param->name);
else pos += snprintf(NULL, 0, "%s INT", param->name);
break;
19.6.1.2.4. SQLite String
case PARAM_STRING:
if(write) pos += sprintf(&buf[pos], "%s TEXT", param->name);
else pos += snprintf(NULL, 0, "%s TEXT", param->name);
break;
19.6.1.2.5. SQLite Monome State
Well, this is really represented as the TEXT type.
case PARAM_MSTATE:
if(write) pos += sprintf(&buf[pos], "%s TEXT", param->name);
else pos += snprintf(NULL, 0, "%s TEXT", param->name);
break;
19.6.1.2.6. SQLite Blob
SQLite uses its own BLOB type.
case PARAM_BLOB:
if(write) pos += sprintf(&buf[pos], "%s BLOB", param->name);
else pos += snprintf(NULL, 0, "%s BLOB", param->name);
break;
19.6.2. SQL Insert Command from Schema
The following function described below will create an SQLite template string, which can then be used by the SQLite API to write data into a string.
unsigned int monolith_state_schema_sql_insert(monolith_state_schema *s,
const char *tab,
unsigned int keylen,
char *buf,
int write);
unsigned int monolith_state_schema_sql_insert(monolith_state_schema *s,
const char *tab,
unsigned int len,
char *buf,
int write)
{
unsigned int pos;
unsigned int n;
monolith_state_param *param;
int p;
pos = 0;
if(write) strncpy(&buf[pos], "REPLACE INTO ", 14);
pos += 13;
if(write) strncpy(&buf[pos], tab, len);
pos += len;
if(write) strncpy(&buf[pos], " (key, ", 8);
pos += 7;
for(n = 0; n < s->nparams; n++) {
param = &s->params[n];
if(write) pos += sprintf(&buf[pos], "%s", param->name);
else pos += snprintf(NULL, 0, "%s", param->name);
if(n < s->nparams - 1) {
if(write) pos += sprintf(&buf[pos], ", ");
else pos += snprintf(NULL, 0, ", ");
}
}
if(write) pos += sprintf(&buf[pos], ")\n");
else pos += snprintf(NULL, 0, ")");
if(write) strncpy(&buf[pos], "VALUES(?1, ", 12);
pos += 11;
p = 2;
for(n = 0; n < s->nparams; n++) {
param = &s->params[n];
if(write) pos += sprintf(&buf[pos], "?%d", p);
else pos += snprintf(NULL, 0, "?%d", p);
if(n < s->nparams - 1) {
if(write) pos += sprintf(&buf[pos], ", ");
else pos += snprintf(NULL, 0, ", ");
}
p++;
}
if(write) pos += sprintf(&buf[pos], ");");
else pos += snprintf(NULL, 0, ");");
if(write) buf[pos] = 0;
return pos + 1; /* don't forget the NULL terminator :) */
}
19.6.3. SQL Select Command from Schema
When a state is read from disk, a SQLite select command must be prepared.
unsigned int monolith_state_schema_sql_select(monolith_state_schema *s,
const char *tab,
unsigned int tablen,
const char *key,
unsigned int keylen,
char *buf,
int write);
unsigned int monolith_state_schema_sql_select(monolith_state_schema *s,
const char *tab,
unsigned int tablen,
const char *key,
unsigned int keylen,
char *buf,
int write)
{
unsigned int pos;
pos = 0;
if(write) strncpy(&buf[pos], "SELECT * FROM ", 15);
pos += 14;
if(write) strncpy(&buf[pos], tab, tablen);
pos += tablen;
if(write) strncpy(&buf[pos], " WHERE key == '", 16);
pos += 15;
if(write) strncpy(&buf[pos], key, keylen + 1);
pos += keylen;
if(write) strncpy(&buf[pos], "';", 3);
pos += 2;
if(write) buf[pos] = 0;
return pos + 1; /* don't forget the NULL terminator :) */
}
19.7. SQLite File I/O
Actual operations involving the SQLite API happen with these functions below.
19.7.1. Opening/Closing a SQLite database.
A database is opened with the function monolith_state_dbopen
. This is a
wrapper around sqlite3_open
. A database is closed with
monolith_state_dbclose
. This is a wrapper around sqlite3_close
.
NOTE: these functions have been slightly renamed from state_open
and
state_close
to state_dbopen
and state_dbclose
. This was done
because I wanted to use that namespace to handle the global monolith state
pointer (a design addition I made after this section).
int monolith_state_dbopen(monolith_state *state, const char *filename);
int monolith_state_dbopen(monolith_state *state, const char *filename)
{
int rc;
rc = sqlite3_open(filename, &state->db);
if(rc) {
fprintf(stderr, "Could not open %s: %s\n",
filename,
sqlite3_errmsg(state->db));
sqlite3_close(state->db);
return 0;
}
return 1;
}
void monolith_state_dbclose(monolith_state *state);
void monolith_state_dbclose(monolith_state *state)
{
if(state->db == NULL) return;
sqlite3_close(state->db);
}
19.7.2. A Note on Blobs
NOTE: blobs opt to use the normal blob rather than the blob64 type, which takes
in a signed integer for size (sqlite3_bind_blob
).
This will potentially truncate some larger blobs. This is done because the way to get size (sqlite3bytes) returns an integer, and not an unsigned int.
void *blob;
unsigned int blobsize;
blobsize = 0;
blob = NULL;
monolith_state_param_blob(param, &blob, &blobsize);
sqlite3_bind_blob(stmt, p, blob, (int)blobsize, NULL);
break;
When reading a blob, an integer size is used to match the type in
sqlite3_column_bytes
.
The blob handled by sqlite is freed internally by SQLite, so the data must be copied over and allocated.
int bytes;
int b;
const unsigned char *blob;
unsigned char *buf;
blob = (unsigned char *)sqlite3_column_blob(stmt, p);
bytes = sqlite3_column_bytes(stmt, p);
buf = malloc(bytes);
for(b = 0; b < bytes; b++) buf[b] = blob[b];
monolith_state_param_blob_set(param, buf, bytes, free_the_blob);
break;
The callback used to free the copied data is called free_the_blob
. Just
a wrapper around the system free
.
static void free_the_blob(void *ud);
static void free_the_blob(void *ud)
{
free(ud);
}
19.7.3. Writing a Schema to a SQLite database
The function monolith_state_write_schema
writes a schema to disk. This
function typically gets called inside of a page save callback.
int monolith_state_write_schema(monolith_state *ms,
monolith_state_schema *schema,
const char *tab,
unsigned int tablen,
const char *key,
unsigned int keylen);
int monolith_state_write_schema(monolith_state *ms,
monolith_state_schema *schema,
const char *tab,
unsigned int tablen,
const char *key,
unsigned int keylen)
{
unsigned int nbytes_create;
unsigned int nbytes_insert;
char *tmp;
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
int n;
monolith_state_param *param;
float *ftmp;
int *itmp;
char *stmp;
int p;
rc = 1;
db = ms->db;
if(db == NULL) return 0;
nbytes_create = monolith_state_schema_sql_create(schema,
tab,
tablen,
NULL,
0);
tmp = malloc(nbytes_create);
monolith_state_schema_sql_create(schema, tab, tablen, tmp, 1);
sqlite3_prepare(db, tmp, -1, &stmt, NULL);
rc = sqlite3_step(stmt);
if(rc != SQLITE_DONE) {
fprintf(stderr, "SQLite error: %s\n", sqlite3_errmsg(db));
}
sqlite3_finalize(stmt);
nbytes_insert = monolith_state_schema_sql_insert(schema,
tab,
tablen,
NULL,
0);
if(nbytes_insert > nbytes_create) {
tmp = realloc(tmp, nbytes_insert);
}
monolith_state_schema_sql_insert(schema, tab, tablen, tmp, 1);
sqlite3_prepare(db, tmp, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, key, keylen, NULL);
p = 2;
for(n = 0; n < schema->nparams; n++) {
param = &schema->params[n];
switch(param->type) {
case PARAM_NONE:
break;
case PARAM_FLOAT:
monolith_state_param_float(param, &ftmp);
sqlite3_bind_double(stmt, p, *ftmp);
break;
case PARAM_INT:
monolith_state_param_int(param, &itmp);
sqlite3_bind_double(stmt, p, *itmp);
break;
case PARAM_STRING:
monolith_state_param_string(param, &stmp);
sqlite3_bind_text(stmt, p, stmp, -1, NULL);
break;
case PARAM_MSTATE:
stmp = param->ud;
sqlite3_bind_text(stmt, p, stmp, 24, NULL);
break;
case PARAM_BLOB: {
<<bind_blob_parameter>>
}
default:
break;
}
p++;
}
rc = sqlite3_step(stmt);
if(rc != SQLITE_DONE) {
fprintf(stderr, "SQLite error: %s\n", sqlite3_errmsg(db));
}
sqlite3_finalize(stmt);
free(tmp);
return 1;
}
19.7.4. Reading from a SQLite databse to a Schema
The function monolith_state_read_schema
reads a schema from disk.
This function typically gets called inside of a page load callback.
int monolith_state_read_schema(monolith_state *ms,
monolith_state_schema *schema,
const char *tab,
unsigned int tablen,
const char *key,
unsigned int keylen);
int monolith_state_read_schema(monolith_state *ms,
monolith_state_schema *schema,
const char *tab,
unsigned int tablen,
const char *key,
unsigned int keylen)
{
unsigned int nbytes;
char *tmp;
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
int n;
monolith_state_param *param;
float ftmp;
int itmp;
int p;
rc = 1;
db = ms->db;
if(db == NULL) return 0;
nbytes = monolith_state_schema_sql_select(schema,
tab, tablen,
key, keylen, NULL, 0);
tmp = malloc(nbytes);
monolith_state_schema_sql_select(schema,
tab, tablen,
key, keylen, tmp, 1);
sqlite3_prepare(db, tmp, -1, &stmt, NULL);
rc = sqlite3_step(stmt);
if(rc == SQLITE_DONE) {
fprintf(stderr, "Key doesn't exist: ");
fwrite(key, 1, keylen, stderr);
fprintf(stderr, "\n");
sqlite3_finalize(stmt);
free(tmp);
return 0;
}
p = 1;
for(n = 0; n < schema->nparams; n++) {
param = &schema->params[n];
switch(param->type) {
case PARAM_NONE:
break;
case PARAM_FLOAT:
ftmp = sqlite3_column_double(stmt, p);
monolith_state_param_float_set(param, ftmp);
break;
case PARAM_INT:
itmp = sqlite3_column_int(stmt, p);
monolith_state_param_int_set(param, itmp);
break;
case PARAM_STRING:
fprintf(stderr, "Oops. Not ready to read strings\n");
/* monolith_state_param_string(param, &stmp); */
/* sqlite3_bind_text(stmt, p, stmp, -1, NULL); */
break;
case PARAM_MSTATE: {
const char *str;
str = (const char *)sqlite3_column_text(stmt, p);
monolith_state_param_mstate_set(param, str);
break;
}
case PARAM_BLOB: {
<<get_blob_parameter>>
}
default:
break;
}
p++;
}
sqlite3_finalize(stmt);
free(tmp);
return 1;
}
19.8. Opening/Closing a Monolith State database
This opens the state stored inside of the monolith struct.
19.8.1. Opening The Monolith State
19.8.1.1. Opening State From C
This can be done with the function monolith_state_open
.
int monolith_state_open(monolith_d *m, const char *filename);
int monolith_state_open(monolith_d *m, const char *filename)
{
return monolith_state_dbopen(&m->state, filename);
}
19.8.1.2. Opening State from Scheme
This can be done with the function monolith:state-open
.
{"monolith:state-open", pp_state_open, 1, 1, {STR,___,___}},
static cell pp_state_open(cell p) {
monolith_d *m;
const char *filename;
int rc;
filename = string(car(p));
m = monolith_data_get();
rc = monolith_state_open(m, filename);
if(!rc) error("Could not open file", car(p));
return UNSPECIFIC;
}
19.8.1.3. Opening State from Janet
This is done with monolith/state-open
.
{
"monolith/state-open",
j_state_open,
"Opens a state database.\n"
},
static Janet j_state_open(int32_t argc, Janet *argv)
{
const char *filename;
int rc;
monolith_d *m;
janet_fixarity(argc, 1);
filename = (const char *)janet_getstring(argv, 0);
m = monolith_data_get();
rc = monolith_state_open(m, filename);
if (!rc) fprintf(stderr,
"Could not open file '%s'",
filename);
return janet_wrap_nil();
}
19.8.2. Closing Monolith State
19.8.2.1. Closing the State from C
This can be done with monolith_state_close
#+NAME: function_declarations
void monolith_state_close(monolith_d *m);
void monolith_state_close(monolith_d *m)
{
monolith_state_dbclose(&m->state);
}
19.8.2.2. Closing the State from Scheme
State can be explicitely closed with monolith:state-close
.
{"monolith:state-close", pp_state_close, 0, 0, {___,___,___}},
static cell pp_state_close(cell p) {
monolith_d *m;
m = monolith_data_get();
monolith_state_close(m);
return UNSPECIFIC;
}
19.8.2.3. Opening State from Janet
This is done with monolith/state-close
.
{
"monolith/state-close",
j_state_close,
"Closes a state database.\n"
},
static Janet j_state_close(int32_t argc, Janet *argv)
{
monolith_d *m;
janet_fixarity(argc, 0);
m = monolith_data_get();
monolith_state_close(m);
return janet_wrap_nil();
}
19.9. Raw SQLite commands on open state file
Occasionally, it is desirable to evaluate raw sqlite commands on an open state file.
19.9.1. In C
This can be done with monolith_state_sql
.
int monolith_state_sql(monolith_d *m, const char *cmd);
int monolith_state_sql(monolith_d *m, const char *cmd)
{
monolith_state *state;
sqlite3 *db;
state = &m->state;
db = state->db;
if (db == NULL) return 0;
sqlite3_exec(db, cmd, NULL, NULL, NULL);
return 1;
}
19.9.2. In Scheme
This can be done with monolith:state-sql
#+NAME: primitive_entries
{"monolith:state-sql", pp_state_sql, 1, 1, {STR,___,___}},
static cell pp_state_sql(cell p)
{
monolith_d *m;
const char *cmd;
cmd = string(car(p));
m = monolith_data_get();
monolith_state_sql(m, cmd);
return UNSPECIFIC;
}
prev | home | next