This page will tell you enough to go away and build the clock application. It describes the build and running of the clock application, and also gives an annotation of the code to explain how the application works.
The clock is a dive application so if you run it will happily sit there and work away in the background. You won't see anything for two reasons:
Remember that vishnu is just an application as well, just it is more sophisicated and it renders a view of the database.
So to see your new clock in action:
I've taken some of the superfluous code out in this annotation.
Standard includes. Getting these right is a bit hit and miss.
#include <X11/Xlib.h> #include <X11/Xutil.h> #include <sys/time.h> #include <time.h> #include <math.h> #include "dive/dive.h" #include "dive/lammopt.h" #include "dive/ai_util.h" #include "dive/vrx.h" #include "dive/menu.h" #include "dive/statistics.h"
Name of the world to connect to
#ifndef DEFAULT_STARTING_WORLD
#define DEFAULT_STARTING_WORLD "local"
#endif
Dive object that contains the clock
#define CLOCK_URL "http://www.sics.se/dive/manual/examples/clock.vr"
Parameters that specify how to connect to the diveserver
static int distr_mode; static int distr_debug; static int ttl = 2; static char *nameserver_address; static int nameserver_port; static char *world_name; static divebool printversion;
Our own struct to keep the time and object ids in.
static struct { int hour; int min; int sec; objid_t clock_id; objid_t sec_id; objid_t min_id; objid_t hour_id; } ClockData = { 0, 0, 0, };
Some useful macros to update the clock hands
#define update_hour(id, hours) \ distr_abs_fixedXYZ(id, 0.0, 0.0, ((hours)%12)*(M_PI/6), LOCAL_C, NULL, NULL) #define update_hand(id, seconds) \ distr_abs_fixedXYZ(id, 0.0, 0.0, seconds*(M_PI/30), LOCAL_C, NULL, NULL)
Parse command line options. The first six options are standard for every dive application and specify how the distribution is set up. The seventh is how we specify which world to connect to.
static void command_line(int argc, char *argv[]) { LambOption opts[] = { {"debug", REQ_ARG, &dive_debug, setint, "0", ""}, {"distr_debug", REQ_ARG, &distr_debug, setint, "0", ""}, {"singleuser" ,NO_ARG, &distr_mode, setbool, True_val, False_val}, {"version" , NO_ARG, &printversion, setbool, False_val, True_val}, {"nameserver_addr", REQ_ARG, &nameserver_address, setstr, DIVESERVER_ADDRESS, ""}, {"nameserver_port", REQ_ARG, &nameserver_port, setint, DIVESERVER_STRPORT, ""}, {"ttl", REQ_ARG, &ttl, setint, "2", ""}, {"world", REQ_ARG, &world_name, setstr, DEFAULT_STARTING_WORLD, ""} }; if( !lammopt(argc, argv, opts, sizeof(opts) / sizeof(*opts), TRUE, NULL, NULL, NULL) ) exit(0); }
on_timer is a periodically called function based on a dive alarm. It is called once a second. It increments the time counter and rotates the dive object for the hands accordingly.
static void on_timer(void *arg) { if (!find_entity(&ClockData.clock_id)) dive_exit(0); ClockData.sec ++; if (ClockData.sec == 60) { ClockData.sec = 0; ClockData.min ++; if (ClockData.min == 60) { ClockData.min = 0; ClockData.hour ++; if (ClockData.hour == 24) { ClockData.hour = 0; } update_hour(&ClockData.hour_id, ClockData.hour); } update_hand(&ClockData.min_id, ClockData.min); } update_hand(&ClockData.sec_id, ClockData.sec); }
Set the current time to the system clock and update the hands. This is called by the interaction callback since the dive alarms will diverge from the true time after a while.
static void set_current_time() { time_t t; struct tm *tm; /* Get current system time and update our own structure */ t = time(NULL); tm = localtime(&t); ClockData.hour = tm->tm_hour; ClockData.min = tm->tm_min; ClockData.sec = tm->tm_sec; /* Update the position of the clock hands */ update_hour(&ClockData.hour_id, ClockData.hour); update_hand(&ClockData.min_id, ClockData.min); update_hand(&ClockData.sec_id, ClockData.sec); }
The interaction callback. This is registered to be called with the dive database
static void interaction_cb(objid_t *dest_id, interaction_type_t type, objid_t *pid, objid_t *src_id, point_t *pt, objid_t *viewid,void *arg) { /* No source or no destination! */ if (!src_id || !dest_id) return ; /* If the destination identifier is not the board of the clock */ if (!objid_eq(dest_id, &ClockData.clock_id)) return ; switch (type) { case DIVE_IA_SELECT: /* Setup clock to current time when button is pressed */ set_current_time(); break; default: break; } }
When the application exists we want to remove our own object from the database.
static void clock_exit(void *arg) { distr_entity_remove(&ClockData.clock_id, NULL); }
Our initilization function.
static void clock_init(objid_t *wid) { entity *obj;
Read the clock object from a file
if (distr_readURL(wid, CLOCK_URL, NULL, &ClockData.clock_id) < 0) dive_error("clock_init: Could not read clock data from %s.", CLOCK_URL);
Find the three subobjects that correspond to the hands and keep a note since these will be rotated later on
if (obj = find_sub_byname(&ClockData.clock_id, "Hour Hand")) ClockData.hour_id = obj->id; else dive_error("clock_init: Hour hand not found in %s.", CLOCK_URL); if (obj = find_sub_byname(&ClockData.clock_id, "Minute Hand")) ClockData.min_id = obj->id; else dive_error("clock_init: Minute hand not found in %s.", CLOCK_URL); if (obj = find_sub_byname(&ClockData.clock_id, "Second Hand")) ClockData.sec_id = obj->id; else dive_error("clock_init: Second hand not found in %s.", CLOCK_URL);
Get the initial system time
set_current_time();
Register the interaction callback
callback_register_filter(INTERACTION_SIGNAL, interaction_cb, 0, &ClockData.clock_id, NULL, 0x0, NULL);
Register the exit function
dive_add_exit_fn(clock_exit, NULL); }
Where it starts
void main(int argc, char *argv[]) { objid_t wid;
Standard set up
command_line(argc, argv); /* Printout the DIVE version number */ if (printversion){ fprintf(stdout,"%s\n", DIVE_VERSION); exit(0); } /* Set multicast TTL level: Must be set before nameserver lookup */ dive_distr_ttl(ttl); /* Initialize DIVE */ dive_init(NULL); dive_distr_debug(distr_debug); if (distr_mode){ if (ds_init(nameserver_address, nameserver_port) < 0) set_distr_mode(SOFT_SINGLEUSER); }
Connect to the world using the standard or optional name
objid_zero(&wid); distr_world_connect(world_name, &wid);
Tell the clock to load into this world (registers interaction callback and exit functions)
clock_init(&wid);
Initialise and register and alarm call
alarm_init(1000); alarm_add(on_timer, NULL);
The main loop
threads_main_loop(); }
Anthony Steed, A.Steed@cs.ucl.ac.uk