[quote=“friscokid”]Any idea how to workarround the missing functionality in VS2008?
I guess the vf_Bind.h is required for the vf_concurrent module to work properly?[/quote]
VFLib provides the option of using Boost for the bindings if std::bind is not available for your environment. If it bothers you to require the entire boost distribution, I believe that there is a way in boost of extracting only the headers that you need.
You can try making a special .cpp file that includes both juce_basics.cpp and vf_core.cpp and see if that fixes it. If not, then reverse the order: include vf_core.cpp first and then juce_basics.cpp.[/quote]
I got no success with that vinnie… but I’m not sure I do it right.
I just added a new cpp file to my project, with just the following lines :
You can try making a special .cpp file that includes both juce_basics.cpp and vf_core.cpp and see if that fixes it. If not, then reverse the order: include vf_core.cpp first and then juce_basics.cpp.[/quote]
…
is that what you were suggesting or am I doing something wrong ?[/quote]
No that’s not what I’m suggesting. I was saying to do this:
Unfortunately no.
I wasn’t able to find a combination that avoided the false leak detections.
But as I use several libraries that have a module style, I’m not completely free to include the juce cpp files in custom files without messing up the complete modules structures.
If someone has another idea how to avoid the false leak detections, then please let me know.
What I do is just turn off the JUCE_DETECT_LEAKS and instead rely on the Visual Studio debug heap reporting on exit. When I see Visual Studio reporting something I turn the Juce leak detector back on and hunt it down, then shut it off again.
It would be rather superb to see how you used VFLib system to patch it all together…
I think that this should really be part of Juce even though there are different ways of attacking the problem.
Why? Jules already has enough distractions to where he isn’t even finishing the stuff he starts (for example, third party modules in the IntroJucer). And you want to add this to his plate?
Besides, JUCE provides the building blocks. There are 101 ways to implement thread queues, mine is just one implementation and it does have its limitations. I don’t think it belongs in a general purpose library.
Wait… I don’t think vflib or any part of vflib should be part of juce: I think threadsafe parameters could be part of the set of building blocks provided!
Juce isn’t really “general purpose” it’s “audio purpose” and imho this defines what should be in the set of “basic building blocks”.
I disagree. Mainly because there are many ways to implement thread-safe parameters. Depending on your type of program, some methods are preferable to others.
I'm using call queues to implement thread safe parameters. The technique presented in SimpleDJ looks wonderful and i think i'm beginning to understand how that actually works and learning to use the classes. There's an alarming comment "This is asking for trouble." in the SimpleDJ Param implementation:
void Param::addListener (Listener* listener, vf::CallQueue& thread)
{
// This is asking for trouble.
//jassert (&thread != &m_thread);
// Atomically add the listener and synchronize their state.
StateType::ReadAccess state (m_state);
m_listeners.add (listener, thread);
m_listeners.queue1 (listener, &Listener::onParamChange, this, state->value);
}
I've tried to add the owner thread (or the ManualCallQueue) as a listener thread despite the warning. I've seen a crash somewhere deep in the allocations taking place during the listeners notification. It seems obvious that this is because of having the owner thread in the listener list. I don't recall seeing any mention about this restriction in the documentation. Can someone verify that this will crash? And if so, why is that? What is the best way to work around this limitation. Should i simply implement a separate listener list to notify objects processing in the owner thread?
Edit:
I don't think the crash or the warning has anything to do with having the owner thread listening. I guess the warning is simply there to remind of the possibility of deadlock. The crash is related to this:
I've actually spent a couple of days taking your ManualCallQueue interface and rewriting the implementation.
I removed the memory management stuff and got it down to a single short class. And, with a good deal of help from Ross Bencina (I now now a shit load more about C++!) It's now a single class only dependent on the AbstractFifo class (and std::bind() ).
It might be missing some of the nicer stuff you had - but I wanted something small enough I could understand it :)
If it's of interest I can post it here or send it over to you..
#include <functional>
#include <iostream>
/**
A function call queue which enables functions to be called
asychronously on another thread.
It has the following special features:
- No locking and avoids using the system allocator (except
during the constructor).
Watch out for:
- Objects you pass as function arguments (which are passed by value)
doing memory allocation, which may result in a lock.
*/
template <int RingBufferSize>
class RjManualCallQueue {
public:
RjManualCallQueue()
: fifo(RingBufferSize)
{
// TODO: data should be aligned to a cache line boundary.
// Allocate double size buffer to easily support variable length messages,
// by hanging them over the end of the buffer.
fifodata = new char[ RingBufferSize * 2 ];
}
~RjManualCallQueue()
{
delete [] fifodata;
}
bool isEmpty() {
return fifo.getTotalSize() == fifo.getFreeSpace();
}
template <class Functor>
bool callf(Functor const & f) {
size_t allocSize = roundUpToCacheLineBoundary(sizeof(WorkItem<Functor>));
int idx1, idx2, sz1, sz2;
fifo.prepareToWrite(allocSize, idx1, sz1, idx2, sz2);
if (sz1+sz2 < allocSize) return false;
// double size buffer means we can ignore idx2 and sz2
new (fifodata+idx1) WorkItem<Functor> (f);
fifo.finishedWrite(allocSize);
return true;
}
bool synchronize() {
bool didSomething = false;
while (fifo.getNumReady() > 0) {
int idx1, idx2, sz1, sz2;
fifo.prepareToRead(1, idx1, sz1, idx2, sz2);
didSomething = true;
Work *w = reinterpret_cast<Work*>(fifodata+idx1);
// notice only one function pointer invocation here, not two virtual function calls
size_t sizeofWorkItem = (*w->execAndDestructFn) (w);
size_t allocSize = roundUpToCacheLineBoundary(sizeofWorkItem);
fifo.finishedRead(allocSize);
}
return didSomething;
}
private:
// Avoid a vtable and two separate virtual function
// dispatches (operator() and destructor) by putting
// everything into a single function and implementing
// our own virtual function call.
// type for pointer to a function that executes work,
// calls the destructor for the and returns size of
// concrete instance.
typedef size_t (*WorkExecAndDestructFunctionPtr)( void *workItemStorage );
class Work {
// NOTE: no vtable.
public:
Work( WorkExecAndDestructFunctionPtr f ) :
execAndDestructFn(f)
{ }
WorkExecAndDestructFunctionPtr execAndDestructFn;
};
/** WorkItem template - extends Work for each type of Functor */
template <class Functor>
struct WorkItem : public Work {
explicit WorkItem(Functor const & fun) :
Work(&WorkItem::myExecAndDestruct),
myCall(fun)
{}
private:
static size_t myExecAndDestruct(void *workItemStorage) {
// cast to *concrete* work item pointer
WorkItem *that = reinterpret_cast<WorkItem*>(workItemStorage);
that->myCall();
that->~WorkItem(); // invoke concrete dtor (destructs functor)
return sizeof(WorkItem);
}
Functor myCall;
};
/* For clarity (maybe) what's happening here is:
1 There are instances of the templated class
WorkItem for each type of Functor, and hence
multiple versions of myExecAndDestruct.
2 When the WorkItem is created the
Work::execAndDestructFn pointer is set to
point to the appropriate instance of
myExecAndDestruct - the pointer is passed
to the Work superclass constructor by the
WorkItem<Functor> class.
To call we:
- Get the data from the fifo.
- Cast it to be a Work.
- Call the appropriate instance of myExecAndDestruct
via the execAndDestructFn pointer.
*/
AbstractFifo fifo;
char *fifodata;
// Used to avoid false sharing and to give
// correct alignment to embedded structs.
size_t roundUpToCacheLineBoundary( size_t x )
{
return x; // TODO
}
};
And here's a test showing it in operation:
#include "jcf/audiothread_fifo.h"
#include <iostream>
using namespace std;
class TestMainThread;
/*
A test thread that represents the processor thread.
Maintains a total of various types of data. On request
reports the total back to the main thread.
*/
class TestProcessorThread : public Thread {
public:
TestProcessorThread(TestMainThread & t) :
Thread("Test Processor Thread"),
mainthread(t),
total(0)
{
startThread();
}
~TestProcessorThread() {
stopThread(50);
}
void run() {
while (1) {
if (threadShouldExit()) return;
q.synchronize();
}
}
/* Functions called by the main thread */
bool callAddInteger(int i) {
return q.callf(std::bind(&TestProcessorThread::addInteger, this, i));
}
bool callSendTotal() {
return q.callf(std::bind(&TestProcessorThread::sendTotal, this));
}
bool isQueueEmpty() {
return q.isEmpty();
}
private:
void addInteger(int i) {
total += i;
}
void sendTotal();
RjManualCallQueue<4096> q;
TestMainThread &mainthread;
long int total;
};
class TestMainThread {
public:
TestMainThread() :
tpt (*this) {}
void go() {
for (int i = 0; i<1001; i++) {
while (!tpt.callAddInteger(i));
if (i%10 == 0) {
cout << "Call: tpt.callSendTotal() at " << i << endl;
while (!tpt.callSendTotal());
}
q.synchronize();
}
while (!tpt.isQueueEmpty()) {
Time::waitForMillisecondCounter(10);
q.synchronize();
}
Time::waitForMillisecondCounter(10);
q.synchronize();
}
void callSetTotal(long int t) {
q.callf(std::bind(&TestMainThread::setTotal, this, t));
}
private:
void setTotal(long int t) {
cout << "setTotal(" << t << ")" << endl;
}
RjManualCallQueue<4096> q;
TestProcessorThread tpt;
};
void TestProcessorThread::sendTotal() {
mainthread.callSetTotal(total);
}
And I don't remember why I'm putting the size as a template argument, it could easily be passed to the constructor instead.
I'm looking to use Value objects and Value::Listener in order to have GUI elements update according to incoming parameter changes. Are there downsides to using this technique? Currently there will only be 2-3 values that will be updating. I'd like to seperate out processing and GUI stuff in my app.