gamescope/src/waitable.h
Joshua Ashton 528eb7ede7 steamcompmgr: Refactor img wait to handle multiple image waits at once
Refactors the image wait thread to handle multiple image waits at once.
2023-11-21 13:55:39 -08:00

216 lines
4.8 KiB
C++

#pragma once
#include <thread>
#include <stdint.h>
#include <sys/epoll.h>
#include "log.hpp"
extern LogScope g_WaitableLog;
namespace gamescope
{
class IWaitable
{
public:
virtual ~IWaitable() {}
virtual int GetFD() { return -1; }
virtual void OnPollIn() {}
virtual void OnPollOut() {}
virtual void OnPollHangUp() {}
void HandleEvents( uint32_t nEvents )
{
if ( nEvents & EPOLLIN )
this->OnPollIn();
if ( nEvents & EPOLLOUT )
this->OnPollOut();
if ( nEvents & EPOLLHUP )
this->OnPollHangUp();
}
};
class CNudgeWaitable final : public IWaitable
{
public:
CNudgeWaitable()
{
if ( pipe2( m_nFDs, O_CLOEXEC | O_NONBLOCK ) != 0 )
Shutdown();
}
~CNudgeWaitable()
{
Shutdown();
}
void Shutdown()
{
for ( int i = 0; i < 2; i++ )
{
if ( m_nFDs[i] >= 0 )
close( m_nFDs[i] );
}
}
void Drain()
{
if ( m_nFDs[0] < 0 )
return;
char buf[1024];
for (;;)
{
if ( read( m_nFDs[0], buf, sizeof( buf ) ) < 0 )
{
if ( errno != EAGAIN )
g_WaitableLog.errorf_errno( "Failed to drain CNudgeWaitable" );
break;
}
}
}
bool Nudge()
{
return write( m_nFDs[1], "\n", 1 ) >= 0;
}
int GetFD() final { return m_nFDs[0]; }
private:
int m_nFDs[2] = { -1, -1 };
};
template <size_t MaxEvents = 1024>
class CWaiter
{
public:
CWaiter()
: m_nEpollFD{ epoll_create1( 0 ) }
{
AddWaitable( &m_NudgeWaitable, EPOLLIN );
}
~CWaiter()
{
Shutdown();
}
void Shutdown()
{
if ( !m_bRunning )
return;
Nudge();
m_bRunning = false;
if ( m_nEpollFD >= 0 )
{
close( m_nEpollFD );
m_nEpollFD = -1;
}
}
bool AddWaitable( IWaitable *pWaitable, uint32_t nEvents = EPOLLIN | EPOLLOUT | EPOLLHUP )
{
epoll_event event =
{
.events = nEvents,
.data =
{
.ptr = reinterpret_cast<void *>( pWaitable ),
},
};
if ( epoll_ctl( m_nEpollFD, EPOLL_CTL_ADD, pWaitable->GetFD(), &event ) != 0 )
{
g_WaitableLog.errorf_errno( "Failed to add waitable" );
return false;
}
return true;
}
void RemoveWaitable( IWaitable *pWaitable )
{
epoll_ctl( m_nEpollFD, EPOLL_CTL_DEL, pWaitable->GetFD(), nullptr );
}
void PollEvents()
{
epoll_event events[MaxEvents];
int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, -1);
if ( !m_bRunning )
return;
if ( nEventCount < 0 )
{
g_WaitableLog.errorf_errno( "Failed to epoll_wait in CAsyncWaiter" );
return;
}
for ( int i = 0; i < nEventCount; i++ )
{
epoll_event &event = events[i];
IWaitable *pWaitable = reinterpret_cast<IWaitable *>(event.data.ptr);
pWaitable->HandleEvents( event.events );
}
}
bool Nudge()
{
return m_NudgeWaitable.Nudge();
}
bool IsRunning()
{
return m_bRunning;
}
private:
std::atomic<bool> m_bRunning = { true };
CNudgeWaitable m_NudgeWaitable;
int m_nEpollFD = -1;
};
template <size_t MaxEvents = 1024>
class CAsyncWaiter : public CWaiter<MaxEvents>
{
public:
CAsyncWaiter( const char *pszThreadName )
: m_Thread{ [cWaiter = this, cName = pszThreadName](){ cWaiter->WaiterThreadFunc(cName); } }
{
}
~CAsyncWaiter()
{
Shutdown();
}
void Shutdown()
{
CWaiter<MaxEvents>::Shutdown();
if ( m_Thread.joinable() )
m_Thread.join();
}
void WaiterThreadFunc( const char *pszThreadName )
{
pthread_setname_np( pthread_self(), pszThreadName );
while ( this->IsRunning() )
CWaiter<MaxEvents>::PollEvents();
}
private:
std::thread m_Thread;
};
}