2008-10-18 02:32:34 +02:00
|
|
|
|
/*
|
2008-10-30 04:34:05 +01:00
|
|
|
|
AllocationCluster - allocating and owning a pile of objects
|
2010-12-17 23:28:49 +01:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
Copyright (C)
|
|
|
|
|
|
2008, Hermann Vosseler <Ichthyostega@web.de>
|
2010-12-17 23:28:49 +01:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
**Lumiera** 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. See the file COPYING for further details.
|
2010-12-17 23:28:49 +01:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
* *****************************************************************/
|
2008-10-18 02:32:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
2016-11-03 18:22:31 +01:00
|
|
|
|
/** @file allocation-cluster.cpp
|
2016-11-08 13:18:05 +01:00
|
|
|
|
** Implementation of [memory management helper functions](\ref allocation-cluster.hpp)
|
|
|
|
|
|
** for the render engine model. Here, in the actual translation unit, the generic part
|
|
|
|
|
|
** of these functions is emitted, while the corresponding header provides a strictly
|
|
|
|
|
|
** typed front-end, based on templates, which forward to the implementation eventually.
|
2024-05-24 17:05:50 +02:00
|
|
|
|
** \par low-level trickery
|
|
|
|
|
|
** The StorageManager implementation exploits object layout knowledge in order to
|
|
|
|
|
|
** operate with the bare minimum of administrative overhead; notably the next allocation
|
|
|
|
|
|
** is always located _within_ the current extent and by assuming that the remaining size
|
|
|
|
|
|
** is tracked correctly, the start of the current extent can always be re-discovered;
|
|
|
|
|
|
** the sequence of extents is managed as a linked list, where the `next*` resides in the
|
|
|
|
|
|
** first »slot« within each Extent; this pointer is _dressed up_ (reinterpreted) as a
|
|
|
|
|
|
** lib::LinkedElements with a heap allocator, which ends up performing the actual
|
|
|
|
|
|
** allocation in blocks of EXTENT_SIZ.
|
2016-11-03 18:20:10 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
2010-12-18 00:58:19 +01:00
|
|
|
|
#include "lib/allocation-cluster.hpp"
|
2024-05-17 23:34:48 +02:00
|
|
|
|
#include "lib/linked-elements.hpp"
|
2024-06-19 15:22:26 +02:00
|
|
|
|
#include "lib/format-string.hpp"
|
|
|
|
|
|
#include "lib/util-quant.hpp"
|
2008-12-17 17:53:32 +01:00
|
|
|
|
#include "lib/util.hpp"
|
2008-10-18 02:32:34 +02:00
|
|
|
|
|
2023-06-11 04:37:38 +02:00
|
|
|
|
|
2024-05-24 17:05:50 +02:00
|
|
|
|
using util::unConst;
|
2024-06-19 15:22:26 +02:00
|
|
|
|
using util::isPow2;
|
2008-10-23 23:08:27 +02:00
|
|
|
|
using util::isnil;
|
2024-06-19 15:22:26 +02:00
|
|
|
|
using util::_Fmt;
|
2024-05-24 17:05:50 +02:00
|
|
|
|
using std::byte;
|
2008-10-30 04:34:05 +01:00
|
|
|
|
|
2008-10-18 02:32:34 +02:00
|
|
|
|
|
|
|
|
|
|
namespace lib {
|
2024-06-19 15:22:26 +02:00
|
|
|
|
namespace {// Internals...
|
2024-05-17 23:34:48 +02:00
|
|
|
|
|
2024-05-25 05:14:36 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Special allocator-policy for lib::LinkedElements
|
|
|
|
|
|
* - does not allow to allocate new elements
|
|
|
|
|
|
* - can hook up elements allocated elsewhere
|
|
|
|
|
|
* - ensure the destructor of all elements is invoked
|
|
|
|
|
|
*/
|
|
|
|
|
|
struct PolicyInvokeDtor
|
|
|
|
|
|
: lib::linked_elements::NoOwnership
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* while this policy doesn't take ownership,
|
|
|
|
|
|
* it ensures the destructor is invoked
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class X>
|
|
|
|
|
|
void
|
2024-05-26 23:54:32 +02:00
|
|
|
|
dispose (X* elm)
|
2024-05-25 05:14:36 +02:00
|
|
|
|
{
|
|
|
|
|
|
REQUIRE (elm);
|
|
|
|
|
|
elm->~X();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-06-19 15:22:26 +02:00
|
|
|
|
}//(End)configuration and internals
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-05-17 23:34:48 +02:00
|
|
|
|
|
2024-05-25 05:14:36 +02:00
|
|
|
|
|
2024-05-17 23:34:48 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* An _overlay view_ for the AllocationCluster to add functionality
|
|
|
|
|
|
* for adding / clearing extents and registering optional deleter functions.
|
|
|
|
|
|
* @warning this is a tricky construct to operate each Allocation Cluster
|
2024-05-25 19:27:17 +02:00
|
|
|
|
* with the absolute minimum of organisational overhead necessary.
|
2024-05-17 23:34:48 +02:00
|
|
|
|
* The key point to note is that StorageManager is layout compatible
|
|
|
|
|
|
* with AllocationCluster itself — achieved through use of the union
|
|
|
|
|
|
* ManagementView, which holds a Storage descriptor member, but
|
|
|
|
|
|
* also an alternate view to manage a chain of extents as
|
|
|
|
|
|
* intrusive linked list (lib::LinkedElements).
|
|
|
|
|
|
* @remark this trick relies on `std::align(pos,rest)` to manage the storage
|
|
|
|
|
|
* coordinates coherently, allowing to re-establish the begin
|
|
|
|
|
|
* of each storage block always, using pointer arithmetics.
|
|
|
|
|
|
*/
|
|
|
|
|
|
class AllocationCluster::StorageManager
|
|
|
|
|
|
{
|
2024-05-25 19:27:17 +02:00
|
|
|
|
|
2024-05-25 05:14:36 +02:00
|
|
|
|
using Destructors = lib::LinkedElements<Destructor, PolicyInvokeDtor>;
|
2024-05-17 23:34:48 +02:00
|
|
|
|
|
2024-05-25 19:27:17 +02:00
|
|
|
|
/** Block of allocated storage */
|
2024-05-17 23:34:48 +02:00
|
|
|
|
struct Extent
|
2024-05-19 17:53:51 +02:00
|
|
|
|
: util::NonCopyable
|
2024-05-17 23:34:48 +02:00
|
|
|
|
{
|
|
|
|
|
|
Extent* next;
|
|
|
|
|
|
Destructors dtors;
|
2024-06-19 15:22:26 +02:00
|
|
|
|
std::byte storage[max_size()];
|
2024-05-17 23:34:48 +02:00
|
|
|
|
};
|
|
|
|
|
|
using Extents = lib::LinkedElements<Extent>;
|
|
|
|
|
|
|
2024-05-27 19:02:31 +02:00
|
|
|
|
static_assert (sizeof(Destructors) == sizeof(void*));
|
|
|
|
|
|
static_assert (sizeof(Extents) == sizeof(void*));
|
|
|
|
|
|
|
2024-05-17 23:34:48 +02:00
|
|
|
|
union ManagementView
|
|
|
|
|
|
{
|
2024-05-24 17:05:50 +02:00
|
|
|
|
Storage storage;
|
2024-05-17 23:34:48 +02:00
|
|
|
|
Extents extents;
|
2024-05-27 19:02:31 +02:00
|
|
|
|
}; //Note: storage.pos and extents.head_ reside at the same location
|
2024-05-17 23:34:48 +02:00
|
|
|
|
|
|
|
|
|
|
ManagementView view_;
|
|
|
|
|
|
|
2024-05-25 19:27:17 +02:00
|
|
|
|
StorageManager() = delete; ///< @note used as _overlay view_ only, never created
|
2024-05-24 17:05:50 +02:00
|
|
|
|
|
2024-05-17 23:34:48 +02:00
|
|
|
|
public:
|
|
|
|
|
|
static StorageManager&
|
|
|
|
|
|
access (AllocationCluster& clu)
|
|
|
|
|
|
{
|
|
|
|
|
|
return reinterpret_cast<StorageManager&> (clu);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
addBlock()
|
|
|
|
|
|
{
|
|
|
|
|
|
closeCurrentBlock();
|
|
|
|
|
|
prependNextBlock();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-19 17:53:51 +02:00
|
|
|
|
void
|
|
|
|
|
|
discardAll()
|
|
|
|
|
|
{
|
|
|
|
|
|
closeCurrentBlock();
|
|
|
|
|
|
view_.extents.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-25 05:14:36 +02:00
|
|
|
|
void
|
2024-05-25 19:27:17 +02:00
|
|
|
|
attach (Destructor& dtor)
|
2024-05-25 05:14:36 +02:00
|
|
|
|
{
|
2024-05-25 19:27:17 +02:00
|
|
|
|
getCurrentBlockStart()->dtors.push (dtor);
|
2024-05-25 05:14:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-05-25 23:35:12 +02:00
|
|
|
|
bool
|
|
|
|
|
|
empty() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return nullptr == view_.storage.pos;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-24 17:05:50 +02:00
|
|
|
|
size_t
|
2024-05-25 23:35:12 +02:00
|
|
|
|
determineExtentCnt() const
|
2024-05-24 17:05:50 +02:00
|
|
|
|
{
|
2024-05-25 23:35:12 +02:00
|
|
|
|
return empty()? 0
|
|
|
|
|
|
: lib::asLinkedElements (getCurrentBlockStart())
|
|
|
|
|
|
.size();
|
2024-05-24 17:05:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
size_t
|
|
|
|
|
|
calcAllocInCurrentBlock() const
|
|
|
|
|
|
{
|
2024-06-19 15:22:26 +02:00
|
|
|
|
ENSURE (max_size() >= view_.storage.rest);
|
|
|
|
|
|
return max_size() - view_.storage.rest;
|
2024-05-24 17:05:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-25 19:27:17 +02:00
|
|
|
|
|
2024-05-17 23:34:48 +02:00
|
|
|
|
private:
|
2024-05-25 05:14:36 +02:00
|
|
|
|
Extent*
|
2024-05-24 17:05:50 +02:00
|
|
|
|
getCurrentBlockStart() const
|
|
|
|
|
|
{
|
2024-05-25 23:35:12 +02:00
|
|
|
|
REQUIRE (not empty());
|
2024-05-25 05:14:36 +02:00
|
|
|
|
void* pos = static_cast<byte*>(view_.storage.pos)
|
|
|
|
|
|
+ view_.storage.rest
|
|
|
|
|
|
- EXTENT_SIZ;
|
|
|
|
|
|
return static_cast<Extent*> (pos);
|
2024-05-24 17:05:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-17 23:34:48 +02:00
|
|
|
|
void
|
|
|
|
|
|
closeCurrentBlock()
|
|
|
|
|
|
{
|
2024-05-25 23:35:12 +02:00
|
|
|
|
if (empty()) return;
|
2024-05-17 23:34:48 +02:00
|
|
|
|
// relocate the pos-pointer to the start of the block
|
2024-05-24 17:05:50 +02:00
|
|
|
|
view_.storage.pos = getCurrentBlockStart();
|
2024-05-17 23:34:48 +02:00
|
|
|
|
view_.storage.rest = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
prependNextBlock()
|
|
|
|
|
|
{
|
|
|
|
|
|
view_.extents.emplace();
|
|
|
|
|
|
view_.storage.pos = & view_.extents.top().storage;
|
2024-06-19 15:22:26 +02:00
|
|
|
|
view_.storage.rest = max_size();
|
2024-05-17 23:34:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2008-10-18 02:32:34 +02:00
|
|
|
|
|
2008-10-30 04:34:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
2024-05-24 17:05:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Prepare a new clustered allocation to be expanded by extents of size
|
|
|
|
|
|
* EXTENT_SIZ, yet discarded all at once when the dtor is called.
|
|
|
|
|
|
* The constructor does not allocate anything immediately.
|
2008-10-18 02:32:34 +02:00
|
|
|
|
*/
|
|
|
|
|
|
AllocationCluster::AllocationCluster()
|
2024-05-24 17:05:50 +02:00
|
|
|
|
: storage_{}
|
2024-05-17 23:34:48 +02:00
|
|
|
|
{
|
|
|
|
|
|
TRACE (memory, "new AllocationCluster");
|
|
|
|
|
|
}
|
2008-10-18 02:32:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
2024-05-24 17:05:50 +02:00
|
|
|
|
/**
|
2024-05-25 19:27:17 +02:00
|
|
|
|
* The shutdown of an AllocationCluster walks all extents and invokes all
|
2024-05-24 17:05:50 +02:00
|
|
|
|
* registered deleter functions and then discards the complete storage.
|
|
|
|
|
|
* @note it is possible to allocate objects as _disposable_ — meaning
|
|
|
|
|
|
* that no destructors will be enrolled and called for such objects.
|
2008-10-18 02:32:34 +02:00
|
|
|
|
*/
|
2018-04-26 12:07:08 +02:00
|
|
|
|
AllocationCluster::~AllocationCluster() noexcept
|
2024-05-17 23:34:48 +02:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
TRACE (memory, "shutting down AllocationCluster");
|
2024-05-19 17:53:51 +02:00
|
|
|
|
StorageManager::access(*this).discardAll();
|
2024-05-17 23:34:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
ERROR_LOG_AND_IGNORE (progress, "discarding AllocationCluster")
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-05-25 19:27:17 +02:00
|
|
|
|
/** virtual dtor to cause invocation of the payload's dtor on clean-up */
|
|
|
|
|
|
AllocationCluster::Destructor::~Destructor() { };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-05-17 23:34:48 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Expand the alloted storage pool by a block,
|
|
|
|
|
|
* suitable to accommodate at least the indicated request.
|
|
|
|
|
|
* @remark Storage blocks are organised as linked list,
|
|
|
|
|
|
* allowing to de-allocate all blocks together.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
AllocationCluster::expandStorage (size_t allocRequest)
|
2008-10-18 02:32:34 +02:00
|
|
|
|
{
|
2024-06-19 15:22:26 +02:00
|
|
|
|
ENSURE (allocRequest <= max_size());
|
2024-05-17 23:34:48 +02:00
|
|
|
|
StorageManager::access(*this).addBlock();
|
2008-10-22 04:55:28 +02:00
|
|
|
|
}
|
2024-05-25 05:14:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2024-05-25 19:27:17 +02:00
|
|
|
|
AllocationCluster::registerDestructor (Destructor& dtor)
|
2024-05-25 05:14:36 +02:00
|
|
|
|
{
|
2024-05-25 19:27:17 +02:00
|
|
|
|
StorageManager::access(*this).attach (dtor);
|
2024-05-25 05:14:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2008-10-22 04:55:28 +02:00
|
|
|
|
|
2024-06-19 15:22:26 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Allocation cluster uses a comparatively small tile size for its extents,
|
|
|
|
|
|
* which turns out to be a frequently encountered limitation in practice.
|
|
|
|
|
|
* This was deemed acceptable, due to its orientation towards performance.
|
|
|
|
|
|
* @throws err::Fatal when a desired allocation can not be accommodated
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
AllocationCluster::__enforce_limits (size_t allocSiz, size_t align)
|
2024-05-17 23:34:48 +02:00
|
|
|
|
{
|
2024-06-19 15:22:26 +02:00
|
|
|
|
REQUIRE (allocSiz);
|
|
|
|
|
|
REQUIRE (align);
|
|
|
|
|
|
REQUIRE (isPow2 (align));
|
|
|
|
|
|
|
|
|
|
|
|
if (allocSiz > max_size())
|
|
|
|
|
|
throw err::Fatal{_Fmt{"AllocationCluster: desired allocation of %d bytes "
|
|
|
|
|
|
"exceeds the fixed extent size of %d"} % allocSiz % max_size()
|
|
|
|
|
|
,LERR_(CAPACITY)};
|
|
|
|
|
|
|
|
|
|
|
|
if (align > max_size())
|
|
|
|
|
|
throw err::Fatal{_Fmt{"AllocationCluster: data requires alignment at %d bytes, "
|
|
|
|
|
|
"which is beyond the fixed extent size of %d"} % align % max_size()
|
|
|
|
|
|
,LERR_(CAPACITY)};
|
2024-05-17 23:34:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-24 17:05:50 +02:00
|
|
|
|
|
2024-06-19 15:22:26 +02:00
|
|
|
|
|
|
|
|
|
|
/* === diagnostics helpers === */
|
|
|
|
|
|
|
2024-05-19 17:53:51 +02:00
|
|
|
|
size_t
|
|
|
|
|
|
AllocationCluster::numExtents() const
|
|
|
|
|
|
{
|
2024-05-25 23:35:12 +02:00
|
|
|
|
return StorageManager::access (unConst(*this)).determineExtentCnt();
|
2024-05-19 17:53:51 +02:00
|
|
|
|
}
|
2024-05-24 17:05:50 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @warning whenever there are more than one extent,
|
|
|
|
|
|
* the returned byte count is guessed only (upper bound), since
|
|
|
|
|
|
* actually allocated size is not tracked to save some overhead.
|
|
|
|
|
|
*/
|
2024-05-19 17:53:51 +02:00
|
|
|
|
size_t
|
|
|
|
|
|
AllocationCluster::numBytes() const
|
|
|
|
|
|
{
|
2024-05-25 23:35:12 +02:00
|
|
|
|
size_t extents = numExtents();
|
|
|
|
|
|
if (not extents) return 0;
|
|
|
|
|
|
size_t bytes = StorageManager::access (unConst(*this)).calcAllocInCurrentBlock();
|
2024-06-19 15:22:26 +02:00
|
|
|
|
return (extents - 1) * max_size() + bytes;
|
2024-05-19 17:53:51 +02:00
|
|
|
|
}
|
2012-04-30 04:28:16 +02:00
|
|
|
|
|
|
|
|
|
|
|
2008-10-18 02:32:34 +02:00
|
|
|
|
} // namespace lib
|