AsyncUpdater which don't malloc

Why don’t you just do something like this (didn’t try to compile, so maybe there’s an error somewhere):

[code]#include “juce.h”

#define MESSAGEBUFFERSIZE 16384 // fixed size for queue’s buffer. 16384 should really be big enough for realtime purpose. must be power of 2!

class FastMessageQueue
{
public:
FastMessageQueue()
{
writePos=0;
readPos=0;
};

// returns false if the queue's buffer was full
bool addMessage(const Message &m)
{
	if (readPos==(writePos+1)&(MESSAGEBUFFERSIZE-1)) return false;	// buffer full!
	messageBuffer[writePos].intParameter1=m.intParameter1;
	messageBuffer[writePos].intParameter2=m.intParameter2;
	messageBuffer[writePos].intParameter3=m.intParameter3;
	messageBuffer[writePos].pointerParameter=m.pointerParameter;
	
	writePos=(writePos+1)&(MESSAGEBUFFERSIZE-1);
  return true;
}

// returns false if no message in queue
bool getNextMessage(Message &m)
{
	if (readPos==writePos) return false;
	m.intParameter1=messageBuffer[readPos].intParameter1;
	m.intParameter2=messageBuffer[readPos].intParameter2;
	m.intParameter3=messageBuffer[readPos].intParameter3;
	m.pointerParameter=messageBuffer[readPos].pointerParameter;
	
	readPos=(readPos+1)&(MESSAGEBUFFERSIZE-1);
	return true;
}

private:
Message messageBuffer[MESSAGEBUFFERSIZE];
unsigned int readPos, writePos;
};
[/code]

Ofcourse, as soon as the pointerParameter points to some object that must have been allocated before, there’s the mem alloc problem again.
But generally, you won’t need that in realtime messages. Should be Thread-safe too, because all readPos & writePos incrementations are atomic.

I’ve done like previously suggested.
My AudioParameter class is a AsyncUpdater, then in my plugin i have a AudioParameterThread, i choosed to be a singleton because i use it also in my host where each plugin have it’s own set of parameters (and so i would have ended with a Thread for each plugin), but you can also use it with a per plugin instance if you are building a plugin.

/**
    The parameter updating thread

    It is a shared global static class that starts a global thread and updates
    asyncronously the parameters and their attached listeners

*/
class AudioParameterThread : public Thread,
                             public DeletedAtShutdown
{
public:

    /** Constructor */
    AudioParameterThread ();

    /** Destructor */
    ~AudioParameterThread ();

    /**
        Set the current number of parameter update check per second
     */
    void sendParameterChange (AudioParameter* parameter);

    /**
        Set the current number of parameter update check per second
     */
    int getParametersChangeChecksPerSecond () const;

    /**
        Set the current number of parameter update check per second
     */
    void setParametersChangeChecksPerSecond (const int howManyCheckPerSecond);

    /**
        The thread callback. Here we check for changes to parameters and trigger
        async updates 

        @see Thread
    */
    void run();

    juce_DeclareSingleton (AudioParameterThread, true)

private:

    LockFreeFifo<AudioParameter*> parameterChanges;
    int changeChecksPerSecond;
};
juce_ImplementSingleton (AudioParameterThread);

AudioParameterThread::AudioParameterThread ()
    : Thread ("AudioParameterThread"),
      parameterChanges (8192),
      changeChecksPerSecond (50)
{
    startThread (6);
}

AudioParameterThread::~AudioParameterThread ()
{
    stopThread (5000);
}

void AudioParameterThread::sendParameterChange (AudioParameter* parameter)
{
    parameterChanges.put (parameter);
}

int AudioParameterThread::getParametersChangeChecksPerSecond () const
{
    return changeChecksPerSecond;
}

void AudioParameterThread::setParametersChangeChecksPerSecond (const int howManyCheckPerSecond)
{
    changeChecksPerSecond = jmax (1, jmin (100, howManyCheckPerSecond));
}

