Skip to content

Faster undo/redo keeping doc::Objects in memory (#4860)#5437

Merged
dacap merged 4 commits into
aseprite:betafrom
dacap:faster-undo-redo
Feb 13, 2026
Merged

Faster undo/redo keeping doc::Objects in memory (#4860)#5437
dacap merged 4 commits into
aseprite:betafrom
dacap:faster-undo-redo

Conversation

@dacap

@dacap dacap commented Sep 25, 2025

Copy link
Copy Markdown
Member

We can just store the ID of each object into an alternative "suspended ID" to keep the object in memory outside the current doc objects collection, and easily bring it back when we undo an operation that needs the object alive.

Anyway this is tricky and we have to fix some situations. E.g. when removing a Cel, the Cel::id() might be required when we trigger some notification, but we cannot remove the Cel first from the sprite as we need to count the number of links (we need the related sprite/layer for this).

We have to fix these issues yet (e.g. having a links counter in the CelData).

This patch introduces more memory usage for doc::Image, as we are just keeping images in memory, they are uncompressed in the undo history, but before we were compressing/decompressing with write/read_image() functions. We can fix this later compressing the data of suspended images in a background thread or something similar.

Implements part of #4860

@dacap dacap self-assigned this Sep 25, 2025

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/doc/layer.cpp
void LayerImage::suspendObject()
{
CelIterator it = getCelBegin();
CelIterator end = getCelEnd();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable 'end' of type 'CelIterator' (aka '__normal_iterator<doc::Cel **, std::vector<doc::Cel *, std::allocator<doc::Cel *>>>') can be declared 'const' [misc-const-correctness]

Suggested change
CelIterator end = getCelEnd();
CelIterator const end = getCelEnd();

Comment thread src/doc/layer.cpp
{
Layer::restoreObject();
CelIterator it = getCelBegin();
CelIterator end = getCelEnd();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable 'end' of type 'CelIterator' (aka '__normal_iterator<doc::Cel **, std::vector<doc::Cel *, std::allocator<doc::Cel *>>>') can be declared 'const' [misc-const-correctness]

Suggested change
CelIterator end = getCelEnd();
CelIterator const end = getCelEnd();

@dacap dacap force-pushed the faster-undo-redo branch 3 times, most recently from 5242107 to cc15775 Compare September 29, 2025 22:08
@dacap dacap force-pushed the faster-undo-redo branch from cc15775 to 91e1d83 Compare October 8, 2025 20:53

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/doc/image_impl.cpp

int ImageImplBase::getMemSize() const
{
return sizeof(ImageImplBase) + size_t(m_stream ? (size_t)m_stream->tellp() : 0) +

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: redundant explicit casting to the same type 'size_t' (aka 'unsigned long') as the sub-expression, remove this casting [readability-redundant-casting]

Suggested change
return sizeof(ImageImplBase) + size_t(m_stream ? (size_t)m_stream->tellp() : 0) +
return sizeof(ImageImplBase) + (m_stream ? (size_t)m_stream->tellp() : 0) +

Comment thread src/doc/image_impl.h

#include "base/buffer.h"
#include "doc/blend_funcs.h"
#include "doc/image.h"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: circular header file dependency detected while including 'image.h', please check the include path [misc-header-include-cycle]

#include "doc/image.h"
         ^

'image.h' included from here

Comment thread src/doc/image_impl.h

class ImageImplBase : public Image {
protected:
ImageBufferPtr m_buffer;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: member variable 'm_buffer' has protected visibility [misc-non-private-member-variables-in-classes]

  ImageBufferPtr m_buffer;
                 ^

Comment thread src/doc/image_impl.h
class ImageImplBase : public Image {
protected:
ImageBufferPtr m_buffer;
std::unique_ptr<std::stringstream> m_stream;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: member variable 'm_stream' has protected visibility [misc-non-private-member-variables-in-classes]

  std::unique_ptr<std::stringstream> m_stream;
                                     ^

Comment thread src/doc/image_impl.h
m_rows = (address_t*)m_buffer->buffer();
m_bits = (address_t)(m_buffer->buffer() + for_rows);

auto addr = (uint8_t*)m_bits;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: 'auto addr' can be declared as 'auto *addr' [readability-qualified-auto]

Suggested change
auto addr = (uint8_t*)m_bits;
auto *addr = (uint8_t*)m_bits;

@dacap dacap marked this pull request as ready for review February 11, 2026 18:59
…ite#4860)

We can just store the ID of each object into an alternative "suspended
ID" to keep the object in memory outside the current doc objects
collection, and easily bring it back when we undo an operation that
needs the object alive.

Anyway this is tricky and we have to fix some situations. E.g. when
removing a Cel, the Cel::id() might be required when we trigger some
notification, but we cannot remove the Cel first from the sprite as we
need to count the number of links (we need the related sprite/layer
for this).

We have to fix these issues yet (e.g. having a links counter in the
CelData).

This patch introduces more memory usage for doc::Image, as we are just
keeping images in memory, they are uncompressed in the undo history,
but before we were compressing/decompressing with write/read_image()
functions. We can fix this later compressing the data of suspended
images in a background thread or something similar.
We have added a CelData reference counter (CelData::m_refs) to known
how many cels are referencing/linked with the same CelData in the
sprite. It makes the Cel::links() member function impl a lot simpler:
without the need to access the parent layer to iterate all cels to
count all links.
@dacap dacap merged commit 731a3fb into aseprite:beta Feb 13, 2026
12 checks passed
@dacap dacap deleted the faster-undo-redo branch February 13, 2026 19:26
dacap added a commit to dacap/aseprite that referenced this pull request Feb 27, 2026
We also save the bounds of the cel as it's required to restore the
bounds when we restore the image if we SetCelImage(cel, nullptr) and
then undo.
dacap added a commit to dacap/aseprite that referenced this pull request Mar 2, 2026
We also save the bounds of the cel as it's required to restore the
bounds when we restore the image if we SetCelImage(cel, nullptr) and
then undo.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants