Skip to content

fix: add back cache for window preview to avoid lag#1240

Merged
BLumia merged 1 commit into
linuxdeepin:masterfrom
BLumia:cache-preview
Sep 8, 2025
Merged

fix: add back cache for window preview to avoid lag#1240
BLumia merged 1 commit into
linuxdeepin:masterfrom
BLumia:cache-preview

Conversation

@BLumia

@BLumia BLumia commented Sep 5, 2025

Copy link
Copy Markdown
Member

旧的实现中,在预览窗口自己持有的模型中缓存了预览图. 改为新模型后,
预览图每次都是重新获取的. 获取预览图是 dbus 调用, 会导致卡顿. 因此
这里把缓存以另一种形式加回来.

Summary by Sourcery

Restore caching of window preview pixmaps to reduce lag from repeated DBus calls and ensure cache consistency by invalidating entries on window close and model changes

Bug Fixes:

  • Reintroduce static cache for window previews to avoid performance lag

Enhancements:

  • Invalidate cache entries when windows are closed or removed from the model to prevent stale previews

@sourcery-ai

sourcery-ai Bot commented Sep 5, 2025

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Reintroduce caching for window previews using a static QHash to avoid repeated D-Bus calls and reduce UI lag. The cache is checked before fetching, populated after a successful fetch, and invalidated on window close and model changes.

File-Level Changes

Change Details Files
Add static cache for window previews
  • Include QHash and define static QHash s_windowPreviewCache
  • Check cache in fetchWindowPreview before making D-Bus call
  • Insert pixmap into cache after successful fetch
panels/dock/taskmanager/x11preview.cpp
Invalidate cache on window close actions
  • Remove cache entry when closeButton is clicked
  • Remove cache entries for windows closed via container batch close
panels/dock/taskmanager/x11preview.cpp
Clear cache on model row removals
  • Connect QAbstractItemModel::rowsRemoved to clear the entire cache
panels/dock/taskmanager/x11preview.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `panels/dock/taskmanager/x11preview.cpp:132` </location>
<code_context>
     ::close(fd[0]);
     auto pixmap = QPixmap::fromImage(image);

+    if (!pixmap.isNull()) {
+        s_windowPreviewCache.insert(winId, pixmap);
+    }
+
</code_context>

<issue_to_address>
Consider cache invalidation strategy for window previews.

The cache may become outdated if window contents change without triggering rowsRemoved or window close. Consider adding more invalidation triggers if this could cause issues.
</issue_to_address>

