Files
lmms/include/RemotePluginBase.h

649 lines
12 KiB
C++

/*
* RemotePluginBase.h - base class providing RPC like mechanisms
*
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef LMMS_REMOTE_PLUGIN_BASE_H
#define LMMS_REMOTE_PLUGIN_BASE_H
#include <atomic> // IWYU pragma: keep
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include "lmmsconfig.h"
#if !(defined(LMMS_HAVE_SYS_IPC_H) && defined(LMMS_HAVE_SEMAPHORE_H))
#define SYNC_WITH_SHM_FIFO
#ifdef LMMS_HAVE_PROCESS_H
#include <process.h>
#endif
#else // !(LMMS_HAVE_SYS_IPC_H && LMMS_HAVE_SEMAPHORE_H)
#ifdef LMMS_HAVE_UNISTD_H
#include <unistd.h>
#endif
#endif // !(LMMS_HAVE_SYS_IPC_H && LMMS_HAVE_SEMAPHORE_H)
#ifdef LMMS_HAVE_LOCALE_H
#include <clocale> // IWYU pragma: keep
#endif
#ifdef LMMS_HAVE_PTHREAD_H
#include <pthread.h>
#endif
#ifdef BUILD_REMOTE_PLUGIN_CLIENT
#undef LMMS_EXPORT
#define LMMS_EXPORT
#ifndef SYNC_WITH_SHM_FIFO
#include <sys/socket.h>
#include <sys/un.h>
#endif // SYNC_WITH_SHM_FIFO
#else // BUILD_REMOTE_PLUGIN_CLIENT
#include "lmms_export.h"
#include <QString>
#ifndef SYNC_WITH_SHM_FIFO
#include <poll.h>
#include <unistd.h> // IWYU pragma: keep
#endif // SYNC_WITH_SHM_FIFO
#endif // BUILD_REMOTE_PLUGIN_CLIENT
#ifdef SYNC_WITH_SHM_FIFO
#include "SharedMemory.h"
#include "SystemSemaphore.h"
#endif
namespace lmms
{
#ifdef SYNC_WITH_SHM_FIFO
// sometimes we need to exchange bigger messages (e.g. for VST parameter dumps)
// so set a usable value here
const int SHM_FIFO_SIZE = 512*1024;
// implements a FIFO inside a shared memory segment
class shmFifo
{
// need this union to handle different sizes of sem_t on 32 bit
// and 64 bit platforms
union sem32_t
{
int semKey;
char fill[32];
} ;
struct shmData
{
sem32_t dataSem; // semaphore for locking this
// FIFO management data
sem32_t messageSem; // semaphore for incoming messages
int32_t startPtr; // current start of FIFO in memory
int32_t endPtr; // current end of FIFO in memory
char data[SHM_FIFO_SIZE]; // actual data
} ;
public:
#ifndef BUILD_REMOTE_PLUGIN_CLIENT
// constructor for master-side
shmFifo() :
m_invalid( false ),
m_master( true ),
m_lockDepth( 0 )
{
m_data.create();
m_data->startPtr = m_data->endPtr = 0;
static int k = 0;
m_data->dataSem.semKey = ( getpid()<<10 ) + ++k;
m_data->messageSem.semKey = ( getpid()<<10 ) + ++k;
m_dataSem = SystemSemaphore{std::to_string(m_data->dataSem.semKey), 1u};
m_messageSem = SystemSemaphore{std::to_string(m_data->messageSem.semKey), 0u};
}
#endif
// constructor for remote-/client-side - use _shm_key for making up
// the connection to master
shmFifo(const std::string& shmKey) :
m_invalid( false ),
m_master( false ),
m_lockDepth( 0 )
{
m_data.attach(shmKey);
m_dataSem = SystemSemaphore{std::to_string(m_data->dataSem.semKey)};
m_messageSem = SystemSemaphore{std::to_string(m_data->messageSem.semKey)};
}
inline bool isInvalid() const
{
return m_invalid;
}
void invalidate()
{
m_invalid = true;
}
// do we act as master (i.e. not as remote-process?)
inline bool isMaster() const
{
return m_master;
}
// recursive lock
inline void lock()
{
if( !isInvalid() && m_lockDepth.fetch_add( 1 ) == 0 )
{
m_dataSem.acquire();
}
}
// recursive unlock
inline void unlock()
{
if( m_lockDepth.fetch_sub( 1 ) <= 1 )
{
m_dataSem.release();
}
}
// wait until message-semaphore is available
inline void waitForMessage()
{
if( !isInvalid() )
{
m_messageSem.acquire();
}
}
// increase message-semaphore
inline void messageSent()
{
m_messageSem.release();
}
inline int32_t readInt()
{
int32_t i;
read( &i, sizeof( i ) );
return i;
}
inline void writeInt( const int32_t & _i )
{
write( &_i, sizeof( _i ) );
}
inline std::string readString()
{
const int len = readInt();
if( len )
{
char * sc = new char[len + 1];
read( sc, len );
sc[len] = 0;
std::string s( sc );
delete[] sc;
return s;
}
return std::string();
}
inline void writeString( const std::string & _s )
{
const int len = _s.size();
writeInt( len );
write( _s.c_str(), len );
}
inline bool messagesLeft()
{
if( isInvalid() )
{
return false;
}
lock();
const bool empty = ( m_data->startPtr == m_data->endPtr );
unlock();
return !empty;
}
const std::string& shmKey() const
{
return m_data.key();
}
private:
void read( void * _buf, int _len )
{
if( isInvalid() )
{
memset( _buf, 0, _len );
return;
}
lock();
while( isInvalid() == false &&
_len > m_data->endPtr - m_data->startPtr )
{
unlock();
#ifndef LMMS_BUILD_WIN32
usleep( 5 );
#endif
lock();
}
std::memcpy(_buf, m_data->data + m_data->startPtr, _len);
m_data->startPtr += _len;
// nothing left?
if( m_data->startPtr == m_data->endPtr )
{
// then reset to 0
m_data->startPtr = m_data->endPtr = 0;
}
unlock();
}
void write( const void * _buf, int _len )
{
if( isInvalid() || _len > SHM_FIFO_SIZE )
{
return;
}
lock();
while( _len > SHM_FIFO_SIZE - m_data->endPtr )
{
// if no space is left, try to move data to front
if( m_data->startPtr > 0 )
{
memmove( m_data->data,
m_data->data + m_data->startPtr,
m_data->endPtr - m_data->startPtr );
m_data->endPtr = m_data->endPtr -
m_data->startPtr;
m_data->startPtr = 0;
}
unlock();
#ifndef LMMS_BUILD_WIN32
usleep( 5 );
#endif
lock();
}
std::memcpy(m_data->data + m_data->endPtr, _buf, _len);
m_data->endPtr += _len;
unlock();
}
volatile bool m_invalid;
bool m_master;
SharedMemory<shmData> m_data;
SystemSemaphore m_dataSem;
SystemSemaphore m_messageSem;
std::atomic_int m_lockDepth;
};
#endif // SYNC_WITH_SHM_FIFO
enum RemoteMessageIDs
{
IdUndefined,
IdHostInfoGotten,
IdInitDone,
IdQuit,
IdSyncKey,
IdSampleRateInformation,
IdBufferSizeInformation,
IdInformationUpdated,
IdMidiEvent,
IdStartProcessing,
IdProcessingDone,
IdChangeSharedMemoryKey,
IdChangeInputCount,
IdChangeOutputCount,
IdChangeInputOutputCount,
IdShowUI,
IdHideUI,
IdToggleUI,
IdIsUIVisible,
IdSaveSettingsToString,
IdSaveSettingsToFile,
IdLoadSettingsFromString,
IdLoadSettingsFromFile,
IdSavePresetFile,
IdLoadPresetFile,
IdDebugMessage,
IdIdle,
IdUserBase = 64
} ;
class LMMS_EXPORT RemotePluginBase
{
public:
struct message
{
message() :
id( IdUndefined ),
data()
{
}
message( const message & _m ) = default;
message( int _id ) :
id( _id ),
data()
{
}
inline message & addString( const std::string & _s )
{
data.push_back( _s );
return *this;
}
message & addInt( int _i )
{
char buf[32];
std::snprintf(buf, 32, "%d", _i);
data.emplace_back( buf );
return *this;
}
message & addFloat( float _f )
{
char buf[32];
std::snprintf(buf, 32, "%f", _f);
data.emplace_back( buf );
return *this;
}
inline std::string getString( int _p = 0 ) const
{
return data[_p];
}
#ifndef BUILD_REMOTE_PLUGIN_CLIENT
inline QString getQString( int _p = 0 ) const
{
return QString::fromStdString( getString( _p ) );
}
#endif
inline int getInt( int _p = 0 ) const
{
return atoi( data[_p].c_str() );
}
inline float getFloat( int _p ) const
{
return (float) atof( data[_p].c_str() );
}
inline bool operator==( const message & _m ) const
{
return( id == _m.id );
}
int id;
private:
std::vector<std::string> data;
friend class RemotePluginBase;
} ;
#ifdef SYNC_WITH_SHM_FIFO
RemotePluginBase( shmFifo * _in, shmFifo * _out );
#else
RemotePluginBase();
#endif
virtual ~RemotePluginBase();
#ifdef SYNC_WITH_SHM_FIFO
void reset( shmFifo *in, shmFifo *out )
{
delete m_in;
delete m_out;
m_in = in;
m_out = out;
}
#endif
int sendMessage( const message & _m );
message receiveMessage();
inline bool isInvalid() const
{
#ifdef SYNC_WITH_SHM_FIFO
return m_in->isInvalid() || m_out->isInvalid();
#else
return m_invalid;
#endif
}
message waitForMessage( const message & _m,
bool _busy_waiting = false );
inline message fetchAndProcessNextMessage()
{
message m = receiveMessage();
processMessage( m );
return m;
}
#ifndef SYNC_WITH_SHM_FIFO
inline int32_t readInt()
{
int32_t i;
read( &i, sizeof( i ) );
return i;
}
inline void writeInt( const int32_t & _i )
{
write( &_i, sizeof( _i ) );
}
inline std::string readString()
{
const int len = readInt();
if( len )
{
char * sc = new char[len + 1];
read( sc, len );
sc[len] = 0;
std::string s( sc );
delete[] sc;
return s;
}
return std::string();
}
inline void writeString( const std::string & _s )
{
const int len = _s.size();
writeInt( len );
write( _s.c_str(), len );
}
#endif // SYNC_WITH_SHM_FIFO
#ifndef BUILD_REMOTE_PLUGIN_CLIENT
inline bool messagesLeft()
{
#ifdef SYNC_WITH_SHM_FIFO
return m_in->messagesLeft();
#else
struct pollfd pollin;
pollin.fd = m_socket;
pollin.events = POLLIN;
if ( poll( &pollin, 1, 0 ) == -1 )
{
qWarning( "Unexpected poll error." );
}
return pollin.revents & POLLIN;
#endif
}
inline void fetchAndProcessAllMessages()
{
while( messagesLeft() )
{
fetchAndProcessNextMessage();
}
}
static bool isMainThreadWaiting()
{
return waitDepthCounter() > 0;
}
#endif // BUILD_REMOTE_PLUGIN_CLIENT
virtual bool processMessage( const message & _m ) = 0;
protected:
#ifdef SYNC_WITH_SHM_FIFO
inline const shmFifo * in() const
{
return m_in;
}
inline const shmFifo * out() const
{
return m_out;
}
#endif
inline void invalidate()
{
#ifdef SYNC_WITH_SHM_FIFO
m_in->invalidate();
m_out->invalidate();
m_in->messageSent();
#else
m_invalid = true;
#endif
}
#ifndef SYNC_WITH_SHM_FIFO
int m_socket;
#endif
private:
#ifndef BUILD_REMOTE_PLUGIN_CLIENT
static int & waitDepthCounter()
{
static int waitDepth = 0;
return waitDepth;
}
#endif
#ifdef SYNC_WITH_SHM_FIFO
shmFifo * m_in;
shmFifo * m_out;
#else
void read( void * _buf, int _len )
{
if( isInvalid() )
{
memset( _buf, 0, _len );
return;
}
char * buf = (char *) _buf;
int remaining = _len;
while ( remaining )
{
ssize_t nread = ::read( m_socket, buf, remaining );
switch ( nread )
{
case -1:
fprintf( stderr,
"Error while reading.\n" );
case 0:
invalidate();
memset( _buf, 0, _len );
return;
}
buf += nread;
remaining -= nread;
}
}
void write( const void * _buf, int _len )
{
if( isInvalid() )
{
return;
}
const char * buf = (const char *) _buf;
int remaining = _len;
while ( remaining )
{
ssize_t nwritten = ::write( m_socket, buf, remaining );
switch ( nwritten )
{
case -1:
fprintf( stderr,
"Error while writing.\n" );
case 0:
invalidate();
return;
}
buf += nwritten;
remaining -= nwritten;
}
}
bool m_invalid;
pthread_mutex_t m_receiveMutex;
pthread_mutex_t m_sendMutex;
#endif // SYNC_WITH_SHM_FIFO
} ;
} // namespace lmms
#endif // LMMS_REMOTE_PLUGIN_BASE_H