diff options
Diffstat (limited to 'src/server/events')
28 files changed, 3931 insertions, 0 deletions
diff --git a/src/server/events/Connect.cpp b/src/server/events/Connect.cpp new file mode 100644 index 00000000..8937b327 --- /dev/null +++ b/src/server/events/Connect.cpp @@ -0,0 +1,188 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/Store.hpp" +#include "raul/Maid.hpp" +#include "raul/Path.hpp" + +#include "ArcImpl.hpp" +#include "Broadcaster.hpp" +#include "BufferFactory.hpp" +#include "Connect.hpp" +#include "Engine.hpp" +#include "GraphImpl.hpp" +#include "InputPort.hpp" +#include "PortImpl.hpp" +#include "PreProcessContext.hpp" +#include "internals/BlockDelay.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Connect::Connect(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Connect& msg) + : Event(engine, client, msg.seq, timestamp) + , _msg(msg) + , _graph(nullptr) + , _head(nullptr) +{} + +bool +Connect::pre_process(PreProcessContext& ctx) +{ + std::lock_guard<Store::Mutex> lock(_engine.store()->mutex()); + + Node* tail = _engine.store()->get(_msg.tail); + if (!tail) { + return Event::pre_process_done(Status::NOT_FOUND, _msg.tail); + } + + Node* head = _engine.store()->get(_msg.head); + if (!head) { + return Event::pre_process_done(Status::NOT_FOUND, _msg.head); + } + + PortImpl* tail_output = dynamic_cast<PortImpl*>(tail); + _head = dynamic_cast<InputPort*>(head); + if (!tail_output || !_head) { + return Event::pre_process_done(Status::BAD_REQUEST, _msg.head); + } + + BlockImpl* const tail_block = tail_output->parent_block(); + BlockImpl* const head_block = _head->parent_block(); + if (!tail_block || !head_block) { + return Event::pre_process_done(Status::PARENT_NOT_FOUND, _msg.head); + } + + if (tail_block->parent() != head_block->parent() + && tail_block != head_block->parent() + && tail_block->parent() != head_block) { + return Event::pre_process_done(Status::PARENT_DIFFERS, _msg.head); + } + + if (!ArcImpl::can_connect(tail_output, _head)) { + return Event::pre_process_done(Status::TYPE_MISMATCH, _msg.head); + } + + if (tail_block->parent_graph() != head_block->parent_graph()) { + // Arc to a graph port from inside the graph + assert(tail_block->parent() == head_block || head_block->parent() == tail_block); + if (tail_block->parent() == head_block) { + _graph = dynamic_cast<GraphImpl*>(head_block); + } else { + _graph = dynamic_cast<GraphImpl*>(tail_block); + } + } else if (tail_block == head_block && dynamic_cast<GraphImpl*>(tail_block)) { + // Arc from a graph input to a graph output (pass through) + _graph = dynamic_cast<GraphImpl*>(tail_block); + } else { + // Normal arc between blocks with the same parent + _graph = tail_block->parent_graph(); + } + + if (_graph->has_arc(tail_output, _head)) { + return Event::pre_process_done(Status::EXISTS, _msg.head); + } + + _arc = SPtr<ArcImpl>(new ArcImpl(tail_output, _head)); + + /* Need to be careful about graph port arcs here and adding a + block's parent as a dependant/provider, or adding a graph as its own + provider... + */ + if (tail_block != head_block && tail_block->parent() == head_block->parent()) { + // Connection is between blocks inside a graph, compile graph + + // The tail block is now a dependency (provider) of the head block + head_block->providers().insert(tail_block); + + if (!dynamic_cast<Internals::BlockDelayNode*>(tail_block)) { + /* Arcs leaving a delay node are ignored for the purposes of + compilation, since the output is from the previous cycle and + does not affect execution order. Otherwise, the head block is + now a dependant of the head block. */ + tail_block->dependants().insert(head_block); + } + + if (ctx.must_compile(*_graph)) { + if (!(_compiled_graph = compile(*_engine.maid(), *_graph))) { + head_block->providers().erase(tail_block); + tail_block->dependants().erase(head_block); + return Event::pre_process_done(Status::COMPILATION_FAILED); + } + } + } + + _graph->add_arc(_arc); + _head->increment_num_arcs(); + + if (!_head->is_driver_port()) { + BufferFactory& bufs = *_engine.buffer_factory(); + _voices = bufs.maid().make_managed<PortImpl::Voices>(_head->poly()); + _head->pre_get_buffers(bufs, _voices, _head->poly()); + } + + tail_output->inherit_neighbour(_head, _tail_remove, _tail_add); + _head->inherit_neighbour(tail_output, _head_remove, _head_add); + + return Event::pre_process_done(Status::SUCCESS); +} + +void +Connect::execute(RunContext& context) +{ + if (_status == Status::SUCCESS) { + _head->add_arc(context, *_arc.get()); + if (!_head->is_driver_port()) { + _head->set_voices(context, std::move(_voices)); + } + _head->connect_buffers(); + if (_compiled_graph) { + _graph->set_compiled_graph(std::move(_compiled_graph)); + } + } +} + +void +Connect::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS) { + _engine.broadcaster()->message(_msg); + if (!_tail_remove.empty() || !_tail_add.empty()) { + _engine.broadcaster()->delta( + path_to_uri(_msg.tail), _tail_remove, _tail_add); + } + if (!_tail_remove.empty() || !_tail_add.empty()) { + _engine.broadcaster()->delta( + path_to_uri(_msg.tail), _tail_remove, _tail_add); + } + } +} + +void +Connect::undo(Interface& target) +{ + target.disconnect(_msg.tail, _msg.head); +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Connect.hpp b/src/server/events/Connect.hpp new file mode 100644 index 00000000..8a42b984 --- /dev/null +++ b/src/server/events/Connect.hpp @@ -0,0 +1,74 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_CONNECT_HPP +#define INGEN_EVENTS_CONNECT_HPP + +#include "raul/Path.hpp" + +#include "CompiledGraph.hpp" +#include "Event.hpp" +#include "PortImpl.hpp" +#include "types.hpp" + +namespace Raul { +template <typename T> class Array; +} + +namespace Ingen { +namespace Server { + +class ArcImpl; +class GraphImpl; +class InputPort; + +namespace Events { + +/** Make an Arc between two Ports. + * + * \ingroup engine + */ +class Connect : public Event +{ +public: + Connect(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Connect& msg); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + void undo(Interface& target); + +private: + const Ingen::Connect _msg; + GraphImpl* _graph; + InputPort* _head; + MPtr<CompiledGraph> _compiled_graph; + SPtr<ArcImpl> _arc; + MPtr<PortImpl::Voices> _voices; + Properties _tail_remove; + Properties _tail_add; + Properties _head_remove; + Properties _head_add; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_CONNECT_HPP diff --git a/src/server/events/Copy.cpp b/src/server/events/Copy.cpp new file mode 100644 index 00000000..fc9d40f7 --- /dev/null +++ b/src/server/events/Copy.cpp @@ -0,0 +1,216 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/Parser.hpp" +#include "ingen/Serialiser.hpp" +#include "ingen/Store.hpp" +#include "raul/Path.hpp" + +#include "BlockImpl.hpp" +#include "Broadcaster.hpp" +#include "Engine.hpp" +#include "EnginePort.hpp" +#include "GraphImpl.hpp" +#include "PreProcessContext.hpp" +#include "events/Copy.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Copy::Copy(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Copy& msg) + : Event(engine, client, msg.seq, timestamp) + , _msg(msg) + , _old_block(nullptr) + , _parent(nullptr) + , _block(nullptr) +{} + +bool +Copy::pre_process(PreProcessContext& ctx) +{ + std::lock_guard<Store::Mutex> lock(_engine.store()->mutex()); + + if (uri_is_path(_msg.old_uri)) { + // Old URI is a path within the engine + const Raul::Path old_path = uri_to_path(_msg.old_uri); + + // Find the old node + const Store::iterator i = _engine.store()->find(old_path); + if (i == _engine.store()->end()) { + return Event::pre_process_done(Status::NOT_FOUND, old_path); + } + + // Ensure it is a block (ports are not supported for now) + if (!(_old_block = dynamic_ptr_cast<BlockImpl>(i->second))) { + return Event::pre_process_done(Status::BAD_OBJECT_TYPE, old_path); + } + + if (uri_is_path(_msg.new_uri)) { + // Copy to path within the engine + return engine_to_engine(ctx); + } else if (_msg.new_uri.scheme() == "file") { + // Copy to filesystem path (i.e. save) + return engine_to_filesystem(ctx); + } else { + return Event::pre_process_done(Status::BAD_REQUEST); + } + } else if (_msg.old_uri.scheme() == "file") { + if (uri_is_path(_msg.new_uri)) { + return filesystem_to_engine(ctx); + } else { + // Ingen is not your file manager + return Event::pre_process_done(Status::BAD_REQUEST); + } + } + + return Event::pre_process_done(Status::BAD_URI); +} + +bool +Copy::engine_to_engine(PreProcessContext& ctx) +{ + // Only support a single source for now + const Raul::Path new_path = uri_to_path(_msg.new_uri); + if (!Raul::Symbol::is_valid(new_path.symbol())) { + return Event::pre_process_done(Status::BAD_REQUEST); + } + + // Ensure the new node doesn't already exist + if (_engine.store()->find(new_path) != _engine.store()->end()) { + return Event::pre_process_done(Status::EXISTS, new_path); + } + + // Find new parent graph + const Raul::Path parent_path = new_path.parent(); + const Store::iterator p = _engine.store()->find(parent_path); + if (p == _engine.store()->end()) { + return Event::pre_process_done(Status::NOT_FOUND, parent_path); + } + if (!(_parent = dynamic_cast<GraphImpl*>(p->second.get()))) { + return Event::pre_process_done(Status::BAD_OBJECT_TYPE, parent_path); + } + + // Create new block + if (!(_block = dynamic_cast<BlockImpl*>( + _old_block->duplicate(_engine, Raul::Symbol(new_path.symbol()), _parent)))) { + return Event::pre_process_done(Status::INTERNAL_ERROR); + } + + _block->activate(*_engine.buffer_factory()); + + // Add block to the store and the graph's pre-processor only block list + _parent->add_block(*_block); + _engine.store()->add(_block); + + // Compile graph with new block added for insertion in audio thread + _compiled_graph = ctx.maybe_compile(*_engine.maid(), *_parent); + + return Event::pre_process_done(Status::SUCCESS); +} + +static bool +ends_with(const std::string& str, const std::string& end) +{ + if (str.length() >= end.length()) { + return !str.compare(str.length() - end.length(), end.length(), end); + } + return false; +} + +bool +Copy::engine_to_filesystem(PreProcessContext& ctx) +{ + // Ensure source is a graph + SPtr<GraphImpl> graph = dynamic_ptr_cast<GraphImpl>(_old_block); + if (!graph) { + return Event::pre_process_done(Status::BAD_OBJECT_TYPE, _msg.old_uri); + } + + if (!_engine.world()->serialiser()) { + return Event::pre_process_done(Status::INTERNAL_ERROR); + } + + std::lock_guard<std::mutex> lock(_engine.world()->rdf_mutex()); + + if (ends_with(_msg.new_uri, ".ingen") || ends_with(_msg.new_uri, ".ingen/")) { + _engine.world()->serialiser()->write_bundle(graph, URI(_msg.new_uri)); + } else { + _engine.world()->serialiser()->start_to_file(graph->path(), _msg.new_uri); + _engine.world()->serialiser()->serialise(graph); + _engine.world()->serialiser()->finish(); + } + + return Event::pre_process_done(Status::SUCCESS); +} + +bool +Copy::filesystem_to_engine(PreProcessContext& ctx) +{ + if (!_engine.world()->parser()) { + return Event::pre_process_done(Status::INTERNAL_ERROR); + } + + std::lock_guard<std::mutex> lock(_engine.world()->rdf_mutex()); + + // Old URI is a filesystem path and new URI is a path within the engine + const std::string src_path(_msg.old_uri.path()); + const Raul::Path dst_path = uri_to_path(_msg.new_uri); + boost::optional<Raul::Path> dst_parent; + boost::optional<Raul::Symbol> dst_symbol; + if (!dst_path.is_root()) { + dst_parent = dst_path.parent(); + dst_symbol = Raul::Symbol(dst_path.symbol()); + } + + _engine.world()->parser()->parse_file( + _engine.world(), _engine.world()->interface().get(), src_path, + dst_parent, dst_symbol); + + return Event::pre_process_done(Status::SUCCESS); +} + +void +Copy::execute(RunContext& context) +{ + if (_block && _compiled_graph) { + _parent->set_compiled_graph(std::move(_compiled_graph)); + } +} + +void +Copy::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS) { + _engine.broadcaster()->message(_msg); + } +} + +void +Copy::undo(Interface& target) +{ + if (uri_is_path(_msg.new_uri)) { + target.del(_msg.new_uri); + } +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Copy.hpp b/src/server/events/Copy.hpp new file mode 100644 index 00000000..5216b56e --- /dev/null +++ b/src/server/events/Copy.hpp @@ -0,0 +1,68 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_COPY_HPP +#define INGEN_EVENTS_COPY_HPP + +#include <list> + +#include "ingen/Store.hpp" +#include "raul/Path.hpp" + +#include "CompiledGraph.hpp" +#include "Event.hpp" + +namespace Ingen { +namespace Server { + +class BlockImpl; +class GraphImpl; + +namespace Events { + +/** Copy a graph object to a new path. + * \ingroup engine + */ +class Copy : public Event +{ +public: + Copy(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Copy& msg); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + void undo(Interface& target); + +private: + bool engine_to_engine(PreProcessContext& ctx); + bool engine_to_filesystem(PreProcessContext& ctx); + bool filesystem_to_engine(PreProcessContext& ctx); + + const Ingen::Copy _msg; + SPtr<BlockImpl> _old_block; + GraphImpl* _parent; + BlockImpl* _block; + MPtr<CompiledGraph> _compiled_graph; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_COPY_HPP diff --git a/src/server/events/CreateBlock.cpp b/src/server/events/CreateBlock.cpp new file mode 100644 index 00000000..d678bea3 --- /dev/null +++ b/src/server/events/CreateBlock.cpp @@ -0,0 +1,180 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/Forge.hpp" +#include "ingen/Store.hpp" +#include "ingen/URIs.hpp" +#include "raul/Maid.hpp" +#include "raul/Path.hpp" + +#include "BlockFactory.hpp" +#include "BlockImpl.hpp" +#include "Broadcaster.hpp" +#include "CreateBlock.hpp" +#include "Engine.hpp" +#include "GraphImpl.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "PreProcessContext.hpp" +#include "LV2Block.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +CreateBlock::CreateBlock(Engine& engine, + SPtr<Interface> client, + int32_t id, + SampleCount timestamp, + const Raul::Path& path, + Properties& properties) + : Event(engine, client, id, timestamp) + , _path(path) + , _properties(properties) + , _graph(nullptr) + , _block(nullptr) +{} + +bool +CreateBlock::pre_process(PreProcessContext& ctx) +{ + typedef Properties::const_iterator iterator; + + const Ingen::URIs& uris = _engine.world()->uris(); + const SPtr<Store> store = _engine.store(); + + // Check sanity of target path + if (_path.is_root()) { + return Event::pre_process_done(Status::BAD_URI, _path); + } else if (store->get(_path)) { + return Event::pre_process_done(Status::EXISTS, _path); + } else if (!(_graph = dynamic_cast<GraphImpl*>(store->get(_path.parent())))) { + return Event::pre_process_done(Status::PARENT_NOT_FOUND, _path.parent()); + } + + // Map old ingen:prototype to new lv2:prototype + auto range = _properties.equal_range(uris.ingen_prototype); + for (auto i = range.first; i != range.second;) { + const auto value = i->second; + auto next = i; + next = _properties.erase(i); + _properties.emplace(uris.lv2_prototype, value); + i = next; + } + + // Get prototype + iterator t = _properties.find(uris.lv2_prototype); + if (t == _properties.end() || !uris.forge.is_uri(t->second)) { + // Missing/invalid prototype + return Event::pre_process_done(Status::BAD_REQUEST); + } + + const URI prototype(uris.forge.str(t->second, false)); + + // Find polyphony + const iterator p = _properties.find(uris.ingen_polyphonic); + const bool polyphonic = (p != _properties.end() && + p->second.type() == uris.forge.Bool && + p->second.get<int32_t>()); + + // Find and instantiate/duplicate prototype (plugin/existing node) + if (uri_is_path(prototype)) { + // Prototype is an existing block + BlockImpl* const ancestor = dynamic_cast<BlockImpl*>( + store->get(uri_to_path(prototype))); + if (!ancestor) { + return Event::pre_process_done(Status::PROTOTYPE_NOT_FOUND, prototype); + } else if (!(_block = ancestor->duplicate( + _engine, Raul::Symbol(_path.symbol()), _graph))) { + return Event::pre_process_done(Status::CREATION_FAILED, _path); + } + + /* Replace prototype with the ancestor's. This is less informative, + but the client expects an actual LV2 plugin as prototype. */ + _properties.erase(uris.ingen_prototype); + _properties.erase(uris.lv2_prototype); + _properties.emplace(uris.lv2_prototype, + uris.forge.make_urid(ancestor->plugin()->uri())); + } else { + // Prototype is a plugin + PluginImpl* const plugin = _engine.block_factory()->plugin(prototype); + if (!plugin) { + return Event::pre_process_done(Status::PROTOTYPE_NOT_FOUND, prototype); + } + + // Load state from directory if given in properties + LilvState* state = nullptr; + auto s = _properties.find(uris.state_state); + if (s != _properties.end() && s->second.type() == uris.forge.Path) { + state = LV2Block::load_state( + _engine.world(), FilePath(s->second.ptr<char>())); + } + + // Instantiate plugin + if (!(_block = plugin->instantiate(*_engine.buffer_factory(), + Raul::Symbol(_path.symbol()), + polyphonic, + _graph, + _engine, + state))) { + return Event::pre_process_done(Status::CREATION_FAILED, _path); + } + } + + // Activate block + _block->properties().insert(_properties.begin(), _properties.end()); + _block->activate(*_engine.buffer_factory()); + + // Add block to the store and the graph's pre-processor only block list + _graph->add_block(*_block); + store->add(_block); + + /* Compile graph with new block added for insertion in audio thread + TODO: Since the block is not connected at this point, a full compilation + could be avoided and the block simply appended. */ + _compiled_graph = ctx.maybe_compile(*_engine.maid(), *_graph); + + _update.put_block(_block); + + return Event::pre_process_done(Status::SUCCESS); +} + +void +CreateBlock::execute(RunContext& context) +{ + if (_status == Status::SUCCESS && _compiled_graph) { + _graph->set_compiled_graph(std::move(_compiled_graph)); + } +} + +void +CreateBlock::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS) { + _update.send(*_engine.broadcaster()); + } +} + +void +CreateBlock::undo(Interface& target) +{ + target.del(_block->uri()); +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/CreateBlock.hpp b/src/server/events/CreateBlock.hpp new file mode 100644 index 00000000..0a29e68c --- /dev/null +++ b/src/server/events/CreateBlock.hpp @@ -0,0 +1,66 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_CREATEBLOCK_HPP +#define INGEN_EVENTS_CREATEBLOCK_HPP + +#include "ingen/Resource.hpp" + +#include "ClientUpdate.hpp" +#include "CompiledGraph.hpp" +#include "Event.hpp" + +namespace Ingen { +namespace Server { + +class BlockImpl; +class GraphImpl; + +namespace Events { + +/** An event to load a Block and insert it into a Graph. + * + * \ingroup engine + */ +class CreateBlock : public Event +{ +public: + CreateBlock(Engine& engine, + SPtr<Interface> client, + int32_t id, + SampleCount timestamp, + const Raul::Path& path, + Properties& properties); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + void undo(Interface& target); + +private: + Raul::Path _path; + Properties& _properties; + ClientUpdate _update; + GraphImpl* _graph; + BlockImpl* _block; + MPtr<CompiledGraph> _compiled_graph; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_CREATEBLOCK_HPP diff --git a/src/server/events/CreateGraph.cpp b/src/server/events/CreateGraph.cpp new file mode 100644 index 00000000..390fdd9a --- /dev/null +++ b/src/server/events/CreateGraph.cpp @@ -0,0 +1,236 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/Forge.hpp" +#include "ingen/Store.hpp" +#include "ingen/URIs.hpp" +#include "raul/Maid.hpp" +#include "raul/Path.hpp" + +#include "Broadcaster.hpp" +#include "Engine.hpp" +#include "GraphImpl.hpp" +#include "PreProcessContext.hpp" +#include "events/CreateGraph.hpp" +#include "events/CreatePort.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +CreateGraph::CreateGraph(Engine& engine, + SPtr<Interface> client, + int32_t id, + SampleCount timestamp, + const Raul::Path& path, + const Properties& properties) + : Event(engine, client, id, timestamp) + , _path(path) + , _properties(properties) + , _graph(nullptr) + , _parent(nullptr) +{} + +CreateGraph::~CreateGraph() +{ + for (Event* ev : _child_events) { + delete ev; + } +} + +void +CreateGraph::build_child_events() +{ + const Ingen::URIs& uris = _engine.world()->uris(); + + // Properties common to both ports + Properties control_properties; + control_properties.put(uris.atom_bufferType, uris.atom_Sequence); + control_properties.put(uris.atom_supports, uris.patch_Message); + control_properties.put(uris.lv2_designation, uris.lv2_control); + control_properties.put(uris.lv2_portProperty, uris.lv2_connectionOptional); + control_properties.put(uris.rdf_type, uris.atom_AtomPort); + control_properties.put(uris.rsz_minimumSize, uris.forge.make(4096)); + + // Add control port (message receive) + Properties in_properties(control_properties); + in_properties.put(uris.lv2_index, uris.forge.make(0)); + in_properties.put(uris.lv2_name, uris.forge.alloc("Control")); + in_properties.put(uris.rdf_type, uris.lv2_InputPort); + in_properties.put(uris.ingen_canvasX, uris.forge.make(32.0f), + Resource::Graph::EXTERNAL); + in_properties.put(uris.ingen_canvasY, uris.forge.make(32.0f), + Resource::Graph::EXTERNAL); + + _child_events.push_back( + new Events::CreatePort( + _engine, _request_client, -1, _time, + _path.child(Raul::Symbol("control")), + in_properties)); + + // Add notify port (message respond) + Properties out_properties(control_properties); + out_properties.put(uris.lv2_index, uris.forge.make(1)); + out_properties.put(uris.lv2_name, uris.forge.alloc("Notify")); + out_properties.put(uris.rdf_type, uris.lv2_OutputPort); + out_properties.put(uris.ingen_canvasX, uris.forge.make(128.0f), + Resource::Graph::EXTERNAL); + out_properties.put(uris.ingen_canvasY, uris.forge.make(32.0f), + Resource::Graph::EXTERNAL); + + _child_events.push_back( + new Events::CreatePort(_engine, _request_client, -1, _time, + _path.child(Raul::Symbol("notify")), + out_properties)); +} + +bool +CreateGraph::pre_process(PreProcessContext& ctx) +{ + if (_engine.store()->get(_path)) { + return Event::pre_process_done(Status::EXISTS, _path); + } + + if (!_path.is_root()) { + const Raul::Path up(_path.parent()); + if (!(_parent = dynamic_cast<GraphImpl*>(_engine.store()->get(up)))) { + return Event::pre_process_done(Status::PARENT_NOT_FOUND, up); + } + } + + const Ingen::URIs& uris = _engine.world()->uris(); + + typedef Properties::const_iterator iterator; + + uint32_t ext_poly = 1; + uint32_t int_poly = 1; + iterator p = _properties.find(uris.ingen_polyphony); + if (p != _properties.end() && p->second.type() == uris.forge.Int) { + int_poly = p->second.get<int32_t>(); + } + + if (int_poly < 1 || int_poly > 128) { + return Event::pre_process_done(Status::INVALID_POLY, _path); + } + + if (!_parent || int_poly == _parent->internal_poly()) { + ext_poly = int_poly; + } + + const Raul::Symbol symbol(_path.is_root() ? "graph" : _path.symbol()); + + // Get graph prototype + iterator t = _properties.find(uris.lv2_prototype); + if (t == _properties.end()) { + t = _properties.find(uris.lv2_prototype); + } + + if (t != _properties.end() && + uris.forge.is_uri(t->second) && + URI::is_valid(uris.forge.str(t->second, false)) && + uri_is_path(URI(uris.forge.str(t->second, false)))) { + // Create a duplicate of an existing graph + const URI prototype(uris.forge.str(t->second, false)); + GraphImpl* ancestor = dynamic_cast<GraphImpl*>( + _engine.store()->get(uri_to_path(prototype))); + if (!ancestor) { + return Event::pre_process_done(Status::PROTOTYPE_NOT_FOUND, prototype); + } else if (!(_graph = dynamic_cast<GraphImpl*>( + ancestor->duplicate(_engine, symbol, _parent)))) { + return Event::pre_process_done(Status::CREATION_FAILED, _path); + } + } else { + // Create a new graph + _graph = new GraphImpl(_engine, symbol, ext_poly, _parent, + _engine.sample_rate(), int_poly); + _graph->add_property(uris.rdf_type, uris.ingen_Graph.urid); + _graph->add_property(uris.rdf_type, + Property(uris.ingen_Block, + Resource::Graph::EXTERNAL)); + } + + _graph->set_properties(_properties); + + if (_parent) { + // Add graph to parent + _parent->add_block(*_graph); + if (_parent->enabled()) { + _graph->enable(); + } + _compiled_graph = ctx.maybe_compile(*_engine.maid(), *_parent); + } + + _graph->activate(*_engine.buffer_factory()); + + // Insert into store and build update to send to clients + _engine.store()->add(_graph); + _update.put_graph(_graph); + for (BlockImpl& block : _graph->blocks()) { + _engine.store()->add(&block); + } + + // Build and pre-process child events to create standard ports + build_child_events(); + for (Event* ev : _child_events) { + ev->pre_process(ctx); + } + + return Event::pre_process_done(Status::SUCCESS); +} + +void +CreateGraph::execute(RunContext& context) +{ + if (_graph) { + if (_parent) { + if (_compiled_graph) { + _parent->set_compiled_graph(std::move(_compiled_graph)); + } + } else { + _engine.set_root_graph(_graph); + _graph->enable(); + } + + for (Event* ev : _child_events) { + ev->execute(context); + } + } +} + +void +CreateGraph::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS) { + _update.send(*_engine.broadcaster()); + } + + if (_graph) { + for (Event* ev : _child_events) { + ev->post_process(); + } + } +} + +void +CreateGraph::undo(Interface& target) +{ + target.del(_graph->uri()); +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/CreateGraph.hpp b/src/server/events/CreateGraph.hpp new file mode 100644 index 00000000..564d553b --- /dev/null +++ b/src/server/events/CreateGraph.hpp @@ -0,0 +1,74 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_CREATEGRAPH_HPP +#define INGEN_EVENTS_CREATEGRAPH_HPP + +#include <list> + +#include "ingen/Resource.hpp" + +#include "CompiledGraph.hpp" +#include "Event.hpp" +#include "events/Get.hpp" + +namespace Ingen { +namespace Server { + +class GraphImpl; + +namespace Events { + +/** Creates a new Graph. + * + * \ingroup engine + */ +class CreateGraph : public Event +{ +public: + CreateGraph(Engine& engine, + SPtr<Interface> client, + int32_t id, + SampleCount timestamp, + const Raul::Path& path, + const Properties& properties); + + ~CreateGraph(); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + void undo(Interface& target); + + GraphImpl* graph() { return _graph; } + +private: + void build_child_events(); + + const Raul::Path _path; + Properties _properties; + ClientUpdate _update; + GraphImpl* _graph; + GraphImpl* _parent; + MPtr<CompiledGraph> _compiled_graph; + std::list<Event*> _child_events; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_CREATEGRAPH_HPP diff --git a/src/server/events/CreatePort.cpp b/src/server/events/CreatePort.cpp new file mode 100644 index 00000000..e17b8b01 --- /dev/null +++ b/src/server/events/CreatePort.cpp @@ -0,0 +1,219 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <utility> + +#include "ingen/Atom.hpp" +#include "ingen/Store.hpp" +#include "ingen/URIMap.hpp" +#include "ingen/URIs.hpp" +#include "raul/Array.hpp" +#include "raul/Path.hpp" + +#include "Broadcaster.hpp" +#include "BufferFactory.hpp" +#include "CreatePort.hpp" +#include "Driver.hpp" +#include "DuplexPort.hpp" +#include "Engine.hpp" +#include "GraphImpl.hpp" +#include "PortImpl.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +CreatePort::CreatePort(Engine& engine, + SPtr<Interface> client, + int32_t id, + SampleCount timestamp, + const Raul::Path& path, + const Properties& properties) + : Event(engine, client, id, timestamp) + , _path(path) + , _port_type(PortType::UNKNOWN) + , _buf_type(0) + , _graph(nullptr) + , _graph_port(nullptr) + , _engine_port(nullptr) + , _properties(properties) +{ + const Ingen::URIs& uris = _engine.world()->uris(); + + typedef Properties::const_iterator Iterator; + typedef std::pair<Iterator, Iterator> Range; + + const Range types = properties.equal_range(uris.rdf_type); + for (Iterator i = types.first; i != types.second; ++i) { + const Atom& type = i->second; + if (type == uris.lv2_AudioPort) { + _port_type = PortType::AUDIO; + } else if (type == uris.lv2_ControlPort) { + _port_type = PortType::CONTROL; + } else if (type == uris.lv2_CVPort) { + _port_type = PortType::CV; + } else if (type == uris.atom_AtomPort) { + _port_type = PortType::ATOM; + } else if (type == uris.lv2_InputPort) { + _flow = Flow::INPUT; + } else if (type == uris.lv2_OutputPort) { + _flow = Flow::OUTPUT; + } + } + + const Range buffer_types = properties.equal_range(uris.atom_bufferType); + for (Iterator i = buffer_types.first; i != buffer_types.second; ++i) { + if (uris.forge.is_uri(i->second)) { + _buf_type = _engine.world()->uri_map().map_uri( + uris.forge.str(i->second, false)); + } + } +} + +bool +CreatePort::pre_process(PreProcessContext& ctx) +{ + if (_port_type == PortType::UNKNOWN) { + return Event::pre_process_done(Status::UNKNOWN_TYPE, _path); + } else if (!_flow) { + return Event::pre_process_done(Status::UNKNOWN_TYPE, _path); + } else if (_path.is_root()) { + return Event::pre_process_done(Status::BAD_URI, _path); + } else if (_engine.store()->get(_path)) { + return Event::pre_process_done(Status::EXISTS, _path); + } + + const Raul::Path parent_path = _path.parent(); + Node* const parent = _engine.store()->get(parent_path); + if (!parent) { + return Event::pre_process_done(Status::PARENT_NOT_FOUND, parent_path); + } else if (!(_graph = dynamic_cast<GraphImpl*>(parent))) { + return Event::pre_process_done(Status::INVALID_PARENT, parent_path); + } else if (!_graph->parent() && _engine.activated() && + !_engine.driver()->dynamic_ports()) { + return Event::pre_process_done(Status::CREATION_FAILED, _path); + } + + const URIs& uris = _engine.world()->uris(); + BufferFactory& bufs = *_engine.buffer_factory(); + const uint32_t buf_size = bufs.default_size(_buf_type); + const int32_t old_n_ports = _graph->num_ports_non_rt(); + + typedef Properties::const_iterator PropIter; + + PropIter index_i = _properties.find(uris.lv2_index); + int32_t index = 0; + if (index_i != _properties.end()) { + // Ensure given index is sane and not taken + if (index_i->second.type() != uris.forge.Int) { + return Event::pre_process_done(Status::BAD_REQUEST); + } + + index = index_i->second.get<int32_t>(); + if (_graph->has_port_with_index(index)) { + return Event::pre_process_done(Status::BAD_INDEX); + } + } else { + // No index given, append + index = old_n_ports; + index_i = _properties.emplace(uris.lv2_index, + _engine.world()->forge().make(index)); + } + + const PropIter poly_i = _properties.find(uris.ingen_polyphonic); + const bool polyphonic = (poly_i != _properties.end() && + poly_i->second.type() == uris.forge.Bool && + poly_i->second.get<int32_t>()); + + // Create 0 value if the port requires one + Atom value; + if (_port_type == PortType::CONTROL || _port_type == PortType::CV) { + value = bufs.forge().make(0.0f); + } + + // Create port + _graph_port = new DuplexPort(bufs, _graph, Raul::Symbol(_path.symbol()), + index, + polyphonic, + _port_type, _buf_type, buf_size, + value, _flow == Flow::OUTPUT); + assert((_flow == Flow::OUTPUT && _graph_port->is_output()) || + (_flow == Flow::INPUT && _graph_port->is_input())); + _graph_port->properties().insert(_properties.begin(), _properties.end()); + + _engine.store()->add(_graph_port); + if (_flow == Flow::OUTPUT) { + _graph->add_output(*_graph_port); + } else { + _graph->add_input(*_graph_port); + } + + if (!_graph->parent()) { + _engine_port = _engine.driver()->create_port(_graph_port); + } + + _ports_array = bufs.maid().make_managed<GraphImpl::Ports>( + old_n_ports + 1, nullptr); + + _update = _graph_port->properties(); + + assert(_graph_port->index() == (uint32_t)index_i->second.get<int32_t>()); + assert(_graph->num_ports_non_rt() == (uint32_t)old_n_ports + 1); + assert(_ports_array->size() == _graph->num_ports_non_rt()); + assert(_graph_port->index() < _ports_array->size()); + return Event::pre_process_done(Status::SUCCESS); +} + +void +CreatePort::execute(RunContext& context) +{ + if (_status == Status::SUCCESS) { + const MPtr<GraphImpl::Ports>& old_ports = _graph->external_ports(); + if (old_ports) { + for (uint32_t i = 0; i < old_ports->size(); ++i) { + const auto* const old_port = (*old_ports)[i]; + assert(old_port->index() < _ports_array->size()); + (*_ports_array)[old_port->index()] = (*old_ports)[i]; + } + } + assert(!(*_ports_array)[_graph_port->index()]); + (*_ports_array)[_graph_port->index()] = _graph_port; + _graph->set_external_ports(std::move(_ports_array)); + + if (_engine_port) { + _engine.driver()->add_port(context, _engine_port); + } + } +} + +void +CreatePort::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS) { + _engine.broadcaster()->put(path_to_uri(_path), _update); + } +} + +void +CreatePort::undo(Interface& target) +{ + target.del(_graph_port->uri()); +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/CreatePort.hpp b/src/server/events/CreatePort.hpp new file mode 100644 index 00000000..a2ea7682 --- /dev/null +++ b/src/server/events/CreatePort.hpp @@ -0,0 +1,82 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_CREATEPORT_HPP +#define INGEN_EVENTS_CREATEPORT_HPP + +#include <boost/optional.hpp> + +#include "ingen/Resource.hpp" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "raul/Array.hpp" +#include "raul/Path.hpp" + +#include "BlockImpl.hpp" +#include "Event.hpp" +#include "PortType.hpp" + +namespace Ingen { +namespace Server { + +class DuplexPort; +class EnginePort; +class GraphImpl; +class PortImpl; + +namespace Events { + +/** An event to add a Port to a Graph. + * + * \ingroup engine + */ +class CreatePort : public Event +{ +public: + CreatePort(Engine& engine, + SPtr<Interface> client, + int32_t id, + SampleCount timestamp, + const Raul::Path& path, + const Properties& properties); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + void undo(Interface& target); + +private: + enum class Flow { + INPUT, + OUTPUT + }; + + Raul::Path _path; + PortType _port_type; + LV2_URID _buf_type; + GraphImpl* _graph; + DuplexPort* _graph_port; + MPtr<BlockImpl::Ports> _ports_array; ///< New external port array for Graph + EnginePort* _engine_port; ///< Driver port if on the root + Properties _properties; + Properties _update; + boost::optional<Flow> _flow; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_CREATEPORT_HPP diff --git a/src/server/events/Delete.cpp b/src/server/events/Delete.cpp new file mode 100644 index 00000000..e8f9582c --- /dev/null +++ b/src/server/events/Delete.cpp @@ -0,0 +1,216 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/Store.hpp" +#include "raul/Maid.hpp" +#include "raul/Path.hpp" + +#include "BlockImpl.hpp" +#include "Broadcaster.hpp" +#include "ControlBindings.hpp" +#include "Delete.hpp" +#include "DisconnectAll.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "EnginePort.hpp" +#include "GraphImpl.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "PreProcessContext.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Delete::Delete(Engine& engine, + SPtr<Interface> client, + FrameTime timestamp, + const Ingen::Del& msg) + : Event(engine, client, msg.seq, timestamp) + , _msg(msg) + , _engine_port(nullptr) + , _disconnect_event(nullptr) +{ + if (uri_is_path(msg.uri)) { + _path = uri_to_path(msg.uri); + } +} + +Delete::~Delete() +{ + delete _disconnect_event; + for (ControlBindings::Binding* b : _removed_bindings) { + delete b; + } +} + +bool +Delete::pre_process(PreProcessContext& ctx) +{ + const Ingen::URIs& uris = _engine.world()->uris(); + if (_path.is_root() || _path == "/control" || _path == "/notify") { + return Event::pre_process_done(Status::NOT_DELETABLE, _path); + } + + _engine.control_bindings()->get_all(_path, _removed_bindings); + + auto iter = _engine.store()->find(_path); + if (iter == _engine.store()->end()) { + return Event::pre_process_done(Status::NOT_FOUND, _path); + } + + if (!(_block = dynamic_ptr_cast<BlockImpl>(iter->second))) { + _port = dynamic_ptr_cast<DuplexPort>(iter->second); + } + + if ((!_block && !_port) || (_port && !_engine.driver()->dynamic_ports())) { + return Event::pre_process_done(Status::NOT_DELETABLE, _path); + } + + GraphImpl* parent = _block ? _block->parent_graph() : _port->parent_graph(); + if (!parent) { + return Event::pre_process_done(Status::INTERNAL_ERROR, _path); + } + + // Take a writer lock while we modify the store + std::lock_guard<Store::Mutex> lock(_engine.store()->mutex()); + + _engine.store()->remove(iter, _removed_objects); + + if (_block) { + parent->remove_block(*_block); + _disconnect_event = new DisconnectAll(_engine, parent, _block.get()); + _disconnect_event->pre_process(ctx); + _compiled_graph = ctx.maybe_compile(*_engine.maid(), *parent); + } else if (_port) { + parent->remove_port(*_port); + _disconnect_event = new DisconnectAll(_engine, parent, _port.get()); + _disconnect_event->pre_process(ctx); + + _compiled_graph = ctx.maybe_compile(*_engine.maid(), *parent); + if (parent->enabled()) { + _ports_array = parent->build_ports_array(*_engine.maid()); + assert(_ports_array->size() == parent->num_ports_non_rt()); + + // Adjust port indices if necessary and record changes for later + for (size_t i = 0; i < _ports_array->size(); ++i) { + PortImpl* const port = _ports_array->at(i); + if (port->index() != i) { + _port_index_changes.emplace( + port->path(), std::make_pair(port->index(), i)); + port->remove_property(uris.lv2_index, uris.patch_wildcard); + port->set_property( + uris.lv2_index, + _engine.buffer_factory()->forge().make((int32_t)i)); + } + } + } + + if (!parent->parent()) { + _engine_port = _engine.driver()->get_port(_port->path()); + } + } + + return Event::pre_process_done(Status::SUCCESS); +} + +void +Delete::execute(RunContext& context) +{ + if (_status != Status::SUCCESS) { + return; + } + + if (_disconnect_event) { + _disconnect_event->execute(context); + } + + if (!_removed_bindings.empty()) { + _engine.control_bindings()->remove(context, _removed_bindings); + } + + GraphImpl* parent = _block ? _block->parent_graph() : nullptr; + if (_port) { + // Adjust port indices if necessary + for (size_t i = 0; i < _ports_array->size(); ++i) { + PortImpl* const port = _ports_array->at(i); + if (port->index() != i) { + port->set_index(context, i); + } + } + + // Replace ports array in graph + parent = _port->parent_graph(); + parent->set_external_ports(std::move(_ports_array)); + + if (_engine_port) { + _engine.driver()->remove_port(context, _engine_port); + } + } + + if (parent && _compiled_graph) { + parent->set_compiled_graph(std::move(_compiled_graph)); + } +} + +void +Delete::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS && (_block || _port)) { + if (_block) { + _block->deactivate(); + } + + _engine.broadcaster()->message(_msg); + } + + if (_engine_port) { + _engine.driver()->unregister_port(*_engine_port); + delete _engine_port; + } +} + +void +Delete::undo(Interface& target) +{ + const Ingen::URIs& uris = _engine.world()->uris(); + Ingen::Forge& forge = _engine.buffer_factory()->forge(); + + auto i = _removed_objects.find(_path); + if (i != _removed_objects.end()) { + // Undo disconnect + if (_disconnect_event) { + _disconnect_event->undo(target); + } + + // Put deleted item back + target.put(_msg.uri, i->second->properties()); + + // Adjust port indices + for (const auto& c : _port_index_changes) { + if (c.first != _msg.uri.path()) { + target.set_property(path_to_uri(c.first), + uris.lv2_index, + forge.make(int32_t(c.second.first))); + } + } + } +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Delete.hpp b/src/server/events/Delete.hpp new file mode 100644 index 00000000..8b2a0a91 --- /dev/null +++ b/src/server/events/Delete.hpp @@ -0,0 +1,86 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_DELETE_HPP +#define INGEN_EVENTS_DELETE_HPP + +#include <map> +#include <vector> + +#include "ingen/Store.hpp" + +#include "CompiledGraph.hpp" +#include "ControlBindings.hpp" +#include "Event.hpp" +#include "GraphImpl.hpp" + +namespace Raul { +template<typename T> class Array; +} + +namespace Ingen { +namespace Server { + +class BlockImpl; +class DuplexPort; +class EnginePort; +class PortImpl; + +namespace Events { + +class DisconnectAll; + +/** Delete a graph object. + * \ingroup engine + */ +class Delete : public Event +{ +public: + Delete(Engine& engine, + SPtr<Interface> client, + FrameTime timestamp, + const Ingen::Del& msg); + + ~Delete(); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + void undo(Interface& target); + +private: + using IndexChange = std::pair<uint32_t, uint32_t>; + using IndexChanges = std::map<Raul::Path, IndexChange>; + + const Ingen::Del _msg; + Raul::Path _path; + SPtr<BlockImpl> _block; ///< Non-NULL iff a block + SPtr<DuplexPort> _port; ///< Non-NULL iff a port + EnginePort* _engine_port; + MPtr<GraphImpl::Ports> _ports_array; ///< New (external) ports for Graph + MPtr<CompiledGraph> _compiled_graph; ///< Graph's new process order + DisconnectAll* _disconnect_event; + Store::Objects _removed_objects; + IndexChanges _port_index_changes; + + std::vector<ControlBindings::Binding*> _removed_bindings; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_DELETE_HPP diff --git a/src/server/events/Delta.cpp b/src/server/events/Delta.cpp new file mode 100644 index 00000000..b23ae884 --- /dev/null +++ b/src/server/events/Delta.cpp @@ -0,0 +1,670 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <vector> +#include <thread> + +#include "ingen/Log.hpp" +#include "ingen/Store.hpp" +#include "ingen/URIs.hpp" +#include "raul/Maid.hpp" + +#include "Broadcaster.hpp" +#include "ControlBindings.hpp" +#include "CreateBlock.hpp" +#include "CreateGraph.hpp" +#include "CreatePort.hpp" +#include "Delta.hpp" +#include "Engine.hpp" +#include "GraphImpl.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "PortType.hpp" +#include "SetPortValue.hpp" +#include "events/Get.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Delta::Delta(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Put& msg) + : Event(engine, client, msg.seq, timestamp) + , _create_event(nullptr) + , _subject(msg.uri) + , _properties(msg.properties) + , _object(nullptr) + , _graph(nullptr) + , _binding(nullptr) + , _state(nullptr) + , _context(msg.ctx) + , _type(Type::PUT) + , _block(false) +{ + init(); +} + +Delta::Delta(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Delta& msg) + : Event(engine, client, msg.seq, timestamp) + , _create_event(nullptr) + , _subject(msg.uri) + , _properties(msg.add) + , _remove(msg.remove) + , _object(nullptr) + , _graph(nullptr) + , _binding(nullptr) + , _state(nullptr) + , _context(msg.ctx) + , _type(Type::PATCH) + , _block(false) +{ + init(); +} + +Delta::Delta(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::SetProperty& msg) + : Event(engine, client, msg.seq, timestamp) + , _create_event(nullptr) + , _subject(msg.subject) + , _properties{{msg.predicate, msg.value}} + , _object(nullptr) + , _graph(nullptr) + , _binding(nullptr) + , _state(nullptr) + , _context(msg.ctx) + , _type(Type::SET) + , _block(false) +{ + init(); +} + +Delta::~Delta() +{ + for (auto& s : _set_events) { + delete s; + } + + delete _create_event; +} + +void +Delta::init() +{ + if (_context != Resource::Graph::DEFAULT) { + for (auto& p : _properties) { + p.second.set_context(_context); + } + } + + // Set atomic execution if polyphony is to be changed + const Ingen::URIs& uris = _engine.world()->uris(); + if (_properties.count(uris.ingen_polyphonic) || + _properties.count(uris.ingen_polyphony)) { + _block = true; + } +} + +void +Delta::add_set_event(const char* port_symbol, + const void* value, + uint32_t size, + uint32_t type) +{ + BlockImpl* block = dynamic_cast<BlockImpl*>(_object); + PortImpl* port = block->port_by_symbol(port_symbol); + if (!port) { + _engine.log().warn(fmt("Unknown port `%1%' in state") % port_symbol); + return; + } + + SetPortValue* ev = new SetPortValue( + _engine, _request_client, _request_id, _time, + port, Atom(size, type, value), false, true); + + _set_events.push_back(ev); +} + +static void +s_add_set_event(const char* port_symbol, + void* user_data, + const void* value, + uint32_t size, + uint32_t type) +{ + ((Delta*)user_data)->add_set_event(port_symbol, value, size, type); +} + +static LilvNode* +get_file_node(LilvWorld* lworld, const URIs& uris, const Atom& value) +{ + if (value.type() == uris.atom_Path) { + return lilv_new_file_uri(lworld, nullptr, value.ptr<char>()); + } else if (uris.forge.is_uri(value)) { + const std::string str = uris.forge.str(value, false); + if (str.substr(0, 5) == "file:") { + return lilv_new_uri(lworld, value.ptr<char>()); + } + } + return nullptr; +} + +bool +Delta::pre_process(PreProcessContext& ctx) +{ + const Ingen::URIs& uris = _engine.world()->uris(); + + const bool is_graph_object = uri_is_path(_subject); + const bool is_client = (_subject == "ingen:/clients/this"); + const bool is_engine = (_subject == "ingen:/"); + const bool is_file = (_subject.scheme() == "file"); + + if (_type == Type::PUT && is_file) { + // Ensure type is Preset, the only supported file put + const auto t = _properties.find(uris.rdf_type); + if (t == _properties.end() || t->second != uris.pset_Preset) { + return Event::pre_process_done(Status::BAD_REQUEST, _subject); + } + + // Get "prototype" for preset (node to save state for) + const auto p = _properties.find(uris.lv2_prototype); + if (p == _properties.end()) { + return Event::pre_process_done(Status::BAD_REQUEST, _subject); + } else if (!_engine.world()->forge().is_uri(p->second)) { + return Event::pre_process_done(Status::BAD_REQUEST, _subject); + } + + const URI prot(_engine.world()->forge().str(p->second, false)); + if (!uri_is_path(prot)) { + return Event::pre_process_done(Status::BAD_URI, _subject); + } + + Node* node = _engine.store()->get(uri_to_path(prot)); + if (!node) { + return Event::pre_process_done(Status::NOT_FOUND, prot); + } + + BlockImpl* block = dynamic_cast<BlockImpl*>(node); + if (!block) { + return Event::pre_process_done(Status::BAD_OBJECT_TYPE, prot); + } + + if ((_preset = block->save_preset(_subject, _properties))) { + return Event::pre_process_done(Status::SUCCESS); + } else { + return Event::pre_process_done(Status::FAILURE); + } + } + + std::lock_guard<Store::Mutex> lock(_engine.store()->mutex()); + + _object = is_graph_object + ? static_cast<Ingen::Resource*>(_engine.store()->get(uri_to_path(_subject))) + : static_cast<Ingen::Resource*>(_engine.block_factory()->plugin(_subject)); + + if (!_object && !is_client && !is_engine && + (!is_graph_object || _type != Type::PUT)) { + return Event::pre_process_done(Status::NOT_FOUND, _subject); + } + + if (is_graph_object && !_object) { + Raul::Path path(uri_to_path(_subject)); + bool is_graph = false, is_block = false, is_port = false, is_output = false; + Ingen::Resource::type(uris, _properties, is_graph, is_block, is_port, is_output); + + if (is_graph) { + _create_event = new CreateGraph( + _engine, _request_client, _request_id, _time, path, _properties); + } else if (is_block) { + _create_event = new CreateBlock( + _engine, _request_client, _request_id, _time, path, _properties); + } else if (is_port) { + _create_event = new CreatePort( + _engine, _request_client, _request_id, _time, + path, _properties); + } + if (_create_event) { + if (_create_event->pre_process(ctx)) { + _object = _engine.store()->get(path); // Get object for setting + } else { + return Event::pre_process_done(Status::CREATION_FAILED, _subject); + } + } else { + return Event::pre_process_done(Status::BAD_OBJECT_TYPE, _subject); + } + } + + _types.reserve(_properties.size()); + + NodeImpl* obj = dynamic_cast<NodeImpl*>(_object); + + // Remove any properties removed in delta + for (const auto& r : _remove) { + const URI& key = r.first; + const Atom& value = r.second; + if (key == uris.midi_binding && value == uris.patch_wildcard) { + PortImpl* port = dynamic_cast<PortImpl*>(_object); + if (port) { + _engine.control_bindings()->get_all(port->path(), _removed_bindings); + } + } + if (_object) { + _removed.emplace(key, value); + _object->remove_property(key, value); + } else if (is_engine && key == uris.ingen_loadedBundle) { + LilvWorld* lworld = _engine.world()->lilv_world(); + LilvNode* bundle = get_file_node(lworld, uris, value); + if (bundle) { + for (const auto& p : _engine.block_factory()->plugins()) { + if (p.second->bundle_uri() == lilv_node_as_string(bundle)) { + p.second->set_is_zombie(true); + _update.del(p.second->uri()); + } + } + lilv_world_unload_bundle(lworld, bundle); + _engine.block_factory()->refresh(); + lilv_node_free(bundle); + } else { + _status = Status::BAD_VALUE; + } + } + } + + // Remove all added properties if this is a put or set + if (_object && (_type == Type::PUT || _type == Type::SET)) { + for (auto p = _properties.begin(); + p != _properties.end(); + p = _properties.upper_bound(p->first)) { + for (auto q = _object->properties().find(p->first); + q != _object->properties().end() && q->first == p->first;) { + auto next = q; + ++next; + + if (!_properties.contains(q->first, q->second)) { + const auto r = std::make_pair(q->first, q->second); + _object->properties().erase(q); + _object->on_property_removed(r.first, r.second); + _removed.insert(r); + } + + q = next; + } + } + } + + for (const auto& p : _properties) { + const URI& key = p.first; + const Property& value = p.second; + SpecialType op = SpecialType::NONE; + if (obj) { + Resource& resource = *obj; + if (value != uris.patch_wildcard) { + if (resource.add_property(key, value, value.context())) { + _added.emplace(key, value); + } + } + + BlockImpl* block = nullptr; + PortImpl* port = dynamic_cast<PortImpl*>(_object); + if (port) { + if (key == uris.ingen_broadcast) { + if (value.type() == uris.forge.Bool) { + op = SpecialType::ENABLE_BROADCAST; + } else { + _status = Status::BAD_VALUE_TYPE; + } + } else if (key == uris.ingen_value || key == uris.ingen_activity) { + SetPortValue* ev = new SetPortValue( + _engine, _request_client, _request_id, _time, port, value, + key == uris.ingen_activity); + _set_events.push_back(ev); + } else if (key == uris.midi_binding) { + if (port->is_a(PortType::CONTROL) || port->is_a(PortType::CV)) { + if (value == uris.patch_wildcard) { + _engine.control_bindings()->start_learn(port); + } else if (value.type() == uris.atom_Object) { + op = SpecialType::CONTROL_BINDING; + _binding = new ControlBindings::Binding(); + } else { + _status = Status::BAD_VALUE_TYPE; + } + } else { + _status = Status::BAD_OBJECT_TYPE; + } + } else if (key == uris.lv2_index) { + op = SpecialType::PORT_INDEX; + port->set_property(key, value); + } + } else if ((block = dynamic_cast<BlockImpl*>(_object))) { + if (key == uris.midi_binding && value == uris.patch_wildcard) { + op = SpecialType::CONTROL_BINDING; // Internal block learn + } else if (key == uris.ingen_enabled) { + if (value.type() == uris.forge.Bool) { + op = SpecialType::ENABLE; + } else { + _status = Status::BAD_VALUE_TYPE; + } + } else if (key == uris.pset_preset) { + URI uri; + if (uris.forge.is_uri(value)) { + const std::string uri_str = uris.forge.str(value, false); + if (URI::is_valid(uri_str)) { + uri = URI(uri_str); + } + } else if (value.type() == uris.forge.Path) { + uri = URI(FilePath(value.ptr<char>())); + } + + if (!uri.empty()) { + op = SpecialType::PRESET; + if ((_state = block->load_preset(uri))) { + lilv_state_emit_port_values( + _state, s_add_set_event, this); + } else { + _engine.log().warn(fmt("Failed to load preset <%1%>\n") % uri); + } + } else { + _status = Status::BAD_VALUE; + } + } + } + + if ((_graph = dynamic_cast<GraphImpl*>(_object))) { + if (key == uris.ingen_enabled) { + if (value.type() == uris.forge.Bool) { + op = SpecialType::ENABLE; + // FIXME: defer this until all other metadata has been processed + if (value.get<int32_t>() && !_graph->enabled()) { + if (!(_compiled_graph = compile(*_engine.maid(), *_graph))) { + _status = Status::COMPILATION_FAILED; + } + } + } else { + _status = Status::BAD_VALUE_TYPE; + } + } else if (key == uris.ingen_polyphony) { + if (value.type() == uris.forge.Int) { + if (value.get<int32_t>() < 1 || value.get<int32_t>() > 128) { + _status = Status::INVALID_POLY; + } else { + op = SpecialType::POLYPHONY; + _graph->prepare_internal_poly( + *_engine.buffer_factory(), value.get<int32_t>()); + } + } else { + _status = Status::BAD_VALUE_TYPE; + } + } + } + + if (!_create_event && key == uris.ingen_polyphonic) { + GraphImpl* parent = dynamic_cast<GraphImpl*>(obj->parent()); + if (!parent) { + _status = Status::BAD_OBJECT_TYPE; + } else if (value.type() != uris.forge.Bool) { + _status = Status::BAD_VALUE_TYPE; + } else { + op = SpecialType::POLYPHONIC; + obj->set_property(key, value, value.context()); + BlockImpl* block = dynamic_cast<BlockImpl*>(obj); + if (block) { + block->set_polyphonic(value.get<int32_t>()); + } + if (value.get<int32_t>()) { + obj->prepare_poly(*_engine.buffer_factory(), parent->internal_poly()); + } else { + obj->prepare_poly(*_engine.buffer_factory(), 1); + } + } + } + } else if (is_client && key == uris.ingen_broadcast) { + _engine.broadcaster()->set_broadcast( + _request_client, value.get<int32_t>()); + } else if (is_engine && key == uris.ingen_loadedBundle) { + LilvWorld* lworld = _engine.world()->lilv_world(); + LilvNode* bundle = get_file_node(lworld, uris, value); + if (bundle) { + lilv_world_load_bundle(lworld, bundle); + const std::set<PluginImpl*> new_plugins = + _engine.block_factory()->refresh(); + + for (PluginImpl* p : new_plugins) { + if (p->bundle_uri() == lilv_node_as_string(bundle)) { + _update.put_plugin(p); + } + } + lilv_node_free(bundle); + } else { + _status = Status::BAD_VALUE; + } + } + + if (_status != Status::NOT_PREPARED) { + break; + } + + _types.push_back(op); + } + + for (auto& s : _set_events) { + s->pre_process(ctx); + } + + return Event::pre_process_done( + _status == Status::NOT_PREPARED ? Status::SUCCESS : _status, + _subject); +} + +void +Delta::execute(RunContext& context) +{ + if (_status != Status::SUCCESS || _preset) { + return; + } + + const Ingen::URIs& uris = _engine.world()->uris(); + + if (_create_event) { + _create_event->set_time(_time); + _create_event->execute(context); + } + + for (auto& s : _set_events) { + s->set_time(_time); + s->execute(context); + } + + if (!_removed_bindings.empty()) { + _engine.control_bindings()->remove(context, _removed_bindings); + } + + NodeImpl* const object = dynamic_cast<NodeImpl*>(_object); + BlockImpl* const block = dynamic_cast<BlockImpl*>(_object); + PortImpl* const port = dynamic_cast<PortImpl*>(_object); + + std::vector<SpecialType>::const_iterator t = _types.begin(); + for (const auto& p : _properties) { + const URI& key = p.first; + const Atom& value = p.second; + switch (*t++) { + case SpecialType::ENABLE_BROADCAST: + if (port) { + port->enable_monitoring(value.get<int32_t>()); + } + break; + case SpecialType::ENABLE: + if (_graph) { + if (value.get<int32_t>()) { + if (_compiled_graph) { + _graph->set_compiled_graph(std::move(_compiled_graph)); + } + _graph->enable(); + } else { + _graph->disable(context); + } + } else if (block) { + block->set_enabled(value.get<int32_t>()); + } + break; + case SpecialType::POLYPHONIC: { + GraphImpl* parent = reinterpret_cast<GraphImpl*>(object->parent()); + if (value.get<int32_t>()) { + object->apply_poly(context, parent->internal_poly_process()); + } else { + object->apply_poly(context, 1); + } + } break; + case SpecialType::POLYPHONY: + if (!_graph->apply_internal_poly(context, + *_engine.buffer_factory(), + *_engine.maid(), + value.get<int32_t>())) { + _status = Status::INTERNAL_ERROR; + } + break; + case SpecialType::PORT_INDEX: + if (port) { + port->set_index(context, value.get<int32_t>()); + } + break; + case SpecialType::CONTROL_BINDING: + if (port) { + if (!_engine.control_bindings()->set_port_binding(context, port, _binding, value)) { + _status = Status::BAD_VALUE; + } + } else if (block) { + if (uris.ingen_Internal == block->plugin_impl()->type()) { + block->learn(); + } + } + break; + case SpecialType::PRESET: + block->set_enabled(false); + break; + case SpecialType::NONE: + if (port) { + if (key == uris.lv2_minimum) { + port->set_minimum(value); + } else if (key == uris.lv2_maximum) { + port->set_maximum(value); + } + } + case SpecialType::LOADED_BUNDLE: + break; + } + } +} + +void +Delta::post_process() +{ + if (_state) { + BlockImpl* block = dynamic_cast<BlockImpl*>(_object); + if (block) { + block->apply_state(_engine.sync_worker(), _state); + block->set_enabled(true); + } + lilv_state_free(_state); + } + + Broadcaster::Transfer t(*_engine.broadcaster()); + + if (_create_event) { + _create_event->post_process(); + if (_create_event->status() != Status::SUCCESS) { + return; // Creation failed, nothing else to do + } + } + + for (auto& s : _set_events) { + if (s->synthetic() || s->status() != Status::SUCCESS) { + s->post_process(); // Set failed, report error + } + } + + if (respond() == Status::SUCCESS) { + _update.send(*_engine.broadcaster()); + + switch (_type) { + case Type::SET: + /* Kludge to avoid feedback for set events only. The GUI + depends on put responses to e.g. initially place blocks. + Some more sensible way of controlling this is needed. */ + if (_mode == Mode::NORMAL) { + _engine.broadcaster()->set_ignore_client(_request_client); + } + _engine.broadcaster()->set_property( + _subject, + _properties.begin()->first, + _properties.begin()->second); + if (_mode == Mode::NORMAL) { + _engine.broadcaster()->clear_ignore_client(); + } + break; + case Type::PUT: + if (_type == Type::PUT && _subject.scheme() == "file") { + // Preset save + ClientUpdate response; + response.put(_preset->uri(), _preset->properties()); + response.send(*_engine.broadcaster()); + } else { + // Graph object put + _engine.broadcaster()->put(_subject, _properties, _context); + } + break; + case Type::PATCH: + _engine.broadcaster()->delta(_subject, _remove, _properties, _context); + break; + } + } +} + +void +Delta::undo(Interface& target) +{ + if (_create_event) { + _create_event->undo(target); + } else if (_type == Type::PATCH) { + target.delta(_subject, _added, _removed, _context); + } else if (_type == Type::SET || _type == Type::PUT) { + if (_removed.size() == 1) { + target.set_property(_subject, + _removed.begin()->first, + _removed.begin()->second, + _context); + } else if (_removed.empty()) { + target.delta(_subject, _added, {}, _context); + } else { + target.put(_subject, _removed, _context); + } + } +} + +Event::Execution +Delta::get_execution() const +{ + return _block ? Execution::ATOMIC : Execution::NORMAL; +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Delta.hpp b/src/server/events/Delta.hpp new file mode 100644 index 00000000..af337b57 --- /dev/null +++ b/src/server/events/Delta.hpp @@ -0,0 +1,133 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_DELTA_HPP +#define INGEN_EVENTS_DELTA_HPP + +#include <vector> + +#include <boost/optional.hpp> + +#include "lilv/lilv.h" + +#include "CompiledGraph.hpp" +#include "ControlBindings.hpp" +#include "Event.hpp" +#include "PluginImpl.hpp" + +namespace Ingen { + +class Resource; + +namespace Server { + +class Engine; +class GraphImpl; +class RunContext; + +namespace Events { + +class SetPortValue; + +/** Set properties of a graph object. + * \ingroup engine + */ +class Delta : public Event +{ +public: + Delta(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Put& msg); + + Delta(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Delta& msg); + + Delta(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::SetProperty& msg); + + ~Delta(); + + void add_set_event(const char* port_symbol, + const void* value, + uint32_t size, + uint32_t type); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + void undo(Interface& target); + + Execution get_execution() const; + +private: + enum class Type { + SET, + PUT, + PATCH + }; + + enum class SpecialType { + NONE, + ENABLE, + ENABLE_BROADCAST, + POLYPHONY, + POLYPHONIC, + PORT_INDEX, + CONTROL_BINDING, + PRESET, + LOADED_BUNDLE + }; + + typedef std::vector<SetPortValue*> SetEvents; + + void init(); + + Event* _create_event; + SetEvents _set_events; + std::vector<SpecialType> _types; + std::vector<SpecialType> _remove_types; + URI _subject; + Properties _properties; + Properties _remove; + ClientUpdate _update; + Ingen::Resource* _object; + GraphImpl* _graph; + MPtr<CompiledGraph> _compiled_graph; + ControlBindings::Binding* _binding; + LilvState* _state; + Resource::Graph _context; + Type _type; + + Properties _added; + Properties _removed; + + std::vector<ControlBindings::Binding*> _removed_bindings; + + boost::optional<Resource> _preset; + + bool _block; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_DELTA_HPP diff --git a/src/server/events/Disconnect.cpp b/src/server/events/Disconnect.cpp new file mode 100644 index 00000000..4553c8a2 --- /dev/null +++ b/src/server/events/Disconnect.cpp @@ -0,0 +1,224 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <set> + +#include "ingen/Store.hpp" +#include "raul/Maid.hpp" +#include "raul/Path.hpp" + +#include "ArcImpl.hpp" +#include "Broadcaster.hpp" +#include "Buffer.hpp" +#include "DuplexPort.hpp" +#include "Engine.hpp" +#include "GraphImpl.hpp" +#include "InputPort.hpp" +#include "PortImpl.hpp" +#include "PreProcessContext.hpp" +#include "RunContext.hpp" +#include "ThreadManager.hpp" +#include "events/Disconnect.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Disconnect::Disconnect(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Disconnect& msg) + : Event(engine, client, msg.seq, timestamp) + , _msg(msg) + , _graph(nullptr) + , _impl(nullptr) +{ +} + +Disconnect::~Disconnect() +{ + delete _impl; +} + +Disconnect::Impl::Impl(Engine& e, + GraphImpl* graph, + PortImpl* t, + InputPort* h) + : _engine(e) + , _tail(t) + , _head(h) + , _arc(graph->remove_arc(_tail, _head)) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + BlockImpl* const tail_block = _tail->parent_block(); + BlockImpl* const head_block = _head->parent_block(); + + // Remove tail from head's providers + auto hp = head_block->providers().find(tail_block); + if (hp != head_block->providers().end()) { + head_block->providers().erase(hp); + } + + // Remove head from tail's providers + auto td = tail_block->dependants().find(head_block); + if (td != tail_block->dependants().end()) { + tail_block->dependants().erase(td); + } + + _head->decrement_num_arcs(); + + if (_head->num_arcs() == 0) { + if (!_head->is_driver_port()) { + BufferFactory& bufs = *_engine.buffer_factory(); + _voices = bufs.maid().make_managed<PortImpl::Voices>(_head->poly()); + _head->pre_get_buffers(bufs, _voices, _head->poly()); + + if (_head->is_a(PortType::CONTROL) || + _head->is_a(PortType::CV)) { + // Reset buffer to control value + const float value = _head->value().get<float>(); + for (uint32_t i = 0; i < _voices->size(); ++i) { + Buffer* buf = _voices->at(i).buffer.get(); + buf->set_block(value, 0, e.block_length()); + } + } else { + for (uint32_t i = 0; i < _voices->size(); ++i) { + _voices->at(i).buffer->clear(); + } + } + } + } +} + +bool +Disconnect::pre_process(PreProcessContext& ctx) +{ + std::lock_guard<Store::Mutex> lock(_engine.store()->mutex()); + + if (_msg.tail.parent().parent() != _msg.head.parent().parent() + && _msg.tail.parent() != _msg.head.parent().parent() + && _msg.tail.parent().parent() != _msg.head.parent()) { + return Event::pre_process_done(Status::PARENT_DIFFERS, _msg.head); + } + + PortImpl* tail = dynamic_cast<PortImpl*>(_engine.store()->get(_msg.tail)); + if (!tail) { + return Event::pre_process_done(Status::PORT_NOT_FOUND, _msg.tail); + } + + PortImpl* head = dynamic_cast<PortImpl*>(_engine.store()->get(_msg.head)); + if (!head) { + return Event::pre_process_done(Status::PORT_NOT_FOUND, _msg.head); + } + + BlockImpl* const tail_block = tail->parent_block(); + BlockImpl* const head_block = head->parent_block(); + + if (tail_block->parent_graph() != head_block->parent_graph()) { + // Arc to a graph port from inside the graph + assert(tail_block->parent() == head_block || head_block->parent() == tail_block); + if (tail_block->parent() == head_block) { + _graph = dynamic_cast<GraphImpl*>(head_block); + } else { + _graph = dynamic_cast<GraphImpl*>(tail_block); + } + } else if (tail_block == head_block && dynamic_cast<GraphImpl*>(tail_block)) { + // Arc from a graph input to a graph output (pass through) + _graph = dynamic_cast<GraphImpl*>(tail_block); + } else { + // Normal arc between blocks with the same parent + _graph = tail_block->parent_graph(); + } + + if (!_graph) { + return Event::pre_process_done(Status::INTERNAL_ERROR, _msg.head); + } else if (!_graph->has_arc(tail, head)) { + return Event::pre_process_done(Status::NOT_FOUND, _msg.head); + } + + if (tail_block == nullptr || head_block == nullptr) { + return Event::pre_process_done(Status::PARENT_NOT_FOUND, _msg.head); + } + + _impl = new Impl(_engine, + _graph, + dynamic_cast<PortImpl*>(tail), + dynamic_cast<InputPort*>(head)); + + _compiled_graph = ctx.maybe_compile(*_engine.maid(), *_graph); + + return Event::pre_process_done(Status::SUCCESS); +} + +bool +Disconnect::Impl::execute(RunContext& context, bool set_head_buffers) +{ + if (!_arc) { + return false; + } + + _head->remove_arc(*_arc.get()); + if (_head->is_driver_port()) { + return true; + } + + if (set_head_buffers) { + if (_voices) { + _head->set_voices(context, std::move(_voices)); + } else { + _head->setup_buffers(context, *_engine.buffer_factory(), _head->poly()); + } + _head->connect_buffers(); + } else { + _head->recycle_buffers(); + } + + return true; +} + +void +Disconnect::execute(RunContext& context) +{ + if (_status == Status::SUCCESS) { + if (_impl->execute(context, true)) { + if (_compiled_graph) { + _graph->set_compiled_graph(std::move(_compiled_graph)); + } + } else { + _status = Status::NOT_FOUND; + } + } +} + +void +Disconnect::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS) { + _engine.broadcaster()->message(_msg); + } +} + +void +Disconnect::undo(Interface& target) +{ + target.connect(_msg.tail, _msg.head); +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Disconnect.hpp b/src/server/events/Disconnect.hpp new file mode 100644 index 00000000..44290d7c --- /dev/null +++ b/src/server/events/Disconnect.hpp @@ -0,0 +1,87 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_DISCONNECT_HPP +#define INGEN_EVENTS_DISCONNECT_HPP + +#include "raul/Path.hpp" + +#include "BufferFactory.hpp" +#include "CompiledGraph.hpp" +#include "Event.hpp" +#include "GraphImpl.hpp" +#include "types.hpp" + +namespace Raul { +template <typename T> class Array; +} + +namespace Ingen { +namespace Server { + +class InputPort; +class PortImpl; + +namespace Events { + +/** Remove an Arc between two Ports. + * + * \ingroup engine + */ +class Disconnect : public Event +{ +public: + Disconnect(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Disconnect& msg); + + ~Disconnect(); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + void undo(Interface& target); + + class Impl { + public: + Impl(Engine& e, GraphImpl* graph, PortImpl* t, InputPort* h); + + bool execute(RunContext& context, bool set_head_buffers); + + inline PortImpl* tail() { return _tail; } + inline InputPort* head() { return _head; } + + private: + Engine& _engine; + PortImpl* _tail; + InputPort* _head; + SPtr<ArcImpl> _arc; + MPtr<PortImpl::Voices> _voices; + }; + +private: + const Ingen::Disconnect _msg; + GraphImpl* _graph; + Impl* _impl; + MPtr<CompiledGraph> _compiled_graph; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_DISCONNECT_HPP diff --git a/src/server/events/DisconnectAll.cpp b/src/server/events/DisconnectAll.cpp new file mode 100644 index 00000000..11311d12 --- /dev/null +++ b/src/server/events/DisconnectAll.cpp @@ -0,0 +1,176 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <set> + +#include "ingen/Store.hpp" +#include "raul/Array.hpp" +#include "raul/Maid.hpp" +#include "raul/Path.hpp" + +#include "ArcImpl.hpp" +#include "BlockImpl.hpp" +#include "Broadcaster.hpp" +#include "Engine.hpp" +#include "GraphImpl.hpp" +#include "InputPort.hpp" +#include "PortImpl.hpp" +#include "PreProcessContext.hpp" +#include "events/Disconnect.hpp" +#include "events/DisconnectAll.hpp" +#include "util.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +DisconnectAll::DisconnectAll(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::DisconnectAll& msg) + : Event(engine, client, msg.seq, timestamp) + , _msg(msg) + , _parent(nullptr) + , _block(nullptr) + , _port(nullptr) + , _deleting(false) +{ +} + +/** Internal version for use by other events. + */ +DisconnectAll::DisconnectAll(Engine& engine, + GraphImpl* parent, + Node* object) + : Event(engine) + , _msg{0, parent->path(), object->path()} + , _parent(parent) + , _block(dynamic_cast<BlockImpl*>(object)) + , _port(dynamic_cast<PortImpl*>(object)) + , _deleting(true) +{ +} + +DisconnectAll::~DisconnectAll() +{ + for (auto& i : _impls) { + delete i; + } +} + +bool +DisconnectAll::pre_process(PreProcessContext& ctx) +{ + std::unique_lock<Store::Mutex> lock(_engine.store()->mutex(), + std::defer_lock); + + if (!_deleting) { + lock.lock(); + + _parent = dynamic_cast<GraphImpl*>(_engine.store()->get(_msg.graph)); + if (!_parent) { + return Event::pre_process_done(Status::PARENT_NOT_FOUND, + _msg.graph); + } + + NodeImpl* const object = dynamic_cast<NodeImpl*>( + _engine.store()->get(_msg.path)); + if (!object) { + return Event::pre_process_done(Status::NOT_FOUND, _msg.path); + } + + if (object->parent_graph() != _parent + && object->parent()->parent_graph() != _parent) { + return Event::pre_process_done(Status::INVALID_PARENT, _msg.graph); + } + + // Only one of these will succeed + _block = dynamic_cast<BlockImpl*>(object); + _port = dynamic_cast<PortImpl*>(object); + + if (!_block && !_port) { + return Event::pre_process_done(Status::INTERNAL_ERROR, _msg.path); + } + } + + // Find set of arcs to remove + std::set<ArcImpl*> to_remove; + for (const auto& a : _parent->arcs()) { + ArcImpl* const arc = (ArcImpl*)a.second.get(); + if (_block) { + if (arc->tail()->parent_block() == _block + || arc->head()->parent_block() == _block) { + to_remove.insert(arc); + } + } else if (_port) { + if (arc->tail() == _port || arc->head() == _port) { + to_remove.insert(arc); + } + } + } + + // Create disconnect events (which erases from _parent->arcs()) + for (const auto& a : to_remove) { + _impls.push_back(new Disconnect::Impl( + _engine, _parent, + dynamic_cast<PortImpl*>(a->tail()), + dynamic_cast<InputPort*>(a->head()))); + } + + if (!_deleting && ctx.must_compile(*_parent)) { + if (!(_compiled_graph = compile(*_engine.maid(), *_parent))) { + return Event::pre_process_done(Status::COMPILATION_FAILED); + } + } + + return Event::pre_process_done(Status::SUCCESS); +} + +void +DisconnectAll::execute(RunContext& context) +{ + if (_status == Status::SUCCESS) { + for (auto& i : _impls) { + i->execute(context, + !_deleting || (i->head()->parent_block() != _block)); + } + } + + if (_compiled_graph) { + _parent->set_compiled_graph(std::move(_compiled_graph)); + } +} + +void +DisconnectAll::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS) { + _engine.broadcaster()->message(_msg); + } +} + +void +DisconnectAll::undo(Interface& target) +{ + for (auto& i : _impls) { + target.connect(i->tail()->path(), i->head()->path()); + } +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/DisconnectAll.hpp b/src/server/events/DisconnectAll.hpp new file mode 100644 index 00000000..947e538f --- /dev/null +++ b/src/server/events/DisconnectAll.hpp @@ -0,0 +1,78 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_DISCONNECTALL_HPP +#define INGEN_EVENTS_DISCONNECTALL_HPP + +#include <list> + +#include "raul/Path.hpp" + +#include "CompiledGraph.hpp" +#include "Disconnect.hpp" +#include "Event.hpp" + +namespace Ingen { +namespace Server { + +class BlockImpl; +class GraphImpl; +class PortImpl; + +namespace Events { + +class Disconnect; + +/** An event to disconnect all connections to a Block. + * + * \ingroup engine + */ +class DisconnectAll : public Event +{ +public: + DisconnectAll(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::DisconnectAll& msg); + + DisconnectAll(Engine& engine, + GraphImpl* parent, + Node* object); + + ~DisconnectAll(); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + void undo(Interface& target); + +private: + typedef std::list<Disconnect::Impl*> Impls; + + const Ingen::DisconnectAll _msg; + GraphImpl* _parent; + BlockImpl* _block; + PortImpl* _port; + Impls _impls; + MPtr<CompiledGraph> _compiled_graph; + bool _deleting; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_DISCONNECTALL_HPP diff --git a/src/server/events/Get.cpp b/src/server/events/Get.cpp new file mode 100644 index 00000000..e53e8c41 --- /dev/null +++ b/src/server/events/Get.cpp @@ -0,0 +1,111 @@ +/* + This file is part of Ingen. + Copyright 2007-2017 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <utility> + +#include "ingen/Interface.hpp" +#include "ingen/Node.hpp" +#include "ingen/Store.hpp" + +#include "BlockImpl.hpp" +#include "Broadcaster.hpp" +#include "BufferFactory.hpp" +#include "Engine.hpp" +#include "Get.hpp" +#include "GraphImpl.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Get::Get(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Get& msg) + : Event(engine, client, msg.seq, timestamp) + , _msg(msg) + , _object(nullptr) + , _plugin(nullptr) +{} + +bool +Get::pre_process(PreProcessContext& ctx) +{ + std::lock_guard<Store::Mutex> lock(_engine.store()->mutex()); + + const auto& uri = _msg.subject; + if (uri == "ingen:/plugins") { + _plugins = _engine.block_factory()->plugins(); + return Event::pre_process_done(Status::SUCCESS); + } else if (uri == "ingen:/engine") { + return Event::pre_process_done(Status::SUCCESS); + } else if (uri_is_path(uri)) { + if ((_object = _engine.store()->get(uri_to_path(uri)))) { + const BlockImpl* block = nullptr; + const GraphImpl* graph = nullptr; + const PortImpl* port = nullptr; + if ((graph = dynamic_cast<const GraphImpl*>(_object))) { + _response.put_graph(graph); + } else if ((block = dynamic_cast<const BlockImpl*>(_object))) { + _response.put_block(block); + } else if ((port = dynamic_cast<const PortImpl*>(_object))) { + _response.put_port(port); + } else { + return Event::pre_process_done(Status::BAD_OBJECT_TYPE, uri); + } + return Event::pre_process_done(Status::SUCCESS); + } + return Event::pre_process_done(Status::NOT_FOUND, uri); + } else if ((_plugin = _engine.block_factory()->plugin(uri))) { + _response.put_plugin(_plugin); + return Event::pre_process_done(Status::SUCCESS); + } else { + return Event::pre_process_done(Status::NOT_FOUND, uri); + } +} + +void +Get::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS && _request_client) { + if (_msg.subject == "ingen:/plugins") { + _engine.broadcaster()->send_plugins_to(_request_client.get(), _plugins); + } else if (_msg.subject == "ingen:/engine") { + // TODO: Keep a proper RDF model of the engine + URIs& uris = _engine.world()->uris(); + Properties props = { + { uris.param_sampleRate, + uris.forge.make(int32_t(_engine.sample_rate())) }, + { uris.bufsz_maxBlockLength, + uris.forge.make(int32_t(_engine.block_length())) }, + { uris.ingen_numThreads, + uris.forge.make(int32_t(_engine.n_threads())) } }; + + const Properties load_props = _engine.load_properties(); + props.insert(load_props.begin(), load_props.end()); + _request_client->put(URI("ingen:/engine"), props); + } else { + _response.send(*_request_client); + } + } +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Get.hpp b/src/server/events/Get.hpp new file mode 100644 index 00000000..7392550f --- /dev/null +++ b/src/server/events/Get.hpp @@ -0,0 +1,65 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_GET_HPP +#define INGEN_EVENTS_GET_HPP + +#include <vector> + +#include "BlockFactory.hpp" +#include "ClientUpdate.hpp" +#include "Event.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { + +class BlockImpl; +class GraphImpl; +class PluginImpl; +class PortImpl; + +namespace Events { + +/** A request from a client to send an object. + * + * \ingroup engine + */ +class Get : public Event +{ +public: + Get(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Get& msg); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context) {} + void post_process(); + +private: + const Ingen::Get _msg; + const Node* _object; + PluginImpl* _plugin; + BlockFactory::Plugins _plugins; + ClientUpdate _response; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_GET_HPP diff --git a/src/server/events/Mark.cpp b/src/server/events/Mark.cpp new file mode 100644 index 00000000..3c0dfaaf --- /dev/null +++ b/src/server/events/Mark.cpp @@ -0,0 +1,112 @@ +/* + This file is part of Ingen. + Copyright 2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "Engine.hpp" +#include "PreProcessContext.hpp" +#include "UndoStack.hpp" +#include "events/Mark.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Mark::Mark(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::BundleBegin& msg) + : Event(engine, client, msg.seq, timestamp) + , _type(Type::BUNDLE_BEGIN) + , _depth(0) +{} + +Mark::Mark(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::BundleEnd& msg) + : Event(engine, client, msg.seq, timestamp) + , _type(Type::BUNDLE_END) + , _depth(0) +{} + +bool +Mark::pre_process(PreProcessContext& ctx) +{ + const UPtr<UndoStack>& stack = ((_mode == Mode::UNDO) + ? _engine.redo_stack() + : _engine.undo_stack()); + + switch (_type) { + case Type::BUNDLE_BEGIN: + ctx.set_in_bundle(true); + _depth = stack->start_entry(); + break; + case Type::BUNDLE_END: + _depth = stack->finish_entry(); + ctx.set_in_bundle(false); + if (!ctx.dirty_graphs().empty()) { + for (GraphImpl* g : ctx.dirty_graphs()) { + MPtr<CompiledGraph> cg = compile(*_engine.maid(), *g); + if (cg) { + _compiled_graphs.emplace(g, std::move(cg)); + } + } + ctx.dirty_graphs().clear(); + } + break; + } + + return Event::pre_process_done(Status::SUCCESS); +} + +void +Mark::execute(RunContext& context) +{ + for (auto& g : _compiled_graphs) { + g.first->set_compiled_graph(std::move(g.second)); + } +} + +void +Mark::post_process() +{ + respond(); +} + +Event::Execution +Mark::get_execution() const +{ + if (!_engine.atomic_bundles()) { + return Execution::NORMAL; + } + + switch (_type) { + case Type::BUNDLE_BEGIN: + if (_depth == 1) { + return Execution::BLOCK; + } + break; + case Type::BUNDLE_END: + if (_depth == 0) { + return Execution::UNBLOCK; + } + break; + } + return Execution::NORMAL; +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Mark.hpp b/src/server/events/Mark.hpp new file mode 100644 index 00000000..eaeb9332 --- /dev/null +++ b/src/server/events/Mark.hpp @@ -0,0 +1,69 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_MARK_HPP +#define INGEN_EVENTS_MARK_HPP + +#include "Event.hpp" + +namespace Ingen { +namespace Server { + +class Engine; + +namespace Events { + +/** Delineate the start or end of a bundle of events. + * + * This is used to mark the ends of an undo transaction, so a single undo can + * undo the effects of many events (such as a paste or a graph load). + * + * \ingroup engine + */ +class Mark : public Event +{ +public: + Mark(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::BundleBegin& msg); + + Mark(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::BundleEnd& msg); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + + Execution get_execution() const; + +private: + enum class Type { BUNDLE_BEGIN, BUNDLE_END }; + + typedef std::map<GraphImpl*, MPtr<CompiledGraph>> CompiledGraphs; + + CompiledGraphs _compiled_graphs; + Type _type; + int _depth; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_MARK_HPP diff --git a/src/server/events/Move.cpp b/src/server/events/Move.cpp new file mode 100644 index 00000000..b0935675 --- /dev/null +++ b/src/server/events/Move.cpp @@ -0,0 +1,91 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/Store.hpp" +#include "raul/Path.hpp" + +#include "BlockImpl.hpp" +#include "Broadcaster.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "EnginePort.hpp" +#include "GraphImpl.hpp" +#include "events/Move.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Move::Move(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Move& msg) + : Event(engine, client, msg.seq, timestamp) + , _msg(msg) +{ +} + +bool +Move::pre_process(PreProcessContext& ctx) +{ + std::lock_guard<Store::Mutex> lock(_engine.store()->mutex()); + + if (!_msg.old_path.parent().is_parent_of(_msg.new_path)) { + return Event::pre_process_done(Status::PARENT_DIFFERS, _msg.new_path); + } + + const Store::iterator i = _engine.store()->find(_msg.old_path); + if (i == _engine.store()->end()) { + return Event::pre_process_done(Status::NOT_FOUND, _msg.old_path); + } + + if (_engine.store()->find(_msg.new_path) != _engine.store()->end()) { + return Event::pre_process_done(Status::EXISTS, _msg.new_path); + } + + EnginePort* eport = _engine.driver()->get_port(_msg.old_path); + if (eport) { + _engine.driver()->rename_port(_msg.old_path, _msg.new_path); + } + + _engine.store()->rename(i, _msg.new_path); + + return Event::pre_process_done(Status::SUCCESS); +} + +void +Move::execute(RunContext& context) +{ +} + +void +Move::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS) { + _engine.broadcaster()->message(_msg); + } +} + +void +Move::undo(Interface& target) +{ + target.move(_msg.new_path, _msg.old_path); +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Move.hpp b/src/server/events/Move.hpp new file mode 100644 index 00000000..459d2709 --- /dev/null +++ b/src/server/events/Move.hpp @@ -0,0 +1,57 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_MOVE_HPP +#define INGEN_EVENTS_MOVE_HPP + +#include "ingen/Store.hpp" +#include "raul/Path.hpp" + +#include "Event.hpp" + +namespace Ingen { +namespace Server { + +class GraphImpl; +class PortImpl; + +namespace Events { + +/** Move a graph object to a new path. + * \ingroup engine + */ +class Move : public Event +{ +public: + Move(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Move& msg); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + void undo(Interface& target); + +private: + const Ingen::Move _msg; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_MOVE_HPP diff --git a/src/server/events/SetPortValue.cpp b/src/server/events/SetPortValue.cpp new file mode 100644 index 00000000..62f2def6 --- /dev/null +++ b/src/server/events/SetPortValue.cpp @@ -0,0 +1,139 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/LV2Features.hpp" +#include "ingen/Store.hpp" +#include "ingen/URIs.hpp" +#include "ingen/World.hpp" + +#include "BlockImpl.hpp" +#include "Broadcaster.hpp" +#include "Buffer.hpp" +#include "ControlBindings.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "RunContext.hpp" +#include "SetPortValue.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +/** Internal */ +SetPortValue::SetPortValue(Engine& engine, + SPtr<Interface> client, + int32_t id, + SampleCount timestamp, + PortImpl* port, + const Atom& value, + bool activity, + bool synthetic) + : Event(engine, client, id, timestamp) + , _port(port) + , _value(value) + , _activity(activity) + , _synthetic(synthetic) +{ +} + +bool +SetPortValue::pre_process(PreProcessContext& ctx) +{ + Ingen::URIs& uris = _engine.world()->uris(); + if (_port->is_output()) { + return Event::pre_process_done(Status::DIRECTION_MISMATCH, _port->path()); + } + + if (!_activity) { + // Set value metadata (does not affect buffers) + _port->set_value(_value); + _port->set_property(_engine.world()->uris().ingen_value, _value); + } + + _binding = _engine.control_bindings()->port_binding(_port); + + if (_port->buffer_type() == uris.atom_Sequence) { + _buffer = _engine.buffer_factory()->get_buffer( + _port->buffer_type(), + _value.type() == uris.atom_Float ? _value.type() : 0, + _engine.buffer_factory()->default_size(_port->buffer_type())); + } + + return Event::pre_process_done(Status::SUCCESS); +} + +void +SetPortValue::execute(RunContext& context) +{ + assert(_time >= context.start() && _time <= context.end()); + apply(context); + _engine.control_bindings()->port_value_changed(context, _port, _binding, _value); +} + +void +SetPortValue::apply(RunContext& context) +{ + if (_status != Status::SUCCESS) { + return; + } + + Ingen::URIs& uris = _engine.world()->uris(); + Buffer* buf = _port->buffer(0).get(); + + if (_buffer) { + if (_port->user_buffer(context)) { + buf = _port->user_buffer(context).get(); + } else { + _port->set_user_buffer(context, _buffer); + buf = _buffer.get(); + } + } + + if (buf->type() == uris.atom_Sound || buf->type() == uris.atom_Float) { + if (_value.type() == uris.forge.Float) { + _port->set_control_value(context, _time, _value.get<float>()); + } else { + _status = Status::TYPE_MISMATCH; + } + } else if (buf->type() == uris.atom_Sequence) { + if (!buf->append_event(_time - context.start(), + _value.size(), + _value.type(), + (const uint8_t*)_value.get_body())) { + _status = Status::NO_SPACE; + } + } else if (buf->type() == uris.atom_URID) { + buf->get<LV2_Atom_URID>()->body = _value.get<int32_t>(); + } else { + _status = Status::BAD_VALUE_TYPE; + } +} + +void +SetPortValue::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS && !_activity) { + _engine.broadcaster()->set_property( + _port->uri(), + _engine.world()->uris().ingen_value, + _value); + } +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/SetPortValue.hpp b/src/server/events/SetPortValue.hpp new file mode 100644 index 00000000..4df60019 --- /dev/null +++ b/src/server/events/SetPortValue.hpp @@ -0,0 +1,71 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_SETPORTVALUE_HPP +#define INGEN_EVENTS_SETPORTVALUE_HPP + +#include "ingen/Atom.hpp" + +#include "BufferRef.hpp" +#include "ControlBindings.hpp" +#include "Event.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { + +class PortImpl; + +namespace Events { + +/** An event to change the value of a port. + * + * \ingroup engine + */ +class SetPortValue : public Event +{ +public: + SetPortValue(Engine& engine, + SPtr<Interface> client, + int32_t id, + SampleCount timestamp, + PortImpl* port, + const Atom& value, + bool activity, + bool synthetic = false); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + + bool synthetic() const { return _synthetic; } + +private: + void apply(RunContext& context); + + PortImpl* _port; + const Atom _value; + BufferRef _buffer; + ControlBindings::Key _binding; + bool _activity; + bool _synthetic; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_SETPORTVALUE_HPP diff --git a/src/server/events/Undo.cpp b/src/server/events/Undo.cpp new file mode 100644 index 00000000..e06a5951 --- /dev/null +++ b/src/server/events/Undo.cpp @@ -0,0 +1,85 @@ +/* + This file is part of Ingen. + Copyright 2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/AtomReader.hpp" + +#include "Engine.hpp" +#include "EventWriter.hpp" +#include "Undo.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Undo::Undo(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Undo& msg) + : Event(engine, client, msg.seq, timestamp) + , _is_redo(false) +{} + +Undo::Undo(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Redo& msg) + : Event(engine, client, msg.seq, timestamp) + , _is_redo(true) +{} + +bool +Undo::pre_process(PreProcessContext& ctx) +{ + const UPtr<UndoStack>& stack = _is_redo ? _engine.redo_stack() : _engine.undo_stack(); + const Event::Mode mode = _is_redo ? Event::Mode::REDO : Event::Mode::UNDO; + + if (stack->empty()) { + return Event::pre_process_done(Status::NOT_FOUND); + } + + const Event::Mode orig_mode = _engine.event_writer()->get_event_mode(); + _entry = stack->pop(); + _engine.event_writer()->set_event_mode(mode); + if (_entry.events.size() > 1) { + _engine.interface()->bundle_begin(); + } + + for (const LV2_Atom* ev : _entry.events) { + _engine.atom_interface()->write(ev); + } + + if (_entry.events.size() > 1) { + _engine.interface()->bundle_end(); + } + _engine.event_writer()->set_event_mode(orig_mode); + + return Event::pre_process_done(Status::SUCCESS); +} + +void +Undo::execute(RunContext& context) +{ +} + +void +Undo::post_process() +{ + respond(); +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Undo.hpp b/src/server/events/Undo.hpp new file mode 100644 index 00000000..af4b0d65 --- /dev/null +++ b/src/server/events/Undo.hpp @@ -0,0 +1,58 @@ +/* + This file is part of Ingen. + Copyright 2016 David Robillard <http://drobilla.net/> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_EVENTS_UNDO_HPP +#define INGEN_EVENTS_UNDO_HPP + +#include "Event.hpp" +#include "UndoStack.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +/** A request to undo the last change to the engine. + * + * \ingroup engine + */ +class Undo : public Event +{ +public: + Undo(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Undo& msg); + + Undo(Engine& engine, + SPtr<Interface> client, + SampleCount timestamp, + const Ingen::Redo& msg); + + bool pre_process(PreProcessContext& ctx); + void execute(RunContext& context); + void post_process(); + +private: + UndoStack::Entry _entry; + bool _is_redo; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_UNDO_HPP |