GoToFlow shared its YACReaderFlow flow object with SlideInitializer
objects it created. GoToFlow modified the flow object in the main thread
and SlideInitializer modified the flow object in its own thread without
any thread synchronization. The only reason this usually worked was the
timing: SlideInitializer finished its work before the next time GoToFlow
heavily accessed the flow object. This was a data race and thus
undefined behavior.
Additionally, this commit eliminates a memory leak:
a new SlideInitializer object was constructed each time a comic was
opened in YACReader. These objects were never destroyed.
SlideInitializer called PictureFlow::triggerRender() from its own
thread. This function restarted triggerTimer, which QTimer's API forbids
doing from another thread. This commit eliminates the following errors
from YACReader's standard output:
QObject::killTimer: Timers cannot be stopped from another thread
QObject::startTimer: Timers cannot be started from another thread
Without this fix YACReader on GNU/Linux often uses an entire CPU core
from launch till exit if hardware acceleration is disabled (#56).
The data race could alternatively be fixed by adding thread
synchronization code into most GoToFlow member functions. But the code
in SlideInitializer::run() is executed only once per opened comic; it is
not slow enough to justify creating a dedicated thread and suffer the
overhead of thread synchronization while the comic is being read.
I have tested several 550 pages long 1 GB comic books.
SlideInitializer::run() took up to 25 milliseconds in the worst case,
but usually 2-4 times less. For smaller 30-page comics it usually takes
less than a millisecond.
Even though mutexGoToFlow was locked in SlideInitializer::run(), there
is no need to lock it in the code moved to GoToFlow::setNumSlides():
this function is called in the main thread like all the other GoToFlow's
member functions that access the flow object. PageLoader does not have
access to the flow object.
* bug: 'else' without braces but several lines indented
* always set previousIndex in Render::updateBuffer()
As discussed in #41, the previous index was meant to be
updated in every case, but the faulty indentation
suggested another behavior.
signal is emitted so it is actually processed and we don't get dangling pointers.
Use modern signal slot syntax so that the compiler and source check tools can
verify we actually fixed the problem.