void AudioParameterThread::run()
{
    AudioParameter* parameter;

    while (! threadShouldExit ())
    {
        int32 currentWaitTime = Time::getMillisecondCounter ();

        while (! parameterChanges.isEmpty ())
        {
            parameter = (AudioParameter*) parameterChanges.get ();
            if (parameter)
                parameter->triggerAsyncUpdate ();
        }

        currentWaitTime = 1000 / changeChecksPerSecond
                        - (Time::getMillisecondCounter () - currentWaitTime);

        Thread::sleep (jmax (1, jmin (1000, currentWaitTime)));
    }
}

then in the real time thread i just do:

parameterUpdater->sendParameterChange (myParameter);

it works quite well with a reasonable amount of parameter changes (i have tested with midi automation in my plugins and i can automate more than 30 parameters without see any slider slowdown or jump when automating)

To speed up response, you can use wait() and notify() methods of Thread to wake it up when there is stuff in the queue.

that is a good idea thanx !

btw, what you think about that class approach ?
i think is better suited cause it doesn’t need any modifications in the AsyncUpdater class (which is good like it is for GUI objects) while still keeping things clean and simple.

My question wasn’t so much about how this could be done, but rather when? or how much Jule consider this a priority?

Of course anyone can come up with its own real-time AsyncUpdater variants. But for example the MidiKeyboard or simply the Juce Plugin DemoFilter are using the original AsyncUpdater at the root of their notification mechanism.

So anyone starting using them will have troubles when building for RTAS especially for those targetting PT 7.4 and above on leopard with multiple cores.

[quote=“mdsp”]
So anyone starting using them will have troubles when building for RTAS especially for those targetting PT 7.4 and above on leopard with multiple cores.[/quote]
+1

I consider it as a major problem in Juce library but I also understand that it is not the case of all Juce users.
I know that you (Jules) have a lot of things to do. For the moment, I think that your are spending time in porting to Cocoa.
But can you give us some information about when you will do something for this.

Thx

Kevin

It’s high on my to-do-list, and not too difficult…

ok,
nice to read this :D.

Hi Jules,

We would like to release an update of UVIWorkstation to avoid
this problem in Protools and currently we are a bit stuck.
W have placed an ad in the digi partners magazine and it would be much better to have a really working product :slight_smile:

Any chance you can take a look at this shortly ?

Thanks,

If you’re in a hurry and need something rock-solid, just go with the simple timer-based class we discussed earlier! Unless you’ve got thousands of these objects, that’ll work just fine.

Hi Jules,

Problem is, if I inherit juce::AsyncUpdaterInternal from juce:Timer
then the first time, it creates a juce::InternalTimerThread which inherits from
juce::AsyncUpdater.

Infinite loop.

Any idea ?

Thanks,

Well removing the fact that juce::InternalTimerThread use AsyncUpdate is enough and starting the thread in the ctor is enough.

What was the reason behind this move ?
(do not start the thread in InternalTimerThread ctor)

[code]#include “…/…/juce_core/basics/juce_StandardHeader.h”

#include
#include

BEGIN_JUCE_NAMESPACE

#include “juce_AsyncUpdater.h”
#include “…/…/juce_core/threads/juce_Thread.h”
#include “…/…/juce_core/threads/juce_WaitableEvent.h”
#include “…/…/juce_core/threads/juce_ScopedLock.h”
#include “juce_MessageManager.h”
#include “…/application/juce_DeletedAtShutdown.h”

template class LockFreeFIFO
{
public:
LockFreeFIFO(size_t size)
: readIndex(0), writeIndex(0), buffer(size)
{
}

void resize(size_t size)
{
buffer.resize(size);
}

size_t size() const { return buffer.size(); }

void pop()
{
if ((readIndex+1) >= buffer.size())
{
readIndex = 0;
}
else
{
readIndex++;
}
}

const Type &front() const
{
return buffer[readIndex];
}

bool push(const Type &object)
{
const size_t newHead = (writeIndex+1)%buffer.size();

if (newHead != readIndex) 
{
  buffer[writeIndex] = object;
  writeIndex = newHead;
  return true;
}
return false;

}

bool empty() const
{
if (readIndex == writeIndex)
{
return true;
}
return false;
}

private:
volatile size_t readIndex, writeIndex;
std::vector buffer;
};

class InternalAsyncUpdaterThread :
private Thread,
private MessageListener,
private DeletedAtShutdown
{
private:
friend class AsyncUpdater;
static InternalAsyncUpdaterThread* instance;

CriticalSection lock;
WaitableEvent waitableEvent;
LockFreeFIFO<AsyncUpdater*> fifo;
std::set<AsyncUpdater*> asyncUpdaters;

InternalAsyncUpdaterThread (const InternalAsyncUpdaterThread&);
const InternalAsyncUpdaterThread& operator= (const InternalAsyncUpdaterThread&);

public:
void addAsyncUpdater (AsyncUpdater* const a)
{
jassert(MessageManager::getInstance()->isThisTheMessageThread());
asyncUpdaters.insert(a);
if (fifo.size() < asyncUpdaters.size())
{
ScopedLock l(lock);
fifo.resize(asyncUpdaters.size());
}
}

void removeAsyncUpdater (AsyncUpdater* const a)
{
jassert(MessageManager::getInstance()->isThisTheMessageThread());
asyncUpdaters.erase(a);
}

InternalAsyncUpdaterThread()
: Thread (“Juce AsyncUpdater”), fifo(1024)
{
startThread ();
}

~InternalAsyncUpdaterThread()
{
signalThreadShouldExit();
waitableEvent.signal();
waitForThreadToExit(-1);

jassert (instance == this || instance == 0);
if (instance == this)
  instance = 0;

}

void post(AsyncUpdater* const a)
{
ScopedLock l(lock);
bool res = fifo.push(a); // there can’t be another instance of the AsyncUpdater in the fifo as asyncMessagePending would be true so post won’t be called
jassert(res);
waitableEvent.signal();
}

virtual void handleMessage(const Message &)
{
jassert(MessageManager::getInstance()->isThisTheMessageThread());
while(!fifo.empty())
{
AsyncUpdater* a = fifo.front();
fifo.pop();
if (asyncUpdaters.find(a) != asyncUpdaters.end())
{
a->handleUpdateNowIfNeeded();
}
}
}

void run()
{
while (!threadShouldExit())
{
waitableEvent.wait();
if (!threadShouldExit())
{
postMessage(new Message());
}
}
}

static bool hasInstance()
{
return (instance != 0);
}

static InternalAsyncUpdaterThread* getInstance()
{
if (instance == 0)
instance = new InternalAsyncUpdaterThread();

return instance;

}
};

InternalAsyncUpdaterThread* InternalAsyncUpdaterThread::instance = 0;

//==============================================================================
AsyncUpdater::AsyncUpdater() throw()
: asyncMessagePending (false)
{
InternalAsyncUpdaterThread::getInstance()->addAsyncUpdater(this);
}

AsyncUpdater::~AsyncUpdater()
{
if (InternalAsyncUpdaterThread::hasInstance()) // might happen if another DeletedAtShutdown inherit from AsyncUpdater(like juce::Desktop)
{
InternalAsyncUpdaterThread::getInstance()->removeAsyncUpdater(this);
}
}

void AsyncUpdater::triggerAsyncUpdate() throw()
{
if (! asyncMessagePending)
{
asyncMessagePending = true;
InternalAsyncUpdaterThread::getInstance()->post(this);
}
}

void AsyncUpdater::cancelPendingUpdate() throw()
{
asyncMessagePending = false;
}

void AsyncUpdater::handleUpdateNowIfNeeded()
{
if (asyncMessagePending)
{
asyncMessagePending = false;
handleAsyncUpdate();
}
}

END_JUCE_NAMESPACE
[/code]

I was not really happy with the timer solution, so I’ve come up with this.

I wasn’t able to do a real lock free fifo as I was lacking something like juce_atomic_set

Comments are welcomed.

Thanks,

Bump.

Does recent changes in Juce would allow to fix this issue or another class to use instead of this one ?

