sync documentation with newly merged master

This commit is contained in:
Fischlurch 2011-11-27 06:22:57 +01:00
commit d62029492c
112 changed files with 4507 additions and 1278 deletions

View file

@ -55,11 +55,15 @@
#include "gui/window-manager.hpp"
#include <boost/noncopyable.hpp>
#include <tr1/memory>
#include <vector>
namespace gui {
using std::tr1::shared_ptr;
/* ====== The Application Class ====== */

View file

@ -24,7 +24,7 @@
#include "clip.hpp"
#include <boost/shared_ptr.hpp>
using namespace std::tr1;
namespace gui {
namespace model {
@ -33,7 +33,7 @@ namespace model {
{
// TEST CODE: add a clip to the track
boost::shared_ptr<model::Clip> modelClip(new model::Clip());
shared_ptr<model::Clip> modelClip(new model::Clip());
modelClip->setName("Clip Name");
clips.push_back(modelClip);
@ -50,7 +50,7 @@ namespace model {
return os.str();
}
lumiera::observable_list< boost::shared_ptr<Clip> >&
lumiera::observable_list< shared_ptr<Clip> >&
ClipTrack::getClipList()
{
return clips;

View file

@ -28,7 +28,7 @@
#ifndef CLIP_TRACK_HPP
#define CLIP_TRACK_HPP
#include "track.hpp"
#include "gui/model/track.hpp"
#include "lib/observable-list.hpp"
namespace gui {
@ -53,12 +53,12 @@ namespace model {
/**
* Gets the list of clips associated with this track.
*/
lumiera::observable_list< boost::shared_ptr<Clip> >&
lumiera::observable_list<shared_ptr<Clip> >&
getClipList(void);
private:
lumiera::observable_list< boost::shared_ptr<Clip> > clips;
lumiera::observable_list<shared_ptr<Clip> > clips;
};

View file

@ -32,7 +32,7 @@ namespace gui {
namespace model {
Clip::Clip()
: timeCoord_(Time(FSecs(1)), FSecs(2))
: timeCoord_(Time(FSecs(1)), FSecs(3))
{ }
@ -40,14 +40,14 @@ namespace model {
Clip::setBegin (Time newStartTime)
{
timeCoord_.accept (Mutation::changeTime (newStartTime));
// TODO: emit signal
TODO("Emit A Signal");
}
void
Clip::setDuration (Duration newLength)
{
timeCoord_.accept (Mutation::changeDuration(newLength));
// TODO: emit signal
TODO("Emit A Signal");
}
void

View file

@ -23,7 +23,7 @@
#include "parent-track.hpp"
#include <boost/foreach.hpp>
using namespace boost;
using std::tr1::shared_ptr;
namespace gui {
namespace model {
@ -32,13 +32,13 @@ ParentTrack::ParentTrack()
{
}
const std::list< boost::shared_ptr<Track> >&
const std::list<shared_ptr<Track> >&
ParentTrack::get_child_tracks() const
{
return tracks.get_list();
}
lumiera::observable_list< boost::shared_ptr<Track> >&
lumiera::observable_list<shared_ptr<Track> >&
ParentTrack::get_child_track_list()
{
return tracks;
@ -52,11 +52,11 @@ ParentTrack::can_host_children() const
bool
ParentTrack::remove_descendant_track(
const boost::shared_ptr<Track> track)
const shared_ptr<Track> track)
{
REQUIRE(track);
boost::shared_ptr<ParentTrack> parent =
shared_ptr<ParentTrack> parent =
find_descendant_track_parent(track);
if(parent)
{
@ -67,23 +67,24 @@ ParentTrack::remove_descendant_track(
return false;
}
boost::shared_ptr<ParentTrack>
ParentTrack::find_descendant_track_parent(
boost::shared_ptr<Track> child)
shared_ptr<ParentTrack>
ParentTrack::find_descendant_track_parent(shared_ptr<Track> child)
{
using namespace boost;
REQUIRE(child != NULL);
BOOST_FOREACH(shared_ptr<Track> track, tracks)
BOOST_FOREACH(std::tr1::shared_ptr<Track> track, tracks)
{
if(track == child)
return shared_from_this();
shared_ptr<ParentTrack> result =
std::tr1::shared_ptr<ParentTrack> result =
track->find_descendant_track_parent(child);
if(result)
return result;
}
return shared_ptr<ParentTrack>();
return std::tr1::shared_ptr<ParentTrack>();
}
} // namespace model

View file

@ -28,13 +28,9 @@
#ifndef PARENT_TRACK_HPP
#define PARENT_TRACK_HPP
#include "track.hpp"
#include "gui/model/track.hpp"
#include "lib/observable-list.hpp"
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
namespace gui {
namespace model {
@ -44,7 +40,7 @@ namespace model {
*/
class ParentTrack :
public Track,
public boost::enable_shared_from_this<ParentTrack>
public std::tr1::enable_shared_from_this<ParentTrack>
{
protected:
/**
@ -57,13 +53,13 @@ public:
/**
* Gets a read-only reference to the the list of child tracks.
*/
const std::list< boost::shared_ptr<Track> >&
const std::list<shared_ptr<Track> >&
get_child_tracks() const;
/**
* Gets read-write access to the list of child tracks.
*/
lumiera::observable_list< boost::shared_ptr<Track> >&
lumiera::observable_list<shared_ptr<Track> >&
get_child_track_list();
/**
@ -79,7 +75,7 @@ public:
* @param The model track to try and remove.
* @return Returns true if the track was successfully removed.
*/
bool remove_descendant_track(const boost::shared_ptr<Track> track);
bool remove_descendant_track (const shared_ptr<Track> track);
/**
* A utility function that attempts to find the parent of a track by
@ -88,14 +84,14 @@ public:
* @return Returns the parent track if one was found, or an empty
* shared_ptr if none was found.
*/
boost::shared_ptr<ParentTrack>
find_descendant_track_parent(boost::shared_ptr<Track> child);
shared_ptr<ParentTrack>
find_descendant_track_parent (shared_ptr<Track> child);
protected:
/**
* The internal list of child tracks of this parent.
*/
lumiera::observable_list< boost::shared_ptr<Track> > tracks;
lumiera::observable_list<shared_ptr<Track> > tracks;
};
} // namespace model

View file

@ -26,7 +26,7 @@
#include "sequence.hpp"
using namespace std;
using namespace boost;
using namespace std::tr1;
namespace gui {
namespace model {
@ -43,7 +43,7 @@ Project::~Project()
}
lumiera::observable_list< boost::shared_ptr<Sequence> >&
lumiera::observable_list< shared_ptr<Sequence> >&
Project::get_sequences()
{
return sequences;

View file

@ -27,7 +27,7 @@
#ifndef PROJECT_HPP
#define PROJECT_HPP
#include "sequence.hpp"
#include "gui/model/sequence.hpp"
#include "lib/observable-list.hpp"
namespace gui {
@ -40,14 +40,14 @@ public:
~Project();
lumiera::observable_list< boost::shared_ptr<Sequence> >&
lumiera::observable_list<shared_ptr<Sequence> >&
get_sequences();
void add_new_sequence(uString name);
private:
lumiera::observable_list< boost::shared_ptr<Sequence> > sequences;
lumiera::observable_list<shared_ptr<Sequence> > sequences;
};
} // namespace model

View file

@ -27,7 +27,7 @@
#include "group-track.hpp"
#include "clip-track.hpp"
using namespace boost;
using namespace std::tr1;
namespace gui {
namespace model {

View file

@ -27,7 +27,7 @@
#ifndef SEQUENCE_HPP
#define SEQUENCE_HPP
#include "parent-track.hpp"
#include "gui/model/parent-track.hpp"
namespace gui {
namespace model {

View file

@ -24,7 +24,7 @@
#include "parent-track.hpp"
#include <boost/foreach.hpp>
using namespace boost;
using namespace std::tr1;
using namespace std;
namespace gui {
@ -35,11 +35,12 @@ const list< shared_ptr<Track> > Track::NoChildren;
Track::Track()
: enabled(true),
locked(false)
{
{ }
}
Track::~Track() { }
const std::list< boost::shared_ptr<Track> >&
const std::list< shared_ptr<Track> >&
Track::get_child_tracks() const
{
return Track::NoChildren;
@ -96,9 +97,9 @@ Track::print_branch()
return print_branch_recursive(0);
}
boost::shared_ptr<ParentTrack>
shared_ptr<ParentTrack>
Track::find_descendant_track_parent(
boost::shared_ptr<Track> /*child*/)
shared_ptr<Track> /*child*/)
{
return shared_ptr<ParentTrack>();
}

View file

@ -47,6 +47,7 @@ protected:
Track();
public:
virtual ~Track(); /// this is an interface
/**
* Returns true if this track can own any child tracks.
@ -57,7 +58,7 @@ public:
/**
* Gets the list of child tracks.
*/
virtual const std::list< boost::shared_ptr<Track> >&
virtual const std::list<shared_ptr<Track> >&
get_child_tracks () const;
/**
@ -106,8 +107,8 @@ public:
* @return Returns the parent track if one was found, or an empty
* shared_ptr if none was found.
*/
virtual boost::shared_ptr<ParentTrack>
find_descendant_track_parent (boost::shared_ptr<Track> child);
virtual shared_ptr<ParentTrack>
find_descendant_track_parent (shared_ptr<Track> child);
/**
* A signal which fires when the enabled status changes.
@ -154,7 +155,7 @@ protected:
* An object used internally as a return value for when there's no
* children.
*/
static const std::list< boost::shared_ptr<Track> > NoChildren;
static const std::list< shared_ptr<Track> > NoChildren;
/**
* The internal implementation of print_branch.

View file

@ -27,11 +27,12 @@
#ifndef PANEL_HPP
#define PANEL_HPP
#include <gdl/gdl-dock-item.h>
#include "gui/gtk-lumiera.hpp"
#include "gui/widgets/panel-bar.hpp"
#include <gdl/gdl-dock-item.h>
namespace gui {
namespace workspace {

View file

@ -39,8 +39,8 @@ using namespace gui::widgets;
using namespace gui::widgets::timeline;
using namespace gui::model;
using boost::shared_ptr; ///////////////////////////////TICKET #796
using boost::weak_ptr; ///////////////////////////////TICKET #796
using std::tr1::shared_ptr;
using std::tr1::weak_ptr;
using util::contains;
namespace gui {
@ -130,8 +130,7 @@ TimelinePanel::TimelinePanel (workspace::PanelManager &panel_manager,
zoomScale .set_tooltip_text(_("Adjust timeline zoom scale"));
// Setup the timeline widget
shared_ptr<Sequence> sequence ///////////////////////////////TICKET #796 : should use std::tr1::shared_ptr instead of boost
= *get_project().get_sequences().begin();
shared_ptr<Sequence> sequence = *(get_project().get_sequences().begin());
timelineWidget.reset(new TimelineWidget(load_state(sequence)));
pack_start(*timelineWidget, PACK_EXPAND_WIDGET);
@ -139,7 +138,7 @@ TimelinePanel::TimelinePanel (workspace::PanelManager &panel_manager,
// wire the zoom slider to react on timeline state changes
zoomScale.wireTimelineState (timelineWidget->get_state(),
timelineWidget->state_changed_signal());
// Set the initial UI state
update_sequence_chooser();
update_tool_buttons();
@ -334,19 +333,9 @@ TimelinePanel::update_tool_buttons()
void
TimelinePanel::update_zoom_buttons()
{
REQUIRE(timelineWidget);
const shared_ptr<timeline::TimelineState> state =
timelineWidget->get_state();
if(state)
{
timeline::TimelineViewWindow &viewWindow =
state->get_view_window();
zoomIn.set_sensitive(viewWindow.get_time_scale() != 1);
zoomOut.set_sensitive(viewWindow.get_time_scale()
!= TimelineWidget::MaxScale);
}
/* This function is no longer needed
* TODO: Let the ZoomScaleWidget perform
* the update on its own */
}
void

View file

@ -36,13 +36,15 @@
#include "lib/time/timevalue.hpp"
#include <boost/scoped_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <tr1/memory>
using namespace gui::widgets;
namespace gui {
using std::tr1::shared_ptr;
using std::tr1::weak_ptr;
namespace model {
class Sequence;
@ -53,6 +55,7 @@ namespace panels {
using lib::time::Time;
/**
* The definition of the timeline panel class, which holds timeline
* widgets.
@ -127,8 +130,8 @@ private:
void show_time (Time);
boost::shared_ptr<widgets::timeline::TimelineState> ///////////////////////////////TICKET #796
load_state (boost::weak_ptr<model::Sequence> sequence);
shared_ptr<widgets::timeline::TimelineState>
load_state (weak_ptr<model::Sequence> sequence);
private:
@ -148,7 +151,7 @@ private:
* An invisible column which will be used to identify the sequence
* of a row.
*/
Gtk::TreeModelColumn< boost::weak_ptr<model::Sequence> >
Gtk::TreeModelColumn< weak_ptr<model::Sequence> >
sequenceColumn;
/**
@ -172,9 +175,10 @@ private:
// Body Widgets
boost::scoped_ptr<TimelineWidget> timelineWidget;
std::map< boost::weak_ptr<model::Sequence>,
boost::shared_ptr<widgets::timeline::TimelineState> > ///////////////////////////////TICKET #796 : should use std::tr1::shared_ptr
std::map< weak_ptr<model::Sequence>
, shared_ptr<widgets::timeline::TimelineState>
>
timelineStates;
// Toolbar Widgets

View file

@ -21,13 +21,13 @@
* *****************************************************/
#include "gui/gtk-lumiera.hpp"
#include "gui/panels/viewer-panel.hpp"
#include "gui/workspace/workspace-window.hpp"
#include "gui/controller/controller.hpp"
#include "gui/controller/playback-controller.hpp"
#include "gui/display-service.hpp"
#include "gui/workspace/workspace-window.hpp"
#include "viewer-panel.hpp"
using namespace Gtk;
using namespace gui::widgets;

View file

@ -26,11 +26,12 @@
#ifndef VIEWER_PANEL_HPP
#define VIEWER_PANEL_HPP
#include <gtkmm.h>
#include "panel.hpp"
#include "gui/panels/panel.hpp"
#include "gui/widgets/video-display-widget.hpp"
#include <gtkmm.h>
namespace gui {
namespace panels {

View file

@ -27,7 +27,8 @@
using namespace Gtk;
using namespace std;
using namespace boost;
using namespace std::tr1;
using namespace util;
using namespace gui::widgets::timeline;
@ -40,21 +41,19 @@ namespace widgets {
const int TimelineWidget::TrackPadding = 1;
const int TimelineWidget::HeaderWidth = 150;
const int TimelineWidget::HeaderIndentWidth = 10;
const double TimelineWidget::ZoomIncrement = 1.25;
const int64_t TimelineWidget::MaxScale = 30000000; // 30 Million
TimelineWidget::TimelineWidget(
boost::shared_ptr<timeline::TimelineState> source_state) :
Table(2, 2),
layoutHelper(*this),
headerContainer(NULL),
body(NULL),
ruler(NULL),
horizontalAdjustment(0, 0, 0),
verticalAdjustment(0, 0, 0),
horizontalScroll(horizontalAdjustment),
verticalScroll(verticalAdjustment),
update_tracks_frozen(false)
TimelineWidget::TimelineWidget(shared_ptr<timeline::TimelineState> source_state)
: Table(2, 2)
, layoutHelper(*this)
, headerContainer(NULL)
, body(NULL)
, ruler(NULL)
, horizontalAdjustment(0, 0, 0)
, verticalAdjustment(0, 0, 0)
, horizontalScroll(horizontalAdjustment)
, verticalScroll(verticalAdjustment)
, update_tracks_frozen(false)
{
body = manage(new TimelineBody(*this));
ENSURE(body != NULL);
@ -62,7 +61,7 @@ TimelineWidget::TimelineWidget(
ENSURE(headerContainer != NULL);
ruler = manage(new TimelineRuler(*this));
ENSURE(ruler != NULL);
horizontalAdjustment.signal_value_changed().connect( sigc::mem_fun(
this, &TimelineWidget::on_scroll) );
verticalAdjustment.signal_value_changed().connect( sigc::mem_fun(
@ -71,7 +70,7 @@ TimelineWidget::TimelineWidget(
this, &TimelineWidget::on_motion_in_body_notify_event) );
update_tracks();
attach(*body, 1, 2, 1, 2, FILL|EXPAND, FILL|EXPAND);
attach(*ruler, 1, 2, 0, 1, FILL|EXPAND, SHRINK);
attach(*headerContainer, 0, 1, 1, 2, SHRINK, FILL|EXPAND);
@ -93,7 +92,7 @@ TimelineWidget::~TimelineWidget()
/* ===== Data Access ===== */
boost::shared_ptr<timeline::TimelineState>
shared_ptr<timeline::TimelineState>
TimelineWidget::get_state()
{
return state;
@ -334,8 +333,8 @@ TimelineWidget::create_timeline_track_from_modelTrack(
void
TimelineWidget::remove_orphaned_tracks()
{
std::map<boost::shared_ptr<model::Track>,
boost::shared_ptr<timeline::Track> >
std::map<shared_ptr<model::Track>,
shared_ptr<timeline::Track> >
orphan_track_map(trackMap);
// Remove all tracks which are still present in the sequence
@ -356,9 +355,9 @@ TimelineWidget::remove_orphaned_tracks()
void
TimelineWidget::search_orphaned_tracks_in_branch(
boost::shared_ptr<model::Track> modelTrack,
std::map<boost::shared_ptr<model::Track>,
boost::shared_ptr<timeline::Track> > &orphan_track_map)
shared_ptr<model::Track> modelTrack,
std::map<shared_ptr<model::Track>,
shared_ptr<timeline::Track> > &orphan_track_map)
{
REQUIRE(modelTrack);
@ -414,12 +413,13 @@ TimelineWidget::update_scroll()
if(state)
{
///////////////////////////////////////////////TICKET #861 shoudln't that be performed by TimelineViewWindow, instead of manipulating values from the outside?
timeline::TimelineViewWindow &window = state->get_view_window();
//----- Horizontal Scroll ------//
// TEST CODE
horizontalAdjustment.set_upper(1000 * GAVL_TIME_SCALE / 200);
horizontalAdjustment.set_upper( 1000 * GAVL_TIME_SCALE / 200);
horizontalAdjustment.set_lower(-1000 * GAVL_TIME_SCALE / 200);
// Set the page size
@ -480,7 +480,7 @@ TimelineWidget::on_motion_in_body_notify_event(GdkEventMotion *event)
return true;
}
boost::shared_ptr<model::Sequence>
shared_ptr<model::Sequence>
TimelineWidget::sequence() const
{
if(!state)

View file

@ -64,17 +64,13 @@ class TimelineWidget : public Gtk::Table
{
public:
/**
* Constructor
* @param source_state The state that will be used as the data source
* for this timeline widget.
*/
TimelineWidget(
boost::shared_ptr<timeline::TimelineState> source_state);
TimelineWidget (shared_ptr<timeline::TimelineState> source_state);
/**
* Destructor
*/
~TimelineWidget();
virtual ~TimelineWidget();
/* ===== Data Access ===== */
public:
@ -84,13 +80,13 @@ public:
* @return The state object that the timeline widget is currently
* working with.
*/
boost::shared_ptr<timeline::TimelineState> get_state();
shared_ptr<timeline::TimelineState> get_state();
/**
* Replaces the current TimelineState object with another.
* @param new_state The new state to swap in.
*/
void set_state(boost::shared_ptr<timeline::TimelineState> new_state);
void set_state(shared_ptr<timeline::TimelineState> new_state);
/**
* Zooms the view in or out as by a number of steps while keeping a
@ -110,13 +106,13 @@ public:
*/
void set_tool(timeline::ToolType tool_type);
boost::shared_ptr<timeline::Track>
shared_ptr<timeline::Track>
get_hovering_track() const;
public:
/* ===== Signals ===== */
typedef sigc::signal<void, boost::shared_ptr<timeline::TimelineState> > TimelineStateChangeSignal;
typedef sigc::signal<void, boost::shared_ptr<timeline::Track> > HoveringTrackChangedSignal;
typedef sigc::signal<void, shared_ptr<timeline::TimelineState> > TimelineStateChangeSignal;
typedef sigc::signal<void, shared_ptr<timeline::Track> > HoveringTrackChangedSignal;
sigc::signal<void, lib::time::Time> mouse_hover_signal() const;
@ -169,8 +165,8 @@ private:
* already exist in trackMap.
* @param list The parent track of the branch.
*/
void create_timeline_tracks_from_branch(
boost::shared_ptr<model::Track> modelTrack);
void
create_timeline_tracks_from_branch (shared_ptr<model::Track> modelTrack);
/**
* Creates a timeline UI track to correspond to a model track.
@ -178,9 +174,8 @@ private:
* @return The timeline track created, or an empty shared_ptr if
* modelTrack has an unreckognised type (this is an error condition).
*/
boost::shared_ptr<timeline::Track>
create_timeline_track_from_modelTrack(
boost::shared_ptr<model::Track> modelTrack);
shared_ptr<timeline::Track>
create_timeline_track_from_modelTrack(shared_ptr<model::Track> modelTrack);
/**
* Removes any UI tracks which no longer have corresponding model
@ -188,10 +183,10 @@ private:
*/
void remove_orphaned_tracks();
void search_orphaned_tracks_in_branch(
boost::shared_ptr<model::Track> modelTrack,
std::map<boost::shared_ptr<model::Track>,
boost::shared_ptr<timeline::Track> > &orphan_track_map);
void
search_orphaned_tracks_in_branch (shared_ptr<model::Track> modelTrack,
std::map<shared_ptr<model::Track>,
shared_ptr<timeline::Track> > &orphan_track_map);
/**
* Looks up a timeline UI track in trackMap that corresponds to a
@ -201,8 +196,8 @@ private:
* modelTrack has no corresponding timeline UI track (this is an
* error condition).
*/
boost::shared_ptr<timeline::Track> lookup_timeline_track(
boost::shared_ptr<model::Track> modelTrack) const;
shared_ptr<timeline::Track>
lookup_timeline_track (shared_ptr<model::Track> modelTrack) const;
// ----- Layout Functions ----- //
@ -232,12 +227,12 @@ private:
* Helper to get the sequence object from the state.
* @return Returns a shared pointer to the sequence.
*/
boost::shared_ptr<model::Sequence> sequence() const;
shared_ptr<model::Sequence> sequence() const;
// ----- Other Functions ----- //
void set_hovering_track(
boost::shared_ptr<timeline::Track> hovering_track);
void
set_hovering_track (shared_ptr<timeline::Track> hovering_track);
protected:
@ -245,7 +240,7 @@ protected:
* The state that will be used as the data source for this timeline
* widget.
*/
boost::shared_ptr<timeline::TimelineState> state;
shared_ptr<timeline::TimelineState> state;
// Model Data
@ -256,11 +251,11 @@ protected:
* widget is updated with update_tracks, timeline tracks are added and
* removed from the map in correspondence with the tree.
*/
std::map<boost::shared_ptr<model::Track>,
boost::shared_ptr<timeline::Track> >
std::map<shared_ptr<model::Track>
,shared_ptr<timeline::Track> >
trackMap;
boost::shared_ptr<timeline::Track> hoveringTrack;
shared_ptr<timeline::Track> hoveringTrack;
// Helper Classes
timeline::TimelineLayoutHelper layoutHelper;
@ -283,19 +278,10 @@ protected:
bool update_tracks_frozen;
/* ===== Constants ===== */
public:
/**
* The maximum scale for timeline display.
* @remarks At MaxScale, every pixel on the timeline is equivalent
* to 30000000 lumiera::Time increments.
*/
static const int64_t MaxScale;
protected:
static const int TrackPadding;
static const int HeaderWidth;
static const int HeaderIndentWidth;
static const double ZoomIncrement;
friend class timeline::TimelineBody;
friend class timeline::TimelineHeaderContainer;

View file

@ -55,17 +55,17 @@ namespace timeline {
Tool::on_button_press_event(event);
// Convert the mouse click position to a Time
boost::shared_ptr<TimelineState> state = timelineBody.getTimelineWidget().get_state();
shared_ptr<TimelineState> state = timelineBody.getTimelineWidget().get_state();
REQUIRE(state);
TimelineViewWindow const& window = state->get_view_window();
Time tpoint = window.x_to_time(mousePoint.get_x());
// Get the clip, if any
boost::shared_ptr<timeline::Track> track = getHoveringTrack();
boost::shared_ptr<Clip> clip = track->getClipAt(tpoint);
shared_ptr<timeline::Track> track = getHoveringTrack();
shared_ptr<Clip> clip = track->getClipAt(tpoint);
// Nothing to do if there is no clip
if (clip == boost::shared_ptr<Clip>())
if (!clip)
return;
clip->setSelected(true);
@ -77,8 +77,7 @@ namespace timeline {
REQUIRE (event != NULL);
Tool::on_button_release_event(event);
boost::shared_ptr<timeline::Track> track =
getHoveringTrack();
shared_ptr<timeline::Track> track = getHoveringTrack();
}
void
@ -92,10 +91,10 @@ namespace timeline {
return;
}
boost::shared_ptr<timeline::Track>
shared_ptr<timeline::Track>
ArrowTool::getHoveringTrack ()
{
boost::shared_ptr<timeline::Track> track(
shared_ptr<timeline::Track> track(
timelineBody.getTimelineWidget().get_hovering_track());
return track;
}

View file

@ -81,7 +81,7 @@ namespace timeline {
private:
boost::shared_ptr<timeline::Track>
shared_ptr<timeline::Track>
getHoveringTrack ();
bool selectionRectangleActive;

View file

@ -34,12 +34,10 @@
using namespace Gtk;
using namespace std;
using namespace boost;
using namespace lumiera;
using gui::util::CairoUtil;
using boost::shared_ptr; ////////////////////TICKET #796
using std::tr1::shared_ptr;
namespace gui {
namespace widgets {
@ -59,9 +57,12 @@ TimelineBody::TimelineBody (TimelineWidget &timelineWidget)
timelineWidget.state_changed_signal().connect(
sigc::mem_fun(this, &TimelineBody::on_state_changed) );
// Set a default Tool
this->set_tool(Arrow);
// Install style properties
register_styles();
// Reset the state
propagateStateChange();
}
@ -92,10 +93,10 @@ TimelineBody::get_tool() const
}
void
TimelineBody::set_tool(timeline::ToolType tool_type)
TimelineBody::set_tool(timeline::ToolType tool_type, bool force)
{
// Tidy up old tool
if(tool)
if(tool && !force)
{
// Do we need to change tools?
if(tool->get_type() == tool_type)
@ -266,7 +267,8 @@ TimelineBody::on_motion_notify_event(GdkEventMotion *event)
if (timelineState)
{
// Handle a middle-mouse drag if one is occuring
// Handle a middle-mouse drag if one is occurring
/////////////////////////////TICKET #861 : shoudln't all of that be performed by TimelineViewWindow, instead of manipulating values from the outside?
switch(dragType)
{
case Shift:
@ -318,7 +320,10 @@ TimelineBody::propagateStateChange()
viewWindow().changed_signal().connect(
sigc::mem_fun(this, &TimelineBody::on_update_view) );
}
// Need to reload the current tool...
set_tool (get_tool(), true);
// Redraw
queue_draw();
}
@ -351,7 +356,7 @@ TimelineBody::draw_tracks(Cairo::RefPtr<Cairo::Context> cr)
const shared_ptr<timeline::Track> timeline_track =
timelineWidget.lookup_timeline_track(*iterator);
optional<Gdk::Rectangle> rect =
boost::optional<Gdk::Rectangle> rect =
layout_helper.get_track_header_rect(timeline_track);
// Is this track visible?

View file

@ -46,6 +46,7 @@ class TimelineWidget;
namespace timeline {
using lib::time::TimeVar;
using lib::time::TimeSpan;
class Track;
@ -84,7 +85,7 @@ public:
* @param tool_type The type of tool to set.
*/
void
set_tool(ToolType tool_type);
set_tool(ToolType tool_type, bool force=false);
/* ===== Events ===== */
protected:
@ -124,7 +125,7 @@ protected:
/**
* The event handler for when the TimelineWidget's state is switched.
*/
void on_state_changed (boost::shared_ptr<TimelineState> newState);
void on_state_changed (shared_ptr<TimelineState> newState);
/* ===== Internals ===== */
private:
@ -142,8 +143,8 @@ private:
void draw_tracks(Cairo::RefPtr<Cairo::Context> cr);
void draw_track(Cairo::RefPtr<Cairo::Context> cr,
boost::shared_ptr<timeline::Track> timeline_track,
const int view_width) const;
shared_ptr<timeline::Track> timeline_track,
const int view_width) const;
/**
* Draws the selected timeline period.
@ -200,7 +201,7 @@ private:
Cairo::RefPtr<Cairo::SolidPattern> playbackPointColour;
gui::widgets::TimelineWidget &timelineWidget;
boost::shared_ptr<TimelineState> timelineState; ////////////////////TICKET #796 : should use std::tr1::shared_ptr
shared_ptr<TimelineState> timelineState;
friend class Tool;

View file

@ -22,13 +22,13 @@
#include <boost/foreach.hpp>
#include "timeline-clip.hpp"
#include "timeline-clip-track.hpp"
#include "timeline-view-window.hpp"
#include "gui/widgets/timeline/timeline-clip.hpp"
#include "gui/widgets/timeline/timeline-clip-track.hpp"
#include "gui/widgets/timeline/timeline-view-window.hpp"
using namespace Gtk;
using boost::dynamic_pointer_cast;
using boost::shared_ptr;
using std::tr1::shared_ptr;
using util::contains;
namespace gui {
@ -76,7 +76,7 @@ namespace timeline {
}
}
boost::shared_ptr<timeline::Clip>
shared_ptr<timeline::Clip>
ClipTrack::getClipAt(Time position) const
{
std::pair<shared_ptr<model::Clip>, shared_ptr<timeline::Clip> >
@ -88,7 +88,7 @@ namespace timeline {
}
// Nothing found
return boost::shared_ptr<timeline::Clip>();
return shared_ptr<timeline::Clip>();
}
//// private methods
@ -97,8 +97,8 @@ namespace timeline {
ClipTrack::createTimelineClips()
{
// Share the draw strategy between all objects
// TODO: use factory/builder here
static boost::shared_ptr<timeline::DrawStrategy> drawStrategy(new BasicDrawStrategy());
TODO("Use factory/builder to create Timline Clips");
static shared_ptr<timeline::DrawStrategy> drawStrategy(new BasicDrawStrategy());
BOOST_FOREACH (shared_ptr<model::Clip> modelClip, getModelTrack()->getClipList())
{
// Is a timeline UI clip present in the map already?
@ -115,7 +115,7 @@ namespace timeline {
shared_ptr<model::ClipTrack>
ClipTrack::getModelTrack ()
{
return dynamic_pointer_cast<model::ClipTrack>(modelTrack);
return std::tr1::dynamic_pointer_cast<model::ClipTrack>(modelTrack);
}
void

View file

@ -52,7 +52,7 @@ namespace timeline {
* Constructor.
*/
ClipTrack(TimelineWidget &timelineWidget,
boost::shared_ptr<model::ClipTrack> track);
shared_ptr<model::ClipTrack> track);
/**
* Draw the track in the timeline.
@ -66,7 +66,7 @@ namespace timeline {
* pointer.
* @param the given time
*/
boost::shared_ptr<timeline::Clip>
shared_ptr<timeline::Clip>
getClipAt(Time position) const;
private:
@ -80,7 +80,7 @@ namespace timeline {
/**
* Gets the modelTrack as a ClipTrack.
*/
boost::shared_ptr<model::ClipTrack>
shared_ptr<model::ClipTrack>
getModelTrack ();
/**
@ -107,8 +107,8 @@ namespace timeline {
* The clipMap maps model clips to timeline widget clips which are responsible for the
* UI representation of a clip.
*/
std::map<boost::shared_ptr<model::Clip>,
boost::shared_ptr<timeline::Clip> >
std::map<shared_ptr<model::Clip>,
shared_ptr<timeline::Clip> >
clipMap;
};

View file

@ -27,11 +27,11 @@ namespace gui {
namespace widgets {
namespace timeline {
using boost::shared_ptr; //////////////////////TICKET #796
using std::tr1::shared_ptr;
Clip::Clip (boost::shared_ptr<model::Clip> clip,
boost::shared_ptr<timeline::DrawStrategy> drawStrategy)
Clip::Clip (shared_ptr<model::Clip> clip,
shared_ptr<timeline::DrawStrategy> drawStrategy)
: Entity(drawStrategy)
, modelClip(clip)
, selected(false)

View file

@ -49,8 +49,8 @@ namespace timeline {
class Clip : public Entity
{
public:
Clip (boost::shared_ptr<model::Clip> clip,
boost::shared_ptr<timeline::DrawStrategy> drawStrategy);
Clip (shared_ptr<model::Clip> clip,
shared_ptr<timeline::DrawStrategy> drawStrategy);
Time getBegin() const;
@ -62,7 +62,7 @@ namespace timeline {
private:
boost::shared_ptr<model::Clip> modelClip; ////////////////////////TICKET #796 : should use std::tr1
shared_ptr<model::Clip> modelClip;
/**
* True when this clip is selected in the GUI.

View file

@ -29,7 +29,7 @@ namespace gui {
namespace widgets {
namespace timeline {
Entity::Entity(boost::shared_ptr<timeline::DrawStrategy> drawStrategy)
Entity::Entity (shared_ptr<timeline::DrawStrategy> drawStrategy)
: enabled(true),
drawStrategy(drawStrategy)
{ }

View file

@ -31,14 +31,16 @@
#include "lib/time/timevalue.hpp"
#include <string>
#include <tr1/memory>
#include <cairomm/cairomm.h>
#include <boost/shared_ptr.hpp> //////////////////////TICKET #796
namespace gui {
namespace widgets {
namespace timeline {
using lib::time::Time;
using std::tr1::shared_ptr;
class DrawStrategy;
@ -51,7 +53,7 @@ namespace timeline {
class Entity {
protected:
Entity(boost::shared_ptr<timeline::DrawStrategy> drawStrategy);
Entity (shared_ptr<timeline::DrawStrategy> drawStrategy);
virtual ~Entity();
@ -81,7 +83,7 @@ namespace timeline {
bool enabled;
boost::shared_ptr<timeline::DrawStrategy> drawStrategy;
shared_ptr<timeline::DrawStrategy> drawStrategy;
};
} // namespace timeline

View file

@ -24,7 +24,7 @@
#include "gui/widgets/timeline-widget.hpp"
using namespace Gtk;
using namespace boost;
using namespace std::tr1;
using namespace sigc;
namespace gui {

View file

@ -26,7 +26,7 @@
#ifndef TIMELINE_GROUP_TRACK_HPP
#define TIMELINE_GROUP_TRACK_HPP
#include "timeline-track.hpp"
#include "gui/widgets/timeline/timeline-track.hpp"
#include "gui/model/group-track.hpp"
namespace gui {
@ -36,19 +36,16 @@ namespace timeline {
class GroupTrack : public timeline::Track
{
public:
GroupTrack(TimelineWidget &timeline_widget,
boost::shared_ptr<model::GroupTrack> track);
GroupTrack (TimelineWidget &timeline_widget,
shared_ptr<model::GroupTrack> track);
void draw_track(Cairo::RefPtr<Cairo::Context> cairo,
TimelineViewWindow* constwindow)
const;
void draw_track (Cairo::RefPtr<Cairo::Context> cairo,
TimelineViewWindow* constwindow) const;
protected:
void on_child_list_changed();
};
} // namespace timeline
} // namespace widgets
} // namespace gui
}}} // namespace gui::widgets::timeline
#endif // TIMELINE_GROUP_TRACK_HPP

View file

@ -30,7 +30,7 @@
using namespace Gtk;
using namespace std;
using namespace boost;
using namespace std::tr1;
using namespace util;
namespace gui {
@ -342,12 +342,9 @@ TimelineHeaderContainer::on_scroll()
}
void
TimelineHeaderContainer::on_hovering_track_changed(
boost::shared_ptr<timeline::Track> hovering_track)
TimelineHeaderContainer::on_hovering_track_changed( shared_ptr<timeline::Track>)
{
(void)hovering_track;
/* do nothing */
}
bool
@ -392,7 +389,7 @@ TimelineHeaderContainer::layout_headers()
Widget &widget = timeline_track->get_header_widget();
optional<Gdk::Rectangle> header_rect =
boost::optional<Gdk::Rectangle> header_rect =
layout_helper.get_track_header_rect(timeline_track);
if(header_rect)

View file

@ -139,8 +139,7 @@ private:
*/
void on_scroll();
void on_hovering_track_changed(
boost::shared_ptr<timeline::Track> hovering_track);
void on_hovering_track_changed(shared_ptr<timeline::Track> hovering_track);
private:
/* ===== Internal Event Handlers ===== */
@ -168,9 +167,8 @@ private:
* to control the amount of indention.
* @param offset The vertical offset of the headers in pixels.
*/
void draw_header_decoration(
boost::shared_ptr<model::Track> modelTrack,
const Gdk::Rectangle &clip_rect);
void draw_header_decoration(shared_ptr<model::Track> modelTrack,
const Gdk::Rectangle &clip_rect);
/**
* A helper function which calls lookup_timeline_track within the
@ -182,8 +180,8 @@ private:
* @remarks If the return value is going to be NULL, an ENSURE will
* fail.
*/
boost::shared_ptr<timeline::Track> lookup_timeline_track(
boost::shared_ptr<model::Track> modelTrack);
shared_ptr<timeline::Track>
lookup_timeline_track (shared_ptr<model::Track> modelTrack);
void begin_drag();
@ -248,7 +246,7 @@ private:
int scrollSlideRate;
//----- User Interaction State -----//
boost::shared_ptr<timeline::Track> hoveringTrack;
shared_ptr<timeline::Track> hoveringTrack;
Gdk::Point mousePoint;

View file

@ -28,9 +28,10 @@
using namespace Gtk;
using namespace std;
using namespace boost;
using namespace util;
using std::tr1::shared_ptr;
namespace gui {
namespace widgets {
namespace timeline {

View file

@ -24,12 +24,10 @@
#include "gui/widgets/timeline-widget.hpp"
#include "lib/time/mutation.hpp"
#include <boost/shared_ptr.hpp>
using namespace gui::widgets;
using lib::time::Mutation;
using boost::shared_ptr; /////////////////////////TICKET #796
using std::tr1::shared_ptr;
namespace gui {
namespace widgets {
@ -45,15 +43,18 @@ const int IBeamTool::ScrollSlideEventInterval = 40;
IBeamTool::IBeamTool(TimelineBody &timeline_body) :
Tool(timeline_body),
selectionControl(),
dragType(None),
pinnedDragTime(),
scrollSlideRate(0)
{
// Connect the timlinebody selection to the selectionControl
get_state()->set_selection_control (selectionControl);
}
IBeamTool::~IBeamTool()
{
selectionControl.disconnect();
end_scroll_slide();
}
@ -120,9 +121,7 @@ IBeamTool::on_button_press_event(GdkEventButton* event)
// User began the drag in clear space, begin a Select drag
dragType = Selection;
pinnedDragTime = time;
state->setSelection (Mutation::changeTime(time));
state->setSelection (Mutation::changeDuration(Duration::NIL));
//////////////////////////////////////////////////////TICKET #797 : this is cheesy. Should provide a single Mutation to change all
selectionControl (TimeSpan(time, Duration::NIL));
}
}
}
@ -181,8 +180,8 @@ IBeamTool::on_motion_notify_event(GdkEventMotion *event)
bool
IBeamTool::on_scroll_slide_timer()
{
const Gdk::Rectangle body_rect(get_body_rectangle());
view_window().shift_view(body_rect.get_width(), scrollSlideRate);
const Gdk::Rectangle body_rect (get_body_rectangle());
view_window().shift_view (body_rect.get_width(), scrollSlideRate);
// Return true to keep the timer going
return true;
@ -193,16 +192,17 @@ IBeamTool::set_leading_x(const int x)
{
shared_ptr<TimelineState> state = get_state();
const bool set_playback_period = dragType == Selection;
// The line below needs handled differently now;
//
//const bool set_playback_period = dragType == Selection;
TimeVar newStartPoint (state->get_view_window().x_to_time(x));
Offset selectionLength (pinnedDragTime, newStartPoint);
if (newStartPoint > pinnedDragTime)
newStartPoint=pinnedDragTime; // use the smaller one as selection start
state->setSelection (Mutation::changeTime(newStartPoint) , set_playback_period);
state->setSelection (Mutation::changeDuration(selectionLength), set_playback_period);
//////////////////////////////////////////////////////TICKET #797 : this is cheesy. Should provide a single Mutation to change all at once
selectionControl (TimeSpan (newStartPoint, Duration(selectionLength)));
}
void

View file

@ -30,14 +30,17 @@
#include "gui/widgets/timeline/timeline-tool.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/time/timequant.hpp"
#include "lib/time/control.hpp"
#include <gtkmm.h>
namespace gui {
namespace widgets {
namespace timeline {
using lib::time::TimeVar;
using lib::time::TimeSpan;
using lib::time::Control;
/**
* A helper class to implement the timeline i-beam tool
@ -135,12 +138,12 @@ private:
enum DragType
{
/**
* No drag is occuring
* No drag is occurring
*/
None,
/**
* A selection drag is occuring.
* A selection drag is occurring.
* @remarks The position of one end of the selection was set at
* mouse-down of the drag, and the other end is set by
* drag-release.
@ -159,6 +162,8 @@ private:
};
/* ==== Internals ===== */
Control<TimeSpan> selectionControl;
/**
* Specifies the type of drag currently taking place.
*/

View file

@ -30,7 +30,7 @@
using namespace Gtk;
using namespace std;
using namespace boost;
using namespace std::tr1;
using namespace lumiera;
using namespace util;
using namespace gui::util;
@ -80,17 +80,17 @@ TimelineLayoutHelper::add_branch(
}
}
optional<Gdk::Rectangle>
boost::optional<Gdk::Rectangle>
TimelineLayoutHelper::get_track_header_rect(
boost::weak_ptr<timeline::Track> track)
std::tr1::weak_ptr<timeline::Track> track)
{
if(contains(headerBoxes, track))
{
Gdk::Rectangle rect(headerBoxes[track]);
rect.set_y(rect.get_y() - timelineWidget.get_y_scroll_offset());
return optional<Gdk::Rectangle>(rect);
return boost::optional<Gdk::Rectangle>(rect);
}
return optional<Gdk::Rectangle>();
return boost::optional<Gdk::Rectangle>();
}
shared_ptr<timeline::Track>
@ -112,8 +112,8 @@ TimelineLayoutHelper::header_from_point(Gdk::Point point)
return shared_ptr<timeline::Track>();
}
boost::shared_ptr<timeline::Track>
TimelineLayoutHelper::track_from_y(int y)
shared_ptr<timeline::Track>
TimelineLayoutHelper::track_from_y (int y)
{
// Apply the scroll offset
y += timelineWidget.get_y_scroll_offset();
@ -299,8 +299,7 @@ TimelineLayoutHelper::is_animating() const
}
TimelineLayoutHelper::TrackTree::pre_order_iterator
TimelineLayoutHelper::iterator_from_track(
boost::shared_ptr<model::Track> modelTrack)
TimelineLayoutHelper::iterator_from_track(shared_ptr<model::Track> modelTrack)
{
REQUIRE(modelTrack);
@ -666,7 +665,7 @@ TimelineLayoutHelper::apply_drop_to_modelTree(
timelineWidget.thaw_update_tracks();
}
boost::shared_ptr<model::Sequence>
shared_ptr<model::Sequence>
TimelineLayoutHelper::get_sequence() const
{
REQUIRE(timelineWidget.state);

View file

@ -59,7 +59,7 @@ public:
/**
* Definition of the layout track tree type.
*/
typedef lumiera::tree< boost::shared_ptr<model::Track> > TrackTree;
typedef lumiera::tree< std::tr1::shared_ptr<model::Track> > TrackTree;
public:
/**
@ -101,7 +101,7 @@ public:
* @see update_layout()
*/
boost::optional<Gdk::Rectangle> get_track_header_rect(
boost::weak_ptr<timeline::Track> track);
std::tr1::weak_ptr<timeline::Track> track);
/**
* Searches for a header which has the specified point inside of it.
@ -114,8 +114,7 @@ public:
* tracks.
* @see update_layout()
*/
boost::shared_ptr<timeline::Track> header_from_point(
Gdk::Point point);
shared_ptr<timeline::Track> header_from_point (Gdk::Point point);
/**
* Searches for a tack which has the specified y-offset inside of it.
@ -128,15 +127,15 @@ public:
* tracks.
* @see update_layout()
*/
boost::shared_ptr<timeline::Track> track_from_y(int y);
shared_ptr<timeline::Track> track_from_y (int y);
/**
* Begins to drag the track under mouse_point, if there is one.
* @param mouse_point The mouse point to begin dragging from, measured
* in pixels from the top left of the header container widget.
*/
boost::shared_ptr<timeline::Track>
begin_dragging_track(const Gdk::Point &mouse_point);
shared_ptr<timeline::Track>
begin_dragging_track (Gdk::Point const& mouse_point);
/**
* Drops the dragging track.
@ -185,8 +184,8 @@ public:
* @return Returns the model iterator of layoutTree.end() if no
* iterator was found.
*/
TrackTree::pre_order_iterator iterator_from_track(
boost::shared_ptr<model::Track> modelTrack);
TrackTree::pre_order_iterator
iterator_from_track (shared_ptr<model::Track> modelTrack);
/**
* A function that recursively calculates the visible height of a
@ -262,7 +261,7 @@ protected:
* @see clone_tree_from_sequence()
*/
void add_branch(TrackTree::iterator_base parent_iterator,
boost::shared_ptr<model::Track> parent);
shared_ptr<model::Track> parent);
/**
* Recursively calculates the boxes for a given branch in the timeline
@ -298,8 +297,8 @@ protected:
* @remarks If the return value is going to be NULL, an ENSURE will
* fail.
*/
boost::shared_ptr<timeline::Track> lookup_timeline_track(
boost::shared_ptr<model::Track> modelTrack);
shared_ptr<timeline::Track>
lookup_timeline_track(shared_ptr<model::Track> modelTrack);
/**
* A helper function which kicks off the animation timer.
@ -343,7 +342,8 @@ protected:
* Helper to get the sequence object from the state.
* @return Returns a shared pointer to the sequence.
*/
boost::shared_ptr<model::Sequence> get_sequence() const;
shared_ptr<model::Sequence>
get_sequence() const;
protected:
/**
@ -363,7 +363,7 @@ protected:
* the update_layout method.
* @see update_layout()
*/
std::map<boost::weak_ptr<timeline::Track>, Gdk::Rectangle>
std::map<std::tr1::weak_ptr<timeline::Track>, Gdk::Rectangle>
headerBoxes;
/**

View file

@ -36,7 +36,7 @@ using namespace gui;
using namespace gui::widgets;
using namespace gui::widgets::timeline;
using boost::shared_ptr; ////////////////////TICKET #796
using std::tr1::shared_ptr;
using gui::util::CairoUtil;
using lib::time::Time;
using lib::time::TimeVar;
@ -274,6 +274,7 @@ TimelineRuler::draw_ruler(Cairo::RefPtr<Cairo::Context> cr,
REQUIRE(ruler_rect.get_width() > 0);
REQUIRE(ruler_rect.get_height() > 0);
/////////////////////////////TICKET #861 : what part of these calculation could be centralised within TimelineViewWindow?
const TimelineViewWindow &window = viewWindow();
const gavl_time_t left_offset = _raw(window.get_time_offset());
const int64_t time_scale = window.get_time_scale();
@ -504,6 +505,7 @@ TimelineRuler::calculate_major_spacing() const
{
unsigned int i;
/////////////////////////////TICKET #861 : couldn't that be a library function in TimelineViewWindow?
const gavl_time_t major_spacings[] = {
GAVL_TIME_SCALE / 1000,
GAVL_TIME_SCALE / 400,

View file

@ -111,7 +111,7 @@ private:
/**
* The event handler for when the TimelineWidget's state is switched.
*/
void on_state_changed (boost::shared_ptr<TimelineState> newState); ////////////////////TICKET #796 : should use std::tr1::shared_ptr
void on_state_changed (shared_ptr<TimelineState> newState);
private:
/* ===== Internal Methods ===== */
@ -242,7 +242,7 @@ private:
/**
* the currently active timeline state object
*/
boost::shared_ptr<TimelineState> timelineState; ////////////////////TICKET #796 : should use std::tr1::shared_ptr
shared_ptr<TimelineState> timelineState;
/**
* The caches image of the ruler, over which the chevrons etc. will

View file

@ -24,6 +24,7 @@
#include "gui/widgets/timeline/timeline-state.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/time/mutation.hpp"
#include "lib/time/control.hpp"
using namespace Gtk;
using namespace sigc;
@ -36,31 +37,37 @@ using lib::time::FSecs;
using lib::time::Offset;
using lib::time::Duration;
using lib::time::Mutation;
using boost::shared_ptr; /////////////////////////TICKET #796
using lib::time::Control;
using std::tr1::shared_ptr;
TimelineState::TimelineState (boost::shared_ptr<model::Sequence> source_sequence)
TimelineState::TimelineState (shared_ptr<model::Sequence> source_sequence)
: sequence(source_sequence)
, viewWindow(Offset(Time::ZERO), 1)
, selection_(Time::ZERO, Duration::NIL)
, selectionListener()
, playbackPeriod_(Time::ZERO, Duration::NIL)
, playbackPoint_(Time::ZERO)
, isPlayback_(false)
{
REQUIRE(sequence);
// Initialize the selection listener
selectionListener (TimeSpan (Time::ZERO, Duration::NIL));
selectionListener.connect(
mem_fun(*this, &TimelineState::on_selection_changed));
////////////////////////////////////////////////////////////TICKET #798: how to handle GUI default state
const int64_t DEFAULT_TIMELINE_SCALE =21000000;
const int64_t DEFAULT_TIMELINE_SCALE =6400;
viewWindow.set_time_scale(DEFAULT_TIMELINE_SCALE);
setSelection (Mutation::changeTime (Time(FSecs(2))));
setSelection (Mutation::changeDuration(Duration(FSecs(2))));
setSelection (Mutation::changeDuration (Duration(FSecs(2))));
//////////////////////////////////////////////////////TICKET #797 : this is cheesy. Should provide a single Mutation to change all
}
boost::shared_ptr<model::Sequence>
shared_ptr<model::Sequence>
TimelineState::get_sequence() const
{
return sequence;
@ -97,6 +104,14 @@ TimelineState::setPlaybackPoint (Time newPosition)
playbackChangedSignal.emit();
}
void
TimelineState::set_selection_control (SelectionControl &control)
{
control.disconnect();
selection_.accept (control);
control.connectChangeNotification (selectionListener);
}
sigc::signal<void>
TimelineState::selection_changed_signal() const
{
@ -109,6 +124,12 @@ TimelineState::playback_changed_signal() const
return playbackChangedSignal;
}
void
TimelineState::on_selection_changed (TimeSpan selection)
{
selectionChangedSignal.emit();
}
} // namespace timeline
} // namespace widgets
} // namespace gui

View file

@ -28,8 +28,8 @@
#include "gui/widgets/timeline/timeline-view-window.hpp"
#include "lib/time/mutation.hpp"
#include "lib/time/control.hpp"
#include <boost/shared_ptr.hpp> /////////////////////////TICKET #796
namespace gui {
@ -39,9 +39,44 @@ class Sequence;
namespace widgets {
namespace timeline {
using lib::time::Control;
using lib::time::Mutation;
typedef Control<TimeSpan> SelectionControl;
/**
* SelectionListener is a template class which emits a signal when
* the value is changed by it's associated time::Control object.
* SelectionListener wraps a sigc::signal that emits every time
* the selection is changed by the time::Control object.
* SelectionListener does NOT emit the signal if a change to the
* selection is made outside of the Control/Listener partnership.
*/
template<class TI>
class SelectionListener
: boost::noncopyable
{
sigc::signal<void, TI> valueChangedSignal;
public:
SelectionListener() { }
void
operator() (TI const& changeValue) const
{
valueChangedSignal.emit (changeValue);
}
void connect (const sigc::slot<void, TI> &connection)
{
valueChangedSignal.connect (connection);
}
};
/**
* TimelineState is a container for the state data for TimelineWidget.
* @remarks TimelineState s can be swapped out so that TimelineWidget
@ -52,11 +87,10 @@ class TimelineState
public:
/**
* Constructor
* @param source_sequence The sequence on which the TimelineWidget
* will operate when this TimelineState is attached.
*/
TimelineState(boost::shared_ptr<model::Sequence> source_sequence);
TimelineState (shared_ptr<model::Sequence> source_sequence);
public:
@ -64,7 +98,7 @@ public:
* Gets the sequence that is attached to this timeline state object.
* @return Returns a shared_ptr to the sequence object.
*/
boost::shared_ptr<model::Sequence> get_sequence() const;
shared_ptr<model::Sequence> get_sequence() const;
/**
* Gets a reference to the timeline view window object.
@ -72,7 +106,9 @@ public:
*/
timeline::TimelineViewWindow& get_view_window();
SelectionListener<TimeSpan>&
get_selection_listener() { return selectionListener; }
Time getSelectionStart() const { return selection_.start();}
Time getSelectionEnd() const { return selection_.end(); }
Time getPlaybackPeriodStart() const { return selection_.start();}
@ -80,10 +116,12 @@ public:
Time getPlaybackPoint() const { return playbackPoint_; }
/** is there currently any ongoing playback process?
* Otherwise the #getPlaybackPoint is meaningless */
bool isPlaying() const { return isPlayback_; }
void set_selection_control (SelectionControl &control);
/**
* Sets the period of the selection.
@ -114,6 +152,15 @@ public:
* changed.
*/
sigc::signal<void> playback_changed_signal() const;
private:
/* ========= Event Handlers ========== */
/**
* Event handler for when the selection is changed
*/
void on_selection_changed (TimeSpan selection);
private:
@ -122,19 +169,23 @@ private:
* @remarks This pointer is set by the constructor and is constant, so
* will not change in value during the lifetime of the class.
*/
boost::shared_ptr<model::Sequence> sequence;
shared_ptr<model::Sequence> sequence;
// View State
/**
* The ViewWindow for the TimelineWidget display with.
*/
/* == View State == */
/** ViewWindow for the TimelineWidget display */
timeline::TimelineViewWindow viewWindow;
// Selection State
/* == Selection State == */
/** currently selected time period. */
TimeSpan selection_;
/** listens for a selection change */
SelectionListener<TimeSpan> selectionListener;
/** current playback period. */
TimeSpan playbackPeriod_;
@ -146,7 +197,8 @@ private:
bool isPlayback_;
// Signals
/* == Signals == */
/**
* A signal to notify when the selected period has changed.
@ -160,8 +212,7 @@ private:
sigc::signal<void> playbackChangedSignal;
};
} // namespace timeline
} // namespace widgets
} // namespace gui
}}} // namespace gui::widgets::timeline
#endif // TIMELINE_STATE_HPP

View file

@ -24,7 +24,7 @@
#include "gui/widgets/timeline-widget.hpp"
using namespace Gdk;
using namespace boost;
using std::tr1::shared_ptr;
namespace gui {
namespace widgets {

View file

@ -133,7 +133,7 @@ protected:
/**
* A helper function to get the state
*/
boost::shared_ptr<TimelineState> get_state() const;
shared_ptr<TimelineState> get_state() const;
/**
* A helper function to get the view window

View file

@ -29,7 +29,7 @@
using namespace Gtk;
using namespace sigc;
using boost::shared_ptr; //////////////////////////////TICKET #796 : should use std::tr1
using std::tr1::shared_ptr;
namespace gui {
namespace widgets {
@ -125,7 +125,7 @@ shared_ptr<timeline::Clip>
Track::getClipAt(Time) const
{
// Default implementation returns empty pointer
return boost::shared_ptr<timeline::Clip>();
return shared_ptr<timeline::Clip>();
}
void
@ -272,7 +272,7 @@ void
Track::on_remove_track()
{
REQUIRE(modelTrack);
boost::shared_ptr<TimelineState> state = timelineWidget.get_state();
shared_ptr<TimelineState> state = timelineWidget.get_state();
REQUIRE(state);
state->get_sequence()->remove_descendant_track(modelTrack);

View file

@ -72,20 +72,14 @@ public:
Collapse
};
/**
* Constructor
*/
Track(TimelineWidget &timeline_widget,
boost::shared_ptr<model::Track> track);
/**
* Destructor
*/
~Track();
shared_ptr<model::Track> track);
virtual ~Track();
Gtk::Widget& get_header_widget();
boost::shared_ptr<model::Track> //////////////////////////////TICKET #796 : should use std::tr1
shared_ptr<model::Track>
getModelTrack() const;
/**
@ -155,7 +149,7 @@ public:
* The default implementation simply returns an empty pointer.
* @param the given time
*/
virtual boost::shared_ptr<timeline::Clip> //////////////////////////////TICKET #796 : should use std::tr1
virtual shared_ptr<timeline::Clip>
getClipAt (Time position) const;
private:
@ -209,7 +203,7 @@ private:
protected:
TimelineWidget &timelineWidget;
boost::shared_ptr<model::Track> modelTrack;
shared_ptr<model::Track> modelTrack;
private:
/**

View file

@ -31,12 +31,19 @@ namespace gui {
namespace widgets {
namespace timeline {
/* == public constants == */
const int64_t TimelineViewWindow::MaxScale = 30000000; // 30 Million
const double TimelineViewWindow::ZoomIncrement = 1.25; // Not currently used
const double TimelineViewWindow::ZoomSmoothing = 9.0;
TimelineViewWindow::TimelineViewWindow (Offset offset, int64_t scale)
: timeOffset(offset)
, timeScale(scale)
{
TODO("Create a function to limit timescale between 1 and MaxScale");
TODO("TICKET #795 Some functions need to be private");
}
Offset
@ -68,7 +75,7 @@ TimelineViewWindow::set_time_scale(int64_t scale)
void
TimelineViewWindow::set_time_scale(double ratio)
{
int64_t max = TimelineWidget::MaxScale;
int64_t max = MaxScale;
int64_t min = 1;
if(ratio <= 0.0)
@ -85,24 +92,32 @@ TimelineViewWindow::set_time_scale(double ratio)
set_time_scale((int64_t)(ratio * max));
}
double
TimelineViewWindow::get_smoothed_time_scale() const
{
double linear_scale ( 1.0 / MaxScale * timeScale);
// reverse the effect of zoom scale smoothing
return pow (linear_scale, (1.0 / ZoomSmoothing));
}
void
TimelineViewWindow::zoom_view(int point, double time_scale_ratio)
{
TODO("Find a Central place for a Zoom Smoothing Factor Variable. Right now it is hard coded at 9.0");
int64_t new_time_scale =
(int64_t)( pow(time_scale_ratio, 9.0) * (double)TimelineWidget::MaxScale);
// Apply the smoothing factor
int64_t new_time_scale(pow (time_scale_ratio, ZoomSmoothing)
* double(MaxScale));
/* Prevent Zooming in To Close and Far */
if(new_time_scale < 1)
new_time_scale = 1;
if(new_time_scale > TimelineWidget::MaxScale)
new_time_scale = TimelineWidget::MaxScale;
if(new_time_scale > MaxScale)
new_time_scale = MaxScale;
// The view must be shifted so that the zoom is centred on the cursor
TimeVar newStartPoint = get_time_offset();
newStartPoint += TimeValue(point * (timeScale - new_time_scale));
set_time_offset(newStartPoint);
TimeVar newStartPoint = timeOffset + TimeValue(point * (timeScale - new_time_scale));
set_time_offset (newStartPoint);
// Apply the new scale
set_time_scale(new_time_scale);
@ -111,13 +126,13 @@ TimelineViewWindow::zoom_view(int point, double time_scale_ratio)
void
TimelineViewWindow::shift_view(int view_width, int shift_size)
{
set_time_offset(timeOffset + TimeValue(timeScale * shift_size * view_width / 256));
set_time_offset (timeOffset + TimeValue(timeScale * shift_size * view_width / 256));
}
int
TimelineViewWindow::time_to_x(TimeValue const& time) const
{
return int(_raw(time - timeOffset) / timeScale); //////TODO protect against values out-of range
return int (_raw(time - timeOffset) / timeScale); //////TODO protect against values out-of range
}
Time
@ -133,7 +148,5 @@ TimelineViewWindow::changed_signal() const
return changedSignal;
}
} // namespace timeline
} // namespace widgets
} // namespace gui
}}} // namespace gui::widgets::timeline

View file

@ -54,6 +54,16 @@ using lib::time::Offset;
class TimelineViewWindow
{
public:
/**
* The maximum scale for timeline display.
* @remarks At MaxScale, every pixel on the timeline is equivalent
* to 30000000 lumiera::Time increments.
*/
static const int64_t MaxScale;
static const double ZoomSmoothing;
static const double ZoomIncrement;
/**
* @param offset The initial view offset.
@ -95,6 +105,10 @@ public:
*/
void set_time_scale(int64_t time_scale);
void set_time_scale(double ratio);
/** get the current time scale with zoom smoothing applied */
double get_smoothed_time_scale() const;
/**
* Zooms the view in or out as by a number of steps while keeping a
* given point on the timeline still.
@ -138,6 +152,13 @@ public:
private:
TimeVar timeOffset;
/**
* The scale of the Timline Body.
* @remarks This value represents the time span that is visible in
* the TimelineBodyWidget. Smaller numbers here will "zoom in"
* while larger numbers will "zoom out"
*/
int64_t timeScale;
sigc::signal<void> changedSignal;

View file

@ -20,8 +20,8 @@
* *****************************************************/
#include "gui/widgets/timeline/timeline-zoom-scale.hpp"
#include "gui/widgets/timeline-widget.hpp"
#include "gui/widgets/timeline/timeline-zoom-scale.hpp"
using namespace Gtk;
@ -65,56 +65,47 @@ TimelineZoomScale::TimelineZoomScale()
, button_step_size(0.03)
{
/* Setup the Slider Control */
slider.set_adjustment(adjustment);
slider.set_size_request(123,10);
slider.set_digits(6);
slider.set_inverted(true);
slider.set_draw_value(false);
slider.set_adjustment (adjustment);
slider.set_size_request (123,10);
slider.set_digits (6);
/* Inverted because smaller values "zoom in" */
slider.set_inverted (true);
slider.set_draw_value (false);
/* Make our connections */
zoomIn.signal_clicked().
connect(sigc::mem_fun(this, &TimelineZoomScale::on_zoom_in_clicked));
connect (sigc::mem_fun(this, &TimelineZoomScale::on_zoom_in_clicked));
zoomOut.signal_clicked().
connect(sigc::mem_fun(this, &TimelineZoomScale::on_zoom_out_clicked));
connect (sigc::mem_fun(this, &TimelineZoomScale::on_zoom_out_clicked));
adjustment.signal_value_changed().
connect(sigc::mem_fun(this, &TimelineZoomScale::on_zoom));
connect (sigc::mem_fun(this, &TimelineZoomScale::on_zoom));
/* Add Our Widgets and show them */
pack_start(zoomOut,PACK_SHRINK);
pack_start(slider,PACK_SHRINK);
pack_start(zoomIn,PACK_SHRINK);
pack_start (zoomOut,PACK_SHRINK);
pack_start (slider,PACK_SHRINK);
pack_start (zoomIn,PACK_SHRINK);
show_all();
}
void
TimelineZoomScale::wireTimelineState (boost::shared_ptr<TimelineState> currentState,
TimelineZoomScale::wireTimelineState (shared_ptr<TimelineState> currentState,
TimelineWidget::TimelineStateChangeSignal stateChangeSignal)
{
on_timeline_state_changed (currentState);
stateChangeSignal.connect (sigc::mem_fun(this, &TimelineZoomScale::on_timeline_state_changed));
stateChangeSignal.connect (
sigc::mem_fun(this, &TimelineZoomScale::on_timeline_state_changed));
}
void
TimelineZoomScale::on_timeline_state_changed (boost::shared_ptr<TimelineState> newState)
TimelineZoomScale::on_timeline_state_changed (shared_ptr<TimelineState> newState)
{
REQUIRE (newState);
timelineState = newState;
int64_t current_scale =
getViewWindow().get_time_scale();
double linear_scale =
(double) current_scale / (double) TimelineWidget::MaxScale;
/* We have to Revese the Smoothing */
TODO("Find a central place for ZoomSmoothingFactor Variable. right now it is 9.0");
double new_relative_scale =
pow(linear_scale,(1.0/9.0));
adjustment.set_value(new_relative_scale);
adjustment.set_value (getViewWindow().get_smoothed_time_scale());
}
void
@ -134,7 +125,7 @@ TimelineZoomScale::on_zoom_out_clicked()
void
TimelineZoomScale::on_zoom()
{
zoomSignal.emit(adjustment.get_value()) ;
zoomSignal.emit (adjustment.get_value()) ;
}
sigc::signal<void, double>

View file

@ -29,10 +29,6 @@
#include "gui/gtk-lumiera.hpp"
#include "gui/widgets/mini-button.hpp"
#include "gui/widgets/timeline-widget.hpp"
#include "gui/widgets/timeline/timeline-state.hpp"
#include "gui/widgets/timeline/timeline-view-window.hpp"
#include <boost/shared_ptr.hpp>
using namespace Gtk;
using namespace gui::widgets;
@ -55,7 +51,9 @@ public:
*/
sigc::signal<void, double> signal_zoom();
void wireTimelineState (boost::shared_ptr<TimelineState> currentState,
void set_value(double val) { adjustment.set_value(val); }
void wireTimelineState (shared_ptr<TimelineState> currentState,
TimelineWidget::TimelineStateChangeSignal);
private:
@ -65,7 +63,7 @@ private:
* Update the slider position when the timeline state
* is changed.
*/
void on_timeline_state_changed (boost::shared_ptr<TimelineState> newState); ////////////////////TICKET #796 : should use std::tr1::shared_ptr
void on_timeline_state_changed (shared_ptr<TimelineState> newState);
/**
* Event handler for when the zoomIn Button
@ -100,7 +98,7 @@ private:
const double button_step_size;
boost::shared_ptr<TimelineState> timelineState;
shared_ptr<TimelineState> timelineState;
};
} // namespace gui

View file

@ -33,13 +33,12 @@ using util::cStr;
using namespace Gtk;
using namespace Glib;
using namespace boost;
using namespace std;
using namespace std::tr1;
using namespace gui::workspace;
namespace fsys = boost::filesystem;
namespace gui {
IconSize WindowManager::GiantIconSize = ICON_SIZE_INVALID;

View file

@ -39,14 +39,14 @@
#include "gui/gtk-base.hpp"
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <cairomm/cairomm.h>
#include <string>
#include <tr1/memory>
namespace gui {
using std::string;
using std::tr1::shared_ptr;
namespace model { class Project; }
@ -195,7 +195,7 @@ private:
private:
std::list< boost::shared_ptr<workspace::WorkspaceWindow> > windowList;
std::list<shared_ptr<workspace::WorkspaceWindow> > windowList;
public:

View file

@ -90,6 +90,9 @@ NOBUG_CPP_DEFINE_FLAG_PARENT ( fileheader_dbg, backend_dbg);
/** base of debug logging for the proc layer */
NOBUG_CPP_DEFINE_FLAG_PARENT ( proc_dbg, debugging);
NOBUG_CPP_DEFINE_FLAG_PARENT ( command_dbg, proc_dbg);
NOBUG_CPP_DEFINE_FLAG_PARENT ( session_dbg, proc_dbg);
NOBUG_CPP_DEFINE_FLAG_PARENT ( player_dbg, proc_dbg);
NOBUG_CPP_DEFINE_FLAG_PARENT ( engine_dbg, proc_dbg);
/** base of debug logging for the gui */
NOBUG_CPP_DEFINE_FLAG_PARENT ( gui_dbg, debugging);
/** base if debug logging for the support library */

View file

@ -215,6 +215,27 @@ namespace lumiera {
} // namespace lumiera
/******************************************************
* convenience shortcut for a sequence of catch blocks
* just logging and consuming an error. Typically
* this sequence will be used within destructors,
* which, by convention, must not throw
*/
#define ERROR_LOG_AND_IGNORE(_FLAG_,_OP_DESCR_) \
catch (std::exception& problem) \
{ \
const char* errID = lumiera_error(); \
WARN (_FLAG_, "%s failed: %s", _OP_DESCR_, problem.what()); \
TRACE (debugging, "Error flag was: %s", errID);\
} \
catch (...) \
{ \
const char* errID = lumiera_error(); \
ERROR (_FLAG_, "%s failed with unknown exception; " \
"error flag is: %s" \
, _OP_DESCR_, errID); \
}
/******************************************************

View file

@ -22,13 +22,14 @@
/** @file iter-adapter.hpp
** Helper template(s) for creating <b>lumiera forward iterators</b>.
** These are the foundation to build up iterator like types from scratch.
** Usually, these templates will be created and provided by a custom
** container type and accessed by the client through a typedef name
** "iterator" (similar to the usage within the STL). For more advanced
** usage, the providing container might want to subclass these iterators,
** e.g. to provide an additional, specialised API.
**
** Depending on the concrete situation, there are several flavours
** Depending on the concrete situation, several flavours are provided:
** - the IterAdapter retains an active callback connection to the
** controlling container, thus allowing arbitrary complex behaviour.
** - the RangeIter allows just to expose a range of elements defined
@ -36,21 +37,32 @@
** - often, objects are managed internally by pointers, while allowing
** the clients to use direct references; to support this usage scenario,
** PtrDerefIter wraps an existing iterator, while dereferencing any value
** automatically on access.
** automatically on access.
**
** There are many further ways of yielding a Lumiera forward iterator.
** For example, lib::IterSource builds a "iterable" source of data elements,
** while hiding the actual container or generator implementation behind a
** vtable call. Besides, there are adapters for the most common usages
** with STL containers, and such iterators can also be combined and
** extended with the help of itertools.hpp
**
** Basically every class in compliance with our specific iterator concept
** can be used as a building block in this framework.
**
**
** \par Lumiera forward iterator concept
**
** Similar to the STL, instead of using a common "Iterator" base class,
** instead we define a common set of functions and behaviour which can
** we rather define a common set of functions and behaviour which can
** be expected from any such iterator. These rules are similar to STL's
** "forward iterator", with the addition of an bool check to detect
** iteration end. The latter s inspired by the \c hasNext() function
** iteration end. The latter is inspired by the \c hasNext() function
** found in many current languages supporting iterators. In a similar
** vein (inspired from functional programming), we deliberately don't
** support the various extended iterator concepts from STL and boost
** (random access iterators, output iterators and the like). According
** to this concept, <i>an iterator is a promise for pulling values,</i>
** (random access iterators, output iterators, arithmetics, difference
** between iterators and the like). According to this concept,
** <i>an iterator is a promise for pulling values,</i>
** and nothing beyond that.
**
** - Any Lumiera forward iterator can be in a "exhausted" (invalid) state,
@ -58,7 +70,7 @@
** created by the default ctor is always fixed to that state. This
** state is final and can't be reset, meaning that any iterator is
** a disposable one-way-off object.
** - iterators are copyable and comparable
** - iterators are copyable and equality comparable
** - when an iterator is \em not in the exhausted state, it may be
** \em dereferenced to yield the "current" value.
** - moreover, iterators may be incremented until exhaustion.
@ -408,7 +420,7 @@ namespace lib {
typedef typename RemovePtr<TY>::Type ValueType;
template<class T2>
struct SimilarIter
struct SimilarIter ///< rebind to a similarly structured Iterator with value type T2
{
typedef Iter<T2,CON> Type;
};
@ -445,7 +457,12 @@ namespace lib {
public:
typedef typename IT::value_type pointer;
typedef typename RemovePtr<pointer>::Type value_type;
typedef value_type& reference;
typedef value_type& reference;
// for use with STL algorithms
typedef void difference_type;
typedef std::forward_iterator_tag iterator_category;
// the purpose of the following typedefs is to ease building a correct "const iterator"
@ -462,6 +479,7 @@ namespace lib {
/** PtrDerefIter is always created
* by wrapping an existing iterator.
*/
explicit
PtrDerefIter (IT srcIter)
: i_(srcIter)
{ }
@ -485,9 +503,31 @@ namespace lib {
operator= (PtrDerefIter<WrappedIterType> const& ref)
{
i_ = reinterpret_cast<IT const&> (ref.getBase());
return *this;
}
/** explicit builder to allow creating a const variant from the basic srcIter type.
* Again, the reason necessitating this "backdoor" is that we want to swallow one level
* of indirection. Generally speaking \code const T ** \endcode is not the same as
* \code T * const * \endcode, but in our specific case the API ensures that a
* PtrDerefIter<WrappedConstIterType> only exposes const elements.
*/
static PtrDerefIter
build_by_cast (WrappedIterType const& srcIter)
{
return PtrDerefIter (reinterpret_cast<IT const&> (srcIter));
}
static PtrDerefIter
nil()
{
return PtrDerefIter (IT());
}
/* === lumiera forward iterator concept === */

122
src/lib/maybe.hpp Normal file
View file

@ -0,0 +1,122 @@
/*
MAYBE.hpp - dealing with optional values
Copyright (C) Lumiera.org
2011, 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 maybe.hpp
** Support for representation of optional values.
** This implements a concept ("option monad") known from functional programming,
** allowing to express the fact of some value possibly be unavailable. Using this
** approach allows to avoid the dangerous technique of (ab)using NULL pointers to
** represent missing values.
**
** While a NULL pointer carries this special meaning just by convention, marking a
** parameter or return value as optional states this fact first class, and enforces
** the necessary "is available" check through the type system. Surprisingly, this
** leads not only to more secure, but also much more compact code, as we're now
** able to substitute a fallback just by a "or else use this" clause.
** Basically, there are different ways to access the actual value
** - access through implicit conversion raises an exception for missing values
** - evaluation as boolean allows to check, if the value is available
** - an alternative or fallback value may be attached.
**
** @todo WIP and rather brainstorming as of 2/10
**
** @see backend::ThreadJob usage example
*/
#ifndef LIB_MAYBE_H
#define LIB_MAYBE_H
//#include "pre.hpp"
#include "lib/error.hpp"
//#include "lib/wrapper.hpp"
#include "lib/util.hpp"
#include <string>
namespace lib {
using util::isnil;
using std::string;
namespace error = lumiera::error;
namespace maybe {
}
/**
* A value, which might be unavailable
* @throw error::State on any attempt to access a missing value
* without prior checking the availability
*/
template<typename VAL>
class Maybe
{
VAL value_;
public:
/** mark an invalid/failed result */
Maybe ()
{ }
/** standard case: valid result */
Maybe (VAL const& value)
: value_(value)
{ }
bool
isValid() const
{
UNIMPLEMENTED ("check if optional value is available");
}
void
maybeThrow(Literal explanation =0) const
{
if (!isValid())
throw error::State (explanation.empty()? "optional value not available" : string(explanation),
error::LUMIERA_ERROR_BOTTOM_VALUE);
}
VAL
get() const
{
maybeThrow();
return value_;
}
};
} // namespace lib
#endif

View file

@ -48,8 +48,10 @@
#include "include/logging.h"
#include "lib/iter-adapter.hpp"
#include "lib/error.hpp"
#include "lib/util.hpp"
#include <vector>
#include <algorithm>
#include <boost/noncopyable.hpp>
@ -123,22 +125,43 @@ namespace lib {
} }
/** withdraw responsibility for a specific object.
* This object will be removed form this collection
* and returned as-is; it won't be deleted when the
* ScopedPtrVect goes out of scope.
* @param obj address of the object in question.
* @return pointer to the object, if found.
* Otherwise, NULL will be returned and the
* collection of managed objects remains unaltered
* @note EX_STRONG
* @todo TICKET #856 better return a Maybe<T&> instead of a pointer?
*/
T*
detach (void* objAddress)
{
T* extracted = static_cast<T*> (objAddress);
VIter pos = std::find (_Vec::begin(),_Vec::end(), extracted);
if (pos != _Vec::end() && bool(*pos))
{
extracted = *pos;
_Vec::erase(pos); // EX_STRONG
return extracted;
}
return NULL;
}
void
clear()
{
VIter e = _Vec::end();
for (VIter i = _Vec::begin(); i!=e; ++i)
{
if (*i)
try
{
delete *i;
*i = 0;
}
catch(std::exception& ex)
{
WARN (library, "Problem while deallocating ScopedPtrVect: %s", ex.what());
} }
if (*i)
try {
delete *i;
*i = 0;
}
ERROR_LOG_AND_IGNORE (library, "Clean-up of ScopedPtrVect");
_Vec::clear();
}
@ -155,9 +178,9 @@ namespace lib {
typedef ConstIterType const_iterator;
iterator begin() { return iterator (allPtrs()); }
const_iterator begin() const { return const_iterator (allPtrs()); }
iterator end() { return iterator ( RIter() ); }
const_iterator end() const { return const_iterator (RcIter() ); }
const_iterator begin() const { return const_iterator::build_by_cast (allPtrs()); }
const_iterator end() const { return const_iterator::nil(); }
@ -188,6 +211,12 @@ namespace lib {
{
return RIter (_Vec::begin(), _Vec::end());
}
RIter
allPtrs () const
{
_Vec& elements = util::unConst(*this);
return RIter (elements.begin(), elements.end());
}
};

View file

@ -22,6 +22,7 @@
#include "lib/test/test-helper.hpp"
#include "lib/test/testdummy.hpp"
#include <boost/format.hpp>
@ -55,7 +56,12 @@ namespace test{
garbage[--p] = alpha[rand() % MAXAL];
return garbage;
}
/** storage for testdummy flags */
long Dummy::_local_checksum = 0;
bool Dummy::_throw_in_ctor = false;
}} // namespace lib::test

View file

@ -0,0 +1,99 @@
/*
TESTDUMMY.hpp - yet another test dummy for tracking ctor/dtor calls
Copyright (C) Lumiera.org
2008, 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.
* *****************************************************/
#include <boost/noncopyable.hpp>
#include <algorithm>
namespace lib {
namespace test{
class Dummy
: boost::noncopyable
{
int val_;
/** to verify ctor/dtor calls */
static long _local_checksum;
static bool _throw_in_ctor;
public:
Dummy ()
: val_(1 + (rand() % 100000000))
{ init(); }
Dummy (int v)
: val_(v)
{ init(); }
~Dummy()
{
checksum() -= val_;
}
long add (int i) { return val_+i; }
int getVal() const { return val_; }
void
setVal (int newVal)
{
checksum() += newVal - val_;
val_ = newVal;
}
friend void
swap (Dummy& dum1, Dummy& dum2) ///< checksum neutral
{
std::swap(dum1.val_, dum2.val_);
}
static long&
checksum()
{
return _local_checksum;
}
static void
activateCtorFailure(bool indeed =true)
{
_throw_in_ctor = indeed;
}
private:
void
init()
{
checksum() += val_;
if (_throw_in_ctor)
throw val_;
}
};
}} // namespace lib::test

View file

@ -57,6 +57,7 @@
#include "lib/time/mutation.hpp"
#include "lib/time/timevalue.hpp"
#include <tr1/functional>
#include <vector>
@ -64,6 +65,8 @@ namespace lib {
namespace time {
namespace mutation {
using std::tr1::function;
@ -148,7 +151,7 @@ namespace mutation {
/** disconnect any observers */
void
disconnnect()
disconnect()
{
listeners_.clear();
}

View file

@ -151,7 +151,7 @@ namespace time {
/** disconnect from observed entity and
* cease any change notification */
void disconnnect();
void disconnect();
};
@ -209,7 +209,7 @@ namespace time {
template<class TI>
void
Control<TI>::disconnnect()
Control<TI>::disconnect()
{
notifyListeners_.disconnect();
this->unbind();

View file

@ -64,7 +64,7 @@ namespace wrapper {
/**
* Extension to boost::reference_wrapper:
* Extension to std::reference_wrapper:
* Allows additionally to re-bind to another reference,
* almost like a pointer. Helpful for building templates.
* @warning potentially dangerous
@ -128,7 +128,7 @@ namespace wrapper {
* A copyable, assignable value object, not managing ownership.
* It can be default constructed and \c bool evaluated to detect
* an empty holder. The value is retrieved pointer-like, by
* explicit dereferentiation. (Contrast this to boost::ref,
* explicit dereferentiation. (Contrast this to std::ref,
* where the original reference is retrieved by conversion)
*
* The purpose of this template is to be able to remember

View file

@ -0,0 +1,128 @@
/*
BUFFER-LOCAL-KEY.hpp - opaque data for BufferProvider implementation
Copyright (C) Lumiera.org
2011, 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.
*/
#ifndef PROC_ENGINE_BUFFR_LOCAL_KEY_H
#define PROC_ENGINE_BUFFR_LOCAL_KEY_H
#include "lib/error.hpp"
#include <boost/functional/hash.hpp>
namespace lib {
typedef size_t HashVal;
}
namespace engine {
namespace metadata {
class Key;
class Entry;
}
class BufferMetadata;
using lib::HashVal;
/**
* an opaque ID to be used by the BufferProvider implementation.
* Typically this will be used, to set apart some pre-registered
* kinds of buffers. It is treated as being part of the buffer type.
* LocalKey objects may be copied but not re-assigned or changed.
*/
class LocalKey
{
union OpaqueData
{
uint64_t _as_number;
void* _as_pointer;
};
OpaqueData privateID_;
public:
explicit
LocalKey (uint64_t opaqueValue=0)
{
privateID_._as_number = opaqueValue;
}
LocalKey (void* impl_related_ptr)
{
privateID_._as_number = 0;
privateID_._as_pointer = impl_related_ptr;
}
operator uint64_t() const
{
return privateID_._as_number;
}
operator void*() const
{
return privateID_._as_pointer;
}
bool
isDefined() const
{
return bool(privateID_._as_number);
}
friend size_t
hash_value (LocalKey const& lkey)
{
boost::hash<uint64_t> hashFunction;
return hashFunction(lkey.privateID_._as_number);
}
friend bool
operator== (LocalKey const& left, LocalKey const& right)
{
return uint64_t(left) == uint64_t(right);
}
friend bool
operator!= (LocalKey const& left, LocalKey const& right)
{
return uint64_t(left) != uint64_t(right);
}
private:
/** assignment usually prohibited */
LocalKey& operator= (LocalKey const& o)
{
privateID_ = o.privateID_;
return *this;
}
/** but Key assignments are acceptable */
friend class metadata::Key;
};
} // namespace engine
#endif

View file

@ -31,7 +31,7 @@
** - that overall storage size available within the buffer
** - a pair of custom \em creator and \em destructor functions to use together with this buffer
** - an additional client key to distinguish otherwise otherwise identical client requests
** These three distinctions are applied in sequence, thus forming a tree with 3 levels.
** These three distinctions are applied in sequence, thus forming a type tree with 3 levels.
** Only the first distinguishing level (the size) is mandatory. The others are provided,
** because some of the foreseeable buffer providers allow to re-access the data placed
** into the buffer, by assigning an internally managed ID to the buffer. The most
@ -56,10 +56,12 @@
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/functor-util.hpp"
#include "lib/util-foreach.hpp"
#include "include/logging.h"
#include "proc/engine/type-handler.hpp"
#include "proc/engine/buffer-local-key.hpp"
#include <tr1/functional>
#include <boost/functional/hash.hpp>
#include <tr1/unordered_map>
#include <boost/noncopyable.hpp>
@ -67,9 +69,7 @@ namespace engine {
using lib::HashVal;
using lib::Literal;
using std::tr1::bind;
using std::tr1::function;
using std::tr1::placeholders::_1;
using util::for_each;
namespace error = lumiera::error;
@ -77,165 +77,27 @@ namespace engine {
class Key;
class Entry;
}
class BufferMetadata;
/**
* Buffer states
* usable within BufferProvider
* and stored within the metadata
*/
enum BufferState
{ NIL,
FREE,
LOCKED,
EMITTED,
BLOCKED
{ NIL, ///< abstract entry, not yet allocated
FREE, ///< allocated buffer, no longer in use
LOCKED, ///< allocated buffer actively in use
EMITTED, ///< allocated buffer, returned from client
BLOCKED ///< allocated buffer blocked by protocol failure
};
/**
* an opaque ID to be used by the BufferProvider implementation.
* Typically this will be used, to set apart some pre-registered
* kinds of buffers. It is treated as being part of the buffer type.
* LocalKey objects may be copied but not re-assigned or changed.
*/
class LocalKey
{
uint64_t privateID_;
public:
LocalKey (uint64_t opaqueValue=0)
: privateID_(opaqueValue)
{ }
operator uint64_t() const { return privateID_; }
friend size_t
hash_value (LocalKey const& lkey)
{
boost::hash<uint64_t> hashFunction;
return hashFunction(lkey.privateID_);
}
private:
/** assignment usually prohibited */
LocalKey& operator= (LocalKey const& o)
{
privateID_ = o.privateID_;
return *this;
}
/** but Key assignments are acceptable */
friend class metadata::Key;
};
namespace { // Helpers for construction within the buffer...
template<class X>
inline void
buildIntoBuffer (void* storageBuffer)
{
new(storageBuffer) X();
}
template<class X, typename A1>
inline void
buildIntoBuffer_A1 (void* storageBuffer, A1 arg1)
{
new(storageBuffer) X(arg1);
}
template<class X>
inline void
destroyInBuffer (void* storageBuffer)
{
X* embedded = static_cast<X*> (storageBuffer);
embedded->~X();
}
}//(End)placement-new helpers
/**
* A pair of functors to maintain a datastructure within the buffer.
* TypeHandler describes how to outfit the buffer in a specific way.
* When defined, the buffer will be prepared when locking and cleanup
* will be invoked automatically when releasing. Especially, this
* can be used to \em attach an object to the buffer (placement-new)
*/
struct TypeHandler
{
typedef function<void(void*)> DoInBuffer;
DoInBuffer createAttached;
DoInBuffer destroyAttached;
/** build an invalid NIL TypeHandler */
TypeHandler()
: createAttached()
, destroyAttached()
{ }
/** build a TypeHandler
* binding to arbitrary constructor and destructor functions.
* On invocation, these functions get a void* to the buffer.
* @note the functor objects created from these operations
* might be shared for handling multiple buffers.
* Be careful with any state or arguments.
*/
template<typename CTOR, typename DTOR>
TypeHandler(CTOR ctor, DTOR dtor)
: createAttached (ctor)
, destroyAttached (dtor)
{ }
/** builder function defining a TypeHandler
* to place a default-constructed object
* into the buffer. */
template<class X>
static TypeHandler
create ()
{
return TypeHandler (buildIntoBuffer<X>, destroyInBuffer<X>);
}
template<class X, typename A1>
static TypeHandler
create (A1 a1)
{
return TypeHandler ( bind (buildIntoBuffer_A1<X,A1>, _1, a1)
, destroyInBuffer<X>);
}
bool
isValid() const
{
return bool(createAttached)
&& bool(destroyAttached);
}
friend HashVal
hash_value (TypeHandler const& handler)
{
HashVal hash(0);
if (handler.isValid())
{
boost::hash_combine(hash, handler.createAttached);
boost::hash_combine(hash, handler.destroyAttached);
}
return hash;
}
friend bool
operator== (TypeHandler const& left, TypeHandler const& right)
{
return (!left.isValid() && !right.isValid())
|| ( util::rawComparison(left.createAttached, right.createAttached)
&& util::rawComparison(left.destroyAttached, right.destroyAttached)
);
}
friend bool
operator!= (TypeHandler const& left, TypeHandler const& right)
{
return !(left == right);
}
};
namespace { // internal constants to mark the default case
@ -258,7 +120,7 @@ namespace engine {
/* === Implementation === */
/* === Metadata Implementation === */
namespace metadata {
@ -275,6 +137,13 @@ namespace engine {
}
}
/**
* Description of a Buffer-"type".
* Key elements will be used to generate hash IDs,
* to be embedded into a BufferDescriptor.
* Keys are chained hierarchically.
*/
class Key
{
HashVal parent_;
@ -342,27 +211,108 @@ namespace engine {
{ }
/** build derived Key for a concrete buffer Entry
* @param parent type key to subsume this buffer
* @param bufferAddr pointer to the concrete buffer
* @return Child key with hashID based on the buffer address.
* For NULL buffer a copy of the parent is returned.
*/
static Key
forEntry (Key const& parent, const void* bufferAddr, LocalKey const& implID =UNSPECIFIC)
{
Key newKey(parent);
if (bufferAddr)
{
newKey.parent_ = HashVal(parent);
newKey.hashID_ = chainedHash(parent, bufferAddr);
if (nontrivial(implID))
{
REQUIRE (!newKey.specifics_.isDefined(),
"Implementation defined local key should not be overridden. "
"Underlying buffer type already defines a nontrivial LocalKey");
newKey.specifics_ = implID;
} }
return newKey;
}
void
useTypeHandlerFrom (Key const& ref)
{
if (nontrivial(this->instanceFunc_))
throw error::Logic ("unable to supersede an already attached TypeHandler"
, LUMIERA_ERROR_LIFECYCLE);
instanceFunc_ = ref.instanceFunc_;
}
LocalKey const& localKey() const { return specifics_;}
size_t storageSize() const { return storageSize_; }
HashVal parentKey() const { return parent_;}
operator HashVal() const { return hashID_;}
};
/**
* A complete metadata Entry, based on a Key.
* This special Key element usually describes an actual Buffer.
* Entries are to be managed in a hashtable, which is "the metadata table".
* As a special case, an entry without a concrete buffer storage pointer
* can be created. This corresponds to a (plain) key and describes just
* a buffer type. Such type-only entries are fixed to the NIL state.
* All other entries allow for state transitions.
*
* The "metadata table" with its entries is maintained by an engine::BufferMetadata
* instance. For the latter, Entry serves as representation and access point
* to the individual metadata; this includes using the TypeHandler for
* building and destroying buffer structures.
*/
class Entry
: public Key
{
BufferState state_;
const void* buffer_;
void* buffer_;
protected:
Entry (Key const& parent, void* bufferPtr =0, LocalKey const& implID =UNSPECIFIC)
: Key (Key::forEntry (parent, bufferPtr, implID))
, state_(bufferPtr? LOCKED:NIL)
, buffer_(bufferPtr)
{ }
/// BufferMetadata is allowed to create
friend class engine::BufferMetadata;
// standard copy operations permitted
public:
virtual BufferState
/** is this Entry currently associated to a
* concrete buffer? Is this buffer in use? */
bool
isLocked() const
{
ASSERT (!buffer_ || (NIL != state_ && FREE != state_));
return bool(buffer_);
}
/** is this Entry just an (abstract) placeholder for a type?
* @return false if it's a real entry corresponding to a concrete buffer
*/
bool
isTypeKey() const
{
return NIL == state_ && !buffer_;
}
BufferState
state() const
{
__must_not_be_NIL();
return state_;
}
virtual const void*
access() const
void*
access()
{
__must_not_be_NIL();
__must_not_be_FREE();
@ -371,40 +321,83 @@ namespace engine {
return buffer_;
}
virtual Entry&
/** Buffer state machine */
Entry&
mark (BufferState newState)
{
switch (this->state_)
__must_not_be_NIL();
if ( (state_ == FREE && newState == LOCKED)
||(state_ == LOCKED && newState == EMITTED)
||(state_ == LOCKED && newState == BLOCKED)
||(state_ == LOCKED && newState == FREE)
||(state_ == EMITTED && newState == BLOCKED)
||(state_ == EMITTED && newState == FREE)
||(state_ == BLOCKED && newState == FREE))
{
case NIL: __must_not_be_NIL();
case FREE: __must_not_be_FREE();
case LOCKED:
if (newState == EMITTED) break; // allow transition
case EMITTED:
if (newState == BLOCKED) break; // allow transition
case BLOCKED:
if (newState == FREE) // note fall through for LOCKED and EMITTED too
{
buffer_ = 0;
break; // allow transition
}
default:
throw error::Fatal ("Invalid buffer state encountered.");
// allowed transition
if (newState == FREE)
invokeEmbeddedDtor_and_clear();
if (newState == LOCKED)
invokeEmbeddedCtor();
state_ = newState;
return *this;
}
state_ = newState;
throw error::Fatal ("Invalid buffer state transition.");
}
Entry&
lock (void* newBuffer)
{
__must_be_FREE();
buffer_ = newBuffer;
return mark (LOCKED);
}
Entry&
invalidate (bool invokeDtor =true)
{
if (buffer_ && invokeDtor)
invokeEmbeddedDtor_and_clear();
buffer_ = 0;
state_ = FREE;
return *this;
}
protected:
/** @internal maybe invoke a registered TypeHandler's
* constructor function, which typically builds some
* content object into the buffer by placement new. */
void
invokeEmbeddedCtor()
{
__buffer_required();
if (nontrivial (instanceFunc_))
instanceFunc_.createAttached (buffer_);
}
/** @internal maybe invoke a registered TypeHandler's
* destructor function, which typically clears up some
* content object living within the buffer */
void
invokeEmbeddedDtor_and_clear()
{
__buffer_required();
if (nontrivial (instanceFunc_))
instanceFunc_.destroyAttached (buffer_);
buffer_ = 0;
}
private:
void
__must_not_be_NIL() const
{
if (NIL == state_)
throw error::Fatal ("Concrete buffer entry with state==NIL encountered."
"State transition logic broken (programming error)");
throw error::Fatal ("Buffer metadata entry with state==NIL encountered."
"State transition logic broken (programming error)"
, LUMIERA_ERROR_LIFECYCLE);
}
void
@ -416,21 +409,144 @@ namespace engine {
"You should invoke markLocked(buffer) prior to access."
, LUMIERA_ERROR_LIFECYCLE );
}
void
__must_be_FREE() const
{
if (FREE != state_)
throw error::Logic ("Buffer already in use"
, LUMIERA_ERROR_LIFECYCLE );
REQUIRE (!buffer_, "Buffer marked as free, "
"but buffer pointer is set.");
}
void
__buffer_required() const
{
if (!buffer_)
throw error::Fatal ("Need concrete buffer for any further operations");
}
};
}
/**
* (Hash)Table to store and manage buffer metadata.
* Buffer metadata entries are comprised of a Key part and an extended
* Entry, holding the actual management and housekeeping metadata. The
* Keys are organised hierarchically and denote the "kind" of buffer.
* The hash values for lookup are based on the key part, chained with
* the actual memory location of the concrete buffer corresponding
* to the metadata entry to be retrieved.
*/
class Table
{
typedef std::tr1::unordered_map<HashVal,Entry> MetadataStore;
MetadataStore entries_;
public:
~Table() { verify_all_buffers_freed(); }
/** fetch metadata record, if any
* @param hashID for the Key part of the metadata entry
* @return pointer to the entry in the table or NULL
*/
Entry*
fetch (HashVal hashID)
{
MetadataStore::iterator pos = entries_.find (hashID);
if (pos != entries_.end())
return &(pos->second);
else
return NULL;
}
const Entry*
fetch (HashVal hashID) const
{
MetadataStore::const_iterator pos = entries_.find (hashID);
if (pos != entries_.end())
return &(pos->second);
else
return NULL;
}
/** store a copy of the given new metadata entry.
* The hash key for lookup is retrieved from the given Entry, by conversion to HashVal.
* Consequently, this will be the hashID of the parent Key (type), when the entry holds
* a NULL buffer (i.e a "pseudo entry"). Otherwise, it will be this parent Key hash,
* extended by hashing the actual buffer address.
* @return reference to relevant entry for this Key. This might be a copy
* of the new entry, or an already existing entry with the same Key
*/
Entry&
store (Entry const& newEntry)
{
using std::make_pair;
REQUIRE (!fetch (newEntry), "duplicate buffer metadata entry");
MetadataStore::iterator pos = entries_.insert (make_pair (HashVal(newEntry), newEntry))
.first;
ENSURE (pos != entries_.end());
return pos->second;
}
void
remove (HashVal hashID)
{
uint cnt = entries_.erase (hashID);
ENSURE (cnt, "entry to remove didn't exist");
}
private:
void
verify_all_buffers_freed()
try
{
for_each (entries_, verify_is_free);
}
ERROR_LOG_AND_IGNORE (engine,"Shutdown of BufferProvider metadata store")
static void
verify_is_free (std::pair<HashVal, Entry> const& e)
{
WARN_IF (e.second.isLocked(), engine,
"Buffer still in use while shutting down BufferProvider? ");
}
};
}//namespace metadata
class Metadata
/* ===== Buffer Metadata Frontend ===== */
/**
* Registry for managing buffer metadata.
* This is an implementation level service,
* used by the standard BufferProvider implementation.
* Each metadata registry (instance) defines and maintains
* a family of "buffer types"; beyond the buffer storage size,
* the concrete meaning of those types is tied to the corresponding
* BufferProvider implementation and remains opaque. These types are
* represented as hierarchically linked hash keys. The implementation
* may bind a TypeHandler to a specific type, allowing automatic invocation
* of a "constructor" and "destructor" function on each buffer of this type,
* when \em locking or \em freeing the corresponding buffer.
*/
class BufferMetadata
: boost::noncopyable
{
Literal id_;
HashVal family_;
public:
metadata::Table table_;
public:
typedef metadata::Key Key;
typedef metadata::Entry Entry;
@ -438,10 +554,10 @@ namespace engine {
* Such will maintain a family of buffer type entries
* and provide a service for storing and retrieving metadata
* for concrete buffer entries associated with these types.
* @param implementationID to distinguish families
* of type keys belonging to different registries.
* @param implementationID to distinguish families of
* type keys belonging to different registries.
*/
Metadata (Literal implementationID)
BufferMetadata (Literal implementationID)
: id_(implementationID)
, family_(hash_value(id_))
{ }
@ -459,20 +575,16 @@ namespace engine {
Key
key ( size_t storageSize
, TypeHandler instanceFunc =RAW_BUFFER
, LocalKey specifics =UNSPECIFIC)
, LocalKey specifics =UNSPECIFIC)
{
REQUIRE (storageSize);
Key typeKey = trackKey (family_, storageSize);
if (nontrivial(instanceFunc))
{
typeKey = trackKey (typeKey, instanceFunc);
}
if (nontrivial(specifics))
{
typeKey = trackKey (typeKey, specifics);
}
return typeKey;
}
@ -484,50 +596,159 @@ namespace engine {
return trackKey (parentKey, instanceFunc);
}
/** create a sub-type, using a different private-ID (implementation defined) */
/** create a sub-type,
* using a different private-ID (implementation defined) */
Key
key (Key const& parentKey, LocalKey specifics)
{
return trackKey (parentKey, specifics);
}
Key
key (Key const& parentKey, const void* concreteBuffer)
/** shortcut to access the Key part of a (probably new) Entry
* describing a concrete buffer at the given address
* @note might create/register a new Entry as a side-effect
*/
Key const&
key (Key const& parentKey, void* concreteBuffer, LocalKey const& implID =UNSPECIFIC)
{
UNIMPLEMENTED ("create sub-object key for concrete buffer");
Key derivedKey = Key::forEntry (parentKey, concreteBuffer);
Entry* existing = table_.fetch (derivedKey);
return existing? *existing
: markLocked (parentKey,concreteBuffer,implID);
}
Key const&
/** core operation to access or create a concrete buffer metadata entry.
* The hashID of the entry in question is built, based on the parentKey,
* which denotes a buffer type, and the concrete buffer address. If yet
* unknown, a new concrete buffer metadata Entry is created and initialised
* to LOCKED state. Otherwise just the existing Entry is fetched.
* @note this function really \em activates the buffer.
* In case the type (Key) involves a TypeHandler (functor),
* its constructor function will be invoked, if actually a new
* entry gets created. Typically this mechanism will be used
* to placement-create an object into the buffer.
* @param parentKey a key describing the \em type of the buffer
* @param concreteBuffer storage pointer, must not be NULL
* @param onlyNew disallow fetching an existing entry
* @throw error::Logic when #onlyNew is set, but an equivalent entry
* was registered previously. This indicates a serious error
* in buffer lifecycle management.
* @throw error::Invalid when invoked with NULL buffer. Use the #key
* functions instead to register and track type keys.
* @return reference to the entry stored in the metadata table.
* @warning the exposed reference might become invalid when the
* buffer is released or re-used later.
*/
Entry&
lock (Key const& parentKey
,void* concreteBuffer
,LocalKey const& implID =UNSPECIFIC
,bool onlyNew =false)
{
if (!concreteBuffer)
throw error::Invalid ("Attempt to lock a slot for a NULL buffer"
, error::LUMIERA_ERROR_BOTTOM_VALUE);
Entry newEntry(parentKey, concreteBuffer, implID);
Entry* existing = table_.fetch (newEntry);
if (existing && onlyNew)
throw error::Logic ("Attempt to lock a slot for a new buffer, "
"while actually the old buffer is still locked"
, error::LUMIERA_ERROR_LIFECYCLE );
if (existing && existing->isLocked())
throw error::Logic ("Attempt to re-lock a buffer still in use"
, error::LUMIERA_ERROR_LIFECYCLE );
if (!existing)
return store_and_lock (newEntry); // actual creation
else
return existing->lock (concreteBuffer);
}
/** access the metadata record registered with the given hash key.
* This might be a pseudo entry in case of a Key describing a buffer type.
* Otherwise, the entry associated with a concrete buffer pointer is returned
* by reference, an can be modified (e.g. state change)
* @param hashID which can be calculated from the Key
* @throw error::Invalid when there is no such entry
* @note use #isKnown to check existence
*/
Entry&
get (HashVal hashID)
{
UNIMPLEMENTED ("access the plain key entry");
}
Entry&
get (Key key)
{
UNIMPLEMENTED ("access, possibly create metadata records");
Entry* entry = table_.fetch (hashID);
if (!entry)
throw error::Invalid ("Attempt to access an unknown buffer metadata entry");
return *entry;
}
bool
isKnown (HashVal key) const
{
UNIMPLEMENTED ("diagnostics: known record?");
return bool(table_.fetch (key));
}
bool
isLocked (HashVal key) const
{
UNIMPLEMENTED ("diagnostics: actually locked buffer instance record?");
const Entry* entry = table_.fetch (key);
return entry
&& entry->isLocked();
}
/* == memory management == */
Entry& markLocked (Key const& parentKey, const void* buffer);
void release (HashVal key);
/* == memory management operations == */
private:
/** combine the type (Key) with a concrete buffer,
* thereby marking this buffer as locked. Store a concrete
* metadata Entry to account for this fact. This might include
* invoking a constructor function, in case the type (Key)
* defines a (nontrivial) TypeHandler.
* @throw error::Fatal when locking a NULL buffer
* @throw exceptions which might be raised by a TypeHandler's
* constructor function. In this case, the Entry remains
* created, but is marked as FREE
*/
Entry&
markLocked (Key const& parentKey, void* buffer, LocalKey const& implID =UNSPECIFIC)
{
if (!buffer)
throw error::Fatal ("Attempt to lock for a NULL buffer. Allocation floundered?"
, error::LUMIERA_ERROR_BOTTOM_VALUE);
return this->lock(parentKey, buffer, implID, true); // force creation of a new entry
}
/** purge the bare metadata Entry from the metadata tables.
* @throw error::Logic if the entry isn't marked FREE already
*/
void
release (HashVal key)
{
Entry* entry = table_.fetch (key);
if (!entry) return;
ASSERT (entry && (key == HashVal(*entry)));
release (*entry);
}
void
release (Entry const& entry)
{
if (FREE != entry.state())
throw error::Logic ("Attempt to release a buffer still in use"
, error::LUMIERA_ERROR_LIFECYCLE);
table_.remove (HashVal(entry));
}
private:
template<typename PAR, typename DEF>
Key
@ -542,43 +763,31 @@ namespace engine {
maybeStore (Key const& key)
{
if (isKnown (key)) return;
UNIMPLEMENTED ("registry for type keys");
table_.store (Entry (key, NULL));
}
Entry&
store_and_lock (Entry const& metadata)
{
Entry& newEntry = table_.store (metadata);
try
{
newEntry.invokeEmbeddedCtor();
ENSURE (LOCKED == newEntry.state());
ENSURE (newEntry.access());
}
catch(...)
{
newEntry.mark(FREE);
throw;
}
return newEntry;
}
};
/** */
inline Metadata::Entry&
Metadata::markLocked (Key const& parentKey, const void* buffer)
{
UNIMPLEMENTED ("transition to locked state");
if (!buffer)
throw error::Fatal ("Attempt to lock for a NULL buffer. Allocation floundered?"
, error::LUMIERA_ERROR_BOTTOM_VALUE);
Key newKey = this->key (parentKey, buffer);
if (isLocked(newKey))
throw error::Logic ("Attempt to lock a slot for a new buffer, "
"while actually the old buffer is still locked."
, error::LUMIERA_ERROR_LIFECYCLE );
return this->get(newKey);
}
inline void
Metadata::release (HashVal key)
{
UNIMPLEMENTED ("metadata memory management");
}
} // namespace engine
#endif

View file

@ -21,9 +21,12 @@
* *****************************************************/
#include "lib/error.hpp"
#include "proc/engine/buffer-provider.hpp"
#include "proc/engine/buffer-metadata.hpp"
#include "lib/util.hpp"
using util::isSameObject;
namespace engine {
@ -33,10 +36,17 @@ namespace engine {
const uint DEFAULT_DESCRIPTOR = 0;
}
LUMIERA_ERROR_DEFINE (BUFFER_MANAGEMENT, "Problem providing working buffers");
/** build a new provider instance, managing a family of buffers.
* The metadata of these buffers is organised hierarchically based on
* chained hash values, using the #implementationID as a seed.
* @param implementationID symbolic ID setting these family of buffers apart.
*/
BufferProvider::BufferProvider (Literal implementationID)
: meta_(new Metadata (implementationID))
: meta_(new BufferMetadata (implementationID))
{ }
BufferProvider::~BufferProvider() { }
@ -47,9 +57,9 @@ namespace engine {
* currently locked and usable by client code
*/
bool
BufferProvider::verifyValidity (BufferDescriptor const&)
BufferProvider::verifyValidity (BufferDescriptor const& bufferID) const
{
UNIMPLEMENTED ("BufferProvider basic and default implementation");
return meta_->isLocked (bufferID);
}
@ -58,8 +68,176 @@ namespace engine {
{
return BufferDescriptor (*this, meta_->key (storageSize));
}
BufferDescriptor
BufferProvider::getDescriptorFor(size_t storageSize, TypeHandler specialTreatment)
{
return BufferDescriptor (*this, meta_->key (storageSize, specialTreatment));
}
size_t
BufferProvider::getBufferSize (HashVal typeID) const
{
metadata::Key& typeKey = meta_->get (typeID);
return typeKey.storageSize();
}
/** callback from implementation to build and enrol a BufferHandle,
* to be returned to the client as result of the #lockBuffer call.
* Performs the necessary metadata state transition leading from an
* abstract buffer type to a metadata::Entry corresponding to an
* actual buffer, which is locked for exclusive use by one client.
*/
BuffHandle
BufferProvider::buildHandle (HashVal typeID, void* storage, LocalKey const& implID)
{
metadata::Key& typeKey = meta_->get (typeID);
metadata::Entry& entry = meta_->markLocked(typeKey, storage, implID);
return BuffHandle (BufferDescriptor(*this, entry), storage);
}
/** BufferProvider API: declare in advance the need for working buffers.
* This optional call allows client code to ensure the availability of the
* necessary working space, prior to starting the actual operations. The
* client may reasonably assume to get the actual number of buffers, as
* indicated by the return value. A provider may be able to handle
* various kinds of buffers (e.g. of differing size), which are
* distinguished by the \em type embodied into the BufferDescriptor.
* @return maximum number of simultaneously usable buffers of this type,
* to be retrieved later through calls to #lockBuffer.
* @throw error::State when no buffer of this kind can be provided
* @note the returned count may differ from the requested count.
*/
uint
BufferProvider::announce (uint count, BufferDescriptor const& type)
{
uint actually_possible = prepareBuffers (count, type);
if (!actually_possible)
throw error::State ("unable to fulfil request for buffers"
,LUMIERA_ERROR_BUFFER_MANAGEMENT);
return actually_possible;
}
/** BufferProvider API: retrieve a single buffer for exclusive use.
* This call actually claims a buffer of this type and marks it for
* use by client code. The returned handle allows for convenient access,
* but provides no automatic tracking or memory management. The client is
* explicitly responsible to invoke #releaseBuffer (which can be done directly
* on the BuffHandle).
* @return a copyable handle, representing this buffer and this usage transaction.
* @throw error::State when unable to provide this buffer
* @note this function may be used right away, without prior announcing, but then
* the client should be prepared for exceptions. The #announce operation allows
* to establish a reliably available baseline.
*/
BuffHandle
BufferProvider::lockBuffer (BufferDescriptor const& type)
{
REQUIRE (was_created_by_this_provider (type));
return provideLockedBuffer (type);
} // is expected to call buildHandle() --> state transition
/** BufferProvider API: state transition to \em emitted state.
* Client code may signal a state transition through this optional operation.
* The actual meaning of an "emitted" buffer is implementation defined; similarly,
* some back-ends may actually do something when emitting a buffer (e.g. commit data
* to cache), while others just set a flag or do nothing at all. This state transition
* may be invoked at most once per locked buffer.
* @throw error::Fatal in case of invalid state transition sequence. Only a locked buffer
* may be emitted, and at most once.
* @warning by convention, emitting a buffer implies that the contained data is ready and
* might be used by other parts of the application.
* An emitted buffer should not be modified anymore.
*/
void
BufferProvider::emitBuffer (BuffHandle const& handle)
{
metadata::Entry& metaEntry = meta_->get (handle.entryID());
mark_emitted (metaEntry.parentKey(), metaEntry.localKey());
metaEntry.mark(EMITTED);
}
/** BufferProvider API: declare done and detach.
* Client code is required to release \em each previously locked buffer eventually.
* @warning invalidates the BuffHandle, clients mustn't access the buffer anymore.
* Right after releasing, an access through the handle will throw;
* yet the buffer might be re-used and the handle become valid
* later on accidentally.
* @note EX_FREE
*/
void
BufferProvider::releaseBuffer (BuffHandle const& handle)
try {
metadata::Entry& metaEntry = meta_->get (handle.entryID());
metaEntry.mark(FREE); // might invoke embedded dtor function
detachBuffer (metaEntry.parentKey(), metaEntry.localKey());
meta_->release (metaEntry);
}
ERROR_LOG_AND_IGNORE (engine, "releasing a buffer from BufferProvider")
/** @warning this operation locally modifies the metadata entry of a single buffer
* to attach a TypeHandler taking ownership of an object embedded within the buffer.
* The client is responsible for actually placement-constructing the object; moreover
* the client is responsible for any damage done to already existing buffer content.
* @note the buffer must be in locked state and the underlying buffer type must not define
* an non-trivial TypeDescriptor, because there is no clean way of superseding an
* existing TypeDescriptor, which basically is just a functor and possibly
* could perform any operation on buffer clean-up.
* @note EX_STRONG
*/
void
BufferProvider::attachTypeHandler (BuffHandle const& target, BufferDescriptor const& reference)
{
metadata::Entry& metaEntry = meta_->get (target.entryID());
metadata::Entry& refEntry = meta_->get (reference);
REQUIRE (refEntry.isTypeKey());
REQUIRE (!metaEntry.isTypeKey());
if (!metaEntry.isLocked())
throw error::Logic ("unable to attach an object because buffer isn't locked for use"
, LUMIERA_ERROR_LIFECYCLE);
metaEntry.useTypeHandlerFrom (refEntry); // EX_STRONG
}
/** @internal abort normal lifecycle, reset the underlying buffer and detach from it.
* This allows to break out of normal usage and reset the handle to \em invalid state
* @param invokeDtor if possibly the clean-up function of an TypeHandler registered with
* the buffer metadata should be invoked prior to resetting the metadata state.
* Default is \em not to invoke anything
* @note EX_FREE
*/
void
BufferProvider::emergencyCleanup (BuffHandle const& target, bool invokeDtor)
try {
metadata::Entry& metaEntry = meta_->get (target.entryID());
metaEntry.invalidate (invokeDtor);
detachBuffer (metaEntry.parentKey(), metaEntry.localKey());
meta_->release (metaEntry);
}
ERROR_LOG_AND_IGNORE (engine, "cleanup of buffer metadata while handling an error")
bool
BufferProvider::was_created_by_this_provider (BufferDescriptor const& descr) const
{
return isSameObject (*this, *descr.provider_);
}
/* === BufferDescriptor and BuffHandle === */
@ -71,12 +249,67 @@ namespace engine {
}
size_t
BufferDescriptor::determineBufferSize() const
{
return provider_->getBufferSize (*this);
}
void
BuffHandle::emit()
{
REQUIRE (isValid());
descriptor_.provider_->emitBuffer(*this);
}
void
BuffHandle::release()
{
UNIMPLEMENTED ("forward buffer release call to buffer provider");
if (pBuffer_)
{
REQUIRE (isValid());
descriptor_.provider_->releaseBuffer(*this);
pBuffer_ = 0;
}
ENSURE (!isValid());
}
void
BuffHandle::emergencyCleanup()
{
descriptor_.provider_->emergencyCleanup(*this); // EX_FREE
pBuffer_ = 0;
}
/** Install a standard TypeHandler for an already locked buffer.
* This causes the dtor function to be invoked when releasing this buffer.
* The assumption is that client code will placement-construct an object
* into this buffer right away, and thus we're taking ownership on that object.
* @param type a reference BufferDescriptor defining an embedded TypeHandler to use
* A copy of this TypeHandler will be stored into the local metadata for
* this buffer only, not altering the basic buffer type in any way
* @throw lifecycle error when attempting to treat an buffer not in locked state
* @throw error::Logic in case of insufficient buffer space to hold the
* intended target object
* @note EX_STRONG
*/
void
BuffHandle::takeOwnershipFor(BufferDescriptor const& type)
{
if (!this->isValid())
throw error::Logic ("attaching an object requires an buffer in locked state"
, LUMIERA_ERROR_LIFECYCLE);
if (this->size() < type.determineBufferSize())
throw error::Logic ("insufficient buffer size to hold an instance of that type");
descriptor_.provider_->attachTypeHandler(*this, type); // EX_STRONG
}
} // namespace engine

View file

@ -22,7 +22,7 @@
/** @file buffer-provider.hpp
** Abstraction to represent buffer management and lifecycle within the render engine.
** It turns out that -- throughout the render engine implementation -- we never need
** It turns out that -- throughout the render engine implementation -- we never need
** direct access to the buffers holding media data. Buffers are just some entity to be \em managed,
** i.e. "allocated", "locked" and "released"; the actual meaning of these operations is an implementation detail.
** The code within the render engine just pushes around BufferHandle objects, which act as a front-end,
@ -45,6 +45,8 @@
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "proc/engine/buffhandle.hpp"
#include "proc/engine/type-handler.hpp"
#include "proc/engine/buffer-local-key.hpp"
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
@ -56,7 +58,10 @@ namespace engine {
using lib::Literal;
class Metadata;
class BufferMetadata;
LUMIERA_ERROR_DECLARE (BUFFER_MANAGEMENT); ///< Problem providing working buffers
/**
@ -66,46 +71,70 @@ namespace engine {
* - "locking" a buffer to yield a buffer handle
* - dereferencing this smart-handle class
*
* @warning all of BufferProvider is assumed to run within a threadsafe environment.
*
* @todo as of 6/2011 buffer management within the engine is still a bit vague
* @todo as of 11/11 thread safety within the engine remains to be clarified
*/
class BufferProvider
: boost::noncopyable
{
scoped_ptr<Metadata> meta_;
scoped_ptr<BufferMetadata> meta_;
protected: /* === for Implementation by concrete providers === */
protected:
BufferProvider (Literal implementationID);
virtual uint prepareBuffers (uint count, HashVal typeID) =0;
virtual BuffHandle provideLockedBuffer (HashVal typeID) =0;
virtual void mark_emitted (HashVal typeID, LocalKey const&) =0;
virtual void detachBuffer (HashVal typeID, LocalKey const&) =0;
public:
virtual ~BufferProvider(); ///< this is an interface
virtual ~BufferProvider(); ///< this is an ABC
virtual uint announce (uint count, BufferDescriptor const&) =0;
uint announce (uint count, BufferDescriptor const&);
virtual BuffHandle lockBufferFor (BufferDescriptor const&) =0;
virtual void releaseBuffer (BuffHandle const&) =0;
BuffHandle lockBuffer (BufferDescriptor const&);
void emitBuffer (BuffHandle const&);
void releaseBuffer (BuffHandle const&);
template<typename BU>
BuffHandle lockBufferFor ();
/** allow for attaching and owing an object within an already created buffer */
void attachTypeHandler (BuffHandle const& target, BufferDescriptor const& reference);
void emergencyCleanup (BuffHandle const& target, bool invokeDtor =false);
/** describe the kind of buffer managed by this provider */
BufferDescriptor getDescriptorFor(size_t storageSize=0);
BufferDescriptor getDescriptorFor(size_t storageSize, TypeHandler specialTreatment);
template<typename BU>
BufferDescriptor getDescriptor();
/* === API for BuffHandle internal access === */
bool verifyValidity (BufferDescriptor const&);
bool verifyValidity (BufferDescriptor const&) const;
size_t getBufferSize (HashVal typeID) const;
protected:
BuffHandle buildHandle (HashVal typeID, void* storage, LocalKey const&);
bool was_created_by_this_provider (BufferDescriptor const&) const;
};
/* === Implementation === */
/** convenience shortcut:
@ -119,15 +148,20 @@ namespace engine {
BuffHandle
BufferProvider::lockBufferFor()
{
UNIMPLEMENTED ("convenience shortcut to announce and lock for a specific object type");
BufferDescriptor attach_object_automatically = getDescriptor<BU>();
return lockBuffer (attach_object_automatically);
}
/** define a "buffer type" for automatically creating
* an instance of the template type embedded into the buffer
* and destroying that embedded object when releasing the buffer.
*/
template<typename BU>
BufferDescriptor
BufferProvider::getDescriptor()
{
UNIMPLEMENTED ("build descriptor for automatically placing an object instance into the buffer");
return getDescriptorFor (sizeof(BU), TypeHandler::create<BU>());
}

View file

@ -0,0 +1,129 @@
/*
BUFFHANDLE-ATTACH.hpp - Buffer handle extension to attach objects into the buffer
Copyright (C) Lumiera.org
2008, 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 buffhandle-attach.hpp
** Extension to allow placing objects right into the buffers, taking ownership.
** This extension is mostly helpful for writing unit-tests, and beyond that for the
** rather unusual case where we need to place an full-blown object into the buffer,
** instead of just plain data. A possible use case for this mechanism is to allow for
** state pre calculation stream, feeding this local state to the individual render node
** embedded into a "state frame". Some effect processors indeed need to maintain state
** beyond the single frame (e.g. averaging, integrating, sound compression), which usually
** is handled by applying an "instance" of that processor to the frames to be calculated
** in a straight sequence.
**
** BuffHandle and the underlying BufferProvider standard implementation support that case
** by attaching an object managing functor to the metadata. This way, the state can live
** directly embedded into the frame and still be accessed like an object. To keep the
** header and compilation footprint low, the implementation of the functions supporting
** this special case was split out of the basic buffhandle.hpp
**
** @see BuffHandle
** @see BufferProviderProtocol_test usage demonstration
*/
#ifndef ENGINE_BUFFHANDLE_ATTACH_H
#define ENGINE_BUFFHANDLE_ATTACH_H
#include "lib/error.hpp"
#include "proc/engine/buffer-provider.hpp"
#include "proc/engine/buffhandle.hpp"
namespace engine {
/* === BuffHandle Implementation === */
#define _EXCEPTION_SAFE_INVOKE(_CTOR_) \
try \
{ \
return *new(pBuffer_) _CTOR_; \
} \
catch(...) \
{ \
emergencyCleanup(); /* EX_FREE */ \
pBuffer_ = 0; \
throw; \
}
/** convenience shortcut: place and maintain an object within the buffer.
* This operation performs the necessary steps to attach an object;
* if the buffer isn't locked yet, it will do so. Moreover, the created
* object will be owned by the buffer management facilities, i.e. the
* destructor is registered as cleanup function.
* @throw error::Logic in case there is already another TypeHandler registered
* in charge of managing the buffer contents, or when the object to create
* would not fit into this buffer.
*/
template<typename BU>
inline BU&
BuffHandle::create()
{
takeOwnershipFor<BU>();
_EXCEPTION_SAFE_INVOKE (BU());
}
#undef _EXCEPTION_SAFE_INVOKE
/** @internal helper to attach an TypeHandler after-the fact.
* @note this prepares the buffer for placement-creating an embedded object.
* It doesn't actually create an object
* @throw error::Logic in case there is already another TypeHandler registered
* in charge of managing the buffer contents, or when the object to create
* would not fit into this buffer.
*/
template<typename BU>
inline void
BuffHandle::takeOwnershipFor()
{
BufferDescriptor howto_attach_object_automatically
= descriptor_.provider_->getDescriptor<BU>();
takeOwnershipFor (howto_attach_object_automatically); // EX_STRONG
}
/** convenience shortcut: access the buffer contents casted to a specific type.
* @warning this is a \em blind cast, there is no type safety.
* @note clients can utilise the metadata::LocalKey to keep track of some
* specific property of the buffer, like e.g. the type of object.
*/
template<typename BU>
inline BU&
BuffHandle::accessAs()
{
if (!pBuffer_)
throw error::Logic ("buffer not (yet) locked for access by clients"
, LUMIERA_ERROR_LIFECYCLE);
return *reinterpret_cast<BU*> (pBuffer_);
}
} // namespace engine
#endif

View file

@ -21,9 +21,9 @@
*/
/** @file buffhandle.hpp
** Various bits needed to support the buffer management within the render nodes.
** A front-end to support the buffer management within the render nodes.
** When pulling data from predecessor nodes and calculating new data, each render node
** needs several input and output buffers. These may be allocated and provided by several
** needs several input and output buffers. These may be allocated and provided by various
** different "buffer providers" (for example the frame cache). Typically, the real buffers
** will be passed as parameters to the actual job instance when scheduled, drawing on the
** results of prerequisite jobs. Yet the actual job implementation remains agnostic with
@ -31,13 +31,19 @@
** objects around. The actual render function gets an array of C-pointers to the actual
** buffers, and for accessing those buffers, the node needs to keep a table of buffer
** pointers, and for releasing the buffers later on, we utilise the buffer handles.
** The usage pattern of those buffer pointer tables is stack-like, thus the actual
** implementation utilises a single large buffer pointer array per pull() call
** sequence and dynamically claims small chunks for each node.
**
** @see nodewiring-def.hpp
** @see nodeoperation.hpp
** @see bufftable.hpp storage for the buffer table
** These buffer handles are based on a buffer descriptor record, which is opaque as far
** as the client is concerned. BufferDescriptor acts as a representation of the type or
** kind of buffer. The only way to obtain such a BufferDescriptor is from a concrete
** BufferProvider implementation. A back-link to this owning and managing provider is
** embedded into the BufferDescriptor, allowing to retrieve an buffer handle, corresponding
** to an actual buffer provided and managed behind the scenes. There is no automatic
** resource management; clients are responsible to invoke BuffHandle#release when done.
**
** @see BufferProvider
** @see BufferProviderProtocol_test usage demonstration
** @see OutputSlot
** @see bufftable.hpp storage for the buffer table
** @see engine::RenderInvocation
*/
@ -52,6 +58,12 @@
namespace engine {
namespace error = lumiera::error;
using error::LUMIERA_ERROR_LIFECYCLE;
typedef size_t HashVal; ////////////TICKET #722
class BuffHandle;
class BufferProvider;
@ -64,54 +76,36 @@ namespace engine {
* @note this descriptor and especially the #subClassification_ is really owned
* by the BufferProvider, which may use (and even change) the opaque contents
* to organise the internal buffer management.
*
* @todo try to move that definition into buffer-provider.hpp ////////////////////////////////////TICKET #249
*/
class BufferDescriptor
{
protected:
BufferProvider* provider_;
uint64_t subClassification_;
HashVal subClassification_;
BufferDescriptor(BufferProvider& manager, uint64_t detail)
BufferDescriptor(BufferProvider& manager, HashVal detail)
: provider_(&manager)
, subClassification_(detail)
{ }
friend class BufferProvider;
friend class BuffHandle;
public:
// using standard copy operations
bool verifyValidity() const;
size_t determineBufferSize() const;
operator HashVal() const { return subClassification_; }
};
class ProcNode;
typedef ProcNode* PNode;
struct ChannelDescriptor ///////TODO really need to define that here? it is needed for node wiring only
{
const lumiera::StreamType * bufferType; /////////////////////////////////////////TICKET #828
};
struct InChanDescriptor : ChannelDescriptor
{
PNode dataSrc; ///< the ProcNode to pull this input from
uint srcChannel; ///< output channel to use on the predecessor node
};
/**
* Handle for a buffer for processing data, abstracting away the actual implementation.
* The real buffer pointer can be retrieved by dereferencing this smart-handle class.
*
* @todo as of 6/2011 it isn't clear how buffer handles are actually created
* and how the lifecycle (and memory) management works //////////////////////TICKET #249 rework BuffHandle creation and usage
*/
class BuffHandle
: public lib::BoolCheckable<BuffHandle>
@ -127,15 +121,16 @@ namespace engine {
/** @internal a buffer handle may be obtained by "locking"
* a buffer from the corresponding BufferProvider */
BuffHandle(BufferDescriptor const& typeInfo, PBuff storage = 0)
BuffHandle(BufferDescriptor const& typeInfo, void* storage = 0)
: descriptor_(typeInfo)
, pBuffer_(storage)
, pBuffer_(static_cast<PBuff>(storage))
{ }
// using standard copy operations
void emit();
void release();
@ -146,6 +141,8 @@ namespace engine {
BU& accessAs();
//////////////////////////////////////////TICKET #249 this operator looks obsolete. The Buff type is a placeholder type,
//////////////////////////////////////////TODO it should never be accessed directly from within Lumiera engine code
Buff&
operator* () const
{
@ -160,42 +157,27 @@ namespace engine {
&& descriptor_.verifyValidity();
}
HashVal
entryID() const
{
return HashVal(descriptor_);
}
size_t
size() const
{
UNIMPLEMENTED ("forward to the buffer provider for storage size diagnostics");
return descriptor_.determineBufferSize();
}
private:
template<typename BU>
void takeOwnershipFor();
void takeOwnershipFor(BufferDescriptor const& type);
void emergencyCleanup();
};
/* === Implementation details === */
/** convenience shortcut: place and maintain an object within the buffer.
* This operation performs the necessary steps to attach an object;
* if the buffer isn't locked yet, it will do so. Moreover, the created
* object will be owned by the buffer management facilities, i.e. the
* destructor is registered as cleanup function.
*/
template<typename BU>
BU&
BuffHandle::create()
{
UNIMPLEMENTED ("convenience shortcut to attach/place an object in one sway");
}
/** convenience shortcut: access the buffer contents in a typesafe fashion.
* This is equivalent to a plain dereferentiation with additional metadata check
* @throw error::Logic in case of type mismatch \c LUMIERA_ERROR_WRONG_TYPE
*/
template<typename BU>
BU&
BuffHandle::accessAs()
{
UNIMPLEMENTED ("convenience shortcut to access buffer contents typesafe");
}
} // namespace engine

View file

@ -26,7 +26,7 @@
#include "lib/error.hpp"
#include "proc/engine/buffhandle.hpp"
#include "proc/engine/channel-descriptor.hpp"
#include "proc/engine/procnode.hpp"
#include <boost/noncopyable.hpp>
@ -70,6 +70,7 @@ namespace engine {
PBu inBuff;
};
class BufferDescriptor;
/** Obsolete, to be rewritten /////TICKET #826 */
class BuffTableStorage

View file

@ -0,0 +1,75 @@
/*
CHANNEL-DESCRIPTOR.hpp - Channel / Buffer type representation for the engine
Copyright (C) Lumiera.org
2008, 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 channel-descriptor.hpp
** Representation of the Media type of a data channel used within the engine.
**
** @todo as it stands (11/2011) this file is obsoleted and needs to be refactored,
** alongside with adapting the node invocation to the new BufferProvider interface.
**
** @see nodewiring-def.hpp
** @see nodeoperation.hpp
** @see bufftable-obsolete.hpp storage for the buffer table
** @see engine::RenderInvocation
*/
#ifndef ENGINE_CHANNEL_DESCRIPTOR_H
#define ENGINE_CHANNEL_DESCRIPTOR_H
#include "lib/error.hpp"
#include "lib/streamtype.hpp"
#include "lib/bool-checkable.hpp"
namespace engine {
namespace error = lumiera::error;
using error::LUMIERA_ERROR_LIFECYCLE;
typedef size_t HashVal; ////////////TICKET #722
class BuffHandle;
class BufferProvider;
class BufferDescriptor;
class ProcNode;
typedef ProcNode* PNode;
struct ChannelDescriptor ///////TODO really need to define that here? it is needed for node wiring only
{
const lumiera::StreamType * bufferType; /////////////////////////////////////////TICKET #828
};
struct InChanDescriptor : ChannelDescriptor
{
PNode dataSrc; ///< the ProcNode to pull this input from
uint srcChannel; ///< output channel to use on the predecessor node
};
} // namespace engine
#endif

View file

@ -22,18 +22,10 @@
#include "lib/error.hpp"
#include "include/logging.h"
#include "lib/meta/function.hpp"
#include "lib/scoped-ptrvect.hpp"
#include "proc/engine/diagnostic-buffer-provider.hpp"
#include <boost/scoped_array.hpp>
//#include <vector>
using lib::ScopedPtrVect;
using boost::scoped_array;
#include "proc/engine/tracking-heap-block-provider.hpp"
namespace engine {
@ -43,115 +35,10 @@ namespace engine {
lib::Singleton<DiagnosticBufferProvider> DiagnosticBufferProvider::diagnostics;
class Block
: boost::noncopyable
{
size_t size_;
scoped_array<char> storage_;
bool was_locked_;
public:
Block()
: size_(0)
, storage_()
, was_locked_(false)
{ }
bool
was_used() const
{
return was_locked_;
}
bool
was_closed() const
{
return was_locked_;
}
void*
accessMemory() const
{
return storage_.get();
}
};
namespace { // Details of allocation and accounting
const uint MAX_BUFFERS = 50;
} // (END) Details of allocation and accounting
/**
* @internal DiagnosticBufferProvider's PImpl.
* Uses a linearly growing table of heap allocated buffer blocks,
* which will never be discarded, unless the PImpl is discarded as a whole.
* This way, the tracked usage information remains available after the fact.
*/
class DiagnosticBufferProvider::HeapMemProvider
: public BufferProvider
, public ScopedPtrVect<Block>
{
virtual uint
announce (uint count, BufferDescriptor const& type)
{
UNIMPLEMENTED ("pre-register storage for buffers of a specific kind");
}
virtual BuffHandle
lockBufferFor (BufferDescriptor const& descriptor)
{
UNIMPLEMENTED ("lock buffer for exclusive use");
}
virtual void
releaseBuffer (BuffHandle const& handle)
{
UNIMPLEMENTED ("release a buffer and invalidate the handle");
}
public:
HeapMemProvider()
: BufferProvider ("Diagnostic_HeapAllocated")
{ }
virtual ~HeapMemProvider()
{
INFO (proc_mem, "discarding %zu diagnostic buffer entries", HeapMemProvider::size());
}
Block&
access_or_create (uint bufferID)
{
while (!withinStorageSize (bufferID))
manage (new Block);
ENSURE (withinStorageSize (bufferID));
return (*this)[bufferID];
}
private:
bool
withinStorageSize (uint bufferID) const
{
if (bufferID >= MAX_BUFFERS)
throw error::Fatal ("hardwired internal limit for test buffers exceeded");
return bufferID < size();
}
};
DiagnosticBufferProvider::DiagnosticBufferProvider()
: pImpl_() //////////TODO create PImpl here
: pImpl_()
{ }
DiagnosticBufferProvider::~DiagnosticBufferProvider() { }
@ -174,13 +61,13 @@ namespace engine {
return diagnostics();
}
DiagnosticBufferProvider::HeapMemProvider&
TrackingHeapBlockProvider&
DiagnosticBufferProvider::reset()
{
pImpl_.reset(new HeapMemProvider());
pImpl_.reset(new TrackingHeapBlockProvider());
return *pImpl_;
}
@ -189,9 +76,9 @@ namespace engine {
{
return &implInstance == pImpl_.get();
}
/* === diagnostic API === */
@ -199,21 +86,21 @@ namespace engine {
bool
DiagnosticBufferProvider::buffer_was_used (uint bufferID) const
{
return pImpl_->access_or_create(bufferID).was_used();
return pImpl_->access_emitted(bufferID).was_used();
}
bool
DiagnosticBufferProvider::buffer_was_closed (uint bufferID) const
{
return pImpl_->access_or_create(bufferID).was_closed();
return pImpl_->access_emitted(bufferID).was_closed();
}
void*
DiagnosticBufferProvider::accessMemory (uint bufferID) const
{
return pImpl_->access_or_create(bufferID).accessMemory();
return pImpl_->access_emitted(bufferID).accessMemory();
}

View file

@ -21,7 +21,7 @@
*/
/** @file diagnostic-buffer-provider.hpp
** An facility for writing unit-tests targetting the BufferProvider interface.
** An facility for writing unit-tests targeting the BufferProvider interface.
**
** @see buffer-provider-protocol-test.cpp
*/
@ -33,6 +33,7 @@
#include "lib/error.hpp"
#include "lib/singleton.hpp"
#include "lib/util.hpp"
#include "proc/engine/type-handler.hpp"
#include "proc/engine/buffer-provider.hpp"
#include <boost/scoped_ptr.hpp>
@ -44,6 +45,13 @@ namespace engine {
namespace error = lumiera::error;
/**
* simple BufferProvider implementation
* with additional allocation tracking
*/
class TrackingHeapBlockProvider;
/********************************************************************
* Helper for unit tests: Buffer provider reference implementation.
*
@ -53,18 +61,11 @@ namespace engine {
: boost::noncopyable
{
/**
* simple BufferProvider implementation
* with additional allocation tracking
*/
class HeapMemProvider;
boost::scoped_ptr<HeapMemProvider> pImpl_;
boost::scoped_ptr<TrackingHeapBlockProvider> pImpl_;
static lib::Singleton<DiagnosticBufferProvider> diagnostics;
HeapMemProvider& reset();
TrackingHeapBlockProvider& reset();
bool isCurrent (BufferProvider const&);
@ -98,22 +99,6 @@ namespace engine {
bool all_buffers_released() const;
template<typename BU>
bool
object_was_attached (uint bufferID) const
{
UNIMPLEMENTED ("verify object attachment status of a specific buffer");
}
template<typename BU>
bool
object_was_destroyed (uint bufferID) const
{
UNIMPLEMENTED ("verify object attachment status of a specific buffer");
}
private:

View file

@ -61,7 +61,7 @@ namespace engine{
CalcStream
EngineService::calculate(ModelPort mPort,
Timings nominalTimings,
OutputConnection output,
OutputConnection& output,
Quality serviceQuality)
{
UNIMPLEMENTED ("build a standard calculation stream");

View file

@ -141,7 +141,7 @@ namespace engine{
CalcStream
calculate(ModelPort mPort,
Timings nominalTimings,
OutputConnection output,
OutputConnection& output,
Quality serviceQuality =QoS_DEFAULT);
CalcStream

View file

@ -55,7 +55,7 @@
#include "proc/state.hpp"
#include "proc/engine/procnode.hpp"
#include "proc/engine/buffhandle.hpp"
#include "proc/engine/channel-descriptor.hpp"
#include "proc/engine/bufftable-obsolete.hpp"

View file

@ -59,7 +59,7 @@
#include "proc/state.hpp"
#include "proc/engine/procnode.hpp"
#include "proc/engine/buffhandle.hpp"
#include "proc/engine/channel-descriptor.hpp"
#include "proc/engine/bufftable-obsolete.hpp"
#include "proc/engine/nodeinvocation.hpp"

View file

@ -46,6 +46,7 @@
#include "proc/state.hpp"
#include "proc/asset/proc.hpp"
#include "proc/mobject/parameter.hpp"
#include "proc/engine/channel-descriptor.hpp"
#include "lib/frameid.hpp"
#include "lib/ref-array.hpp"

View file

@ -0,0 +1,325 @@
/*
TrackingHeapBlockProvider - plain heap allocating BufferProvider implementation for tests
Copyright (C) Lumiera.org
2011, 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.
* *****************************************************/
#include "lib/error.hpp"
#include "include/logging.h"
//#include "lib/meta/function.hpp"
#include "lib/bool-checkable.hpp"
#include "lib/scoped-ptrvect.hpp"
#include "lib/scoped-holder.hpp"
#include "lib/util-foreach.hpp"
#include "proc/engine/tracking-heap-block-provider.hpp"
#include <boost/noncopyable.hpp>
#include <algorithm>
#include <vector>
//using util::for_each;
using util::and_all;
using std::vector;
using lib::ScopedHolder;
using lib::ScopedPtrVect;
namespace engine {
namespace error = lumiera::error;
namespace { // implementation helpers...
using diagn::Block;
/** helper to find Block entries
* based on their raw memory address */
inline bool
identifyBlock (Block const& inQuestion, void* storage)
{
return storage == &inQuestion;
}
/** build a searching predicate */
inline function<bool(Block const&)>
search_for_block_using_this_storage (void* storage)
{
return bind (identifyBlock, _1, storage);
}
template<class VEC>
inline Block*
pick_Block_by_storage (VEC& vec, void* blockLocation)
{
typename VEC::iterator pos
= std::find_if (vec.begin(),vec.end()
,search_for_block_using_this_storage(blockLocation));
if (pos!=vec.end())
return &(*pos);
else
return NULL;
}
}
namespace diagn {
typedef ScopedPtrVect<Block> PoolVec;
typedef ScopedHolder<PoolVec> PoolHolder;
/**
* @internal Pool of allocated buffer Blocks of a specific size.
* Helper for implementing a Diagnostic BufferProvider; actually does
* just heap allocations for the Blocks, but keeps a collection of
* allocated Blocks around. Individual entries can be retrieved
* and thus removed from the responsibility of BlockPool.
*
* The idea is that each buffer starts its lifecycle within some pool
* and later gets "emitted" to an output sequence, where it remains for
* later investigation and diagnostics.
*/
class BlockPool
: public lib::BoolCheckable<BlockPool>
{
uint maxAllocCount_;
size_t memBlockSize_;
PoolHolder blockList_;
public:
BlockPool()
: maxAllocCount_(0) // unlimited by default
, memBlockSize_(0)
, blockList_()
{ }
void
initialise (size_t blockSize)
{
blockList_.create();
memBlockSize_ = blockSize;
}
// standard copy operations are valid, but will
// raise an runtime error, once BlockPool is initialised.
~BlockPool()
{
if (!verify_all_children_idle())
ERROR (test, "Block actively in use while shutting down BufferProvider "
"allocation pool. This might lead to Segfault and memory leaks.");
}
uint
prepare_for (uint number_of_expected_buffers)
{
if (maxAllocCount_ &&
maxAllocCount_ < blockList_->size() + number_of_expected_buffers)
{
ASSERT (maxAllocCount_ >= blockList_->size());
return maxAllocCount_ - blockList_->size();
}
// currently no hard limit imposed
return number_of_expected_buffers;
}
Block&
createBlock()
{
return blockList_->manage (new Block(memBlockSize_));
}
Block*
find (void* blockLocation)
{
return pick_Block_by_storage (*blockList_, blockLocation);
}
Block*
transferResponsibility (Block* allocatedBlock)
{
return blockList_->detach (allocatedBlock);
}
size_t
size() const
{
return blockList_->size();
}
bool
isValid() const
{
return blockList_;
}
private:
bool
verify_all_children_idle()
{
try {
if (blockList_)
return and_all (*blockList_, is_in_sane_state);
}
ERROR_LOG_AND_IGNORE (test, "State verification of diagnostic BufferProvider allocation pool");
return true;
}
static bool
is_in_sane_state (Block const& block)
{
return !block.was_used()
|| block.was_closed();
}
};
}
namespace { // Details of allocation and accounting
const uint MAX_BUFFERS = 50;
diagn::Block emptyPlaceholder(0);
} // (END) Details of allocation and accounting
/**
* @internal create a memory tracking BufferProvider,
*/
TrackingHeapBlockProvider::TrackingHeapBlockProvider()
: BufferProvider ("Diagnostic_HeapAllocated")
, pool_(new diagn::PoolTable)
, outSeq_()
{ }
TrackingHeapBlockProvider::~TrackingHeapBlockProvider()
{
INFO (proc_mem, "discarding %zu diagnostic buffer entries", outSeq_.size());
}
/* ==== Implementation of the BufferProvider interface ==== */
uint
TrackingHeapBlockProvider::prepareBuffers(uint requestedAmount, HashVal typeID)
{
diagn::BlockPool& responsiblePool = getBlockPoolFor (typeID);
return responsiblePool.prepare_for (requestedAmount);
}
BuffHandle
TrackingHeapBlockProvider::provideLockedBuffer(HashVal typeID)
{
diagn::BlockPool& blocks = getBlockPoolFor (typeID);
diagn::Block& newBlock = blocks.createBlock();
return buildHandle (typeID, newBlock.accessMemory(), &newBlock);
}
void
TrackingHeapBlockProvider::mark_emitted (HashVal typeID, LocalKey const& implID)
{
diagn::Block* block4buffer = locateBlock (typeID, implID);
if (!block4buffer)
throw error::Logic ("Attempt to emit a buffer not known to this BufferProvider"
, LUMIERA_ERROR_BUFFER_MANAGEMENT);
diagn::BlockPool& pool = getBlockPoolFor (typeID);
outSeq_.manage (pool.transferResponsibility (block4buffer));
}
/** mark a buffer as officially discarded */
void
TrackingHeapBlockProvider::detachBuffer (HashVal typeID, LocalKey const& implID)
{
diagn::Block* block4buffer = locateBlock (typeID, implID);
REQUIRE (block4buffer, "releasing a buffer not allocated through this provider");
block4buffer->markReleased();
}
/* ==== Implementation details ==== */
size_t
TrackingHeapBlockProvider::emittedCnt() const
{
return outSeq_.size();
}
diagn::Block&
TrackingHeapBlockProvider::access_emitted (uint bufferID)
{
if (!withinOutputSequence (bufferID))
return emptyPlaceholder;
else
return outSeq_[bufferID];
}
bool
TrackingHeapBlockProvider::withinOutputSequence (uint bufferID) const
{
if (bufferID >= MAX_BUFFERS)
throw error::Fatal ("hardwired internal limit for test buffers exceeded");
return bufferID < outSeq_.size();
}
diagn::BlockPool&
TrackingHeapBlockProvider::getBlockPoolFor (HashVal typeID)
{
diagn::BlockPool& pool = (*pool_)[typeID];
if (!pool)
pool.initialise(getBufferSize(typeID));
return pool;
}
diagn::Block*
TrackingHeapBlockProvider::locateBlock (HashVal typeID, void* storage)
{
diagn::BlockPool& pool = getBlockPoolFor (typeID);
diagn::Block* block4buffer = pool.find (storage);
return block4buffer? block4buffer
: searchInOutSeqeuence (storage);
}
diagn::Block*
TrackingHeapBlockProvider::searchInOutSeqeuence (void* blockLocation)
{
return pick_Block_by_storage (outSeq_, blockLocation);
}
} // namespace engine

View file

@ -0,0 +1,187 @@
/*
TRACKING-HEAP-BLOCK-PROVIDER.hpp - plain heap allocating BufferProvider implementation for tests
Copyright (C) Lumiera.org
2011, 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 tracking-heap-block-provider.hpp
** Dummy implementation of the BufferProvider interface to support writing unit tests.
** This BufferProvider is especially straight forward and brain dead: it just claims
** more and more heap blocks and never releases any memory dynamically. This allows
** to investigate additional tracking status flags for each allocated block after
** the fact.
**
** The allocated buffers are numbered with a simple ascending sequence of integers,
** used as LocalKey (see BufferMetadata). Clients can just request a Buffer with the
** given number, causing that block to be allocated. There is a "backdoor", allowing
** to access any allocated block, even if it is considered "released" by the terms
** of the usual lifecycle. Only when the provider object itself gets destroyed,
** all allocated blocks will be discarded.
**
** @see DiagnosticOutputSlot
** @see DiagnosticBufferProvider
** @see buffer-provider-protocol-test.cpp
*/
#ifndef PROC_ENGINE_TRACKING_HEAP_BLOCK_PROVIDER_H
#define PROC_ENGINE_TRACKING_HEAP_BLOCK_PROVIDER_H
#include "lib/error.hpp"
#include "proc/engine/buffer-provider.hpp"
#include "lib/scoped-ptrvect.hpp"
#include <tr1/unordered_map>
#include <boost/scoped_ptr.hpp>
#include <boost/scoped_array.hpp>
namespace engine {
namespace error = lumiera::error;
using lib::ScopedPtrVect;
namespace diagn {
using boost::scoped_ptr;
using boost::scoped_array;
/**
* Helper for a diagnostic BufferProvider:
* A block of heap allocated storage, with the capability
* to store some additional tracking information.
*/
class Block
: boost::noncopyable
{
scoped_array<char> storage_;
bool was_released_;
public:
explicit
Block(size_t bufferSize)
: storage_(bufferSize? new char[bufferSize] : NULL)
, was_released_(false)
{ }
bool
was_used() const
{
return bool(storage_);
}
bool
was_closed() const
{
return was_released_;
}
void*
accessMemory() const
{
REQUIRE (storage_, "Block was never prepared for use");
return storage_.get();
}
void
markReleased()
{
was_released_ = true;
}
};
class BlockPool;
typedef std::tr1::unordered_map<HashVal,BlockPool> PoolTable;
}
/**
* simple BufferProvider implementation with additional allocation tracking.
* @internal used as PImpl by DiagnosticBufferProvider and DiagnosticOutputSlot.
*
* This dummy implementation of the BufferProvider interface uses a linearly growing
* table of heap allocated buffer blocks, which will never be discarded, unless the object
* is discarded as a whole. There is an additional testing/diagnostics API to access the
* tracked usage information, even when blocks are already marked as "released".
*/
class TrackingHeapBlockProvider
: public BufferProvider
{
scoped_ptr<diagn::PoolTable> pool_;
ScopedPtrVect<diagn::Block> outSeq_;
public:
/* === BufferProvider interface === */
virtual uint prepareBuffers (uint count, HashVal typeID);
virtual BuffHandle provideLockedBuffer (HashVal typeID);
virtual void mark_emitted (HashVal entryID, LocalKey const&);
virtual void detachBuffer (HashVal entryID, LocalKey const&);
public:
TrackingHeapBlockProvider();
virtual ~TrackingHeapBlockProvider();
size_t emittedCnt() const;
diagn::Block& access_emitted (uint bufferID);
template<typename TY>
TY& accessAs (uint bufferID);
private:
bool withinOutputSequence (uint bufferID) const;
diagn::BlockPool& getBlockPoolFor (HashVal typeID);
diagn::Block* locateBlock (HashVal typeID, void*);
diagn::Block* searchInOutSeqeuence (void* storage);
};
/** convenience shortcut: access the buffer with the given number,
* then try to convert the raw memory to the templated type.
* @throw error::Invalid if the required fame number is beyond
* the number of buffers marked as "emitted"
* @throw error::Fatal if conversion is not possible or the
* conversion path chosen doesn't work (which might
* be due to RTTI indicating an incompatible type).
*/
template<typename TY>
TY&
TrackingHeapBlockProvider::accessAs (uint bufferID)
{
if (!withinOutputSequence (bufferID))
throw error::Invalid ("Buffer with the given ID not yet emitted");
diagn::Block& memoryBlock = access_emitted (bufferID);
TY* converted = reinterpret_cast<TY*> (memoryBlock.accessMemory());
REQUIRE (converted);
return *converted;
}
} // namespace engine
#endif

View file

@ -0,0 +1,182 @@
/*
TYPE-HANDLER.hpp - a functor pair for setup and destruction
Copyright (C) Lumiera.org
2011, 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 type-handler.hpp
** Helper holding a pair of type-build-up and destruction functors.
** Basically these two functors embody all type specific knowledge required
** to place an object into some buffer space and to clean up later. They may even
** be used in a more unspecific way, e.g. just to "prepare" a buffer or frame and to
** "clean up" after usage.
**
** Within the Lumiera Engine, the BufferProvider default implementation utilises instances
** of TypeHandler to \em describe specific buffer types capable of managing an attached object,
** or requiring some other kind of special treatment of the memory area used for the buffer.
** This BufferDescriptor is embodied into the BufferMetadata::Key and used later on to invoke
** the contained ctor / dtor functors, passing a concrete buffer (memory area).
**
** @see buffer-metadata.hpp
** @see buffer-provider.hpp
** @see BufferMetadataKey_test#verifyTypeHandler unit-test
*/
#ifndef PROC_ENGINE_TYPE_HANDLER_H
#define PROC_ENGINE_TYPE_HANDLER_H
#include "lib/error.hpp"
#include "lib/functor-util.hpp"
#include <tr1/functional>
#include <boost/functional/hash.hpp>
namespace engine {
using lib::HashVal;
using std::tr1::bind;
using std::tr1::function;
using std::tr1::placeholders::_1;
namespace error = lumiera::error;
namespace { // (optional) helpers to build an object embedded into a buffer...
template<class X>
inline void
buildIntoBuffer (void* storageBuffer)
{
new(storageBuffer) X();
}
template<class X, typename A1>
inline void
buildIntoBuffer_A1 (void* storageBuffer, A1 arg1)
{
new(storageBuffer) X(arg1);
}
template<class X>
inline void
destroyInBuffer (void* storageBuffer)
{
X* embedded = static_cast<X*> (storageBuffer);
embedded->~X();
}
}//(End)placement-new helpers
/**
* A pair of functors to maintain a datastructure within a buffer.
* TypeHandler describes how to outfit the buffer in a specific way.
* Special convenience builder function(s) are provided to create a
* TypeHandler performing placement-new into a buffer given on invocation.
* @note engine::BufferMetadata uses a TypeHandler to represent any
* special treatment of a buffer space. When defined, the buffer
* will be prepared on locking and cleanup will be invoked
* automatically when releasing.
* @warning comparison and hash values rely on internals of the
* tr1::function implementation and might not be 100% accurate
*/
struct TypeHandler
{
typedef function<void(void*)> DoInBuffer;
DoInBuffer createAttached;
DoInBuffer destroyAttached;
/** build an invalid NIL TypeHandler */
TypeHandler()
: createAttached()
, destroyAttached()
{ }
/** build a TypeHandler
* binding to arbitrary constructor and destructor functions.
* On invocation, these functions get a void* to the buffer.
* @note the functor objects created from these operations
* might be shared for handling multiple buffers.
* Be careful with any state or arguments.
*/
template<typename CTOR, typename DTOR>
TypeHandler(CTOR ctor, DTOR dtor)
: createAttached (ctor)
, destroyAttached (dtor)
{ }
/** builder function defining a TypeHandler
* to place a default-constructed object
* into the buffer. */
template<class X>
static TypeHandler
create ()
{
return TypeHandler (buildIntoBuffer<X>, destroyInBuffer<X>);
}
template<class X, typename A1>
static TypeHandler
create (A1 a1)
{
return TypeHandler ( bind (buildIntoBuffer_A1<X,A1>, _1, a1)
, destroyInBuffer<X>);
}
bool
isValid() const
{
return bool(createAttached)
&& bool(destroyAttached);
}
friend HashVal
hash_value (TypeHandler const& handler)
{
HashVal hash(0);
if (handler.isValid())
{
boost::hash_combine(hash, handler.createAttached);
boost::hash_combine(hash, handler.destroyAttached);
}
return hash;
}
friend bool
operator== (TypeHandler const& left, TypeHandler const& right)
{
return (!left.isValid() && !right.isValid())
|| ( util::rawComparison(left.createAttached, right.createAttached)
&& util::rawComparison(left.destroyAttached, right.destroyAttached)
);
}
friend bool
operator!= (TypeHandler const& left, TypeHandler const& right)
{
return !(left == right);
}
};
} // namespace engine
#endif

View file

@ -90,7 +90,7 @@ namespace test {
string
display(Symbol name) const
{
static format fmt("%s(ID=%4d)");
static format fmt("%s(ID=%03d)");
return boost::str(fmt % name % this->id_);
}
};

View file

@ -0,0 +1,212 @@
/*
OUTPUT-SLOT-CONNECTION.hpp - implementation API for concrete output slots
Copyright (C) Lumiera.org
2011, 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 output-slot-connection.hpp
** Interface for concrete output implementations to talk to the OutputSlot frontend.
** The OutputSlot concept helps to decouple the render engine implementation from the details
** of handling external output connections. For this to work, a concrete implementation of such
** an external output needs to integrate with the generic OutputSlot frontend, as used by the
** engine. This generic frontend uses a PImpl, pointing to a ConnectionState object, which embodies
** the actual implementation. Moreover, this actual implementation is free to use specifically crafted
** OutputSlot::Connection elements to handle the ongoing output for individual channels. The latter
** thus becomes the central implementation side API for providing actual output capabilities.
**
** @see OutputSlotProtocol_test
** @see diagnostic-output-slot.hpp ////TODO
*/
#ifndef PROC_PLAY_OUTPUT_SLOT_CONNECTION_H
#define PROC_PLAY_OUTPUT_SLOT_CONNECTION_H
#include "lib/error.hpp"
#include "proc/play/output-slot.hpp"
#include "lib/handle.hpp"
//#include "lib/time/timevalue.hpp"
//#include "proc/engine/buffer-provider.hpp"
//#include "proc/play/timings.hpp"
#include "lib/iter-source.hpp"
#include "lib/iter-adapter-stl.hpp"
//#include "lib/sync.hpp"
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
//#include <string>
//#include <vector>
//#include <tr1/memory>
namespace proc {
namespace play {
using ::engine::BuffHandle;
//using ::engine::BufferProvider;
//using lib::time::Time;
//using std::string;
using lib::transform;
using lib::iter_stl::eachElm;
//using std::vector;
//using std::tr1::shared_ptr;
using boost::scoped_ptr;
/** @internal represents the \em active
* point in each of the per-channel connections
* used when this OutputSlot is operational.
*
* \par OutputSlot Core API
*
* Actually, this extension point towards the implementation
* of the actual output handling carries the core API of OutputSlot.
* Thus, the task of actually implementing an OutputSlot boils down
* to implementing this interface and providing a ConnectionState.
* - \c lock() announces this FrameID and the corresponding buffer
* to be in exclusive use by the client from now on
* - \c transfer() ends the client sided processing and initiates
* the outputting of the data found in the corresponding buffer.
* - \c pushout() actually pushes the denoted buffer to the output.
* Typically, \c pushout() is called from the \c transfer()
* implementation; yet it may as well be called from a separate
* service thread or some kind of callback.
* @note the meaning of FrameID is implementation defined.
*/
class OutputSlot::Connection
{
public:
virtual ~Connection();
virtual BuffHandle claimBufferFor(FrameID) =0;
virtual bool isTimely (FrameID, TimeValue) =0;
virtual void transfer (BuffHandle const&) =0;
virtual void pushout (BuffHandle const&) =0;
virtual void discard (BuffHandle const&) =0;
virtual void shutDown () =0;
};
/**
* Extension point for Implementation.
* The ConnectionState is where the concrete output
* handling implementation is expected to reside.
* OutputSlot is a frontend and accesses
* ConnectionState in the way of a PImpl.
*/
class OutputSlot::ConnectionState
: public OutputSlot::Allocation
, boost::noncopyable
{
public:
virtual ~ConnectionState() { }
};
/**
* Base class for the typical implementation approach.
* Using this class is \em not mandatory. But obviously,
* we'd get to manage a selection of Connection objects
* representing the "active points" in several media channels
* connected through this OutputSlot. These Connection subclasses
* are what is referenced by the DataSink smart-ptrs handed out
* to the client code. As ConnectionState implements the Allocation
* API, it has the liability to create these DataSink smart-ptrs,
* which means to wire them appropriately and also provide an
* deleter function (here #shutdownConnection) to be invoked
* when the last copy of the smart-handle goes out of scope.
*
* The typical standard/base implementation provided here
* manages a collection of active Connection subclass objects.
*/
template<class CON>
class ConnectionStateManager
: public OutputSlot::ConnectionState
, public vector<CON>
{
typedef OutputSlot::OpenedSinks OpenedSinks;
/* == Allocation Interface == */
OpenedSinks
getOpenedSinks()
{
REQUIRE (this->isActive());
return transform (eachElm(*this), connectOutputSink);
}
bool
isActive()
{
return 0 < vector<CON>::size();
}
public:
ConnectionStateManager()
{ }
virtual
~ConnectionStateManager()
{ }
void
init (uint numChannels)
{
for (uint i=0; i<numChannels; ++i)
push_back(buildConnection());
}
/** factory function to build the actual
* connection handling objects per channel */
virtual CON buildConnection() =0;
private: // Implementation details
static DataSink
connectOutputSink (CON& connection)
{
DataSink newSink;
newSink.activate(&connection, shutdownConnection);
return newSink;
}
static void
shutdownConnection (OutputSlot::Connection* toClose)
{
REQUIRE (toClose);
toClose->shutDown();
}
};
}} // namespace proc::play
#endif

View file

@ -21,13 +21,21 @@
* *****************************************************/
#include "lib/error.hpp"
#include "proc/play/output-slot.hpp"
#include "proc/play/output-slot-connection.hpp"
#include <boost/noncopyable.hpp>
#include <vector>
namespace proc {
namespace play {
using std::vector;
namespace error = lumiera::error;
namespace { // hidden local details of the service implementation....
@ -36,8 +44,17 @@ namespace play {
OutputSlot::~OutputSlot() { } // emit VTables here....
OutputSlot::Allocation::~Allocation() { }
OutputSlot::Connection::~Connection() { }
/** whether this output slot is occupied
@ -47,9 +64,53 @@ namespace play {
bool
OutputSlot::isFree() const
{
UNIMPLEMENTED ("connection state");
return ! this->state_;
}
/** */
OutputSlot::Allocation&
OutputSlot::allocate()
{
if (!isFree())
throw error::Logic ("Attempt to open/allocate an OutputSlot already in use.");
UNIMPLEMENTED ("internal interface to determine the number of channel-connections");
state_.reset (this->buildState());
return *state_;
}
void
OutputSlot::disconnect()
{
if (!isFree())
state_.reset(0);
}
/* === DataSink frontend === */
BuffHandle
DataSink::lockBufferFor(FrameID frameNr)
{
return impl().claimBufferFor(frameNr);
}
void
DataSink::emit (FrameID frameNr, BuffHandle const& data2emit, TimeValue currentTime)
{
OutputSlot::Connection& connection = impl();
if (connection.isTimely(frameNr,currentTime))
connection.transfer(data2emit);
else
connection.discard(data2emit);
}

View file

@ -45,10 +45,10 @@
//#include "lib/sync.hpp"
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
//#include <string>
//#include <vector>
//#include <tr1/memory>
//#include <boost/scoped_ptr.hpp>
namespace proc {
@ -56,29 +56,18 @@ namespace play {
using ::engine::BuffHandle;
using ::engine::BufferProvider;
using lib::time::Time;
using lib::time::TimeValue;
//using std::string;
//using std::vector;
//using std::tr1::shared_ptr;
//using boost::scoped_ptr;
using boost::scoped_ptr;
/** established output channel */
class Connection;
typedef int64_t FrameNr;
class DataSink
: public lib::Handle<Connection>
{
public:
BuffHandle lockBufferFor(FrameNr);
void emit(FrameNr);
};
class DataSink;
typedef int64_t FrameID;
@ -90,32 +79,46 @@ namespace play {
class OutputSlot
: boost::noncopyable
{
protected:
/** Table to maintain connection state */
class ConnectionState;
scoped_ptr<ConnectionState> state_;
virtual ConnectionState* buildState() =0;
public:
virtual ~OutputSlot();
typedef lib::IterSource<DataSink>::iterator OpenedSinks;
struct Allocation
class Allocation
{
OpenedSinks getOpenedSinks();
bool isActive();
public:
virtual OpenedSinks getOpenedSinks() =0;
virtual bool isActive() =0;
/////TODO add here the getters for timing constraints
protected:
~Allocation();
};
/** established output channel */
class Connection;
/** can this OutputSlot be allocated? */
bool isFree() const;
Allocation
allocate();
/** claim this slot for exclusive use */
Allocation& allocate();
protected:
friend class DataSink;
virtual void lock (FrameNr, uint channel) =0;
virtual void transfer (FrameNr, uint channel) =0;
virtual void pushout (FrameNr, uint channel) =0;
/** disconnect from this OutputSlot
* @warning may block until DataSinks are gone */
void disconnect();
private:
@ -123,5 +126,18 @@ namespace play {
class DataSink
: public lib::Handle<OutputSlot::Connection>
{
public:
BuffHandle lockBufferFor(FrameID);
void emit(FrameID, BuffHandle const&, TimeValue currentTime = Time::MAX); ///////////////TICKET #855
};
}} // namespace proc::play
#endif

View file

@ -20,53 +20,31 @@
// 1/11 - integer floor and wrap operation(s)
// 1/11 - how to fetch the path of the own executable -- at least under Linux?
// 10/11 - simple demo using a pointer and a struct
// 11/11 - using the boost random number generator(s)
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/random/linear_congruential.hpp>
using boost::lexical_cast;
using std::cout;
/**
* custom datastructure
* holding a constant char array with "hey"
*/
struct MyStruct
{
char data_[3];
const int length_;
MyStruct()
: length_(3)
{
const char *tmp = "hey";
for (int i=0; i<length_; ++i)
data_[i] = *(tmp+i);
}
};
// define a global variable holding a MyStruct
MyStruct theStruct;
void
printMyStruct(MyStruct* myPointer)
{
for (int i=0; i < myPointer->length_; ++i)
cout << myPointer->data_[i];
cout << "\n";
}
using std::endl;
int
main (int, char**) //(int argc, char* argv[])
main (int cnt, char* argv[])
{
printMyStruct (&theStruct);
int32_t seed = (2 == cnt)? lexical_cast<int32_t> (argv[1]) : 42;
boost::rand48 ranGen(seed);
cout << "seed = "<< seed << endl;
for (uint i=0; i< 100; ++i)
cout << ranGen() % CHAR_MAX <<"__";
cout << "\n.gulp.\n";

View file

@ -96,9 +96,9 @@ out: Placement.+mobject.+test.+TestSubMO1.+. use-cnt=1 .*
out: Placement.+mobject.+test.+TestSubMO2.+. use-cnt=1 .*
out: Placement.+mobject.+test.+TestSubMO21.+ use-cnt=2 .*
out: Placement.+mobject.+session.+Clip.+.... use-cnt=2 .*
out: TestSubMO1\(ID= [0-9]{3}\)
out: TestSubMO2\(ID= [0-9]{3}\)
out: TestSubMO21\(ID= [0-9]{3}\)
out: TestSubMO1\(ID=[0-9]{3}\)
out: TestSubMO2\(ID=[0-9]{3}\)
out: TestSubMO21\(ID=[0-9]{3}\)
out: specialAPI()
out: pID\(\w{6,16}\)
END

View file

@ -2,16 +2,27 @@ TESTING "Component Test Suite: Render Engine parts" ./test-components --group=en
PLANNED "Buffer provider diagnostics" BufferProviderProtocol_test <<END
TEST "Test support: dummy frames" TestFrame_test <<END
return: 0
END
PLANNED "buffer metadata type keys" BufferMetadataKey_test <<END
TEST "Test support: dummy buffer provider" TrackingHeapBlockProvider_test <<END
return: 0
END
PLANNED "buffer metadata and state transitions" BufferMetadata_test <<END
TEST "Buffer provider diagnostics" BufferProviderProtocol_test <<END
return: 0
END
TEST "buffer metadata type keys" BufferMetadataKey_test <<END
return: 0
END
TEST "buffer metadata and state transitions" BufferMetadata_test <<END
END

View file

@ -52,7 +52,7 @@ namespace asset {
* or resort to template metaprogramming tricks.
* Just providing templated comparison operators
* would generally override the behaviour of
* boost::shared_ptr, which is not desirable.
* std::shared_ptr, which is not desirable.
* @see Asset::Ident#compare
*/
class OrderingOfAssets_test : public Test

View file

@ -167,6 +167,13 @@ namespace test {
// serialise, then de-serialise into a new instance and compare both
}
int
twoRandomDigits()
{
return 10 + rand() % 90;
}
} // test-helper implementation
@ -244,7 +251,7 @@ namespace test {
arg3->storeTuple (tuple::make (rand() % 10, TimeVar(randTime())));
arg4->storeTuple (tuple::make (rand() % 10, TimeVar(randTime())));
arg5->storeTuple (tuple::make (TTime (randTime()), Tstr("glorious"), 10 + rand() % 90));
arg5->storeTuple (tuple::make (TTime (randTime()), Tstr("glorious"), twoRandomDigits() ));
CHECK (!arg5->canUndo());
@ -333,7 +340,7 @@ namespace test {
// store a set of parameter values, later to be used on invocation
args.storeTuple (
tuple::make (TTime(randTime()), Tstr("Lumiera rocks"), rand() % 100));
tuple::make (TTime(randTime()), Tstr("Lumiera rocks"), twoRandomDigits() ));
CHECK (!isnil (args));
cout << args << endl;
@ -377,7 +384,7 @@ namespace test {
protocol << "RESET...";
args.storeTuple (
tuple::make (TTime(TimeValue(123456)), Tstr("unbelievable"), rand() %100));
tuple::make (TTime(TimeValue(123456)), Tstr("unbelievable"), twoRandomDigits() ));
cout << "modified: " << args << endl;
cout << "copied : " << argsCopy << endl; // holds still the old params & memento

View file

@ -46,7 +46,7 @@ namespace test {
using boost::str;
using std::tr1::function;
using std::tr1::bind;
using boost::ref;
using std::tr1::ref;
using boost::lexical_cast;
using util::contains;

View file

@ -24,37 +24,25 @@
#include "lib/error.hpp"
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
//#include "lib/util-foreach.hpp"
#include "lib/util.hpp"
//#include "proc/play/diagnostic-output-slot.hpp"
//#include "proc/engine/testframe.hpp"
//#include "proc/engine/diagnostic-buffer-provider.hpp"
#include "proc/engine/buffer-metadata.hpp"
#include "proc/engine/testframe.hpp"
//#include "proc/engine/buffhandle.hpp"
//#include "proc/engine/bufftable.hpp"
//#include <boost/format.hpp>
#include <boost/scoped_ptr.hpp>
#include <cstdlib>
//#include <iostream>
#include <cstring>
//using boost::format;
//using std::string;
//using std::cout;
//using util::for_each;
using std::strncpy;
using boost::scoped_ptr;
using util::isnil;
using lib::test::randStr;
using util::isSameObject;
using util::isnil;
namespace engine{
namespace test {
// using lib::AllocationCluster;
// using mobject::session::PEffect;
// using ::engine::BuffHandle;
using lumiera::error::LUMIERA_ERROR_FATAL;
using lumiera::error::LUMIERA_ERROR_INVALID;
using lumiera::error::LUMIERA_ERROR_LIFECYCLE;
@ -66,13 +54,23 @@ namespace test {
const size_t SIZE_A = 1 + rand() % TEST_MAX_SIZE;
const size_t SIZE_B = 1 + rand() % TEST_MAX_SIZE;
const HashVal JUST_SOMETHING = 123;
const void* const SOME_POINTER = &JUST_SOMETHING;
// const uint TEST_SIZE = 1024*1024;
// const uint TEST_ELMS = 20;
HashVal JUST_SOMETHING = 123;
void* const SOME_POINTER = &JUST_SOMETHING;
}
template<typename TY>
TY&
accessAs (metadata::Entry& entry)
{
TY* ptr = reinterpret_cast<TY*> (entry.access());
ASSERT (ptr);
return *ptr;
}
}//(End) Test fixture and helpers
/*******************************************************************
@ -83,7 +81,7 @@ namespace test {
class BufferMetadata_test : public Test
{
/** common Metadata table to be tested */
scoped_ptr<Metadata> meta_;
scoped_ptr<BufferMetadata> meta_;
virtual void
run (Arg)
@ -91,7 +89,7 @@ namespace test {
CHECK (ensure_proper_fixture());
verifyBasicProperties();
verifyStandardCase();
UNIMPLEMENTED ("cover all metadata properties");
verifyStateMachine();
}
@ -99,7 +97,7 @@ namespace test {
ensure_proper_fixture()
{
if (!meta_)
meta_.reset(new Metadata("BufferMetadata_test"));
meta_.reset(new BufferMetadata("BufferMetadata_test"));
return (SIZE_A != SIZE_B)
&& (JUST_SOMETHING != meta_->key(SIZE_A))
@ -112,11 +110,11 @@ namespace test {
verifyBasicProperties()
{
// retrieve some type keys
Metadata::Key key = meta_->key(SIZE_A);
metadata::Key key = meta_->key(SIZE_A);
CHECK (key);
Metadata::Key key1 = meta_->key(SIZE_A);
Metadata::Key key2 = meta_->key(SIZE_B);
metadata::Key key1 = meta_->key(SIZE_A);
metadata::Key key2 = meta_->key(SIZE_B);
CHECK (key1);
CHECK (key2);
CHECK (key == key1);
@ -133,16 +131,16 @@ namespace test {
CHECK ( isSameObject (meta_->get(key), meta_->get(key1)));
CHECK (!isSameObject (meta_->get(key), meta_->get(key2)));
// entries retrieved this far are inactive (type only) entries
Metadata::Entry& m1 = meta_->get(key);
// entries retrieved thus far were inactive (type only) entries
metadata::Entry& m1 = meta_->get(key);
CHECK (NIL == m1.state());
CHECK (!meta_->isLocked(key));
VERIFY_ERROR (LIFECYCLE, m1.mark(EMITTED) );
VERIFY_ERROR (LIFECYCLE, m1.mark(LOCKED) );
VERIFY_ERROR (LIFECYCLE, m1.mark(EMITTED));
VERIFY_ERROR (LIFECYCLE, m1.mark(FREE) );
// now create an active (buffer) entry
Metadata::Entry& m2 = meta_->markLocked (key, SOME_POINTER);
metadata::Entry& m2 = meta_->markLocked (key, SOME_POINTER);
CHECK (!isSameObject (m1,m2));
CHECK (NIL == m1.state());
CHECK (LOCKED == m2.state());
@ -173,7 +171,7 @@ namespace test {
CHECK ( meta_->isKnown(keyX));
CHECK ( meta_->isKnown(key1));
VERIFY_ERROR (LIFECYCLE, m2.access());
VERIFY_ERROR (LIFECYCLE, m2.mark(LOCKED));
VERIFY_ERROR (FATAL, m2.mark(LOCKED)); // buffer missing
CHECK ( isSameObject (m2, meta_->get(keyX))); // still accessible
// release buffer...
@ -189,18 +187,18 @@ namespace test {
* @note to get the big picture, please refer to
* BufferProviderProtocol_test#verifyStandardCase()
* This testcase here performs precisely the metadata related
* operations necessary to carry out the standard case outlined
* in that more high level test.
* operations necessary to carry out the standard case
* outlined on a higher level in the mentioned test.
*/
void
verifyStandardCase()
{
// to build a descriptor for a buffer holding a TestFrame
TypeHandler attachTestFrame = TypeHandler::create<TestFrame>();
Metadata::Key bufferType1 = meta_->key(sizeof(TestFrame), attachTestFrame);
metadata::Key bufferType1 = meta_->key(sizeof(TestFrame), attachTestFrame);
// to build a descriptor for a raw buffer of size SIZE_B
Metadata::Key rawBuffType = meta_->key(SIZE_B);
metadata::Key rawBuffType = meta_->key(SIZE_B);
// to announce using a number of buffers of this type
LocalKey transaction1(1);
@ -219,23 +217,25 @@ namespace test {
// a real-world BufferProvider would use some kind of allocator
// track individual buffers by metadata entries
Metadata::Entry f0 = meta_->markLocked(bufferType1, &frames[0]);
Metadata::Entry f1 = meta_->markLocked(bufferType1, &frames[1]);
Metadata::Entry f2 = meta_->markLocked(bufferType1, &frames[2]);
metadata::Entry& f0 = meta_->markLocked(bufferType1, &frames[0]);
metadata::Entry& f1 = meta_->markLocked(bufferType1, &frames[1]);
metadata::Entry& f2 = meta_->markLocked(bufferType1, &frames[2]);
metadata::Entry& r0 = meta_->markLocked(rawBuffType, &rawbuf[0]);
metadata::Entry& r1 = meta_->markLocked(rawBuffType, &rawbuf[1]);
Metadata::Entry r0 = meta_->markLocked(bufferType1, &rawbuf[0]);
Metadata::Entry r1 = meta_->markLocked(bufferType1, &rawbuf[1]);
CHECK (LOCKED == f0.state());
CHECK (LOCKED == f1.state());
CHECK (LOCKED == f2.state());
CHECK (LOCKED == r0.state());
CHECK (LOCKED == r1.state());
// for the TestFrame buffers, additionally we'd have to create/attach an object
attachTestFrame.createAttached (frames+0); ////////////////////////////////////////TODO: shouldn't this happen automatically??
attachTestFrame.createAttached (frames+1);
attachTestFrame.createAttached (frames+2);
CHECK (transaction1 == f0.localKey());
CHECK (transaction1 == f1.localKey());
CHECK (transaction1 == f2.localKey());
CHECK (transaction2 == r0.localKey());
CHECK (transaction2 == r1.localKey());
CHECK (f0.access() == frames+0);
CHECK (f1.access() == frames+1);
@ -243,6 +243,11 @@ namespace test {
CHECK (r0.access() == rawbuf+0);
CHECK (r1.access() == rawbuf+1);
TestFrame defaultFrame;
CHECK (defaultFrame == f0.access());
CHECK (defaultFrame == f1.access());
CHECK (defaultFrame == f2.access());
// at that point, we'd return BuffHandles to the client
HashVal handle_f0(f0);
HashVal handle_f1(f1);
@ -250,14 +255,35 @@ namespace test {
HashVal handle_r0(r0);
HashVal handle_r1(r1);
// client uses the buffers
// client uses the buffers---------------------(Start)
accessAs<TestFrame> (f0) = testData(1);
accessAs<TestFrame> (f1) = testData(2);
accessAs<TestFrame> (f2) = testData(3);
//////////////////TODO: access the storage through the metadata-key
//////////////////TODO: to a state transition on the metadata
CHECK (testData(1) == frames[0]);
CHECK (testData(2) == frames[1]);
CHECK (testData(3) == frames[2]);
CHECK (TestFrame::isAlive (f0.access()));
CHECK (TestFrame::isAlive (f1.access()));
CHECK (TestFrame::isAlive (f2.access()));
strncpy (& accessAs<char> (r0), randStr(SIZE_B - 1).c_str(), SIZE_B);
strncpy (& accessAs<char> (r1), randStr(SIZE_B - 1).c_str(), SIZE_B);
// client might trigger some state transitions
f0.mark(EMITTED);
f1.mark(EMITTED);
f1.mark(BLOCKED);
// client uses the buffers---------------------(End)
f0.mark(FREE); // note: implicitly invoking the embedded dtor
f1.mark(FREE);
f2.mark(FREE);
r0.mark(FREE);
r1.mark(FREE);
attachTestFrame.destroyAttached (frames+0); ////////////////////////////////////////TODO: shouldn't this happen automatically??
attachTestFrame.destroyAttached (frames+1);
attachTestFrame.destroyAttached (frames+2);
meta_->release(handle_f0);
meta_->release(handle_f1);
@ -265,14 +291,106 @@ namespace test {
meta_->release(handle_r0);
meta_->release(handle_r1);
CHECK (TestFrame::isDead (&frames[0])); // was destroyed implicitly
CHECK (TestFrame::isDead (&frames[1]));
CHECK (TestFrame::isDead (&frames[2]));
// manual cleanup of test allocations
delete[] frames;
delete[] rawbuf;
CHECK (!meta_->isLocked(handle_f0));
CHECK (!meta_->isLocked(handle_f1));
CHECK (!meta_->isLocked(handle_f2));
CHECK (!meta_->isLocked(handle_r0));
CHECK (!meta_->isLocked(handle_r1));
}
void
verifyStateMachine()
{
// start with building a type key....
metadata::Key key = meta_->key(SIZE_A);
CHECK (NIL == meta_->get(key).state());
CHECK (meta_->get(key).isTypeKey());
CHECK (!meta_->isLocked(key));
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #834
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #834
VERIFY_ERROR (LIFECYCLE, meta_->get(key).mark(LOCKED) );
VERIFY_ERROR (LIFECYCLE, meta_->get(key).mark(EMITTED));
VERIFY_ERROR (LIFECYCLE, meta_->get(key).mark(BLOCKED));
VERIFY_ERROR (LIFECYCLE, meta_->get(key).mark(FREE) );
VERIFY_ERROR (LIFECYCLE, meta_->get(key).mark(NIL) );
// now build a concrete buffer entry
metadata::Entry& entry = meta_->markLocked(key, SOME_POINTER);
CHECK (LOCKED == entry.state());
CHECK (!entry.isTypeKey());
CHECK (SOME_POINTER == entry.access());
VERIFY_ERROR (FATAL, entry.mark(LOCKED) ); // invalid state transition
VERIFY_ERROR (FATAL, entry.mark(NIL) );
entry.mark (EMITTED); // valid transition
CHECK (EMITTED == entry.state());
CHECK (entry.isLocked());
VERIFY_ERROR (FATAL, entry.mark(LOCKED) );
VERIFY_ERROR (FATAL, entry.mark(EMITTED));
VERIFY_ERROR (FATAL, entry.mark(NIL) );
CHECK (EMITTED == entry.state());
entry.mark (FREE);
CHECK (FREE == entry.state());
CHECK (!entry.isLocked());
CHECK (!entry.isTypeKey());
VERIFY_ERROR (LIFECYCLE, entry.access() );
VERIFY_ERROR (FATAL, entry.mark(LOCKED) );
VERIFY_ERROR (FATAL, entry.mark(EMITTED));
VERIFY_ERROR (FATAL, entry.mark(BLOCKED));
VERIFY_ERROR (FATAL, entry.mark(FREE) );
VERIFY_ERROR (FATAL, entry.mark(NIL) );
// re-use buffer slot, start new lifecycle
void* OTHER_LOCATION = this;
entry.lock (OTHER_LOCATION);
CHECK (LOCKED == entry.state());
CHECK (entry.isLocked());
VERIFY_ERROR (LIFECYCLE, entry.lock(SOME_POINTER));
entry.mark (BLOCKED); // go directly to the blocked state
CHECK (BLOCKED == entry.state());
VERIFY_ERROR (FATAL, entry.mark(LOCKED) );
VERIFY_ERROR (FATAL, entry.mark(EMITTED) );
VERIFY_ERROR (FATAL, entry.mark(BLOCKED) );
VERIFY_ERROR (FATAL, entry.mark(NIL) );
CHECK (OTHER_LOCATION == entry.access());
entry.mark (FREE);
CHECK (!entry.isLocked());
VERIFY_ERROR (LIFECYCLE, entry.access() );
meta_->lock(key, SOME_POINTER);
CHECK (entry.isLocked());
entry.mark (EMITTED);
entry.mark (BLOCKED);
CHECK (BLOCKED == entry.state());
CHECK (SOME_POINTER == entry.access());
// can't discard metadata, need to free first
VERIFY_ERROR (LIFECYCLE, meta_->release(entry) );
CHECK (meta_->isKnown(entry));
CHECK (entry.isLocked());
entry.mark (FREE);
meta_->release(entry);
CHECK (!meta_->isKnown(entry));
CHECK ( meta_->isKnown(key));
}
};

View file

@ -24,29 +24,25 @@
#include "lib/error.hpp"
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/test/testdummy.hpp"
#include "lib/util-foreach.hpp"
//#include "proc/play/diagnostic-output-slot.hpp"
#include "proc/engine/testframe.hpp"
#include "proc/engine/diagnostic-buffer-provider.hpp"
#include "proc/engine/buffhandle.hpp"
#include "proc/engine/buffhandle-attach.hpp"
#include "proc/engine/bufftable.hpp"
//#include <boost/format.hpp>
//#include <iostream>
//using boost::format;
//using std::string;
//using std::cout;
using util::isSameObject;
using util::for_each;
namespace engine{
namespace test {
// using lib::AllocationCluster;
// using mobject::session::PEffect;
using lib::test::Dummy;
using ::engine::BuffHandle;
using lumiera::error::LUMIERA_ERROR_LIFECYCLE;
using error::LUMIERA_ERROR_LOGIC;
using error::LUMIERA_ERROR_LIFECYCLE;
namespace { // Test fixture
@ -64,21 +60,26 @@ namespace test {
}
/*******************************************************************
* @test verify the OutputSlot interface and base implementation
* by performing full data exchange cycle. This is a
* kind of "dry run" for documentation purposes,
* both the actual OutputSlot implementation
* as the client using this slot are Mocks.
/******************************************************************************
* @test verify and demonstrate the usage cycle of data buffers for the engine
* based on the BufferProvider interface. This is kind of a "dry run"
* for documentation purposes, because the BufferProvider implementation
* used here is just a diagnostics facility, allowing to investigate
* the state of individual buffers even after "releasing" them.
*
* This test should help understanding the sequence of buffer management
* operations performed at various stages while passing an calculation job
* through the render engine.
*/
class BufferProviderProtocol_test : public Test
{
virtual void
run (Arg)
{
UNIMPLEMENTED ("build a diagnostic buffer provider and perform a full lifecycle");
verifySimpleUsage();
verifyStandardCase();
verifyObjectAttachment();
verifyObjectAttachmentFailure();
}
@ -93,11 +94,12 @@ namespace test {
BuffHandle buff = provider.lockBufferFor<TestFrame>();
CHECK (buff.isValid());
CHECK (sizeof(TestFrame) <= buff.size());
buff.create<TestFrame>() = testData(0);
buff.accessAs<TestFrame>() = testData(0);
TestFrame& storage = buff.accessAs<TestFrame>();
CHECK (testData(0) == storage);
TestFrame& content = buff.accessAs<TestFrame>();
CHECK (testData(0) == content);
buff.emit();
buff.release();
CHECK (!buff.isValid());
VERIFY_ERROR (LIFECYCLE, buff.accessAs<TestFrame>() );
@ -105,8 +107,6 @@ namespace test {
DiagnosticBufferProvider& checker = DiagnosticBufferProvider::access(provider);
CHECK (checker.buffer_was_used (0));
CHECK (checker.buffer_was_closed (0));
CHECK (checker.object_was_attached<TestFrame> (0));
CHECK (checker.object_was_destroyed<TestFrame> (0));
CHECK (testData(0) == checker.accessMemory (0));
}
@ -147,6 +147,83 @@ namespace test {
CHECK (checker.all_buffers_released());
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #829
}
void
verifyObjectAttachment()
{
BufferProvider& provider = DiagnosticBufferProvider::build();
BufferDescriptor type_A = provider.getDescriptorFor(sizeof(TestFrame));
BufferDescriptor type_B = provider.getDescriptorFor(sizeof(int));
BufferDescriptor type_C = provider.getDescriptor<int>();
BuffHandle handle_A = provider.lockBuffer(type_A);
BuffHandle handle_B = provider.lockBuffer(type_B);
BuffHandle handle_C = provider.lockBuffer(type_C);
CHECK (handle_A);
CHECK (handle_B);
CHECK (handle_C);
CHECK (sizeof(TestFrame) == handle_A.size());
CHECK (sizeof( int ) == handle_B.size());
CHECK (sizeof( int ) == handle_C.size());
TestFrame& embeddedFrame = handle_A.create<TestFrame>();
CHECK (isSameObject (*handle_A, embeddedFrame));
CHECK (embeddedFrame.isAlive());
CHECK (embeddedFrame.isSane());
VERIFY_ERROR (LOGIC, handle_B.create<TestFrame>()); // too small to hold a TestFrame
VERIFY_ERROR (LIFECYCLE, handle_C.create<int>()); // has already an attached TypeHandler (creating an int)
handle_A.release();
handle_B.release();
handle_C.release();
CHECK (embeddedFrame.isDead());
CHECK (embeddedFrame.isSane());
}
void
verifyObjectAttachmentFailure()
{
BufferProvider& provider = DiagnosticBufferProvider::build();
BufferDescriptor type_D = provider.getDescriptorFor(sizeof(Dummy));
Dummy::checksum() = 0;
BuffHandle handle_D = provider.lockBuffer(type_D);
CHECK (0 == Dummy::checksum()); // nothing created thus far
handle_D.create<Dummy>();
CHECK (0 < Dummy::checksum());
handle_D.release();
CHECK (0 == Dummy::checksum());
BuffHandle handle_DD = provider.lockBuffer(type_D);
CHECK (0 == Dummy::checksum());
Dummy::activateCtorFailure();
CHECK (handle_DD.isValid());
try
{
handle_DD.create<Dummy>();
NOTREACHED ("Dummy ctor should fail");
}
catch (int val)
{
CHECK (!handle_DD.isValid());
CHECK (0 < Dummy::checksum());
CHECK (val == Dummy::checksum());
}
VERIFY_ERROR (LIFECYCLE, handle_DD.accessAs<Dummy>() );
VERIFY_ERROR (LIFECYCLE, handle_DD.create<Dummy>() );
}
};

View file

@ -93,7 +93,7 @@ namespace test {
ModelPort port(pipe);
OutputSlot& oSlot = DiagnosticOutputSlot::build();
Allocation output = oSlot.allocate();
Allocation& output = oSlot.allocate();
Timings timings; /////////TODO
// Invoke test subject...

View file

@ -26,7 +26,7 @@
#include "proc/engine/nodefactory.hpp"
#include "proc/engine/nodewiring.hpp"
#include "proc/engine/stateproxy.hpp"
#include "proc/engine/buffhandle.hpp"
#include "proc/engine/channel-descriptor.hpp"
#include "proc/mobject/session/effect.hpp"
#include "lib/allocation-cluster.hpp"

View file

@ -0,0 +1,208 @@
/*
TestFrame(Test) - verify proper operation of dummy data frames
Copyright (C) Lumiera.org
2011, 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.
* *****************************************************/
#include "lib/test/run.hpp"
#include "proc/engine/testframe.hpp"
#include "lib/util.hpp"
#include <cstdlib>
#include <limits.h>
#include <boost/scoped_ptr.hpp>
using test::Test;
using std::rand;
using util::isSameObject;
using boost::scoped_ptr;
namespace engine{
namespace test {
namespace { // used internally
const uint CHAN_COUNT = 30; // independent families of test frames to generate
const uint NUM_FRAMES = 1000; // number of test frames in each of these families
void
corruptMemory(void* base, uint offset, uint count)
{
char* accessor = reinterpret_cast<char*> (base);
while (count--)
accessor[offset+count] = rand() % CHAR_MAX;
}
} // (End) internal defs
/*******************************************************************
* @test verify test helper for engine tests: a dummy data frame.
* TestFrame instances can be created right away, without any
* external library dependencies. A test frame is automatically
* filled with random data; multiple frames are arranged in
* sequences and channels, causing the random data to be
* reproducible yet different in each frame.
*
* To ease writing unit tests, TestFrame provides comparison
* and assignment and tracks lifecycle automatically. As tests
* regarding the engine typically have to deal with buffer
* management, an arbitrary memory location can be interpreted
* as TestFrame and checked for corruption.
*/
class TestFrame_test : public Test
{
virtual void
run (Arg)
{
verifyBasicProperties();
verifyFrameLifecycle();
verifyFrameSeries();
useFrameTable();
}
void
verifyBasicProperties()
{
CHECK (1024 < sizeof(TestFrame));
TestFrame frameA;
TestFrame frameB;
TestFrame frameC(5);
CHECK (frameA == frameB);
CHECK (frameA != frameC);
CHECK (frameB != frameC);
CHECK (frameA.isAlive());
CHECK (frameB.isAlive());
CHECK (frameC.isAlive());
CHECK (frameA.isSane());
CHECK (frameB.isSane());
CHECK (frameC.isSane());
void * frameMem = &frameB;
CHECK (frameA == frameMem);
corruptMemory(frameMem,20,5);
CHECK (!frameB.isSane());
frameB = frameC;
CHECK (frameB.isSane());
CHECK (frameA != frameB);
CHECK (frameA != frameC);
CHECK (frameB == frameC);
}
void
verifyFrameLifecycle()
{
CHECK (!TestFrame::isDead (this));
CHECK (!TestFrame::isAlive (this));
TestFrame* onHeap = new TestFrame(23);
CHECK ( TestFrame::isAlive (onHeap));
CHECK (!onHeap->isDead());
CHECK (onHeap->isAlive());
CHECK (onHeap->isSane());
delete onHeap;
CHECK ( TestFrame::isDead (onHeap));
CHECK (!TestFrame::isAlive (onHeap));
}
/** @test build sequences of test frames,
* organised into multiple families (channels).
* Verify that adjacent frames hold differing data
*/
void
verifyFrameSeries()
{
scoped_ptr<TestFrame> thisFrames[CHAN_COUNT];
scoped_ptr<TestFrame> prevFrames[CHAN_COUNT];
for (uint i=0; i<CHAN_COUNT; ++i)
thisFrames[i].reset (new TestFrame(0, i));
for (uint nr=1; nr<NUM_FRAMES; ++nr)
for (uint i=0; i<CHAN_COUNT; ++i)
{
thisFrames[i].swap (prevFrames[i]);
thisFrames[i].reset (new TestFrame(nr, i));
CHECK (thisFrames[i]->isSane());
CHECK (prevFrames[i]->isSane());
CHECK (prevFrames[i]->isAlive());
CHECK (*thisFrames[i] != *prevFrames[i]); // differs from predecessor within the same channel
for (uint j=0; j<i; ++j)
{
ENSURE (j!=i);
CHECK (*thisFrames[i] != *thisFrames[j]); // differs from frames in other channels at this point
CHECK (*thisFrames[i] != *prevFrames[j]); // differs cross wise from predecessors in other channels
} } }
/** @test the table of test frames
* computed on demand */
void
useFrameTable()
{
TestFrame& frX = testData(3,50);
TestFrame& frY = testData(3,25);
TestFrame& frZ = testData(3,50);
CHECK (frX.isSane());
CHECK (frY.isSane());
CHECK (frZ.isSane());
CHECK (frX != frY);
CHECK (frX == frZ);
CHECK (frY != frZ);
CHECK (isSameObject (frX, frZ));
corruptMemory(&frZ,40,20);
CHECK (!frX.isSane());
CHECK (!testData(3,50).isSane());
CHECK ( testData(3,51).isSane());
CHECK ( testData(3,49).isSane());
resetTestFrames();
CHECK ( testData(3,50).isSane());
}
};
/** Register this test class... */
LAUNCHER (TestFrame_test, "unit engine");
}} // namespace engine::test

View file

@ -0,0 +1,292 @@
/*
TestFrame - test data frame (stub) for checking Render engine functionality
Copyright (C) Lumiera.org
2011, 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.
* *****************************************************/
#include "proc/engine/testframe.hpp"
#include "lib/error.hpp"
#include <boost/random/linear_congruential.hpp>
#include <boost/scoped_ptr.hpp>
#include <limits.h>
#include <cstring>
#include <vector>
namespace engine {
namespace test {
using std::vector;
using std::memcpy;
typedef boost::rand48 PseudoRandom;
namespace error = lumiera::error;
namespace { // hidden local support facilities....
/** @internal helper for generating unique test frames.
* This "discriminator" is used as a random seed when
* filling the test frame data buffers. It is generated
* to be different on adjacent frames of the same series,
* as well as to differ to all near by neighbouring channels.
* @param seq the sequence number of the frame within the channel
* @param family the channel this frame belongs to
*/
uint64_t
generateDistinction(uint seq, uint family)
{
// random offset, but fixed per executable run
static uint base(10 + rand() % 990);
// use the family as stepping
return (seq+1) * (base+family);
}
TestFrame&
accessAsTestFrame (void* memoryLocation)
{
REQUIRE (memoryLocation);
return *reinterpret_cast<TestFrame*> (memoryLocation);
}
/**
* @internal table to hold test data frames.
* These frames are built on demand, but retained thereafter.
* Some tests might rely on the actual memory locations, using the
* test frames to simulate a real input frame data stream.
* @param CHA the maximum number of channels to expect
* @param FRA the maximum number of frames to expect per channel
* @warning choose the maximum number parameters wisely.
* We're allocating memory to hold a table of test frames
* e.g. sizeof(TestFrame) * 20channels * 100frames 2 MiB
* The table uses vectors, and thus will grow on demand,
* but this might cause existing frames to be relocated in memory;
* some tests might rely on fixed memory locations. Just be cautious!
*/
template<uint CHA, uint FRA>
struct TestFrameTable
: vector<vector<TestFrame> >
{
typedef vector<vector<TestFrame> > VECT;
TestFrameTable()
: VECT(CHA)
{
for (uint i=0; i<CHA; ++i)
at(i).reserve(FRA);
}
TestFrame&
getFrame (uint seqNr, uint chanNr=0)
{
if (chanNr >= this->size())
{
WARN (test, "Growing table of test frames to %d channels, "
"which is > the default (%d)", chanNr, CHA);
resize(chanNr+1);
}
ENSURE (chanNr < this->size());
vector<TestFrame>& channel = at(chanNr);
if (seqNr >= channel.size())
{
WARN_IF (seqNr >= FRA, test,
"Growing channel #%d of test frames to %d elements, "
"which is > the default (%d)", chanNr, seqNr, FRA);
for (uint i=channel.size(); i<=seqNr; ++i)
channel.push_back (TestFrame (i,chanNr));
}
ENSURE (seqNr < channel.size());
return channel[seqNr];
}
};
const uint INITIAL_CHAN = 20;
const uint INITIAL_FRAMES = 100;
typedef TestFrameTable<INITIAL_CHAN,INITIAL_FRAMES> TestFrames;
boost::scoped_ptr<TestFrames> testFrames;
TestFrame&
accessTestFrame (uint seqNr, uint chanNr)
{
if (!testFrames) testFrames.reset (new TestFrames);
return testFrames->getFrame(seqNr,chanNr);
}
} // (End) hidden impl details
TestFrame&
testData (uint seqNr)
{
return accessTestFrame (seqNr, 0);
}
TestFrame&
testData (uint chanNr, uint seqNr)
{
return accessTestFrame (seqNr,chanNr);
}
void
resetTestFrames()
{
testFrames.reset(0);
}
/* ===== TestFrame class ===== */
TestFrame::~TestFrame()
{
stage_ = DISCARDED;
}
TestFrame::TestFrame(uint seq, uint family)
: distinction_(generateDistinction (seq,family))
, stage_(CREATED)
{
ASSERT (0 < distinction_);
buildData();
}
TestFrame::TestFrame (TestFrame const& o)
: distinction_(o.distinction_)
, stage_(CREATED)
{
memcpy (data_, o.data_, BUFFSIZ);
}
TestFrame&
TestFrame::operator= (TestFrame const& o)
{
if (DISCARDED == stage_)
throw new error::Logic ("target TestFrame is already dead");
if (this != &o)
{
distinction_ = o.distinction_;
stage_ = CREATED;
memcpy (data_, o.data_, BUFFSIZ);
}
return *this;
}
/** @note performing an unchecked conversion of the given
* memory location to be accessed as TestFrame.
* The sanity of the data found at that location
* is checked as well, not only the lifecycle flag.
*/
bool
TestFrame::isAlive (void* memLocation)
{
TestFrame& candidate (accessAsTestFrame (memLocation));
return candidate.isSane()
&& candidate.isAlive();
}
bool
TestFrame::isDead (void* memLocation)
{
TestFrame& candidate (accessAsTestFrame (memLocation));
return candidate.isSane()
&& candidate.isDead();
}
bool
TestFrame::operator== (void* memLocation) const
{
TestFrame& candidate (accessAsTestFrame (memLocation));
return candidate.isSane()
&& candidate == *this;
}
bool
TestFrame::contentEquals (TestFrame const& o) const
{
for (uint i=0; i<BUFFSIZ; ++i)
if (data_[i] != o.data_[i])
return false;
return true;
}
bool
TestFrame::verifyData() const
{
PseudoRandom gen(distinction_);
for (uint i=0; i<BUFFSIZ; ++i)
if (data_[i] != (gen() % CHAR_MAX))
return false;
return true;
}
void
TestFrame::buildData()
{
PseudoRandom gen(distinction_);
for (uint i=0; i<BUFFSIZ; ++i)
data_[i] = (gen() % CHAR_MAX);
}
bool
TestFrame::isAlive() const
{
return (CREATED == stage_)
|| (EMITTED == stage_);
}
bool
TestFrame::isDead() const
{
return (DISCARDED == stage_);
}
bool
TestFrame::isSane() const
{
return ( (CREATED == stage_)
||(EMITTED == stage_)
||(DISCARDED == stage_))
&& verifyData();
}
}} // namespace engine::test

View file

@ -25,21 +25,14 @@
#define PROC_ENGINE_TESTFRAME_H
//#include "lib/time/timevalue.hpp"
//#include <string>
//using std::tr1::shared_ptr;
//using std::string;
#include <cstdlib>
#include <stdint.h>
namespace engine {
namespace test {
//class TestPlacement;
/**
* Mock data frame for simulated rendering.
* A test frame can be created and placed instead of a real data frame.
@ -47,52 +40,80 @@ namespace test {
* Placeholder functions are provided for assignment (simulating the actual
* calculations); additional diagnostic functions allow to verify the
* performed operations after-the fact
*
* @todo WIP-WIP-WIP 9/11
*
* Each TestFrame is automatically filled with pseudo random data;
* multiple frames are arranged in sequences and channels, causing the random data
* to be reproducible yet different within each frame. TestFrame's lifecycle is
* tracked and marked in an embedded state field. Moreover, the contents of the
* data block can be verified, because the sequence of bytes is reproducible,
* based on the channel and sequence number of the test frame.
*
* @see TestFrame_test
* @see OutputSlotProtocol_test
*
*/
class TestFrame
{
enum StageOfLife {
CREATED, EMITTED, DISCARDED
};
static const size_t BUFFSIZ = 1024;
uint64_t distinction_;
StageOfLife stage_;
char data_[BUFFSIZ];
public:
~TestFrame();
TestFrame (uint seq=0, uint family=0);
TestFrame (TestFrame const&);
TestFrame& operator= (TestFrame const&);
bool
operator== (void* memLocation)
{
UNIMPLEMENTED ("verify contents of an arbitrary memory location");
}
/** Helper to verify that a given memory location holds
* an active TestFrame instance (created, not yet destroyed)
* @return true if the TestFrame datastructure is intact and
* marked as still alive.
*/
static bool isAlive (void* memLocation);
friend bool
operator== (TestFrame const& f1, TestFrame const& f2)
{
UNIMPLEMENTED ("equality of test data frames");
}
/** Helper to verify a given memory location holds
* an already destroyed TestFrame instance */
static bool isDead (void* memLocation);
friend bool
operator!= (TestFrame const& f1, TestFrame const& f2)
{
return !(f1 == f2);
}
bool isAlive() const;
bool isDead() const;
bool isSane() const;
bool operator== (void* memLocation) const;
friend bool operator== (TestFrame const& f1, TestFrame const& f2) { return f1.contentEquals(f2); }
friend bool operator!= (TestFrame const& f1, TestFrame const& f2) { return !f1.contentEquals(f2); }
private:
bool contentEquals (TestFrame const& o) const;
bool verifyData() const;
void buildData ();
};
inline TestFrame
testData (uint seqNr)
{
UNIMPLEMENTED ("build, memorise and expose test data frames on demand");
}
/** Helper to access a specific frame of test data at a fixed memory location.
* The series of test frames is generated on demand, but remains in memory thereafter,
* similar to real data accessible from some kind of source stream. Each of these generated
* test frames filled with different yet reproducible pseudo random data.
* Client code is free to access and corrupt this data.
*/
TestFrame& testData (uint seqNr);
inline TestFrame
testData (uint chanNr, uint seqNr)
{
UNIMPLEMENTED ("build, memorise and expose test data frames on demand (multi-channel)");
}
TestFrame& testData (uint chanNr, uint seqNr);
/* == some test data to check == */
// extern const lib::time::Duration LENGTH_TestClip;
/** discards all the TestFrame instances and
* initialises an empty table of test frames */
void resetTestFrames();
}} // namespace engine::test

View file

@ -0,0 +1,222 @@
/*u4
TrackingHeapBlockProvider(Test) - verify a support facility for diagnostic/test purposes
Copyright (C) Lumiera.org
2011, 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.
* *****************************************************/
#include "lib/error.hpp"
#include "lib/test/run.hpp"
#include "proc/engine/tracking-heap-block-provider.hpp"
#include "proc/engine/buffhandle-attach.hpp"
#include "proc/engine/testframe.hpp"
#include <cstdlib>
#include <vector>
using std::rand;
namespace engine{
namespace test {
namespace { // Test fixture
const size_t TEST_ELM_SIZE = sizeof(uint);
const uint MAX_ELMS = 50;
std::vector<uint> testNumbers(MAX_ELMS);
bool
has_expectedContent (uint nr, diagn::Block& memoryBlock)
{
void* mem = memoryBlock.accessMemory();
uint data = *static_cast<uint*> (mem);
return data == testNumbers[nr];
}
bool
verifyUsedBlock (uint nr, diagn::Block& memoryBlock)
{
return memoryBlock.was_used()
&& memoryBlock.was_closed()
&& has_expectedContent (nr, memoryBlock);
}
}
/**********************************************************************
* @test verify a test support facility, used to write mock components
* to test the lumiera engine. The TrackingHeapBlockProvider is a
* braindead implementation of the BufferProvider interface: it just
* claims new heap blocks and never de-allocates them, allowing other
* test and mock objects to verify allocated buffers after the fact.
*/
class TrackingHeapBlockProvider_test : public Test
{
virtual void
run (Arg)
{
simpleExample();
verifyStandardCase();
verifyTestProtocol();
}
void
simpleExample()
{
TrackingHeapBlockProvider provider;
BuffHandle testBuff = provider.lockBufferFor<TestFrame>();
CHECK (testBuff);
CHECK (testBuff.accessAs<TestFrame>().isSane());
uint dataID = 1 + rand() % 29;
testBuff.accessAs<TestFrame>() = testData(dataID);
provider.emitBuffer (testBuff);
provider.releaseBuffer(testBuff);
diagn::Block& block0 = provider.access_emitted(0);
CHECK (testData(dataID) == block0.accessMemory());
}
void
verifyStandardCase()
{
TrackingHeapBlockProvider provider;
BufferDescriptor buffType = provider.getDescriptorFor(TEST_ELM_SIZE);
uint numElms = provider.announce(MAX_ELMS, buffType);
CHECK (0 < numElms);
CHECK (numElms <= MAX_ELMS);
for (uint i=0; i<numElms; ++i)
{
BuffHandle buff = provider.lockBuffer(buffType);
buff.accessAs<uint>() = testNumbers[i] = rand() % 100000;
provider.emitBuffer (buff);
provider.releaseBuffer(buff);
}
for (uint nr=0; nr<numElms; ++nr)
{
CHECK (verifyUsedBlock (nr, provider.access_emitted(nr)));
}
}
void
verifyTestProtocol()
{
TrackingHeapBlockProvider provider;
BufferDescriptor buffType = provider.getDescriptorFor(TEST_ELM_SIZE);
BuffHandle bu1 = provider.lockBuffer (buffType);
BuffHandle bu2 = provider.lockBuffer (buffType);
BuffHandle bu3 = provider.lockBuffer (buffType);
BuffHandle bu4 = provider.lockBuffer (buffType);
BuffHandle bu5 = provider.lockBuffer (buffType);
// buffers are locked,
// but still within the per-type allocation pool
// while the output sequence is still empty
CHECK (!provider.access_emitted(0).was_used());
CHECK (!provider.access_emitted(1).was_used());
CHECK (!provider.access_emitted(2).was_used());
CHECK (!provider.access_emitted(3).was_used());
CHECK (!provider.access_emitted(4).was_used());
// can use the buffers for real
bu1.accessAs<uint>() = 1;
bu2.accessAs<uint>() = 2;
bu3.accessAs<uint>() = 3;
bu4.accessAs<uint>() = 4;
bu5.accessAs<uint>() = 5;
CHECK (0 == provider.emittedCnt());
// now emit buffers in shuffled order
provider.emitBuffer (bu3);
provider.emitBuffer (bu1);
provider.emitBuffer (bu5);
provider.emitBuffer (bu4);
provider.emitBuffer (bu2);
CHECK (5 == provider.emittedCnt());
CHECK (3 == provider.accessAs<uint>(0));
CHECK (1 == provider.accessAs<uint>(1));
CHECK (5 == provider.accessAs<uint>(2));
CHECK (4 == provider.accessAs<uint>(3));
CHECK (2 == provider.accessAs<uint>(4));
CHECK ( provider.access_emitted(0).was_used());
CHECK ( provider.access_emitted(1).was_used());
CHECK ( provider.access_emitted(2).was_used());
CHECK ( provider.access_emitted(3).was_used());
CHECK ( provider.access_emitted(4).was_used());
CHECK (!provider.access_emitted(0).was_closed());
CHECK (!provider.access_emitted(1).was_closed());
CHECK (!provider.access_emitted(2).was_closed());
CHECK (!provider.access_emitted(3).was_closed());
CHECK (!provider.access_emitted(4).was_closed());
bu5.release();
CHECK (!provider.access_emitted(0).was_closed());
CHECK (!provider.access_emitted(1).was_closed());
CHECK ( provider.access_emitted(2).was_closed());
CHECK (!provider.access_emitted(3).was_closed());
CHECK (!provider.access_emitted(4).was_closed());
bu2.release();
bu2.release();
bu5.release();
CHECK (!provider.access_emitted(0).was_closed());
CHECK (!provider.access_emitted(1).was_closed());
CHECK ( provider.access_emitted(2).was_closed());
CHECK (!provider.access_emitted(3).was_closed());
CHECK ( provider.access_emitted(4).was_closed());
CHECK (!bu2);
CHECK (bu3);
bu1.release();
bu3.release();
bu4.release();
CHECK (5 == provider.emittedCnt());
}
};
/** Register this test class... */
LAUNCHER (TrackingHeapBlockProvider_test, "unit player");
}} // namespace engine::test

View file

@ -34,11 +34,10 @@
#include "lib/query.hpp"
#include <tr1/functional>
#include <boost/ref.hpp>
#include <iostream>
#include <set>
using boost::ref;
using std::tr1::ref;
using std::tr1::placeholders::_1;
using util::isSameObject;
using util::and_all;

View file

@ -32,15 +32,19 @@
#include "lib/error.hpp"
#include "include/logging.h"
#include "proc/play/output-slot.hpp"
#include "proc/play/output-slot-connection.hpp"
#include "proc/engine/buffhandle.hpp"
#include "proc/engine/tracking-heap-block-provider.hpp"
#include "lib/iter-source.hpp" ////////////TODO really going down that path...?
#include "proc/engine/testframe.hpp"
//#include "lib/sync.hpp"
//#include <boost/noncopyable.hpp>
#include <boost/noncopyable.hpp>
//#include <string>
//#include <vector>
//#include <tr1/memory>
#include <tr1/memory>
//#include <boost/scoped_ptr.hpp>
@ -48,14 +52,105 @@ namespace proc {
namespace play {
//using std::string;
using ::engine::BufferDescriptor;
using ::engine::test::TestFrame;
using ::engine::TrackingHeapBlockProvider;
//using std::vector;
//using std::tr1::shared_ptr;
using std::tr1::shared_ptr;
//using boost::scoped_ptr;
class TrackingInMemoryBlockSequence
: public OutputSlot::Connection
{
shared_ptr<TrackingHeapBlockProvider> buffProvider_;
BufferDescriptor bufferType_;
/* === Connection API === */
BuffHandle
claimBufferFor(FrameID frameNr)
{
return buffProvider_->lockBuffer (bufferType_);
}
bool
isTimely (FrameID frameNr, TimeValue currentTime)
{
if (Time::MAX == currentTime)
return true;
UNIMPLEMENTED ("find out about timings");
return false;
}
void
transfer (BuffHandle const& filledBuffer)
{
pushout (filledBuffer);
}
void
pushout (BuffHandle const& data4output)
{
buffProvider_->emitBuffer (data4output);
buffProvider_->releaseBuffer(data4output);
}
void
discard (BuffHandle const& superseededData)
{
buffProvider_->releaseBuffer (superseededData);
}
void
shutDown ()
{
buffProvider_.reset();
}
public:
TrackingInMemoryBlockSequence()
: buffProvider_(new TrackingHeapBlockProvider())
, bufferType_(buffProvider_->getDescriptor<TestFrame>())
{
INFO (engine_dbg, "building in-memory diagnostic output sequence");
}
virtual
~TrackingInMemoryBlockSequence()
{
INFO (engine_dbg, "releasing diagnostic output sequence");
}
};
class SimulatedOutputSequences
: public ConnectionStateManager<TrackingInMemoryBlockSequence>
, boost::noncopyable
{
TrackingInMemoryBlockSequence
buildConnection()
{
return TrackingInMemoryBlockSequence();
}
public:
SimulatedOutputSequences (uint numChannels)
{
init (numChannels);
}
};
/********************************************************************
* Helper for unit tests: Mock output sink.
*
@ -64,6 +159,17 @@ namespace play {
class DiagnosticOutputSlot
: public OutputSlot
{
static const uint MAX_CHANNELS = 5;
/* === hook into the OutputSlot frontend === */
ConnectionState*
buildState()
{
return new SimulatedOutputSequences(MAX_CHANNELS);
}
public:
/** build a new Diagnostic Output Slot instance,
* discard the existing one. Use the static query API
@ -105,28 +211,28 @@ namespace play {
bool
buffer_was_used (uint channel, FrameNr frame)
buffer_was_used (uint channel, FrameID frame)
{
UNIMPLEMENTED ("determine if the denoted buffer was indeed used");
}
bool
buffer_unused (uint channel, FrameNr frame)
buffer_unused (uint channel, FrameID frame)
{
UNIMPLEMENTED ("determine if the specified buffer was never touched/locked for use");
}
bool
buffer_was_closed (uint channel, FrameNr frame)
buffer_was_closed (uint channel, FrameID frame)
{
UNIMPLEMENTED ("determine if the specified buffer was indeed closed properly");
}
bool
emitted (uint channel, FrameNr frame)
emitted (uint channel, FrameID frame)
{
UNIMPLEMENTED ("determine if the specivied buffer was indeed handed over for emitting output");
}

Some files were not shown because too many files have changed in this diff Show more