$Id: zzspec.wml,v 1.23 2000/09/13 14:54:51 tjl Exp $Written by
Note that this document contains some formatting that is best rendered using a true CSS1 standard-compliant browser such as Mozilla; however, it should work reasonably well with any browser that can at least ignore CSS that it doesn't understand. Unfortunately, Netscape Navigator 4.7, for example, doesn't. Well, that's life...
A non-CSS version exists: you can try by compiling this document from its WML version with some switches.. (if you came here through the WWW, there should be an alternate link on the referring page. However, I like the pretty rendering by Mozilla so much that that's still the default.
The purpose of this document is to be a living specification of the features in the GZigZag system. The GZigZag system is an implementation of the ZigZag structure, invented by Ted Nelson. Much of this document is simply a somewhat more verbose version of discussions with him but other places go into more technical detail.
Some parts of this specification are not yet correctly implemented by the current version, but in these cases, this document is correct (or it should be ;-) and the implementation wrong.
Some parts of this spec are at the moment just general ramblings about a topic - once we have the pole editor, I will definitely rearrange it completely. The idea is to try to make them more and more like a true spec as time goes by.
The parts marked with the dreaded triple-X symbol (XXX) are as yet incomplete and should be taken with a not only a grain but a mountain of salt. They mostly contain just a few loose sentences setting the topic.
If you see anything suspicious, feel free to ask me at lukka@iki.fi
.
ZigZag is a delightfully simple way of operating structures. As such, it has its own uses simply as a personal information manager for people who like multidimensionality,
In addition to stand-alone use ZigZag is related to Ted Nelson's Xanadu system; a newer version of Xanadu is being designed to make use of ZigZag as a platform. As a brief description of Xanadu, it has
ZigZag can also work as a platform for other types of applications --- or preferably applitudes, meaning that they should also expose the other possibilities of ZigZag at the same time as being normal applications. This is so that the interconnectivity provided by ZigZag can be used to make the whole of two applitudes greater than the sum of the parts.
A cell is the fundamental container of data. A cell can "physically" contain either a text string or a single, contiguous span (which is an address, and references a permascroll).
Cells may be connected to each other along dimensions, so that each cell can be connected to another cell in two directions (negative and positive) on each dimension. The more global structure is not constrained: any two cells can be neighbours on any dimension, but some of these connections are used for interpreting the structure into views and actions (see below).
It is not yet clear whether dimensions are strings or whether they are cells. The various slice and compound space implementations will probably affect and depend on this. If dimensions are cells, then several uniqueness problems are quickly solved, but their being strings may be easier on the users.
One important point is that of headcells: on non-circular ranks, the headcell is the cell at the negative end of the rank. Ted specifies that all ranks should have a headcell and that there should be a way to specify the headcell of a circular rank. The exact mechanisms here are as of yet unclear.
An important way to describe the space is to take dimensions to be "objects": the dimensions are invertible mappings between cells. This is opposed to the immediate way of thinking "cell and connections", and there is a difference.
This view is important because here it is easy to discuss special dimensions that have interesting properties.
The following terms may prove useful from time to time:
XXX: are these definitions OK?
The path from the viewcell viewspx
to the cell the cursor of the view points to A
via the main cursor cell C
which may or may not be the
same as viewspx
.
C
is at the negend of d.cursor-cargo
from viewspx
and the accursed cell A
is
at the negend of d.cursor
.
The viewcell points to a particular cell where its cursor lies. The idea of cursors is not restricted to cursors of views but, as we shall see below, anything where selecting a single cell is important. This makes it possible to create a new view to select that cell on the fly (see below).
In the structure, a relcell is used so that several cursors can be
maintained pointing to the same cell. The path to find the cell the
view is pointing to is start at the viewcell, go to the posend on
d.mycursor
and then to the posend on d.cursor
.
In order not to disturb other cursors, the insert operation should be
used to move the relcell to a new d.cursor
rank. This operation
will leave the original and the new rank otherwise intact.
It is possible that any other operation on d.cursor
will
be declared illegal.
NOTE: this is liable to change. It is only documented here for understanding.
An example of the inherited parameter traveral to find a parameter
value. Starting from start
, we go poswards on d.2
until we reach a cell with a negward connection on d.3
.
We include that list and return to the main list. The parameters
that have values store them on d.1
or as cursors starting
on the parameter cell.
This section defines a way to inherit parameter lists based on the structure.
Paths are a precursor to Clang, and allow the user to express abstract paths in the structure. XXX See the docs for ZZPath.java.
Selectors are a generalization of paths: where paths are like zeroth-order logic, selectors are first-order logic, allowing quantifiers ("every cell in this rank") but not quantification over quantifiers, or anything terribly general.
The point is to be able to easily describe in the structure the operation "show also all replies to this email", where the replies are generally connected to the original by some route.
One interesting idea that may or may not see the light of day is using inverse paths for selectors. This has certain aesthetic appeal.
A view is represented by one maincell in the structure, called the viewcell. This cell can contain text which will be interpreted as the ``title'' of the view, but this is not mandatory.
XXX Since some views which contain more than one cursor are planned, it is possible that something will change below.
The path from the viewcell to the cell specifying the Y dimension shown.
First, we go two steps on d.dims
, then
use the same method as in the previous figure for getting to the accursed
cell from there.
Note that both the viewspx
cell and the X
have
cursors similarly attached to them.
Dimensions that are currently visible
are listed on d.dims
from the viewcell, in order X,
Y, Z (and possibly others, in case of complicated view rasters). The
dimension cells are treated as cursors, i.e. the cell representing the
dimension is found by going to the posend on d.mycursor
and then
to the posend on d.cursor
from the dimension cell.
The point of this odd-seeming arrangement is, as alluded to in the Cursors section above, is that we can very simply create a new view that is bound to a dimension cell of another view.
In order to change dimensions shown, the views provide an operation ``move
the X/Y/Z-dimension-selector-cursor one step pos/negwards on d.2
''.
The usual plain-vanilla dimension lists are thus simply lists of cells on d.2
which
contain the dimension name as a string (possibly by cloning).
Most often these lists are cyclic but this is not mandatory: trying
to move past the end/beginning of a list simply does nothing.
This is just the default arrangement - the user is free to create other dimlist operators and use them to change the dimensions taking advantage of the structure in a different way. One example of an operation we may want to provide at some point is "change dimension but not to one already being shown".
To select the dimension list for a particular dimension of a particular
view, the user can simply create a new view for the dimension cursor,
using the cursor-cargo mechanism.
To set all the dimensions to the same list, the user can set the X
dimension and then invoke a special operation that sets Y to the
d.1
poswards neighbour of X, Z to the neighbour of Y and so on, on the
same list.
Getting to the raster-defining cell R
from the view specs.
Note that the cursor-traversing operation is abbreviated to the curvy arrow.
Also note that the inheriting mechanism defined previously is allowed.
The raster to use is obtained similar to the dimensions to use;
only the first motion is different: down on d.2
until the text
FlobRaster
is found.
A sample specification of a vanishing
raster, specifying values for two parameters.
Here, the vanishing
cell would stand in for the
R
cell in the previous image.
The raster itself is specified using a corner list. An incomplete list of parameters follows:
There are currently some undocumented interactions to provide for hard rasters; see the source.
This operation changes one connection. It takes two directions and one cell as parameters. XXX image.
Keybindings offer a nice way of seeing how flexible the ZigZag structure is for programming.
Most fundamentally, the names of the keys
(e.g. Ctrl-k
) are on cells connected along d.2
. The
action for each key is the poscell on d.1
from those cells - this
way several keys can be bound to the same action quite easily.
However, this is not yet sufficient: there may be several input states, e.g. with the vi editor there is the insert mode and the command mode, and inside the command mode there is the special mode of waiting for a motion command. So in addition to the command, there needs to be a way to specify the next state. Likewise, some states are similar to each other so states should be able to inherit commands from each other in various ways.
GZigZag uses blank cells to represent inheritance: when the
routine that searches for a key is going down along d.2
, it will
go to the poscell from that cell and check all the bindings on
that dimension before returning to continue the search along
the original rank.
The cursor mechanism is useful for keybindings as well: the current state can easily be maintaned by a cursor as shown above, allowing the user to easily set the cursor to a different state. However, care has to be taken here as moving the cursor in question can render keybindings unusable, so it is better to use an interface where the cursor can be immediately transported to the right place (such as clicking with the mouse).
The actual structure is currently such that the bindings
cursor is the posend on d.bind
of the view cell. This cursor
points to the place where the inheritable parameter search for
the next binding is begun. The cursor is set, if the inheritable
parameter's value (on d.1
) has a negward connection on d.3
--
it is set to the negend.
Additional problems arise when the same view can show different rasters. A different visualization of a structure often makes different means of navigation necessary, or different operations desirable. To provide for this, it is possible to assign a raster a list of modifications to a bindings mode; when the mode is selected, the raster's modifications are searched first, and the standard bindings for the mode are searched when the binding is not found in the modifications list.
A raster can be assigned two groups of modification lists:
one to be applied when the raster is selected in the left
(control) view, and one to be applied when the raster is
selected in the right (data) view. These groups are obtained
from the same corner list as the parameters given to the raster
(see above); the text searched for is ctrlbindings
and databindings
, respective.
From that cell, an intersection on d.1
and d.clone
with
the current bindings mode is searched. That means that a mode
which is intended to be modified is cloned, and attatched to the
raster's bindings cell on d.1
. From this cell, a definition for
the binding is searched on d.2
; if none is found, the mode's
default bindings are invoked. For any of these searches, the
standard parameter inheritance (see above) is allowed.
This method is practical because it "feels" very much
like the definition of the modes themselves, as the modes are
commonly listed on a d.1
rank from the Bindings
cell on the system list. On the other hand, it's somewhat
different structurally.
$Id: keybindings.wml,v 1.3 2000/10/26 13:08:11 ajk Exp $
These are still being discussed and are not yet frozen.
Key(s) | Binding |
Ctrl-S | Commit the current changes to disk. |
ijl,kK | Up-left-right-down-Z+-Z- in view 1. |
esfcdD | Up-left-right-down-Z+-Z- in view 0. |
n dir | Create a new cell in given direction |
b dir | Break a connection in given direction |
h dir | Hop the accursed cell in the given direction. Hopping means simply switching the places of the accursed and the indicated cells in that rank, no other ranks are affected by the operation. |
xyz | Show next dimension on list on X/Y/Z in view 1 |
Alt-xyz | Show previous dimension on list on X/Y/Z in view 1 |
XYZ | Show next dimension on list on X/Y/Z in view 0 |
Alt-Shift-XYZ | Show previous dimension on list on X/Y/Z in view 0 |
/ dir | Connect the center cells of the right and left views in the given direction, if no connections will be broken by this. |
~ | Exchange the cursor positions of the two views (no other view parameters are changed). |
Delete | Delete the center cell of view 1 and move the cursor. Cursor move tries following directions in order: X-, X+, Y-, Y+, Z-, Z+ and finally goes to home cell. |
m | Mark or unmark the cell in view 1 |
Enter | Execute cell in view 0, with the cell in view 1 as parameter |
v | Change raster in view 1. |
V | Change raster in view 0. |
Alt-v | Change raster in view 1 backwards. |
Alt-V | Change raster in view 0 backwards. |
Home | Go home: move view 1 cursor to homecell. |
Esc | Move both views to home and reset dimensions to first three dimensions on first dimlist. |
0123456789 | Insert the given number into the number insert buffer for cell IDs. |
g | Move view 1 to cell whose ID number was in buffer |
G | Move view 0 to cell whose ID number was in buffer |
Backspace | Remove one number from the number insert buffer |
t dir | Clone the view 1 cursor to given direction (may be in either view). |
T dir | Clone the view 0 cursor to given direction (may be in either view). |
% | Exchange the X and Y connections of the two accursed cells with each other. |
o | Go to original (rootclone, cell from which accursed cell was cloned) in view 1. |
O | Go to original (rootclone, cell from which accursed cell was cloned) in view 0. |
End dir | Move to te end of the rank in the given direction. |
< | Set the cursor of the left-hand-view to the cell the right-hand view is pointing to. |
> | Set the cursor of the right-hand-view to the cell the left-hand view is pointing to. |
- dir | Connect the current view1 cursor into marked cells in given direction. Direction must be in view 1. |
Alt-c | Switch into "curseling" (cursor selection) mode (see below). |
a dir dir | Monochug: change one connection. See above. |
Key(s) | Binding |
Esc or Tab | Switch off text edit mode |
Delete | Delete one character after cursor |
Backspace | Delete one character before cursor |
Left, Right | Move the cursor within the current cell |
Home | Move the cursor to the beginning of the text in the current cell |
End | Move the cursor to the end of the text in the current cell |
Ctrl-A | Move the cursor to the beginning of the current line in the text of the current cell |
Ctrl-E | Move the cursor to the end of the current line in the text of the current cell |
Enter | Insert a line separator in the text before the cursor |
any key producing a printable character | Insert the character in the text before the cursor |
As an intermediate for multiple cursors, there is a key binding mode for "curseling:" cursor selection. In the standard keybindings, Alt-c is used to go into this mode, which by default supports the following key bindings:
Key(s) | Binding |
Tab, Spacebar, Alt-c or Esc | Select this cursor, quit curseling mode. |
Left, Right, jl | Select next/last cursor in system cursor list in view 1. |
Up, Down, i, | Select next/last cursor positioned on the same cell in view 1. |
sfec | Like Left/Right/Up/Down, operating on view 0. |
Note that you can create four additional cursors through executing the CreateCursors command, found in the action list, by centering the left view on it and hitting Enter.
A compound space is simply a way of taking one or more existing spaces and creating a new space that is a view of these spaces combined according to some rule.
XXX
There are many situations where one may want to be informed about changes to the structure, either before it happens (with the possibility of vetoing it), or after (e.g.~to reraster a window).
Out of an email from Ted,
There are several issues here. One is just the mechanics of a clean method of synchronization. The other is the problem of A FEELING OF FAST RESPONSE-- which means, very importantly, PREVENTING the non-current windows from refreshing in order to have truly INSTANTANEOUS response to each user action. Anyway, as much as possible.
In order to ensure a speedy response, we have to consider the numbers. There are thousands of cells. Each view shows some fraction of them. At each time, there are probably 1-10 views open and changes to cells will reflect in several of them.
Therefore, it is probably unnecessarily slow to store the information about which cells are seen by which views (except if it is naturally stored by the view itself). Rather, for views, there should just be a global list which gives the priorities with which the views get to refresh themselves.
Now, it makes no sense to start updating at each change to the structure - internally there has to be some method to freeze and thaw the global update queue.
One interesting problem here is finding which view is really the current view: for instance, in the two-part view Also, displaying only the central portion of the views whose cursor changes first could make a big difference. Or the rank along which the cursor moves plus some small number of cells.
However, this is not the whole story: views are not the only things that need to be informed when something changes. It should be possible to define per-cell triggers as well.
This is possible in the Java code by passing an extra parameter, a
ZZObs
to a routine that returns some structural information,
such as a neighbour, a headcell or the contents.
The ZZObs will then be called once after any of the items it is
observing changes, i.e. the return value of the function the ZZObs
was passed to has changed.
The callback is not necessarily instantaneous; rather, all the callbacks are grouped and run after the activity that caused the trigger has finished.
Eventually, it should be possible to define these triggers in the structure as well, but the best mechanism for that is not yet known. However, for some applications these can't wait. Especially important are cursor triggers, which do something whenever a cursor changes value.
A cursor trigger is placed by placing the action (XXX In what form)
on d..cursor-trigger
of the cursor-cargo cell.
These actions are queued to be called sometime after the cursor is
changed (usually before the next screen update).
Most of the structure is persistent but some parts should be transient in order to save space and time. For example, if the information about the rastered representation of a part of the space (coordinates on the 2-D display) is stored in the space, this would waste an enormous amount of space each time the cursor moved.
The transiency is specified at the time of the cell creation on the Java level. How this figures in the space is yet to be decided.
The transient cells are not trivial: because of design considerations, they can't simply override connections and have all their connections appear deleted in the persistent space. Instead, the persistent space should appear as if the cells had been truly deleted using the delete operation, which causes the cells on opposite sides on a dimension from the cell being deleted to be connected to each other. In other words, if we have the rank ABCDE and the cell D is a transient cell, then this should correspond to a rank ABCE in the persistent space.
The slice design is what Ted ultimately wants but because it's somewhat more complicated to implement, it's postponed until some other parts of the system clear up (most importantly synchronization) and the existence of other types of subspaces and their interaction.
A key mechanism for versioning is timestamps, which specify moments in the past. The full state of the ZZ space at each timestamp can be accessed through the file formats.
The past versions of cell structures are shown in the structure
as virtual, non-modifiable cells, except for the one allowed
source of modification: d.cursor
. The dimension d.cursor
is a source of much headache in versioning because of its
nature but the current solution is to move past versions
of d.cursor
to d..cursor-past
.
The past versions of a cell can be accessed on d.version
,
and the past versions where the cell's content or connections
have changed is on d.cell-version
, and the past versions
where the cell's content has changed are on d.content-version
.
So d.cell-version
skips on the cells of d.version
,
and d.content-version
skips on the cells of d.cell-version
.
The skipped-to cells are the first cells with
the new, changed property.
XXX Can we do d.cell-version
efficiently???
Referencing stable media streams is an important part of the overall design. For the Dominica project, we need a subset of the features.
A cell may contain either its text directly or a reference to a
stable media stream - either an address or a span between two
addresses. For now, we distinguish between the two types by having
the references to stable media connected to themselves
along d.stableref
(this mechanism {\em will} change later, and
is encapsulated in the ZZCell class as the contenttype routines).
Temporarily, we use the address format AAnumber where the number is simply the byte offset into the invariant media stream. Later on, the addresses will be converted into tumblers.
Given a space, it is possible to obtain a list of cells that overlap with a given list of spans. These can be used in various ways to display parts of texts that have links in different ways etc.
Later on, an enfiladic (tree-like) structure will be used to perform the cross-matching of the cell lists.
Blobs are a stop en route to full hyperFlobs. Blobs are basically objects whose coordinates and appearance in 2D come somehow from the ZZ space, according to user-given instructions, which are themselves stored in the ZZ structure.
A blob has two separate aspects: location and appearance. The location is composed of multiple dimensions, specified in some way by the structure, of which the user will select a subset of two or three (or some more if using e.g. linear combinations). The appearance can also be selected from multiple possibilities, with different visual aspects showing different aspects of the entity the blob represents.
Throughout this section, I will use email as the recurring example. Email is a good application for blobs since it makes it possible to see the usefulness of blobs in a practical setting. Everybody gets too much email. Current email programs are not able to handle the load - first of all, they are too slow, reading the entire mailbox every time the program is started and the mailbox is opened. The obvious solution of splitting your mail into several folders is not nice either --- it makes it much more difficult to find anything. This problem is well solved by the stable media streams in the previous section: instead of different files as folders, folders are just sets of pointers to the original mailbox file. The algorithmically great thing is that the whole mailbox need not be reread at any point, only the new messages have to be inserted into the structure. After that, the structure is a good way of accessing the contents of the mailbox. But I digress - back to blobs.
In the world of blobs, each email would be represented as one blob.
Likewise each person and subject line (modulus Re
).
Each ``statement'' or ``expression' in the ZZ space will have some parameters. For instance, an expression of ``headcell'' will need a dimension and a direction.
For maximum flexibility and ease, we want to be able to store the
dimension parameter in two different ways: first, as a direct cell or
clone of a cell, e.g. d.1
, and second, as a pointer.
For design considerations, the first thing to note about pointers
is that it would be quite powerful if we could bind a number of pointers
to point to the same cell --- just like cursors above. So why not
equate pointer$=$cursor, so that a cell is interpreted as itself
if there's no d.mycursor
poswards and otherwise as a cursor.
The whole concept of blobs (and flobs) is based on being able to grab visualizable dimensions from various user-defined places in the structure and throw the blobs on the screen at those coordinates.
Dimensions can be defined by the blobs themselves or by proxy (e.g. an email can have as one of its dimensions one of the dimensions of the sender of the email).
Eventually, it should be possible to edit some dimensions by just clicking and dragging. Some dimensions, like the date of an email, should be read-only but you should be able to drag email on an urgency dimension or some dimensions of your own (to arrange them in a pleasing way in space).
Note that dimensions do not need to have numerical values. An ordering can be quite sufficient and useful. For example, a time dimension to email may be more useful if it is not represented linearly but rather nonlinearly, based on the message density, expanding dense places and possibly (like in billowing) places that are near the cursor.
When the blobview is described in the structure, there will be one or many sets of blobs to render, as well as their connections. Each set of blobs is described by
The connections between blobs will currently be described simply by giving paths from the maincell to other maincells that may be shown on the screen and if it is shown, a line is drawn.
I warned you that email would be the predominant example of this section. First, let's see how we describe emails in the ZZ structure for this simple example.
First of all, there is the handle for each email. This is just
a cell that is used to designate the whole email.
As emails contain the Message-ID field which is guaranteed
to be unique, the handle cell will contain that string.
Poswards on d.handle
from the handle cell are the header, the body
and the attachments.
The header is a simple d.2
and d.1
job for the field type
and contents. However, both the email address cells, the subject
cell and the references (message ids) are connected structurally:
all of them point on d.ref
to the handle of the corresponding
structure. It may be that the structure that these cells point to is
not loaded, in which case they just form a rank on d.ref
without
the handle cell at the end.
It is going to be fairly common for a Java class to get its parameters
from the structure.
The ZOb mechanism is useful for easily creating such Java classes that
read their parameters in a standard way.
Basically, a ZOb is a Java class for which the instance variables are
defined inside a STRUCTPARAMS {}
block. There is some
syntax support for specifying the number of elements required in arrays
etc.
The point of the mechanism is to allow more latitude with the parameters later: for example, inserting them into the structure with descriptions, or caching the ZOb parameters read from the structure for larger ZOb systems to speed up the process.
This is a specification for the first version of the flob-supporting email
applitude.
The relevant structure is shown in the figure.
XXX email1
The handle cell is connected along d.handle
to the most important header
fields and the cell containing the actual content of the message.
The rest of the headers are stored in the structure but are not relevant here.
The message IDs connect replies together: the In-Reply-To:
and Message-Id
fields: the message ids on the reply-to fields are
clones of the original message's message-id field. This makes it easy
to track threads using only the structure.
The flob dimension specification is an interesting part of this problem:
it should be compatible with the other rasters but store extra information
for each dimension about where that dimension is to be found and whether
it's user-modifiable (by clicking and dragging).
Since each dimension is special, it doesn't make sense to have the same
dimension lists for flob dimensions and normal dimensions but the operation
of advancing a dimension can remain the same, i.e. advancing a cursor on
d.2
.
In the flob coordinates, the cell on that rank gives the name and is
connected on d.1
to the actual instructions on how to obtain
the correct cell and label.
The fourth dimension is used for finding the flobs to show, i.e. to select the rank of references to show.
This section deals with the interactions between the various planned features. Unfortunately, this is one part that is not yet fully specced but at least we're speccing what the problems are. (XXX todo)
Clones | Cursors | Versioning (access to old versions) | Slices | Content links | I18N | |
---|---|---|---|---|---|---|
Clones | ||||||
Cursors | ||||||
Versioning (access to old versions) | ||||||
Slices | ||||||
Content links | ||||||
I18N |
d.clone
.
d.cursor-list
and d.cursor-cargo
.
d.cursor
from the new position
of the cursor (the accursed cell).
d.cursor-cargo
provides a mechanism for locking
several cursors together.
This section describes the GZigZag file formats that store the low-level structure. This is separate from the space description which describes e.g. the system list: the file format is lower-level still.
The file format is arranged as several layers, in order to make it more flexible in the future.
Runs are the lowest level of the file: they specify a sequence of changes to the structure, moving forwards in time.
The run formats given are version 0 of the run format.
A dimension change run is a sequence of instructions telling how one dimension changed between two specific times. It contains records specifying connecting and disconnecting cells. The structure is that of a simple hash: disconnecting only disconnects in one direction.
The records are:
Connect record: connect cell id 1 to cell id 2 poswards. | |
nbytes | content |
1 | 'c' (99). Record identifier. |
2..65537 | Java UTF string. Cell id 1. |
2..65537 | Java UTF string. Cell id 2. |
Disconnect record: disconnect cell id 1 in given direction. | |
nbytes | content |
1 | 'd' (100). Record identifier. |
1 | '+' (43) or '-' (45). Positive or negative direction. |
2..65537 | Java UTF string. Cell id. |
These records are concatenated with no record identifier in between.
This is the corresponding sequence for cell content changes.
New string content for a cell. | |
nbytes | content |
1 | 's' (115). Record identifier. |
2..65537 | Java UTF string. Cell id. |
2..65537 | Java UTF string. Text content. |
New span content for a cell. | |
nbytes | content |
1 | 'S' (83). Record identifier. |
2..65537 | Java UTF string. Cell id. |
2..65537 | Java UTF string. Content, as a string representation of a span. |
The character scroll for text is simply a sequence of 16-bit unicode characters, addressable by offsets.
The dimensions and content (but not scrolls), are stored in streams that contain synchronization markers between runs of changes.
Header, identifying the file type, the content type and content version. | |
nbytes | content |
4 | ASCII "GZZ0". Magic "number". |
4 | Java int: wrapper version number. |
4 | Java int: content type. |
42 = dimension | |
43 = content | |
4 | Java int: content version. |
After the header, the actual data starts. The data is given in runs:
Run: A single run between timestamps. | |
nbytes | content |
1 | 't' (114). Record identifier. |
4 | Java int: timestamp number. |
4 | Java int: number of bytes to follow. |
0..2147483647 | The run. |
Only the last timestamp before which changes occurred is stored: empty runs are not necessarily stored except for the content file. The time the save was done could be saved in a cell with the ID 'savetime' to facilitate backup recovery by time.
A stream set, then, is a container for all of the above things, with streams named by unicode strings.
Currently, this is implemented simply as a directory, which means that using non-ascii characters, or anything that's not alphanumeric or dots or slashes is a really bad idea.
Later, it will probably be a single stream that encapsulates the other streams; it may even be that runsynch and streamset will be combined later on.