summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/audio_core/audio_out.cpp3
-rw-r--r--src/audio_core/cubeb_sink.cpp2
-rw-r--r--src/audio_core/cubeb_sink.h2
-rw-r--r--src/audio_core/null_sink.h2
-rw-r--r--src/audio_core/sink_details.cpp53
-rw-r--r--src/audio_core/sink_details.h25
-rw-r--r--src/common/thread_queue_list.h16
-rw-r--r--src/core/hle/kernel/process.cpp2
-rw-r--r--src/core/hle/kernel/process.h3
-rw-r--r--src/core/hle/kernel/scheduler.cpp66
-rw-r--r--src/core/hle/kernel/scheduler.h69
-rw-r--r--src/core/hle/kernel/svc.cpp90
-rw-r--r--src/core/hle/kernel/svc.h16
-rw-r--r--src/core/hle/kernel/svc_wrap.h24
-rw-r--r--src/core/hle/kernel/thread.h1
-rw-r--r--src/core/hle/kernel/vm_manager.cpp25
-rw-r--r--src/core/hle/kernel/vm_manager.h152
-rw-r--r--src/yuzu/configuration/configure_audio.cpp7
-rw-r--r--src/yuzu/debugger/wait_tree.cpp2
-rw-r--r--src/yuzu/debugger/wait_tree.h2
20 files changed, 430 insertions, 132 deletions
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp
index 0c8f5b18e..cbba17632 100644
--- a/src/audio_core/audio_out.cpp
+++ b/src/audio_core/audio_out.cpp
@@ -30,8 +30,7 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
30StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name, 30StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name,
31 Stream::ReleaseCallback&& release_callback) { 31 Stream::ReleaseCallback&& release_callback) {
32 if (!sink) { 32 if (!sink) {
33 const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id); 33 sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id);
34 sink = sink_details.factory(Settings::values.audio_device_id);
35 } 34 }
36 35
37 return std::make_shared<Stream>( 36 return std::make_shared<Stream>(
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index d31a1c844..097328901 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -107,7 +107,7 @@ private:
107 static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); 107 static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
108}; 108};
109 109
110CubebSink::CubebSink(std::string target_device_name) { 110CubebSink::CubebSink(std::string_view target_device_name) {
111 if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) { 111 if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
112 LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); 112 LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
113 return; 113 return;
diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h
index 59cbf05e9..efb9d1634 100644
--- a/src/audio_core/cubeb_sink.h
+++ b/src/audio_core/cubeb_sink.h
@@ -15,7 +15,7 @@ namespace AudioCore {
15 15
16class CubebSink final : public Sink { 16class CubebSink final : public Sink {
17public: 17public:
18 explicit CubebSink(std::string device_id); 18 explicit CubebSink(std::string_view device_id);
19 ~CubebSink() override; 19 ~CubebSink() override;
20 20
21 SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, 21 SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h
index a78d78893..61a28d542 100644
--- a/src/audio_core/null_sink.h
+++ b/src/audio_core/null_sink.h
@@ -10,7 +10,7 @@ namespace AudioCore {
10 10
11class NullSink final : public Sink { 11class NullSink final : public Sink {
12public: 12public:
13 explicit NullSink(std::string){}; 13 explicit NullSink(std::string_view) {}
14 ~NullSink() override = default; 14 ~NullSink() override = default;
15 15
16 SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/, 16 SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/,
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
index 67cf1f3b2..a848eb1c9 100644
--- a/src/audio_core/sink_details.cpp
+++ b/src/audio_core/sink_details.cpp
@@ -14,31 +14,68 @@
14#include "common/logging/log.h" 14#include "common/logging/log.h"
15 15
16namespace AudioCore { 16namespace AudioCore {
17namespace {
18struct SinkDetails {
19 using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
20 using ListDevicesFn = std::vector<std::string> (*)();
17 21
18// g_sink_details is ordered in terms of desirability, with the best choice at the top. 22 /// Name for this sink.
19const std::vector<SinkDetails> g_sink_details = { 23 const char* id;
24 /// A method to call to construct an instance of this type of sink.
25 FactoryFn factory;
26 /// A method to call to list available devices.
27 ListDevicesFn list_devices;
28};
29
30// sink_details is ordered in terms of desirability, with the best choice at the top.
31constexpr SinkDetails sink_details[] = {
20#ifdef HAVE_CUBEB 32#ifdef HAVE_CUBEB
21 SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices}, 33 SinkDetails{"cubeb",
34 [](std::string_view device_id) -> std::unique_ptr<Sink> {
35 return std::make_unique<CubebSink>(device_id);
36 },
37 &ListCubebSinkDevices},
22#endif 38#endif
23 SinkDetails{"null", &std::make_unique<NullSink, std::string>, 39 SinkDetails{"null",
40 [](std::string_view device_id) -> std::unique_ptr<Sink> {
41 return std::make_unique<NullSink>(device_id);
42 },
24 [] { return std::vector<std::string>{"null"}; }}, 43 [] { return std::vector<std::string>{"null"}; }},
25}; 44};
26 45
27const SinkDetails& GetSinkDetails(std::string_view sink_id) { 46const SinkDetails& GetSinkDetails(std::string_view sink_id) {
28 auto iter = 47 auto iter =
29 std::find_if(g_sink_details.begin(), g_sink_details.end(), 48 std::find_if(std::begin(sink_details), std::end(sink_details),
30 [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); 49 [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
31 50
32 if (sink_id == "auto" || iter == g_sink_details.end()) { 51 if (sink_id == "auto" || iter == std::end(sink_details)) {
33 if (sink_id != "auto") { 52 if (sink_id != "auto") {
34 LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); 53 LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);
35 } 54 }
36 // Auto-select. 55 // Auto-select.
37 // g_sink_details is ordered in terms of desirability, with the best choice at the front. 56 // sink_details is ordered in terms of desirability, with the best choice at the front.
38 iter = g_sink_details.begin(); 57 iter = std::begin(sink_details);
39 } 58 }
40 59
41 return *iter; 60 return *iter;
42} 61}
62} // Anonymous namespace
63
64std::vector<const char*> GetSinkIDs() {
65 std::vector<const char*> sink_ids(std::size(sink_details));
66
67 std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
68 [](const auto& sink) { return sink.id; });
69
70 return sink_ids;
71}
72
73std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) {
74 return GetSinkDetails(sink_id).list_devices();
75}
76
77std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) {
78 return GetSinkDetails(sink_id).factory(device_id);
79}
43 80
44} // namespace AudioCore 81} // namespace AudioCore
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h
index 03534b187..bc8786270 100644
--- a/src/audio_core/sink_details.h
+++ b/src/audio_core/sink_details.h
@@ -4,34 +4,21 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <functional>
8#include <memory>
9#include <string> 7#include <string>
10#include <string_view> 8#include <string_view>
11#include <utility>
12#include <vector> 9#include <vector>
13 10
14namespace AudioCore { 11namespace AudioCore {
15 12
16class Sink; 13class Sink;
17 14
18struct SinkDetails { 15/// Retrieves the IDs for all available audio sinks.
19 using FactoryFn = std::function<std::unique_ptr<Sink>(std::string)>; 16std::vector<const char*> GetSinkIDs();
20 using ListDevicesFn = std::function<std::vector<std::string>()>;
21 17
22 SinkDetails(const char* id_, FactoryFn factory_, ListDevicesFn list_devices_) 18/// Gets the list of devices for a particular sink identified by the given ID.
23 : id(id_), factory(std::move(factory_)), list_devices(std::move(list_devices_)) {} 19std::vector<std::string> GetDeviceListForSink(std::string_view sink_id);
24 20
25 /// Name for this sink. 21/// Creates an audio sink identified by the given device ID.
26 const char* id; 22std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
27 /// A method to call to construct an instance of this type of sink.
28 FactoryFn factory;
29 /// A method to call to list available devices.
30 ListDevicesFn list_devices;
31};
32
33extern const std::vector<SinkDetails> g_sink_details;
34
35const SinkDetails& GetSinkDetails(std::string_view sink_id);
36 23
37} // namespace AudioCore 24} // namespace AudioCore
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index 133122c5f..e7594db68 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -49,6 +49,22 @@ struct ThreadQueueList {
49 return T(); 49 return T();
50 } 50 }
51 51
52 template <typename UnaryPredicate>
53 T get_first_filter(UnaryPredicate filter) const {
54 const Queue* cur = first;
55 while (cur != nullptr) {
56 if (!cur->data.empty()) {
57 for (const auto& item : cur->data) {
58 if (filter(item))
59 return item;
60 }
61 }
62 cur = cur->next_nonempty;
63 }
64
65 return T();
66 }
67
52 T pop_first() { 68 T pop_first() {
53 Queue* cur = first; 69 Queue* cur = first;
54 while (cur != nullptr) { 70 while (cur != nullptr) {
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index c817fb449..5356a4a3f 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -150,7 +150,7 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
150 vm_manager 150 vm_manager
151 .MapMemoryBlock(vm_manager.GetTLSIORegionEndAddress() - stack_size, 151 .MapMemoryBlock(vm_manager.GetTLSIORegionEndAddress() - stack_size,
152 std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size, 152 std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size,
153 MemoryState::Mapped) 153 MemoryState::Stack)
154 .Unwrap(); 154 .Unwrap();
155 155
156 vm_manager.LogLayout(); 156 vm_manager.LogLayout();
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index bcb9ac4b8..459eedfa6 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -262,8 +262,7 @@ public:
262 ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms); 262 ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
263 ResultCode HeapFree(VAddr target, u32 size); 263 ResultCode HeapFree(VAddr target, u32 size);
264 264
265 ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, 265 ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);
266 MemoryState state = MemoryState::Mapped);
267 266
268 ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size); 267 ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size);
269 268
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 5a5f4cef1..df4d6cf0a 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -9,6 +9,7 @@
9#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "core/arm/arm_interface.h" 10#include "core/arm/arm_interface.h"
11#include "core/core.h" 11#include "core/core.h"
12#include "core/core_cpu.h"
12#include "core/core_timing.h" 13#include "core/core_timing.h"
13#include "core/hle/kernel/kernel.h" 14#include "core/hle/kernel/kernel.h"
14#include "core/hle/kernel/process.h" 15#include "core/hle/kernel/process.h"
@@ -179,4 +180,69 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
179 ready_queue.prepare(priority); 180 ready_queue.prepare(priority);
180} 181}
181 182
183Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const {
184 std::lock_guard<std::mutex> lock(scheduler_mutex);
185
186 const u32 mask = 1U << core;
187 return ready_queue.get_first_filter([mask, maximum_priority](Thread const* thread) {
188 return (thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority;
189 });
190}
191
192void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
193 ASSERT(thread != nullptr);
194 // Avoid yielding if the thread isn't even running.
195 ASSERT(thread->GetStatus() == ThreadStatus::Running);
196
197 // Sanity check that the priority is valid
198 ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
199
200 // Yield this thread -- sleep for zero time and force reschedule to different thread
201 WaitCurrentThread_Sleep();
202 GetCurrentThread()->WakeAfterDelay(0);
203}
204
205void Scheduler::YieldWithLoadBalancing(Thread* thread) {
206 ASSERT(thread != nullptr);
207 const auto priority = thread->GetPriority();
208 const auto core = static_cast<u32>(thread->GetProcessorID());
209
210 // Avoid yielding if the thread isn't even running.
211 ASSERT(thread->GetStatus() == ThreadStatus::Running);
212
213 // Sanity check that the priority is valid
214 ASSERT(priority < THREADPRIO_COUNT);
215
216 // Sleep for zero time to be able to force reschedule to different thread
217 WaitCurrentThread_Sleep();
218 GetCurrentThread()->WakeAfterDelay(0);
219
220 Thread* suggested_thread = nullptr;
221
222 // Search through all of the cpu cores (except this one) for a suggested thread.
223 // Take the first non-nullptr one
224 for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) {
225 const auto res =
226 Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread(
227 core, priority);
228
229 // If scheduler provides a suggested thread
230 if (res != nullptr) {
231 // And its better than the current suggested thread (or is the first valid one)
232 if (suggested_thread == nullptr ||
233 suggested_thread->GetPriority() > res->GetPriority()) {
234 suggested_thread = res;
235 }
236 }
237 }
238
239 // If a suggested thread was found, queue that for this core
240 if (suggested_thread != nullptr)
241 suggested_thread->ChangeCore(core, suggested_thread->GetAffinityMask());
242}
243
244void Scheduler::YieldAndWaitForLoadBalancing(Thread* thread) {
245 UNIMPLEMENTED_MSG("Wait for load balancing thread yield type is not implemented!");
246}
247
182} // namespace Kernel 248} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index c63032b7d..97ced4dfc 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -51,6 +51,75 @@ public:
51 /// Sets the priority of a thread in the scheduler 51 /// Sets the priority of a thread in the scheduler
52 void SetThreadPriority(Thread* thread, u32 priority); 52 void SetThreadPriority(Thread* thread, u32 priority);
53 53
54 /// Gets the next suggested thread for load balancing
55 Thread* GetNextSuggestedThread(u32 core, u32 minimum_priority) const;
56
57 /**
58 * YieldWithoutLoadBalancing -- analogous to normal yield on a system
59 * Moves the thread to the end of the ready queue for its priority, and then reschedules the
60 * system to the new head of the queue.
61 *
62 * Example (Single Core -- but can be extrapolated to multi):
63 * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC (->exec order->)
64 * Currently Running: ThreadR
65 *
66 * ThreadR calls YieldWithoutLoadBalancing
67 *
68 * ThreadR is moved to the end of ready_queue[prio=0]:
69 * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC, ThreadR (->exec order->)
70 * Currently Running: Nothing
71 *
72 * System is rescheduled (ThreadA is popped off of queue):
73 * ready_queue[prio=0]: ThreadB, ThreadC, ThreadR (->exec order->)
74 * Currently Running: ThreadA
75 *
76 * If the queue is empty at time of call, no yielding occurs. This does not cross between cores
77 * or priorities at all.
78 */
79 void YieldWithoutLoadBalancing(Thread* thread);
80
81 /**
82 * YieldWithLoadBalancing -- yield but with better selection of the new running thread
83 * Moves the current thread to the end of the ready queue for its priority, then selects a
84 * 'suggested thread' (a thread on a different core that could run on this core) from the
85 * scheduler, changes its core, and reschedules the current core to that thread.
86 *
87 * Example (Dual Core -- can be extrapolated to Quad Core, this is just normal yield if it were
88 * single core):
89 * ready_queue[core=0][prio=0]: ThreadA, ThreadB (affinities not pictured as irrelevant
90 * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
91 * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
92 *
93 * ThreadQ calls YieldWithLoadBalancing
94 *
95 * ThreadQ is moved to the end of ready_queue[core=0][prio=0]:
96 * ready_queue[core=0][prio=0]: ThreadA, ThreadB
97 * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
98 * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
99 *
100 * A list of suggested threads for each core is compiled
101 * Suggested Threads: {ThreadC on Core 1}
102 * If this were quad core (as the switch is), there could be between 0 and 3 threads in this
103 * list. If there are more than one, the thread is selected by highest prio.
104 *
105 * ThreadC is core changed to Core 0:
106 * ready_queue[core=0][prio=0]: ThreadC, ThreadA, ThreadB, ThreadQ
107 * ready_queue[core=1][prio=0]: ThreadD
108 * Currently Running: None on Core 0 || ThreadP on Core 1
109 *
110 * System is rescheduled (ThreadC is popped off of queue):
111 * ready_queue[core=0][prio=0]: ThreadA, ThreadB, ThreadQ
112 * ready_queue[core=1][prio=0]: ThreadD
113 * Currently Running: ThreadC on Core 0 || ThreadP on Core 1
114 *
115 * If no suggested threads can be found this will behave just as normal yield. If there are
116 * multiple candidates for the suggested thread on a core, the highest prio is taken.
117 */
118 void YieldWithLoadBalancing(Thread* thread);
119
120 /// Currently unknown -- asserts as unimplemented on call
121 void YieldAndWaitForLoadBalancing(Thread* thread);
122
54 /// Returns a list of all threads managed by the scheduler 123 /// Returns a list of all threads managed by the scheduler
55 const std::vector<SharedPtr<Thread>>& GetThreadList() const { 124 const std::vector<SharedPtr<Thread>>& GetThreadList() const {
56 return thread_list; 125 return thread_list;
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 8b0b3671a..348a22904 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -35,6 +35,7 @@
35#include "core/hle/lock.h" 35#include "core/hle/lock.h"
36#include "core/hle/result.h" 36#include "core/hle/result.h"
37#include "core/hle/service/service.h" 37#include "core/hle/service/service.h"
38#include "core/memory.h"
38 39
39namespace Kernel { 40namespace Kernel {
40namespace { 41namespace {
@@ -273,7 +274,7 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
273 return result; 274 return result;
274 } 275 }
275 276
276 return current_process->MirrorMemory(dst_addr, src_addr, size); 277 return current_process->MirrorMemory(dst_addr, src_addr, size, MemoryState::Stack);
277} 278}
278 279
279/// Unmaps a region that was previously mapped with svcMapMemory 280/// Unmaps a region that was previously mapped with svcMapMemory
@@ -1066,10 +1067,9 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
1066 return shared_memory->Unmap(*current_process, addr); 1067 return shared_memory->Unmap(*current_process, addr);
1067} 1068}
1068 1069
1069/// Query process memory 1070static ResultCode QueryProcessMemory(VAddr memory_info_address, VAddr page_info_address,
1070static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/, 1071 Handle process_handle, VAddr address) {
1071 Handle process_handle, u64 addr) { 1072 LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address);
1072 LOG_TRACE(Kernel_SVC, "called process=0x{:08X} addr={:X}", process_handle, addr);
1073 const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); 1073 const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
1074 SharedPtr<Process> process = handle_table.Get<Process>(process_handle); 1074 SharedPtr<Process> process = handle_table.Get<Process>(process_handle);
1075 if (!process) { 1075 if (!process) {
@@ -1079,28 +1079,32 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i
1079 } 1079 }
1080 1080
1081 const auto& vm_manager = process->VMManager(); 1081 const auto& vm_manager = process->VMManager();
1082 const auto vma = vm_manager.FindVMA(addr); 1082 const MemoryInfo memory_info = vm_manager.QueryMemory(address);
1083 1083
1084 memory_info->attributes = 0; 1084 Memory::Write64(memory_info_address, memory_info.base_address);
1085 if (vm_manager.IsValidHandle(vma)) { 1085 Memory::Write64(memory_info_address + 8, memory_info.size);
1086 memory_info->base_address = vma->second.base; 1086 Memory::Write32(memory_info_address + 16, memory_info.state);
1087 memory_info->permission = static_cast<u32>(vma->second.permissions); 1087 Memory::Write32(memory_info_address + 20, memory_info.attributes);
1088 memory_info->size = vma->second.size; 1088 Memory::Write32(memory_info_address + 24, memory_info.permission);
1089 memory_info->type = static_cast<u32>(vma->second.meminfo_state); 1089 Memory::Write32(memory_info_address + 32, memory_info.ipc_ref_count);
1090 } else { 1090 Memory::Write32(memory_info_address + 28, memory_info.device_ref_count);
1091 memory_info->base_address = 0; 1091 Memory::Write32(memory_info_address + 36, 0);
1092 memory_info->permission = static_cast<u32>(VMAPermission::None); 1092
1093 memory_info->size = 0; 1093 // Page info appears to be currently unused by the kernel and is always set to zero.
1094 memory_info->type = static_cast<u32>(MemoryState::Unmapped); 1094 Memory::Write32(page_info_address, 0);
1095 }
1096 1095
1097 return RESULT_SUCCESS; 1096 return RESULT_SUCCESS;
1098} 1097}
1099 1098
1100/// Query memory 1099static ResultCode QueryMemory(VAddr memory_info_address, VAddr page_info_address,
1101static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAddr addr) { 1100 VAddr query_address) {
1102 LOG_TRACE(Kernel_SVC, "called, addr={:X}", addr); 1101 LOG_TRACE(Kernel_SVC,
1103 return QueryProcessMemory(memory_info, page_info, CurrentProcess, addr); 1102 "called, memory_info_address=0x{:016X}, page_info_address=0x{:016X}, "
1103 "query_address=0x{:016X}",
1104 memory_info_address, page_info_address, query_address);
1105
1106 return QueryProcessMemory(memory_info_address, page_info_address, CurrentProcess,
1107 query_address);
1104} 1108}
1105 1109
1106/// Exits the current process 1110/// Exits the current process
@@ -1204,18 +1208,38 @@ static void ExitThread() {
1204static void SleepThread(s64 nanoseconds) { 1208static void SleepThread(s64 nanoseconds) {
1205 LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); 1209 LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
1206 1210
1207 // Don't attempt to yield execution if there are no available threads to run, 1211 enum class SleepType : s64 {
1208 // this way we avoid a useless reschedule to the idle thread. 1212 YieldWithoutLoadBalancing = 0,
1209 if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads()) 1213 YieldWithLoadBalancing = -1,
1210 return; 1214 YieldAndWaitForLoadBalancing = -2,
1215 };
1211 1216
1212 // Sleep current thread and check for next thread to schedule 1217 if (nanoseconds <= 0) {
1213 WaitCurrentThread_Sleep(); 1218 auto& scheduler{Core::System::GetInstance().CurrentScheduler()};
1219 switch (static_cast<SleepType>(nanoseconds)) {
1220 case SleepType::YieldWithoutLoadBalancing:
1221 scheduler.YieldWithoutLoadBalancing(GetCurrentThread());
1222 break;
1223 case SleepType::YieldWithLoadBalancing:
1224 scheduler.YieldWithLoadBalancing(GetCurrentThread());
1225 break;
1226 case SleepType::YieldAndWaitForLoadBalancing:
1227 scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread());
1228 break;
1229 default:
1230 UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
1231 }
1232 } else {
1233 // Sleep current thread and check for next thread to schedule
1234 WaitCurrentThread_Sleep();
1214 1235
1215 // Create an event to wake the thread up after the specified nanosecond delay has passed 1236 // Create an event to wake the thread up after the specified nanosecond delay has passed
1216 GetCurrentThread()->WakeAfterDelay(nanoseconds); 1237 GetCurrentThread()->WakeAfterDelay(nanoseconds);
1238 }
1217 1239
1218 Core::System::GetInstance().PrepareReschedule(); 1240 // Reschedule all CPU cores
1241 for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i)
1242 Core::System::GetInstance().CpuCore(i).PrepareReschedule();
1219} 1243}
1220 1244
1221/// Wait process wide key atomic 1245/// Wait process wide key atomic
@@ -1907,7 +1931,7 @@ static const FunctionDef SVC_Table[] = {
1907 {0x73, nullptr, "SetProcessMemoryPermission"}, 1931 {0x73, nullptr, "SetProcessMemoryPermission"},
1908 {0x74, nullptr, "MapProcessMemory"}, 1932 {0x74, nullptr, "MapProcessMemory"},
1909 {0x75, nullptr, "UnmapProcessMemory"}, 1933 {0x75, nullptr, "UnmapProcessMemory"},
1910 {0x76, nullptr, "QueryProcessMemory"}, 1934 {0x76, SvcWrap<QueryProcessMemory>, "QueryProcessMemory"},
1911 {0x77, nullptr, "MapProcessCodeMemory"}, 1935 {0x77, nullptr, "MapProcessCodeMemory"},
1912 {0x78, nullptr, "UnmapProcessCodeMemory"}, 1936 {0x78, nullptr, "UnmapProcessCodeMemory"},
1913 {0x79, nullptr, "CreateProcess"}, 1937 {0x79, nullptr, "CreateProcess"},
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index b06aac4ec..c37ae0f98 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.h
@@ -8,22 +8,6 @@
8 8
9namespace Kernel { 9namespace Kernel {
10 10
11struct MemoryInfo {
12 u64 base_address;
13 u64 size;
14 u32 type;
15 u32 attributes;
16 u32 permission;
17 u32 device_refcount;
18 u32 ipc_refcount;
19 INSERT_PADDING_WORDS(1);
20};
21static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size.");
22
23struct PageInfo {
24 u64 flags;
25};
26
27void CallSVC(u32 immediate); 11void CallSVC(u32 immediate);
28 12
29} // namespace Kernel 13} // namespace Kernel
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index 24aef46c9..2f758b959 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -7,9 +7,7 @@
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "core/arm/arm_interface.h" 8#include "core/arm/arm_interface.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/hle/kernel/svc.h"
11#include "core/hle/result.h" 10#include "core/hle/result.h"
12#include "core/memory.h"
13 11
14namespace Kernel { 12namespace Kernel {
15 13
@@ -129,7 +127,12 @@ void SvcWrap() {
129template <ResultCode func(u64, u64, u32, u32)> 127template <ResultCode func(u64, u64, u32, u32)>
130void SvcWrap() { 128void SvcWrap() {
131 FuncReturn( 129 FuncReturn(
132 func(Param(0), Param(1), static_cast<u32>(Param(3)), static_cast<u32>(Param(3))).raw); 130 func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw);
131}
132
133template <ResultCode func(u64, u64, u32, u64)>
134void SvcWrap() {
135 FuncReturn(func(Param(0), Param(1), static_cast<u32>(Param(2)), Param(3)).raw);
133} 136}
134 137
135template <ResultCode func(u32, u64, u32)> 138template <ResultCode func(u32, u64, u32)>
@@ -191,21 +194,6 @@ void SvcWrap() {
191 FuncReturn(retval); 194 FuncReturn(retval);
192} 195}
193 196
194template <ResultCode func(MemoryInfo*, PageInfo*, u64)>
195void SvcWrap() {
196 MemoryInfo memory_info = {};
197 PageInfo page_info = {};
198 u32 retval = func(&memory_info, &page_info, Param(2)).raw;
199
200 Memory::Write64(Param(0), memory_info.base_address);
201 Memory::Write64(Param(0) + 8, memory_info.size);
202 Memory::Write32(Param(0) + 16, memory_info.type);
203 Memory::Write32(Param(0) + 20, memory_info.attributes);
204 Memory::Write32(Param(0) + 24, memory_info.permission);
205
206 FuncReturn(retval);
207}
208
209template <ResultCode func(u32*, u64, u64, u32)> 197template <ResultCode func(u32*, u64, u64, u32)>
210void SvcWrap() { 198void SvcWrap() {
211 u32 param_1 = 0; 199 u32 param_1 = 0;
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index d384d50db..77aec099a 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -26,6 +26,7 @@ enum ThreadPriority : u32 {
26 THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps 26 THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
27 THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps 27 THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
28 THREADPRIO_LOWEST = 63, ///< Lowest thread priority 28 THREADPRIO_LOWEST = 63, ///< Lowest thread priority
29 THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities.
29}; 30};
30 31
31enum ThreadProcessorId : s32 { 32enum ThreadProcessorId : s32 {
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 6187993ce..d3b55a51e 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -25,14 +25,14 @@ static const char* GetMemoryStateName(MemoryState state) {
25 "CodeMutable", "Heap", 25 "CodeMutable", "Heap",
26 "Shared", "Unknown1", 26 "Shared", "Unknown1",
27 "ModuleCodeStatic", "ModuleCodeMutable", 27 "ModuleCodeStatic", "ModuleCodeMutable",
28 "IpcBuffer0", "Mapped", 28 "IpcBuffer0", "Stack",
29 "ThreadLocal", "TransferMemoryIsolated", 29 "ThreadLocal", "TransferMemoryIsolated",
30 "TransferMemory", "ProcessMemory", 30 "TransferMemory", "ProcessMemory",
31 "Unknown2", "IpcBuffer1", 31 "Inaccessible", "IpcBuffer1",
32 "IpcBuffer3", "KernelStack", 32 "IpcBuffer3", "KernelStack",
33 }; 33 };
34 34
35 return names[static_cast<int>(state)]; 35 return names[ToSvcMemoryState(state)];
36} 36}
37 37
38bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const { 38bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
@@ -302,6 +302,25 @@ ResultCode VMManager::HeapFree(VAddr target, u64 size) {
302 return RESULT_SUCCESS; 302 return RESULT_SUCCESS;
303} 303}
304 304
305MemoryInfo VMManager::QueryMemory(VAddr address) const {
306 const auto vma = FindVMA(address);
307 MemoryInfo memory_info{};
308
309 if (IsValidHandle(vma)) {
310 memory_info.base_address = vma->second.base;
311 memory_info.permission = static_cast<u32>(vma->second.permissions);
312 memory_info.size = vma->second.size;
313 memory_info.state = ToSvcMemoryState(vma->second.meminfo_state);
314 } else {
315 memory_info.base_address = address_space_end;
316 memory_info.permission = static_cast<u32>(VMAPermission::None);
317 memory_info.size = 0 - address_space_end;
318 memory_info.state = static_cast<u32>(MemoryState::Inaccessible);
319 }
320
321 return memory_info;
322}
323
305ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) { 324ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) {
306 const auto vma = FindVMA(src_addr); 325 const auto vma = FindVMA(src_addr);
307 326
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index a12419d1e..10bacac3e 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -43,26 +43,129 @@ enum class VMAPermission : u8 {
43 ReadWriteExecute = Read | Write | Execute, 43 ReadWriteExecute = Read | Write | Execute,
44}; 44};
45 45
46/// Set of values returned in MemoryInfo.state by svcQueryMemory. 46// clang-format off
47/// Represents memory states and any relevant flags, as used by the kernel.
48/// svcQueryMemory interprets these by masking away all but the first eight
49/// bits when storing memory state into a MemoryInfo instance.
47enum class MemoryState : u32 { 50enum class MemoryState : u32 {
48 Unmapped = 0x0, 51 Mask = 0xFF,
49 Io = 0x1, 52 FlagProtect = 1U << 8,
50 Normal = 0x2, 53 FlagDebug = 1U << 9,
51 CodeStatic = 0x3, 54 FlagIPC0 = 1U << 10,
52 CodeMutable = 0x4, 55 FlagIPC3 = 1U << 11,
53 Heap = 0x5, 56 FlagIPC1 = 1U << 12,
54 Shared = 0x6, 57 FlagMapped = 1U << 13,
55 ModuleCodeStatic = 0x8, 58 FlagCode = 1U << 14,
56 ModuleCodeMutable = 0x9, 59 FlagAlias = 1U << 15,
57 IpcBuffer0 = 0xA, 60 FlagModule = 1U << 16,
58 Mapped = 0xB, 61 FlagTransfer = 1U << 17,
59 ThreadLocal = 0xC, 62 FlagQueryPhysicalAddressAllowed = 1U << 18,
60 TransferMemoryIsolated = 0xD, 63 FlagSharedDevice = 1U << 19,
61 TransferMemory = 0xE, 64 FlagSharedDeviceAligned = 1U << 20,
62 ProcessMemory = 0xF, 65 FlagIPCBuffer = 1U << 21,
63 IpcBuffer1 = 0x11, 66 FlagMemoryPoolAllocated = 1U << 22,
64 IpcBuffer3 = 0x12, 67 FlagMapProcess = 1U << 23,
65 KernelStack = 0x13, 68 FlagUncached = 1U << 24,
69 FlagCodeMemory = 1U << 25,
70
71 // Convenience flag sets to reduce repetition
72 IPCFlags = FlagIPC0 | FlagIPC3 | FlagIPC1,
73
74 CodeFlags = FlagDebug | IPCFlags | FlagMapped | FlagCode | FlagQueryPhysicalAddressAllowed |
75 FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
76
77 DataFlags = FlagProtect | IPCFlags | FlagMapped | FlagAlias | FlagTransfer |
78 FlagQueryPhysicalAddressAllowed | FlagSharedDevice | FlagSharedDeviceAligned |
79 FlagMemoryPoolAllocated | FlagIPCBuffer | FlagUncached,
80
81 Unmapped = 0x00,
82 Io = 0x01 | FlagMapped,
83 Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed,
84 CodeStatic = 0x03 | CodeFlags | FlagMapProcess,
85 CodeMutable = 0x04 | CodeFlags | FlagMapProcess | FlagCodeMemory,
86 Heap = 0x05 | DataFlags | FlagCodeMemory,
87 Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated,
88 ModuleCodeStatic = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
89 ModuleCodeMutable = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
90
91 IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated |
92 IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned,
93
94 Stack = 0x0B | FlagMapped | IPCFlags | FlagQueryPhysicalAddressAllowed |
95 FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
96
97 ThreadLocal = 0x0C | FlagMapped | FlagMemoryPoolAllocated,
98
99 TransferMemoryIsolated = 0x0D | IPCFlags | FlagMapped | FlagQueryPhysicalAddressAllowed |
100 FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated |
101 FlagUncached,
102
103 TransferMemory = 0x0E | FlagIPC3 | FlagIPC1 | FlagMapped | FlagQueryPhysicalAddressAllowed |
104 FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
105
106 ProcessMemory = 0x0F | FlagIPC3 | FlagIPC1 | FlagMapped | FlagMemoryPoolAllocated,
107
108 // Used to signify an inaccessible or invalid memory region with memory queries
109 Inaccessible = 0x10,
110
111 IpcBuffer1 = 0x11 | FlagIPC3 | FlagIPC1 | FlagMapped | FlagQueryPhysicalAddressAllowed |
112 FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
113
114 IpcBuffer3 = 0x12 | FlagIPC3 | FlagMapped | FlagQueryPhysicalAddressAllowed |
115 FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
116
117 KernelStack = 0x13 | FlagMapped,
118};
119// clang-format on
120
121constexpr MemoryState operator|(MemoryState lhs, MemoryState rhs) {
122 return static_cast<MemoryState>(u32(lhs) | u32(rhs));
123}
124
125constexpr MemoryState operator&(MemoryState lhs, MemoryState rhs) {
126 return static_cast<MemoryState>(u32(lhs) & u32(rhs));
127}
128
129constexpr MemoryState operator^(MemoryState lhs, MemoryState rhs) {
130 return static_cast<MemoryState>(u32(lhs) ^ u32(rhs));
131}
132
133constexpr MemoryState operator~(MemoryState lhs) {
134 return static_cast<MemoryState>(~u32(lhs));
135}
136
137constexpr MemoryState& operator|=(MemoryState& lhs, MemoryState rhs) {
138 lhs = lhs | rhs;
139 return lhs;
140}
141
142constexpr MemoryState& operator&=(MemoryState& lhs, MemoryState rhs) {
143 lhs = lhs & rhs;
144 return lhs;
145}
146
147constexpr MemoryState& operator^=(MemoryState& lhs, MemoryState rhs) {
148 lhs = lhs ^ rhs;
149 return lhs;
150}
151
152constexpr u32 ToSvcMemoryState(MemoryState state) {
153 return static_cast<u32>(state & MemoryState::Mask);
154}
155
156struct MemoryInfo {
157 u64 base_address;
158 u64 size;
159 u32 state;
160 u32 attributes;
161 u32 permission;
162 u32 ipc_ref_count;
163 u32 device_ref_count;
164};
165static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size.");
166
167struct PageInfo {
168 u32 flags;
66}; 169};
67 170
68/** 171/**
@@ -186,8 +289,15 @@ public:
186 ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms); 289 ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
187 ResultCode HeapFree(VAddr target, u64 size); 290 ResultCode HeapFree(VAddr target, u64 size);
188 291
189 ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, 292 ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);
190 MemoryState state = MemoryState::Mapped); 293
294 /// Queries the memory manager for information about the given address.
295 ///
296 /// @param address The address to query the memory manager about for information.
297 ///
298 /// @return A MemoryInfo instance containing information about the given address.
299 ///
300 MemoryInfo QueryMemory(VAddr address) const;
191 301
192 /** 302 /**
193 * Scans all VMAs and updates the page table range of any that use the given vector as backing 303 * Scans all VMAs and updates the page table range of any that use the given vector as backing
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index eb1da0f9e..5d9ccc6e8 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -17,8 +17,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
17 17
18 ui->output_sink_combo_box->clear(); 18 ui->output_sink_combo_box->clear();
19 ui->output_sink_combo_box->addItem("auto"); 19 ui->output_sink_combo_box->addItem("auto");
20 for (const auto& sink_detail : AudioCore::g_sink_details) { 20 for (const char* id : AudioCore::GetSinkIDs()) {
21 ui->output_sink_combo_box->addItem(sink_detail.id); 21 ui->output_sink_combo_box->addItem(id);
22 } 22 }
23 23
24 connect(ui->volume_slider, &QSlider::valueChanged, this, 24 connect(ui->volume_slider, &QSlider::valueChanged, this,
@@ -97,8 +97,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {
97 ui->audio_device_combo_box->addItem(AudioCore::auto_device_name); 97 ui->audio_device_combo_box->addItem(AudioCore::auto_device_name);
98 98
99 const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); 99 const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
100 const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices(); 100 for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) {
101 for (const auto& device : device_list) {
102 ui->audio_device_combo_box->addItem(QString::fromStdString(device)); 101 ui->audio_device_combo_box->addItem(QString::fromStdString(device));
103 } 102 }
104} 103}
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index f9c18ede4..6b3a757e0 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -75,7 +75,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()
75 return item_list; 75 return item_list;
76} 76}
77 77
78WaitTreeText::WaitTreeText(const QString& t) : text(t) {} 78WaitTreeText::WaitTreeText(QString t) : text(std::move(t)) {}
79WaitTreeText::~WaitTreeText() = default; 79WaitTreeText::~WaitTreeText() = default;
80 80
81QString WaitTreeText::GetText() const { 81QString WaitTreeText::GetText() const {
diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h
index 492fb6ac9..e639ef412 100644
--- a/src/yuzu/debugger/wait_tree.h
+++ b/src/yuzu/debugger/wait_tree.h
@@ -52,7 +52,7 @@ private:
52class WaitTreeText : public WaitTreeItem { 52class WaitTreeText : public WaitTreeItem {
53 Q_OBJECT 53 Q_OBJECT
54public: 54public:
55 explicit WaitTreeText(const QString& text); 55 explicit WaitTreeText(QString text);
56 ~WaitTreeText() override; 56 ~WaitTreeText() override;
57 57
58 QString GetText() const override; 58 QString GetText() const override;