Thanks,

There are a couple of new functions in Atomics that might be helpful, but I’ve not looked it myself, sorry.

Sorry to bump but I would really like a clean solution to this in the Juce trunk :slight_smile:

Thanks,

Hi Jules,

I’m trying to put back my old code which resolved this problem in the new trunk and I’m getting some weird crashes in Desktop::getMainMouseSource at the startup (JUCEApplication::initialiseApp)

Here is the code:

/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-10 by Raw Material Software Ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online at www.gnu.org/licenses.

   JUCE 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.

  ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.rawmaterialsoftware.com/juce for more information.

  ==============================================================================
*/

#include "../core/juce_StandardHeader.h"

#include <vector>
#include <set>

BEGIN_JUCE_NAMESPACE

#include "juce_AsyncUpdater.h"
#include "../threads/juce_Thread.h"
#include "../threads/juce_WaitableEvent.h"
#include "../threads/juce_ScopedLock.h"
#include "juce_MessageManager.h"
#include "../utilities/juce_DeletedAtShutdown.h"


template<class Type> class LockFreeFIFO
{
public:
  LockFreeFIFO() 
    : readIndex(0), writeIndex(0)
  {
  }

  void resize(size_t size)
  {
    buffer.resize(size);
  }

  size_t size() const { return buffer.size(); }

  void pop()
  {
    if ((readIndex.get()+1) >= buffer.size())
    {
      readIndex = 0;
    }
    else
    {
      ++readIndex;
    }
  }

  const Type &front() const
  {
    return buffer[readIndex.get()];
  }

  bool push(const Type &object)
  {
    const size_t newHead = (writeIndex.get()+1)%buffer.size();

    if (newHead != readIndex.get()) 
    {
      buffer[writeIndex.get()] = object;
      writeIndex = newHead;
      return true;
    }
    return false;
  }

  bool empty() const
  {
    if (readIndex.get() == writeIndex.get())
    {
      return true;
    }
    return false;
  }

private:
  Atomic<size_t> readIndex, writeIndex;
  std::vector<Type> buffer;
};

class InternalAsyncUpdaterThread  : 
  private Thread,
  private MessageListener,
  private DeletedAtShutdown
{
private:
  friend class AsyncUpdater;
  static InternalAsyncUpdaterThread* instance;

  CriticalSection lock;
  WaitableEvent waitableEvent;
  LockFreeFIFO<AsyncUpdater*> fifo;
  std::set<AsyncUpdater*> asyncUpdaters;

  InternalAsyncUpdaterThread (const InternalAsyncUpdaterThread&);
  const InternalAsyncUpdaterThread& operator= (const InternalAsyncUpdaterThread&);

public:
  void addAsyncUpdater (AsyncUpdater* const a)
  {
    jassert(MessageManager::getInstance()->isThisTheMessageThread());
    asyncUpdaters.insert(a);
    if (fifo.size() < asyncUpdaters.size())
    {
      ScopedLock l(lock);
      fifo.resize(asyncUpdaters.size());
    }
  }

  void removeAsyncUpdater (AsyncUpdater* const a)
  {
    jassert(MessageManager::getInstance()->isThisTheMessageThread());
    asyncUpdaters.erase(a);
  }

  InternalAsyncUpdaterThread()
    : Thread ("Juce AsyncUpdater"), fifo()
  {
    startThread ();
  }

  ~InternalAsyncUpdaterThread()
  {
    signalThreadShouldExit();
    waitableEvent.signal();
    waitForThreadToExit(-1);

    jassert (instance == this || instance == 0);
    if (instance == this)
      instance = 0;
  }

  void post(AsyncUpdater* const a)
  {
    ScopedLock l(lock);
    bool res = fifo.push(a); // there can't be another instance of the AsyncUpdater in the fifo as asyncMessagePending would be true so post won't be called
    jassert(res);
    waitableEvent.signal();
  }

  virtual void handleMessage(const Message &)
  {
    jassert(MessageManager::getInstance()->isThisTheMessageThread());
    while(!fifo.empty())
    {
      AsyncUpdater* a = fifo.front();
      fifo.pop();
      if (asyncUpdaters.find(a) != asyncUpdaters.end())
      {
        a->handleUpdateNowIfNeeded();
      }
    }
  }

  void run()
  {
    while (!threadShouldExit())
    {
      waitableEvent.wait();
      if (!threadShouldExit())
      {
        postMessage(new Message());
      }
    }
  }

  static bool hasInstance()
  {
    return (instance != 0);
  }

  static InternalAsyncUpdaterThread* getInstance()
  {
    if (instance == 0)
      instance = new InternalAsyncUpdaterThread();

    return instance;
  }
};

