fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
@ -0,0 +1,98 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An interface for handling analytics events collected by an Analytics object.
|
||||
|
||||
For basic analytics logging you can implement this interface and add your
|
||||
class to an Analytics object.
|
||||
|
||||
For more advanced logging you may want to subclass
|
||||
ThreadedAnalyticsDestination instead, which is more suitable for interacting
|
||||
with web servers and other time consuming destinations.
|
||||
|
||||
@see Analytics, ThreadedAnalyticsDestination
|
||||
|
||||
@tags{Analytics}
|
||||
*/
|
||||
struct JUCE_API AnalyticsDestination
|
||||
{
|
||||
/** Contains information about an event to be logged. */
|
||||
struct AnalyticsEvent
|
||||
{
|
||||
/** The name of the event. */
|
||||
String name;
|
||||
|
||||
/** An optional integer representing the type of the event. You can use
|
||||
this to indicate if the event was a screenview, session start,
|
||||
exception, etc.
|
||||
*/
|
||||
int eventType;
|
||||
|
||||
/**
|
||||
The timestamp of the event.
|
||||
|
||||
Timestamps are automatically applied by an Analytics object and are
|
||||
derived from Time::getMillisecondCounter(). As such these timestamps
|
||||
do not represent absolute times, but relative timings of events for
|
||||
each user in each session will be accurate.
|
||||
*/
|
||||
uint32 timestamp;
|
||||
|
||||
/** The parameters of the event. */
|
||||
StringPairArray parameters;
|
||||
|
||||
/** The user ID associated with the event. */
|
||||
String userID;
|
||||
|
||||
/** Properties associated with the user. */
|
||||
StringPairArray userProperties;
|
||||
};
|
||||
|
||||
/** Constructor. */
|
||||
AnalyticsDestination() = default;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~AnalyticsDestination() {}
|
||||
|
||||
/**
|
||||
When an AnalyticsDestination is added to an Analytics object this method
|
||||
is called when an analytics event is triggered from the Analytics
|
||||
object.
|
||||
|
||||
Override this method to log the event information somewhere useful.
|
||||
*/
|
||||
virtual void logEvent (const AnalyticsEvent& event) = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyticsDestination)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,290 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ThreadedAnalyticsDestination::ThreadedAnalyticsDestination (const String& threadName)
|
||||
: dispatcher (threadName, *this)
|
||||
{}
|
||||
|
||||
ThreadedAnalyticsDestination::~ThreadedAnalyticsDestination()
|
||||
{
|
||||
// If you hit this assertion then the analytics thread has not been shut down
|
||||
// before this class is destroyed. Call stopAnalyticsThread() in your destructor!
|
||||
jassert (! dispatcher.isThreadRunning());
|
||||
}
|
||||
|
||||
void ThreadedAnalyticsDestination::setBatchPeriod (int newBatchPeriodMilliseconds)
|
||||
{
|
||||
dispatcher.batchPeriodMilliseconds = newBatchPeriodMilliseconds;
|
||||
}
|
||||
|
||||
void ThreadedAnalyticsDestination::logEvent (const AnalyticsEvent& event)
|
||||
{
|
||||
dispatcher.addToQueue (event);
|
||||
}
|
||||
|
||||
void ThreadedAnalyticsDestination::startAnalyticsThread (int initialBatchPeriodMilliseconds)
|
||||
{
|
||||
setBatchPeriod (initialBatchPeriodMilliseconds);
|
||||
dispatcher.startThread();
|
||||
}
|
||||
|
||||
void ThreadedAnalyticsDestination::stopAnalyticsThread (int timeout)
|
||||
{
|
||||
dispatcher.signalThreadShouldExit();
|
||||
stopLoggingEvents();
|
||||
dispatcher.stopThread (timeout);
|
||||
|
||||
if (dispatcher.eventQueue.size() > 0)
|
||||
saveUnloggedEvents (dispatcher.eventQueue);
|
||||
}
|
||||
|
||||
ThreadedAnalyticsDestination::EventDispatcher::EventDispatcher (const String& threadName,
|
||||
ThreadedAnalyticsDestination& destination)
|
||||
: Thread (threadName),
|
||||
parent (destination)
|
||||
{}
|
||||
|
||||
void ThreadedAnalyticsDestination::EventDispatcher::run()
|
||||
{
|
||||
// We may have inserted some events into the queue (on the message thread)
|
||||
// before this thread has started, so make sure the old events are at the
|
||||
// front of the queue.
|
||||
{
|
||||
std::deque<AnalyticsEvent> restoredEventQueue;
|
||||
parent.restoreUnloggedEvents (restoredEventQueue);
|
||||
|
||||
const ScopedLock lock (queueAccess);
|
||||
|
||||
for (auto rit = restoredEventQueue.rbegin(); rit != restoredEventQueue.rend(); ++rit)
|
||||
eventQueue.push_front (*rit);
|
||||
}
|
||||
|
||||
const int maxBatchSize = parent.getMaximumBatchSize();
|
||||
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
{
|
||||
const auto numEventsInBatch = eventsToSend.size();
|
||||
const auto freeBatchCapacity = maxBatchSize - numEventsInBatch;
|
||||
|
||||
if (freeBatchCapacity > 0)
|
||||
{
|
||||
const auto numNewEvents = (int) eventQueue.size() - numEventsInBatch;
|
||||
|
||||
if (numNewEvents > 0)
|
||||
{
|
||||
const ScopedLock lock (queueAccess);
|
||||
|
||||
const auto numEventsToAdd = jmin (numNewEvents, freeBatchCapacity);
|
||||
const auto newBatchSize = numEventsInBatch + numEventsToAdd;
|
||||
|
||||
for (auto i = numEventsInBatch; i < newBatchSize; ++i)
|
||||
eventsToSend.add (eventQueue[(size_t) i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto submissionTime = Time::getMillisecondCounter();
|
||||
|
||||
if (! eventsToSend.isEmpty())
|
||||
{
|
||||
if (parent.logBatchedEvents (eventsToSend))
|
||||
{
|
||||
const ScopedLock lock (queueAccess);
|
||||
|
||||
for (auto i = 0; i < eventsToSend.size(); ++i)
|
||||
eventQueue.pop_front();
|
||||
|
||||
eventsToSend.clearQuick();
|
||||
}
|
||||
}
|
||||
|
||||
while (Time::getMillisecondCounter() - submissionTime < (uint32) batchPeriodMilliseconds.get())
|
||||
{
|
||||
if (threadShouldExit())
|
||||
return;
|
||||
|
||||
Thread::sleep (100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadedAnalyticsDestination::EventDispatcher::addToQueue (const AnalyticsEvent& event)
|
||||
{
|
||||
const ScopedLock lock (queueAccess);
|
||||
eventQueue.push_back (event);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
namespace DestinationTestHelpers
|
||||
{
|
||||
//==============================================================================
|
||||
struct BasicDestination : public ThreadedAnalyticsDestination
|
||||
{
|
||||
BasicDestination (std::deque<AnalyticsEvent>& loggedEvents,
|
||||
std::deque<AnalyticsEvent>& unloggedEvents)
|
||||
: ThreadedAnalyticsDestination ("ThreadedAnalyticsDestinationTest"),
|
||||
loggedEventQueue (loggedEvents),
|
||||
unloggedEventStore (unloggedEvents)
|
||||
{
|
||||
startAnalyticsThread (20);
|
||||
}
|
||||
|
||||
~BasicDestination()
|
||||
{
|
||||
stopAnalyticsThread (1000);
|
||||
}
|
||||
|
||||
int getMaximumBatchSize() override
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
void saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave) override
|
||||
{
|
||||
unloggedEventStore = eventsToSave;
|
||||
}
|
||||
|
||||
void restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue) override
|
||||
{
|
||||
restoredEventQueue = unloggedEventStore;
|
||||
}
|
||||
|
||||
bool logBatchedEvents (const Array<AnalyticsEvent>& events) override
|
||||
{
|
||||
jassert (events.size() <= getMaximumBatchSize());
|
||||
|
||||
if (loggingIsEnabled)
|
||||
{
|
||||
const ScopedLock lock (eventQueueChanging);
|
||||
|
||||
for (auto& event : events)
|
||||
loggedEventQueue.push_back (event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void stopLoggingEvents() override {}
|
||||
|
||||
void setLoggingEnabled (bool shouldLogEvents)
|
||||
{
|
||||
loggingIsEnabled = shouldLogEvents;
|
||||
}
|
||||
|
||||
std::deque<AnalyticsEvent>& loggedEventQueue;
|
||||
std::deque<AnalyticsEvent>& unloggedEventStore;
|
||||
bool loggingIsEnabled = true;
|
||||
CriticalSection eventQueueChanging;
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ThreadedAnalyticsDestinationTests : public UnitTest
|
||||
{
|
||||
ThreadedAnalyticsDestinationTests()
|
||||
: UnitTest ("ThreadedAnalyticsDestination")
|
||||
{}
|
||||
|
||||
void compareEventQueues (const std::deque<AnalyticsDestination::AnalyticsEvent>& a,
|
||||
const std::deque<AnalyticsDestination::AnalyticsEvent>& b)
|
||||
{
|
||||
const auto numEntries = a.size();
|
||||
expectEquals ((int) b.size(), (int) numEntries);
|
||||
|
||||
for (size_t i = 0; i < numEntries; ++i)
|
||||
{
|
||||
expectEquals (a[i].name, b[i].name);
|
||||
expect (a[i].timestamp == b[i].timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
std::deque<AnalyticsDestination::AnalyticsEvent> testEvents;
|
||||
|
||||
for (int i = 0; i < 7; ++i)
|
||||
testEvents.push_back ({ String (i), 0, Time::getMillisecondCounter(), {}, "TestUser", {} });
|
||||
|
||||
std::deque<AnalyticsDestination::AnalyticsEvent> loggedEvents, unloggedEvents;
|
||||
|
||||
beginTest ("New events");
|
||||
|
||||
{
|
||||
DestinationTestHelpers::BasicDestination destination (loggedEvents, unloggedEvents);
|
||||
|
||||
for (auto& event : testEvents)
|
||||
destination.logEvent (event);
|
||||
|
||||
size_t waitTime = 0, numLoggedEvents = 0;
|
||||
|
||||
while (numLoggedEvents < testEvents.size())
|
||||
{
|
||||
if (waitTime > 4000)
|
||||
{
|
||||
expect (waitTime < 4000);
|
||||
break;
|
||||
}
|
||||
|
||||
Thread::sleep (40);
|
||||
waitTime += 40;
|
||||
|
||||
const ScopedLock lock (destination.eventQueueChanging);
|
||||
numLoggedEvents = loggedEvents.size();
|
||||
}
|
||||
}
|
||||
|
||||
compareEventQueues (loggedEvents, testEvents);
|
||||
expect (unloggedEvents.size() == 0);
|
||||
|
||||
loggedEvents.clear();
|
||||
|
||||
beginTest ("Unlogged events");
|
||||
{
|
||||
DestinationTestHelpers::BasicDestination destination (loggedEvents, unloggedEvents);
|
||||
destination.setLoggingEnabled (false);
|
||||
|
||||
for (auto& event : testEvents)
|
||||
destination.logEvent (event);
|
||||
}
|
||||
|
||||
compareEventQueues (unloggedEvents, testEvents);
|
||||
expect (loggedEvents.size() == 0);
|
||||
}
|
||||
};
|
||||
|
||||
static ThreadedAnalyticsDestinationTests threadedAnalyticsDestinationTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,218 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for dispatching analytics events on a dedicated thread.
|
||||
|
||||
This class is particularly useful for sending analytics events to a web
|
||||
server without blocking the message thread. It can also save (and restore)
|
||||
events that were not dispatched so no information is lost when an internet
|
||||
connection is absent or something else prevents successful logging.
|
||||
|
||||
Once startAnalyticsThread is called the logBatchedEvents method is
|
||||
periodically invoked on an analytics thread, with the period determined by
|
||||
calls to setBatchPeriod. Here events are grouped together into batches, with
|
||||
the maximum batch size set by the implementation of getMaximumBatchSize.
|
||||
|
||||
It's important to call stopAnalyticsThread in the destructor of your
|
||||
subclass (or before then) to give the analytics thread time to shut down.
|
||||
Calling stopAnalyticsThread will, in turn, call stopLoggingEvents, which
|
||||
you should use to terminate the currently running logBatchedEvents call.
|
||||
|
||||
@see Analytics, AnalyticsDestination, AnalyticsDestination::AnalyticsEvent
|
||||
|
||||
@tags{Analytics}
|
||||
*/
|
||||
class JUCE_API ThreadedAnalyticsDestination : public AnalyticsDestination
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/**
|
||||
Creates a ThreadedAnalyticsDestination.
|
||||
|
||||
@param threadName used to identify the analytics
|
||||
thread in debug builds
|
||||
*/
|
||||
ThreadedAnalyticsDestination (const String& threadName = "Analytics thread");
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ThreadedAnalyticsDestination();
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Override this method to provide the maximum batch size you can handle in
|
||||
your subclass.
|
||||
|
||||
Calls to logBatchedEvents will contain no more than this number of events.
|
||||
*/
|
||||
virtual int getMaximumBatchSize() = 0;
|
||||
|
||||
/**
|
||||
This method will be called periodically on the analytics thread.
|
||||
|
||||
If this method returns false then the subsequent call of this function will
|
||||
contain the same events as previous call, plus any new events that have been
|
||||
generated in the period between calls. The order of events will not be
|
||||
changed. This allows you to retry logging events until they are logged
|
||||
successfully.
|
||||
|
||||
@param events a list of events to be logged
|
||||
@returns if the events were successfully logged
|
||||
*/
|
||||
virtual bool logBatchedEvents (const Array<AnalyticsEvent>& events) = 0;
|
||||
|
||||
/**
|
||||
You must always call stopAnalyticsThread in the destructor of your subclass
|
||||
(or before then) to give the analytics thread time to shut down.
|
||||
|
||||
Calling stopAnalyticsThread triggers a call to this method. At this point
|
||||
you are guaranteed that logBatchedEvents has been called for the last time
|
||||
and you should make sure that the current call to logBatchedEvents finishes
|
||||
as quickly as possible. This method and a subsequent call to
|
||||
saveUnloggedEvents must both complete before the timeout supplied to
|
||||
stopAnalyticsThread.
|
||||
|
||||
In a normal use case stopLoggingEvents will be called on the message thread
|
||||
from the destructor of your ThreadedAnalyticsDestination subclass, and must
|
||||
stop the logBatchedEvents method which is running on the analytics thread.
|
||||
|
||||
@see stopAnalyticsThread
|
||||
*/
|
||||
virtual void stopLoggingEvents() = 0;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Call this to set the period between logBatchedEvents invocations.
|
||||
|
||||
This method is thread safe and can be used to implements things like
|
||||
exponential backoff in logBatchedEvents calls.
|
||||
|
||||
@param newSubmissionPeriodMilliseconds the new submission period to
|
||||
use in milliseconds
|
||||
*/
|
||||
void setBatchPeriod (int newSubmissionPeriodMilliseconds);
|
||||
|
||||
/**
|
||||
Adds an event to the queue, which will ultimately be submitted to
|
||||
logBatchedEvents.
|
||||
|
||||
This method is thread safe.
|
||||
|
||||
@param event the analytics event to add to the queue
|
||||
*/
|
||||
void logEvent (const AnalyticsEvent& event) override final;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/**
|
||||
Starts the analytics thread, with an initial event batching period.
|
||||
|
||||
@param initialBatchPeriodMilliseconds the initial event batching period
|
||||
in milliseconds
|
||||
*/
|
||||
void startAnalyticsThread (int initialBatchPeriodMilliseconds);
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Triggers the shutdown of the analytics thread.
|
||||
|
||||
You must call this method in the destructor of your subclass (or before
|
||||
then) to give the analytics thread time to shut down.
|
||||
|
||||
This method invokes stopLoggingEvents and you should ensure that both the
|
||||
analytics thread and a call to saveUnloggedEvents are able to finish before
|
||||
the supplied timeout. This timeout is important because on platforms like
|
||||
iOS an app is killed if it takes too long to shut down.
|
||||
|
||||
@param timeoutMilliseconds the number of milliseconds before
|
||||
the analytics thread is forcibly
|
||||
terminated
|
||||
*/
|
||||
void stopAnalyticsThread (int timeoutMilliseconds);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
/**
|
||||
This method will be called when the analytics thread is shut down,
|
||||
giving you the chance to save any analytics events that could not be
|
||||
logged. Once saved these events can be put back into the queue of events
|
||||
when the ThreadedAnalyticsDestination is recreated via
|
||||
restoreUnloggedEvents.
|
||||
|
||||
This method should return as quickly as possible, as both
|
||||
stopLoggingEvents and this method need to complete inside the timeout
|
||||
set in stopAnalyticsThread.
|
||||
|
||||
@param eventsToSave the events that could not be logged
|
||||
|
||||
@see stopAnalyticsThread, stopLoggingEvents, restoreUnloggedEvents
|
||||
*/
|
||||
virtual void saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave) = 0;
|
||||
|
||||
/**
|
||||
The counterpart to saveUnloggedEvents.
|
||||
|
||||
Events added to the event queue provided by this method will be the
|
||||
first events supplied to logBatchedEvents calls. Use this method to
|
||||
restore any unlogged events previously stored in a call to
|
||||
saveUnloggedEvents.
|
||||
|
||||
This method is called on the analytics thread.
|
||||
|
||||
@param restoredEventQueue place restored events into this queue
|
||||
|
||||
@see saveUnloggedEvents
|
||||
*/
|
||||
virtual void restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue) = 0;
|
||||
|
||||
struct EventDispatcher : public Thread
|
||||
{
|
||||
EventDispatcher (const String& threadName, ThreadedAnalyticsDestination&);
|
||||
|
||||
void run() override;
|
||||
void addToQueue (const AnalyticsEvent&);
|
||||
|
||||
ThreadedAnalyticsDestination& parent;
|
||||
|
||||
std::deque<AnalyticsEvent> eventQueue;
|
||||
CriticalSection queueAccess;
|
||||
|
||||
Atomic<int> batchPeriodMilliseconds { 1000 };
|
||||
|
||||
Array<AnalyticsEvent> eventsToSend;
|
||||
};
|
||||
|
||||
const String destinationName;
|
||||
EventDispatcher dispatcher;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadedAnalyticsDestination)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user