Compare commits

...

1 Commits

Author SHA1 Message Date
Leonard Hecker
8ace5a2ffb Add til::fused_event 2025-12-17 13:18:07 +01:00

View File

@@ -86,6 +86,177 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
winrt::event<winrt::Windows::Foundation::TypedEventHandler<SenderT, ArgsT>> _handlers;
};
// Unlike winrt::event, this event will only call handlers once at most.
// It's otherwise a copy of winrt::event's implementation.
template<typename ArgsT>
struct fused_event
{
using delegate_type = ArgsT;
using delegate_array = winrt::com_ptr<winrt::impl::event_array<delegate_type>>;
fused_event() = default;
fused_event(const fused_event&) = delete;
fused_event& operator=(const fused_event&) = delete;
fused_event(fused_event&& other)
{
const winrt::slim_lock_guard change_guard{ other.m_change };
if (!other.m_targets)
{
return;
}
const winrt::slim_lock_guard swap_guard{ other.m_swap };
m_targets = std::move(other.m_targets);
}
fused_event& operator=(fused_event&& other)
{
if (this != &other)
{
const winrt::slim_lock_guard other_change_guard{ other.m_change };
const winrt::slim_lock_guard other_swap_guard{ other.m_swap };
const winrt::slim_lock_guard self_change_guard{ m_change };
const winrt::slim_lock_guard self_swap_guard{ m_swap };
m_targets = std::move(other.m_targets);
}
return *this;
}
explicit operator bool() const noexcept
{
return m_targets != nullptr;
}
winrt::event_token operator()(const delegate_type& delegate)
{
return add_agile(winrt::impl::make_agile_delegate(delegate));
}
void operator()(const winrt::event_token token)
{
// Extends life of old targets array to release delegates outside of lock.
delegate_array temp_targets;
{
const winrt::slim_lock_guard change_guard{ m_change };
if (!m_targets)
{
return;
}
uint32_t available_slots = m_targets->size() - 1;
delegate_array new_targets;
bool removed = false;
if (available_slots == 0)
{
if (get_token(*m_targets->begin()) == token)
{
removed = true;
}
}
else
{
new_targets = winrt::impl::make_event_array<delegate_type>(available_slots);
auto new_iterator = new_targets->begin();
for (delegate_type const& element : *m_targets)
{
if (!removed && token == get_token(element))
{
removed = true;
continue;
}
if (available_slots == 0)
{
WINRT_ASSERT(!removed);
break;
}
*new_iterator = element;
++new_iterator;
--available_slots;
}
}
if (removed)
{
const winrt::slim_lock_guard swap_guard{ m_swap };
temp_targets = std::exchange(m_targets, std::move(new_targets));
}
}
}
template<typename... Arg>
void raise(const Arg&... args)
{
delegate_array temp_targets;
{
const winrt::slim_lock_guard change_guard{ m_change };
if (!m_targets)
{
return;
}
const winrt::slim_lock_guard swap_guard{ m_swap };
temp_targets = std::move(m_targets);
}
if (temp_targets)
{
for (const auto& element : *temp_targets)
{
if (!winrt::impl::invoke(element, args...))
{
operator()(get_token(element));
}
}
}
}
private:
WINRT_IMPL_NOINLINE winrt::event_token add_agile(delegate_type delegate)
{
winrt::event_token token;
// Extends life of old targets array to release delegates outside of lock.
delegate_array temp_targets;
{
const winrt::slim_lock_guard change_guard{ m_change };
const auto size = !m_targets ? 0 : m_targets->size();
auto new_targets = winrt::impl::make_event_array<delegate_type>(size + 1);
if (m_targets)
{
std::copy_n(m_targets->begin(), m_targets->size(), new_targets->begin());
}
new_targets->back() = std::move(delegate);
token = get_token(new_targets->back());
const winrt::slim_lock_guard swap_guard{ m_swap };
temp_targets = std::exchange(m_targets, std::move(new_targets));
}
return token;
}
winrt::event_token get_token(delegate_type const& delegate) const noexcept
{
return winrt::event_token{ reinterpret_cast<int64_t>(WINRT_IMPL_EncodePointer(winrt::get_abi(delegate))) };
}
delegate_array m_targets;
winrt::slim_mutex m_swap;
winrt::slim_mutex m_change;
};
#endif
#ifdef WINRT_Windows_UI_Xaml_Data_H