msb.c
and
msb.h
which have more information about
using MSB. There is an MSB type used internally by MSB to reflect the
definitions of MSB types implemented in msb.c
which can serve as an example for how to create a new type.
For the example I'm using a "classical" struct stat
because it is familiar and the binding can be simple or complex.
I wouldn't really recommend putting stat in your server.
Modern struct stat
is a bit complex but here's what
it looked like in SVR4. You don't actually have to know what your structure
looks like internally, you just need to know what members it has and what
their types are. If you look in
/usr/include/sys/stat.h
you'll probably see
more fields and some of these may even be provided as macros, like
st_atime
,
st_mtime
and
st_ctime
. They should still work for our example.
struct stat { dev_t st_dev; ino_t st_ino; mode_t st_mode; nlink_t st_nlink; uid_t st_uid; gid_t st_gid; dev_t st_rdev; off_t st_size; time_t st_atime; time_t st_mtime; time_t st_ctime; };
"stat"
and the C variables will have the prefix
msb_stat
. You can choose whatever you like. New
types have a string name which is available in MOO code as
var.class.name
.
There are several
type accessors provided with MSB, so use those if you can. They all start
with msbp_
followed by a typename. The types in
your structure
may be masked by typedefs, but if you can figure out what they actually
are you can choose to ignore that distinction. In
/usr/include/sys/stat.h
you can see that a
ino_t
is really an
int
, or close enough for our purposes. If you
want to maintain the abstraction, you can create your own type
accessors (described later) or use a #define
to add an alias to the property accessor:
/* * This isn't strictly necessary but it clarifies how you are * overloading the property accessors. */ #define msbp_ino_t msbp_int #define msbp_dev_t msbp_int #define msbp_mode_t msbp_ushort #define msbp_nlink_t msbp_int #define msbp_uid_t msbp_int #define msbp_gid_t msbp_int #define msbp_off_t msbp_int #define msbp_time_t msbp_int msb_property_t msb_stat_properties[] = { /* name, offset in structure, accessor func, flags */ { "st_dev", MsbOffset(struct stat, st_dev), msbp_dev_t, MSBP_F_R }, { "st_ino", MsbOffset(struct stat, st_ino), msbp_ino_t, MSBP_F_R }, { "st_mode", MsbOffset(struct stat, st_mode), msbp_mode_t, MSBP_F_R }, { "st_nlink", MsbOffset(struct stat, st_nlink), msbp_nlink_t, MSBP_F_R }, { "st_uid", MsbOffset(struct stat, st_uid), msbp_uid_t, MSBP_F_R }, { "st_gid", MsbOffset(struct stat, st_gid), msbp_gid_t, MSBP_F_R }, { "st_size", MsbOffset(struct stat, st_size), msbp_off_t, MSBP_F_R }, { "st_atime", MsbOffset(struct stat, st_atime), msbp_time_t, MSBP_F_R }, { "st_mtime", MsbOffset(struct stat, st_mtime), msbp_time_t, MSBP_F_R }, { "st_ctime", MsbOffset(struct stat, st_ctime), msbp_time_t, MSBP_F_R }, { NULL } };
Notes:
st_rdev
because MOO doesn't care about the major/minor of a device.
var.class:properties()
.
msb_no_properties
which is an empty list of
property definitions.
I can't think of a natural operation on a struct stat
so I offer a slightly contrived one: finding the age of the file.
msb_verb_t msb_stat_verbs[] = { /* name, function, flags, min args, max args, prototype... */ { "age", stat_age, MSBV_F_ALLX, 0, 0 }, { NULL } };
Notes:
"set_*"
. All verb handlers are called with
the verbname so you can figure out what verb was actually called.
If you use any wildcarded verbs, remember that verbs will be matched
from first to last.
var.class:verbs()
.
register_function()
interface.
MSBV_MAX_PROTOTYPE
entries in your prototype list, just increase that value in
msb.h
. If you forget the server will panic
when your type is registered. Increasing that value doesn't make individual
MSBs larger, just the type definitions.
msb_no_verbs
which is an empty list of
verb definitions.
There is a convenience macro MSBV_DECL(name)
which declares a function named name with the right arguments
for a msbv_handler_t
. It actually looks like this:
package name(MSB *m, void *ptr, const char *verbname, Objid progr, Var arglist)
Using the macro protects you from interface changes. The function is
called with the following values:
MSB *m
: the MSB instance that the verb was called on.
void *ptr
: the private data assoicated with this
instance. For a live MSB (created with msb_new()
) it
is the actual pointer passed in. For copies
(created with msb_new_copy()
) it is the copy. You
may want to translate a copy into a live data structure for your operation.
const char *verbname
: the name the verb was called
with. Only useful if your verb has a wildcard name.
Objid progr
: the equivalent of
caller_perms()
in MOO-code.
Var arglist
: the arguments passed in. These
have already been checked against your prototype for you.
package
like a builtin function.
It must not return a BI_CALL
package!
This means that MSB verbs cannot call back into the interpreter. This
may be supported eventually.
Our example takes no args, so the argument checking is complete before the function is even called:
MSBV_DECL(stat_age) { struct stat *sbp = (struct stat *)ptr; Var result; /* no args to extract */ /* must always free arglist */ free_var(arglist); result.type = TYPE_INT; result.v.num = time(0) - sbp->st_mtime; return make_var_pack(result); }
Currently this is incompletely implemented.
msb_null_ops
which is all-NULL (or at least
all no-op) operations.
Currently this is incompletely implemented.
msb_t
which gathers the last three
things together:
msb_t msb_stat = { "stat", msb_stat_properties, msb_stat_verbs, &msb_stat_ops, sizeof(struct stat), MSB_F_WRITABLE };There is an MSB-supplied MSB for this structure, and MOO references to
yourmsb.class
will return it. The class
definition is reflected in-MOO as:
yourmsb.class.name
yourmsb.class:properties()
yourmsb.class:verbs()
msb_init_type(&msb_stat);
msbp_handler_t
for the entries of that larger
structure.
You could also introduce values by using them as arguments to verbs
which you call with
run_server_task()
.
Somehow your code must introduce the values into the database, as there is no syntax or MSB-supplied builtin for creating instances of your new type.
Here we take the builtin approach. This code makes
stat("file")
return the stat MSB we're making:
static package bf_stat(Var arglist, Byte next, void *vdata, Objid progr) { const char *filename = arglist.v.list[1].v.str; struct stat sb; /* your perms check may not be so strict */ if (!is_wizard(progr)) { free_var(arglist); return make_error_pack(E_PERM); } if (-1 == stat(filename, &sb)) { Var bad = var_ref(arglist.v.list[1]); free_var(arglist); return make_raise_pack(E_INVARG, strerror(errno), bad); } free_var(arglist); return make_var_pack(msb_new_copy(&msb_stat, progr, &sb)); }
register_function("stat", 1, 1, bf_stat, TYPE_STR);
msb_stat
> ;stat("/etc/motd"); => <<class.name->stat owner->#2 st_dev->160800 st_ino->46 st_mode->33188 st_nlink->1 st_uid->0 st_gid->0 st_size->373 st_atime->984080406 st_mtime->983836012 st_ctime->983836012>> > @prop me.tmp Property added with value 0. > ;me.tmp = stat("/homes/bjj/Mail") => <<class.name->stat owner->#2 st_dev->18954 st_ino->26880 st_mode->16832 st_nlink->459341865 st_uid->7009 st_gid->1233 st_size->2048 st_atime->984104214 st_mtime->984111545 st_ctime->984111545>> > ;me.tmp:age() => 30 > ;me.tmp.class => <<class.name->msb_class owner->#2 name->"stat">> > ;me.tmp.class:verbs() => {"age"} > ;me.tmp.class:properties() => {"st_dev", "st_ino", "st_mode", "st_nlink", "st_uid", "st_gid", "st_size", "st_atime", "st_mtime", "st_ctime"}
xpbinding.c
{ "score", xp_player_score, 0, 4, 4, TYPE_INT, TYPE_INT, TYPE_INT, TYPE_STR }, static MSBV_DECL(xp_player_score) { player *pl_copy = (player *)ptr, *pl; int points = arglist.v.list[1].v.num; int x = arglist.v.list[2].v.num; int y = arglist.v.list[3].v.num; const char *msg = str_ref(arglist.v.list[4].v.str); package err; Var newscore; free_var(arglist); if (NULL == (pl = xp_player_copy_to_real(pl_copy, &err))) { free_str(msg); return err; } /* XPilot now locked */ SCORE(GetInd[pl->id], points, x, y, msg); newscore.type = TYPE_INT; newscore.v.num = pl->score; UNLOCK_XPILOT(); free_str(msg); return make_var_pack(newscore); }
msb_property_t msb_xp_ivec_properties[] = { { "x", MsbOffset(ivec, x), msbp_int, MSBP_F_R }, { "y", MsbOffset(ivec, y), msbp_int, MSBP_F_R }, { NULL } }; msb_t msb_xp_ivec = { "xp_ivec", msb_xp_ivec_properties, msb_no_verbs, &msb_null_ops, sizeof(ivec), MSB_F_WRITABLE }; MSBP_DECL(ivec) { if (write) return E_NACC; else { *inout = msb_new_parent(&msb_xp_ivec, msb_owner(m), ptr, m); return E_NONE; } }
MSBP_DECL(modifiers) { modifiers mods = *((modifiers *) ptr); if (write) return E_NACC; else { static Stream *s; char *modstr; if (!s) s = new_stream(20); if (BIT(mods.nuclear, FULLNUCLEAR)) stream_add_char(s, 'F'); if (BIT(mods.nuclear, NUCLEAR)) stream_add_char(s, 'N'); if (BIT(mods.warhead, CLUSTER)) stream_add_string(s, " C"); if (BIT(mods.warhead, IMPLOSION)) stream_add_string(s, " I"); if (mods.velocity) stream_printf(s, " V%d", mods.velocity); if (mods.mini) stream_printf(s, " X%d", mods.mini); if (mods.spread) stream_printf(s, " Z%d", mods.spread); if (mods.power) stream_printf(s, " B%d", mods.power); if (mods.laser) { stream_add_string(s, " L"); stream_add_char(s, (BIT(mods.laser, STUN)) ? 'S':'B'); } modstr = reset_stream(s); if (modstr[0] == ' ') ++modstr; inout->type = TYPE_STR; inout->v.str = str_dup(modstr); return E_NONE; } }
{ "set_*", xp_player_set_star, 0, 1, 1, TYPE_ANY }, static MSBV_DECL(xp_player_set_star) { Var value = arglist.v.list[1], changed; player *pl_copy = (player *)ptr, *pl; package err; enum error res, cres; Var live; if (NULL == (pl = xp_player_copy_to_real(pl_copy, &err))) { free_var(arglist); return err; } /* XPilot now locked */ /* * Create a new MSB that points to the *active* pl data. * This must not escape into the wild! It will only be used * as a handle to perform a live property update. */ live = msb_new(&msb_xp_player, msb_owner(m), pl); /* * msb_put_prop will handle perms checking for us. The live * instance has the same ownership as the original. The * msbp_handler_t for this value will handle typechecking. */ res = msb_put_prop(live.v.msb, verbname + 4, value, progr); /* * If that worked, re-get the property to return to the caller, * who might want to notice if there was some kind of roundoff * error. However, failure of this step is not an error, it * just means we return 0. */ if (res == E_NONE) { cres = msb_get_prop(live.v.msb, verbname + 4, &changed, progr); } UNLOCK_XPILOT(); free_var(live); free_var(arglist); if (res != E_NONE) return make_error_pack(res); else if (cres == E_NONE) return make_var_pack(changed); else return no_var_pack(); }