Skip to content

Commit

Permalink
Merge pull request #160 from roman-koshchei/master
Browse files Browse the repository at this point in the history
Implementing circular stack for Command Stack
  • Loading branch information
PanosK92 authored Oct 7, 2024
2 parents 3ab5045 + e057737 commit 4834f0a
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 51 deletions.
98 changes: 98 additions & 0 deletions runtime/Commands/CircularStack.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
Copyright(c) 2024 Roman Koshchei
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#pragma once

//= INCLUDES ===================
#include <optional>
//==============================

namespace Spartan
{
template <typename T>
class CircularStack {
private:
uint64_t top_item_index;
uint64_t items_count;

uint64_t buffer_capacity;
T* buffer_start;

public:
CircularStack(uint64_t capacity);
~CircularStack();

void Push(T item);
std::optional<T> Pop();
void Clear();
};

template <typename T>
CircularStack<T>::CircularStack(uint64_t capacity): buffer_capacity(capacity), items_count(0) {
this->buffer_start = new T[capacity];
this->top_item_index = capacity - 1;
}

template <typename T>
CircularStack<T>::~CircularStack() {
delete[] buffer_start;
}

template <typename T>
void CircularStack<T>::Push(T item) {
top_item_index += 1;
if (top_item_index == buffer_capacity) {
top_item_index = 0;
}

buffer_start[top_item_index] = item;

if (items_count < buffer_capacity) {
items_count += 1;
}
}

template <typename T>
std::optional<T> CircularStack<T>::Pop() {
if (items_count == 0) {
return std::nullopt;
}

T item = buffer_start[top_item_index];

if (top_item_index == 0) {
top_item_index = buffer_capacity - 1;
}
else {
top_item_index -= 1;
}

items_count -= 1;

return item;
}

template <typename T>
void CircularStack<T>::Clear() {
this->items_count = 0;
this->top_item_index = this->buffer_capacity - 1;
}
};
29 changes: 15 additions & 14 deletions runtime/Commands/CommandStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,38 +30,39 @@ using namespace std;

namespace Spartan
{
Spartan::CircularStack<shared_ptr<Command>> CommandStack::m_undo_buffer = Spartan::CircularStack<shared_ptr<Command>>(Spartan::max_undo_steps);
Spartan::CircularStack<shared_ptr<Command>> CommandStack::m_redo_buffer = Spartan::CircularStack<shared_ptr<Command>>(Spartan::max_undo_steps);

void CommandStack::Undo()
{
if (m_undo_buffer.size() == 0)
return;

// fetch
shared_ptr<Command> undo_command = m_undo_buffer.back();
m_undo_buffer.pop_back();
optional<shared_ptr<Command>> optional_undo_command = m_undo_buffer.Pop();
if (!optional_undo_command.has_value()) {
return;
}
shared_ptr<Command> undo_command = optional_undo_command.value();

// undo
undo_command->OnRevert();

// push it to the top of the redo stack
m_redo_buffer.push_back(undo_command);
m_redo_buffer.Push(undo_command);
}

void CommandStack::Redo()
{
if (m_redo_buffer.size() == 0)
return;

// fetch
shared_ptr<Command> redo_command = m_redo_buffer.back();
m_redo_buffer.pop_back();
optional<shared_ptr<Command>> optional_redo_command = m_redo_buffer.Pop();
if (!optional_redo_command.has_value()) {
return;
}
shared_ptr<Command> redo_command = optional_redo_command.value();

// redo
redo_command->OnApply();

// push it to the top of the undo stack
m_undo_buffer.push_back(redo_command);
m_undo_buffer.Push(redo_command);
}

vector<shared_ptr<Spartan::Command>> CommandStack::m_undo_buffer;
vector<shared_ptr<Spartan::Command>> CommandStack::m_redo_buffer;
}
71 changes: 34 additions & 37 deletions runtime/Commands/CommandStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,43 +24,40 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//= INCLUDES ===================
#include "Definitions.h"
#include "../Commands/Command.h"
#include "../Commands/CircularStack.h"
//==============================

namespace Spartan
{
// @todo make editor setting instead of compile time constant expression
constexpr uint64_t max_undo_steps = 128;

class SP_CLASS CommandStack
{
public:
template<typename CommandType, typename... Args>
static void Add(Args&&... args)
{
// @todo this is garbage for performance, as it has to copy the entire buffer when it's full
// could be solved by using linked lists instead of dynamic arrays (vectors)
// optimal solution may be to preallocate an array instead, and use a cursor to manage undo/redo <-- probably do this
// luckily we only store pointers so should be decent performance for now (as long as max_undo_steps doesn't grow too large)
if (m_undo_buffer.size() >= max_undo_steps)
{
m_undo_buffer.erase(m_undo_buffer.begin());
}

std::shared_ptr<Command> new_command = std::make_shared<CommandType>(std::forward<Args>(args)...);
m_undo_buffer.push_back(new_command);

// Make sure to clear the redo buffer if you apply a new command, to preserve the time continuum.
m_redo_buffer.clear();
}

/** Undoes the latest applied command */
static void Undo();

/** Redoes the latest undone command */
static void Redo();

protected:
static std::vector<std::shared_ptr<Command>> m_undo_buffer;
static std::vector<std::shared_ptr<Command>> m_redo_buffer;
};
namespace Spartan {
// @todo make editor setting instead of compile time constant expression
constexpr uint64_t max_undo_steps = 128;

class SP_CLASS CommandStack {
public:
template<typename CommandType, typename... Args>
static void Add(Args&&... args) {
// @todo this is garbage for performance, as it has to copy the entire buffer when it's full
// could be solved by using linked lists instead of dynamic arrays (vectors)
// optimal solution may be to preallocate an array instead, and use a cursor to manage undo/redo <-- probably do this
// luckily we only store pointers so should be decent performance for now (as long as max_undo_steps doesn't grow too large)
//
// CircularStack author: not sure I fully made optimal solution
// I suppose we can store it all in single stack, I will look into it

std::shared_ptr<Command> new_command = std::make_shared<CommandType>(std::forward<Args>(args)...);
m_undo_buffer.Push(new_command);

// Make sure to clear the redo buffer if you apply a new command, to preserve the time continuum.
m_redo_buffer.Clear();
}

/** Undoes the latest applied command */
static void Undo();

/** Redoes the latest undone command */
static void Redo();

protected:
static Spartan::CircularStack<std::shared_ptr<Command>> m_undo_buffer;
static Spartan::CircularStack<std::shared_ptr<Command>> m_redo_buffer;
};
}

0 comments on commit 4834f0a

Please sign in to comment.