InternalAsyncUpdaterThread* InternalAsyncUpdaterThread::instance = 0;

//==============================================================================
AsyncUpdater::AsyncUpdater() throw()
: asyncMessagePending (false)
{
  InternalAsyncUpdaterThread::getInstance()->addAsyncUpdater(this);
}

AsyncUpdater::~AsyncUpdater()
{
  if (InternalAsyncUpdaterThread::hasInstance()) // might happen if another DeletedAtShutdown inherit from AsyncUpdater(like juce::Desktop)
  {
    InternalAsyncUpdaterThread::getInstance()->removeAsyncUpdater(this);
  }
}

void AsyncUpdater::triggerAsyncUpdate()
{
  if (! asyncMessagePending)
  {
    asyncMessagePending = true;
    InternalAsyncUpdaterThread::getInstance()->post(this);
  }
}

void AsyncUpdater::cancelPendingUpdate() throw()
{
  asyncMessagePending = false;
}

void AsyncUpdater::handleUpdateNowIfNeeded()
{
  if (asyncMessagePending)
  {
    asyncMessagePending = false;
    handleAsyncUpdate();
  }
}

END_JUCE_NAMESPACE

Any idea what could go wrong ?

Thanks,

What’s the stack trace?

here it is

test.exe!juce::OwnedArray<juce::MouseInputSource,juce::DummyCriticalSection>::getUnchecked(const int index=0)  Line 125	C++
test.exe!juce::Desktop::getMainMouseSource()  Line 249	C++
test.exe!juce::Component::sendFakeMouseMove()  Line 2710	C++
test.exe!juce::Component::setVisible(bool shouldBeVisible=true)  Line 152	C++
test.exe!juce::Button::setVisible(bool shouldBeVisible=true)  Line 467	C++
test.exe!juce::Component::addAndMakeVisible(juce::Component * const child=0x05623198, int zOrder=-1)  Line 1141	C++
test.exe!juce::DocumentWindow::lookAndFeelChanged()  Line 348	C++
test.exe!juce::DocumentWindow::DocumentWindow(const juce::String & title={...}, const juce::Colour & backgroundColour={...}, const int requiredButtons_=5, const bool addToDesktop_=true)  Line 84	C++
test.exe!uvi::StandalonePluginWindow::StandalonePluginWindow(const juce::String & title={...}, const juce::Colour & backgroundColour={...})  Line 155	C++
test.exe!uvi::Application::initialise(const juce::String & commandLine={...})  Line 36	C++
test.exe!juce::JUCEApplication::initialiseApp(const juce::String & commandLine={...})  Line 171	C++
test.exe!juce::JUCEApplication::main(const juce::String & commandLine={...})  Line 223	C++
test.exe!WinMain(HINSTANCE__ * __formal=0x00400000, HINSTANCE__ * __formal=0x00400000, HINSTANCE__ * __formal=0x00400000, HINSTANCE__ * __formal=0x00400000)  Line 91	C++
test.exe!__tmainCRTStartup()  Line 324	C
test.exe!WinMainCRTStartup()  Line 196	C
kernel32.dll!7c816fe7() 	
[Frames below may be incorrect and/or missing, no symbols loaded for kernel32.dll]	

mouseSources returns by getMainMouseSource looks like a bogus value

Thanks,

No idea! Are you sure your changes aren’t corrupting some memory or something…? Does it crash if you use my standard async updater code?