### Comment 2
<location> `panels/dock/taskmanager/x11preview.cpp:429` </location>
<code_context>
         if (sourceModel) {
             connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, [this]() {
+                // 当窗口被移除时,清理相关缓存
+                s_windowPreviewCache.clear();
                 // 延迟调用,确保视图完全更新后再计算大小
                 QTimer::singleShot(0, this, [this]() {
</code_context>

<issue_to_address>
Clearing the entire cache on rowsRemoved may be excessive.

Instead of clearing the entire cache, remove only the entries for the windows that were actually removed to prevent unnecessary cache misses for remaining windows.

Suggested implementation:

```cpp
        if (sourceModel) {
            connect(sourceModel, &QAbstractItemModel::rowsRemoved, this,
                [this](const QModelIndex &parent, int first, int last) {
                    // 只清理被移除窗口的缓存
                    if (m_sourceModel) {
                        for (int row = first; row <= last; ++row) {
                            QModelIndex idx = m_sourceModel->index(row, 0, parent);
                            QVariant windowIdVar = m_sourceModel->data(idx, WindowIdRole); // 假设WindowIdRole为窗口ID的role
                            if (windowIdVar.isValid()) {
                                s_windowPreviewCache.remove(windowIdVar.toUInt());
                            }
                        }
                    }
                    // 延迟调用,确保视图完全更新后再计算大小
                    QTimer::singleShot(0, this, [this]() {
                        if (m_sourceModel) {

```

- Make sure that `WindowIdRole` is defined and used as the role for window IDs in your model. If your model uses a different role or method to get the window ID, adjust the code accordingly.
- If your model does not provide window IDs via a role, you may need to adapt the cache removal logic to match your data structure.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +132 to +133
if (!pixmap.isNull()) {
s_windowPreviewCache.insert(winId, pixmap);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

question: Consider cache invalidation strategy for window previews.

The cache may become outdated if window contents change without triggering rowsRemoved or window close. Consider adding more invalidation triggers if this could cause issues.

if (sourceModel) {
connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, [this]() {
// 当窗口被移除时,清理相关缓存
s_windowPreviewCache.clear();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Clearing the entire cache on rowsRemoved may be excessive.

Instead of clearing the entire cache, remove only the entries for the windows that were actually removed to prevent unnecessary cache misses for remaining windows.

Suggested implementation:

        if (sourceModel) {
            connect(sourceModel, &QAbstractItemModel::rowsRemoved, this,
                [this](const QModelIndex &parent, int first, int last) {
                    // 只清理被移除窗口的缓存
                    if (m_sourceModel) {
                        for (int row = first; row <= last; ++row) {
                            QModelIndex idx = m_sourceModel->index(row, 0, parent);
                            QVariant windowIdVar = m_sourceModel->data(idx, WindowIdRole); // 假设WindowIdRole为窗口ID的role
                            if (windowIdVar.isValid()) {
                                s_windowPreviewCache.remove(windowIdVar.toUInt());
                            }
                        }
                    }
                    // 延迟调用,确保视图完全更新后再计算大小
                    QTimer::singleShot(0, this, [this]() {
                        if (m_sourceModel) {
  • Make sure that WindowIdRole is defined and used as the role for window IDs in your model. If your model uses a different role or method to get the window ID, adjust the code accordingly.
  • If your model does not provide window IDs via a role, you may need to adapt the cache removal logic to match your data structure.

// 建立模型变化监听(只在模型真正变化时建立)
if (sourceModel) {
connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, [this]() {
// 当窗口被移除时,清理相关缓存

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

这个缓存是不是缓存的时间太长了,导致窗口状态可能不太对,
是不是应该在重新show出来时就把之前的给删掉,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

那应该说是在 hide 的时候就清除掉缓存可能更合适吧?我刚在 callHide() 里加了一个,代码已更新。

if (!WM_HELPER->hasComposite())
return QPixmap();

if (s_windowPreviewCache.contains(winId)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

这样加缓存的话,以后就不好用多线程来扩展优化了吧,
这里能不能不用静态变量,在调用这个接口的的地方去做缓存,

旧的实现中,在预览窗口自己持有的模型中缓存了预览图. 改为新模型后,
预览图每次都是重新获取的. 获取预览图是 dbus 调用, 会导致卡顿. 因此
这里把缓存以另一种形式加回来.

Log:
@18202781743

Copy link
Copy Markdown
Contributor

cache的代码弄丢了?

@BLumia

BLumia commented Sep 6, 2025

Copy link
Copy Markdown
Member Author

cache的代码弄丢了?

我可能后来推错分支了。。我下周确认下

@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

我对这个代码变更进行了审查,主要关注了语法逻辑、代码质量、性能和安全性。以下是我的改进建议:

语法逻辑

  1. 代码逻辑整体清晰,但有一些地方可以优化:
    • s_windowPreviewCache.clear() 在多个地方被调用(rowsRemoved信号、callHide函数等),这可能导致缓存被过度清理。建议只在必要时(如窗口关闭或应用退出时)清理特定窗口的缓存,而不是全部清空。

代码质量

  1. 缓存管理:

    • 添加了静态缓存 s_windowPreviewCache 是个好主意,可以提高性能,但缺少缓存大小限制和过期机制。随着长时间运行,缓存可能会占用过多内存。
    • 建议添加最大缓存条目限制,当超过限制时,采用LRU(最近最少使用)策略清理旧缓存。
  2. 错误处理:

    • fetchWindowPreview 函数中虽然有错误处理,但可以更完善。例如,在获取图像失败时,应该记录错误日志,并考虑是否需要从缓存中移除损坏的条目。
  3. 代码注释:

    • 删除了一些有用的注释(如"角色枚举已移除"),建议保留有价值的注释,特别是在解释复杂逻辑时。

性能

  1. 缓存策略:

    • 当前缓存实现简单,没有考虑缓存过期和内存管理。建议:
      • 添加时间戳,定期清理长时间未使用的缓存
      • 限制缓存大小,避免内存无限增长
      • 考虑使用QCache代替QHash,它内置了大小限制和自动清理机制
  2. 图像处理:

    • fetchWindowPreview 中,每次都会创建新的QImage和QPixmap,即使已有缓存。可以优化为只有缓存未命中时才进行这些操作。

安全性

  1. 资源管理:

    • 文件描述符的关闭是正确的,但建议使用RAII(资源获取即初始化)方式管理,例如使用QFileDescriptor或类似类。
  2. 并发访问:

    • 缓存 s_windowPreviewCache 是静态的,可能在多线程环境下被访问。虽然QHash是线程安全的,但复合操作(如检查存在性后获取值)需要加锁保护。

具体改进建议代码:

// 添加缓存大小限制和过期机制
static const int MAX_CACHE_SIZE = 100; // 最大缓存条目数
static const int CACHE_EXPIRY_SECONDS = 300; // 缓存过期时间(秒)

struct CacheEntry {
    QPixmap pixmap;
    QDateTime timestamp;
};

static QHash<uint32_t, CacheEntry> s_windowPreviewCache;
static QMutex s_cacheMutex; // 用于线程安全

QPixmap fetchWindowPreview(const uint32_t &winId) {
    QMutexLocker locker(&s_cacheMutex);
    
    // 检查缓存是否存在且未过期
    if (s_windowPreviewCache.contains(winId)) {
        auto entry = s_windowPreviewCache.value(winId);
        if (entry.timestamp.secsTo(QDateTime::currentDateTime()) < CACHE_EXPIRY_SECONDS) {
            return entry.pixmap;
        }
        s_windowPreviewCache.remove(winId);
    }

    // 获取新图像...
    QPixmap pixmap = ...; // 原有获取图像的代码
    
    // 更新缓存
    if (!pixmap.isNull()) {
        // 检查缓存大小
        if (s_windowPreviewCache.size() >= MAX_CACHE_SIZE) {
            // 移除最旧的条目
            uint32_t oldestId = 0;
            QDateTime oldestTime = QDateTime::currentDateTime();
            for (auto it = s_windowPreviewCache.constBegin(); it != s_windowPreviewCache.constEnd(); ++it) {
                if (it.value().timestamp < oldestTime) {
                    oldestTime = it.value().timestamp;
                    oldestId = it.key();
                }
            }
            s_windowPreviewCache.remove(oldestId);
        }
        
        CacheEntry entry{pixmap, QDateTime::currentDateTime()};
        s_windowPreviewCache.insert(winId, entry);
    }
    
    return pixmap;
}

// 在窗口关闭时只移除特定窗口的缓存
void closeWindow(uint32_t winId) {
    QMutexLocker locker(&s_cacheMutex);
    s_windowPreviewCache.remove(winId);
    X11Utils::instance()->closeWindow(winId);
}

这些改进可以提高代码的性能、安全性和可维护性,同时减少内存泄漏和资源浪费的风险。

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, BLumia

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@BLumia BLumia merged commit 95d552a into linuxdeepin:master Sep 8, 2025
8 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants