SPREE(2)SPREE(2)NAME
Spree - distributed interactive sessions.
SYNOPSIS
include "sys.m";
include "draw.m";
include "sets.m";
include "spree.m";
spree := load Spree Spree->PATH;
Range, Object, Clique, Member: import spree;
Set: import Sets;
Archive: import Archives;
Range: adt {
start: int;
end: int;
};
Object: adt {
transfer: fn(o: self ref Object,
r: Range, dst: ref Object, i: int);
setvisibility: fn(o: self ref Object,
visibility: Set);
setattrvisibility: fn(o: self ref Object,
name: string, visibility: Set);
setattr: fn(o: self ref Object,
name: string, val: string, vis: Set);
getattr: fn(o: self ref Object, name: string): string;
delete: fn(o: self ref Object);
deletechildren: fn(o: self ref Object, r: Range);
id: int;
parentid: int;
children: array of ref Object;
objtype: string;
visibility: Set;
# ...private data
};
Clique: adt {
new: fn(parent: self ref Clique, archive: ref Archive,
owner: string): (int, string, string);
newobject: fn(clique: self ref Clique, parent: ref Object,
visibility: int, objtype: string): ref Object;
action: fn(clique: self ref Clique, cmd: string,
objs: list of int, rest: string, whoto: int);
member: fn(clique: self ref Clique, id: int): ref Member;
start: fn(clique: self ref Clique);
breakmsg: fn(clique: self ref Clique, whoto: Sets->Set);
members: fn(clique: self ref Clique): list of ref Member;
owner: fn(clique: self ref Clique): string;
hangup: fn(clique: self ref Clique);
notify: fn(clique: self ref Clique, cliqueid: int, msg: string);
objects: array of ref Object;
cliqueid: int;
# ...private data
};
Member: adt {
obj: fn(m: self ref Member, id: int): ref Object;
del: fn(m: self ref Member, suspend: int);
id: int;
name: string;
# ...private data
};
Engine: module {
init: fn(srvmod: Spree, clique: ref Clique, argv: list of string): string;
command: fn(member: ref Member, e: string): string;
join: fn(member: ref Member , e: string, suspended: int): string;
leave: fn(member: ref Member): int;
notify: fn(fromid: int, s: string);
readfile: fn(f: int, offset: big, count: int): array of byte;
};
Archives: module {
Archive: adt {
argv: list of string; # how to restart the session.
members: array of string; # members involved.
info: list of (string, string); # any other information.
objects: array of ref Object;
};
init: fn(mod: Spree);
write: fn(clique: ref Clique, info: list of (string, string), file: string, members: Set): string;
read: fn(file: string): (ref Archive, string);
readheader: fn(file: string): (ref Archive, string);
};
rand: fn(n: int): int;
DESCRIPTION
Spree provides a general server interface that allows sets of distrib‐
uted clients, cliques, to interact in a controlled manner, with the
interaction mediated by Limbo modules, known as engines. Each engine
decides on the rules of its particular clique; the engine interface is
described at the end of this manual page, under ``Module Interface''.
This manual page describes the interface as presented to an engine once
it has been loaded by spree. A loaded instance of an engine is respon‐
sible for a particular clique, in which one or more members partici‐
pate. Messages sent by members are interpreted by the engine, which
responds by making changes to the hierarchical object database held by
the clique. Behind the scenes spree distributes updates to this data‐
base to members of the clique as appropriate (see spree(4) for
details).
Objects and visibility
Objects hold a clique's visible state. An object has a unique integer
id, which is an index into the array clique.objects; it also holds a
set of attribute-value pairs, a type, and zero or more child objects.
Together, all the objects in the clique form a hierarchical tree,
rooted at the root object (id 0), which always exists. Each attribute
and each object also has an associated visibility set, the set of mem‐
ber that sees updates to the attributes or the children of the object.
Each member has a unique id; in a visibility set (see sets(2)), a mem‐
ber is ``visible'' if the set contains the member's id.
Note that the visibility set of an object does not alter the visibility
of that object's attributes, but only that of its children (and of
their children: in general an object is visible to a member if the
intersection of all its ancestors' visibility sets contains that mem‐
ber).
Objects can be transferred inside the hierarchy from one parent to
another. If an object is moved to a parent whose visibility conceals it
from a member, then it will appear to that member to have been deleted;
if it is later made visible, then it will be recreated for that member.
A clique engine can almost always ignore this technicality, except for
one thing: the identifier used by a particular member to identify an
object is not necessarily the same as that used by the clique engine.
Thus when an engine receives an object id in a member's message, it
should convert it using the member.obj() function.
Clique
The Clique type holds all the objects in a clique. It allows the cre‐
ation of new objects, and provides a way of communicating with members
directly. All data members of a Clique should be treated as read-only.
clique.objects
This array holds the objects in the clique. An object with
identifier id is found at clique.objects[id].
clique.new(archive,owner)
New creates a new clique. Archive is an archive of the game
to be created; archive.argv should be non-nil; its first ele‐
ment should name the engine to be loaded (as a path relative
to the engine module directory, and without the .dis exten‐
sion).
clique.newobject(parent, visibility, objtype)
Newobject creates a new object at the end of parent's chil‐
dren; If parent is nil, the new object is created under the
root object. The new object has visibility visibility, and
type objtype. An object's type cannot be changed once it has
been created.
clique.action(cmd, objs, rest, whoto)
Action sends a message to some members without affecting the
object hierarchy. It can be used to send transient events
that have no meaning when stored statically (for example,
network latency probes). The message is sent to the set of
members given by whoto. Objs is assumed to be a list of
object ids, which are converted appropriately for each member
receiving the message; the final message is a string built by
concatenating cmd, the list of object ids, and rest, sepa‐
rated by spaces.
clique.breakmsg(whoto)
Messages are usually sent to clients in an uninterrupted
stream (as documented in spree(4)), with a single read
returning a potentially large set of messages. Breakmsg
arranges that subsequent messages received by the members
specified in whoto will see not be merged with messages sent
prior to the call to breakmsg. This is used to enable a new
client module to be started without needing to pass it data
received in the previous read.
clique.member(id)
Member yields the member corresponding to identifier id, or
nil if there is none.
clique.membernamed(name)
Membernamed searches for a member of clique named name and
returns it if it finds it, otherwise nil.
clique.members()
Members returns a list of all the members of clique, includ‐
ing those that have been suspended.
clique.owner()
Owner returns the name of the owner of the clique; i.e. the
user that created it.
clique.hangup()
Hangup terminates a game and informs all the players of that
fact.
clique.notify(cliqueid,msg)
Notify sends an informational message to another clique. The
clique so referenced must be either the parent or a child of
clique. The message is not sent synchronously, and care
should be taken not to send messages that can cause an indef‐
inite recursion.
Member
The Member type represents a member of a clique.
member.id The member's identifier is an integer unique across all cur‐
rent members of the clique, but ids of members that have left
the clique will be reused. There may not be two members of
the same name in the same clique.
member.name
Name holds the authenticated name of the member. This is
necessarily unique over the members of a clique.
member.obj(id)
Obj converts from a member's external object identifier to
the clique's local Object that it represents. It returns nil
if there is no such object.
member.del(suspend)
Del deletes member from the clique; no more requests from
member will be received by the clique engine. If suspend is
non-zero, if a member of the same name joins again it will be
allocated the same object id, allowing a member to leave and
join again without losing state.
Object
The Object type is the basic unit of clique engine state. An object's
children can be selectively concealed from members; it holds a set of
(attribute, value) pairs, each of which can be concealed likewise.
Where an argument r, of Range type is used, it refers to a range of an
object's children starting at index r.start, and finishing at r.end-1.
All the data members of an Object should be treated as read-only.
obj.setattr(name, val, vis)
Setattr sets attribute name in obj to val. If the attribute
is being created for the first time, then it will be given
visibility vis. Name should be non-empty, and should not
contain any space characters. Note that it is not possible
for an attribute to refer directly to an object by its iden‐
tifier; if this facility is needed, another identifying
scheme should be used. This also applies to member identi‐
fiers, which will change if the clique is saved and loaded
again.
obj.getattr(name)
Getattr yields the current value of the attribute name in
obj. If an attribute is not set, it yields nil.
obj.delete()
Delete removes obj from the object hierarchy.
obj.deletechildren(r)
Deletechildren deletes children in range r from obj.
obj.transfer(r, dst, i)
Transfer transfers the children in range r from obj to just
before the object at index i in dst. It is permissible for
obj and dst to be the same object.
obj.setvisibility(visibility)
Setvisibility allows the set of members given in visibility
to see the children of obj, and denies access to all others.
Members are notified of the change.
obj.setattrvisibility(name, visibility)
Setattrvisibility allows the set of members given in visibil‐
ity to see the value of obj's attribute name, and denies
access to all others. Members are not notified of the
change; if there is a need to communicate the fact of an
attribute becoming invisible to members, it should be done by
using another (visible) attribute to communicate the change.
Archives
The Archives module provides a means of committing a clique to perma‐
nent storage and retrieving it later. It should first be initialised
by calling init, passing the Spree module in mod. Write writes clique
to the file file. Info gives a set of attributes and values associated
with the clique; members gives the set of clique members for which vis‐
ibility information will be archived; visibility information for other
members is forgotten.
Read opens file and returns it as an Archive adt, say a. A.argv holds
the command line arguments to the clique module (including the name of
the module, its first element); a.members gives the names of all
archived members - the id of an archived member is given by its index
in the array; a.info gives the list of attributes an values as stored
by write; a.objects holds the clique objects. Readheader is just like
read except that it parses the header only, so will return an Archive
adt such that a.objects is nil.
Module Interface
An engine module, mod, must implement the following functions. Where a
function returns a string, it is interpreted as an error response to
the member responsible for the request; an empty string signifies no
error.
mod->init(srvmod,clique,argv)
Init initialises the clique engine. Clique is the clique that
the engine is controlling, and srvmod is the Spree module hold‐
ing its associated data. An error response from this function
causes the clique to be aborted. Argv gives a list of arguments
to the engine, starting with its module name.
mod->join(member,e,suspended)
Member has made a request to join the clique; an error response
causes the request to be refused, otherwise the member joins the
clique. E is a message from the client about how it would like
to join the clique (e.g. join, watch, etc). Suspended is non-
zero if the member was previously suspended from the clique.
mod->leave(member)
Member has left the clique. If leave returns zero, the member
will not be deleted from the clique, but merely suspended until
they should join again.
mod->command(member, e)
Member has sent the command e. The command usually follows the
simple message conventions used in spree(4), i.e. simple space-
separated tokens.
mod->notify(cliqueid,s)
A notification, s, has been posted to the current clique by the
clique identified by cliqueid. The posting clique is either a
parent or a child of the current clique.
mod.
EXAMPLE
The following is a small, but working example of a clique engine that
acts as a chat server (parsing error checking omitted, and white-space
compressed to save paper):
implement Cliquemodule;
include "sys.m";
sys: Sys;
include "draw.m";
include "../spree.m";
spree: Spree;
Clique, Member: import spree;
clique: ref Clique;
clienttype(): string
{
return "chat";
}
init(g: ref Clique, srvmod: Spree): string
{
(sys, clique, spree) = (load Sys Sys->PATH, g, srvmod);
return nil;
}
join(nil: ref Member): string
{
return nil;
}
leave(nil: ref Member)
{
}
command(member: ref Member, cmd: string): string
{
clique.action("say " + string member.id + " " + cmd, nil, nil, ~0);
return nil;
}
SOURCE
/appl/cmd/cliques/spree.b
SEE ALSOspree(4), spree-objstore(2), spree-cardlib(2), spree-allow(2),
BUGS
The reuse of object ids can lead to problems when objects are deleted
and recreated on the server before clients become aware of the changes.
SPREE(2)