1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
|
.TH SPREE 2
.SH NAME
Spree \- distributed interactive sessions.
.SH SYNOPSIS
.EX
.ps -1
.vs -1
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;
.ps +1
.vs +1
.EE
.SH DESCRIPTION
.I Spree
provides a general server interface that allows sets of distributed
clients,
.IR cliques ,
to interact in a controlled manner, with the
interaction mediated
by Limbo modules, known as
.IR 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''.
.PP
This manual page describes the
interface as presented to an engine
once it has been loaded by
.IR spree .
A loaded instance of an engine is responsible for a particular
.IR clique ,
in which one or more
.I members
participate. Messages sent by members
are interpreted by the engine, which
responds by making changes to the hierarchical
.I object
database held by the clique.
Behind the scenes
.I spree
distributes updates to this database to members
of the clique as appropriate (see
.IR spree (4)
for details).
.SS "Objects and visibility"
Objects hold a clique's visible state. An object
has a unique integer
.IR id ,
which is an index into the array
.IB clique .objects\fR;\fP
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
.IR "root object"
(id 0), which always exists.
Each attribute and each object also has an associated
.IR "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
.IR sets (2)),
a member is ``visible'' if the set contains the member's id.
.PP
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
member).
.PP
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
.IB member .obj()
function.
.SS \fBClique\fP
The
.B Clique
type holds all the objects in a clique. It allows the
creation of new objects, and provides a way of communicating
with members directly.
All data members of a
.B Clique
should be treated as read-only.
.TP 10
.IB clique .objects
This array holds the objects in the clique. An object with
identifier
.I id
is found at
.IB clique .objects[ id ]\fR.\fP
.TP
.IB clique .new(\fIarchive\fP, \fIowner\fP)
.B New
creates a new clique.
.I Archive
is an archive of the game to be created;
.IB 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
.B .dis
extension).
.TP
.IB clique .newobject(\fIparent\fP,\ \fIvisibility\fP,\ \fIobjtype\fP)
.B Newobject
creates a new object at the end
of
.IR parent 's
children;
If
.I parent
is nil, the new object is created under the root object.
The new object has visibility
.IR visibility ,
and type
.IR objtype .
An object's type cannot be changed once
it has been created.
.TP
.IB clique .action(\fIcmd\fP,\ \fIobjs\fP,\ \fIrest\fP,\ \fIwhoto\fP)
.B 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
.IR whoto .
.I 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
.IR cmd ,
the list of object ids, and
.IR rest ,
separated by spaces.
.TP
.IB clique .breakmsg(\fIwhoto\fP)
Messages are usually sent to clients in an uninterrupted
stream (as documented in
.IR spree (4)),
with a single read returning a potentially large
set of messages.
.B Breakmsg
arranges that subsequent messages received by the members specified in
.I whoto
will see not be merged with messages sent prior to the call to
.BR breakmsg .
This is used to enable a new client module to be started
without needing to pass it data received in the previous read.
.TP
.IB clique .member(\fIid\fP)
.B Member
yields the member corresponding to identifier
.IR id ,
or
.B nil
if there is none.
.TP
.IB clique .membernamed(\fIname\fP)
.B Membernamed
searches for a member of
.I clique
named
.I name
and returns it if it finds it, otherwise
.BR nil .
.TP
.IB clique .members()
.B Members
returns a list of all the members of
.IR clique ,
including those that have been suspended.
.TP
.IB clique .owner()
.B Owner
returns the name of the owner of the clique;
i.e. the user that created it.
.TP
.IB clique .hangup()
.B Hangup
terminates a game and informs all the
players of that fact.
.TP
.IB clique .notify(\fIcliqueid\fP, \fImsg\fP)
.B Notify
sends an informational message to another clique.
The clique so referenced must be either the parent
or a child of
.IR clique .
The message is not sent synchronously,
and care should be taken not to send messages that
can cause an indefinite recursion.
.SS Member
The
.B Member
type represents a member of a clique.
.TP 10
.IB 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.
.TP
.IB member .name
.B Name
holds the authenticated name of the member.
This is necessarily unique over the members
of a clique.
.TP
.IB member .obj(\fIid\fP)
.B Obj
converts from a member's external object
identifier to the clique's local
.B Object
that it represents. It returns
.B nil
if there is no such object.
.TP
.IB member .del(\fIsuspend\fP)
.B Del
deletes
.I member
from the clique;
no more requests from
.I member
will be received by the clique engine.
If
.I 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.
.SS \fBObject\fP
The
.B 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
.RI ( attribute ,\ value )
pairs, each of which can be concealed likewise.
Where an argument
.IR r ,
of
.B Range
type is used, it refers to a range of an object's
children starting at index
.IB r .start\fR,\fP
and finishing at
.IB r .end-1\fR.\fP
All the data members of an
.B Object
should be treated as read-only.
.TP 10
.IB obj .setattr(\fIname\fP,\ \fIval\fP,\ \fIvis\fP)
.B Setattr
sets attribute
.I name
in
.I obj
to
.IR val.
If the attribute is being created for the
first time, then it will be given visibility
.IR vis .
.I 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.
.TP
.IB obj .getattr(\fIname\fP)
.B Getattr
yields the current value of the
attribute
.I name
in
.IR obj .
If an attribute is not set, it yields
.BR nil .
.TP
.IB obj .delete()
.B Delete
removes
.I obj
from the object
hierarchy.
.TP
.IB obj .deletechildren(\fIr\fP)
.B Deletechildren
deletes children in range
.I r
from
.IR obj .
.TP
.IB obj .transfer(\fIr\fP,\ \fIdst\fP,\ \fIi\fP)
.B Transfer
transfers the children in range
.I r
from
.I obj
to just before the object at index
.I i
in
.IR dst .
It is permissible for
.I obj
and
.I dst
to be the same object.
.TP
.IB obj .setvisibility(\fIvisibility\fP)
.B Setvisibility
allows the set of members
given in
.I visibility
to see the children of
.IR obj ,
and denies access to all others.
Members are notified of the change.
.TP
.IB obj .setattrvisibility(\fIname\fP,\ \fIvisibility\fP)
.B Setattrvisibility
allows the set of members
given in
.I visibility
to see the value of
.IR obj 's
attribute
.IR 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.
.SS "Archives"
The
.B Archives
module provides a means of committing a clique
to permanent storage and retrieving it later.
It should first be initialised by calling
.BR init ,
passing the
.B Spree
module in
.IR mod .
.B Write
writes
.I clique
to the file
.IR file .
.I Info
gives a set of attributes and values associated with the clique;
.I members
gives the set of clique members for which visibility information
will be archived; visibility information for other members is forgotten.
.PP
.B Read
opens
.I file
and returns it as an
.B Archive
adt, say
.IR a .
.IB A .argv
holds the command line arguments to the clique module
(including the name of the module, its first element);
.IB a .members
gives the names of all archived members - the id
of an archived member is given by its index in the array;
.IB a .info
gives the list of attributes an values as stored by
.BR write ;
.IB a .objects
holds the clique objects.
.B Readheader
is just like
.B read
except that it parses the header only, so will return an
.B Archive
adt such that
.IB a .objects
is nil.
.SS "Module Interface"
An engine module,
.IR 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.
.TP
.IB mod ->init(\fIsrvmod\fP, \fIclique\fP, \fIargv\fP)
.B Init
initialises the clique engine.
.I Clique
is the clique that the engine is controlling,
and
.I srvmod
is the
.B Spree
module holding its associated data.
An error response from this function
causes the clique to be aborted.
.I Argv
gives a list of arguments to the engine, starting
with its module name.
.TP
.IB mod ->join(\fImember\fP, \fIe\fP, \fIsuspended\fP)
.I Member
has made a request to join the clique;
an error response causes the request to be
refused, otherwise the member joins the
clique.
.I E
is a message from the client about how it would like to
join the clique (e.g.
.BR join ,
.BR watch ,
etc).
.I Suspended
is non-zero if the member was previously suspended from the clique.
.TP
.IB mod ->leave(\fImember\fP)
.I Member
has left the clique.
If
.B leave
returns zero, the member will not be deleted from the
clique, but merely suspended until they should join again.
.TP
.IB mod ->command(\fImember\fP,\ \fIe\fP)
.I Member
has sent the command
.IR e .
The command usually follows
the simple message conventions
used in
.IR spree (4),
i.e. simple space-separated tokens.
.TP
.IB mod ->notify(\fIcliqueid\fP, \fIs\fP)
A notification,
.IR s ,
has been posted to
the current clique
by the clique identified by
.IR cliqueid .
The posting clique is either a parent or a child of the current clique.
.TP
.IB mod .
.SH 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):
.PP
.EX
.ps -1
.vs -1
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;
}
.ps +1
.vs +1
.EE
.SH SOURCE
.B /appl/cmd/cliques/spree.b
.SH "SEE ALSO"
.IR spree (4),
.IR spree-objstore (2),
.IR spree-cardlib (2),
.IR spree-allow (2),
.SH 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.
|