Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
cmeyer committed Nov 14, 2024
2 parents 5e8bfc5 + 2fa38c1 commit 5f25f64
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{% set win_exe = "NionSwift" %}
{% set summary = "A native launcher for Nion Swift." %}

{% set version = "5.0.0" %}
{% set version = "5.0.1" %}

{% set name_u = name|replace('-', '_') %}

Expand Down
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Changelog (nionswift-tool)
==========================

5.0.1 (2024-11-14)
------------------
- Fix possible crash when closing canvas items.

5.0.0 (2024-10-26)
------------------
- Eliminate dependence on NumPy for build.
Expand Down
2 changes: 1 addition & 1 deletion launcher/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ option(USE_CONSOLE "Enable console" ON)
set(APP_NAME "NionSwiftLauncher")

# app version
set(APP_VERSION "5.0.0")
set(APP_VERSION "5.0.1")

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build)

Expand Down
96 changes: 59 additions & 37 deletions launcher/DocumentWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,52 +98,52 @@ class RepaintManager
{
}

void requestRepaint(QWidget *window, PyCanvas *canvas)
void requestRepaint(PyCanvas *canvas)
{
QMutexLocker locker(&mutex);

for (const auto &p : requests)
for (const auto &r : requests)
{
if (p.first == window && p.second == canvas)
if (r == canvas)
return;
}

requests.push_back(std::pair<QWidget *, PyCanvas *>(window, canvas));
requests.push_back(canvas);
}

void cancelRepaintRequest(PyCanvas *canvas)
{
QMutexLocker locker(&mutex);

std::list<std::pair<QWidget *, PyCanvas *>> new_requests;
std::list<PyCanvas *> new_requests;

for (const auto &p : requests)
for (const auto &r : requests)
{
if (p.second != canvas)
new_requests.push_back(p);
if (r != canvas)
new_requests.push_back(r);
}

requests = new_requests;
}

void update(QWidget *widget)
void update()
{
// ideally only the passed widget could be updated; the widget may be a QDockWidget
// which never calls update; so as a workaround, just update everything and clear the list.
// this may be a problem (too many updates) in the future with multiple document windows.
QMutexLocker locker(&mutex);

for (const auto &p : requests)
for (const auto &r : requests)
{
p.second->update();
r->update();
}

requests.clear();
}

private:
QMutex mutex;
std::list<std::pair<QWidget *, PyCanvas *>> requests;
std::list<PyCanvas *> requests;
};

RepaintManager repaintManager;
Expand Down Expand Up @@ -192,7 +192,7 @@ void DocumentWindow::timerEvent(QTimerEvent *event)
{
if (event->timerId() == m_periodic_timer && isVisible())
{
repaintManager.update(this);
repaintManager.update();
application()->dispatchPyMethod(m_py_object, "periodic", QVariantList());
}
}
Expand Down Expand Up @@ -2448,7 +2448,8 @@ CanvasSection::CanvasSection(int section_id, float device_pixel_ratio)
*/

PyCanvas::PyCanvas()
: m_pressed(false)
: m_closing(false)
, m_pressed(false)
, m_grab_mouse_count(0)
{
setMouseTracking(true);
Expand All @@ -2457,6 +2458,7 @@ PyCanvas::PyCanvas()

PyCanvas::~PyCanvas()
{
m_closing = true;
// cancel any outstanding requests before shutting down the thread.
repaintManager.cancelRepaintRequest(this);
// now shut down the rendering thread by waiting until not rendering.
Expand Down Expand Up @@ -2510,12 +2512,15 @@ void PyCanvas::continuePaintingSection(const RenderResult &render_result)
section->record_latency = render_result.record_latency;
auto pending_commands = section->m_pending_drawing_commands;
section->m_pending_drawing_commands.reset();
if (pending_commands)
// do not start a new task if closing.
if (!m_closing && pending_commands)
{
task = new PyCanvasRenderTask(this, section, pending_commands, section->m_device_pixel_ratio, section->m_rendered_timestamps);
section->m_render_task = task;
}
repaintManager.requestRepaint(window(), this);
// note: this may be occurring during a delete, in which case even the window may not be available.
if (!m_closing)
repaintManager.requestRepaint(this);
}

// launch the task outside of the mutex.
Expand Down Expand Up @@ -3000,30 +3005,35 @@ void PyCanvas::setBinarySectionCommands(int section_id, const DrawingCommandsSha
{
QMutexLocker locker(&m_sections_mutex);

CanvasSectionSharedPtr section;

if (m_sections.contains(section_id))
{
section = m_sections[section_id];
}
else
// this request can come in on a thread during shutdown and add a new request
// after the destructor has synced threading. so check if closing before proceeding.
if (!m_closing)
{
auto screen = this->screen();
auto device_pixel_ratio = screen ? screen->devicePixelRatio() : 1.0; // m_screen may be nullptr in earlier versions of Qt
section.reset(new CanvasSection(section_id, device_pixel_ratio));
m_sections[section_id] = section;
}
CanvasSectionSharedPtr section;

pending_drawing_commands = section->m_pending_drawing_commands;
if (m_sections.contains(section_id))
{
section = m_sections[section_id];
}
else
{
auto screen = this->screen();
auto device_pixel_ratio = screen ? screen->devicePixelRatio() : 1.0; // m_screen may be nullptr in earlier versions of Qt
section.reset(new CanvasSection(section_id, device_pixel_ratio));
m_sections[section_id] = section;
}

if (!section->m_render_task)
{
task = new PyCanvasRenderTask(this, section, drawing_commands, section->m_device_pixel_ratio, section->m_rendered_timestamps);
section->m_render_task = task;
}
else
{
section->m_pending_drawing_commands = drawing_commands;
pending_drawing_commands = section->m_pending_drawing_commands;

if (!section->m_render_task)
{
task = new PyCanvasRenderTask(this, section, drawing_commands, section->m_device_pixel_ratio, section->m_rendered_timestamps);
section->m_render_task = task;
}
else
{
section->m_pending_drawing_commands = drawing_commands;
}
}
}

Expand All @@ -3035,6 +3045,18 @@ void PyCanvas::setBinarySectionCommands(int section_id, const DrawingCommandsSha
void PyCanvas::removeSection(int section_id)
{
QMutexLocker locker(&m_sections_mutex);

// ensure the section is not pending before removing.
auto section = m_sections[section_id];
while (true)
{
if (!section->m_render_task)
break;
m_sections_mutex.unlock();
QThread::msleep(1);
m_sections_mutex.lock();
}

m_sections.remove(section_id);
}

Expand Down
1 change: 1 addition & 0 deletions launcher/DocumentWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ class PyCanvas : public QWidget
void continuePaintingSection(const RenderResult &render_result);

private:
bool m_closing;
QVariant m_py_object;
QMutex m_sections_mutex;
QMap<int, CanvasSectionSharedPtr> m_sections;
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
tool_id = "nionswift"
launcher = "NionSwiftLauncher"

version = "5.0.0"
version = "5.0.1"


def package_files(directory: str, prefix: str, prefix_drop: int) -> list[typing.Tuple[str, list[str]]]:
Expand Down

0 comments on commit 5f25f64

Please sign in to comment.