This is the libray on which MGL-MAT (see MAT manual) is built. The idea of automatically translating between various representations may be useful for other applications, so this got its own package and all ties to MGL-MAT has been severed.
This package defines CUBE
, an abstract base class that provides a
framework for automatic conversion between various representations
of the same data. To define a cube, CUBE
needs to be subclassed and
the Facet extension API be implemented.
If you are only interested in how to use cubes in general, read Basics, Destroying cubes and Facet barriers.
If you want to implement a new cube datatype, then see Facet extension API, The default implementation of CALL-WITH-FACET* and Views.
Here we learn what a CUBE
is and how to access the data in it with
WITH-FACET
.
[class] CUBE
A datacube that has various representations of the
same stuff. These representations go by the name `facet'. Clients
must use WITH-FACET
to acquire a dynamic extent reference to a
facet. With the information provided in the DIRECTION
argument of
WITH-FACET
, the cube keeps track of which facets are up-to-date and
copies data between them as necessary.
The cube is an abstract class, it does not provide useful behavior in itself. One must subclass it and implement the Facet extension API.
Also see Views, Destroying cubes and Facet barriers.
[macro] WITH-FACET (FACET (CUBE FACET-NAME &KEY (DIRECTION :IO) TYPE)) &BODY BODY
Bind the variable FACET
to the facet with FACET-NAME
of CUBE
. FACET
is to be treated as dynamic extent: it is not allowed to keep a
reference to it. For the description of the DIRECTION
parameter, see
the type DIRECTION
.
[type] DIRECTION
Used by WITH-FACET
, DIRECTION
can be :INPUT
, :OUTPUT
or :IO
.
:INPUT
promises that the facet will only be read and never
written. Other up-to-date facets of the same cube remain
up-to-date. If the facet in question is not up-to-date then data
is copied to it from one of the up-to-date facets (see
SELECT-COPY-SOURCE-FOR-FACET*
).
:OUTPUT
promises that all data will be overwritten without
reading any data. All up-to-date facets become non-up-to-date,
while this facet is marked as up-to-date. No copying of data takes
place.
:IO
promises nothing about the type of access. All up-to-date
facets become non-up-to-date, while this facet is marked as
up-to-date. If the facet in question is not up-to-date then data
is copied to it from one of the up-to-date facets (see
SELECT-COPY-SOURCE-FOR-FACET*
).
Any number of WITH-FACET
's with direction :INPUT
may be active at
the same time, but :IO
and :OUTPUT
cannot coexists with any other
WITH-FACET
regardless of the direction. See CHECK-NO-WRITERS
and
CHECK-NO-WATCHERS
called by The default implementation of CALL-WITH-FACET*.
[macro] WITH-FACETS (&REST FACET-BINDING-SPECS) &BODY BODY
A shorthand for writing nested WITH-FACET
calls.
(with-facet (f1 (c1 'name1 :direction :input))
(with-facet (f2 (c2 'name2 :direction :output))
...))
is equivalent to:
(with-facets ((f1 (c1 'name1 :direction :input))
(f2 (c2 'name2 :direction :output)))
...))
[locative] FACET-NAME
The FACET-NAME
locative is to refer to stuff defined with
DEFINE-FACET-NAME
.
[macro] DEFINE-FACET-NAME SYMBOL LAMBDA-LIST &BODY DOCSTRING
Just a macro to document the symbol FACET-NAME
means a facet
name (as in the FACET-NAME
).
[generic-function] MAKE-FACET* CUBE FACET-NAME
As the first value, return a new object capable of
storing CUBE
's data in the facet with FACET-NAME
. As the second
value, return a facet description which is going to be passed to
DESTROY-FACET*
. As the third value, return a generalized boolean
indicating whether this facet must be explicitly destroyed (in which
case a finalizer will be added to CUBE
). Called by WITH-FACET
(or
more directly WATCH-FACET
) when there is no facet with
FACET-NAME
.
[generic-function] DESTROY-FACET* FACET-NAME FACET FACET-DESCRIPTION
Destroy FACET
that belongs to a facet with
FACET-NAME
and FACET-DESCRIPTION
. The cube this facet belongs to is
not among the parameters because this method can be called from a
finalizer on the cube (so we can't have a reference to the cube
portably) which also means that it may run in an unpredictable
thread.
[generic-function] COPY-FACET* CUBE FROM-FACET-NAME FROM-FACET TO-FACET-NAME TO-FACET
Copy the CUBE
's data from FROM-FACET
with
FROM-FACET-NAME
to TO-FACET
with TO-FACET-NAME
. Called by
WITH-FACET
(or more directly WATCH-FACET
) when necessary. FROM-FACET
is what SELECT-COPY-SOURCE-FOR-FACET*
returned.
[generic-function] CALL-WITH-FACET* CUBE FACET-NAME DIRECTION FN
Ensure that the facet with FACET-NAME
exists.
Depending on DIRECTION
and up-to-dateness, maybe copy data. Finally,
call FN
with the facet. The default implementation acquires the
facet from WATCH-FACET
calls FN
with it and finally calls
UNWATCH-FACET
. However, specializations are allowed to create only
temporary, dynamic extent views without ever calling WATCH-FACET
and
UNWATCH-FACET
.
[generic-function] SET-UP-TO-DATE-P* CUBE FACET-NAME VIEW VALUE
Set the VIEW-UP-TO-DATE-P
slot of VIEW
to VALUE
.
The default implementation simply SETFs it. This being a generic
function allows subclasses to ensure that certain facets which share
storage are always up-to-date at the same time.
[generic-function] SELECT-COPY-SOURCE-FOR-FACET* CUBE TO-NAME TO-FACET
Return a facet name selected from the up-to-date
views from which CUBE
's data should be copied to TO-FACET
. The
default method simply returns the first up-to-date view.
Also see The default implementation of CALL-WITH-FACET*.
[method] CALL-WITH-FACET* CUBE FACET-NAME DIRECTION FN
The default implementation of CALL-WITH-FACET*
is defined in terms
of the WATCH-FACET
and the UNWATCH-FACET
generic functions. These can
be considered part of the Facet extension API.
[generic-function] WATCH-FACET CUBE FACET-NAME DIRECTION
This is what the default CALL-WITH-FACET*
method,
in terms of which WITH-FACET
is implemented, calls first. The
default method takes care of creating views, copying and tracking
up-to-dateness.
Calls CHECK-NO-WRITERS
(unless *LET-INPUT-THROUGH-P*
) and
CHECK-NO-WATCHERS
(unless *LET-OUTPUT-THROUGH-P*
) depending on
DIRECTION
to detect situations with a writer being concurrent to
readers/writers because that would screw up the tracking
up-to-dateness.
The default implementation should suffice most of the time. MGL-MAT
specializes it to override the DIRECTION
arg if it's :OUTPUT
but not
all elements are visible due to reshaping.
[generic-function] UNWATCH-FACET CUBE FACET-NAME
This is what the default CALL-WITH-FACET*
method,
in terms of which WITH-FACET
is implemented, calls last. The default
method takes care of taking down views. External resource managers
may want to hook into this to handle unused facets.
[variable] *LET-INPUT-THROUGH-P* NIL
If true, WITH-FACETS
(more precisely, the default implementation of
CALL-WITH-FACET*
) with :DIRECTION
:INPUT
does not call
CHECK-NO-WRITERS
. This knob is intended to be bound locally for
debugging purposes.
[variable] *LET-OUTPUT-THROUGH-P* NIL
If true, WITH-FACETS
(more precisely, the default implementation of
CALL-WITH-FACET*
) with :DIRECTION
:IO
or :OUTPUT
does not call
CHECK-NO-WATCHERS
. This knob is intended to be bound locally for
debugging purposes.
[function] CHECK-NO-WRITERS CUBE MESSAGE-FORMAT &REST MESSAGE-ARGS
Signal an error if CUBE
has views being written (i.e. direction
is :IO
or :OUTPUT
.
[function] CHECK-NO-WATCHERS CUBE MESSAGE-FORMAT &REST MESSAGE-ARGS
Signal an error if CUBE
has active views regardless of the
direction.
We learn what a VIEW
is, how it's related to facets. See VIEWS
,
FIND-VIEW
, and the default method of SET-UP-TO-DATE-P*
. Views are
only visible to those implementing the Facet extension API.
[class] VIEW STRUCTURE-OBJECT
A cube has facets, as we discussed in Basics. The object
which holds the data in a particular representation is the facet. A
VIEW
holds one such facet and some metadata pertaining to it: its
name (VIEW-FACET-NAME
), whether it's up-to-date (VIEW-UP-TO-DATE-P
),
etc. VIEW
ojbects are never seen when simply using a cube, they are
for implementing the Facet extension API.
[function] VIEWS CUBE
Return the views of CUBE
.
[function] FIND-VIEW CUBE FACET-NAME
Return the view of CUBE
for the facet with FACET-NAME
or NIL
if no
such view exists.
Lifetime management of facets is manual (but facets of garbage
cubes are freed automatically by a finalizer, see MAKE-FACET*
). One
may destroy a single facet or all facets of a cube with
DESTROY-FACET
and DESTROY-CUBE
, respectively. Also see
Facet barriers.
[function] DESTROY-FACET CUBE FACET-NAME
Free resources associated with the facet with FACET-NAME
and remove
it from VIEWS
of CUBE
.
[function] DESTROY-CUBE CUBE
Destroy all facets of CUBE
with DESTROY-FACET
.
A facility to control lifetime of facets tied to a dynamic extent. Also see Destroying cubes.
[macro] WITH-FACET-BARRIER (CUBE-TYPE ENSURES DESTROYS) &BODY BODY
When BODY
exits, destroy facets which:
are of cubes with CUBE-TYPE
have a facet name among DESTROYS
were created in in the dynamic extent of BODY
Before destroying the facets make sure that the facets with names
among ENSURES
are up-to-date. WITH-FACET-BARRIERs can be nested, in
case of multiple barriers matching the cube's type and the created
facet's name, the innermost one takes precedence.
The purpose of this macro is twofold. First, it makes it easy to temporarily work with a certain facet of many cubes without leaving newly created facets around. Second, it can be used to make sure that facets whose extent is tied to some dynamic boundary (such as the thread in which they were created) are destroyed.
[function] COUNT-BARRED-FACETS FACET-NAME &KEY (TYPE 'CUBE)
Count facets with FACET-NAME
of cubes of TYPE
which will be
destroyed by a facet barrier.