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;

     Page 1                       Plan 9             (printed 3/29/24)

     SPREE(2)                                                 SPREE(2)

              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 distributed 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

     Page 2                       Plan 9             (printed 3/29/24)

     SPREE(2)                                                 SPREE(2)

          of an engine is responsible for a particular clique, in
          which one or more members participate. Messages sent by mem-
          bers 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
          database 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 member 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 member 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 ances-
          tors' visibility sets contains that member).

          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 visi-
          ble, 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 creation of new objects, and provides a way of communi-
          cating 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

     Page 3                       Plan 9             (printed 3/29/24)

     SPREE(2)                                                 SPREE(2)

                    of the game to be created; archive.argv should be
                    non-nil; its first element should name the engine
                    to be loaded (as a path relative to the engine
                    module directory, and without the .dis extension).

          clique.newobject(parent, visibility, objtype)
                    Newobject creates a new object at the end of
                    parent's children; 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 mem-
                    bers 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, separated by
                    spaces.

          clique.breakmsg(whoto)
                    Messages are usually sent to clients in an unin-
                    terrupted stream (as documented in spree(4)), with
                    a single read returning a potentially large set of
                    messages.  Breakmsg arranges that subsequent mes-
                    sages 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 identi-
                    fier 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, including those that have been suspended.

          clique.owner()
                    Owner returns the name of the owner of the clique;
                    i.e. the user that created it.

     Page 4                       Plan 9             (printed 3/29/24)

     SPREE(2)                                                 SPREE(2)

          clique.hangup()
                    Hangup terminates a game and informs all the play-
                    ers 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 indefinite
                    recursion.

        Member
          The Member type represents a member of a clique.

          member.id The member's identifier is an integer  unique
                    across all current 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 iden-
                    tifier to the clique's local Object that it repre-
                    sents. 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 mem-
                    ber of the same name joins again it will be allo-
                    cated 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 mem-
          bers; 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,

     Page 5                       Plan 9             (printed 3/29/24)

     SPREE(2)                                                 SPREE(2)

                    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
                    identifier; if this facility is needed, another
                    identifying scheme should be used. This also
                    applies to member identifiers, 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 visibility 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 permanent storage and retrieving it later.  It should
          first be initialised by calling init, passing the Spree mod-
          ule 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 visibility
          information will be archived; visibility information for
          other members is forgotten.

     Page 6                       Plan 9             (printed 3/29/24)

     SPREE(2)                                                 SPREE(2)

          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 func-
          tions. 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 holding 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 fol-
               lows 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 post-
               ing 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

     Page 7                       Plan 9             (printed 3/29/24)

     SPREE(2)                                                 SPREE(2)

          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 ALSO
          spree(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.

     Page 8                       Plan 9             (printed 3/29/24)