Strokes are Working Now
Okay, good news today. I have been porting DefaultTool to the new node-replacing system and it is working now, finally, at least for the part I have already done.
The work involves combining a number of different modules in Krita: the stroke system, KoInteractionTool and its interaction strategies, and, well, the COW mechanism in Flake.
KoInteractionTool
is the class used to manage the interaction with vector shapes, and
is subclassed by DefaultTool
. The behaviours of KoInteractionTool
(and thus DefaultTool
)
are defined by KoInteractionStrategy
s. Upon the press of the mouse button, DefaultTool
creates an instance of some subclass of KoInteractionStrategy
, say, ShapeMoveStrategy
,
according to the point of the click as well as keyboard modifiers. Mouse move events after that
are all handled by the interaction strategy. When the mouse is released, the interaction strategy's
finishInteraction()
is called, and then createCommand()
. If the latter returns some
KUndo2Command
, the command is added to the undo history. Till now it sounds simple.
So how does the stroke system come in? I have experimented the interaction strategy
without the stroke system (https://invent.kde.org/tusooaw/krita/commit/638bfcd84c622d3cfefda1e5132380439dd3fdc2),
but it is really slow and even freezes Krita for a while sometimes. The stroke system
allows the modification of the shapes to run in the image thread, instead of the GUI thread.
A stroke is a set of jobs scheduled and run by a KisStrokesFacade
(here, KisImage
).
One creates the stroke in a strokes facade using a stroke strategy, which defines the behaviour
of the stroke. After creation, jobs can be added to the stroke and then executed at some later
time (it is asynchronous).
So combining these two, we have an interaction strategy and a stroke strategy --
when the interaction strategy is created, we start the stroke in the image;
when there is mouse move, we add individual jobs that change the shapes to the stroke;
when the mouse released, we end the stroke.
My discussion with Dmitry firstly tended to make the interaction strategy inherit
the stroke strategy but later it proves not a viable solution since the interaction
strategy is owned and deleted by KoInteractionTool
while the stroke strategy is owned
by the stroke --- which will lead to double deletion. So we divide it into two classes
instead: the interaction strategy starts the stroke, and the stroke strategy takes a copy
of the current active layer upon creation; when handling mouse move events, a job is added
to the stroke to modify the current layer; finally when the interaction finishes,
the interaction strategy ends the stroke and creates an undo command if the layer has been
changed.
A problem I found lies in the final stage--if the mouse is released as soon as being pressed
and no undo command is created, Krita will simply crash. It does not happen when I use gdb
to start Krita so it seems to be a timing issue though it leads to difficulty for debugging as
well. Dmitry used a self-modified version of Qt to produce a backtrace, indicating the problem
probably lies in KisCanvas2
's canvasUpdateCompressor
, which is not thread-safe. However,
after I changed it to KisThreadSafeSignalCompressor
, the crash still happens, unfortunately.
The final inspiration comes from the comments in KisThreadSafeSignalCompressor
, though. It
indicates we cannot delete the compressor from other threads --- we have to use obj->deleteLater()
instead, since it lies in the gui thread. And aha, that is the problem. The stroke strategy's destructor
is executed in the image thread; if the undo command is not created, there is only one reference to
our copied KisNode
, namely in our stroke strategy, so it has to be destructed there. However, upon
the creation of the KisNode
, it is moved into the gui thread. So it simply means we cannot let it
be deleted in the image thread. The solution looks a little bit messy, but it works:
1 | KisNode *node = m_d->originalState.data(); // take the address from KisSharedPtr |