diff options
-rwxr-xr-x | README.md | 2 | ||||
-rwxr-xr-x | src/common/CMakeLists.txt | 2 | ||||
-rwxr-xr-x | src/common/fs/file.cpp | 29 | ||||
-rwxr-xr-x | src/common/fs/file.h | 11 | ||||
-rwxr-xr-x | src/common/logging/backend.cpp | 19 | ||||
-rwxr-xr-x | src/common/thread_worker.h | 102 | ||||
-rwxr-xr-x | src/common/unique_function.h | 64 | ||||
-rwxr-xr-x | src/core/hle/service/mii/manager.cpp | 5 | ||||
-rwxr-xr-x | src/tests/CMakeLists.txt | 1 | ||||
-rwxr-xr-x | src/tests/common/unique_function.cpp | 108 |
10 files changed, 320 insertions, 23 deletions
@@ -1,7 +1,7 @@ | |||
1 | yuzu emulator early access | 1 | yuzu emulator early access |
2 | ============= | 2 | ============= |
3 | 3 | ||
4 | This is the source code for early-access 1846. | 4 | This is the source code for early-access 1847. |
5 | 5 | ||
6 | ## Legal Notice | 6 | ## Legal Notice |
7 | 7 | ||
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index a6fa9a85d..e03fffd8d 100755 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
@@ -180,7 +180,6 @@ add_library(common STATIC | |||
180 | thread.cpp | 180 | thread.cpp |
181 | thread.h | 181 | thread.h |
182 | thread_queue_list.h | 182 | thread_queue_list.h |
183 | thread_worker.cpp | ||
184 | thread_worker.h | 183 | thread_worker.h |
185 | threadsafe_queue.h | 184 | threadsafe_queue.h |
186 | time_zone.cpp | 185 | time_zone.cpp |
@@ -188,6 +187,7 @@ add_library(common STATIC | |||
188 | tiny_mt.h | 187 | tiny_mt.h |
189 | tree.h | 188 | tree.h |
190 | uint128.h | 189 | uint128.h |
190 | unique_function.h | ||
191 | uuid.cpp | 191 | uuid.cpp |
192 | uuid.h | 192 | uuid.h |
193 | vector_math.h | 193 | vector_math.h |
diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index 077f34995..274f57659 100755 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp | |||
@@ -306,9 +306,9 @@ bool IOFile::Flush() const { | |||
306 | errno = 0; | 306 | errno = 0; |
307 | 307 | ||
308 | #ifdef _WIN32 | 308 | #ifdef _WIN32 |
309 | const auto flush_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0; | 309 | const auto flush_result = std::fflush(file) == 0; |
310 | #else | 310 | #else |
311 | const auto flush_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0; | 311 | const auto flush_result = std::fflush(file) == 0; |
312 | #endif | 312 | #endif |
313 | 313 | ||
314 | if (!flush_result) { | 314 | if (!flush_result) { |
@@ -320,6 +320,28 @@ bool IOFile::Flush() const { | |||
320 | return flush_result; | 320 | return flush_result; |
321 | } | 321 | } |
322 | 322 | ||
323 | bool IOFile::Commit() const { | ||
324 | if (!IsOpen()) { | ||
325 | return false; | ||
326 | } | ||
327 | |||
328 | errno = 0; | ||
329 | |||
330 | #ifdef _WIN32 | ||
331 | const auto commit_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0; | ||
332 | #else | ||
333 | const auto commit_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0; | ||
334 | #endif | ||
335 | |||
336 | if (!commit_result) { | ||
337 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
338 | LOG_ERROR(Common_Filesystem, "Failed to commit the file at path={}, ec_message={}", | ||
339 | PathToUTF8String(file_path), ec.message()); | ||
340 | } | ||
341 | |||
342 | return commit_result; | ||
343 | } | ||
344 | |||
323 | bool IOFile::SetSize(u64 size) const { | 345 | bool IOFile::SetSize(u64 size) const { |
324 | if (!IsOpen()) { | 346 | if (!IsOpen()) { |
325 | return false; | 347 | return false; |
@@ -347,6 +369,9 @@ u64 IOFile::GetSize() const { | |||
347 | return 0; | 369 | return 0; |
348 | } | 370 | } |
349 | 371 | ||
372 | // Flush any unwritten buffered data into the file prior to retrieving the file size. | ||
373 | std::fflush(file); | ||
374 | |||
350 | std::error_code ec; | 375 | std::error_code ec; |
351 | 376 | ||
352 | const auto file_size = fs::file_size(file_path, ec); | 377 | const auto file_size = fs::file_size(file_path, ec); |
diff --git a/src/common/fs/file.h b/src/common/fs/file.h index 588fe619d..2c4ab4332 100755 --- a/src/common/fs/file.h +++ b/src/common/fs/file.h | |||
@@ -396,13 +396,22 @@ public: | |||
396 | [[nodiscard]] size_t WriteString(std::span<const char> string) const; | 396 | [[nodiscard]] size_t WriteString(std::span<const char> string) const; |
397 | 397 | ||
398 | /** | 398 | /** |
399 | * Attempts to flush any unwritten buffered data into the file and flush the file into the disk. | 399 | * Attempts to flush any unwritten buffered data into the file. |
400 | * | 400 | * |
401 | * @returns True if the flush was successful, false otherwise. | 401 | * @returns True if the flush was successful, false otherwise. |
402 | */ | 402 | */ |
403 | bool Flush() const; | 403 | bool Flush() const; |
404 | 404 | ||
405 | /** | 405 | /** |
406 | * Attempts to commit the file into the disk. | ||
407 | * Note that this is an expensive operation as this forces the operating system to write | ||
408 | * the contents of the file associated with the file descriptor into the disk. | ||
409 | * | ||
410 | * @returns True if the commit was successful, false otherwise. | ||
411 | */ | ||
412 | bool Commit() const; | ||
413 | |||
414 | /** | ||
406 | * Resizes the file to a given size. | 415 | * Resizes the file to a given size. |
407 | * If the file is resized to a smaller size, the remainder of the file is discarded. | 416 | * If the file is resized to a smaller size, the remainder of the file is discarded. |
408 | * If the file is resized to a larger size, the new area appears as if zero-filled. | 417 | * If the file is resized to a larger size, the new area appears as if zero-filled. |
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index b6fa4affb..61dddab3f 100755 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp | |||
@@ -171,19 +171,22 @@ FileBackend::FileBackend(const std::filesystem::path& filename) { | |||
171 | FileBackend::~FileBackend() = default; | 171 | FileBackend::~FileBackend() = default; |
172 | 172 | ||
173 | void FileBackend::Write(const Entry& entry) { | 173 | void FileBackend::Write(const Entry& entry) { |
174 | if (!file->IsOpen()) { | ||
175 | return; | ||
176 | } | ||
177 | |||
174 | using namespace Common::Literals; | 178 | using namespace Common::Literals; |
175 | // prevent logs from going over the maximum size (in case its spamming and the user doesn't | 179 | // Prevent logs from exceeding a set maximum size in the event that log entries are spammed. |
176 | // know) | ||
177 | constexpr std::size_t MAX_BYTES_WRITTEN = 100_MiB; | 180 | constexpr std::size_t MAX_BYTES_WRITTEN = 100_MiB; |
178 | constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1_GiB; | 181 | constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1_GiB; |
179 | 182 | ||
180 | if (!file->IsOpen()) { | 183 | const bool write_limit_exceeded = |
181 | return; | 184 | bytes_written > MAX_BYTES_WRITTEN_EXTENDED || |
182 | } | 185 | (bytes_written > MAX_BYTES_WRITTEN && !Settings::values.extended_logging); |
183 | 186 | ||
184 | if (Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN_EXTENDED) { | 187 | // Close the file after the write limit is exceeded. |
185 | return; | 188 | if (write_limit_exceeded) { |
186 | } else if (!Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN) { | 189 | file->Close(); |
187 | return; | 190 | return; |
188 | } | 191 | } |
189 | 192 | ||
diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h index f1859971f..ddd73220a 100755 --- a/src/common/thread_worker.h +++ b/src/common/thread_worker.h | |||
@@ -7,24 +7,110 @@ | |||
7 | #include <atomic> | 7 | #include <atomic> |
8 | #include <functional> | 8 | #include <functional> |
9 | #include <mutex> | 9 | #include <mutex> |
10 | #include <stop_token> | ||
10 | #include <string> | 11 | #include <string> |
12 | #include <thread> | ||
13 | #include <type_traits> | ||
11 | #include <vector> | 14 | #include <vector> |
12 | #include <queue> | 15 | #include <queue> |
13 | 16 | ||
17 | #include "common/thread.h" | ||
18 | #include "common/unique_function.h" | ||
19 | |||
14 | namespace Common { | 20 | namespace Common { |
15 | 21 | ||
16 | class ThreadWorker final { | 22 | template <class StateType = void> |
23 | class StatefulThreadWorker { | ||
24 | static constexpr bool with_state = !std::is_same_v<StateType, void>; | ||
25 | |||
26 | struct DummyCallable { | ||
27 | int operator()() const noexcept { | ||
28 | return 0; | ||
29 | } | ||
30 | }; | ||
31 | |||
32 | using Task = | ||
33 | std::conditional_t<with_state, UniqueFunction<void, StateType*>, UniqueFunction<void>>; | ||
34 | using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>; | ||
35 | |||
17 | public: | 36 | public: |
18 | explicit ThreadWorker(std::size_t num_workers, const std::string& name); | 37 | explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {}) |
19 | ~ThreadWorker(); | 38 | : workers_queued{num_workers}, thread_name{std::move(name)} { |
20 | void QueueWork(std::function<void()>&& work); | 39 | const auto lambda = [this, func](std::stop_token stop_token) { |
40 | Common::SetCurrentThreadName(thread_name.c_str()); | ||
41 | { | ||
42 | std::conditional_t<with_state, StateType, int> state{func()}; | ||
43 | while (!stop_token.stop_requested()) { | ||
44 | Task task; | ||
45 | { | ||
46 | std::unique_lock lock{queue_mutex}; | ||
47 | if (requests.empty()) { | ||
48 | wait_condition.notify_all(); | ||
49 | } | ||
50 | condition.wait(lock, stop_token, [this] { return !requests.empty(); }); | ||
51 | if (stop_token.stop_requested()) { | ||
52 | break; | ||
53 | } | ||
54 | task = std::move(requests.front()); | ||
55 | requests.pop(); | ||
56 | } | ||
57 | if constexpr (with_state) { | ||
58 | task(&state); | ||
59 | } else { | ||
60 | task(); | ||
61 | } | ||
62 | ++work_done; | ||
63 | } | ||
64 | } | ||
65 | ++workers_stopped; | ||
66 | wait_condition.notify_all(); | ||
67 | }; | ||
68 | threads.reserve(num_workers); | ||
69 | for (size_t i = 0; i < num_workers; ++i) { | ||
70 | threads.emplace_back(lambda); | ||
71 | } | ||
72 | } | ||
73 | |||
74 | StatefulThreadWorker& operator=(const StatefulThreadWorker&) = delete; | ||
75 | StatefulThreadWorker(const StatefulThreadWorker&) = delete; | ||
76 | |||
77 | StatefulThreadWorker& operator=(StatefulThreadWorker&&) = delete; | ||
78 | StatefulThreadWorker(StatefulThreadWorker&&) = delete; | ||
79 | |||
80 | void QueueWork(Task work) { | ||
81 | { | ||
82 | std::unique_lock lock{queue_mutex}; | ||
83 | requests.emplace(std::move(work)); | ||
84 | ++work_scherduled; | ||
85 | } | ||
86 | condition.notify_one(); | ||
87 | } | ||
88 | |||
89 | void WaitForRequests(std::stop_token stop_token = {}) { | ||
90 | std::stop_callback callback(stop_token, [this] { | ||
91 | for (auto& thread : threads) { | ||
92 | thread.request_stop(); | ||
93 | } | ||
94 | }); | ||
95 | std::unique_lock lock{queue_mutex}; | ||
96 | wait_condition.wait(lock, [this] { | ||
97 | return workers_stopped >= workers_queued || work_done >= work_scherduled; | ||
98 | }); | ||
99 | } | ||
21 | 100 | ||
22 | private: | 101 | private: |
23 | std::vector<std::thread> threads; | 102 | std::queue<Task> requests; |
24 | std::queue<std::function<void()>> requests; | ||
25 | std::mutex queue_mutex; | 103 | std::mutex queue_mutex; |
26 | std::condition_variable condition; | 104 | std::condition_variable_any condition; |
27 | std::atomic_bool stop{}; | 105 | std::condition_variable wait_condition; |
106 | std::atomic<size_t> work_scherduled{}; | ||
107 | std::atomic<size_t> work_done{}; | ||
108 | std::atomic<size_t> workers_stopped{}; | ||
109 | std::atomic<size_t> workers_queued{}; | ||
110 | std::string thread_name; | ||
111 | std::vector<std::jthread> threads; | ||
28 | }; | 112 | }; |
29 | 113 | ||
114 | using ThreadWorker = StatefulThreadWorker<>; | ||
115 | |||
30 | } // namespace Common | 116 | } // namespace Common |
diff --git a/src/common/unique_function.h b/src/common/unique_function.h new file mode 100755 index 000000000..0a2ee9bb5 --- /dev/null +++ b/src/common/unique_function.h | |||
@@ -0,0 +1,64 @@ | |||
1 | // Copyright 2021 yuzu emulator team | ||
2 | // Licensed under GPLv2 or any later version | ||
3 | // Refer to the license.txt file included. | ||
4 | |||
5 | #pragma once | ||
6 | |||
7 | #include <memory> | ||
8 | #include <utility> | ||
9 | |||
10 | namespace Common { | ||
11 | |||
12 | template <typename ResultType, typename... Args> | ||
13 | class UniqueFunction { | ||
14 | class CallableBase { | ||
15 | public: | ||
16 | virtual ~CallableBase() = default; | ||
17 | virtual ResultType operator()(Args...) = 0; | ||
18 | }; | ||
19 | |||
20 | template <typename Functor> | ||
21 | class Callable final : public CallableBase { | ||
22 | public: | ||
23 | Callable(Functor&& functor_) : functor{std::move(functor_)} {} | ||
24 | ~Callable() override = default; | ||
25 | |||
26 | ResultType operator()(Args... args) override { | ||
27 | return functor(std::forward<Args>(args)...); | ||
28 | } | ||
29 | |||
30 | private: | ||
31 | Functor functor; | ||
32 | }; | ||
33 | |||
34 | public: | ||
35 | UniqueFunction() = default; | ||
36 | |||
37 | template <typename Functor> | ||
38 | UniqueFunction(Functor&& functor) | ||
39 | : callable{std::make_unique<Callable<Functor>>(std::move(functor))} {} | ||
40 | |||
41 | UniqueFunction& operator=(UniqueFunction<ResultType, Args...>&& rhs) noexcept { | ||
42 | callable = std::move(rhs.callable); | ||
43 | return *this; | ||
44 | } | ||
45 | |||
46 | UniqueFunction(UniqueFunction<ResultType, Args...>&& rhs) noexcept | ||
47 | : callable{std::move(rhs.callable)} {} | ||
48 | |||
49 | ResultType operator()(Args... args) const { | ||
50 | return (*callable)(std::forward<Args>(args)...); | ||
51 | } | ||
52 | |||
53 | explicit operator bool() const noexcept { | ||
54 | return callable != nullptr; | ||
55 | } | ||
56 | |||
57 | UniqueFunction& operator=(const UniqueFunction<ResultType, Args...>&) = delete; | ||
58 | UniqueFunction(const UniqueFunction<ResultType, Args...>&) = delete; | ||
59 | |||
60 | private: | ||
61 | std::unique_ptr<CallableBase> callable; | ||
62 | }; | ||
63 | |||
64 | } // namespace Common | ||
diff --git a/src/core/hle/service/mii/manager.cpp b/src/core/hle/service/mii/manager.cpp index 114aff31c..869d2763f 100755 --- a/src/core/hle/service/mii/manager.cpp +++ b/src/core/hle/service/mii/manager.cpp | |||
@@ -20,6 +20,7 @@ namespace { | |||
20 | 20 | ||
21 | constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; | 21 | constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; |
22 | 22 | ||
23 | constexpr std::size_t BaseMiiCount{2}; | ||
23 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; | 24 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; |
24 | 25 | ||
25 | constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; | 26 | constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; |
@@ -415,7 +416,7 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const { | |||
415 | count += 0; | 416 | count += 0; |
416 | } | 417 | } |
417 | if ((source_flag & SourceFlag::Default) != SourceFlag::None) { | 418 | if ((source_flag & SourceFlag::Default) != SourceFlag::None) { |
418 | count += DefaultMiiCount; | 419 | count += (DefaultMiiCount - BaseMiiCount); |
419 | } | 420 | } |
420 | return static_cast<u32>(count); | 421 | return static_cast<u32>(count); |
421 | } | 422 | } |
@@ -445,7 +446,7 @@ ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_ | |||
445 | return MakeResult(std::move(result)); | 446 | return MakeResult(std::move(result)); |
446 | } | 447 | } |
447 | 448 | ||
448 | for (std::size_t index = 0; index < DefaultMiiCount; index++) { | 449 | for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) { |
449 | result.emplace_back(BuildDefault(index), Source::Default); | 450 | result.emplace_back(BuildDefault(index), Source::Default); |
450 | } | 451 | } |
451 | 452 | ||
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 96bc30cac..c4c012f3d 100755 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt | |||
@@ -5,6 +5,7 @@ add_executable(tests | |||
5 | common/host_memory.cpp | 5 | common/host_memory.cpp |
6 | common/param_package.cpp | 6 | common/param_package.cpp |
7 | common/ring_buffer.cpp | 7 | common/ring_buffer.cpp |
8 | common/unique_function.cpp | ||
8 | core/core_timing.cpp | 9 | core/core_timing.cpp |
9 | core/network/network.cpp | 10 | core/network/network.cpp |
10 | tests.cpp | 11 | tests.cpp |
diff --git a/src/tests/common/unique_function.cpp b/src/tests/common/unique_function.cpp new file mode 100755 index 000000000..ac9912738 --- /dev/null +++ b/src/tests/common/unique_function.cpp | |||
@@ -0,0 +1,108 @@ | |||
1 | // Copyright 2021 yuzu Emulator Project | ||
2 | // Licensed under GPLv2 or any later version | ||
3 | // Refer to the license.txt file included. | ||
4 | |||
5 | #include <string> | ||
6 | |||
7 | #include <catch2/catch.hpp> | ||
8 | |||
9 | #include "common/unique_function.h" | ||
10 | |||
11 | namespace { | ||
12 | struct Noisy { | ||
13 | Noisy() : state{"Default constructed"} {} | ||
14 | Noisy(Noisy&& rhs) noexcept : state{"Move constructed"} { | ||
15 | rhs.state = "Moved away"; | ||
16 | } | ||
17 | Noisy& operator=(Noisy&& rhs) noexcept { | ||
18 | state = "Move assigned"; | ||
19 | rhs.state = "Moved away"; | ||
20 | } | ||
21 | Noisy(const Noisy&) : state{"Copied constructed"} {} | ||
22 | Noisy& operator=(const Noisy&) { | ||
23 | state = "Copied assigned"; | ||
24 | } | ||
25 | |||
26 | std::string state; | ||
27 | }; | ||
28 | } // Anonymous namespace | ||
29 | |||
30 | TEST_CASE("UniqueFunction", "[common]") { | ||
31 | SECTION("Capture reference") { | ||
32 | int value = 0; | ||
33 | Common::UniqueFunction<void> func = [&value] { value = 5; }; | ||
34 | func(); | ||
35 | REQUIRE(value == 5); | ||
36 | } | ||
37 | SECTION("Capture pointer") { | ||
38 | int value = 0; | ||
39 | int* pointer = &value; | ||
40 | Common::UniqueFunction<void> func = [pointer] { *pointer = 5; }; | ||
41 | func(); | ||
42 | REQUIRE(value == 5); | ||
43 | } | ||
44 | SECTION("Move object") { | ||
45 | Noisy noisy; | ||
46 | REQUIRE(noisy.state == "Default constructed"); | ||
47 | |||
48 | Common::UniqueFunction<void> func = [noisy = std::move(noisy)] { | ||
49 | REQUIRE(noisy.state == "Move constructed"); | ||
50 | }; | ||
51 | REQUIRE(noisy.state == "Moved away"); | ||
52 | func(); | ||
53 | } | ||
54 | SECTION("Move construct function") { | ||
55 | int value = 0; | ||
56 | Common::UniqueFunction<void> func = [&value] { value = 5; }; | ||
57 | Common::UniqueFunction<void> new_func = std::move(func); | ||
58 | new_func(); | ||
59 | REQUIRE(value == 5); | ||
60 | } | ||
61 | SECTION("Move assign function") { | ||
62 | int value = 0; | ||
63 | Common::UniqueFunction<void> func = [&value] { value = 5; }; | ||
64 | Common::UniqueFunction<void> new_func; | ||
65 | new_func = std::move(func); | ||
66 | new_func(); | ||
67 | REQUIRE(value == 5); | ||
68 | } | ||
69 | SECTION("Default construct then assign function") { | ||
70 | int value = 0; | ||
71 | Common::UniqueFunction<void> func; | ||
72 | func = [&value] { value = 5; }; | ||
73 | func(); | ||
74 | REQUIRE(value == 5); | ||
75 | } | ||
76 | SECTION("Pass arguments") { | ||
77 | int result = 0; | ||
78 | Common::UniqueFunction<void, int, int> func = [&result](int a, int b) { result = a + b; }; | ||
79 | func(5, 4); | ||
80 | REQUIRE(result == 9); | ||
81 | } | ||
82 | SECTION("Pass arguments and return value") { | ||
83 | Common::UniqueFunction<int, int, int> func = [](int a, int b) { return a + b; }; | ||
84 | REQUIRE(func(5, 4) == 9); | ||
85 | } | ||
86 | SECTION("Destructor") { | ||
87 | int num_destroyed = 0; | ||
88 | struct Foo { | ||
89 | Foo(int* num_) : num{num_} {} | ||
90 | Foo(Foo&& rhs) : num{std::exchange(rhs.num, nullptr)} {} | ||
91 | Foo(const Foo&) = delete; | ||
92 | |||
93 | ~Foo() { | ||
94 | if (num) { | ||
95 | ++*num; | ||
96 | } | ||
97 | } | ||
98 | |||
99 | int* num = nullptr; | ||
100 | }; | ||
101 | Foo object{&num_destroyed}; | ||
102 | { | ||
103 | Common::UniqueFunction<void> func = [object = std::move(object)] {}; | ||
104 | REQUIRE(num_destroyed == 0); | ||
105 | } | ||
106 | REQUIRE(num_destroyed == 1); | ||
107 | } | ||
108 | } | ||