Skip to content

Commit

Permalink
[core] Implement a command transaction system
Browse files Browse the repository at this point in the history
  • Loading branch information
jcelerier committed Nov 19, 2024
1 parent f1c441e commit c901789
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 40 deletions.
1 change: 1 addition & 0 deletions src/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ set(SRCS
"${CMAKE_CURRENT_SOURCE_DIR}/score/command/CommandGeneratorMap.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/score/command/PropertyCommand.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/score/command/Validity/ValidityChecker.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/score/command/Dispatchers/SendStrategy.cpp"

"${CMAKE_CURRENT_SOURCE_DIR}/score/model/Component.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/score/model/ColorInterpolator.cpp"
Expand Down
15 changes: 13 additions & 2 deletions src/lib/core/command/CommandStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ void CommandStack::setIndex(int index)

void CommandStack::undoQuiet()
{
CommandTransaction t{*this};
updateStack([&]() {
auto cmd = m_undoable.pop();
cmd->undo(m_ctx);
Expand All @@ -131,6 +132,7 @@ void CommandStack::undoQuiet()

void CommandStack::redoQuiet()
{
CommandTransaction t{*this};
updateStack([&]() {
auto cmd = m_redoable.pop();
cmd->redo(m_ctx);
Expand Down Expand Up @@ -212,12 +214,17 @@ void CommandStack::validateDocument() const
m_checker();
}

CommandStackFacade::CommandStackFacade(CommandStack& stack)
CommandTransaction CommandStack::transaction()
{
return CommandTransaction{*this};
}

CommandStackFacade::CommandStackFacade(CommandStack& stack) noexcept
: m_stack{stack}
{
}

const DocumentContext& CommandStackFacade::context() const
const DocumentContext& CommandStackFacade::context() const noexcept
{
return m_stack.context();
}
Expand All @@ -242,4 +249,8 @@ void CommandStackFacade::enableActions() const
m_stack.enableActions();
}

CommandTransaction CommandStackFacade::transaction() const
{
return m_stack.transaction();
}
}
39 changes: 38 additions & 1 deletion src/lib/core/command/CommandStack.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace score
{
class Document;

struct CommandTransaction;
/**
* \class score::CommandStack
*
Expand Down Expand Up @@ -123,6 +123,9 @@ class SCORE_LIB_BASE_EXPORT CommandStack final : public QObject
void redoQuiet();
W_INVOKABLE(redoQuiet)

void beginTransaction() E_SIGNAL(SCORE_LIB_BASE_EXPORT, beginTransaction)
void endTransaction() E_SIGNAL(SCORE_LIB_BASE_EXPORT, endTransaction)

/**
* @brief push Pushes a command on the stack
* @param cmd The command
Expand Down Expand Up @@ -205,14 +208,48 @@ class SCORE_LIB_BASE_EXPORT CommandStack final : public QObject

void validateDocument() const;

CommandTransaction transaction();

private:
friend struct CommandTransaction;
QStack<score::Command*> m_undoable;
QStack<score::Command*> m_redoable;

int m_savedIndex{};
int m_inTransaction{};

DocumentValidator m_checker;
const score::DocumentContext& m_ctx;
};

struct CommandTransaction
{
CommandStack& self;

explicit CommandTransaction(CommandStack& self)
: self{self}
{
if(self.m_inTransaction == 0)
{
self.beginTransaction();
}
self.m_inTransaction++;
}
CommandTransaction(const CommandTransaction& other) = delete;
CommandTransaction(CommandTransaction&& other) = delete;
CommandTransaction& operator=(const CommandTransaction& other) = delete;
CommandTransaction& operator=(CommandTransaction&& other) = delete;

~CommandTransaction()
{
SCORE_ASSERT(self.m_inTransaction > 0);

self.m_inTransaction--;
if(self.m_inTransaction == 0)
{
self.endTransaction();
}
}
};
}
W_REGISTER_ARGTYPE(score::Command*)
3 changes: 3 additions & 0 deletions src/lib/core/document/DocumentBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,10 @@ Document* DocumentBuilder::restoreDocument(
ctx.components, writer, doc->commandStack(), [doc](score::Command* cmd) {
try
{
qDebug() << ".. replaying: " << cmd->key().toString().c_str()
<< cmd->description();
cmd->redo(doc->context());
qApp->processEvents();
return true;
}
catch(...)
Expand Down
7 changes: 5 additions & 2 deletions src/lib/score/command/CommandStackFacade.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace score
{
class Command;
class CommandStack;
struct CommandTransaction;
struct DocumentContext;

/**
Expand All @@ -22,14 +23,16 @@ class SCORE_LIB_BASE_EXPORT CommandStackFacade
score::CommandStack& m_stack;

public:
explicit CommandStackFacade(score::CommandStack& stack);
explicit CommandStackFacade(score::CommandStack& stack) noexcept;

const score::DocumentContext& context() const;
const score::DocumentContext& context() const noexcept;

void push(score::Command* cmd) const;
void redoAndPush(score::Command* cmd) const;

void disableActions() const;
void enableActions() const;

CommandTransaction transaction() const;
};
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#include <score/command/CommandStackFacade.hpp>
#include <score/command/Dispatchers/ICommandDispatcher.hpp>
#include <score/command/Dispatchers/SendStrategy.hpp>
#include <score/plugins/StringFactoryKey.hpp>
Expand Down
40 changes: 40 additions & 0 deletions src/lib/score/command/Dispatchers/SendStrategy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include "SendStrategy.hpp"

#include <score/command/CommandStackFacade.hpp>
#include <score/document/DocumentContext.hpp>

#include <core/command/CommandStack.hpp>

namespace SendStrategy
{

void Simple::send(const score::CommandStackFacade& stack, score::Command* other)
{
auto trans = stack.context().commandStack.transaction();
stack.redoAndPush(other);
}

void UndoRedo::send(const score::CommandStackFacade& stack, score::Command* other)
{
auto trans = stack.context().commandStack.transaction();
other->undo(stack.context());
stack.redoAndPush(other);
}

void Quiet::send(const score::CommandStackFacade& stack, score::Command* other)
{
stack.push(other);
}

}
namespace RedoStrategy
{
void Redo::redo(const score::DocumentContext& ctx, score::Command& cmd)
{
auto trans = ctx.commandStack.transaction();
cmd.redo(ctx);
}

void Quiet::redo(const score::DocumentContext& ctx, score::Command& cmd) { }

}
41 changes: 17 additions & 24 deletions src/lib/score/command/Dispatchers/SendStrategy.hpp
Original file line number Diff line number Diff line change
@@ -1,45 +1,38 @@
#pragma once
#include <score/command/CommandStackFacade.hpp>
#include <score_lib_base_export.h>
namespace score
{
class CommandStackFacade;
class Command;
struct DocumentContext;
}

namespace SendStrategy
{
struct Simple
struct SCORE_LIB_BASE_EXPORT Simple
{
static void send(const score::CommandStackFacade& stack, score::Command* other)
{
stack.redoAndPush(other);
}
static void send(const score::CommandStackFacade& stack, score::Command* other);
};

struct Quiet
struct SCORE_LIB_BASE_EXPORT Quiet
{
static void send(const score::CommandStackFacade& stack, score::Command* other)
{
stack.push(other);
}
static void send(const score::CommandStackFacade& stack, score::Command* other);
};

struct UndoRedo
struct SCORE_LIB_BASE_EXPORT UndoRedo
{
static void send(const score::CommandStackFacade& stack, score::Command* other)
{
other->undo(stack.context());
stack.redoAndPush(other);
}
static void send(const score::CommandStackFacade& stack, score::Command* other);
};
}
namespace RedoStrategy
{
struct Redo
struct SCORE_LIB_BASE_EXPORT Redo
{
static void redo(const score::DocumentContext& ctx, score::Command& cmd)
{
cmd.redo(ctx);
}
static void redo(const score::DocumentContext& ctx, score::Command& cmd);
};

struct Quiet
struct SCORE_LIB_BASE_EXPORT Quiet
{
static void redo(const score::DocumentContext& ctx, score::Command& cmd) { }
static void redo(const score::DocumentContext& ctx, score::Command& cmd);
};
}
1 change: 1 addition & 0 deletions src/plugins/score-lib-process/Effect/EffectLayout.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <cmath>

#include <cstdint>
// FIXME put the entirety of this as dynamic behaviour instead
namespace Process
{

Expand Down
4 changes: 4 additions & 0 deletions src/plugins/score-lib-process/Process/ExecutionContext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ struct SCORE_LIB_PROCESS_EXPORT Context
const std::shared_ptr<ossia::graph_interface>& execGraph;
const std::shared_ptr<ossia::execution_state>& execState;

std::shared_ptr<Execution::Transaction>& transaction;

void execCommand(ExecutionCommand&& cmd);

auto& context() const { return *this; }

#if !defined(_MSC_VER)
Expand Down
17 changes: 12 additions & 5 deletions src/plugins/score-lib-process/Process/ExecutionSetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@

namespace Execution
{
void Context::execCommand(ExecutionCommand&& cmd)
{
if(transaction)
transaction->push_back(std::move(cmd));
else
executionQueue.enqueue(std::move(cmd));
}

static auto enqueue_in_context(SetupContext& self) noexcept
{
return [&self]<typename F>(F&& f) {
static_assert(std::is_nothrow_move_constructible_v<F>);
self.context.executionQueue.enqueue(std::move(f));
self.context.execCommand(std::move(f));
};
}

Expand Down Expand Up @@ -72,7 +79,7 @@ void SetupContext::on_cableRemoved(const Process::Cable& c)
auto it = m_cables.find(c.id());
if(it != m_cables.end())
{
context.executionQueue.enqueue(
context.execCommand(
[cable = it->second, graph = context.execGraph] { graph->disconnect(cable); });
}
}
Expand Down Expand Up @@ -135,7 +142,7 @@ void SetupContext::connectCable(Process::Cable& cable)
}

m_cables[cable.id()] = edge;
context.executionQueue.enqueue([edge, graph = context.execGraph]() mutable {
context.execCommand([edge, graph = context.execGraph]() mutable {
graph->connect(std::move(edge));
});
}
Expand Down Expand Up @@ -428,7 +435,7 @@ void SetupContext::unregister_inlet(
if(ossia_port_it != inlets.end())
{
std::weak_ptr<ossia::execution_state> ws = context.execState;
context.executionQueue.enqueue([ws, ossia_port = ossia_port_it->second.second] {
context.execCommand([ws, ossia_port = ossia_port_it->second.second] {
if(auto state = ws.lock())
state->unregister_port(*ossia_port);
});
Expand Down Expand Up @@ -533,7 +540,7 @@ void SetupContext::unregister_node(
{
std::weak_ptr<ossia::graph_interface> wg = context.execGraph;
std::weak_ptr<ossia::execution_state> ws = context.execState;
context.executionQueue.enqueue([wg, ws, node] {
context.execCommand([wg, ws, node] {
if(auto s = ws.lock())
{
ossia::for_each_inlet(*node, [&](auto& p) { s->unregister_port(p); });
Expand Down
30 changes: 29 additions & 1 deletion src/plugins/score-plugin-engine/Execution/DocumentPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ DocumentPlugin::ContextData::ContextData(const score::DocumentContext& ctx)
, context
{
{}, ctx, m_created, {}, {}, m_execQueue, m_editionQueue, m_gcQueue, setupContext,
execGraph, execState
execGraph, execState, currentTransaction
#if(__cplusplus > 201703L) && !defined(_MSC_VER)
,
{
Expand Down Expand Up @@ -91,6 +91,34 @@ DocumentPlugin::DocumentPlugin(const score::DocumentContext& ctx, QObject* paren
connect(
this, &DocumentPlugin::finished, this, &DocumentPlugin::on_finished,
Qt::DirectConnection);

auto& cstack = ctx.document.commandStack();

connect(
&cstack, &score::CommandStack::beginTransaction, this,
[this] {
qDebug("Begin transaction");

if(m_ctxData)
{
SCORE_ASSERT(!m_ctxData->currentTransaction);
m_ctxData->currentTransaction
= std::make_shared<Execution::Transaction>(m_ctxData->context);
}
},
Qt::DirectConnection);

connect(
&cstack, &score::CommandStack::endTransaction, this,
[this] {
qDebug("Submit transaction");
if(m_ctxData)
{
m_ctxData->currentTransaction->run_all();
m_ctxData->currentTransaction.reset();
}
},
Qt::DirectConnection);
}

void DocumentPlugin::recreateBase()
Expand Down
Loading

0 comments on commit c901789

Please sign in to comment.