Please Discuss:
I missed a simple fifo to pass pointers between threads, when you now there is only one reader and one writer.
I didn’t test this template very much, i used AbstractFifo as a base. // EDIT: i the mean time I’ve done a lot of more testing
Jules, would be cool if you could add something like this to the source-tree.
//EDIT
A simple example (Pseudocode), the templates can be used to create a filter-kernel on a different thread, and push it into the audio-thread
The process-Block always use the latest kernel, and if there is a newer kernel, it will pushed back to the producer thread.
There will be no locks, memory allocation/free, on the AudioThread
//Pseudo Code Demo
ConsumerProducerManager q;
//Thread 1 Producer
{
while (true)
{
q.producerSendObjectToConsumerOrDeleteIt(new Object) // push new Object
q.producerDeleteBackItems() //Delete Objects that came back from the Consumer thread
};
};
// Thread2 Consumer (Audio Thread)
{
q.consumerSwitchCurrentToLatestObject() // we only want the latest object, which was generated
while (true)
{
if (q.consumerIsCurrentObjectAvailable())
{
q.consumerGetCurrentObject()->doSomeThing()
}
};
};
// EDIT , replaced with the newest Version
template <class ObjectClass>
class CircularSingleWriterSingleReaderLockFreePointerFIFO
{
public:
CircularSingleWriterSingleReaderLockFreePointerFIFO (int capacity) noexcept
{
jassert (capacity > 0);
arrayOfPointers.resize(capacity);
}
~CircularSingleWriterSingleReaderLockFreePointerFIFO() {};
int getTotalSize() const noexcept { return arrayOfPointers.size(); }
int getFreeSpace() const noexcept { return arrayOfPointers.size() - getNumReady(); }
int getNumReady() const noexcept
{
const int vs = validStart.get();
const int ve = validEnd.get();
return ve >= vs ? (ve - vs) : (arrayOfPointers.size() - (vs - ve));
}
void reset() noexcept
{
validEnd = 0;
validStart = 0;
}
void setTotalSize (int newSize) noexcept
{
jassert (newSize > 0);
reset();
arrayOfPointers.resize(newSize);
}
//==============================================================================
// adds a Pointer to the FIFO, return true when successful
bool write ( ObjectClass* object) noexcept
{
const int vs = validStart.get();
const int ve = validEnd.value;
const int freeSpace = ve >= vs ? (arrayOfPointers.size() - (ve - vs)) : (vs - ve);
if (freeSpace <=1 )
{
return false;
}
arrayOfPointers.set(ve,object);
int newEnd = ve + 1;
if (newEnd >= arrayOfPointers.size()) newEnd=0;
validEnd.set(newEnd); // ATOMIC at the end
return true;
}
// reads a pointer from the FIFO, returns the pointer or 0 if not successful
ObjectClass* read () noexcept
{
const int vs = validStart.value;
const int ve = validEnd.get();
const int numReady = ve >= vs ? (ve - vs) : ( arrayOfPointers.size() - (vs - ve));
if (numReady <= 0)
{
return 0;
}
int newStart = vs + 1;
if (newStart >= arrayOfPointers.size()) newStart = 0;
validStart.set(newStart); // ATOMIC at the end
return arrayOfPointers[vs];
}
private:
//==============================================================================
Atomic <int> validStart, validEnd;
Array <ObjectClass*> arrayOfPointers;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CircularSingleWriterSingleReaderLockFreePointerFIFO);
};
template <class ObjectClass>
class ConsumerProducerManager
{
public:
ConsumerProducerManager(int capacity)
:producerToConsumer(capacity), consumerToProducer(capacity), currentObject(nullptr)
{
};
~ConsumerProducerManager()
{
//This is a fallback if not all objects were consumed/deleted
producerDeleteBackItems();
deleteUnusedItems();
if (currentObject!=nullptr)
{
delete currentObject;
}
}
// ONLY USED BY PRODUCER !!!
void producerDeleteBackItems()
{
ObjectClass* next=consumerToProducer.read();
while (next!=0)
{
delete next;
next=consumerToProducer.read();
}
};
void deleteUnusedItems()
{
ObjectClass* next=producerToConsumer.read();
while (next!=0)
{
delete next;
next=producerToConsumer.read();
}
};
// ONLY USED BY PRODUCER !!!
void producerSendObjectToConsumerOrDeleteIt(ObjectClass* newObject)
{
if (!producerToConsumer.write(newObject))
{
// if queue is full, fallback!
jassertfalse;
delete newObject;
}
};
// ONLY USED BY CONSUMER
bool consumerIsCurrentObjectAvailable()
{
return currentObject!=nullptr;
};
// ONLY USED BY CONSUMER
ObjectClass* consumerGetCurrentObject()
{
return currentObject;
}
// ONLY USED BY CONSUMER
void consumerSwitchCurrentToLatestObject()
{
ObjectClass* next=producerToConsumer.read();
while (next!=0)
{
if (currentObject!=nullptr)
{
if (!consumerToProducer.write(currentObject))
{
//if the queue is full, delete it here, fallback
delete currentObject;
jassertfalse;
}
}
currentObject=next;
next=producerToConsumer.read();
}
};
private:
CircularSingleWriterSingleReaderLockFreePointerFIFO<ObjectClass> producerToConsumer;
CircularSingleWriterSingleReaderLockFreePointerFIFO<ObjectClass> consumerToProducer;
ObjectClass* currentObject;
};