98 lines
3.1 KiB
Plaintext
98 lines
3.1 KiB
Plaintext
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file completionCounter.I
|
|
* @author rdb
|
|
* @date 2025-01-22
|
|
*/
|
|
|
|
/**
|
|
*
|
|
*/
|
|
INLINE CompletionCounter::
|
|
~CompletionCounter() {
|
|
CounterData *data = _data;
|
|
if (data != nullptr) {
|
|
// then() is not called; we still need something that destructs the data
|
|
// when done.
|
|
auto prev_function = data->_function.exchange(&abandon_callback, std::memory_order_relaxed);
|
|
if (prev_function == nullptr) {
|
|
// Was already done.
|
|
delete data;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a new token. May not be called after then().
|
|
*/
|
|
INLINE CompletionToken CompletionCounter::
|
|
make_token() {
|
|
CompletionToken token;
|
|
if (_data == nullptr) {
|
|
_data = new CounterData;
|
|
_data->_function = &initial_callback;
|
|
}
|
|
auto old_value = _data->_counter.fetch_add(1);
|
|
nassertr(old_value >= 0, token);
|
|
token._callback._data = _data;
|
|
return token;
|
|
}
|
|
|
|
/**
|
|
* Runs the given callback immediately upon completion. If the counter is
|
|
* already done, runs it immediately. This requires an rvalue because it
|
|
* consumes the counter, use std::move() if you don't have an rvalue.
|
|
*
|
|
* The callback will either be called immediately or directly when the last
|
|
* token calls complete(), however, it may also be called if a token is
|
|
* destroyed. This may happen at unexpected times, such as when the lambda
|
|
* holding the token is destroyed prematurely. In this case, however, the
|
|
* passed success argument will always be false.
|
|
*/
|
|
template<class Callable>
|
|
INLINE void CompletionCounter::
|
|
then(Callable callable) && {
|
|
// Replace the callback pointer with something that calls the given callable
|
|
// once the count reaches 0.
|
|
CounterData *data = _data;
|
|
nassertv(data != nullptr);
|
|
_data = nullptr;
|
|
if (data->_function.load(std::memory_order_acquire) == nullptr) {
|
|
// Already done.
|
|
callable((data->_counter.load(std::memory_order_relaxed) & ~0xffff) == 0);
|
|
delete data;
|
|
return;
|
|
}
|
|
|
|
static_assert(sizeof(Callable) <= sizeof(data->_storage),
|
|
"raise storage size in completionCounter.h or reduce lambda captures");
|
|
|
|
new (data->_storage) Callable(std::move(callable));
|
|
|
|
Completable::CallbackFunction *new_function =
|
|
[] (Completable::Data *data_ptr, bool success) {
|
|
CounterData *data = (CounterData *)data_ptr;
|
|
auto prev_count = data->_counter.fetch_add((success ? 0 : 0x10000) - 1, std::memory_order_release);
|
|
if ((short)(prev_count & 0xffff) > 1) {
|
|
return;
|
|
}
|
|
|
|
Callable *callable = (Callable *)data->_storage;
|
|
std::move(*callable)(success && (prev_count & ~0xffff) == 0);
|
|
callable->~Callable();
|
|
delete data;
|
|
};
|
|
|
|
auto prev_function = data->_function.exchange(new_function, std::memory_order_acq_rel);
|
|
if (UNLIKELY(prev_function == nullptr)) {
|
|
// Last token finished in the meantime.
|
|
new_function(data, (data->_counter.load(std::memory_order_relaxed) & ~0xffff) == 0);
|
|
}
|
|
}
|