From f69a543110703e09adc830bbc65e3c39be0cc52b Mon Sep 17 00:00:00 2001
From: wwylele <wwylele@gmail.com>
Date: Fri, 8 Apr 2016 19:28:54 +0300
Subject: [PATCH] implement wait tree widget

---
 src/citra_qt/CMakeLists.txt         |   2 +
 src/citra_qt/debugger/wait_tree.cpp | 417 ++++++++++++++++++++++++++++
 src/citra_qt/debugger/wait_tree.h   | 186 +++++++++++++
 src/citra_qt/main.cpp               |  13 +
 src/citra_qt/main.h                 |   2 +
 src/core/hle/kernel/kernel.cpp      |   4 +
 src/core/hle/kernel/kernel.h        |   3 +
 src/core/hle/kernel/thread.cpp      |   4 +
 src/core/hle/kernel/thread.h        |   5 +
 9 files changed, 636 insertions(+)
 create mode 100644 src/citra_qt/debugger/wait_tree.cpp
 create mode 100644 src/citra_qt/debugger/wait_tree.h

diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index e97d33da4..b3c01ddd8 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -15,6 +15,7 @@ set(SRCS
             debugger/profiler.cpp
             debugger/ramview.cpp
             debugger/registers.cpp
+            debugger/wait_tree.cpp
             util/spinbox.cpp
             util/util.cpp
             bootmanager.cpp
@@ -48,6 +49,7 @@ set(HEADERS
             debugger/profiler.h
             debugger/ramview.h
             debugger/registers.h
+            debugger/wait_tree.h
             util/spinbox.h
             util/util.h
             bootmanager.h
diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp
new file mode 100644
index 000000000..be5a51e52
--- /dev/null
+++ b/src/citra_qt/debugger/wait_tree.cpp
@@ -0,0 +1,417 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "citra_qt/debugger/wait_tree.h"
+#include "citra_qt/util/util.h"
+
+#include "core/hle/kernel/event.h"
+#include "core/hle/kernel/mutex.h"
+#include "core/hle/kernel/semaphore.h"
+#include "core/hle/kernel/session.h"
+#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/timer.h"
+
+WaitTreeItem::~WaitTreeItem() {}
+
+QColor WaitTreeItem::GetColor() const {
+    return QColor(Qt::GlobalColor::black);
+}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeItem::GetChildren() const {
+    return {};
+}
+
+void WaitTreeItem::Expand() {
+    if (IsExpandable() && !expanded) {
+        children = GetChildren();
+        for (std::size_t i = 0; i < children.size(); ++i) {
+            children[i]->parent = this;
+            children[i]->row = i;
+        }
+        expanded = true;
+    }
+}
+
+WaitTreeItem* WaitTreeItem::Parent() const {
+    return parent;
+}
+
+const std::vector<std::unique_ptr<WaitTreeItem>>& WaitTreeItem::Children() const {
+    return children;
+}
+
+bool WaitTreeItem::IsExpandable() const {
+    return false;
+}
+
+std::size_t WaitTreeItem::Row() const {
+    return row;
+}
+
+std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() {
+    const auto& threads = Kernel::GetThreadList();
+    std::vector<std::unique_ptr<WaitTreeThread>> item_list;
+    item_list.reserve(threads.size());
+    for (std::size_t i = 0; i < threads.size(); ++i) {
+        item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
+        item_list.back()->row = i;
+    }
+    return item_list;
+}
+
+WaitTreeText::WaitTreeText(const QString& t) : text(t) {}
+
+QString WaitTreeText::GetText() const {
+    return text;
+}
+
+WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::WaitObject& o) : object(o) {}
+
+bool WaitTreeExpandableItem::IsExpandable() const {
+    return true;
+}
+
+QString WaitTreeWaitObject::GetText() const {
+    return tr("[%1]%2 %3")
+        .arg(object.GetObjectId())
+        .arg(QString::fromStdString(object.GetTypeName()),
+             QString::fromStdString(object.GetName()));
+}
+
+std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitObject& object) {
+    switch (object.GetHandleType()) {
+    case Kernel::HandleType::Event:
+        return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::Event&>(object));
+    case Kernel::HandleType::Mutex:
+        return std::make_unique<WaitTreeMutex>(static_cast<const Kernel::Mutex&>(object));
+    case Kernel::HandleType::Semaphore:
+        return std::make_unique<WaitTreeSemaphore>(static_cast<const Kernel::Semaphore&>(object));
+    case Kernel::HandleType::Timer:
+        return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object));
+    case Kernel::HandleType::Thread:
+        return std::make_unique<WaitTreeThread>(static_cast<const Kernel::Thread&>(object));
+    default:
+        return std::make_unique<WaitTreeWaitObject>(object);
+    }
+}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeWaitObject::GetChildren() const {
+    std::vector<std::unique_ptr<WaitTreeItem>> list;
+
+    const auto& threads = object.GetWaitingThreads();
+    if (threads.empty()) {
+        list.push_back(std::make_unique<WaitTreeText>(tr("waited by no thread")));
+    } else {
+        list.push_back(std::make_unique<WaitTreeThreadList>(threads));
+    }
+    return list;
+}
+
+QString WaitTreeWaitObject::GetResetTypeQString(Kernel::ResetType reset_type) {
+    switch (reset_type) {
+    case Kernel::ResetType::OneShot:
+        return tr("one shot");
+    case Kernel::ResetType::Sticky:
+        return tr("sticky");
+    case Kernel::ResetType::Pulse:
+        return tr("pulse");
+    }
+}
+
+WaitTreeObjectList::WaitTreeObjectList(
+    const std::vector<Kernel::SharedPtr<Kernel::WaitObject>>& list, bool w_all)
+    : object_list(list), wait_all(w_all) {}
+
+QString WaitTreeObjectList::GetText() const {
+    if (wait_all)
+        return tr("waiting for all objects");
+    return tr("waiting for one of the following objects");
+}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeObjectList::GetChildren() const {
+    std::vector<std::unique_ptr<WaitTreeItem>> list(object_list.size());
+    std::transform(object_list.begin(), object_list.end(), list.begin(),
+                   [](const auto& t) { return WaitTreeWaitObject::make(*t); });
+    return list;
+}
+
+WaitTreeThread::WaitTreeThread(const Kernel::Thread& thread) : WaitTreeWaitObject(thread) {}
+
+QString WaitTreeThread::GetText() const {
+    const auto& thread = static_cast<const Kernel::Thread&>(object);
+    QString status;
+    switch (thread.status) {
+    case THREADSTATUS_RUNNING:
+        status = tr("running");
+        break;
+    case THREADSTATUS_READY:
+        status = tr("ready");
+        break;
+    case THREADSTATUS_WAIT_ARB:
+        status = tr("waiting for address 0x%1").arg(thread.wait_address, 8, 16, QLatin1Char('0'));
+        break;
+    case THREADSTATUS_WAIT_SLEEP:
+        status = tr("sleeping");
+        break;
+    case THREADSTATUS_WAIT_SYNCH:
+        status = tr("waiting for objects");
+        break;
+    case THREADSTATUS_DORMANT:
+        status = tr("dormant");
+        break;
+    case THREADSTATUS_DEAD:
+        status = tr("dead");
+        break;
+    }
+    QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
+                          .arg(thread.context.pc, 8, 16, QLatin1Char('0'))
+                          .arg(thread.context.lr, 8, 16, QLatin1Char('0'));
+    return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") ";
+}
+
+QColor WaitTreeThread::GetColor() const {
+    const auto& thread = static_cast<const Kernel::Thread&>(object);
+    switch (thread.status) {
+    case THREADSTATUS_RUNNING:
+        return QColor(Qt::GlobalColor::darkGreen);
+    case THREADSTATUS_READY:
+        return QColor(Qt::GlobalColor::darkBlue);
+    case THREADSTATUS_WAIT_ARB:
+        return QColor(Qt::GlobalColor::darkRed);
+    case THREADSTATUS_WAIT_SLEEP:
+        return QColor(Qt::GlobalColor::darkYellow);
+    case THREADSTATUS_WAIT_SYNCH:
+        return QColor(Qt::GlobalColor::red);
+    case THREADSTATUS_DORMANT:
+        return QColor(Qt::GlobalColor::darkCyan);
+    case THREADSTATUS_DEAD:
+        return QColor(Qt::GlobalColor::gray);
+    default:
+        return WaitTreeItem::GetColor();
+    }
+}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
+    std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
+
+    const auto& thread = static_cast<const Kernel::Thread&>(object);
+
+    QString processor;
+    switch (thread.processor_id) {
+    case ThreadProcessorId::THREADPROCESSORID_DEFAULT:
+        processor = tr("default");
+        break;
+    case ThreadProcessorId::THREADPROCESSORID_ALL:
+        processor = tr("all");
+        break;
+    case ThreadProcessorId::THREADPROCESSORID_0:
+        processor = tr("AppCore");
+        break;
+    case ThreadProcessorId::THREADPROCESSORID_1:
+        processor = tr("SysCore");
+        break;
+    default:
+        processor = tr("Unknown processor %1").arg(thread.processor_id);
+        break;
+    }
+
+    list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
+    list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId())));
+    list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)")
+                                                      .arg(thread.current_priority)
+                                                      .arg(thread.nominal_priority)));
+    list.push_back(std::make_unique<WaitTreeText>(
+        tr("last running ticks = %1").arg(thread.last_running_ticks)));
+
+    if (thread.held_mutexes.empty()) {
+        list.push_back(std::make_unique<WaitTreeText>(tr("not holding mutex")));
+    } else {
+        list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes));
+    }
+    if (thread.status == THREADSTATUS_WAIT_SYNCH) {
+        list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, thread.wait_all));
+    }
+
+    return list;
+}
+
+WaitTreeEvent::WaitTreeEvent(const Kernel::Event& object) : WaitTreeWaitObject(object) {}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const {
+    std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
+
+    list.push_back(std::make_unique<WaitTreeText>(
+        tr("reset type = %1")
+            .arg(GetResetTypeQString(static_cast<const Kernel::Event&>(object).reset_type))));
+    return list;
+}
+
+WaitTreeMutex::WaitTreeMutex(const Kernel::Mutex& object) : WaitTreeWaitObject(object) {}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutex::GetChildren() const {
+    std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
+
+    const auto& mutex = static_cast<const Kernel::Mutex&>(object);
+    if (mutex.lock_count) {
+        list.push_back(
+            std::make_unique<WaitTreeText>(tr("locked %1 times by thread:").arg(mutex.lock_count)));
+        list.push_back(std::make_unique<WaitTreeThread>(*mutex.holding_thread));
+    } else {
+        list.push_back(std::make_unique<WaitTreeText>(tr("free")));
+    }
+    return list;
+}
+
+WaitTreeSemaphore::WaitTreeSemaphore(const Kernel::Semaphore& object)
+    : WaitTreeWaitObject(object) {}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeSemaphore::GetChildren() const {
+    std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
+
+    const auto& semaphore = static_cast<const Kernel::Semaphore&>(object);
+    list.push_back(
+        std::make_unique<WaitTreeText>(tr("available count = %1").arg(semaphore.available_count)));
+    list.push_back(std::make_unique<WaitTreeText>(tr("max count = %1").arg(semaphore.max_count)));
+    return list;
+}
+
+WaitTreeTimer::WaitTreeTimer(const Kernel::Timer& object) : WaitTreeWaitObject(object) {}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
+    std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
+
+    const auto& timer = static_cast<const Kernel::Timer&>(object);
+
+    list.push_back(std::make_unique<WaitTreeText>(
+        tr("reset type = %1").arg(GetResetTypeQString(timer.reset_type))));
+    list.push_back(
+        std::make_unique<WaitTreeText>(tr("initial delay = %1").arg(timer.initial_delay)));
+    list.push_back(
+        std::make_unique<WaitTreeText>(tr("interval delay = %1").arg(timer.interval_delay)));
+    return list;
+}
+
+WaitTreeMutexList::WaitTreeMutexList(
+    const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list)
+    : mutex_list(list) {}
+
+QString WaitTreeMutexList::GetText() const {
+    return tr("holding mutexes");
+}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexList::GetChildren() const {
+    std::vector<std::unique_ptr<WaitTreeItem>> list(mutex_list.size());
+    std::transform(mutex_list.begin(), mutex_list.end(), list.begin(),
+                   [](const auto& t) { return std::make_unique<WaitTreeMutex>(*t); });
+    return list;
+}
+
+WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list)
+    : thread_list(list) {}
+
+QString WaitTreeThreadList::GetText() const {
+    return tr("waited by thread");
+}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThreadList::GetChildren() const {
+    std::vector<std::unique_ptr<WaitTreeItem>> list(thread_list.size());
+    std::transform(thread_list.begin(), thread_list.end(), list.begin(),
+                   [](const auto& t) { return std::make_unique<WaitTreeThread>(*t); });
+    return list;
+}
+
+WaitTreeModel::WaitTreeModel(QObject* parent) : QAbstractItemModel(parent) {}
+
+QModelIndex WaitTreeModel::index(int row, int column, const QModelIndex& parent) const {
+    if (!hasIndex(row, column, parent))
+        return {};
+
+    if (parent.isValid()) {
+        WaitTreeItem* parent_item = static_cast<WaitTreeItem*>(parent.internalPointer());
+        parent_item->Expand();
+        return createIndex(row, column, parent_item->Children()[row].get());
+    }
+
+    return createIndex(row, column, thread_items[row].get());
+}
+
+QModelIndex WaitTreeModel::parent(const QModelIndex& index) const {
+    if (!index.isValid())
+        return {};
+
+    WaitTreeItem* parent_item = static_cast<WaitTreeItem*>(index.internalPointer())->Parent();
+    if (!parent_item) {
+        return QModelIndex();
+    }
+    return createIndex(static_cast<int>(parent_item->Row()), 0, parent_item);
+}
+
+int WaitTreeModel::rowCount(const QModelIndex& parent) const {
+    if (!parent.isValid())
+        return static_cast<int>(thread_items.size());
+
+    WaitTreeItem* parent_item = static_cast<WaitTreeItem*>(parent.internalPointer());
+    parent_item->Expand();
+    return static_cast<int>(parent_item->Children().size());
+}
+
+int WaitTreeModel::columnCount(const QModelIndex&) const {
+    return 1;
+}
+
+QVariant WaitTreeModel::data(const QModelIndex& index, int role) const {
+    if (!index.isValid())
+        return {};
+
+    switch (role) {
+    case Qt::DisplayRole:
+        return static_cast<WaitTreeItem*>(index.internalPointer())->GetText();
+    case Qt::ForegroundRole:
+        return static_cast<WaitTreeItem*>(index.internalPointer())->GetColor();
+    default:
+        return {};
+    }
+}
+
+void WaitTreeModel::ClearItems() {
+    thread_items.clear();
+}
+
+void WaitTreeModel::InitItems() {
+    thread_items = WaitTreeItem::MakeThreadItemList();
+}
+
+WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("Wait Tree"), parent) {
+    setObjectName("WaitTreeWidget");
+    view = new QTreeView(this);
+    view->setHeaderHidden(true);
+    setWidget(view);
+    setEnabled(false);
+}
+
+void WaitTreeWidget::OnDebugModeEntered() {
+    if (!Core::g_app_core)
+        return;
+    model->InitItems();
+    view->setModel(model);
+    setEnabled(true);
+}
+
+void WaitTreeWidget::OnDebugModeLeft() {
+    setEnabled(false);
+    view->setModel(nullptr);
+    model->ClearItems();
+}
+
+void WaitTreeWidget::OnEmulationStarting(EmuThread* emu_thread) {
+    model = new WaitTreeModel(this);
+    view->setModel(model);
+    setEnabled(false);
+}
+
+void WaitTreeWidget::OnEmulationStopping() {
+    view->setModel(nullptr);
+    delete model;
+    setEnabled(false);
+}
diff --git a/src/citra_qt/debugger/wait_tree.h b/src/citra_qt/debugger/wait_tree.h
new file mode 100644
index 000000000..5d1d964d1
--- /dev/null
+++ b/src/citra_qt/debugger/wait_tree.h
@@ -0,0 +1,186 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <boost/container/flat_set.hpp>
+
+#include <QAbstractItemModel>
+#include <QDockWidget>
+#include <QTreeView>
+
+#include "core/core.h"
+#include "core/hle/kernel/kernel.h"
+
+class EmuThread;
+
+namespace Kernel {
+class WaitObject;
+class Event;
+class Mutex;
+class Semaphore;
+class Session;
+class Thread;
+class Timer;
+}
+
+class WaitTreeThread;
+
+class WaitTreeItem : public QObject {
+    Q_OBJECT
+public:
+    virtual bool IsExpandable() const;
+    virtual std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const;
+    virtual QString GetText() const = 0;
+    virtual QColor GetColor() const;
+    virtual ~WaitTreeItem();
+    void Expand();
+    WaitTreeItem* Parent() const;
+    const std::vector<std::unique_ptr<WaitTreeItem>>& Children() const;
+    std::size_t Row() const;
+    static std::vector<std::unique_ptr<WaitTreeThread>> MakeThreadItemList();
+
+private:
+    std::size_t row;
+    bool expanded = false;
+    WaitTreeItem* parent = nullptr;
+    std::vector<std::unique_ptr<WaitTreeItem>> children;
+};
+
+class WaitTreeText : public WaitTreeItem {
+    Q_OBJECT
+public:
+    WaitTreeText(const QString& text);
+    QString GetText() const override;
+
+private:
+    QString text;
+};
+
+class WaitTreeExpandableItem : public WaitTreeItem {
+    Q_OBJECT
+public:
+    bool IsExpandable() const override;
+};
+
+class WaitTreeWaitObject : public WaitTreeExpandableItem {
+    Q_OBJECT
+public:
+    WaitTreeWaitObject(const Kernel::WaitObject& object);
+    static std::unique_ptr<WaitTreeWaitObject> make(const Kernel::WaitObject& object);
+    QString GetText() const override;
+    std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+
+protected:
+    const Kernel::WaitObject& object;
+
+    static QString GetResetTypeQString(Kernel::ResetType reset_type);
+};
+
+class WaitTreeObjectList : public WaitTreeExpandableItem {
+    Q_OBJECT
+public:
+    WaitTreeObjectList(const std::vector<Kernel::SharedPtr<Kernel::WaitObject>>& list,
+                       bool wait_all);
+    QString GetText() const override;
+    std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+
+private:
+    const std::vector<Kernel::SharedPtr<Kernel::WaitObject>>& object_list;
+    bool wait_all;
+};
+
+class WaitTreeThread : public WaitTreeWaitObject {
+    Q_OBJECT
+public:
+    WaitTreeThread(const Kernel::Thread& thread);
+    QString GetText() const override;
+    QColor GetColor() const override;
+    std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+};
+
+class WaitTreeEvent : public WaitTreeWaitObject {
+    Q_OBJECT
+public:
+    WaitTreeEvent(const Kernel::Event& object);
+    std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+};
+
+class WaitTreeMutex : public WaitTreeWaitObject {
+    Q_OBJECT
+public:
+    WaitTreeMutex(const Kernel::Mutex& object);
+    std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+};
+
+class WaitTreeSemaphore : public WaitTreeWaitObject {
+    Q_OBJECT
+public:
+    WaitTreeSemaphore(const Kernel::Semaphore& object);
+    std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+};
+
+class WaitTreeTimer : public WaitTreeWaitObject {
+    Q_OBJECT
+public:
+    WaitTreeTimer(const Kernel::Timer& object);
+    std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+};
+
+class WaitTreeMutexList : public WaitTreeExpandableItem {
+    Q_OBJECT
+public:
+    WaitTreeMutexList(const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list);
+    QString GetText() const override;
+    std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+
+private:
+    const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& mutex_list;
+};
+
+class WaitTreeThreadList : public WaitTreeExpandableItem {
+    Q_OBJECT
+public:
+    WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list);
+    QString GetText() const override;
+    std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+
+private:
+    const std::vector<Kernel::SharedPtr<Kernel::Thread>>& thread_list;
+};
+
+class WaitTreeModel : public QAbstractItemModel {
+    Q_OBJECT
+
+public:
+    WaitTreeModel(QObject* parent = nullptr);
+
+    QVariant data(const QModelIndex& index, int role) const override;
+    QModelIndex index(int row, int column, const QModelIndex& parent) const override;
+    QModelIndex parent(const QModelIndex& index) const override;
+    int rowCount(const QModelIndex& parent) const override;
+    int columnCount(const QModelIndex& parent) const override;
+
+    void ClearItems();
+    void InitItems();
+
+private:
+    std::vector<std::unique_ptr<WaitTreeThread>> thread_items;
+};
+
+class WaitTreeWidget : public QDockWidget {
+    Q_OBJECT
+
+public:
+    WaitTreeWidget(QWidget* parent = nullptr);
+
+public slots:
+    void OnDebugModeEntered();
+    void OnDebugModeLeft();
+
+    void OnEmulationStarting(EmuThread* emu_thread);
+    void OnEmulationStopping();
+
+private:
+    QTreeView* view;
+    WaitTreeModel* model;
+};
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 82667446b..929392b14 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -25,6 +25,7 @@
 #include "citra_qt/debugger/profiler.h"
 #include "citra_qt/debugger/ramview.h"
 #include "citra_qt/debugger/registers.h"
+#include "citra_qt/debugger/wait_tree.h"
 #include "citra_qt/game_list.h"
 #include "citra_qt/hotkeys.h"
 #include "citra_qt/main.h"
@@ -104,6 +105,10 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
     connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this,
             SLOT(OnCreateGraphicsSurfaceViewer()));
 
+    waitTreeWidget = new WaitTreeWidget(this);
+    addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
+    waitTreeWidget->hide();
+
     QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));
     debug_menu->addAction(graphicsSurfaceViewerAction);
     debug_menu->addSeparator();
@@ -119,6 +124,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
     debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
     debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction());
     debug_menu->addAction(graphicsTracingWidget->toggleViewAction());
+    debug_menu->addAction(waitTreeWidget->toggleViewAction());
 
     // Set default UI state
     // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
@@ -184,6 +190,9 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
     connect(this, SIGNAL(EmulationStarting(EmuThread*)), graphicsTracingWidget,
             SLOT(OnEmulationStarting(EmuThread*)));
     connect(this, SIGNAL(EmulationStopping()), graphicsTracingWidget, SLOT(OnEmulationStopping()));
+    connect(this, SIGNAL(EmulationStarting(EmuThread*)), waitTreeWidget,
+            SLOT(OnEmulationStarting(EmuThread*)));
+    connect(this, SIGNAL(EmulationStopping()), waitTreeWidget, SLOT(OnEmulationStopping()));
 
     // Setup hotkeys
     RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
@@ -344,12 +353,16 @@ void GMainWindow::BootGame(const std::string& filename) {
             SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
     connect(emu_thread.get(), SIGNAL(DebugModeEntered()), callstackWidget,
             SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
+    connect(emu_thread.get(), SIGNAL(DebugModeEntered()), waitTreeWidget,
+            SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
     connect(emu_thread.get(), SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft()),
             Qt::BlockingQueuedConnection);
     connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()),
             Qt::BlockingQueuedConnection);
     connect(emu_thread.get(), SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft()),
             Qt::BlockingQueuedConnection);
+    connect(emu_thread.get(), SIGNAL(DebugModeLeft()), waitTreeWidget, SLOT(OnDebugModeLeft()),
+            Qt::BlockingQueuedConnection);
 
     // Update the GUI
     registersWidget->OnDebugModeEntered();
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index c4349513f..2cf308d80 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -21,6 +21,7 @@ class RegistersWidget;
 class CallstackWidget;
 class GPUCommandStreamWidget;
 class GPUCommandListWidget;
+class WaitTreeWidget;
 
 class GMainWindow : public QMainWindow {
     Q_OBJECT
@@ -128,6 +129,7 @@ private:
     CallstackWidget* callstackWidget;
     GPUCommandStreamWidget* graphicsWidget;
     GPUCommandListWidget* graphicsCommandsWidget;
+    WaitTreeWidget* waitTreeWidget;
 
     QAction* actions_recent_files[max_recent_files_item];
 };
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 9a2c8ce05..9e1795927 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -40,6 +40,10 @@ void WaitObject::WakeupAllWaitingThreads() {
     HLE::Reschedule(__func__);
 }
 
+const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const {
+    return waiting_threads;
+}
+
 HandleTable::HandleTable() {
     next_generation = 1;
     Clear();
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 0e95f7ff0..40c78b436 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -149,6 +149,9 @@ public:
     /// Wake up all threads waiting on this object
     void WakeupAllWaitingThreads();
 
+    /// Get a const reference to the waiting threads list for debug use
+    const std::vector<SharedPtr<Thread>>& GetWaitingThreads() const;
+
 private:
     /// Threads waiting for this object to become available
     std::vector<SharedPtr<Thread>> waiting_threads;
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 4486a812c..c4eeeee56 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -665,4 +665,8 @@ void ThreadingShutdown() {
     ready_queue.clear();
 }
 
+const std::vector<SharedPtr<Thread>>& GetThreadList() {
+    return thread_list;
+}
+
 } // namespace
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index f63131716..e0ffcea8a 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -236,4 +236,9 @@ void ThreadingInit();
  */
 void ThreadingShutdown();
 
+/**
+ * Get a const reference to the thread list for debug use
+ */
+const std::vector<SharedPtr<Thread>>& GetThreadList();
+
 } // namespace