base: new 'Id_space' data structure

This data structure allows the association of objects with IDs. IDs are
kept in an AVL tree. So in contrast to a bit allocator, the ID space can be
sparsely populated and does not need to be dimensioned. The lifetime of
an ID is bound to an 'Element' object, which relieves the programmer
from manually allocating/deallocating IDs for objects.

Issue #2120
This commit is contained in:
Norman Feske 2016-11-06 14:23:28 +01:00 committed by Christian Helmuth
parent 58eabc756b
commit 513a3f1d37

View File

@ -0,0 +1,245 @@
/*
* \brief ID name space
* \author Norman Feske
* \date 2016-10-10
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _INCLUDE__BASE__ID_SPACE_H_
#define _INCLUDE__BASE__ID_SPACE_H_
#include <util/noncopyable.h>
#include <base/lock.h>
#include <base/log.h>
#include <util/avl_tree.h>
namespace Genode { template <typename T> class Id_space; }
template <typename T>
class Genode::Id_space : public Noncopyable
{
public:
struct Id
{
unsigned long value;
bool operator == (Id const &other) const { return value == other.value; }
void print(Output &out) const { Genode::print(out, value); }
};
class Out_of_ids : Exception { };
class Conflicting_id : Exception { };
class Element : public Avl_node<Element>, Noncopyable
{
private:
T &_obj;
Id_space &_id_space;
Id _id { 0 };
friend class Id_space;
/**
* Search the tree for the element with the given ID
*/
Element *_lookup(Id id)
{
if (id.value == _id.value) return this;
Element *e = Avl_node<Element>::child(id.value > _id.value);
return e ? e->_lookup(id) : 0;
}
template <typename ARG, typename FUNC>
void _for_each(FUNC const &fn) const
{
if (Avl_node<Element>::child(Avl_node_base::LEFT))
Avl_node<Element>::child(Avl_node_base::LEFT)->_for_each<ARG>(fn);
fn(static_cast<ARG &>(_obj));
if (Avl_node<Element>::child(Avl_node_base::RIGHT))
Avl_node<Element>::child(Avl_node_base::RIGHT)->_for_each<ARG>(fn);
}
public:
/**
* Constructor
*
* \throw Out_of_ids ID space is exhausted
*/
Element(T &obj, Id_space &id_space)
:
_obj(obj), _id_space(id_space)
{
Lock::Guard guard(_id_space._lock);
_id = id_space._unused_id(*this);
_id_space._elements.insert(this);
}
/**
* Constructor
*
* \throw Conflicting_id 'id' is already present in ID space
*/
Element(T &obj, Id_space &id_space, Id_space::Id id)
:
_obj(obj), _id_space(id_space), _id(id)
{
Lock::Guard guard(_id_space._lock);
_id_space._check_conflict(*this, id);
_id_space._elements.insert(this);
}
~Element()
{
Lock::Guard guard(_id_space._lock);
_id_space._elements.remove(this);
}
/**
* Avl_node interface
*/
bool higher(Element *other) { return other->_id.value > _id.value; }
Id id() const { return _id; }
void print(Output &out) const { Genode::print(out, _id); }
};
private:
Lock mutable _lock; /* protect '_elements' and '_cnt' */
Avl_tree<Element> _elements;
unsigned long _cnt = 0;
/**
* Return ID that does not exist within the ID space
*
* \return ID assigned to the element within the ID space
* \throw Out_of_ids
*/
Id _unused_id(Element &e)
{
unsigned long _attempts = 0;
for (; _attempts < ~0UL; _attempts++, _cnt++) {
Id const id { _cnt };
/* another attempt if is already in use */
if (_elements.first() && _elements.first()->_lookup(id))
continue;
return id;
}
throw Out_of_ids();
}
/**
* Check if ID is already in use
*
* \throw Conflicting_id
*/
void _check_conflict(Element &e, Id id)
{
if (_elements.first() && _elements.first()->_lookup(id))
throw Conflicting_id();
}
public:
class Unknown_id : Exception { };
/**
* Apply functor 'fn' to each ID present in the ID space
*
* \param ARG argument type passed to 'fn', must be convertible
* from 'T' via a 'static_cast'
*
* This function is called with the ID space locked. Hence, it is not
* possible to modify the ID space from within 'fn'.
*/
template <typename ARG, typename FUNC>
void for_each(FUNC const &fn) const
{
Lock::Guard guard(_lock);
if (_elements.first())
_elements.first()->template _for_each<ARG>(fn);
}
/**
* Apply functor 'fn' to object with given ID
*
* See 'for_each' for a description of the 'ARG' argument.
*
* \throw Unknown_id
*/
template <typename ARG, typename FUNC>
void apply(Id id, FUNC const &fn)
{
T *obj = nullptr;
{
Lock::Guard guard(_lock);
if (!_elements.first())
throw Unknown_id();
if (Element *e = _elements.first()->_lookup(id))
obj = &e->_obj;
}
if (obj)
fn(static_cast<ARG &>(*obj));
else
throw Unknown_id();
}
/**
* Apply functor 'fn' to an arbitrary ID present in the ID space
*
* See 'for_each' for a description of the 'ARG' argument.
*
* The functor is called with a reference to the managed object as
* argument. This method is designated for the destruction of ID
* spaces. It allows the caller to remove all IDs by subseqently
* calling this function and destructing the object in 'fn'.
*
* \return true if 'fn' was applied, or
* false if the ID space is empty.
*/
template <typename ARG, typename FUNC>
bool apply_any(FUNC const &fn)
{
T *obj = nullptr;
{
Lock::Guard guard(_lock);
if (_elements.first())
obj = &_elements.first()->_obj;
else
return false;
}
fn(static_cast<ARG &>(*obj));
return true;
}
~Id_space()
{
if (_elements.first())
error("ID space not empty at destruction time");
}
};
#endif /* _INCLUDE__BASE__ID_SPACE_H_ */