src/display.h
Server-side display
This file implements the server-side of the interactive display of a running Basilisk code. The client-side is typically done by the javascript implementation.
This works using two main components:
The initial state of the display can be controlled using the DISPLAY macro, following these rules:
-DDISPLAY=1, -DDISPLAY
: play controls, start running immediately.-DDISPLAY=-1
: play controls, initially paused.-DDISPLAY=0
: no play controls, start running immediately.#include "display.h"
: play controls, initially paused.
This can also be changed by setting display_play = 0;
in
main()
.
Display port
Multiple servers can run simultaneously on the same machine but must
use different ports. When connecting, the server looks for a free port
in the default range (typically 7100 to 7200, see the DISPLAY_RANGE
macro below). The corresponding connection information (including the
URL of the javascript interface) is
written in the display.html
file, which can be used to open
the connection on the client side (i.e. the web browser).
Display control
The values of integer or double variables can be controlled
interactively using the display_control()
macro, for
example:
(value, 0, 100, "Short Name", "Long tooltip"); display_control
where the first argument is the name of the variable and the numbers indicate the (optional) range (min and max values). The (optional) name and tooltip are used to generate the Basilisk View interface. The only compulsory argument is the variable name.
Default display
Default drawing function calls, which will be sent to an empty javascript client, can be setup using something like:
("squares (color = 'u.x', spread = -1);"); display
where the single quotes will be expanded to doubles quotes when interpreted. Do not forget the trailing semi-column.
Note that these function calls will be appended to existing ones. To overwrite existing calls, use:
("squares (color = 'u.x', spread = -1);", true); display
#ifndef DISPLAY_JS
# define DISPLAY_JS "http://basilisk.fr/three.js/editor/index.html"
#endif
#ifndef DISPLAY_HOST
# define DISPLAY_HOST "localhost"
#endif
#ifndef DISPLAY_RANGE
# define DISPLAY_RANGE "7100:7200"
#endif
#define debug(...) do { if (Display.debug) fprintf (qerr, __VA_ARGS__), fflush(qerr); } while(0)
#include <netdb.h>
#include <wsServer/include/ws.h>
#pragma autolink -L$BASILISK/wsServer -lws
#include "view.h"
#include "khash.h"
typedef struct {
int fd;
int iter;
} DisplayClient;
(strhash, DisplayClient *)
KHASH_MAP_INIT_STR
static struct {
(strhash) * objects;
khash_tint sock, port;
char * error;
* controls;
Array bool debug;
} Display = { .sock = -1 };
static void display_display()
{
("**************************\n");
debug for (khiter_t k = kh_begin (Display.objects); k != kh_end (Display.objects);
++k)
if (kh_exist (Display.objects, k)) {
("%s", kh_key (Display.objects, k));
debug DisplayClient * client = kh_value (Display.objects, k);
while (client->fd >= 0) {
(" %d/%d", client->fd, client->iter);
debug ++;
client}
("\n");
debug }
("--------------------------\n");
debug }
static char * read_file_into_buffer (FILE * fp)
{
if (fseek (fp, 0L, SEEK_END) < 0)
return NULL;
long bufsize = ftell (fp);
if (bufsize <= 0)
return NULL;
char * buf = malloc (sizeof(char)*(bufsize + 1));
if (fseek (fp, 0L, SEEK_SET) < 0) {
free (buf);
return NULL;
}
size_t newLen = fread (buf, sizeof(char), bufsize, fp);
[newLen] = '\0'; /* Just to be safe. */
bufreturn buf;
}
static void display_command (const char * command)
{
("display_command (%s)\n", command);
debug
vertex_buffer_setup();
.vertex = 0; // fixme
VertexBuffer
// Temporarily redirect stderr to catch errors
int bak = -1;
if (pid() == 0) {
fflush (stderr);
= dup (2);
bak FILE * fp = tmpfile();
(fileno (fp), 2);
dup2 fclose (fp);
}
char * line = strdup (command);
bool status = process_line (line);
free (line);
free (Display.error);
.error = NULL;
Displayif (status) {
if (VertexBuffer.type < 0)
.type = 0;
VertexBuffer}
else { // An error occured
if (pid() == 0) {
fflush (stderr);
FILE * fp = fdopen (2, "r");
.error = read_file_into_buffer (fp);
Displayint len = Display.error ? strlen (Display.error) : 0;
if (len > 0 && Display.error[len - 1] == '\n')
.error[len - 1] = '\0';
Displayfclose(fp);
}
else.error = strdup ("error (on slave process)");
Display.type = - 1;
VertexBuffer}
if (pid() == 0) {
// Restore stderr to its previous value
(bak, 2);
dup2 (bak);
close }
if (VertexBuffer.type < 0)
("error: '%s'\n", Display.error);
debug else {
if (pid() > 0) {
if (VertexBuffer.normal->len < VertexBuffer.position->len)
.normal->len = 0;
VertexBufferif (VertexBuffer.color->len < VertexBuffer.position->len)
.color->len = 0;
VertexBuffer}
("position: %ld, normal: %ld, color: %ld, index: %ld, type: %d\n",
debug .position->len,
VertexBuffer.normal->len,
VertexBuffer.color->len,
VertexBuffer.index->len, VertexBuffer.type);
VertexBuffer}
}
static int ws_send_array (int fd, Array * a, int status, int type,
unsigned int * shift)
{
#if _MPI
if (pid() == 0) {
void * p = NULL;
long len;
for (int pe = 0; pe < npe(); pe++) {
if (pe == 0)
= a->p, len = a->len;
p else {
;
MPI_Status status(&len, 1, MPI_LONG, pe, 22, MPI_COMM_WORLD, &status);
MPI_Recv if (len > 0) {
= malloc (len);
p (p, len, MPI_BYTE, pe, 23, MPI_COMM_WORLD, &status);
MPI_Recv }
else= NULL;
p }
if (type == 0) // position
[pe] = (pe > 0 ? shift[pe - 1] : 0) + len/(3*sizeof(float));
shiftelse if (type == 1 && pe > 0) // index
for (unsigned int i = 0; i < len/sizeof(unsigned int); i++)
((unsigned int *) p)[i] += shift[pe - 1];
if (status >= 0 && len > 0 && ws_send (fd, p, len) < len)
= -1;
status if (pe > 0)
free (p);
}
}
else { // pid() > 0
(&a->len, 1, MPI_LONG, 0, 22, MPI_COMM_WORLD);
MPI_Send if (a->len > 0)
(a->p, a->len, MPI_BYTE, 0, 23, MPI_COMM_WORLD);
MPI_Send }
#else
if (status >= 0 && a->len > 0 && ws_send (fd, a->p, a->len) < a->len)
= -1;
status #endif
return status;
}
static int display_send (const char * command, int fd)
{
int status = 0;
("sending '%s' to %d\n", command, fd);
debug
unsigned int commandlen = strlen (command);
unsigned int errorlen = Display.error ? strlen (Display.error) : 0;
int paddedlen = 4*ceil(commandlen/4.);
size_t len = 2*sizeof(unsigned int) + paddedlen;
unsigned int lens[] = {VertexBuffer.position->len,
.normal->len,
VertexBuffer.color->len,
VertexBuffer.index->len}, glens[4];
VertexBufferint type = VertexBuffer.type, gtype;
#if _MPI
(lens, glens, 4, MPI_UNSIGNED, MPI_SUM, 0, MPI_COMM_WORLD);
MPI_Reduce (&type, >ype, 1, MPI_INT, MPI_MAX, MPI_COMM_WORLD);
MPI_Allreduce #else
for (int i = 0; i < 4; i++) glens[i] = lens[i];
= type;
gtype #endif
if (gtype < 0)
+= errorlen;
len
else+= 2*sizeof(int) +
len 4*sizeof (unsigned int) + glens[0] + glens[1] + glens[2] + glens[3];
if (pid() == 0) {
if (ws_sendframe_init (fd, len, false, WS_FR_OP_BIN) < 0 ||
(fd, &commandlen, sizeof(unsigned int)) < sizeof(unsigned int) ||
ws_send (fd, &errorlen, sizeof(unsigned int)) < sizeof(unsigned int) ||
ws_send (fd, command, commandlen) < commandlen)
ws_send = -1;
status
// padding to a multiple of four
for (int i = 0; i < paddedlen - commandlen && status >= 0; i++) {
char c = '\0';
if (ws_send (fd, &c, 1) < 1)
= -1;
status }
}
if (gtype < 0) {
if (pid() == 0 && status >= 0 &&
(fd, Display.error, errorlen) < errorlen)
ws_send = -1;
status }
else {
if (pid() == 0 && status >= 0 &&
(ws_send (fd, &VertexBuffer.dim, sizeof(int)) < sizeof(int) ||
(fd, >ype, sizeof(int)) < sizeof(int) ||
ws_send (fd, glens, 4*sizeof (unsigned int)) < 4*sizeof (unsigned int)))
ws_send = -1;
status unsigned int * shift = malloc (sizeof(unsigned int)*npe());
= ws_send_array (fd, VertexBuffer.position, status, 0, shift);
status = ws_send_array (fd, VertexBuffer.normal, status, -1, shift);
status = ws_send_array (fd, VertexBuffer.color, status, -1, shift);
status = ws_send_array (fd, VertexBuffer.index, status, 1, shift);
status free (shift);
}
#if _MPI
(&status, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast #endif
return status;
}
static void display_add (const char * command, int fd)
{
("adding '%s'\n", command);
debug khiter_t k = kh_get (strhash, Display.objects, command);
if (k == kh_end (Display.objects)) {
int ret;
= kh_put (strhash, Display.objects, strdup (command), &ret);
k DisplayClient * client = malloc (sizeof(DisplayClient));
->fd = -1;
client(Display.objects, k) = client;
kh_value }
DisplayClient * clients = kh_value (Display.objects, k), * client = clients;
int len = 0;
while (client->fd >= 0) {
if (client->fd == fd)
= -1;
fd ++, len++;
client}
if (fd >= 0) { // not already in the array
(Display.objects, k) = clients =
kh_value (clients, (len + 2)*sizeof (DisplayClient));
realloc [len].fd = fd;
clients[len].iter = -1;
clients[len + 1].fd = -1;
clients}
display_display();
}
#define JSON_BUILD(...) len += snprintf (build + len, 4096, __VA_ARGS__), \
= realloc (build, len + 4096)
build
static void array_remove (khiter_t k, int fd)
{
DisplayClient * clients = kh_value (Display.objects, k), * client = clients;
int i = -1, len = 0;
while (client->fd >= 0) {
if (client->fd == fd) {
if (i != -1)
("array_remove(): error! found multiple %d in '%s'\n",
debug , kh_key (Display.objects, k));
fd= len;
i }
++, len++;
client}
if (i < 0)
("array_remove(): error! could not find %d in '%s'\n",
debug , kh_key (Display.objects, k));
fdelse if (len == 1) {
free ((void *) kh_key (Display.objects, k));
free ((void *) kh_value (Display.objects, k));
(strhash, Display.objects, k);
kh_del }
elsefor (int j = i; j < len; j++)
[j] = clients[j + 1];
clients}
static void display_remove (const char * command, int fd)
{
("removing '%s'\n", command);
debug khiter_t k = kh_get (strhash, Display.objects, command);
if (k == kh_end (Display.objects))
("display_remove(): error! could not find '%s' (%d)\n",
debug , fd);
command
elsearray_remove (k, fd);
display_display();
}
typedef struct {
char * name, * tooltip;
void * ptr;
double min, max;
int size;
} DisplayControl;
static char * display_control_json()
{
char * build = malloc (4096);
int len = 0;
("#{");
JSON_BUILD char sep = ' ';
DisplayControl * d = Display.controls->p;
for (int i = 0; i < Display.controls->len/sizeof(DisplayControl); i++, d++) {
("%c\n \"%s\": { ", sep, d->name); sep = ',';
JSON_BUILD if (d->tooltip)
("\"tooltip\": \"%s\", ", d->tooltip);
JSON_BUILD switch (d->size) {
case 4:
("\"type\": \"int\", \"value\": %d, \"min\": %g, \"max\": %g",
JSON_BUILD *((int *)d->ptr), d->min, d->max);
break;
case 8:
("\"type\": \"double\", \"value\": %g, "
JSON_BUILD "\"min\": %g, \"max\": %g",
*((double *)d->ptr), d->min, d->max);
break;
default:
assert (false);
}
(" }");
JSON_BUILD }
("}");
JSON_BUILD return build;
}
static DisplayControl * display_control_lookup (const char * name)
{
DisplayControl * d = Display.controls->p;
for (int i = 0; i < Display.controls->len/sizeof(DisplayControl); i++, d++)
if (!strcmp (d->name, name))
return d;
return NULL;
}
void display_control_internal (void * ptr,
double min, double max,
char * name = NULL, char * tooltip = NULL,
char * ptr_name, int size)
{
DisplayControl d;
if (!name)
= ptr_name;
name
if (display_control_lookup (name))
return;
.name = strdup (name);
d.tooltip = tooltip ? strdup (tooltip) : NULL;
d.ptr = ptr;
d.size = size;
d.min = min, d.max = max;
d(Display.controls, &d, sizeof (DisplayControl));
array_append
if (pid() == 0) {
char * controls = display_control_json();
(0, controls, true);
ws_sendframe_txt free (controls);
}
}
#undef display_control
#define display_control(val, ...) display_control_internal \
(&(val), __VA_ARGS__, size = sizeof(val), ptr_name = #val)
static void display_control_update (const char * command, int fd)
{
char * s = strdup (command), * s1 = strchr (command, ':');
*s1++ = '\0';
DisplayControl * d = display_control_lookup (command);
if (d == NULL)
("display_control_update(): error! could not find '%s' (%d)\n",
debug , fd);
commandelse {
("display_control_update (%s) = %s\n", command, s1);
debug double val = atof(s1);
if (d->max > d->min)
= clamp (val, d->min, d->max);
val switch (d->size) {
case 4: *((int *)d->ptr) = val; break;
case 8: *((double *)d->ptr) = val; break;
default: assert (false);
}
if (pid() == 0) {
char * controls = display_control_json();
(- fd, controls, true);
ws_sendframe_txt free (controls);
}
}
free (s);
}
static char * bview_interface_json()
{
char * build = malloc (4096);
int len = 0;
("{\n");
JSON_BUILD
int i = 0;
while (bview_interface[i].json) {
("%s", i ? ",\n" : "");
JSON_BUILD += bview_interface[i].json (build + len, 4096);
len = realloc (build, len + 4096);
build ("\n");
JSON_BUILD ++;
i}
("}");
JSON_BUILD return build;
}
void display_onclose (int fd)
{
("closing %d\n", fd);
debug for (khiter_t k = kh_begin (Display.objects); k != kh_end (Display.objects);
++k)
if (kh_exist (Display.objects, k))
array_remove (k, fd);
display_display();
}
void display_onmessage (int fd, const char * msg, size_t size, int type)
{
if (type == WS_FR_OP_TXT) {
if (!msg)
fprintf (stderr, "error receiving data on websocket\n");
else switch (msg[0]) {
case '+': display_add (msg + 1, fd); break;
case '-': display_remove (msg + 1, fd); break;
case '#': display_control_update (msg + 1, fd); break;
default: fprintf (stderr,
"display_onmessage: error: unknown message type '%s'\n",
);
msgbreak;
}
}
elsefprintf (stderr, "display_onmessage: error: unexpected message type '%d'\n",
);
type}
void display_onopen (int fd)
{
char * interface = bview_interface_json();
char * controls = display_control_json();
int status = 0;
if (pid() == 0)
if (ws_sendframe_txt (fd, interface, false) < 0 ||
(fd, controls, false) < 0 ||
ws_sendframe_txt (display_defaults && ws_sendframe_txt (fd, display_defaults, false) < 0))
= -1;
status free (interface);
free (controls);
#if _MPI
(&status, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast #endif
("open %d status %d\n", fd, status);
debug
if (status < 0) {
display_onclose (fd);
if (pid() == 0)
(fd);
close }
}
static void display_update (int i)
{
for (khiter_t k = kh_begin (Display.objects); k != kh_end (Display.objects);
++k)
if (kh_exist (Display.objects, k)) {
DisplayClient * client = kh_value (Display.objects, k);
while (client->fd >= 0) {
if (client->iter < i)
break;
++;
client}
if (client->fd >= 0) { // at least one client needs update
const char * command = kh_key (Display.objects, k);
display_command (command);
= kh_value (Display.objects, k);
client while (client->fd >= 0) {
if (client->iter < i) {
->iter = i;
clientif (display_send (command, client->fd) < 0) {
("error sending '%s' to '%d'\n", command, client->fd);
debug if (pid() == 0)
(client->fd);
close display_onclose (client->fd);
if (!kh_exist (Display.objects, k))
break;
}
else++;
client}
else++;
client}
vertex_buffer_free();
if (Display.error && kh_exist (Display.objects, k)) {
free ((void *) kh_key (Display.objects, k));
free ((void *) kh_value (Display.objects, k));
(strhash, Display.objects, k);
kh_del }
}
}
}
#if _MPI
static Array * pack_messages (struct ws_message * messages)
{
struct ws_message * msg = messages;
* packed = array_new();
Array while (msg && msg->fd >= 0) {
(packed, msg, sizeof(struct ws_message));
array_append (packed, msg->msg, msg->size);
array_append ++;
msg}
return packed;
}
static struct ws_message * unpack_messages (Array * packed)
{
* array = array_new();
Array char * p = packed->p;
while (p - (char *) packed->p < packed->len) {
struct ws_message * msg = (struct ws_message *) p;
->msg = sysmalloc (msg->size + 1);
msg+= sizeof(struct ws_message);
p (msg->msg, p, msg->size);
memcpy ->msg[msg->size] = '\0';
msg(array, msg, sizeof(struct ws_message));
array_append += msg->size;
p }
struct ws_message msg = {-1};
(array, &msg, sizeof(struct ws_message));
array_append struct ws_message * messages = array->p;
free (array);
return messages;
}
#endif
int display_poll (int timeout)
{
struct ws_message * messages = NULL, * msg;
int nmsg = 0;
if (pid() == 0) {
= messages = ws_socket_poll (Display.sock, timeout);
msg while (msg && msg->fd >= 0) msg++, nmsg++;
}
#if _MPI
* packed;
Array if (pid() == 0)
= pack_messages (messages);
packed
else= calloc (1, sizeof (Array));
packed (&packed->len, 1, MPI_LONG, 0, MPI_COMM_WORLD);
MPI_Bcast if (packed->len > 0) {
if (pid() != 0)
->p = malloc (packed->len);
packed(packed->p, packed->len, MPI_BYTE, 0, MPI_COMM_WORLD);
MPI_Bcast if (pid() != 0)
= unpack_messages (packed);
messages }
(packed);
array_free #endif
= messages;
msg = 0;
nmsg while (msg && msg->fd >= 0) {
switch (msg->type) {
case WS_FR_OP_OPEN:
display_onopen (msg->fd); break;
case WS_FR_OP_TXT: case WS_FR_OP_BIN:
display_onmessage (msg->fd, msg->msg, msg->size, msg->type);
break;
case WS_FR_OP_CLSE:
display_onclose (msg->fd); break;
default:
assert (false);
}
(msg->msg);
sysfree ++, nmsg++;
msg}
(messages);
sysfree return nmsg;
}
void display_url (FILE * fp)
{
char hostname[1024];
[1023] = '\0';
hostname(hostname, 1023);
gethostname struct hostent * h = gethostbyname (hostname);
if (!h)
fprintf (stderr,
"src/display.h:%d: warning: gethostbyname(\"%s\") returned NULL\n",
, hostname);
LINENOfprintf (fp, DISPLAY_JS "?ws://%s:%d", h ? h->h_name : "127.0.0.1",
.port);
Display}
int display_usage = 20; // use 20% of runtime, maximum
#ifdef DISPLAY
// negative: pause, zero: play, positive: step
int display_play = DISPLAY < 0 ? -1 : 0;
#else
int display_play = -1;
#endif
static void display_destroy()
{
for (khiter_t k = kh_begin (Display.objects); k != kh_end (Display.objects);
++k)
if (kh_exist (Display.objects, k)) {
free ((void *) kh_key (Display.objects, k));
free ((void *) kh_value (Display.objects, k));
}
(strhash, Display.objects);
kh_destroy
DisplayControl * d = Display.controls->p;
for (int i = 0; i < Display.controls->len/sizeof(DisplayControl); i++, d++) {
free (d->name);
free (d->tooltip);
}
(Display.controls);
array_free
if (pid() == 0) {
("display.html");
remove
// fixme: close connection cleanly
(Display.sock);
close }
}
init_solver
@void display_init()
{
if (pid() == 0) {
const char * port = DISPLAY_RANGE;
if (!strchr (port, ':'))
.sock = ws_socket_open (atoi (port));
Displayelse {
char * s = strdup (port);
char * s1 = strchr (s, ':');
*s1++ = '\0';
int pmax = atoi(s1);
.port = atoi(s);
Displaywhile ((Display.sock = ws_socket_open (Display.port)) < 0 &&
.port < pmax)
Display.port++;
Displayfree (s);
}
if (Display.sock < 0) {
char s[80];
sprintf (s, "display(): could not open port '%s'", port);
(s);
perror exit (1);
}
FILE * fp = fopen ("display.html", "w");
if (!fp)
("display.html");
perror else {
("<head><meta http-equiv=\"refresh\" content=\"0;URL=", fp);
fputs display_url (fp);
("\"></head>\n", fp);
fputs fclose (fp);
}
}
.objects = kh_init (strhash);
Display.controls = array_new();
Display
(display_destroy);
free_solver_func_add
#ifndef DISPLAY
(display_play, -1, 1, "Run/Pause");
display_control #elif DISPLAY != 0
(display_play, -1, 1, "Run/Pause");
display_control #endif
#ifndef DISPLAY_NO_CONTROLS
(display_usage, 0, 50, "Display %",
display_control "maximum % of runtime used by display");
#endif
}
event refresh_display (i++, last)
{
do {
if (display_play)
display_update (i);
if (display_poll (display_play ? - 1 : 0))
display_update (i);
} while (display_play < 0);
static timer global_timer = {0};
static double poll_elapsed = 0.;
int refresh = (poll_elapsed <=
/100.*timer_elapsed (global_timer));
display_usage#if _MPI
(&refresh, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast #endif
if (refresh) {
= timer_start();
global_timer display_update (i);
= timer_elapsed (global_timer);
poll_elapsed }
}