This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,241 @@
#ifdef TARGET_HAS_ThirdPartyLSL
/*
* Notes: This code should be kept compatible with changes to LSL Input Driver and Output Plugin in OpenViBE Acquisition Server.
*
*/
#include "ovpCBoxAlgorithmLSLExport.h"
#include <ctime>
#include <iostream>
#include <system/ovCTime.h>
#include <labstreamlayer/Utils.hpp>
namespace OpenViBE {
namespace Plugins {
namespace NetworkIO {
bool CBoxAlgorithmLSLExport::initialize()
{
m_signalOutlet = nullptr;
m_stimulusOutlet = nullptr;
m_signalDecoder.initialize(*this, 0);
m_stimDecoder.initialize(*this, 1);
m_signalName = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_markerName = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
// These are supposed to be unique, so we don't have them in the box config
m_signalID = CIdentifier::random().toString();
m_markerID = CIdentifier::random().toString();
while (m_markerID == m_signalID) { m_markerID = CIdentifier::random().toString(); } // very unlikely
this->getLogManager() << Kernel::LogLevel_Trace << "Will create streams [" << m_signalName << ", id " << m_signalID
<< "] and [" << m_markerName << ", id " << m_markerID << "]\n";
m_useOVTimestamps = this->getConfigurationManager().expandAsBoolean("${LSL_UseOVTimestamps}", m_useOVTimestamps);
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmLSLExport::uninitialize()
{
m_signalDecoder.uninitialize();
m_stimDecoder.uninitialize();
if (m_signalOutlet)
{
delete m_signalOutlet;
m_signalOutlet = nullptr;
}
if (m_stimulusOutlet)
{
delete m_stimulusOutlet;
m_stimulusOutlet = nullptr;
}
m_buffer.clear();
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmLSLExport::processInput(const size_t /*index*/)
{
// ready to process !
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmLSLExport::process()
{
// the dynamic box context describes the current state of the box inputs and outputs (i.e. the chunks)
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
// Process signals
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
if (m_startTime == CTime(0))
{
// This should be the ov time when acquisition client tagged the first chunk with [0, t], t=bufferSize/samplingRate.
// As the true run time is not know here, we have to do with a slightly delayed estimate. (The delay is the duration
// it took from stamping the first chunk to this point in the code)
m_startTime = System::Time::zgetTime();
}
m_signalDecoder.decode(i);
if (m_signalDecoder.isHeaderReceived() && !m_signalOutlet)
{
const size_t nChannel = m_signalDecoder.getOutputMatrix()->getDimensionSize(0);
const size_t samplesPerBlock = m_signalDecoder.getOutputMatrix()->getDimensionSize(1);
const size_t frequency = m_signalDecoder.getOutputSamplingRate();
m_buffer.resize(nChannel);
// Open a signal stream
lsl::stream_info signalInfo(m_signalName.toASCIIString(), "signal", int(nChannel), int(frequency), lsl::cf_float32, m_signalID.toASCIIString());
lsl::xml_element channels = signalInfo.desc().append_child("channels");
//m_signalDecoder.getOutputMatrix()->getDimensionLabel(0, 1);
for (size_t c = 0; c < nChannel; ++c)
{
const char* name = m_signalDecoder.getOutputMatrix()->getDimensionLabel(0, c);
channels.append_child("channel").append_child_value("label", name).append_child_value("unit", "unknown").append_child_value("type", "signal");
}
#ifdef DEBUG
lsl::xml_element debug = signalInfo.desc().child("channels");
if(debug.child("channel").child_value("label")) { std::cout << "channel label " << debug.child("channel").child_value("label") << "\n"; }
#endif
// make a new outlet
try { m_signalOutlet = new lsl::stream_outlet(signalInfo, samplesPerBlock); }
catch (...)
{
this->getLogManager() << "Unable to create signal outlet\n";
return false;
}
}
if (m_signalDecoder.isBufferReceived() && m_signalOutlet)
{
if (m_signalOutlet->have_consumers())
{
const CMatrix* matrix = m_signalDecoder.getOutputMatrix();
const size_t nChannel = matrix->getDimensionSize(0);
const size_t samplesPerBlock = matrix->getDimensionSize(1);
const double* iBuffer = matrix->getBuffer();
const CTime chunkStartTime(boxContext.getInputChunkStartTime(0, i));
const CTime chunkEndTime(boxContext.getInputChunkEndTime(0, i));
if (samplesPerBlock == 0)
{
this->getLogManager() << Kernel::LogLevel_Error << "Unable to process signal with 0 samples per buffer \n";
return false;
}
// note: the step computed below should be exactly the same as could be obtained from the sampling rate
// n.b. this would work more "lsl-like" if the timestamps were the real acquisition timestamps
if (m_useOVTimestamps)
{
const double sampleStepInSec =
(chunkEndTime - chunkStartTime).toSeconds() / static_cast<double>(samplesPerBlock);
const double chunkStartInSec = chunkStartTime.toSeconds();
for (size_t sample = 0; sample < samplesPerBlock; ++sample)
{
for (size_t channel = 0; channel < nChannel; ++channel)
{
m_buffer[channel] = static_cast<float>(iBuffer[channel * samplesPerBlock + sample]);
}
m_signalOutlet->push_sample(m_buffer, chunkStartInSec + sampleStepInSec * static_cast<double>(sample));
}
}
else
{
const uint64_t sampleStep = (chunkEndTime - chunkStartTime).time() / uint64_t(samplesPerBlock);
for (size_t sample = 0; sample < samplesPerBlock; ++sample)
{
for (size_t channel = 0; channel < nChannel; ++channel)
{
m_buffer[channel] = static_cast<float>(iBuffer[channel * samplesPerBlock + sample]);
}
const double lslTime = OpenViBE::LabStreamLayer::getLSLRelativeTime(m_startTime + chunkStartTime + CTime(sample * sampleStep));
m_signalOutlet->push_sample(m_buffer, lslTime);
}
}
}
}
if (m_signalDecoder.isEndReceived())
{
if (m_signalOutlet)
{
delete m_signalOutlet;
m_signalOutlet = nullptr;
}
}
}
// Process stimuli -> LSL markers.
// Note that stimuli with identifiers not fitting to int will be mangled by a static cast.
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
{
if (m_startTime == CTime(0))
{
// Initialisation done here too as the box may only receive stimulations
m_startTime = System::Time::zgetTime();
}
m_stimDecoder.decode(i);
if (m_stimDecoder.isHeaderReceived() && !m_stimulusOutlet)
{
// Open a stimulus stream
lsl::stream_info info(m_markerName.toASCIIString(), "Markers", 1, lsl::IRREGULAR_RATE, lsl::cf_int32, m_markerID.toASCIIString());
info.desc().append_child("channels").append_child("channel").append_child_value("label", "Stimulations").append_child_value("type", "marker");
try { m_stimulusOutlet = new lsl::stream_outlet(info); }
catch (...)
{
this->getLogManager() << "Unable to create marker outlet\n";
return false;
}
}
if (m_stimDecoder.isBufferReceived() && m_stimulusOutlet)
{
// Output stimuli
if (m_stimulusOutlet->have_consumers())
{
const IStimulationSet* stimSet = m_stimDecoder.getOutputStimulationSet();
for (size_t s = 0; s < stimSet->getStimulationCount(); ++s)
{
const int code = int(stimSet->getStimulationIdentifier(s));
const double date = m_useOVTimestamps ? CTime(stimSet->getStimulationDate(s)).toSeconds()
: OpenViBE::LabStreamLayer::getLSLRelativeTime(m_startTime + CTime(stimSet->getStimulationDate(s)));
m_stimulusOutlet->push_sample(&code, date);
}
}
}
if (m_stimDecoder.isEndReceived())
{
if (m_stimulusOutlet)
{
delete m_stimulusOutlet;
m_stimulusOutlet = nullptr;
}
}
}
return true;
}
#endif
} // namespace NetworkIO
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,111 @@
#pragma once
#ifdef TARGET_HAS_ThirdPartyLSL
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <lsl_cpp.h>
#include <ctime>
#include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace NetworkIO {
/**
* \class CBoxAlgorithmLSLExport
* \author Jussi T. Lindgren (Inria)
* \date Fri Jan 30 09:55:22 2015
* \brief The class CBoxAlgorithmLSLExport describes the box LSL Export.
*
*/
class CBoxAlgorithmLSLExport final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_LSLExport)
protected:
// Decoders
Toolkit::TStimulationDecoder<CBoxAlgorithmLSLExport> m_stimDecoder;
Toolkit::TSignalDecoder<CBoxAlgorithmLSLExport> m_signalDecoder;
lsl::stream_outlet* m_signalOutlet = nullptr;
lsl::stream_outlet* m_stimulusOutlet = nullptr;
std::vector<float> m_buffer;
CString m_signalName;
CString m_signalID;
CString m_markerName;
CString m_markerID;
bool m_useOVTimestamps = false;
CTime m_startTime = CTime(0);
};
class CBoxAlgorithmLSLExportListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
/**
* \class CBoxAlgorithmLSLExportDesc
* \author Jussi T. Lindgren (Inria)
* \date Fri Jan 30 09:55:22 2015
* \brief Descriptor of the box LSL Export.
*
*/
class CBoxAlgorithmLSLExportDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("LSL Export"); }
CString getAuthorName() const override { return CString("Jussi T. Lindgren"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Send input stream out via LabStreamingLayer (LSL)"); }
CString getDetailedDescription() const override { return CString("\n"); }
CString getCategory() const override { return CString("Acquisition and network IO"); }
CString getVersion() const override { return CString("0.1"); }
CString getStockItemName() const override { return CString("gtk-connect"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_LSLExport; }
IPluginObject* create() override { return new CBoxAlgorithmLSLExport; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmLSLExportListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addInput("Input stimulations", OV_TypeId_Stimulations);
prototype.addSetting("Signal stream", OV_TypeId_String, "openvibeSignal");
prototype.addSetting("Marker stream", OV_TypeId_String, "openvibeMarkers");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_LSLExportDesc)
};
} // namespace NetworkIO
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_Boost
@@ -0,0 +1,214 @@
#include "ovpCBoxAlgorithmSharedMemoryWriter.h"
#include <iostream>
#include <sstream>
#include <boost/interprocess/sync/scoped_lock.hpp>
namespace OpenViBE {
namespace Plugins {
namespace FileReadingAndWriting {
//struct timeval currentLTime;
#define time2ms(x, y) ((x) * 1000 + (y) / 1000.0) + 0.5
bool CBoxAlgorithmSharedMemoryWriter::initialize()
{
//remove and create shared memory
m_sharedMemoryName = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0); // can be local variable
boost::interprocess::shared_memory_object::remove(m_sharedMemoryName.toASCIIString());
m_sharedMemoryArray = boost::interprocess::managed_shared_memory(boost::interprocess::create_only, m_sharedMemoryName.toASCIIString(), 655360);
//remove and create mutex
m_mutexName = m_sharedMemoryName + CString("_Mutex");
boost::interprocess::named_mutex::remove(m_mutexName.toASCIIString());
m_mutex = new boost::interprocess::named_mutex(boost::interprocess::open_or_create, m_mutexName.toASCIIString());
//create shared vector for meta info (type and name)
const ShmemAllocatorMetaInfo metaInfo(m_sharedMemoryArray.get_segment_manager());
MyVectorMetaInfo* metaInfoVector = m_sharedMemoryArray.construct<MyVectorMetaInfo>("MetaInfo")(std::less<ShmString>(), metaInfo);
const StringAllocator instString(m_sharedMemoryArray.get_segment_manager());
//fill meta info vector and create shared vector variable for the appropriate types
const Kernel::IBox& boxContext = this->getStaticBoxContext();
for (size_t i = 0; i < boxContext.getInputCount(); ++i)
{
CIdentifier typeID;
std::ostringstream convert; // stream used for the conversion
convert << i;
boxContext.getInputType(i, typeID);
if (typeID == OVTK_TypeId_StreamedMatrix)
{
m_decoders.push_back(new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmSharedMemoryWriter>());
ShmString name("Matrix", instString);
name += ShmString(convert.str().c_str(), instString);
metaInfoVector->insert(std::make_pair(name, typeID));
const ShmemAllocatorMatrix inst(m_sharedMemoryArray.get_segment_manager());
m_streamedMatrices.push_back(m_sharedMemoryArray.construct<MyVectorStreamedMatrix>(name.c_str())(inst));
this->getLogManager() << Kernel::LogLevel_Info << "Constructed variable in shared memory of type matrix with name " << name.c_str() << "\n";
}
else if (typeID == OVTK_TypeId_Stimulations)
{
m_decoders.push_back(new Toolkit::TStimulationDecoder<CBoxAlgorithmSharedMemoryWriter>());
ShmString name("Stimuli", instString);
name += ShmString(convert.str().c_str(), instString);
metaInfoVector->insert(std::make_pair(name, typeID));
const ShmemAllocatorStimulation inst(m_sharedMemoryArray.get_segment_manager());
m_stimSets.push_back(m_sharedMemoryArray.construct<MyVectorStimulation>(name.c_str())(inst));
this->getLogManager() << Kernel::LogLevel_Info << "Constructed variable in shared memory of type stimulation with name " << name.c_str() << "\n";
}
else { this->getLogManager() << Kernel::LogLevel_Warning << "Input type " << typeID << " is not supported\n"; }
m_decoders.back()->initialize(*this, i);
}
//m_inputCounter = 0;
return true;
}
bool CBoxAlgorithmSharedMemoryWriter::uninitialize()
{
m_sharedMemoryArray.destroy<MyVectorMetaInfo>("MetaInfo");
const Kernel::IBox& boxContext = this->getStaticBoxContext();
for (int i = int(boxContext.getInputCount() - 1); i >= 0; i--)
{
CIdentifier typeID;
boxContext.getInputType(i, typeID);
const std::string idxStr = std::to_string(i);
if (typeID == OVTK_TypeId_StreamedMatrix)
{
this->getLogManager() << Kernel::LogLevel_Debug << "Uninitialize shared memory variable associated with input " << i << "\n";
for (size_t j = 0; j < m_streamedMatrices.back()->size(); ++j)
{
m_sharedMemoryArray.deallocate(m_streamedMatrices.back()->at(j)->data.get());
m_sharedMemoryArray.deallocate(m_streamedMatrices.back()->at(j).get());
}
this->getLogManager() << Kernel::LogLevel_Debug << "Deallocated shared memory for variable with name Matrix " << idxStr << "\n";
m_streamedMatrices.back()->clear();
m_sharedMemoryArray.destroy<MyVectorStreamedMatrix>(("Matrix " + idxStr).c_str());
//TODO: pop_back()?
}
else if (typeID == OVTK_TypeId_Stimulations)
{
m_stimSets.back()->clear();
m_sharedMemoryArray.destroy<MyVectorStimulation>(("Stimuli" + idxStr).c_str());
}
}
this->getLogManager() << Kernel::LogLevel_Debug << "Destroyed all shared variables associated with input" << "\n";
boost::interprocess::shared_memory_object::remove(m_sharedMemoryName.toASCIIString());
boost::interprocess::named_mutex::remove(m_mutexName.toASCIIString());
delete m_mutex;
for (size_t i = 0; i < boxContext.getInputCount(); ++i)
{
m_decoders[i]->uninitialize();
delete m_decoders[i];
}
return true;
}
bool CBoxAlgorithmSharedMemoryWriter::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmSharedMemoryWriter::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
const Kernel::IBox& staticBoxContext = this->getStaticBoxContext();
size_t iStimulusCounter = 0;
size_t iMatrixCounter = 0;
for (size_t i = 0; i < staticBoxContext.getInputCount(); ++i)
{
CIdentifier typeID;
staticBoxContext.getInputType(i, typeID);
if (typeID == OVTK_TypeId_Stimulations)
{
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
{
//m_oAlgo0_StimulationDecoder.decode(j,i, false);
m_decoders[i]->decode(j, false);
//CStimulationSet stimSet;
IStimulationSet* stimSet = dynamic_cast<Toolkit::TStimulationDecoder<CBoxAlgorithmSharedMemoryWriter>*>(m_decoders[i])->
getOutputStimulationSet();
//Toolkit::StimulationSet::copy(l_oStimSet, *m_oAlgo0_StimulationDecoder.getOutputStimulationSet());
if (m_decoders[i]->isHeaderReceived()) { boxContext.markInputAsDeprecated(i, j); }
if (m_decoders[i]->isBufferReceived())
{
if (stimSet->getStimulationCount() > 0)
{
boost::interprocess::scoped_lock<boost::interprocess::named_mutex> lock(*m_mutex, boost::interprocess::try_to_lock);
if (lock)
{
for (size_t k = 0; k < stimSet->getStimulationCount(); ++k)
{
// @fixme m_stimSets defined as 32bit will only work correctly with stimuli that fit in 32bits (OV stimulations can be 64bit)
m_stimSets[iStimulusCounter]->push_back(size_t(stimSet->getStimulationIdentifier(k)));
this->getLogManager() << Kernel::LogLevel_Info << "Added stimulus with id " << m_stimSets[iStimulusCounter]->back() <<
" to shared memory variable\n";
}
boxContext.markInputAsDeprecated(i, j);
}
else { this->getLogManager() << Kernel::LogLevel_Warning << "Shared memory writer could not lock mutex\n"; }
}
else { boxContext.markInputAsDeprecated(i, j); }
}
if (m_decoders[i]->isEndReceived()) { boxContext.markInputAsDeprecated(i, j); }
}
iStimulusCounter++;
}
else if (typeID == OVTK_TypeId_StreamedMatrix)
{
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
{
m_decoders[i]->decode(j, false);
CMatrix* matrix = dynamic_cast<Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmSharedMemoryWriter>*>(m_decoders[i])->getOutputMatrix();
if (m_decoders[i]->isHeaderReceived()) { boxContext.markInputAsDeprecated(i, j); }
if (m_decoders[i]->isBufferReceived())
{
boost::interprocess::scoped_lock<boost::interprocess::named_mutex> lock(*m_mutex, boost::interprocess::try_to_lock);
if (lock)
{
boost::interprocess::offset_ptr<SMatrix> shmMatrix = static_cast<SMatrix*>(m_sharedMemoryArray.allocate(sizeof(SMatrix)));
//if we receive a vector (second dimension to 0) we force to one otherwise no memory will be allocated
const size_t row = (matrix->getDimensionSize(1) == 0) ? 1 : matrix->getDimensionSize(1);
shmMatrix->rowDim = row;
shmMatrix->colDim = matrix->getDimensionSize(0);
this->getLogManager() << Kernel::LogLevel_Trace << "dimensions " << matrix->getDimensionCount() << "row " << matrix->getDimensionSize(1)
<< " by " << " columns " << matrix->getDimensionSize(0) << "\n";
shmMatrix->data = static_cast<double*>(m_sharedMemoryArray.allocate(matrix->getDimensionSize(0) * row * sizeof(double)));
for (size_t k = 0; k < matrix->getBufferElementCount(); ++k) { *(shmMatrix->data + k) = *(matrix->getBuffer() + k); }
m_streamedMatrices[iMatrixCounter]->push_back(shmMatrix);
boxContext.markInputAsDeprecated(i, j);
}
}
if (m_decoders[i]->isEndReceived()) { boxContext.markInputAsDeprecated(i, j); }
}
iMatrixCounter++;
}
}
return true;
}
} // namespace FileReadingAndWriting
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,159 @@
#pragma once
//You may have to change this path to match your folder organisation
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
//#include <pair>
#include <vector>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>
#include <boost/interprocess/containers/string.hpp>
// The unique identifiers for the box and its descriptor.
// Identifier are randomly chosen by the skeleton-generator.
namespace OpenViBE {
namespace Plugins {
namespace FileReadingAndWriting {
/**
* \class CBoxAlgorithmSharedMemoryWriter
* \author Dieter Devlaminck (INRIA)
* \date Thu Jan 17 13:34:58 2013
* \brief The class CBoxAlgorithmSharedMemoryWriter describes the box SharedMemoryWriter.
*
*/
struct SMatrix
{
size_t rowDim;
size_t colDim;
boost::interprocess::offset_ptr<double> data;
};
typedef boost::interprocess::allocator<char, boost::interprocess::managed_shared_memory::segment_manager> CharAllocator;
typedef boost::interprocess::basic_string<char, std::char_traits<char>, CharAllocator> ShmString;
typedef boost::interprocess::allocator<ShmString, boost::interprocess::managed_shared_memory::segment_manager> StringAllocator;
//typedef vector<ShmString, StringAllocator> MyShmStringVector;
typedef boost::interprocess::allocator<std::pair<const ShmString, CIdentifier>, boost::interprocess::managed_shared_memory::segment_manager>
ShmemAllocatorMetaInfo;
typedef boost::interprocess::map<ShmString, CIdentifier, std::less<ShmString>, ShmemAllocatorMetaInfo> MyVectorMetaInfo;
typedef boost::interprocess::allocator<size_t, boost::interprocess::managed_shared_memory::segment_manager> ShmemAllocatorStimulation;
typedef boost::interprocess::allocator<boost::interprocess::offset_ptr<SMatrix>, boost::interprocess::managed_shared_memory::segment_manager>
ShmemAllocatorMatrix;
typedef boost::interprocess::vector<size_t, ShmemAllocatorStimulation> MyVectorStimulation;
typedef boost::interprocess::vector<boost::interprocess::offset_ptr<SMatrix>, ShmemAllocatorMatrix> MyVectorStreamedMatrix;
class CBoxAlgorithmSharedMemoryWriter final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
// As we do with any class in openvibe, we use the macro below
// to associate this box to an unique identifier.
// The inheritance information is also made available,
// as we provide the superclass Toolkit::TBoxAlgorithm < IBoxAlgorithm >
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_SharedMemoryWriter)
protected:
std::vector<Toolkit::TDecoder<CBoxAlgorithmSharedMemoryWriter>*> m_decoders;
CString m_sharedMemoryName;
boost::interprocess::managed_shared_memory m_sharedMemoryArray;
private:
CIdentifier m_typeID = CIdentifier::undefined();
CString m_mutexName;
boost::interprocess::named_mutex* m_mutex = nullptr;
std::vector<MyVectorStimulation*> m_stimSets;
std::vector<MyVectorStreamedMatrix*> m_streamedMatrices;
};
// The box listener can be used to call specific callbacks whenever the box structure changes : input added, name changed, etc.
// Please uncomment below the callbacks you want to use.
class CBoxAlgorithmSharedMemoryWriterListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputAdded(Kernel::IBox& /*box*/, const size_t /*index*/) override { return true; }
bool onInputRemoved(Kernel::IBox& /*box*/, const size_t /*index*/) override { return true; }
bool onInputTypeChanged(Kernel::IBox& /*box*/, const size_t /*index*/) override { return true; }
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
/**
* \class CBoxAlgorithmSharedMemoryWriterDesc
* \author Dieter Devlaminck (INRIA)
* \date Thu Jan 17 13:34:58 2013
* \brief Descriptor of the box SharedMemoryWriter.
*
*/
class CBoxAlgorithmSharedMemoryWriterDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("SharedMemoryWriter"); }
CString getAuthorName() const override { return CString("Dieter Devlaminck"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Stream input to shared memory"); }
CString getDetailedDescription() const override
{
return CString(
"The box writes input to shared memory so that it can be read by another process. Stimuli and streamed matrices are supported, and transformed into a format that can be written into shared memory. Based on the input types, a metainfo variable will be created in shared memory that will specify which variables have which type. This way the client can know what it will be reading.");
}
CString getCategory() const override { return CString("File reading and writing"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString(""); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_SharedMemoryWriter; }
IPluginObject* create() override { return new CBoxAlgorithmSharedMemoryWriter; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmSharedMemoryWriterListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("prediction1",OV_TypeId_StreamedMatrix);
//prototype.addInput("prediction2",OV_TypeId_Stimulations);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
//prototype.addFlag(Kernel::BoxFlag_CanRemoveInput);
prototype.addSetting("SharedMemoryName",OV_TypeId_String, "SharedMemory_P300Stimulator");
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_Stimulations);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SharedMemoryWriterDesc)
};
} // namespace FileReadingAndWriting
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,335 @@
#ifdef TARGET_HAS_Boost
#include "ovpCBoxAlgorithmTCPWriter.h"
#include <ctime>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <boost/detail/endian.hpp>
namespace OpenViBE {
namespace Plugins {
namespace NetworkIO {
using boost::asio::ip::tcp;
void CBoxAlgorithmTCPWriter::startAccept()
{
tcp::socket* socket = new tcp::socket(m_acceptor->get_io_service());
// Since startAccept will only be called inside ioService.poll(), there is no need to access control m_sockets
m_sockets.push_back(socket);
this->getLogManager() << Kernel::LogLevel_Debug << "We are now using " << m_sockets.size() << " socket(s)\n";
m_acceptor->async_accept(*socket, boost::bind(&CBoxAlgorithmTCPWriter::handleAccept, this, boost::asio::placeholders::error, socket));
}
void CBoxAlgorithmTCPWriter::handleAccept(const boost::system::error_code& ec, tcp::socket* pSocket)
{
if (!m_acceptor->is_open())
{
this->getLogManager() << Kernel::LogLevel_Debug << "handleAccept() was called with acceptor already closed\n";
return;
}
if (!ec)
{
this->getLogManager() << Kernel::LogLevel_Debug << "Handling a new incoming connection\n";
// Send the known configuration to the client
if (m_activeDecoder != &m_stimulationDecoder || m_outputStyle == TCPWRITER_RAW)
{
try
{
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_rawVersion), sizeof(uint32_t)));
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_endianness), sizeof(uint32_t)));
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_frequency), sizeof(uint32_t)));
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_nChannels), sizeof(uint32_t)));
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_nSamplesPerChunk), sizeof(uint32_t)));
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_reserved0), sizeof(uint32_t)));
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_reserved1), sizeof(uint32_t)));
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_reserved2), sizeof(uint32_t)));
}
catch (boost::system::system_error& error)
{
this->getLogManager() << Kernel::LogLevel_Warning << "Issue '" << error.code().message() << "' with writing header to client\n";
}
}
}
else
{
// @fixme should the socket be closed in this case?
this->getLogManager() << Kernel::LogLevel_Warning << "Issue '" << ec.message() << "' with accepting a connection.\n";
}
// Already schedule the accepting of the next connection
startAccept();
}
bool CBoxAlgorithmTCPWriter::initialize()
{
const Kernel::IBox& boxContext = this->getStaticBoxContext();
boxContext.getInputType(0, m_inputType);
if (m_inputType == OV_TypeId_StreamedMatrix) { m_activeDecoder = &m_matrixDecoder; }
else if (m_inputType == OV_TypeId_Signal) { m_activeDecoder = &m_signalDecoder; }
else { m_activeDecoder = &m_stimulationDecoder; }
m_activeDecoder->initialize(*this, 0);
const uint64_t port = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_outputStyle = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_rawVersion = htonl(1); // TCP Writer output format version
#if defined(BOOST_LITTLE_ENDIAN)
m_endianness = htonl(1);
#elif defined(BOOST_BIG_ENDIAN)
m_endianness = htonl(2);
#elif defined(BOOST_PDP_ENDIAN)
m_endianness = htonl(3);
#else
m_endianness = htonl(0);
this->getLogManager() << Kernel::LogLevel_Warning << "Platform endianness was not recognized\n";
#endif
m_frequency = 0;
m_nChannels = 0;
m_nSamplesPerChunk = 0;
m_reserved0 = 0;
m_reserved1 = 0;
m_reserved2 = 0;
this->getLogManager() << Kernel::LogLevel_Trace << "Setting up an acceptor at port " << port << "\n";
try
{
#ifdef TARGET_OS_Windows
// On Windows, unless we deny reuse_addr, it seems several different servers can bind to socket. This is not what we want.
m_acceptor = new tcp::acceptor(m_ioService, tcp::endpoint(tcp::v4(), uint32_t(port)), false);
#else
// On Linux, unless we allow reuse_addr, disconnection may set the socket to TIME_WAIT state and prevent opening it again until that state expires
m_acceptor = new tcp::acceptor(m_ioService, tcp::endpoint(tcp::v4(), uint32_t(port)), true);
#endif
}
catch (boost::system::system_error& error)
{
this->getLogManager() << Kernel::LogLevel_Warning << "Got error '" << error.code().message() << "' allocating acceptor to port " << port << "\n";
m_activeDecoder->uninitialize();
m_activeDecoder = nullptr;
m_acceptor = nullptr; // if new throws, deleting the returned m_acceptor causes problems on Linux. So we NULL it.
return false;
}
const boost::asio::socket_base::linger option(true, 0);
m_acceptor->set_option(option);
startAccept();
m_ioService.poll();
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmTCPWriter::uninitialize()
{
if (m_activeDecoder)
{
m_activeDecoder->uninitialize();
m_activeDecoder = nullptr;
}
for (tcp::socket* sock : m_sockets)
{
if (sock->is_open())
{
try
{
sock->shutdown(boost::asio::socket_base::shutdown_both);
sock->close();
}
catch (boost::system::system_error& error)
{
// Just report...
this->getLogManager() << Kernel::LogLevel_Warning << "Error in uninitialize() socket shutdown/close: '" << error.code().message() << "'\n";
}
}
}
m_ioService.poll();
m_ioService.stop();
for (tcp::socket* sock : m_sockets) { delete sock; }
m_sockets.clear();
delete m_acceptor;
m_acceptor = nullptr;
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmTCPWriter::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmTCPWriter::sendToClients(const void* buffer, const size_t size)
{
if (size == 0 || buffer == nullptr)
{
// Nothing to send, shouldn't happen
this->getLogManager() << Kernel::LogLevel_Warning << "Asked to send an empty buffer to clients (shouldn't happen)\n";
return false;
}
auto it = m_sockets.begin();
while (it != m_sockets.end())
{
tcp::socket* tmpSock = (*it);
bool hadError = false;
if (tmpSock->is_open())
{
try { write(*tmpSock, boost::asio::buffer(buffer, size)); }
catch (boost::system::system_error& error)
{
this->getLogManager() << Kernel::LogLevel_Warning << "Got error '" << error.code().message() << "' while trying to write to socket\n";
hadError = true;
}
}
if (hadError)
{
// Close the socket
this->getLogManager() << Kernel::LogLevel_Debug << "Closing the socket\n";
try
{
tmpSock->shutdown(boost::asio::socket_base::shutdown_both);
tmpSock->close();
}
catch (boost::system::system_error& error)
{
// Just report...
this->getLogManager() << Kernel::LogLevel_Warning << "Error while socket shutdown/close: '" << error.code().message() << "'\n";
}
m_ioService.poll();
delete tmpSock;
it = m_sockets.erase(it);
}
else { ++it; }
}
return true;
}
bool CBoxAlgorithmTCPWriter::process()
{
// the dynamic box context describes the current state of the box inputs and outputs (i.e. the chunks)
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
// Process the asio loop once (e.g. see if there's new connections)
m_ioService.poll();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_activeDecoder->decode(i);
if (m_activeDecoder->isHeaderReceived())
{
// Matrix part
if (m_activeDecoder == &m_matrixDecoder || m_activeDecoder == &m_signalDecoder)
{
// Casting to base class, ok
Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmTCPWriter>* decoder = (Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmTCPWriter>*)(m_activeDecoder);
const size_t nDim = decoder->getOutputMatrix()->getDimensionCount();
switch (nDim)
{
case 0:
this->getLogManager() << Kernel::LogLevel_Error << "Nothing to send, zero size matrix stream received\n";
return false;
case 1:
// Ok, this is a vector, openvibe style. Interpret it as 1 channel row vector.
m_nChannels = 1;
m_nSamplesPerChunk = decoder->getOutputMatrix()->getDimensionSize(0);
break;
case 2:
m_nChannels = decoder->getOutputMatrix()->getDimensionSize(0);
m_nSamplesPerChunk = decoder->getOutputMatrix()->getDimensionSize(1);
break;
default:
this->getLogManager() << Kernel::LogLevel_Error << "Only 1 and 2 dimensional matrices are supported\n";
return false;
}
// Conformance checking for all matrix based streams
if (m_nChannels == 0 || m_nSamplesPerChunk == 0)
{
this->getLogManager() << Kernel::LogLevel_Error << "For matrix-like inputs, both input dimensions must be larger than 0\n";
return false;
}
}
// Signal specific part
if (m_activeDecoder == &m_signalDecoder) { m_frequency = size_t(m_signalDecoder.getOutputSamplingRate()); }
//if (m_activeDecoder == &m_stimDecoder) { } // Stimulus, do nothing
}
if (m_activeDecoder->isBufferReceived())
{
if (m_activeDecoder == &m_matrixDecoder)
{
const CMatrix* matrix = m_matrixDecoder.getOutputMatrix();
sendToClients((void*)matrix->getBuffer(), matrix->getBufferElementCount() * sizeof(double));
}
else if (m_activeDecoder == &m_signalDecoder)
{
const CMatrix* matrix = m_signalDecoder.getOutputMatrix();
sendToClients((void*)matrix->getBuffer(), matrix->getBufferElementCount() * sizeof(double));
}
else // stimulus
{
const IStimulationSet* stimSet = m_stimulationDecoder.getOutputStimulationSet();
for (size_t j = 0; j < stimSet->getStimulationCount(); ++j)
{
const uint64_t id = stimSet->getStimulationIdentifier(j);
// uint64_t date = stimSet->getStimulationDate(j);
this->getLogManager() << Kernel::LogLevel_Trace << "Sending out " << id << "\n";
switch (m_outputStyle)
{
case TCPWRITER_RAW:
sendToClients((void*)&id, sizeof(id));
break;
case TCPWRITER_HEX:
{
CString tmp = CIdentifier(id).toString() + CString("\r\n");
const char* ptr = tmp.toASCIIString();
sendToClients((void*)ptr, strlen(ptr));
}
break;
case TCPWRITER_STRING:
{
CString tmp = this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, id);
if (tmp == CString("")) { tmp = CString("Unregistered_stimulus ") + CIdentifier(id).toString(); }
tmp = tmp + CString("\r\n");
const char* ptr = tmp.toASCIIString();
sendToClients((void*)ptr, strlen(ptr));
}
break;
default:
this->getLogManager() << Kernel::LogLevel_Error << "Unknown stimulus output style\n";
return false;
}
}
}
}
if (m_activeDecoder->isEndReceived()) { }
}
return true;
}
#endif // TARGET_HAS_Boost
} // namespace NetworkIO
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,162 @@
#pragma once
#ifdef TARGET_HAS_Boost
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <ctime>
#include <boost/asio.hpp>
enum { TCPWRITER_RAW, TCPWRITER_HEX, TCPWRITER_STRING }; // stimulation output types
namespace OpenViBE {
namespace Plugins {
namespace NetworkIO {
/**
* \class CBoxAlgorithmTCPWriter
* \author Jussi T. Lindgren (Inria)
* \date Wed Sep 11 12:55:22 2013
* \brief The class CBoxAlgorithmTCPWriter describes the box TCP Writer.
*
*/
class CBoxAlgorithmTCPWriter final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
//Here is the different process callbacks possible
// - On clock ticks :
//virtual bool processClock(Kernel::CMessageClock& msg);
// - On new input received (the most common behaviour for signal processing) :
bool processInput(const size_t index) override;
// If you want to use processClock, you must provide the clock frequency.
//virtual uint64_t getClockFrequency();
bool process() override;
// As we do with any class in openvibe, we use the macro below
// to associate this box to an unique identifier.
// The inheritance information is also made available,
// as we provide the superclass Toolkit::TBoxAlgorithm < IBoxAlgorithm >
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_TCPWriter)
protected:
bool sendToClients(const void* buffer, size_t size);
// Stream decoder
Toolkit::TStimulationDecoder<CBoxAlgorithmTCPWriter> m_stimulationDecoder;
Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmTCPWriter> m_matrixDecoder;
Toolkit::TSignalDecoder<CBoxAlgorithmTCPWriter> m_signalDecoder;
Toolkit::TDecoder<CBoxAlgorithmTCPWriter>* m_activeDecoder = nullptr;
boost::asio::io_service m_ioService;
boost::asio::ip::tcp::acceptor* m_acceptor = nullptr;
std::vector<boost::asio::ip::tcp::socket*> m_sockets;
uint64_t m_outputStyle = 0;
CIdentifier m_inputType = CIdentifier::undefined();
// Data written as global output header, 8*4 = 32 bytes. Padding allows dumb readers to step with double (==8 bytes).
size_t m_rawVersion = 0; // in network byte order, version of the raw stream
size_t m_endianness = 0; // in network byte order, 0==unknown, 1==little, 2==big, 3==pdp
size_t m_frequency = 0; // this and the rest are in host byte order
size_t m_nChannels = 0;
size_t m_nSamplesPerChunk = 0;
size_t m_reserved0 = 0;
size_t m_reserved1 = 0;
size_t m_reserved2 = 0;
void startAccept();
void handleAccept(const boost::system::error_code& ec, boost::asio::ip::tcp::socket* pSocket);
};
class CBoxAlgorithmTCPWriterListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
CBoxAlgorithmTCPWriterListener(): m_lastType(CIdentifier::undefined()) { }
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier newType = CIdentifier::undefined();
box.getInputType(index, newType);
// Set the right enumeration according to the type if we actualy change it
// TODO find a way to init m_lastType with the right value
if (m_lastType != newType)
{
if (newType != OV_TypeId_Stimulations) { box.setSettingType(1, OVP_TypeID_TCPWriter_RawOutputStyle); }
else { box.setSettingType(1, OVP_TypeID_TCPWriter_OutputStyle); }
box.setSettingValue(1, "Raw");
m_lastType = newType;
}
return true;
}
private:
CIdentifier m_lastType = CIdentifier::undefined();
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
/**
* \class CBoxAlgorithmTCPWriterDesc
* \author Jussi T. Lindgren (Inria)
* \date Wed Sep 11 12:55:22 2013
* \brief Descriptor of the box TCP Writer.
*
*/
class CBoxAlgorithmTCPWriterDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("TCP Writer"); }
CString getAuthorName() const override { return CString("Jussi T. Lindgren"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Send input stream out via a TCP socket"); }
CString getDetailedDescription() const override { return CString("\n"); }
CString getCategory() const override { return CString("Acquisition and network IO"); }
CString getVersion() const override { return CString("0.2"); }
CString getStockItemName() const override { return CString("gtk-connect"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_TCPWriter; }
IPluginObject* create() override { return new CBoxAlgorithmTCPWriter; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmTCPWriterListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input 1",OV_TypeId_StreamedMatrix);
prototype.addSetting("Port",OV_TypeId_Integer, "5678");
prototype.addSetting("Output format", OVP_TypeID_TCPWriter_RawOutputStyle, "Raw");
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Stimulations);
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_TCPWriterDesc)
};
} // namespace NetworkIO
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_Boost
@@ -0,0 +1,22 @@
#pragma once
// Boxes
//---------------------------------------------------------------------------------------------------
#define OVP_ClassId_BoxAlgorithm_SharedMemoryWriter OpenViBE::CIdentifier(0xACC272DD, 0xC1BDC1B1)
#define OVP_ClassId_BoxAlgorithm_SharedMemoryWriterDesc OpenViBE::CIdentifier(0xACC727DD, 0xCACDC1B1)
#define OVP_ClassId_BoxAlgorithm_LSLExport OpenViBE::CIdentifier(0x6F3467FF, 0x52794DA6)
#define OVP_ClassId_BoxAlgorithm_LSLExportDesc OpenViBE::CIdentifier(0x40C03C3F, 0x034A19C2)
#define OVP_ClassId_BoxAlgorithm_TCPWriter OpenViBE::CIdentifier(0x02F24947, 0x17FA0477)
#define OVP_ClassId_BoxAlgorithm_TCPWriterDesc OpenViBE::CIdentifier(0x3C32489D, 0x46F565D3)
#define OVP_TypeID_TCPWriter_OutputStyle OpenViBE::CIdentifier(0x6D7E53DD, 0x6A0A4753)
#define OVP_TypeID_TCPWriter_RawOutputStyle OpenViBE::CIdentifier(0x77D3E238, 0xB954EC48)
// Global defines
//---------------------------------------------------------------------------------------------------
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#include "ovp_global_defines.h"
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#define OV_AttributeId_Box_FlagIsUnstable OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)
@@ -0,0 +1,34 @@
#include "ovp_defines.h"
#include "box-algorithms/ovpCBoxAlgorithmLSLExport.h"
#include "box-algorithms/ovpCBoxAlgorithmTCPWriter.h"
#include "box-algorithms/ovpCBoxAlgorithmSharedMemoryWriter.h"
namespace OpenViBE {
namespace Plugins {
OVP_Declare_Begin()
#ifdef TARGET_HAS_ThirdPartyLSL
OVP_Declare_New(NetworkIO::CBoxAlgorithmLSLExportDesc);
#endif
#ifdef TARGET_HAS_Boost
OVP_Declare_New(FileReadingAndWriting::CBoxAlgorithmSharedMemoryWriterDesc);
OVP_Declare_New(NetworkIO::CBoxAlgorithmTCPWriterDesc);
context.getTypeManager().registerEnumerationEntry(OV_TypeId_BoxAlgorithmFlag, OV_AttributeId_Box_FlagIsUnstable.toString(),
OV_AttributeId_Box_FlagIsUnstable.id());
context.getTypeManager().registerEnumerationType(OVP_TypeID_TCPWriter_OutputStyle, "Stimulus output");
context.getTypeManager().registerEnumerationEntry(OVP_TypeID_TCPWriter_OutputStyle, "Raw", TCPWRITER_RAW);
context.getTypeManager().registerEnumerationEntry(OVP_TypeID_TCPWriter_OutputStyle, "Hex", TCPWRITER_HEX);
context.getTypeManager().registerEnumerationEntry(OVP_TypeID_TCPWriter_OutputStyle, "String", TCPWRITER_STRING);
context.getTypeManager().registerEnumerationType(OVP_TypeID_TCPWriter_RawOutputStyle, "Raw output");
context.getTypeManager().registerEnumerationEntry(OVP_TypeID_TCPWriter_RawOutputStyle, "Raw", TCPWRITER_RAW);
#endif
OVP_Declare_End()
} // namespace Plugins
} // namespace OpenViBE