/*
This file is part of Ingen.
Copyright 2015-2017 David Robillard
Ingen is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or any later version.
Ingen 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 Affero General Public License for details.
You should have received a copy of the GNU Affero General Public License
along with Ingen. If not, see .
*/
#include
#include
#include "ingen/ColorContext.hpp"
#include "ingen/Configuration.hpp"
#include "ingen/Log.hpp"
#include "ingen/World.hpp"
#include "CompiledGraph.hpp"
#include "Engine.hpp"
#include "GraphImpl.hpp"
#include "ThreadManager.hpp"
namespace Ingen {
namespace Server {
using BlockSet = std::unordered_set;
/** Graph contains ambiguous feedback with no delay nodes. */
class FeedbackException : public Raul::Exception {
public:
FeedbackException(const BlockImpl* node, const BlockImpl* root = nullptr)
: Raul::Exception(
std::string("Feedback at ") + node->path()
+ (root ? std::string(" from ") + root->path() : std::string()))
{
}
};
class ScopedMark {
public:
ScopedMark(BlockImpl* block, BlockImpl::Mark active, BlockImpl::Mark done)
: _old(block->get_mark())
, _done(done)
{
block->set_mark(active);
}
~ScopedMark()
{
_block->set_mark(_done);
}
ScopedMark(const ScopedMark&) = delete;
BlockImpl::Mark old() const { return _old; }
private:
BlockImpl* _block;
BlockImpl::Mark _old;
BlockImpl::Mark _done;
};
static bool
has_provider_with_fan_out(BlockImpl* n)
{
for (auto* p : n->providers()) {
if (p->dependants().size() > 1) {
return true;
}
}
return false;
}
static size_t
num_unvisited_dependants(BlockImpl* block)
{
size_t count = 0;
for (BlockImpl* b : block->dependants()) {
if (b->get_mark() == BlockImpl::Mark::UNVISITED) {
++count;
}
}
return count;
}
/** Throw a FeedbackException iff `dependant` has `root` as a dependency.
*
* This must be used when the normal traversal short-cuts and adds to the
* future working set so that feedback cycles that would be discovered by a
* full BFS are still detected.
*/
static void
check_feedback(const BlockImpl* root, BlockImpl* node)
{
if (node == root || node->get_mark() == BlockImpl::Mark::VISITING) {
throw FeedbackException(node, root);
}
const ScopedMark mark(node, BlockImpl::Mark::VISITING, node->get_mark());
for (auto p : node->providers()) {
check_feedback(root, p);
}
}
static void
compile_block(BlockImpl* n, Task& task, BlockSet& k)
{
switch (n->get_mark()) {
case BlockImpl::Mark::UNVISITED: {
ScopedMark mark(n, BlockImpl::Mark::VISITING, BlockImpl::Mark::VISITED);
if (has_provider_with_fan_out(n)) {
// Provider shared by other dependants, enqueue providers for later
// Prepends n to current task: (... n ...)
task.emplace_front(n);
for (auto p : n->providers()) {
check_feedback(n, p);
if (num_unvisited_dependants(p) == 0) {
k.insert(p);
}
}
} else {
// All providers have only this dependant
// Runs node after parallel providers: (... (seq (par p...) n) ...)
task.emplace_front(Task::Mode::SEQUENTIAL);
task.front().emplace_front(n);
task.front().emplace_front(Task::Mode::PARALLEL);
for (auto p : n->providers()) {
compile_block(p, task.front().front(), k);
}
}
break;
}
case BlockImpl::Mark::VISITING:
throw FeedbackException(n);
case BlockImpl::Mark::VISITED:
break;
}
}
CompiledGraph::CompiledGraph(GraphImpl& graph)
: _master(std::unique_ptr(new Task(Task::Mode::SEQUENTIAL)))
{
ThreadManager::assert_thread(THREAD_PRE_PROCESS);
// Start with sink nodes (no outputs, or connected only to graph outputs)
BlockSet blocks;
for (auto& b : graph.blocks()) {
// Mark all blocks as unvisited initially
b.set_mark(BlockImpl::Mark::UNVISITED);
if (b.dependants().empty()) {
// Block has no dependants, add to initial working set
blocks.insert(&b);
}
}
// Keep compiling working set until all nodes are visited
while (!blocks.empty()) {
BlockSet predecessors;
_master->emplace_front(Task::Mode::PARALLEL);
for (auto b : blocks) {
assert(num_unvisited_dependants(b) == 0);
compile_block(b, _master->front(), predecessors);
}
blocks = std::move(predecessors);
}
_master = Task::simplify(std::move(_master));
}
MPtr compile(Raul::Maid& maid, GraphImpl& graph)
{
try {
auto result = maid.make_managed(graph);
if (graph.engine().world()->conf().option("trace").get()) {
Log& log = graph.engine().log();
auto sink = [&log](const std::string& s) { log.trace(s); };
sink(graph.path() + " =>\n ");
result->master().dump(sink, 2, true);
sink("\n");
}
return result;
} catch (const FeedbackException& e) {
graph.engine().log().error(e.what());
}
return MPtr();
}
} // namespace Server
} // namespace Ingen