mirror of
https://github.com/LMMS/lmms.git
synced 2025-12-23 22:58:33 -05:00
649 lines
12 KiB
C++
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
|