LUMIERA.clone/src/lib/advice.hpp

481 lines
16 KiB
C++

/*
ADVICE.hpp - generic loosely coupled interaction guided by symbolic pattern
Copyright (C) Lumiera.org
2010, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file advice.hpp
** Expecting Advice and giving Advice: a cross-cutting collaboration of loosely coupled participants.
** This header exposes the basics of the advice system and the public access points. The advice system
** is a system wide singleton service, but clients never talk directly to this singleton; rather they
** use advice::Provision and advice::Request as access point.
**
** \par Advice collaboration pattern
** Advice collaboration is a special pattern of interaction extracted from multiple use cases within
** Lumiera. Creating this abstraction was partially inspired by aspect oriented programming, especially
** the idea of cross-cutting the primary dependency hierarchy. Another source of inspiration where the
** various incarnations of properties with dynamic binding. For defining the actual binding, we rely
** on predicate notation and matching (planned: unification) as known from rule based systems.
**
** <b>Definition</b>: Advice is an optional, mediated collaboration between entities taking on
** the roles of advisor and advised, thereby passing a custom piece of advice data, managed by
** the advice support system. The possibility of advice is created by both of the collaborators
** entering the system, where the advised entity exposes a point of advice, while the advising
** entity provides an actual advice value.
**
** \par Collaborators
** - the advised entity
** - the advisor
** - point of advice
** - advice system
** - the binding
** - the advice
**
** Usually, the \em advised entity opens the collaboration by requesting an advice. The \em advice itself
** is a piece of data of a custom type, which needs to be copyable. Obviously, both the advised and the
** advisor need to share knowledge about the meaning of this advice data. The actual advice collaboration
** happens at a \em point-of-advice, which needs to be derived first. To this end, the advised puts up an
** \em request by providing his \em binding, which is a pattern for matching. An entity about to give advice
** opens possible advice \em channels by putting up an advisor binding, which similarly is a pattern. The
** advice \em system as mediator resolves both sides, by matching (which in the most general case could be
** an unification). This process creates an advice point \em solution -- allowing the advisor to fed the
** piece of advice into the advice channel, causing it to be placed into the point of advice. After passing
** a certain (implementation defined) break point, the advice leaves the influence of the advisor and gets
** exposed to the advised entities. Especially this involves copying the advice data into a location managed
** by the advice system. In the standard case, the advised entity accesses the advice synchronously and
** non-blocking. Typically, the advice data type is default constructible and thus there is always a basic
** form of advice available, thereby completely decoupling the advised entity from the timings related
** to this collaboration.
**
** \par interfaces and implementation
** Client code is assumed to interface solely through the advice::Request and advice::Provision classes,
** which both can be instantiated and copied freely, may be used as member or mixed in as baseclass.
** The AdviceSystem on the other hand is an implementation facility (actually a singleton) and lives
** in the advice.cpp translation unit. The interface entities inherit protected from AdviceLink,
** which is implemented in the same scope as the AdviceSystem and thus allowed to talk to it
** directly. The AdviceSystem in turn uses advice::Index to keep track of the collaboration
** partners, which, for this purpose, are handled as type-erased PointOfAdvice elements.
** The latter class contains 4 API functions used by the index to manage solutions.
**
** @note as of 6/2010 this is an experimental setup and implemented just enough to work out
** the interfaces and gain practical usage experiences. Ichthyo expects this collaboration
** service to play a central role at various places within proc-layer.
** @todo allow variables in binding patterns
** @todo use the lumiera MPool instead of heap allocations
** @todo consider to provide variations of the basic behaviour by policy classes
** @todo the implementation is generic/defensive, and could be improved and optimised
**
** @see AdviceBasics_test usage example
** @see advice.cpp implementation
** @see advice::Index index datastructure
** @see binding.hpp
**
*/
#ifndef LIB_ADVICE_H
#define LIB_ADVICE_H
#include "lib/error.hpp"
#include "lib/null-value.hpp"
#include "lib/advice/binding.hpp"
#include "lib/symbol.hpp"
#include "lib/util.hpp"
#include <boost/noncopyable.hpp>
namespace lib {
namespace advice {
using lib::Symbol;
using util::isSameObject;
/**
* Basic (abstracted) view of an advice collaboration partner,
* as used internally by the AdviceSystem to manage the participants.
* Each PointOfAdvice is characterised by a binding pattern, used to
* pair up advice::Request and advice::Provision entries. Moreover,
* each PointOfAdvice can refer to an existing advice solution
* provided elsewhere in the system. The specific type of advice
* (and thus the storage requirements) are abstracted away,
* as is the distinction between Request and Provision.
*
* @see AdviceSystem
* @see AdviceIndex_test
*/
class PointOfAdvice
{
Binding::Matcher pattern_;
PointOfAdvice* resolution_;
protected:
/** define or re-define the binding, which
* specifically labels this attachment to the advice system.
* @note issuing this on an existing connection is equivalent
* to re-connecting with the new binding.
*/
void setBindingPattern (Binding const& binding)
{
pattern_ = binding.buildMatcher();
}
public:
explicit
PointOfAdvice (Binding const& binding)
: pattern_(binding.buildMatcher())
, resolution_(0)
{ }
// using default copy/assignment
/* == Adapter interface for use within the Index == */
void
setSolution (PointOfAdvice* solution =0)
{
resolution_ = solution;
}
const PointOfAdvice*
getSolution () const
{
return resolution_;
}
Binding::Matcher
getMatcher () const
{
return pattern_;
}
friend HashVal
hash_value (PointOfAdvice const& entry)
{
return hash_value (entry.pattern_);
}
};
/**
* Advice Collaboration partner, internally connected to the AdviceSystem.
* Both advice::Request and advice::Provision are linked in this way
* to an internal index datastructure, which allows to carry out
* the actual advice exchange and collaboration.
*/
class AdviceLink
: public PointOfAdvice
{
protected:
void publishProvision (PointOfAdvice*);
void discardSolutions ();
void publishRequestBindingChange(HashVal);
void registerRequest();
void deregisterRequest();
static void* getBuffer(size_t);
static void releaseBuffer (void*, size_t);
typedef void (DeleterFunc)(void*);
static void manageAdviceData (PointOfAdvice*, DeleterFunc*);
public:
explicit
AdviceLink (Binding const& binding)
: PointOfAdvice(binding)
{ }
// using default copy/assignment
};
/**
* Access point for the advising entity (server).
* This is the interface intended for client code to set and provide
* concrete advice information of a specific type AD. Instantiating
* automatically creates a \em type-guard binding pattern, but client code
* can (and typically should) provide additional predicates to define the
* "topic" this advice belongs to. This allows advice::Request entries
* to attach to the suitable advice "channels" and get the specific
* piece of advice they're looking for.
*
* Any advice::Provision remains inactive and thus invisible, until
* \link #setAdvice setting the concrete advice data \endlink. After that,
* the provided data is \em copied into the AdviceSystem and remains available
* even after the original Provision goes out of scope. Consequently, it isn't
* possible to \em modify advice data once set. But client code may retract
* the provision or change the binding pattern.
*
* @see AdviceBasics_test usage example
*/
template<class AD>
class Provision
: protected AdviceLink
{
/* == policy definitions == */ ////TODO: extract into policy classes
void deregistrate() { /* NOP */ }
public:
explicit
Provision (Literal bindingSpec =0)
: AdviceLink (Binding(bindingSpec).addTypeGuard<AD>())
{ }
~Provision()
{
this->deregistrate();
}
Provision (Provision const& o)
: AdviceLink(o)
{
setSolution ( NULL );
}
Provision&
operator= (Provision const& o)
{
if (!isSameObject(*this, o))
{
AdviceLink::operator= (o);
setSolution ( NULL );
}
}
void setAdvice (AD const& pieceOfAdvice)
{
publishProvision(
storeCopy (pieceOfAdvice));
}
void retractAdvice()
{
discardSolutions();
}
void
defineBinding (Literal topic)
{
setBindingPattern (Binding(topic).addTypeGuard<AD>());
maybe_rePublish();
}
private:
PointOfAdvice* storeCopy (AD const& advice_given);
static void releaseAdviceData (void*);
void maybe_rePublish ();
};
/**
* Piece of Advice as incorporated into the AdviceSystem.
* This holder-object contains a copy of the advice data
* and is placed into the internal storage buffer; the
* advice index keeps a (type erased) pointer to serve
* any requests which happen to match the binding.
*
* @note the ptr-to-solution in the inherited PointOfAdvice
* is currently (5/10) not used, because this \em is
* already the solution.
*/
template<class AD>
class ActiveProvision
: public PointOfAdvice
, boost::noncopyable
{
AD theAdvice_;
public:
AD const&
getAdvice() const
{
return theAdvice_;
}
protected:
ActiveProvision (PointOfAdvice const& refPoint, AD const& advice_given)
: PointOfAdvice(refPoint)
, theAdvice_(advice_given)
{
this->setSolution (this); // not used currently (5/10)
}
friend class Provision<AD>;
};
/* ==== memory management for Provision data ===== */
/** @internal function to copy advice into an internal buffer.
@return type erased pointer to the data holder created
@throw error::Fatal on allocation problems, plus anything
the advice data may throw during copy construction. */
template<class AD>
inline PointOfAdvice*
Provision<AD>::storeCopy (AD const& advice_given)
{
typedef ActiveProvision<AD> Holder;
void* storage = getBuffer(sizeof(Holder));
try
{
Holder* storedProvision = new(storage) Holder (*this, advice_given);
manageAdviceData (storedProvision, &releaseAdviceData);
return storedProvision;
}
catch(...)
{
Symbol errID = lumiera_error();
releaseBuffer (storage, sizeof(Holder));
throw lumiera::error::Fatal ("Failure to store advice data", errID);
}
}
/** @internal assist the AdviceSystem with deallocating buffer storage.
* Problem is we need to know the exact size of the advice value holder,
* which information is available only here, in the fully typed context.
*/
template<class AD>
inline void
Provision<AD>::releaseAdviceData (void* entry)
{
typedef ActiveProvision<AD> Holder;
if (entry)
{
Holder* obj = static_cast<Holder*> (entry);
obj->~Holder();
releaseBuffer (entry, sizeof(Holder));
}
}
/** @internal in case we've already published this provision,
* we temporarily need a new provision entry, to allow the
* AdviceSystem implementation to rewrite the internal index
*/
template<class AD>
inline void
Provision<AD>::maybe_rePublish ()
{
typedef const ActiveProvision<AD> AdviceProvision;
AdviceProvision* solution = static_cast<AdviceProvision*> (getSolution());
if (solution) // create copy of the data holder, using the new binding
publishProvision(
storeCopy (solution->getAdvice()));
}
/**
* Access point for the advised entity (client).
* This is the interface intended for client code to request advice
* of a specific type and optionally limited to a special topic (binding).
* Instantiating an \c Request<AD> object automatically entails a registration
* with the AdviceSystem behind the scenes, and deleting it causes deregistration.
* Request objects may be instantiated and copied freely, and the binding pattern
* may be changed. The actual advice is accessed through the member function
* #getAdvice, which might return a default-constructed piece of advice data
* in case no specific advice has been provided yet. Accessing advice can be
* considered a lightweight operation, while creating/destroying advice
* causes an index operation and thus requires a lock.
*
* Creating an Request and changing the binding might fail, while the dtor
* is protected against failure (as you'd expect). Accessing advice can also
* be considered safe, given the concrete advice type can be default constructed
* without failure in case there isn't any advice data provided yet.
*/
template<class AD>
class Request
: protected AdviceLink
{
typedef const ActiveProvision<AD> AdviceProvision;
/* == policy definitions == */ ////TODO: extract into policy classes
AD const&
handleMissingSolution() const ///< @warning might segfault when used during shutdown
{
return NullValue<AD>::get();
}
public:
explicit
Request (Literal bindingSpec =0)
: AdviceLink (Binding(bindingSpec).addTypeGuard<AD>())
{
registerRequest();
}
~Request()
{
deregisterRequest();
}
// copying Requests is allowed, using default
AD const&
getAdvice() const
{
AdviceProvision* solution = static_cast<AdviceProvision*> (this->getSolution());
if (!solution)
return this->handleMissingSolution();
else
return solution->getAdvice();
}
void
defineBinding (Literal topic)
{
HashVal previous_bindingKey (hash_value(*this));
setBindingPattern (Binding(topic).addTypeGuard<AD>());
publishRequestBindingChange (previous_bindingKey);
}
};
}} // namespace lib::advice
#endif