git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,271 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
AbstractFifo::AbstractFifo (int capacity) noexcept : bufferSize (capacity)
{
jassert (bufferSize > 0);
}
AbstractFifo::~AbstractFifo() {}
int AbstractFifo::getTotalSize() const noexcept { return bufferSize; }
int AbstractFifo::getFreeSpace() const noexcept { return bufferSize - getNumReady() - 1; }
int AbstractFifo::getNumReady() const noexcept
{
auto vs = validStart.get();
auto ve = validEnd.get();
return ve >= vs ? (ve - vs) : (bufferSize - (vs - ve));
}
void AbstractFifo::reset() noexcept
{
validEnd = 0;
validStart = 0;
}
void AbstractFifo::setTotalSize (int newSize) noexcept
{
jassert (newSize > 0);
reset();
bufferSize = newSize;
}
//==============================================================================
void AbstractFifo::prepareToWrite (int numToWrite, int& startIndex1, int& blockSize1,
int& startIndex2, int& blockSize2) const noexcept
{
auto vs = validStart.get();
auto ve = validEnd.get();
auto freeSpace = ve >= vs ? (bufferSize - (ve - vs)) : (vs - ve);
numToWrite = jmin (numToWrite, freeSpace - 1);
if (numToWrite <= 0)
{
startIndex1 = 0;
startIndex2 = 0;
blockSize1 = 0;
blockSize2 = 0;
}
else
{
startIndex1 = ve;
startIndex2 = 0;
blockSize1 = jmin (bufferSize - ve, numToWrite);
numToWrite -= blockSize1;
blockSize2 = numToWrite <= 0 ? 0 : jmin (numToWrite, vs);
}
}
void AbstractFifo::finishedWrite (int numWritten) noexcept
{
jassert (numWritten >= 0 && numWritten < bufferSize);
auto newEnd = validEnd.get() + numWritten;
if (newEnd >= bufferSize)
newEnd -= bufferSize;
validEnd = newEnd;
}
void AbstractFifo::prepareToRead (int numWanted, int& startIndex1, int& blockSize1,
int& startIndex2, int& blockSize2) const noexcept
{
auto vs = validStart.get();
auto ve = validEnd.get();
auto numReady = ve >= vs ? (ve - vs) : (bufferSize - (vs - ve));
numWanted = jmin (numWanted, numReady);
if (numWanted <= 0)
{
startIndex1 = 0;
startIndex2 = 0;
blockSize1 = 0;
blockSize2 = 0;
}
else
{
startIndex1 = vs;
startIndex2 = 0;
blockSize1 = jmin (bufferSize - vs, numWanted);
numWanted -= blockSize1;
blockSize2 = numWanted <= 0 ? 0 : jmin (numWanted, ve);
}
}
void AbstractFifo::finishedRead (int numRead) noexcept
{
jassert (numRead >= 0 && numRead <= bufferSize);
auto newStart = validStart.get() + numRead;
if (newStart >= bufferSize)
newStart -= bufferSize;
validStart = newStart;
}
//==============================================================================
template <AbstractFifo::ReadOrWrite mode>
AbstractFifo::ScopedReadWrite<mode>::ScopedReadWrite (ScopedReadWrite&& other) noexcept
: startIndex1 (other.startIndex1),
blockSize1 (other.blockSize1),
startIndex2 (other.startIndex2),
blockSize2 (other.blockSize2)
{
swap (other);
}
template <AbstractFifo::ReadOrWrite mode>
AbstractFifo::ScopedReadWrite<mode>&
AbstractFifo::ScopedReadWrite<mode>::operator= (ScopedReadWrite&& other) noexcept
{
swap (other);
return *this;
}
template <AbstractFifo::ReadOrWrite mode>
void AbstractFifo::ScopedReadWrite<mode>::swap (ScopedReadWrite& other) noexcept
{
std::swap (other.fifo, fifo);
std::swap (other.startIndex1, startIndex1);
std::swap (other.blockSize1, blockSize1);
std::swap (other.startIndex2, startIndex2);
std::swap (other.blockSize2, blockSize2);
}
template class AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::read>;
template class AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::write>;
AbstractFifo::ScopedRead AbstractFifo::read (int numToRead) noexcept { return { *this, numToRead }; }
AbstractFifo::ScopedWrite AbstractFifo::write (int numToWrite) noexcept { return { *this, numToWrite }; }
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class AbstractFifoTests : public UnitTest
{
public:
AbstractFifoTests()
: UnitTest ("Abstract Fifo", UnitTestCategories::containers)
{}
struct WriteThread : public Thread
{
WriteThread (AbstractFifo& f, int* b, Random rng)
: Thread ("fifo writer"), fifo (f), buffer (b), random (rng)
{
startThread();
}
~WriteThread()
{
stopThread (5000);
}
void run()
{
int n = 0;
while (! threadShouldExit())
{
int num = random.nextInt (2000) + 1;
auto writer = fifo.write (num);
jassert (writer.blockSize1 >= 0 && writer.blockSize2 >= 0);
jassert (writer.blockSize1 == 0
|| (writer.startIndex1 >= 0 && writer.startIndex1 < fifo.getTotalSize()));
jassert (writer.blockSize2 == 0
|| (writer.startIndex2 >= 0 && writer.startIndex2 < fifo.getTotalSize()));
writer.forEach ([this, &n] (int index) { this->buffer[index] = n++; });
}
}
AbstractFifo& fifo;
int* buffer;
Random random;
};
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6262)
void runTest() override
{
beginTest ("AbstractFifo");
int buffer[5000];
AbstractFifo fifo (numElementsInArray (buffer));
WriteThread writer (fifo, buffer, getRandom());
int n = 0;
Random r = getRandom();
r.combineSeed (12345);
for (int count = 100000; --count >= 0;)
{
int num = r.nextInt (6000) + 1;
auto reader = fifo.read (num);
if (! (reader.blockSize1 >= 0 && reader.blockSize2 >= 0)
&& (reader.blockSize1 == 0
|| (reader.startIndex1 >= 0 && reader.startIndex1 < fifo.getTotalSize()))
&& (reader.blockSize2 == 0
|| (reader.startIndex2 >= 0 && reader.startIndex2 < fifo.getTotalSize())))
{
expect (false, "prepareToRead returned -ve values");
break;
}
bool failed = false;
reader.forEach ([&failed, &buffer, &n] (int index)
{
failed = (buffer[index] != n++) || failed;
});
if (failed)
{
expect (false, "read values were incorrect");
break;
}
}
}
JUCE_END_IGNORE_WARNINGS_MSVC
};
static AbstractFifoTests fifoUnitTests;
#endif
} // namespace juce

View File

@ -0,0 +1,336 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
//==============================================================================
/**
Encapsulates the logic required to implement a lock-free FIFO.
This class handles the logic needed when building a single-reader, single-writer FIFO.
It doesn't actually hold any data itself, but your FIFO class can use one of these to manage
its position and status when reading or writing to it.
To use it, you can call prepareToWrite() to determine the position within your own buffer that
an incoming block of data should be stored, and prepareToRead() to find out when the next
outgoing block should be read from.
e.g.
@code
struct MyFifo
{
void addToFifo (const int* someData, int numItems)
{
const auto scope = abstractFifo.write (numItems);
if (scope.blockSize1 > 0)
copySomeData (myBuffer + scope.startIndex1, someData, scope.blockSize1);
if (scope.blockSize2 > 0)
copySomeData (myBuffer + scope.startIndex2, someData, scope.blockSize2);
}
void readFromFifo (int* someData, int numItems)
{
const auto scope = abstractFifo.read (numItems);
if (scope.blockSize1 > 0)
copySomeData (someData, myBuffer + scope.startIndex1, scope.blockSize1);
if (scope.blockSize2 > 0)
copySomeData (someData + scope.blockSize1, myBuffer + scope.startIndex2, scope.blockSize2);
}
AbstractFifo abstractFifo { 1024 };
int myBuffer[1024];
};
@endcode
@tags{Core}
*/
class JUCE_API AbstractFifo
{
public:
//==============================================================================
/** Creates a FIFO to manage a buffer with the specified capacity. */
AbstractFifo (int capacity) noexcept;
/** Destructor */
~AbstractFifo();
//==============================================================================
/** Returns the total size of the buffer being managed. */
int getTotalSize() const noexcept;
/** Returns the number of items that can currently be added to the buffer without it overflowing. */
int getFreeSpace() const noexcept;
/** Returns the number of items that can currently be read from the buffer. */
int getNumReady() const noexcept;
/** Clears the buffer positions, so that it appears empty. */
void reset() noexcept;
/** Changes the buffer's total size.
Note that this isn't thread-safe, so don't call it if there's any danger that it
might overlap with a call to any other method in this class!
*/
void setTotalSize (int newSize) noexcept;
//==============================================================================
/** Returns the location within the buffer at which an incoming block of data should be written.
Because the section of data that you want to add to the buffer may overlap the end
and wrap around to the start, two blocks within your buffer are returned, and you
should copy your data into the first one, with any remaining data spilling over into
the second.
If the number of items you ask for is too large to fit within the buffer's free space, then
blockSize1 + blockSize2 may add up to a lower value than numToWrite. If this happens, you
may decide to keep waiting and re-trying the method until there's enough space available.
After calling this method, if you choose to write your data into the blocks returned, you
must call finishedWrite() to tell the FIFO how much data you actually added.
e.g.
@code
void addToFifo (const int* someData, int numItems)
{
int start1, size1, start2, size2;
prepareToWrite (numItems, start1, size1, start2, size2);
if (size1 > 0)
copySomeData (myBuffer + start1, someData, size1);
if (size2 > 0)
copySomeData (myBuffer + start2, someData + size1, size2);
finishedWrite (size1 + size2);
}
@endcode
@param numToWrite indicates how many items you'd like to add to the buffer
@param startIndex1 on exit, this will contain the start index in your buffer at which your data should be written
@param blockSize1 on exit, this indicates how many items can be written to the block starting at startIndex1
@param startIndex2 on exit, this will contain the start index in your buffer at which any data that didn't fit into
the first block should be written
@param blockSize2 on exit, this indicates how many items can be written to the block starting at startIndex2
@see finishedWrite
*/
void prepareToWrite (int numToWrite, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept;
/** Called after writing from the FIFO, to indicate that this many items have been added.
@see prepareToWrite
*/
void finishedWrite (int numWritten) noexcept;
/** Returns the location within the buffer from which the next block of data should be read.
Because the section of data that you want to read from the buffer may overlap the end
and wrap around to the start, two blocks within your buffer are returned, and you
should read from both of them.
If the number of items you ask for is greater than the amount of data available, then
blockSize1 + blockSize2 may add up to a lower value than numWanted. If this happens, you
may decide to keep waiting and re-trying the method until there's enough data available.
After calling this method, if you choose to read the data, you must call finishedRead() to
tell the FIFO how much data you have consumed.
e.g.
@code
void readFromFifo (int* someData, int numItems)
{
int start1, size1, start2, size2;
prepareToRead (numSamples, start1, size1, start2, size2);
if (size1 > 0)
copySomeData (someData, myBuffer + start1, size1);
if (size2 > 0)
copySomeData (someData + size1, myBuffer + start2, size2);
finishedRead (size1 + size2);
}
@endcode
@param numWanted indicates how many items you'd like to add to the buffer
@param startIndex1 on exit, this will contain the start index in your buffer at which your data should be written
@param blockSize1 on exit, this indicates how many items can be written to the block starting at startIndex1
@param startIndex2 on exit, this will contain the start index in your buffer at which any data that didn't fit into
the first block should be written
@param blockSize2 on exit, this indicates how many items can be written to the block starting at startIndex2
@see finishedRead
*/
void prepareToRead (int numWanted, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept;
/** Called after reading from the FIFO, to indicate that this many items have now been consumed.
@see prepareToRead
*/
void finishedRead (int numRead) noexcept;
//==============================================================================
private:
enum class ReadOrWrite
{
read,
write
};
public:
/** Class for a scoped reader/writer */
template <ReadOrWrite mode>
class ScopedReadWrite final
{
public:
/** Construct an unassigned reader/writer. Doesn't do anything upon destruction. */
ScopedReadWrite() = default;
/** Construct a reader/writer and immediately call prepareRead/prepareWrite
on the abstractFifo which was passed in.
This object will hold a pointer back to the fifo, so make sure that
the fifo outlives this object.
*/
ScopedReadWrite (AbstractFifo& f, int num) noexcept : fifo (&f)
{
prepare (*fifo, num);
}
ScopedReadWrite (const ScopedReadWrite&) = delete;
ScopedReadWrite (ScopedReadWrite&&) noexcept;
ScopedReadWrite& operator= (const ScopedReadWrite&) = delete;
ScopedReadWrite& operator= (ScopedReadWrite&&) noexcept;
/** Calls finishedRead or finishedWrite if this is a non-null scoped
reader/writer.
*/
~ScopedReadWrite() noexcept
{
if (fifo != nullptr)
finish (*fifo, blockSize1 + blockSize2);
}
/** Calls the passed function with each index that was deemed valid
for the current read/write operation.
*/
template <typename FunctionToApply>
void forEach (FunctionToApply&& func) const
{
for (auto i = startIndex1, e = startIndex1 + blockSize1; i != e; ++i) func (i);
for (auto i = startIndex2, e = startIndex2 + blockSize2; i != e; ++i) func (i);
}
int startIndex1, blockSize1, startIndex2, blockSize2;
private:
void prepare (AbstractFifo&, int) noexcept;
static void finish (AbstractFifo&, int) noexcept;
void swap (ScopedReadWrite&) noexcept;
AbstractFifo* fifo = nullptr;
};
using ScopedRead = ScopedReadWrite<ReadOrWrite::read>;
using ScopedWrite = ScopedReadWrite<ReadOrWrite::write>;
/** Replaces prepareToRead/finishedRead with a single function.
This function returns an object which contains the start indices and
block sizes, and also automatically finishes the read operation when
it goes out of scope.
@code
{
auto readHandle = fifo.read (4);
for (auto i = 0; i != readHandle.blockSize1; ++i)
{
// read the item at index readHandle.startIndex1 + i
}
for (auto i = 0; i != readHandle.blockSize2; ++i)
{
// read the item at index readHandle.startIndex2 + i
}
} // readHandle goes out of scope here, finishing the read operation
@endcode
*/
ScopedRead read (int numToRead) noexcept;
/** Replaces prepareToWrite/finishedWrite with a single function.
This function returns an object which contains the start indices and
block sizes, and also automatically finishes the write operation when
it goes out of scope.
@code
{
auto writeHandle = fifo.write (5);
for (auto i = 0; i != writeHandle.blockSize1; ++i)
{
// write the item at index writeHandle.startIndex1 + i
}
for (auto i = 0; i != writeHandle.blockSize2; ++i)
{
// write the item at index writeHandle.startIndex2 + i
}
} // writeHandle goes out of scope here, finishing the write operation
@endcode
*/
ScopedWrite write (int numToWrite) noexcept;
private:
//==============================================================================
int bufferSize;
Atomic<int> validStart, validEnd;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AbstractFifo)
};
template <>
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::read>::finish (AbstractFifo& f, int num) noexcept
{
f.finishedRead (num);
}
template <>
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::write>::finish (AbstractFifo& f, int num) noexcept
{
f.finishedWrite (num);
}
template <>
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::read>::prepare (AbstractFifo& f, int num) noexcept
{
f.prepareToRead (num, startIndex1, blockSize1, startIndex2, blockSize2);
}
template <>
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::write>::prepare (AbstractFifo& f, int num) noexcept
{
f.prepareToWrite (num, startIndex1, blockSize1, startIndex2, blockSize2);
}
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
//==============================================================================
/**
Implements some basic array storage allocation functions.
This class isn't really for public use - it used to be part of the
container classes but has since been superseded by ArrayBase. Eventually
it will be removed from the API.
@tags{Core}
*/
template <class ElementType, class TypeOfCriticalSectionToUse>
class ArrayAllocationBase : public TypeOfCriticalSectionToUse
{
public:
//==============================================================================
/** Creates an empty array. */
ArrayAllocationBase() = default;
/** Destructor. */
~ArrayAllocationBase() = default;
ArrayAllocationBase (ArrayAllocationBase&& other) noexcept
: elements (std::move (other.elements)),
numAllocated (other.numAllocated)
{
}
ArrayAllocationBase& operator= (ArrayAllocationBase&& other) noexcept
{
elements = std::move (other.elements);
numAllocated = other.numAllocated;
return *this;
}
//==============================================================================
/** Changes the amount of storage allocated.
This will retain any data currently held in the array, and either add or
remove extra space at the end.
@param numElements the number of elements that are needed
*/
void setAllocatedSize (int numElements)
{
if (numAllocated != numElements)
{
if (numElements > 0)
elements.realloc ((size_t) numElements);
else
elements.free();
numAllocated = numElements;
}
}
/** Increases the amount of storage allocated if it is less than a given amount.
This will retain any data currently held in the array, but will add
extra space at the end to make sure there it's at least as big as the size
passed in. If it's already bigger, no action is taken.
@param minNumElements the minimum number of elements that are needed
*/
void ensureAllocatedSize (int minNumElements)
{
if (minNumElements > numAllocated)
setAllocatedSize ((minNumElements + minNumElements / 2 + 8) & ~7);
jassert (numAllocated <= 0 || elements != nullptr);
}
/** Minimises the amount of storage allocated so that it's no more than
the given number of elements.
*/
void shrinkToNoMoreThan (int maxNumElements)
{
if (maxNumElements < numAllocated)
setAllocatedSize (maxNumElements);
}
/** Swap the contents of two objects. */
void swapWith (ArrayAllocationBase& other) noexcept
{
elements.swapWith (other.elements);
std::swap (numAllocated, other.numAllocated);
}
//==============================================================================
HeapBlock<ElementType> elements;
int numAllocated = 0;
private:
JUCE_DECLARE_NON_COPYABLE (ArrayAllocationBase)
};
} // namespace juce

View File

@ -0,0 +1,600 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
#if JUCE_UNIT_TESTS
namespace ArrayBaseTestsHelpers
{
class TriviallyCopyableType
{
public:
TriviallyCopyableType() = default;
TriviallyCopyableType (int v)
: value (v)
{}
TriviallyCopyableType (float v)
: value ((int) v)
{}
bool operator== (const TriviallyCopyableType& other) const
{
return getValue() == other.getValue();
}
int getValue() const { return value; }
private:
int value { -1111 };
};
class NonTriviallyCopyableType
{
public:
NonTriviallyCopyableType() = default;
NonTriviallyCopyableType (int v)
: value (v)
{}
NonTriviallyCopyableType (float v)
: value ((int) v)
{}
NonTriviallyCopyableType (const NonTriviallyCopyableType& other)
: value (other.value)
{}
NonTriviallyCopyableType& operator= (const NonTriviallyCopyableType& other)
{
value = other.value;
return *this;
}
bool operator== (const NonTriviallyCopyableType& other) const
{
return getValue() == other.getValue();
}
int getValue() const { return *ptr; }
private:
int value { -1111 };
int* ptr = &value;
};
}
static bool operator== (const ArrayBaseTestsHelpers::TriviallyCopyableType& tct,
const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct)
{
return tct.getValue() == ntct.getValue();
}
static bool operator== (const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct,
const ArrayBaseTestsHelpers::TriviallyCopyableType& tct)
{
return tct == ntct;
}
class ArrayBaseTests : public UnitTest
{
using CopyableType = ArrayBaseTestsHelpers::TriviallyCopyableType;
using NoncopyableType = ArrayBaseTestsHelpers::NonTriviallyCopyableType;
#if ! (defined(__GNUC__) && __GNUC__ < 5 && ! defined(__clang__))
static_assert (std::is_trivially_copyable<CopyableType>::value,
"Test TriviallyCopyableType is not trivially copyable");
static_assert (! std::is_trivially_copyable<NoncopyableType>::value,
"Test NonTriviallyCopyableType is trivially copyable");
#endif
public:
ArrayBaseTests()
: UnitTest ("ArrayBase", UnitTestCategories::containers)
{}
void runTest() override
{
beginTest ("grow capacity");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
int originalCapacity = 4;
referenceContainer.reserve ((size_t) originalCapacity);
expectEquals ((int) referenceContainer.capacity(), originalCapacity);
copyableContainer.setAllocatedSize (originalCapacity);
expectEquals (copyableContainer.capacity(), originalCapacity);
noncopyableContainer.setAllocatedSize (originalCapacity);
expectEquals (noncopyableContainer.capacity(), originalCapacity);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
addData (referenceContainer, copyableContainer, noncopyableContainer, 33);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
expect ((int) referenceContainer.capacity() != originalCapacity);
expect (copyableContainer.capacity() != originalCapacity);
expect (noncopyableContainer.capacity() != originalCapacity);
}
beginTest ("shrink capacity");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
int numElements = 45;
addData (referenceContainer, copyableContainer, noncopyableContainer, numElements);
copyableContainer.shrinkToNoMoreThan (numElements);
noncopyableContainer.setAllocatedSize (numElements + 1);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
referenceContainer.clear();
copyableContainer.removeElements (0, numElements);
noncopyableContainer.removeElements (0, numElements);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
copyableContainer.setAllocatedSize (0);
noncopyableContainer.setAllocatedSize (0);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
addData (referenceContainer, copyableContainer, noncopyableContainer, numElements);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
}
beginTest ("equality");
{
std::vector<int> referenceContainer = { 1, 2, 3 };
ArrayBase<int, DummyCriticalSection> testContainer1, testContainer2;
for (auto i : referenceContainer)
{
testContainer1.add (i);
testContainer2.add (i);
}
expect (testContainer1 == referenceContainer);
expect (testContainer2 == testContainer1);
testContainer1.ensureAllocatedSize (257);
referenceContainer.shrink_to_fit();
expect (testContainer1 == referenceContainer);
expect (testContainer2 == testContainer1);
testContainer1.removeElements (0, 1);
expect (testContainer1 != referenceContainer);
expect (testContainer2 != testContainer1);
}
beginTest ("accessors");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
addData (referenceContainer, copyableContainer, noncopyableContainer, 3);
int testValue = -123;
referenceContainer[0] = testValue;
copyableContainer[0] = testValue;
noncopyableContainer[0] = testValue;
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
expect (copyableContainer .getFirst().getValue() == testValue);
expect (noncopyableContainer.getFirst().getValue() == testValue);
auto last = referenceContainer.back().getValue();
expectEquals (copyableContainer .getLast().getValue(), last);
expectEquals (noncopyableContainer.getLast().getValue(), last);
ArrayBase<CopyableType, DummyCriticalSection> copyableEmpty;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableEmpty;
auto defualtValue = CopyableType().getValue();
expectEquals (defualtValue, NoncopyableType().getValue());
expectEquals (copyableEmpty .getFirst().getValue(), defualtValue);
expectEquals (noncopyableEmpty.getFirst().getValue(), defualtValue);
expectEquals (copyableEmpty .getLast() .getValue(), defualtValue);
expectEquals (noncopyableEmpty.getLast() .getValue(), defualtValue);
ArrayBase<float*, DummyCriticalSection> floatPointers;
expect (floatPointers.getValueWithDefault (-3) == nullptr);
}
beginTest ("add moved");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
for (int i = 0; i < 5; ++i)
{
CopyableType ref (-i);
CopyableType ct (-i);
NoncopyableType nct (-i);
referenceContainer.push_back (std::move (ref));
copyableContainer.add (std::move (ct));
noncopyableContainer.add (std::move (nct));
}
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
}
beginTest ("add multiple");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
for (int i = 4; i < 7; ++i)
referenceContainer.push_back ({ -i });
copyableContainer.add (CopyableType (-4), CopyableType (-5), CopyableType (-6));
noncopyableContainer.add (NoncopyableType (-4), NoncopyableType (-5), NoncopyableType (-6));
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
}
beginTest ("add array from a pointer");
{
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
std::vector<CopyableType> copyableData = { 3, 4, 5 };
std::vector<NoncopyableType> noncopyableData = { 3, 4, 5 };
copyableContainer.addArray (copyableData.data(), (int) copyableData.size());
noncopyableContainer.addArray (noncopyableData.data(), (int) noncopyableData.size());
checkEqual (copyableContainer, noncopyableContainer, copyableData);
}
beginTest ("add array from a pointer of a different type");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
std::vector<float> floatData = { 1.4f, 2.5f, 3.6f };
for (auto f : floatData)
referenceContainer.push_back ({ f });
copyableContainer.addArray (floatData.data(), (int) floatData.size());
noncopyableContainer.addArray (floatData.data(), (int) floatData.size());
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
}
beginTest ("add array from initializer_list");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
std::initializer_list<CopyableType> ilct { { 3 }, { 4 }, { 5 } };
std::initializer_list<NoncopyableType> ilnct { { 3 }, { 4 }, { 5 } };
for (auto v : ilct)
referenceContainer.push_back ({ v });
copyableContainer.addArray (ilct);
noncopyableContainer.addArray (ilnct);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
}
beginTest ("add array from containers");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
addData (referenceContainer, copyableContainer, noncopyableContainer, 5);
std::vector<CopyableType> referenceContainerCopy (referenceContainer);
std::vector<NoncopyableType> noncopyableReferenceContainerCopy;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainerCopy;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainerCopy;
for (auto& v : referenceContainerCopy)
noncopyableReferenceContainerCopy.push_back ({ v.getValue() });
for (size_t i = 0; i < referenceContainerCopy.size(); ++i)
{
auto value = referenceContainerCopy[i].getValue();
copyableContainerCopy.add ({ value });
noncopyableContainerCopy.add ({ value });
}
// From self-types
copyableContainer.addArray (copyableContainerCopy);
noncopyableContainer.addArray (noncopyableContainerCopy);
for (auto v : referenceContainerCopy)
referenceContainer.push_back (v);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
// From std containers
copyableContainer.addArray (referenceContainerCopy);
noncopyableContainer.addArray (noncopyableReferenceContainerCopy);
for (auto v : referenceContainerCopy)
referenceContainer.push_back (v);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
// From std containers with offset
int offset = 5;
copyableContainer.addArray (referenceContainerCopy, offset);
noncopyableContainer.addArray (noncopyableReferenceContainerCopy, offset);
for (size_t i = 5; i < referenceContainerCopy.size(); ++i)
referenceContainer.push_back (referenceContainerCopy[i]);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
}
beginTest ("insert");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
addData (referenceContainer, copyableContainer, noncopyableContainer, 8);
referenceContainer.insert (referenceContainer.begin(), -4);
copyableContainer.insert (0, -4, 1);
noncopyableContainer.insert (0, -4, 1);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
for (int i = 0; i < 3; ++i)
referenceContainer.insert (referenceContainer.begin() + 1, -3);
copyableContainer.insert (1, -3, 3);
noncopyableContainer.insert (1, -3, 3);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
for (int i = 0; i < 50; ++i)
referenceContainer.insert (referenceContainer.end() - 1, -9);
copyableContainer.insert (copyableContainer.size() - 2, -9, 50);
noncopyableContainer.insert (noncopyableContainer.size() - 2, -9, 50);
}
beginTest ("insert array");
{
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
std::vector<CopyableType> copyableData = { 3, 4, 5, 6, 7, 8 };
std::vector<NoncopyableType> noncopyableData = { 3, 4, 5, 6, 7, 8 };
std::vector<CopyableType> referenceContainer { copyableData };
copyableContainer.insertArray (0, copyableData.data(), (int) copyableData.size());
noncopyableContainer.insertArray (0, noncopyableData.data(), (int) noncopyableData.size());
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
int insertPos = copyableContainer.size() - 1;
for (auto it = copyableData.end(); it != copyableData.begin(); --it)
referenceContainer.insert (referenceContainer.begin() + insertPos, CopyableType (*(it - 1)));
copyableContainer.insertArray (insertPos, copyableData.data(), (int) copyableData.size());
noncopyableContainer.insertArray (insertPos, noncopyableData.data(), (int) noncopyableData.size());
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
}
beginTest ("remove");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
addData (referenceContainer, copyableContainer, noncopyableContainer, 17);
for (int i = 0; i < 4; ++i)
{
referenceContainer.erase (referenceContainer.begin() + i);
copyableContainer.removeElements (i, 1);
noncopyableContainer.removeElements (i, 1);
}
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
addData (referenceContainer, copyableContainer, noncopyableContainer, 17);
int blockSize = 3;
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < blockSize; ++j)
referenceContainer.erase (referenceContainer.begin() + i);
copyableContainer.removeElements (i, blockSize);
noncopyableContainer.removeElements (i, blockSize);
}
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
auto numToRemove = copyableContainer.size() - 2;
for (int i = 0; i < numToRemove; ++i)
referenceContainer.erase (referenceContainer.begin() + 1);
copyableContainer.removeElements (1, numToRemove);
noncopyableContainer.removeElements (1, numToRemove);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
}
beginTest ("move");
{
std::vector<CopyableType> referenceContainer;
ArrayBase<CopyableType, DummyCriticalSection> copyableContainer;
ArrayBase<NoncopyableType, DummyCriticalSection> noncopyableContainer;
addData (referenceContainer, copyableContainer, noncopyableContainer, 6);
std::vector<std::pair<int, int>> testValues;
testValues.emplace_back (2, 4);
testValues.emplace_back (0, 5);
testValues.emplace_back (4, 1);
testValues.emplace_back (5, 0);
for (auto p : testValues)
{
if (p.second > p.first)
std::rotate (referenceContainer.begin() + p.first,
referenceContainer.begin() + p.first + 1,
referenceContainer.begin() + p.second + 1);
else
std::rotate (referenceContainer.begin() + p.second,
referenceContainer.begin() + p.first,
referenceContainer.begin() + p.first + 1);
copyableContainer.move (p.first, p.second);
noncopyableContainer.move (p.first, p.second);
checkEqual (copyableContainer, noncopyableContainer, referenceContainer);
}
}
beginTest ("After converting move construction, ownership is transferred");
{
Derived obj;
ArrayBase<Derived*, DummyCriticalSection> derived;
derived.setAllocatedSize (5);
derived.add (&obj);
ArrayBase<Base*, DummyCriticalSection> base { std::move (derived) };
expectEquals (base.capacity(), 5);
expectEquals (base.size(), 1);
expect (base.getFirst() == &obj);
expectEquals (derived.capacity(), 0);
expectEquals (derived.size(), 0);
expect (derived.data() == nullptr);
}
beginTest ("After converting move assignment, ownership is transferred");
{
Derived obj;
ArrayBase<Derived*, DummyCriticalSection> derived;
derived.setAllocatedSize (5);
derived.add (&obj);
ArrayBase<Base*, DummyCriticalSection> base;
base = std::move (derived);
expectEquals (base.capacity(), 5);
expectEquals (base.size(), 1);
expect (base.getFirst() == &obj);
expectEquals (derived.capacity(), 0);
expectEquals (derived.size(), 0);
expect (derived.data() == nullptr);
}
}
private:
struct Base
{
virtual ~Base() = default;
};
struct Derived : Base
{
};
static void addData (std::vector<CopyableType>& referenceContainer,
ArrayBase<CopyableType, DummyCriticalSection>& copyableContainer,
ArrayBase<NoncopyableType, DummyCriticalSection>& NoncopyableContainer,
int numValues)
{
for (int i = 0; i < numValues; ++i)
{
referenceContainer.push_back ({ i });
copyableContainer.add ({ i });
NoncopyableContainer.add ({ i });
}
}
template <typename A, typename B>
void checkEqual (const ArrayBase<A, DummyCriticalSection>& a,
const ArrayBase<B, DummyCriticalSection>& b)
{
expectEquals ((int) a.size(), (int) b.size());
for (int i = 0; i < (int) a.size(); ++i)
expect (a[i] == b[i]);
}
template <typename A, typename B>
void checkEqual (ArrayBase<A, DummyCriticalSection>& a,
std::vector<B>& b)
{
expectEquals ((int) a.size(), (int) b.size());
for (int i = 0; i < (int) a.size(); ++i)
expect (a[i] == b[(size_t) i]);
}
template <typename A, typename B, typename C>
void checkEqual (ArrayBase<A, DummyCriticalSection>& a,
ArrayBase<B, DummyCriticalSection>& b,
std::vector<C>& c)
{
checkEqual (a, b);
checkEqual (a, c);
checkEqual (b, c);
}
};
static ArrayBaseTests arrayBaseTests;
#endif
} // namespace juce

View File

@ -0,0 +1,607 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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 basic object container.
This class isn't really for public use - it's used by the other
array classes, but might come in handy for some purposes.
It inherits from a critical section class to allow the arrays to use
the "empty base class optimisation" pattern to reduce their footprint.
@see Array, OwnedArray, ReferenceCountedArray
@tags{Core}
*/
template <class ElementType, class TypeOfCriticalSectionToUse>
class ArrayBase : public TypeOfCriticalSectionToUse
{
private:
using ParameterType = typename TypeHelpers::ParameterType<ElementType>::type;
template <class OtherElementType, class OtherCriticalSection>
using AllowConversion = typename std::enable_if<! std::is_same<std::tuple<ElementType, TypeOfCriticalSectionToUse>,
std::tuple<OtherElementType, OtherCriticalSection>>::value>::type;
public:
//==============================================================================
ArrayBase() = default;
~ArrayBase()
{
clear();
}
ArrayBase (ArrayBase&& other) noexcept
: elements (std::move (other.elements)),
numAllocated (other.numAllocated),
numUsed (other.numUsed)
{
other.numAllocated = 0;
other.numUsed = 0;
}
ArrayBase& operator= (ArrayBase&& other) noexcept
{
if (this != &other)
{
auto tmp (std::move (other));
swapWith (tmp);
}
return *this;
}
/** Converting move constructor.
Only enabled when the other array has a different type to this one.
If you see a compile error here, it's probably because you're attempting a conversion that
HeapBlock won't allow.
*/
template <class OtherElementType,
class OtherCriticalSection,
typename = AllowConversion<OtherElementType, OtherCriticalSection>>
ArrayBase (ArrayBase<OtherElementType, OtherCriticalSection>&& other) noexcept
: elements (std::move (other.elements)),
numAllocated (other.numAllocated),
numUsed (other.numUsed)
{
other.numAllocated = 0;
other.numUsed = 0;
}
/** Converting move assignment operator.
Only enabled when the other array has a different type to this one.
If you see a compile error here, it's probably because you're attempting a conversion that
HeapBlock won't allow.
*/
template <class OtherElementType,
class OtherCriticalSection,
typename = AllowConversion<OtherElementType, OtherCriticalSection>>
ArrayBase& operator= (ArrayBase<OtherElementType, OtherCriticalSection>&& other) noexcept
{
// No need to worry about assignment to *this, because 'other' must be of a different type.
elements = std::move (other.elements);
numAllocated = other.numAllocated;
numUsed = other.numUsed;
other.numAllocated = 0;
other.numUsed = 0;
return *this;
}
//==============================================================================
template <class OtherArrayType>
bool operator== (const OtherArrayType& other) const noexcept
{
if (size() != (int) other.size())
return false;
auto* e = begin();
for (auto& o : other)
if (! (*e++ == o))
return false;
return true;
}
template <class OtherArrayType>
bool operator!= (const OtherArrayType& other) const noexcept
{
return ! operator== (other);
}
//==============================================================================
inline ElementType& operator[] (const int index) noexcept
{
jassert (elements != nullptr);
jassert (isPositiveAndBelow (index, numUsed));
return elements[index];
}
inline const ElementType& operator[] (const int index) const noexcept
{
jassert (elements != nullptr);
jassert (isPositiveAndBelow (index, numUsed));
return elements[index];
}
inline ElementType getValueWithDefault (const int index) const noexcept
{
return isPositiveAndBelow (index, numUsed) ? elements[index] : ElementType();
}
inline ElementType getFirst() const noexcept
{
return numUsed > 0 ? elements[0] : ElementType();
}
inline ElementType getLast() const noexcept
{
return numUsed > 0 ? elements[numUsed - 1] : ElementType();
}
//==============================================================================
inline ElementType* begin() noexcept
{
return elements;
}
inline const ElementType* begin() const noexcept
{
return elements;
}
inline ElementType* end() noexcept
{
return elements + numUsed;
}
inline const ElementType* end() const noexcept
{
return elements + numUsed;
}
inline ElementType* data() noexcept
{
return elements;
}
inline const ElementType* data() const noexcept
{
return elements;
}
inline int size() const noexcept
{
return numUsed;
}
inline int capacity() const noexcept
{
return numAllocated;
}
//==============================================================================
void setAllocatedSize (int numElements)
{
jassert (numElements >= numUsed);
if (numAllocated != numElements)
{
if (numElements > 0)
setAllocatedSizeInternal (numElements);
else
elements.free();
}
numAllocated = numElements;
}
void ensureAllocatedSize (int minNumElements)
{
if (minNumElements > numAllocated)
setAllocatedSize ((minNumElements + minNumElements / 2 + 8) & ~7);
jassert (numAllocated <= 0 || elements != nullptr);
}
void shrinkToNoMoreThan (int maxNumElements)
{
if (maxNumElements < numAllocated)
setAllocatedSize (maxNumElements);
}
void clear()
{
for (int i = 0; i < numUsed; ++i)
elements[i].~ElementType();
numUsed = 0;
}
//==============================================================================
void swapWith (ArrayBase& other) noexcept
{
elements.swapWith (other.elements);
std::swap (numAllocated, other.numAllocated);
std::swap (numUsed, other.numUsed);
}
//==============================================================================
void add (const ElementType& newElement)
{
addImpl (newElement);
}
void add (ElementType&& newElement)
{
addImpl (std::move (newElement));
}
template <typename... OtherElements>
void add (const ElementType& firstNewElement, OtherElements&&... otherElements)
{
addImpl (firstNewElement, std::forward<OtherElements> (otherElements)...);
}
template <typename... OtherElements>
void add (ElementType&& firstNewElement, OtherElements&&... otherElements)
{
addImpl (std::move (firstNewElement), std::forward<OtherElements> (otherElements)...);
}
//==============================================================================
template <typename Type>
void addArray (const Type* elementsToAdd, int numElementsToAdd)
{
ensureAllocatedSize (numUsed + numElementsToAdd);
addArrayInternal (elementsToAdd, numElementsToAdd);
numUsed += numElementsToAdd;
}
template <typename TypeToCreateFrom>
void addArray (const std::initializer_list<TypeToCreateFrom>& items)
{
ensureAllocatedSize (numUsed + (int) items.size());
for (auto& item : items)
new (elements + numUsed++) ElementType (item);
}
template <class OtherArrayType>
void addArray (const OtherArrayType& arrayToAddFrom)
{
jassert ((const void*) this != (const void*) &arrayToAddFrom); // can't add from our own elements!
ensureAllocatedSize (numUsed + (int) arrayToAddFrom.size());
for (auto& e : arrayToAddFrom)
addAssumingCapacityIsReady (e);
}
template <class OtherArrayType>
typename std::enable_if<! std::is_pointer<OtherArrayType>::value, int>::type
addArray (const OtherArrayType& arrayToAddFrom,
int startIndex, int numElementsToAdd = -1)
{
jassert ((const void*) this != (const void*) &arrayToAddFrom); // can't add from our own elements!
if (startIndex < 0)
{
jassertfalse;
startIndex = 0;
}
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > (int) arrayToAddFrom.size())
numElementsToAdd = (int) arrayToAddFrom.size() - startIndex;
addArray (arrayToAddFrom.data() + startIndex, numElementsToAdd);
return numElementsToAdd;
}
//==============================================================================
void insert (int indexToInsertAt, ParameterType newElement, int numberOfTimesToInsertIt)
{
checkSourceIsNotAMember (newElement);
auto* space = createInsertSpace (indexToInsertAt, numberOfTimesToInsertIt);
for (int i = 0; i < numberOfTimesToInsertIt; ++i)
new (space++) ElementType (newElement);
numUsed += numberOfTimesToInsertIt;
}
void insertArray (int indexToInsertAt, const ElementType* newElements, int numberOfElements)
{
auto* space = createInsertSpace (indexToInsertAt, numberOfElements);
for (int i = 0; i < numberOfElements; ++i)
new (space++) ElementType (*(newElements++));
numUsed += numberOfElements;
}
//==============================================================================
void removeElements (int indexToRemoveAt, int numElementsToRemove)
{
jassert (indexToRemoveAt >= 0);
jassert (numElementsToRemove >= 0);
jassert (indexToRemoveAt + numElementsToRemove <= numUsed);
if (numElementsToRemove > 0)
{
removeElementsInternal (indexToRemoveAt, numElementsToRemove);
numUsed -= numElementsToRemove;
}
}
//==============================================================================
void swap (int index1, int index2)
{
if (isPositiveAndBelow (index1, numUsed)
&& isPositiveAndBelow (index2, numUsed))
{
std::swap (elements[index1],
elements[index2]);
}
}
//==============================================================================
void move (int currentIndex, int newIndex) noexcept
{
if (isPositiveAndBelow (currentIndex, numUsed))
{
if (! isPositiveAndBelow (newIndex, numUsed))
newIndex = numUsed - 1;
moveInternal (currentIndex, newIndex);
}
}
private:
//==============================================================================
template <typename T>
#if defined(__GNUC__) && __GNUC__ < 5 && ! defined(__clang__)
using IsTriviallyCopyable = std::is_scalar<T>;
#else
using IsTriviallyCopyable = std::is_trivially_copyable<T>;
#endif
template <typename T>
using TriviallyCopyableVoid = typename std::enable_if<IsTriviallyCopyable<T>::value, void>::type;
template <typename T>
using NonTriviallyCopyableVoid = typename std::enable_if<! IsTriviallyCopyable<T>::value, void>::type;
//==============================================================================
template <typename T = ElementType>
TriviallyCopyableVoid<T> addArrayInternal (const ElementType* otherElements, int numElements)
{
if (numElements > 0)
memcpy (elements + numUsed, otherElements, (size_t) numElements * sizeof (ElementType));
}
template <typename Type, typename T = ElementType>
TriviallyCopyableVoid<T> addArrayInternal (const Type* otherElements, int numElements)
{
auto* start = elements + numUsed;
while (--numElements >= 0)
new (start++) ElementType (*(otherElements++));
}
template <typename Type, typename T = ElementType>
NonTriviallyCopyableVoid<T> addArrayInternal (const Type* otherElements, int numElements)
{
auto* start = elements + numUsed;
while (--numElements >= 0)
new (start++) ElementType (*(otherElements++));
}
//==============================================================================
template <typename T = ElementType>
TriviallyCopyableVoid<T> setAllocatedSizeInternal (int numElements)
{
elements.realloc ((size_t) numElements);
}
template <typename T = ElementType>
NonTriviallyCopyableVoid<T> setAllocatedSizeInternal (int numElements)
{
HeapBlock<ElementType> newElements (numElements);
for (int i = 0; i < numUsed; ++i)
{
new (newElements + i) ElementType (std::move (elements[i]));
elements[i].~ElementType();
}
elements = std::move (newElements);
}
//==============================================================================
ElementType* createInsertSpace (int indexToInsertAt, int numElements)
{
ensureAllocatedSize (numUsed + numElements);
if (! isPositiveAndBelow (indexToInsertAt, numUsed))
return elements + numUsed;
createInsertSpaceInternal (indexToInsertAt, numElements);
return elements + indexToInsertAt;
}
template <typename T = ElementType>
TriviallyCopyableVoid<T> createInsertSpaceInternal (int indexToInsertAt, int numElements)
{
auto* start = elements + indexToInsertAt;
auto numElementsToShift = numUsed - indexToInsertAt;
memmove (start + numElements, start, (size_t) numElementsToShift * sizeof (ElementType));
}
template <typename T = ElementType>
NonTriviallyCopyableVoid<T> createInsertSpaceInternal (int indexToInsertAt, int numElements)
{
auto* end = elements + numUsed;
auto* newEnd = end + numElements;
auto numElementsToShift = numUsed - indexToInsertAt;
for (int i = 0; i < numElementsToShift; ++i)
{
new (--newEnd) ElementType (std::move (*(--end)));
end->~ElementType();
}
}
//==============================================================================
template <typename T = ElementType>
TriviallyCopyableVoid<T> removeElementsInternal (int indexToRemoveAt, int numElementsToRemove)
{
auto* start = elements + indexToRemoveAt;
auto numElementsToShift = numUsed - (indexToRemoveAt + numElementsToRemove);
memmove (start, start + numElementsToRemove, (size_t) numElementsToShift * sizeof (ElementType));
}
template <typename T = ElementType>
NonTriviallyCopyableVoid<T> removeElementsInternal (int indexToRemoveAt, int numElementsToRemove)
{
auto numElementsToShift = numUsed - (indexToRemoveAt + numElementsToRemove);
auto* destination = elements + indexToRemoveAt;
auto* source = destination + numElementsToRemove;
for (int i = 0; i < numElementsToShift; ++i)
moveAssignElement (destination++, std::move (*(source++)));
for (int i = 0; i < numElementsToRemove; ++i)
(destination++)->~ElementType();
}
//==============================================================================
template <typename T = ElementType>
TriviallyCopyableVoid<T> moveInternal (int currentIndex, int newIndex) noexcept
{
char tempCopy[sizeof (ElementType)];
memcpy (tempCopy, elements + currentIndex, sizeof (ElementType));
if (newIndex > currentIndex)
{
memmove (elements + currentIndex,
elements + currentIndex + 1,
(size_t) (newIndex - currentIndex) * sizeof (ElementType));
}
else
{
memmove (elements + newIndex + 1,
elements + newIndex,
(size_t) (currentIndex - newIndex) * sizeof (ElementType));
}
memcpy (elements + newIndex, tempCopy, sizeof (ElementType));
}
template <typename T = ElementType>
NonTriviallyCopyableVoid<T> moveInternal (int currentIndex, int newIndex) noexcept
{
auto* e = elements + currentIndex;
ElementType tempCopy (std::move (*e));
auto delta = newIndex - currentIndex;
if (delta > 0)
{
for (int i = 0; i < delta; ++i)
{
moveAssignElement (e, std::move (*(e + 1)));
++e;
}
}
else
{
for (int i = 0; i < -delta; ++i)
{
moveAssignElement (e, std::move (*(e - 1)));
--e;
}
}
moveAssignElement (e, std::move (tempCopy));
}
//==============================================================================
template <typename... Elements>
void addImpl (Elements&&... toAdd)
{
ignoreUnused (std::initializer_list<int> { (((void) checkSourceIsNotAMember (toAdd)), 0)... });
ensureAllocatedSize (numUsed + (int) sizeof... (toAdd));
addAssumingCapacityIsReady (std::forward<Elements> (toAdd)...);
}
template <typename... Elements>
void addAssumingCapacityIsReady (Elements&&... toAdd)
{
ignoreUnused (std::initializer_list<int> { ((void) (new (elements + numUsed++) ElementType (std::forward<Elements> (toAdd))), 0)... });
}
//==============================================================================
template <typename T = ElementType>
typename std::enable_if<std::is_move_assignable<T>::value, void>::type
moveAssignElement (ElementType* destination, ElementType&& source)
{
*destination = std::move (source);
}
template <typename T = ElementType>
typename std::enable_if<! std::is_move_assignable<T>::value, void>::type
moveAssignElement (ElementType* destination, ElementType&& source)
{
destination->~ElementType();
new (destination) ElementType (std::move (source));
}
void checkSourceIsNotAMember (const ElementType& element)
{
// when you pass a reference to an existing element into a method like add() which
// may need to reallocate the array to make more space, the incoming reference may
// be deleted indirectly during the reallocation operation! To work around this,
// make a local copy of the item you're trying to add (and maybe use std::move to
// move it into the add() method to avoid any extra overhead)
jassertquiet (std::addressof (element) < begin() || end() <= std::addressof (element));
}
//==============================================================================
HeapBlock<ElementType> elements;
int numAllocated = 0, numUsed = 0;
template <class OtherElementType, class OtherCriticalSection>
friend class ArrayBase;
JUCE_DECLARE_NON_COPYABLE (ArrayBase)
};
} // namespace juce

View File

@ -0,0 +1,132 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
DynamicObject::DynamicObject()
{
}
DynamicObject::DynamicObject (const DynamicObject& other)
: ReferenceCountedObject(), properties (other.properties)
{
}
DynamicObject::~DynamicObject()
{
}
bool DynamicObject::hasProperty (const Identifier& propertyName) const
{
const var* const v = properties.getVarPointer (propertyName);
return v != nullptr && ! v->isMethod();
}
const var& DynamicObject::getProperty (const Identifier& propertyName) const
{
return properties [propertyName];
}
void DynamicObject::setProperty (const Identifier& propertyName, const var& newValue)
{
properties.set (propertyName, newValue);
}
void DynamicObject::removeProperty (const Identifier& propertyName)
{
properties.remove (propertyName);
}
bool DynamicObject::hasMethod (const Identifier& methodName) const
{
return getProperty (methodName).isMethod();
}
var DynamicObject::invokeMethod (Identifier method, const var::NativeFunctionArgs& args)
{
if (auto function = properties [method].getNativeFunction())
return function (args);
return {};
}
void DynamicObject::setMethod (Identifier name, var::NativeFunction function)
{
properties.set (name, var (function));
}
void DynamicObject::clear()
{
properties.clear();
}
void DynamicObject::cloneAllProperties()
{
for (int i = properties.size(); --i >= 0;)
if (auto* v = properties.getVarPointerAt (i))
*v = v->clone();
}
DynamicObject::Ptr DynamicObject::clone()
{
Ptr d (new DynamicObject (*this));
d->cloneAllProperties();
return d;
}
void DynamicObject::writeAsJSON (OutputStream& out, const int indentLevel, const bool allOnOneLine, int maximumDecimalPlaces)
{
out << '{';
if (! allOnOneLine)
out << newLine;
const int numValues = properties.size();
for (int i = 0; i < numValues; ++i)
{
if (! allOnOneLine)
JSONFormatter::writeSpaces (out, indentLevel + JSONFormatter::indentSize);
out << '"';
JSONFormatter::writeString (out, properties.getName (i));
out << "\": ";
JSONFormatter::write (out, properties.getValueAt (i), indentLevel + JSONFormatter::indentSize, allOnOneLine, maximumDecimalPlaces);
if (i < numValues - 1)
{
if (allOnOneLine)
out << ", ";
else
out << ',' << newLine;
}
else if (! allOnOneLine)
out << newLine;
}
if (! allOnOneLine)
JSONFormatter::writeSpaces (out, indentLevel);
out << '}';
}
} // namespace juce

View File

@ -0,0 +1,127 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
//==============================================================================
/**
Represents a dynamically implemented object.
This class is primarily intended for wrapping scripting language objects,
but could be used for other purposes.
An instance of a DynamicObject can be used to store named properties, and
by subclassing hasMethod() and invokeMethod(), you can give your object
methods.
@tags{Core}
*/
class JUCE_API DynamicObject : public ReferenceCountedObject
{
public:
//==============================================================================
DynamicObject();
DynamicObject (const DynamicObject&);
~DynamicObject() override;
using Ptr = ReferenceCountedObjectPtr<DynamicObject>;
//==============================================================================
/** Returns true if the object has a property with this name.
Note that if the property is actually a method, this will return false.
*/
virtual bool hasProperty (const Identifier& propertyName) const;
/** Returns a named property.
This returns var() if no such property exists.
*/
virtual const var& getProperty (const Identifier& propertyName) const;
/** Sets a named property. */
virtual void setProperty (const Identifier& propertyName, const var& newValue);
/** Removes a named property. */
virtual void removeProperty (const Identifier& propertyName);
//==============================================================================
/** Checks whether this object has the specified method.
The default implementation of this just checks whether there's a property
with this name that's actually a method, but this can be overridden for
building objects with dynamic invocation.
*/
virtual bool hasMethod (const Identifier& methodName) const;
/** Invokes a named method on this object.
The default implementation looks up the named property, and if it's a method
call, then it invokes it.
This method is virtual to allow more dynamic invocation to used for objects
where the methods may not already be set as properties.
*/
virtual var invokeMethod (Identifier methodName,
const var::NativeFunctionArgs& args);
/** Adds a method to the class.
This is basically the same as calling setProperty (methodName, (var::NativeFunction) myFunction), but
helps to avoid accidentally invoking the wrong type of var constructor. It also makes
the code easier to read.
*/
void setMethod (Identifier methodName, var::NativeFunction function);
//==============================================================================
/** Removes all properties and methods from the object. */
void clear();
/** Returns the NamedValueSet that holds the object's properties. */
NamedValueSet& getProperties() noexcept { return properties; }
/** Calls var::clone() on all the properties that this object contains. */
void cloneAllProperties();
//==============================================================================
/** Returns a clone of this object.
The default implementation of this method just returns a new DynamicObject
with a (deep) copy of all of its properties. Subclasses can override this to
implement their own custom copy routines.
*/
virtual Ptr clone();
//==============================================================================
/** Writes this object to a text stream in JSON format.
This method is used by JSON::toString and JSON::writeToStream, and you should
never need to call it directly, but it's virtual so that custom object types
can stringify themselves appropriately.
*/
virtual void writeAsJSON (OutputStream&, int indentLevel, bool allOnOneLine, int maximumDecimalPlaces);
private:
//==============================================================================
NamedValueSet properties;
JUCE_LEAK_DETECTOR (DynamicObject)
};
} // namespace juce

View File

@ -0,0 +1,197 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
#ifndef DOXYGEN
/** This is an internal helper class which converts a juce ElementComparator style
class (using a "compareElements" method) into a class that's compatible with
std::sort (i.e. using an operator() to compare the elements)
@tags{Core}
*/
template <typename ElementComparator>
struct SortFunctionConverter
{
SortFunctionConverter (ElementComparator& e) : comparator (e) {}
template <typename Type>
bool operator() (Type a, Type b) { return comparator.compareElements (a, b) < 0; }
private:
ElementComparator& comparator;
SortFunctionConverter& operator= (const SortFunctionConverter&) = delete;
};
#endif
//==============================================================================
/**
Sorts a range of elements in an array.
The comparator object that is passed-in must define a public method with the following
signature:
@code
int compareElements (ElementType first, ElementType second);
@endcode
..and this method must return:
- a value of < 0 if the first comes before the second
- a value of 0 if the two objects are equivalent
- a value of > 0 if the second comes before the first
To improve performance, the compareElements() method can be declared as static or const.
@param comparator an object which defines a compareElements() method
@param array the array to sort
@param firstElement the index of the first element of the range to be sorted
@param lastElement the index of the last element in the range that needs
sorting (this is inclusive)
@param retainOrderOfEquivalentItems if true, the order of items that the
comparator deems the same will be maintained - this will be
a slower algorithm than if they are allowed to be moved around.
@see sortArrayRetainingOrder
*/
template <class ElementType, class ElementComparator>
static void sortArray (ElementComparator& comparator,
ElementType* const array,
int firstElement,
int lastElement,
const bool retainOrderOfEquivalentItems)
{
jassert (firstElement >= 0);
if (lastElement > firstElement)
{
SortFunctionConverter<ElementComparator> converter (comparator);
if (retainOrderOfEquivalentItems)
std::stable_sort (array + firstElement, array + lastElement + 1, converter);
else
std::sort (array + firstElement, array + lastElement + 1, converter);
}
}
//==============================================================================
/**
Searches a sorted array of elements, looking for the index at which a specified value
should be inserted for it to be in the correct order.
The comparator object that is passed-in must define a public method with the following
signature:
@code
int compareElements (ElementType first, ElementType second);
@endcode
..and this method must return:
- a value of < 0 if the first comes before the second
- a value of 0 if the two objects are equivalent
- a value of > 0 if the second comes before the first
To improve performance, the compareElements() method can be declared as static or const.
@param comparator an object which defines a compareElements() method
@param array the array to search
@param newElement the value that is going to be inserted
@param firstElement the index of the first element to search
@param lastElement the index of the last element in the range (this is non-inclusive)
*/
template <class ElementType, class ElementComparator>
static int findInsertIndexInSortedArray (ElementComparator& comparator,
ElementType* const array,
const ElementType newElement,
int firstElement,
int lastElement)
{
jassert (firstElement <= lastElement);
ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this
// avoids getting warning messages about the parameter being unused
while (firstElement < lastElement)
{
if (comparator.compareElements (newElement, array [firstElement]) == 0)
{
++firstElement;
break;
}
else
{
const int halfway = (firstElement + lastElement) >> 1;
if (halfway == firstElement)
{
if (comparator.compareElements (newElement, array [halfway]) >= 0)
++firstElement;
break;
}
else if (comparator.compareElements (newElement, array [halfway]) >= 0)
{
firstElement = halfway;
}
else
{
lastElement = halfway;
}
}
}
return firstElement;
}
//==============================================================================
/**
A simple ElementComparator class that can be used to sort an array of
objects that support the '<' operator.
This will work for primitive types and objects that implement operator<().
Example: @code
Array<int> myArray;
DefaultElementComparator<int> sorter;
myArray.sort (sorter);
@endcode
@see ElementComparator
@tags{Core}
*/
template <class ElementType>
class DefaultElementComparator
{
private:
using ParameterType = typename TypeHelpers::ParameterType<ElementType>::type;
public:
static int compareElements (ParameterType first, ParameterType second)
{
return (first < second) ? -1 : ((second < first) ? 1 : 0);
}
};
} // namespace juce

View File

@ -0,0 +1,506 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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 simple class to generate hash functions for some primitive types, intended for
use with the HashMap class.
@see HashMap
@tags{Core}
*/
struct DefaultHashFunctions
{
/** Generates a simple hash from an unsigned int. */
static int generateHash (uint32 key, int upperLimit) noexcept { return (int) (key % (uint32) upperLimit); }
/** Generates a simple hash from an integer. */
static int generateHash (int32 key, int upperLimit) noexcept { return generateHash ((uint32) key, upperLimit); }
/** Generates a simple hash from a uint64. */
static int generateHash (uint64 key, int upperLimit) noexcept { return (int) (key % (uint64) upperLimit); }
/** Generates a simple hash from an int64. */
static int generateHash (int64 key, int upperLimit) noexcept { return generateHash ((uint64) key, upperLimit); }
/** Generates a simple hash from a string. */
static int generateHash (const String& key, int upperLimit) noexcept { return generateHash ((uint32) key.hashCode(), upperLimit); }
/** Generates a simple hash from a variant. */
static int generateHash (const var& key, int upperLimit) noexcept { return generateHash (key.toString(), upperLimit); }
/** Generates a simple hash from a void ptr. */
static int generateHash (const void* key, int upperLimit) noexcept { return generateHash ((uint64) (pointer_sized_uint) key, upperLimit); }
/** Generates a simple hash from a UUID. */
static int generateHash (const Uuid& key, int upperLimit) noexcept { return generateHash (key.hash(), upperLimit); }
};
//==============================================================================
/**
Holds a set of mappings between some key/value pairs.
The types of the key and value objects are set as template parameters.
You can also specify a class to supply a hash function that converts a key value
into an hashed integer. This class must have the form:
@code
struct MyHashGenerator
{
int generateHash (MyKeyType key, int upperLimit) const
{
// The function must return a value 0 <= x < upperLimit
return someFunctionOfMyKeyType (key) % upperLimit;
}
};
@endcode
Like the Array class, the key and value types are expected to be copy-by-value
types, so if you define them to be pointer types, this class won't delete the
objects that they point to.
If you don't supply a class for the HashFunctionType template parameter, the
default one provides some simple mappings for strings and ints.
@code
HashMap<int, String> hash;
hash.set (1, "item1");
hash.set (2, "item2");
DBG (hash [1]); // prints "item1"
DBG (hash [2]); // prints "item2"
// This iterates the map, printing all of its key -> value pairs..
for (HashMap<int, String>::Iterator i (hash); i.next();)
DBG (i.getKey() << " -> " << i.getValue());
@endcode
@tparam HashFunctionType The class of hash function, which must be copy-constructible.
@see CriticalSection, DefaultHashFunctions, NamedValueSet, SortedSet
@tags{Core}
*/
template <typename KeyType,
typename ValueType,
class HashFunctionType = DefaultHashFunctions,
class TypeOfCriticalSectionToUse = DummyCriticalSection>
class HashMap
{
private:
using KeyTypeParameter = typename TypeHelpers::ParameterType<KeyType>::type;
using ValueTypeParameter = typename TypeHelpers::ParameterType<ValueType>::type;
public:
//==============================================================================
/** Creates an empty hash-map.
@param numberOfSlots Specifies the number of hash entries the map will use. This will be
the "upperLimit" parameter that is passed to your generateHash()
function. The number of hash slots will grow automatically if necessary,
or it can be remapped manually using remapTable().
@param hashFunction An instance of HashFunctionType, which will be copied and
stored to use with the HashMap. This parameter can be omitted
if HashFunctionType has a default constructor.
*/
explicit HashMap (int numberOfSlots = defaultHashTableSize,
HashFunctionType hashFunction = HashFunctionType())
: hashFunctionToUse (hashFunction)
{
hashSlots.insertMultiple (0, nullptr, numberOfSlots);
}
/** Destructor. */
~HashMap()
{
clear();
}
//==============================================================================
/** Removes all values from the map.
Note that this will clear the content, but won't affect the number of slots (see
remapTable and getNumSlots).
*/
void clear()
{
const ScopedLockType sl (getLock());
for (auto i = hashSlots.size(); --i >= 0;)
{
auto* h = hashSlots.getUnchecked(i);
while (h != nullptr)
{
const std::unique_ptr<HashEntry> deleter (h);
h = h->nextEntry;
}
hashSlots.set (i, nullptr);
}
totalNumItems = 0;
}
//==============================================================================
/** Returns the current number of items in the map. */
inline int size() const noexcept
{
return totalNumItems;
}
/** Returns the value corresponding to a given key.
If the map doesn't contain the key, a default instance of the value type is returned.
@param keyToLookFor the key of the item being requested
*/
inline ValueType operator[] (KeyTypeParameter keyToLookFor) const
{
const ScopedLockType sl (getLock());
if (auto* entry = getEntry (getSlot (keyToLookFor), keyToLookFor))
return entry->value;
return ValueType();
}
/** Returns a reference to the value corresponding to a given key.
If the map doesn't contain the key, a default instance of the value type is
added to the map and a reference to this is returned.
@param keyToLookFor the key of the item being requested
*/
inline ValueType& getReference (KeyTypeParameter keyToLookFor)
{
const ScopedLockType sl (getLock());
auto hashIndex = generateHashFor (keyToLookFor, getNumSlots());
auto* firstEntry = hashSlots.getUnchecked (hashIndex);
if (auto* entry = getEntry (firstEntry, keyToLookFor))
return entry->value;
auto* entry = new HashEntry (keyToLookFor, ValueType(), firstEntry);
hashSlots.set (hashIndex, entry);
++totalNumItems;
if (totalNumItems > (getNumSlots() * 3) / 2)
remapTable (getNumSlots() * 2);
return entry->value;
}
//==============================================================================
/** Returns true if the map contains an item with the specified key. */
bool contains (KeyTypeParameter keyToLookFor) const
{
const ScopedLockType sl (getLock());
return (getEntry (getSlot (keyToLookFor), keyToLookFor) != nullptr);
}
/** Returns true if the hash contains at least one occurrence of a given value. */
bool containsValue (ValueTypeParameter valueToLookFor) const
{
const ScopedLockType sl (getLock());
for (auto i = getNumSlots(); --i >= 0;)
for (auto* entry = hashSlots.getUnchecked(i); entry != nullptr; entry = entry->nextEntry)
if (entry->value == valueToLookFor)
return true;
return false;
}
//==============================================================================
/** Adds or replaces an element in the hash-map.
If there's already an item with the given key, this will replace its value. Otherwise, a new item
will be added to the map.
*/
void set (KeyTypeParameter newKey, ValueTypeParameter newValue) { getReference (newKey) = newValue; }
/** Removes an item with the given key. */
void remove (KeyTypeParameter keyToRemove)
{
const ScopedLockType sl (getLock());
auto hashIndex = generateHashFor (keyToRemove, getNumSlots());
auto* entry = hashSlots.getUnchecked (hashIndex);
HashEntry* previous = nullptr;
while (entry != nullptr)
{
if (entry->key == keyToRemove)
{
const std::unique_ptr<HashEntry> deleter (entry);
entry = entry->nextEntry;
if (previous != nullptr)
previous->nextEntry = entry;
else
hashSlots.set (hashIndex, entry);
--totalNumItems;
}
else
{
previous = entry;
entry = entry->nextEntry;
}
}
}
/** Removes all items with the given value. */
void removeValue (ValueTypeParameter valueToRemove)
{
const ScopedLockType sl (getLock());
for (auto i = getNumSlots(); --i >= 0;)
{
auto* entry = hashSlots.getUnchecked(i);
HashEntry* previous = nullptr;
while (entry != nullptr)
{
if (entry->value == valueToRemove)
{
const std::unique_ptr<HashEntry> deleter (entry);
entry = entry->nextEntry;
if (previous != nullptr)
previous->nextEntry = entry;
else
hashSlots.set (i, entry);
--totalNumItems;
}
else
{
previous = entry;
entry = entry->nextEntry;
}
}
}
}
/** Remaps the hash-map to use a different number of slots for its hash function.
Each slot corresponds to a single hash-code, and each one can contain multiple items.
@see getNumSlots()
*/
void remapTable (int newNumberOfSlots)
{
const ScopedLockType sl (getLock());
Array<HashEntry*> newSlots;
newSlots.insertMultiple (0, nullptr, newNumberOfSlots);
for (auto i = getNumSlots(); --i >= 0;)
{
HashEntry* nextEntry = nullptr;
for (auto* entry = hashSlots.getUnchecked(i); entry != nullptr; entry = nextEntry)
{
auto hashIndex = generateHashFor (entry->key, newNumberOfSlots);
nextEntry = entry->nextEntry;
entry->nextEntry = newSlots.getUnchecked (hashIndex);
newSlots.set (hashIndex, entry);
}
}
hashSlots.swapWith (newSlots);
}
/** Returns the number of slots which are available for hashing.
Each slot corresponds to a single hash-code, and each one can contain multiple items.
@see getNumSlots()
*/
inline int getNumSlots() const noexcept
{
return hashSlots.size();
}
//==============================================================================
/** Efficiently swaps the contents of two hash-maps. */
template <class OtherHashMapType>
void swapWith (OtherHashMapType& otherHashMap) noexcept
{
const ScopedLockType lock1 (getLock());
const typename OtherHashMapType::ScopedLockType lock2 (otherHashMap.getLock());
hashSlots.swapWith (otherHashMap.hashSlots);
std::swap (totalNumItems, otherHashMap.totalNumItems);
}
//==============================================================================
/** Returns the CriticalSection that locks this structure.
To lock, you can call getLock().enter() and getLock().exit(), or preferably use
an object of ScopedLockType as an RAII lock for it.
*/
inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return lock; }
/** Returns the type of scoped lock to use for locking this array */
using ScopedLockType = typename TypeOfCriticalSectionToUse::ScopedLockType;
private:
//==============================================================================
class HashEntry
{
public:
HashEntry (KeyTypeParameter k, ValueTypeParameter val, HashEntry* const next)
: key (k), value (val), nextEntry (next)
{}
const KeyType key;
ValueType value;
HashEntry* nextEntry;
JUCE_DECLARE_NON_COPYABLE (HashEntry)
};
public:
//==============================================================================
/** Iterates over the items in a HashMap.
To use it, repeatedly call next() until it returns false, e.g.
@code
HashMap <String, String> myMap;
HashMap<String, String>::Iterator i (myMap);
while (i.next())
{
DBG (i.getKey() << " -> " << i.getValue());
}
@endcode
The order in which items are iterated bears no resemblance to the order in which
they were originally added!
Obviously as soon as you call any non-const methods on the original hash-map, any
iterators that were created beforehand will cease to be valid, and should not be used.
@see HashMap
*/
struct Iterator
{
Iterator (const HashMap& hashMapToIterate) noexcept
: hashMap (hashMapToIterate), entry (nullptr), index (0)
{}
Iterator (const Iterator& other) noexcept
: hashMap (other.hashMap), entry (other.entry), index (other.index)
{}
/** Moves to the next item, if one is available.
When this returns true, you can get the item's key and value using getKey() and
getValue(). If it returns false, the iteration has finished and you should stop.
*/
bool next() noexcept
{
if (entry != nullptr)
entry = entry->nextEntry;
while (entry == nullptr)
{
if (index >= hashMap.getNumSlots())
return false;
entry = hashMap.hashSlots.getUnchecked (index++);
}
return true;
}
/** Returns the current item's key.
This should only be called when a call to next() has just returned true.
*/
KeyType getKey() const
{
return entry != nullptr ? entry->key : KeyType();
}
/** Returns the current item's value.
This should only be called when a call to next() has just returned true.
*/
ValueType getValue() const
{
return entry != nullptr ? entry->value : ValueType();
}
/** Resets the iterator to its starting position. */
void reset() noexcept
{
entry = nullptr;
index = 0;
}
Iterator& operator++() noexcept { next(); return *this; }
ValueType operator*() const { return getValue(); }
bool operator!= (const Iterator& other) const noexcept { return entry != other.entry || index != other.index; }
void resetToEnd() noexcept { index = hashMap.getNumSlots(); }
private:
//==============================================================================
const HashMap& hashMap;
HashEntry* entry;
int index;
// using the copy constructor is ok, but you cannot assign iterators
Iterator& operator= (const Iterator&) = delete;
JUCE_LEAK_DETECTOR (Iterator)
};
/** Returns a start iterator for the values in this tree. */
Iterator begin() const noexcept { Iterator i (*this); i.next(); return i; }
/** Returns an end iterator for the values in this tree. */
Iterator end() const noexcept { Iterator i (*this); i.resetToEnd(); return i; }
private:
//==============================================================================
enum { defaultHashTableSize = 101 };
friend struct Iterator;
HashFunctionType hashFunctionToUse;
Array<HashEntry*> hashSlots;
int totalNumItems = 0;
TypeOfCriticalSectionToUse lock;
int generateHashFor (KeyTypeParameter key, int numSlots) const
{
const int hash = hashFunctionToUse.generateHash (key, numSlots);
jassert (isPositiveAndBelow (hash, numSlots)); // your hash function is generating out-of-range numbers!
return hash;
}
static HashEntry* getEntry (HashEntry* firstEntry, KeyType keyToLookFor) noexcept
{
for (auto* entry = firstEntry; entry != nullptr; entry = entry->nextEntry)
if (entry->key == keyToLookFor)
return entry;
return nullptr;
}
inline HashEntry* getSlot (KeyType key) const noexcept { return hashSlots.getUnchecked (generateHashFor (key, getNumSlots())); }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HashMap)
};
} // namespace juce

View File

@ -0,0 +1,278 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
struct HashMapTest : public UnitTest
{
HashMapTest()
: UnitTest ("HashMap", UnitTestCategories::containers)
{}
void runTest() override
{
doTest<AddElementsTest> ("AddElementsTest");
doTest<AccessTest> ("AccessTest");
doTest<RemoveTest> ("RemoveTest");
doTest<PersistantMemoryLocationOfValues> ("PersistantMemoryLocationOfValues");
}
//==============================================================================
struct AddElementsTest
{
template <typename KeyType>
static void run (UnitTest& u)
{
AssociativeMap<KeyType, int> groundTruth;
HashMap<KeyType, int> hashMap;
RandomKeys<KeyType> keyOracle (300, 3827829);
Random valueOracle (48735);
int totalValues = 0;
for (int i = 0; i < 10000; ++i)
{
auto key = keyOracle.next();
auto value = valueOracle.nextInt();
bool contains = (groundTruth.find (key) != nullptr);
u.expectEquals ((int) contains, (int) hashMap.contains (key));
groundTruth.add (key, value);
hashMap.set (key, value);
if (! contains) totalValues++;
u.expectEquals (hashMap.size(), totalValues);
}
}
};
struct AccessTest
{
template <typename KeyType>
static void run (UnitTest& u)
{
AssociativeMap<KeyType, int> groundTruth;
HashMap<KeyType, int> hashMap;
fillWithRandomValues (hashMap, groundTruth);
for (auto pair : groundTruth.pairs)
u.expectEquals (hashMap[pair.key], pair.value);
}
};
struct RemoveTest
{
template <typename KeyType>
static void run (UnitTest& u)
{
AssociativeMap<KeyType, int> groundTruth;
HashMap<KeyType, int> hashMap;
fillWithRandomValues (hashMap, groundTruth);
auto n = groundTruth.size();
Random r (3827387);
for (int i = 0; i < 100; ++i)
{
auto idx = r.nextInt (n-- - 1);
auto key = groundTruth.pairs.getReference (idx).key;
groundTruth.pairs.remove (idx);
hashMap.remove (key);
u.expect (! hashMap.contains (key));
for (auto pair : groundTruth.pairs)
u.expectEquals (hashMap[pair.key], pair.value);
}
}
};
// ensure that the addresses of object references don't change
struct PersistantMemoryLocationOfValues
{
struct AddressAndValue { int value; const int* valueAddress; };
template <typename KeyType>
static void run (UnitTest& u)
{
AssociativeMap<KeyType, AddressAndValue> groundTruth;
HashMap<KeyType, int> hashMap;
RandomKeys<KeyType> keyOracle (300, 3827829);
Random valueOracle (48735);
for (int i = 0; i < 1000; ++i)
{
auto key = keyOracle.next();
auto value = valueOracle.nextInt();
hashMap.set (key, value);
if (auto* existing = groundTruth.find (key))
{
// don't change the address: only the value
existing->value = value;
}
else
{
groundTruth.add (key, { value, &hashMap.getReference (key) });
}
for (auto pair : groundTruth.pairs)
{
const auto& hashMapValue = hashMap.getReference (pair.key);
u.expectEquals (hashMapValue, pair.value.value);
u.expect (&hashMapValue == pair.value.valueAddress);
}
}
auto n = groundTruth.size();
Random r (3827387);
for (int i = 0; i < 100; ++i)
{
auto idx = r.nextInt (n-- - 1);
auto key = groundTruth.pairs.getReference (idx).key;
groundTruth.pairs.remove (idx);
hashMap.remove (key);
for (auto pair : groundTruth.pairs)
{
const auto& hashMapValue = hashMap.getReference (pair.key);
u.expectEquals (hashMapValue, pair.value.value);
u.expect (&hashMapValue == pair.value.valueAddress);
}
}
}
};
//==============================================================================
template <class Test>
void doTest (const String& testName)
{
beginTest (testName);
Test::template run<int> (*this);
Test::template run<void*> (*this);
Test::template run<String> (*this);
}
//==============================================================================
template <typename KeyType, typename ValueType>
struct AssociativeMap
{
struct KeyValuePair { KeyType key; ValueType value; };
ValueType* find (KeyType key)
{
auto n = pairs.size();
for (int i = 0; i < n; ++i)
{
auto& pair = pairs.getReference (i);
if (pair.key == key)
return &pair.value;
}
return nullptr;
}
void add (KeyType key, ValueType value)
{
if (ValueType* v = find (key))
*v = value;
else
pairs.add ({key, value});
}
int size() const { return pairs.size(); }
Array<KeyValuePair> pairs;
};
template <typename KeyType, typename ValueType>
static void fillWithRandomValues (HashMap<KeyType, int>& hashMap, AssociativeMap<KeyType, ValueType>& groundTruth)
{
RandomKeys<KeyType> keyOracle (300, 3827829);
Random valueOracle (48735);
for (int i = 0; i < 10000; ++i)
{
auto key = keyOracle.next();
auto value = valueOracle.nextInt();
groundTruth.add (key, value);
hashMap.set (key, value);
}
}
//==============================================================================
template <typename KeyType>
class RandomKeys
{
public:
RandomKeys (int maxUniqueKeys, int seed) : r (seed)
{
for (int i = 0; i < maxUniqueKeys; ++i)
keys.add (generateRandomKey (r));
}
const KeyType& next()
{
int i = r.nextInt (keys.size() - 1);
return keys.getReference (i);
}
private:
static KeyType generateRandomKey (Random&);
Random r;
Array<KeyType> keys;
};
};
template <> int HashMapTest::RandomKeys<int> ::generateRandomKey (Random& rnd) { return rnd.nextInt(); }
template <> void* HashMapTest::RandomKeys<void*>::generateRandomKey (Random& rnd) { return reinterpret_cast<void*> (rnd.nextInt64()); }
template <> String HashMapTest::RandomKeys<String>::generateRandomKey (Random& rnd)
{
String str;
int len = rnd.nextInt (8)+1;
for (int i = 0; i < len; ++i)
str += static_cast<char> (rnd.nextInt (95) + 32);
return str;
}
static HashMapTest hashMapTest;
} // namespace juce

View File

@ -0,0 +1,370 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
//==============================================================================
/**
Helps to manipulate singly-linked lists of objects.
For objects that are designed to contain a pointer to the subsequent item in the
list, this class contains methods to deal with the list. To use it, the ObjectType
class that it points to must contain a LinkedListPointer called nextListItem, e.g.
@code
struct MyObject
{
int x, y, z;
// A linkable object must contain a member with this name and type, which must be
// accessible by the LinkedListPointer class. (This doesn't mean it has to be public -
// you could make your class a friend of a LinkedListPointer<MyObject> instead).
LinkedListPointer<MyObject> nextListItem;
};
LinkedListPointer<MyObject> myList;
myList.append (new MyObject());
myList.append (new MyObject());
int numItems = myList.size(); // returns 2
MyObject* lastInList = myList.getLast();
@endcode
@tags{Core}
*/
template <class ObjectType>
class LinkedListPointer
{
public:
//==============================================================================
/** Creates a null pointer to an empty list. */
LinkedListPointer() noexcept
: item (nullptr)
{
}
/** Creates a pointer to a list whose head is the item provided. */
explicit LinkedListPointer (ObjectType* const headItem) noexcept
: item (headItem)
{
}
/** Sets this pointer to point to a new list. */
LinkedListPointer& operator= (ObjectType* const newItem) noexcept
{
item = newItem;
return *this;
}
LinkedListPointer (LinkedListPointer&& other) noexcept
: item (other.item)
{
other.item = nullptr;
}
LinkedListPointer& operator= (LinkedListPointer&& other) noexcept
{
jassert (this != &other); // hopefully the compiler should make this situation impossible!
item = other.item;
other.item = nullptr;
return *this;
}
//==============================================================================
/** Returns the item which this pointer points to. */
inline operator ObjectType*() const noexcept
{
return item;
}
/** Returns the item which this pointer points to. */
inline ObjectType* get() const noexcept
{
return item;
}
/** Returns the last item in the list which this pointer points to.
This will iterate the list and return the last item found. Obviously the speed
of this operation will be proportional to the size of the list. If the list is
empty the return value will be this object.
If you're planning on appending a number of items to your list, it's much more
efficient to use the Appender class than to repeatedly call getLast() to find the end.
*/
LinkedListPointer& getLast() noexcept
{
auto* l = this;
while (l->item != nullptr)
l = &(l->item->nextListItem);
return *l;
}
/** Returns the number of items in the list.
Obviously with a simple linked list, getting the size involves iterating the list, so
this can be a lengthy operation - be careful when using this method in your code.
*/
int size() const noexcept
{
int total = 0;
for (auto* i = item; i != nullptr; i = i->nextListItem)
++total;
return total;
}
/** Returns the item at a given index in the list.
Since the only way to find an item is to iterate the list, this operation can obviously
be slow, depending on its size, so you should be careful when using this in algorithms.
*/
LinkedListPointer& operator[] (int index) noexcept
{
auto* l = this;
while (--index >= 0 && l->item != nullptr)
l = &(l->item->nextListItem);
return *l;
}
/** Returns the item at a given index in the list.
Since the only way to find an item is to iterate the list, this operation can obviously
be slow, depending on its size, so you should be careful when using this in algorithms.
*/
const LinkedListPointer& operator[] (int index) const noexcept
{
auto* l = this;
while (--index >= 0 && l->item != nullptr)
l = &(l->item->nextListItem);
return *l;
}
/** Returns true if the list contains the given item. */
bool contains (const ObjectType* const itemToLookFor) const noexcept
{
for (auto* i = item; i != nullptr; i = i->nextListItem)
if (itemToLookFor == i)
return true;
return false;
}
//==============================================================================
/** Inserts an item into the list, placing it before the item that this pointer
currently points to.
*/
void insertNext (ObjectType* const newItem)
{
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011)
jassert (newItem != nullptr);
jassert (newItem->nextListItem == nullptr);
newItem->nextListItem = item;
item = newItem;
JUCE_END_IGNORE_WARNINGS_MSVC
}
/** Inserts an item at a numeric index in the list.
Obviously this will involve iterating the list to find the item at the given index,
so be careful about the impact this may have on execution time.
*/
void insertAtIndex (int index, ObjectType* newItem)
{
jassert (newItem != nullptr);
auto* l = this;
while (index != 0 && l->item != nullptr)
{
l = &(l->item->nextListItem);
--index;
}
l->insertNext (newItem);
}
/** Replaces the object that this pointer points to, appending the rest of the list to
the new object, and returning the old one.
*/
ObjectType* replaceNext (ObjectType* const newItem) noexcept
{
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011 28182)
jassert (newItem != nullptr);
jassert (newItem->nextListItem == nullptr);
auto oldItem = item;
item = newItem;
item->nextListItem = oldItem->nextListItem.item;
oldItem->nextListItem.item = nullptr;
return oldItem;
JUCE_END_IGNORE_WARNINGS_MSVC
}
/** Adds an item to the end of the list.
This operation involves iterating the whole list, so can be slow - if you need to
append a number of items to your list, it's much more efficient to use the Appender
class than to repeatedly call append().
*/
void append (ObjectType* const newItem)
{
getLast().item = newItem;
}
/** Creates copies of all the items in another list and adds them to this one.
This will use the ObjectType's copy constructor to try to create copies of each
item in the other list, and appends them to this list.
*/
void addCopyOfList (const LinkedListPointer& other)
{
auto* insertPoint = this;
for (auto* i = other.item; i != nullptr; i = i->nextListItem)
{
insertPoint->insertNext (new ObjectType (*i));
insertPoint = &(insertPoint->item->nextListItem);
}
}
/** Removes the head item from the list.
This won't delete the object that is removed, but returns it, so the caller can
delete it if necessary.
*/
ObjectType* removeNext() noexcept
{
auto oldItem = item;
if (oldItem != nullptr)
{
item = oldItem->nextListItem;
oldItem->nextListItem.item = nullptr;
}
return oldItem;
}
/** Removes a specific item from the list.
Note that this will not delete the item, it simply unlinks it from the list.
*/
void remove (ObjectType* const itemToRemove)
{
if (auto* l = findPointerTo (itemToRemove))
l->removeNext();
}
/** Iterates the list, calling the delete operator on all of its elements and
leaving this pointer empty.
*/
void deleteAll()
{
while (item != nullptr)
{
auto oldItem = item;
item = oldItem->nextListItem;
delete oldItem;
}
}
/** Finds a pointer to a given item.
If the item is found in the list, this returns the pointer that points to it. If
the item isn't found, this returns null.
*/
LinkedListPointer* findPointerTo (ObjectType* const itemToLookFor) noexcept
{
auto* l = this;
while (l->item != nullptr)
{
if (l->item == itemToLookFor)
return l;
l = &(l->item->nextListItem);
}
return nullptr;
}
/** Copies the items in the list to an array.
The destArray must contain enough elements to hold the entire list - no checks are
made for this!
*/
void copyToArray (ObjectType** destArray) const noexcept
{
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011)
jassert (destArray != nullptr);
for (auto* i = item; i != nullptr; i = i->nextListItem)
*destArray++ = i;
JUCE_END_IGNORE_WARNINGS_MSVC
}
/** Swaps this pointer with another one */
void swapWith (LinkedListPointer& other) noexcept
{
std::swap (item, other.item);
}
//==============================================================================
/**
Allows efficient repeated insertions into a list.
You can create an Appender object which points to the last element in your
list, and then repeatedly call Appender::append() to add items to the end
of the list in O(1) time.
*/
class Appender
{
public:
/** Creates an appender which will add items to the given list.
*/
Appender (LinkedListPointer& endOfListPointer) noexcept
: endOfList (&endOfListPointer)
{
// This can only be used to add to the end of a list.
jassert (endOfListPointer.item == nullptr);
}
/** Appends an item to the list. */
void append (ObjectType* const newItem) noexcept
{
*endOfList = newItem;
endOfList = &(newItem->nextListItem);
}
private:
LinkedListPointer* endOfList;
JUCE_DECLARE_NON_COPYABLE (Appender)
};
private:
//==============================================================================
ObjectType* item;
JUCE_DECLARE_NON_COPYABLE (LinkedListPointer)
};
} // namespace juce

View File

@ -0,0 +1,313 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
//==============================================================================
/**
Holds a set of objects and can invoke a member function callback on each object
in the set with a single call.
Use a ListenerList to manage a set of objects which need a callback, and you
can invoke a member function by simply calling call() or callChecked().
E.g.
@code
class MyListenerType
{
public:
void myCallbackMethod (int foo, bool bar);
};
ListenerList<MyListenerType> listeners;
listeners.add (someCallbackObjects...);
// This will invoke myCallbackMethod (1234, true) on each of the objects
// in the list...
listeners.call ([] (MyListenerType& l) { l.myCallbackMethod (1234, true); });
@endcode
If you add or remove listeners from the list during one of the callbacks - i.e. while
it's in the middle of iterating the listeners, then it's guaranteed that no listeners
will be mistakenly called after they've been removed, but it may mean that some of the
listeners could be called more than once, or not at all, depending on the list's order.
Sometimes, there's a chance that invoking one of the callbacks might result in the
list itself being deleted while it's still iterating - to survive this situation, you can
use callChecked() instead of call(), passing it a local object to act as a "BailOutChecker".
The BailOutChecker must implement a method of the form "bool shouldBailOut()", and
the list will check this after each callback to determine whether it should abort the
operation. For an example of a bail-out checker, see the Component::BailOutChecker class,
which can be used to check when a Component has been deleted. See also
ListenerList::DummyBailOutChecker, which is a dummy checker that always returns false.
@tags{Core}
*/
template <class ListenerClass,
class ArrayType = Array<ListenerClass*>>
class ListenerList
{
public:
//==============================================================================
/** Creates an empty list. */
ListenerList() = default;
/** Destructor. */
~ListenerList() = default;
//==============================================================================
/** Adds a listener to the list.
A listener can only be added once, so if the listener is already in the list,
this method has no effect.
@see remove
*/
void add (ListenerClass* listenerToAdd)
{
if (listenerToAdd != nullptr)
listeners.addIfNotAlreadyThere (listenerToAdd);
else
jassertfalse; // Listeners can't be null pointers!
}
/** Removes a listener from the list.
If the listener wasn't in the list, this has no effect.
*/
void remove (ListenerClass* listenerToRemove)
{
jassert (listenerToRemove != nullptr); // Listeners can't be null pointers!
listeners.removeFirstMatchingValue (listenerToRemove);
}
/** Returns the number of registered listeners. */
int size() const noexcept { return listeners.size(); }
/** Returns true if no listeners are registered, false otherwise. */
bool isEmpty() const noexcept { return listeners.isEmpty(); }
/** Clears the list. */
void clear() { listeners.clear(); }
/** Returns true if the specified listener has been added to the list. */
bool contains (ListenerClass* listener) const noexcept { return listeners.contains (listener); }
/** Returns the raw array of listeners. */
const ArrayType& getListeners() const noexcept { return listeners; }
//==============================================================================
/** Calls a member function on each listener in the list, with multiple parameters. */
template <typename Callback>
void call (Callback&& callback)
{
typename ArrayType::ScopedLockType lock (listeners.getLock());
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();)
callback (*iter.getListener());
}
/** Calls a member function with 1 parameter, on all but the specified listener in the list.
This can be useful if the caller is also a listener and needs to exclude itself.
*/
template <typename Callback>
void callExcluding (ListenerClass* listenerToExclude, Callback&& callback)
{
typename ArrayType::ScopedLockType lock (listeners.getLock());
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();)
{
auto* l = iter.getListener();
if (l != listenerToExclude)
callback (*l);
}
}
/** Calls a member function on each listener in the list, with 1 parameter and a bail-out-checker.
See the class description for info about writing a bail-out checker.
*/
template <typename Callback, typename BailOutCheckerType>
void callChecked (const BailOutCheckerType& bailOutChecker, Callback&& callback)
{
typename ArrayType::ScopedLockType lock (listeners.getLock());
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);)
callback (*iter.getListener());
}
/** Calls a member function, with 1 parameter, on all but the specified listener in the list
with a bail-out-checker. This can be useful if the caller is also a listener and needs to
exclude itself. See the class description for info about writing a bail-out checker.
*/
template <typename Callback, typename BailOutCheckerType>
void callCheckedExcluding (ListenerClass* listenerToExclude,
const BailOutCheckerType& bailOutChecker,
Callback&& callback)
{
typename ArrayType::ScopedLockType lock (listeners.getLock());
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);)
{
auto* l = iter.getListener();
if (l != listenerToExclude)
callback (*l);
}
}
//==============================================================================
/** A dummy bail-out checker that always returns false.
See the ListenerList notes for more info about bail-out checkers.
*/
struct DummyBailOutChecker
{
bool shouldBailOut() const noexcept { return false; }
};
using ThisType = ListenerList<ListenerClass, ArrayType>;
using ListenerType = ListenerClass;
//==============================================================================
/** Iterates the listeners in a ListenerList. */
template <class BailOutCheckerType, class ListType>
struct Iterator
{
Iterator (const ListType& listToIterate) noexcept
: list (listToIterate), index (listToIterate.size())
{}
~Iterator() = default;
//==============================================================================
bool next() noexcept
{
if (index <= 0)
return false;
auto listSize = list.size();
if (--index < listSize)
return true;
index = listSize - 1;
return index >= 0;
}
bool next (const BailOutCheckerType& bailOutChecker) noexcept
{
return (! bailOutChecker.shouldBailOut()) && next();
}
typename ListType::ListenerType* getListener() const noexcept
{
return list.getListeners().getUnchecked (index);
}
//==============================================================================
private:
const ListType& list;
int index;
JUCE_DECLARE_NON_COPYABLE (Iterator)
};
//==============================================================================
#ifndef DOXYGEN
// There are now lambda-based call functions that can be used to replace these old method-based versions.
// We'll eventually deprecate these old ones, so please begin moving your code to use lambdas!
void call (void (ListenerClass::*callbackFunction) ())
{
call ([=] (ListenerClass& l) { (l.*callbackFunction)(); });
}
void callExcluding (ListenerClass* listenerToExclude, void (ListenerClass::*callbackFunction) ())
{
callExcluding (listenerToExclude, [=] (ListenerClass& l) { (l.*callbackFunction)(); });
}
template <class BailOutCheckerType>
void callChecked (const BailOutCheckerType& bailOutChecker, void (ListenerClass::*callbackFunction) ())
{
callChecked (bailOutChecker, [=] (ListenerClass& l) { (l.*callbackFunction)(); });
}
template <class BailOutCheckerType>
void callCheckedExcluding (ListenerClass* listenerToExclude,
const BailOutCheckerType& bailOutChecker,
void (ListenerClass::*callbackFunction) ())
{
callCheckedExcluding (listenerToExclude, bailOutChecker, [=] (ListenerClass& l) { (l.*callbackFunction)(); });
}
template <typename... MethodArgs, typename... Args>
void call (void (ListenerClass::*callbackFunction) (MethodArgs...), Args&&... args)
{
typename ArrayType::ScopedLockType lock (listeners.getLock());
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();)
(iter.getListener()->*callbackFunction) (static_cast<typename TypeHelpers::ParameterType<Args>::type> (args)...);
}
template <typename... MethodArgs, typename... Args>
void callExcluding (ListenerClass* listenerToExclude,
void (ListenerClass::*callbackFunction) (MethodArgs...),
Args&&... args)
{
typename ArrayType::ScopedLockType lock (listeners.getLock());
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();)
if (iter.getListener() != listenerToExclude)
(iter.getListener()->*callbackFunction) (static_cast<typename TypeHelpers::ParameterType<Args>::type> (args)...);
}
template <typename BailOutCheckerType, typename... MethodArgs, typename... Args>
void callChecked (const BailOutCheckerType& bailOutChecker,
void (ListenerClass::*callbackFunction) (MethodArgs...),
Args&&... args)
{
typename ArrayType::ScopedLockType lock (listeners.getLock());
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);)
(iter.getListener()->*callbackFunction) (static_cast<typename TypeHelpers::ParameterType<Args>::type> (args)...);
}
template <typename BailOutCheckerType, typename... MethodArgs, typename... Args>
void callCheckedExcluding (ListenerClass* listenerToExclude,
const BailOutCheckerType& bailOutChecker,
void (ListenerClass::*callbackFunction) (MethodArgs...),
Args&&... args)
{
typename ArrayType::ScopedLockType lock (listeners.getLock());
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);)
if (iter.getListener() != listenerToExclude)
(iter.getListener()->*callbackFunction) (static_cast<typename TypeHelpers::ParameterType<Args>::type> (args)...);
}
#endif
private:
//==============================================================================
ArrayType listeners;
JUCE_DECLARE_NON_COPYABLE (ListenerList)
};
} // namespace juce

View File

@ -0,0 +1,306 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
NamedValueSet::NamedValue::NamedValue() noexcept {}
NamedValueSet::NamedValue::~NamedValue() noexcept {}
NamedValueSet::NamedValue::NamedValue (const Identifier& n, const var& v) : name (n), value (v) {}
NamedValueSet::NamedValue::NamedValue (const NamedValue& other) : NamedValue (other.name, other.value) {}
NamedValueSet::NamedValue::NamedValue (NamedValue&& other) noexcept
: NamedValue (std::move (other.name),
std::move (other.value))
{}
NamedValueSet::NamedValue::NamedValue (const Identifier& n, var&& v) noexcept
: name (n), value (std::move (v))
{
}
NamedValueSet::NamedValue::NamedValue (Identifier&& n, var&& v) noexcept
: name (std::move (n)),
value (std::move (v))
{}
NamedValueSet::NamedValue& NamedValueSet::NamedValue::operator= (NamedValue&& other) noexcept
{
name = std::move (other.name);
value = std::move (other.value);
return *this;
}
bool NamedValueSet::NamedValue::operator== (const NamedValue& other) const noexcept { return name == other.name && value == other.value; }
bool NamedValueSet::NamedValue::operator!= (const NamedValue& other) const noexcept { return ! operator== (other); }
//==============================================================================
NamedValueSet::NamedValueSet() noexcept {}
NamedValueSet::~NamedValueSet() noexcept {}
NamedValueSet::NamedValueSet (const NamedValueSet& other) : values (other.values) {}
NamedValueSet::NamedValueSet (NamedValueSet&& other) noexcept
: values (std::move (other.values)) {}
NamedValueSet::NamedValueSet (std::initializer_list<NamedValue> list)
: values (std::move (list))
{
}
NamedValueSet& NamedValueSet::operator= (const NamedValueSet& other)
{
clear();
values = other.values;
return *this;
}
NamedValueSet& NamedValueSet::operator= (NamedValueSet&& other) noexcept
{
other.values.swapWith (values);
return *this;
}
void NamedValueSet::clear()
{
values.clear();
}
bool NamedValueSet::operator== (const NamedValueSet& other) const noexcept
{
auto num = values.size();
if (num != other.values.size())
return false;
for (int i = 0; i < num; ++i)
{
// optimise for the case where the keys are in the same order
if (values.getReference(i).name == other.values.getReference(i).name)
{
if (values.getReference(i).value != other.values.getReference(i).value)
return false;
}
else
{
// if we encounter keys that are in a different order, search remaining items by brute force..
for (int j = i; j < num; ++j)
{
if (auto* otherVal = other.getVarPointer (values.getReference(j).name))
if (values.getReference(j).value == *otherVal)
continue;
return false;
}
return true;
}
}
return true;
}
bool NamedValueSet::operator!= (const NamedValueSet& other) const noexcept { return ! operator== (other); }
int NamedValueSet::size() const noexcept { return values.size(); }
bool NamedValueSet::isEmpty() const noexcept { return values.isEmpty(); }
static const var& getNullVarRef() noexcept
{
static var nullVar;
return nullVar;
}
const var& NamedValueSet::operator[] (const Identifier& name) const noexcept
{
if (auto* v = getVarPointer (name))
return *v;
return getNullVarRef();
}
var NamedValueSet::getWithDefault (const Identifier& name, const var& defaultReturnValue) const
{
if (auto* v = getVarPointer (name))
return *v;
return defaultReturnValue;
}
var* NamedValueSet::getVarPointer (const Identifier& name) noexcept
{
for (auto& i : values)
if (i.name == name)
return &(i.value);
return {};
}
const var* NamedValueSet::getVarPointer (const Identifier& name) const noexcept
{
for (auto& i : values)
if (i.name == name)
return &(i.value);
return {};
}
bool NamedValueSet::set (const Identifier& name, var&& newValue)
{
if (auto* v = getVarPointer (name))
{
if (v->equalsWithSameType (newValue))
return false;
*v = std::move (newValue);
return true;
}
values.add ({ name, std::move (newValue) });
return true;
}
bool NamedValueSet::set (const Identifier& name, const var& newValue)
{
if (auto* v = getVarPointer (name))
{
if (v->equalsWithSameType (newValue))
return false;
*v = newValue;
return true;
}
values.add ({ name, newValue });
return true;
}
bool NamedValueSet::contains (const Identifier& name) const noexcept
{
return getVarPointer (name) != nullptr;
}
int NamedValueSet::indexOf (const Identifier& name) const noexcept
{
auto numValues = values.size();
for (int i = 0; i < numValues; ++i)
if (values.getReference(i).name == name)
return i;
return -1;
}
bool NamedValueSet::remove (const Identifier& name)
{
auto numValues = values.size();
for (int i = 0; i < numValues; ++i)
{
if (values.getReference(i).name == name)
{
values.remove (i);
return true;
}
}
return false;
}
Identifier NamedValueSet::getName (const int index) const noexcept
{
if (isPositiveAndBelow (index, values.size()))
return values.getReference (index).name;
jassertfalse;
return {};
}
const var& NamedValueSet::getValueAt (const int index) const noexcept
{
if (isPositiveAndBelow (index, values.size()))
return values.getReference (index).value;
jassertfalse;
return getNullVarRef();
}
var* NamedValueSet::getVarPointerAt (int index) noexcept
{
if (isPositiveAndBelow (index, values.size()))
return &(values.getReference (index).value);
return {};
}
const var* NamedValueSet::getVarPointerAt (int index) const noexcept
{
if (isPositiveAndBelow (index, values.size()))
return &(values.getReference (index).value);
return {};
}
void NamedValueSet::setFromXmlAttributes (const XmlElement& xml)
{
values.clearQuick();
for (auto* att = xml.attributes.get(); att != nullptr; att = att->nextListItem)
{
if (att->name.toString().startsWith ("base64:"))
{
MemoryBlock mb;
if (mb.fromBase64Encoding (att->value))
{
values.add ({ att->name.toString().substring (7), var (mb) });
continue;
}
}
values.add ({ att->name, var (att->value) });
}
}
void NamedValueSet::copyToXmlAttributes (XmlElement& xml) const
{
for (auto& i : values)
{
if (auto* mb = i.value.getBinaryData())
{
xml.setAttribute ("base64:" + i.name.toString(), mb->toBase64Encoding());
}
else
{
// These types can't be stored as XML!
jassert (! i.value.isObject());
jassert (! i.value.isMethod());
jassert (! i.value.isArray());
xml.setAttribute (i.name.toString(),
i.value.toString());
}
}
}
} // namespace juce

View File

@ -0,0 +1,185 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
//==============================================================================
/** Holds a set of named var objects.
This can be used as a basic structure to hold a set of var object, which can
be retrieved by using their identifier.
@tags{Core}
*/
class JUCE_API NamedValueSet
{
public:
//==============================================================================
/** Structure for a named var object */
struct JUCE_API NamedValue
{
NamedValue() noexcept;
~NamedValue() noexcept;
NamedValue (const Identifier& name, const var& value);
NamedValue (const Identifier& name, var&& value) noexcept;
NamedValue (Identifier&& name, var&& value) noexcept;
NamedValue (const NamedValue&);
NamedValue (NamedValue&&) noexcept;
NamedValue& operator= (NamedValue&&) noexcept;
bool operator== (const NamedValue&) const noexcept;
bool operator!= (const NamedValue&) const noexcept;
Identifier name;
var value;
};
//==============================================================================
/** Creates an empty set. */
NamedValueSet() noexcept;
NamedValueSet (const NamedValueSet&);
NamedValueSet (NamedValueSet&&) noexcept;
NamedValueSet& operator= (const NamedValueSet&);
NamedValueSet& operator= (NamedValueSet&&) noexcept;
/** Creates a NamedValueSet from a list of names and properties. */
NamedValueSet (std::initializer_list<NamedValue>);
/** Destructor. */
~NamedValueSet() noexcept;
/** Two NamedValueSets are considered equal if they contain all the same key/value
pairs, regardless of the order.
*/
bool operator== (const NamedValueSet&) const noexcept;
bool operator!= (const NamedValueSet&) const noexcept;
const NamedValueSet::NamedValue* begin() const noexcept { return values.begin(); }
const NamedValueSet::NamedValue* end() const noexcept { return values.end(); }
//==============================================================================
/** Returns the total number of values that the set contains. */
int size() const noexcept;
/** Returns true if the set is empty. */
bool isEmpty() const noexcept;
/** Returns the value of a named item.
If the name isn't found, this will return a void variant.
*/
const var& operator[] (const Identifier& name) const noexcept;
/** Tries to return the named value, but if no such value is found, this will
instead return the supplied default value.
*/
var getWithDefault (const Identifier& name, const var& defaultReturnValue) const;
/** Changes or adds a named value.
@returns true if a value was changed or added; false if the
value was already set the value passed-in.
*/
bool set (const Identifier& name, const var& newValue);
/** Changes or adds a named value.
@returns true if a value was changed or added; false if the
value was already set the value passed-in.
*/
bool set (const Identifier& name, var&& newValue);
/** Returns true if the set contains an item with the specified name. */
bool contains (const Identifier& name) const noexcept;
/** Removes a value from the set.
@returns true if a value was removed; false if there was no value
with the name that was given.
*/
bool remove (const Identifier& name);
/** Returns the name of the value at a given index.
The index must be between 0 and size() - 1.
*/
Identifier getName (int index) const noexcept;
/** Returns a pointer to the var that holds a named value, or null if there is
no value with this name.
Do not use this method unless you really need access to the internal var object
for some reason - for normal reading and writing always prefer operator[]() and set().
Also note that the pointer returned may become invalid as soon as any subsequent
methods are called on the NamedValueSet.
*/
var* getVarPointer (const Identifier& name) noexcept;
/** Returns a pointer to the var that holds a named value, or null if there is
no value with this name.
Do not use this method unless you really need access to the internal var object
for some reason - for normal reading and writing always prefer operator[]() and set().
Also note that the pointer returned may become invalid as soon as any subsequent
methods are called on the NamedValueSet.
*/
const var* getVarPointer (const Identifier& name) const noexcept;
/** Returns the value of the item at a given index.
The index must be between 0 and size() - 1.
*/
const var& getValueAt (int index) const noexcept;
/** Returns the value of the item at a given index.
The index must be between 0 and size() - 1, or this will return a nullptr
Also note that the pointer returned may become invalid as soon as any subsequent
methods are called on the NamedValueSet.
*/
var* getVarPointerAt (int index) noexcept;
/** Returns the value of the item at a given index.
The index must be between 0 and size() - 1, or this will return a nullptr
Also note that the pointer returned may become invalid as soon as any subsequent
methods are called on the NamedValueSet.
*/
const var* getVarPointerAt (int index) const noexcept;
/** Returns the index of the given name, or -1 if it's not found. */
int indexOf (const Identifier& name) const noexcept;
/** Removes all values. */
void clear();
//==============================================================================
/** Sets properties to the values of all of an XML element's attributes. */
void setFromXmlAttributes (const XmlElement& xml);
/** Sets attributes in an XML element corresponding to each of this object's
properties.
*/
void copyToXmlAttributes (XmlElement& xml) const;
private:
//==============================================================================
Array<NamedValue> values;
};
} // namespace juce

View File

@ -0,0 +1,130 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
#if JUCE_UNIT_TESTS
static struct OwnedArrayTest : public UnitTest
{
struct Base
{
Base() = default;
virtual ~Base() = default;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Base)
};
struct Derived : Base
{
Derived() = default;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Derived)
};
struct DestructorObj
{
DestructorObj (OwnedArrayTest& p,
OwnedArray<DestructorObj>& arr)
: parent (p), objectArray (arr)
{}
~DestructorObj()
{
data = 0;
for (auto* o : objectArray)
{
parent.expect (o != nullptr);
parent.expect (o != this);
if (o != nullptr)
parent.expectEquals (o->data, 956);
}
}
OwnedArrayTest& parent;
OwnedArray<DestructorObj>& objectArray;
int data = 956;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DestructorObj)
};
OwnedArrayTest()
: UnitTest ("OwnedArray", UnitTestCategories::containers)
{}
void runTest() override
{
beginTest ("After converting move construction, ownership is transferred");
{
OwnedArray<Derived> derived { new Derived{}, new Derived{}, new Derived{} };
OwnedArray<Base> base { std::move (derived) };
expectEquals (base.size(), 3);
expectEquals (derived.size(), 0);
}
beginTest ("After converting move assignment, ownership is transferred");
{
OwnedArray<Base> base;
base = OwnedArray<Derived> { new Derived{}, new Derived{}, new Derived{} };
expectEquals (base.size(), 3);
}
beginTest ("Iterate in destructor");
{
{
OwnedArray<DestructorObj> arr;
for (int i = 0; i < 2; ++i)
arr.add (new DestructorObj (*this, arr));
}
OwnedArray<DestructorObj> arr;
for (int i = 0; i < 1025; ++i)
arr.add (new DestructorObj (*this, arr));
while (! arr.isEmpty())
arr.remove (0);
for (int i = 0; i < 1025; ++i)
arr.add (new DestructorObj (*this, arr));
arr.removeRange (1, arr.size() - 3);
for (int i = 0; i < 1025; ++i)
arr.add (new DestructorObj (*this, arr));
arr.set (500, new DestructorObj (*this, arr));
}
}
} ownedArrayTest;
#endif
}

View File

@ -0,0 +1,873 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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 array designed for holding objects.
This holds a list of pointers to objects, and will automatically
delete the objects when they are removed from the array, or when the
array is itself deleted.
Declare it in the form: OwnedArray<MyObjectClass>
..and then add new objects, e.g. myOwnedArray.add (new MyObjectClass());
After adding objects, they are 'owned' by the array and will be deleted when
removed or replaced.
To make all the array's methods thread-safe, pass in "CriticalSection" as the templated
TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection.
@see Array, ReferenceCountedArray, StringArray, CriticalSection
@tags{Core}
*/
template <class ObjectClass,
class TypeOfCriticalSectionToUse = DummyCriticalSection>
class OwnedArray
{
public:
//==============================================================================
/** Creates an empty array. */
OwnedArray() = default;
/** Deletes the array and also deletes any objects inside it.
To get rid of the array without deleting its objects, use its
clear (false) method before deleting it.
*/
~OwnedArray()
{
deleteAllObjects();
}
/** Move constructor. */
OwnedArray (OwnedArray&& other) noexcept
: values (std::move (other.values))
{
}
/** Creates an array from a list of objects. */
OwnedArray (const std::initializer_list<ObjectClass*>& items)
{
addArray (items);
}
/** Move assignment operator. */
OwnedArray& operator= (OwnedArray&& other) noexcept
{
const ScopedLockType lock (getLock());
deleteAllObjects();
values = std::move (other.values);
return *this;
}
/** Converting move constructor. */
template <class OtherObjectClass, class OtherCriticalSection>
OwnedArray (OwnedArray<OtherObjectClass, OtherCriticalSection>&& other) noexcept
: values (std::move (other.values))
{
}
/** Converting move assignment operator. */
template <class OtherObjectClass, class OtherCriticalSection>
OwnedArray& operator= (OwnedArray<OtherObjectClass, OtherCriticalSection>&& other) noexcept
{
const ScopedLockType lock (getLock());
deleteAllObjects();
values = std::move (other.values);
return *this;
}
//==============================================================================
/** Clears the array, optionally deleting the objects inside it first. */
void clear (bool deleteObjects = true)
{
const ScopedLockType lock (getLock());
clearQuick (deleteObjects);
values.setAllocatedSize (0);
}
//==============================================================================
/** Clears the array, optionally deleting the objects inside it first. */
void clearQuick (bool deleteObjects)
{
const ScopedLockType lock (getLock());
if (deleteObjects)
deleteAllObjects();
else
values.clear();
}
//==============================================================================
/** Returns the number of items currently in the array.
@see operator[]
*/
inline int size() const noexcept
{
return values.size();
}
/** Returns true if the array is empty, false otherwise. */
inline bool isEmpty() const noexcept
{
return size() == 0;
}
/** Returns a pointer to the object at this index in the array.
If the index is out-of-range, this will return a null pointer, (and
it could be null anyway, because it's ok for the array to hold null
pointers as well as objects).
@see getUnchecked
*/
inline ObjectClass* operator[] (int index) const noexcept
{
const ScopedLockType lock (getLock());
return values.getValueWithDefault (index);
}
/** Returns a pointer to the object at this index in the array, without checking whether the index is in-range.
This is a faster and less safe version of operator[] which doesn't check the index passed in, so
it can be used when you're sure the index is always going to be legal.
*/
inline ObjectClass* getUnchecked (int index) const noexcept
{
const ScopedLockType lock (getLock());
return values[index];
}
/** Returns a pointer to the first object in the array.
This will return a null pointer if the array's empty.
@see getLast
*/
inline ObjectClass* getFirst() const noexcept
{
const ScopedLockType lock (getLock());
return values.getFirst();
}
/** Returns a pointer to the last object in the array.
This will return a null pointer if the array's empty.
@see getFirst
*/
inline ObjectClass* getLast() const noexcept
{
const ScopedLockType lock (getLock());
return values.getLast();
}
/** Returns a pointer to the actual array data.
This pointer will only be valid until the next time a non-const method
is called on the array.
*/
inline ObjectClass** getRawDataPointer() noexcept
{
return values.begin();
}
//==============================================================================
/** Returns a pointer to the first element in the array.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline ObjectClass** begin() noexcept
{
return values.begin();
}
/** Returns a pointer to the first element in the array.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline ObjectClass* const* begin() const noexcept
{
return values.begin();
}
/** Returns a pointer to the element which follows the last element in the array.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline ObjectClass** end() noexcept
{
return values.end();
}
/** Returns a pointer to the element which follows the last element in the array.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline ObjectClass* const* end() const noexcept
{
return values.end();
}
/** Returns a pointer to the first element in the array.
This method is provided for compatibility with the standard C++ containers.
*/
inline ObjectClass** data() noexcept
{
return begin();
}
/** Returns a pointer to the first element in the array.
This method is provided for compatibility with the standard C++ containers.
*/
inline ObjectClass* const* data() const noexcept
{
return begin();
}
//==============================================================================
/** Finds the index of an object which might be in the array.
@param objectToLookFor the object to look for
@returns the index at which the object was found, or -1 if it's not found
*/
int indexOf (const ObjectClass* objectToLookFor) const noexcept
{
const ScopedLockType lock (getLock());
auto* e = values.begin();
for (; e != values.end(); ++e)
if (objectToLookFor == *e)
return static_cast<int> (e - values.begin());
return -1;
}
/** Returns true if the array contains a specified object.
@param objectToLookFor the object to look for
@returns true if the object is in the array
*/
bool contains (const ObjectClass* objectToLookFor) const noexcept
{
const ScopedLockType lock (getLock());
auto* e = values.begin();
for (; e != values.end(); ++e)
if (objectToLookFor == *e)
return true;
return false;
}
//==============================================================================
/** Appends a new object to the end of the array.
Note that this object will be deleted by the OwnedArray when it is removed,
so be careful not to delete it somewhere else.
Also be careful not to add the same object to the array more than once,
as this will obviously cause deletion of dangling pointers.
@param newObject the new object to add to the array
@returns the new object that was added
@see set, insert, addSorted
*/
ObjectClass* add (ObjectClass* newObject)
{
const ScopedLockType lock (getLock());
values.add (newObject);
return newObject;
}
/** Appends a new object to the end of the array.
Note that this object will be deleted by the OwnedArray when it is removed,
so be careful not to delete it somewhere else.
Also be careful not to add the same object to the array more than once,
as this will obviously cause deletion of dangling pointers.
@param newObject the new object to add to the array
@returns the new object that was added
@see set, insert, addSorted
*/
ObjectClass* add (std::unique_ptr<ObjectClass> newObject)
{
return add (newObject.release());
}
/** Inserts a new object into the array at the given index.
Note that this object will be deleted by the OwnedArray when it is removed,
so be careful not to delete it somewhere else.
If the index is less than 0 or greater than the size of the array, the
element will be added to the end of the array.
Otherwise, it will be inserted into the array, moving all the later elements
along to make room.
Be careful not to add the same object to the array more than once,
as this will obviously cause deletion of dangling pointers.
@param indexToInsertAt the index at which the new element should be inserted
@param newObject the new object to add to the array
@returns the new object that was added
@see add, addSorted, set
*/
ObjectClass* insert (int indexToInsertAt, ObjectClass* newObject)
{
const ScopedLockType lock (getLock());
values.insert (indexToInsertAt, newObject, 1);
return newObject;
}
/** Inserts a new object into the array at the given index.
Note that this object will be deleted by the OwnedArray when it is removed,
so be careful not to delete it somewhere else.
If the index is less than 0 or greater than the size of the array, the
element will be added to the end of the array.
Otherwise, it will be inserted into the array, moving all the later elements
along to make room.
Be careful not to add the same object to the array more than once,
as this will obviously cause deletion of dangling pointers.
@param indexToInsertAt the index at which the new element should be inserted
@param newObject the new object to add to the array
@returns the new object that was added
@see add, addSorted, set
*/
ObjectClass* insert (int indexToInsertAt, std::unique_ptr<ObjectClass> newObject)
{
return insert (indexToInsertAt, newObject.release());
}
/** Inserts an array of values into this array at a given position.
If the index is less than 0 or greater than the size of the array, the
new elements will be added to the end of the array.
Otherwise, they will be inserted into the array, moving all the later elements
along to make room.
@param indexToInsertAt the index at which the first new element should be inserted
@param newObjects the new values to add to the array
@param numberOfElements how many items are in the array
@see insert, add, addSorted, set
*/
void insertArray (int indexToInsertAt,
ObjectClass* const* newObjects,
int numberOfElements)
{
if (numberOfElements > 0)
{
const ScopedLockType lock (getLock());
values.insertArray (indexToInsertAt, newObjects, numberOfElements);
}
}
/** Replaces an object in the array with a different one.
If the index is less than zero, this method does nothing.
If the index is beyond the end of the array, the new object is added to the end of the array.
Be careful not to add the same object to the array more than once,
as this will obviously cause deletion of dangling pointers.
@param indexToChange the index whose value you want to change
@param newObject the new value to set for this index.
@param deleteOldElement whether to delete the object that's being replaced with the new one
@see add, insert, remove
*/
ObjectClass* set (int indexToChange, ObjectClass* newObject, bool deleteOldElement = true)
{
if (indexToChange >= 0)
{
std::unique_ptr<ObjectClass> toDelete;
{
const ScopedLockType lock (getLock());
if (indexToChange < values.size())
{
if (deleteOldElement)
{
toDelete.reset (values[indexToChange]);
if (toDelete.get() == newObject)
toDelete.release();
}
values[indexToChange] = newObject;
}
else
{
values.add (newObject);
}
}
}
else
{
jassertfalse; // you're trying to set an object at a negative index, which doesn't have
// any effect - but since the object is not being added, it may be leaking..
}
return newObject;
}
/** Replaces an object in the array with a different one.
If the index is less than zero, this method does nothing.
If the index is beyond the end of the array, the new object is added to the end of the array.
Be careful not to add the same object to the array more than once,
as this will obviously cause deletion of dangling pointers.
@param indexToChange the index whose value you want to change
@param newObject the new value to set for this index.
@param deleteOldElement whether to delete the object that's being replaced with the new one
@see add, insert, remove
*/
ObjectClass* set (int indexToChange, std::unique_ptr<ObjectClass> newObject, bool deleteOldElement = true)
{
return set (indexToChange, newObject.release(), deleteOldElement);
}
/** Adds elements from another array to the end of this array.
@param arrayToAddFrom the array from which to copy the elements
@param startIndex the first element of the other array to start copying from
@param numElementsToAdd how many elements to add from the other array. If this
value is negative or greater than the number of available elements,
all available elements will be copied.
@see add
*/
template <class OtherArrayType>
void addArray (const OtherArrayType& arrayToAddFrom,
int startIndex = 0,
int numElementsToAdd = -1)
{
const typename OtherArrayType::ScopedLockType lock1 (arrayToAddFrom.getLock());
const ScopedLockType lock2 (getLock());
values.addArray (arrayToAddFrom, startIndex, numElementsToAdd);
}
/** Adds elements from another array to the end of this array. */
template <typename OtherArrayType>
void addArray (const std::initializer_list<OtherArrayType>& items)
{
const ScopedLockType lock (getLock());
values.addArray (items);
}
/** Adds copies of the elements in another array to the end of this array.
The other array must be either an OwnedArray of a compatible type of object, or an Array
containing pointers to the same kind of object. The objects involved must provide
a copy constructor, and this will be used to create new copies of each element, and
add them to this array.
@param arrayToAddFrom the array from which to copy the elements
@param startIndex the first element of the other array to start copying from
@param numElementsToAdd how many elements to add from the other array. If this
value is negative or greater than the number of available elements,
all available elements will be copied.
@see add
*/
template <class OtherArrayType>
void addCopiesOf (const OtherArrayType& arrayToAddFrom,
int startIndex = 0,
int numElementsToAdd = -1)
{
const typename OtherArrayType::ScopedLockType lock1 (arrayToAddFrom.getLock());
const ScopedLockType lock2 (getLock());
if (startIndex < 0)
{
jassertfalse;
startIndex = 0;
}
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size())
numElementsToAdd = arrayToAddFrom.size() - startIndex;
jassert (numElementsToAdd >= 0);
values.ensureAllocatedSize (values.size() + numElementsToAdd);
while (--numElementsToAdd >= 0)
values.add (createCopyIfNotNull (arrayToAddFrom.getUnchecked (startIndex++)));
}
/** Inserts a new object into the array assuming that the array is sorted.
This will use a comparator to find the position at which the new object
should go. If the array isn't sorted, the behaviour of this
method will be unpredictable.
@param comparator the comparator to use to compare the elements - see the sort method
for details about this object's structure
@param newObject the new object to insert to the array
@returns the index at which the new object was added
@see add, sort, indexOfSorted
*/
template <class ElementComparator>
int addSorted (ElementComparator& comparator, ObjectClass* newObject) noexcept
{
// If you pass in an object with a static compareElements() method, this
// avoids getting warning messages about the parameter being unused
ignoreUnused (comparator);
const ScopedLockType lock (getLock());
auto index = findInsertIndexInSortedArray (comparator, values.begin(), newObject, 0, values.size());
insert (index, newObject);
return index;
}
/** Finds the index of an object in the array, assuming that the array is sorted.
This will use a comparator to do a binary-chop to find the index of the given
element, if it exists. If the array isn't sorted, the behaviour of this
method will be unpredictable.
@param comparator the comparator to use to compare the elements - see the sort()
method for details about the form this object should take
@param objectToLookFor the object to search for
@returns the index of the element, or -1 if it's not found
@see addSorted, sort
*/
template <typename ElementComparator>
int indexOfSorted (ElementComparator& comparator, const ObjectClass* objectToLookFor) const noexcept
{
// If you pass in an object with a static compareElements() method, this
// avoids getting warning messages about the parameter being unused
ignoreUnused (comparator);
const ScopedLockType lock (getLock());
int s = 0, e = values.size();
while (s < e)
{
if (comparator.compareElements (objectToLookFor, values[s]) == 0)
return s;
auto halfway = (s + e) / 2;
if (halfway == s)
break;
if (comparator.compareElements (objectToLookFor, values[halfway]) >= 0)
s = halfway;
else
e = halfway;
}
return -1;
}
//==============================================================================
/** Removes an object from the array.
This will remove the object at a given index (optionally also
deleting it) and move back all the subsequent objects to close the gap.
If the index passed in is out-of-range, nothing will happen.
@param indexToRemove the index of the element to remove
@param deleteObject whether to delete the object that is removed
@see removeObject, removeRange
*/
void remove (int indexToRemove, bool deleteObject = true)
{
std::unique_ptr<ObjectClass> toDelete;
{
const ScopedLockType lock (getLock());
if (isPositiveAndBelow (indexToRemove, values.size()))
{
auto** e = values.begin() + indexToRemove;
if (deleteObject)
toDelete.reset (*e);
values.removeElements (indexToRemove, 1);
}
}
if ((values.size() << 1) < values.capacity())
minimiseStorageOverheads();
}
/** Removes and returns an object from the array without deleting it.
This will remove the object at a given index and return it, moving back all
the subsequent objects to close the gap. If the index passed in is out-of-range,
nothing will happen.
@param indexToRemove the index of the element to remove
@see remove, removeObject, removeRange
*/
ObjectClass* removeAndReturn (int indexToRemove)
{
ObjectClass* removedItem = nullptr;
const ScopedLockType lock (getLock());
if (isPositiveAndBelow (indexToRemove, values.size()))
{
removedItem = values[indexToRemove];
values.removeElements (indexToRemove, 1);
if ((values.size() << 1) < values.capacity())
minimiseStorageOverheads();
}
return removedItem;
}
/** Removes a specified object from the array.
If the item isn't found, no action is taken.
@param objectToRemove the object to try to remove
@param deleteObject whether to delete the object (if it's found)
@see remove, removeRange
*/
void removeObject (const ObjectClass* objectToRemove, bool deleteObject = true)
{
const ScopedLockType lock (getLock());
for (int i = 0; i < values.size(); ++i)
{
if (objectToRemove == values[i])
{
remove (i, deleteObject);
break;
}
}
}
/** Removes a range of objects from the array.
This will remove a set of objects, starting from the given index,
and move any subsequent elements down to close the gap.
If the range extends beyond the bounds of the array, it will
be safely clipped to the size of the array.
@param startIndex the index of the first object to remove
@param numberToRemove how many objects should be removed
@param deleteObjects whether to delete the objects that get removed
@see remove, removeObject
*/
void removeRange (int startIndex, int numberToRemove, bool deleteObjects = true)
{
const ScopedLockType lock (getLock());
auto endIndex = jlimit (0, values.size(), startIndex + numberToRemove);
startIndex = jlimit (0, values.size(), startIndex);
numberToRemove = endIndex - startIndex;
if (numberToRemove > 0)
{
Array<ObjectClass*> objectsToDelete;
if (deleteObjects)
objectsToDelete.addArray (values.begin() + startIndex, numberToRemove);
values.removeElements (startIndex, numberToRemove);
for (auto& o : objectsToDelete)
ContainerDeletePolicy<ObjectClass>::destroy (o);
if ((values.size() << 1) < values.capacity())
minimiseStorageOverheads();
}
}
/** Removes the last n objects from the array.
@param howManyToRemove how many objects to remove from the end of the array
@param deleteObjects whether to also delete the objects that are removed
@see remove, removeObject, removeRange
*/
void removeLast (int howManyToRemove = 1,
bool deleteObjects = true)
{
const ScopedLockType lock (getLock());
if (howManyToRemove >= values.size())
clear (deleteObjects);
else
removeRange (values.size() - howManyToRemove, howManyToRemove, deleteObjects);
}
/** Swaps a pair of objects in the array.
If either of the indexes passed in is out-of-range, nothing will happen,
otherwise the two objects at these positions will be exchanged.
*/
void swap (int index1, int index2) noexcept
{
const ScopedLockType lock (getLock());
values.swap (index1, index2);
}
/** Moves one of the objects to a different position.
This will move the object to a specified index, shuffling along
any intervening elements as required.
So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling
move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }.
@param currentIndex the index of the object to be moved. If this isn't a
valid index, then nothing will be done
@param newIndex the index at which you'd like this object to end up. If this
is less than zero, it will be moved to the end of the array
*/
void move (int currentIndex, int newIndex) noexcept
{
if (currentIndex != newIndex)
{
const ScopedLockType lock (getLock());
values.move (currentIndex, newIndex);
}
}
/** This swaps the contents of this array with those of another array.
If you need to exchange two arrays, this is vastly quicker than using copy-by-value
because it just swaps their internal pointers.
*/
template <class OtherArrayType>
void swapWith (OtherArrayType& otherArray) noexcept
{
const ScopedLockType lock1 (getLock());
const typename OtherArrayType::ScopedLockType lock2 (otherArray.getLock());
values.swapWith (otherArray.values);
}
//==============================================================================
/** Reduces the amount of storage being used by the array.
Arrays typically allocate slightly more storage than they need, and after
removing elements, they may have quite a lot of unused space allocated.
This method will reduce the amount of allocated storage to a minimum.
*/
void minimiseStorageOverheads() noexcept
{
const ScopedLockType lock (getLock());
values.shrinkToNoMoreThan (values.size());
}
/** Increases the array's internal storage to hold a minimum number of elements.
Calling this before adding a large known number of elements means that
the array won't have to keep dynamically resizing itself as the elements
are added, and it'll therefore be more efficient.
*/
void ensureStorageAllocated (int minNumElements) noexcept
{
const ScopedLockType lock (getLock());
values.ensureAllocatedSize (minNumElements);
}
//==============================================================================
/** Sorts the elements in the array.
This will use a comparator object to sort the elements into order. The object
passed must have a method of the form:
@code
int compareElements (ElementType* first, ElementType* second);
@endcode
..and this method must return:
- a value of < 0 if the first comes before the second
- a value of 0 if the two objects are equivalent
- a value of > 0 if the second comes before the first
To improve performance, the compareElements() method can be declared as static or const.
@param comparator the comparator to use for comparing elements.
@param retainOrderOfEquivalentItems if this is true, then items
which the comparator says are equivalent will be
kept in the order in which they currently appear
in the array. This is slower to perform, but may
be important in some cases. If it's false, a faster
algorithm is used, but equivalent elements may be
rearranged.
@see sortArray, indexOfSorted
*/
template <class ElementComparator>
void sort (ElementComparator& comparator,
bool retainOrderOfEquivalentItems = false) noexcept
{
// If you pass in an object with a static compareElements() method, this
// avoids getting warning messages about the parameter being unused
ignoreUnused (comparator);
const ScopedLockType lock (getLock());
if (size() > 1)
sortArray (comparator, values.begin(), 0, size() - 1, retainOrderOfEquivalentItems);
}
//==============================================================================
/** Returns the CriticalSection that locks this array.
To lock, you can call getLock().enter() and getLock().exit(), or preferably use
an object of ScopedLockType as an RAII lock for it.
*/
inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return values; }
/** Returns the type of scoped lock to use for locking this array */
using ScopedLockType = typename TypeOfCriticalSectionToUse::ScopedLockType;
//==============================================================================
#ifndef DOXYGEN
[[deprecated ("This method has been replaced by a more flexible templated version and renamed "
"to swapWith to be more consistent with the names used in other classes.")]]
void swapWithArray (OwnedArray& other) noexcept { swapWith (other); }
#endif
private:
//==============================================================================
ArrayBase <ObjectClass*, TypeOfCriticalSectionToUse> values;
void deleteAllObjects()
{
auto i = values.size();
while (--i >= 0)
{
auto* e = values[i];
values.removeElements (i, 1);
ContainerDeletePolicy<ObjectClass>::destroy (e);
}
}
template <class OtherObjectClass, class OtherCriticalSection>
friend class OwnedArray;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OwnedArray)
};
} // namespace juce

View File

@ -0,0 +1,217 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
PropertySet::PropertySet (bool ignoreCaseOfKeyNames)
: properties (ignoreCaseOfKeyNames),
fallbackProperties (nullptr),
ignoreCaseOfKeys (ignoreCaseOfKeyNames)
{
}
PropertySet::PropertySet (const PropertySet& other)
: properties (other.properties),
fallbackProperties (other.fallbackProperties),
ignoreCaseOfKeys (other.ignoreCaseOfKeys)
{
}
PropertySet& PropertySet::operator= (const PropertySet& other)
{
properties = other.properties;
fallbackProperties = other.fallbackProperties;
ignoreCaseOfKeys = other.ignoreCaseOfKeys;
propertyChanged();
return *this;
}
PropertySet::~PropertySet()
{
}
void PropertySet::clear()
{
const ScopedLock sl (lock);
if (properties.size() > 0)
{
properties.clear();
propertyChanged();
}
}
String PropertySet::getValue (StringRef keyName, const String& defaultValue) const noexcept
{
const ScopedLock sl (lock);
auto index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys);
if (index >= 0)
return properties.getAllValues() [index];
return fallbackProperties != nullptr ? fallbackProperties->getValue (keyName, defaultValue)
: defaultValue;
}
int PropertySet::getIntValue (StringRef keyName, int defaultValue) const noexcept
{
const ScopedLock sl (lock);
auto index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys);
if (index >= 0)
return properties.getAllValues() [index].getIntValue();
return fallbackProperties != nullptr ? fallbackProperties->getIntValue (keyName, defaultValue)
: defaultValue;
}
double PropertySet::getDoubleValue (StringRef keyName, double defaultValue) const noexcept
{
const ScopedLock sl (lock);
auto index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys);
if (index >= 0)
return properties.getAllValues()[index].getDoubleValue();
return fallbackProperties != nullptr ? fallbackProperties->getDoubleValue (keyName, defaultValue)
: defaultValue;
}
bool PropertySet::getBoolValue (StringRef keyName, bool defaultValue) const noexcept
{
const ScopedLock sl (lock);
auto index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys);
if (index >= 0)
return properties.getAllValues() [index].getIntValue() != 0;
return fallbackProperties != nullptr ? fallbackProperties->getBoolValue (keyName, defaultValue)
: defaultValue;
}
std::unique_ptr<XmlElement> PropertySet::getXmlValue (StringRef keyName) const
{
return parseXML (getValue (keyName));
}
void PropertySet::setValue (StringRef keyName, const var& v)
{
jassert (keyName.isNotEmpty()); // shouldn't use an empty key name!
if (keyName.isNotEmpty())
{
auto value = v.toString();
const ScopedLock sl (lock);
auto index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys);
if (index < 0 || properties.getAllValues() [index] != value)
{
properties.set (keyName, value);
propertyChanged();
}
}
}
void PropertySet::removeValue (StringRef keyName)
{
if (keyName.isNotEmpty())
{
const ScopedLock sl (lock);
auto index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys);
if (index >= 0)
{
properties.remove (keyName);
propertyChanged();
}
}
}
void PropertySet::setValue (StringRef keyName, const XmlElement* xml)
{
setValue (keyName, xml == nullptr ? var()
: var (xml->toString (XmlElement::TextFormat().singleLine().withoutHeader())));
}
bool PropertySet::containsKey (StringRef keyName) const noexcept
{
const ScopedLock sl (lock);
return properties.getAllKeys().contains (keyName, ignoreCaseOfKeys);
}
void PropertySet::addAllPropertiesFrom (const PropertySet& source)
{
const ScopedLock sl (source.getLock());
for (int i = 0; i < source.properties.size(); ++i)
setValue (source.properties.getAllKeys() [i],
source.properties.getAllValues() [i]);
}
void PropertySet::setFallbackPropertySet (PropertySet* fallbackProperties_) noexcept
{
const ScopedLock sl (lock);
fallbackProperties = fallbackProperties_;
}
std::unique_ptr<XmlElement> PropertySet::createXml (const String& nodeName) const
{
auto xml = std::make_unique<XmlElement> (nodeName);
const ScopedLock sl (lock);
for (int i = 0; i < properties.getAllKeys().size(); ++i)
{
auto e = xml->createNewChildElement ("VALUE");
e->setAttribute ("name", properties.getAllKeys()[i]);
e->setAttribute ("val", properties.getAllValues()[i]);
}
return xml;
}
void PropertySet::restoreFromXml (const XmlElement& xml)
{
const ScopedLock sl (lock);
clear();
for (auto* e : xml.getChildWithTagNameIterator ("VALUE"))
{
if (e->hasAttribute ("name")
&& e->hasAttribute ("val"))
{
properties.set (e->getStringAttribute ("name"),
e->getStringAttribute ("val"));
}
}
if (properties.size() > 0)
propertyChanged();
}
void PropertySet::propertyChanged()
{
}
} // namespace juce

View File

@ -0,0 +1,205 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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 set of named property values, which can be strings, integers, floating point, etc.
Effectively, this just wraps a StringPairArray in an interface that makes it easier
to load and save types other than strings.
See the PropertiesFile class for a subclass of this, which automatically broadcasts change
messages and saves/loads the list from a file.
@tags{Core}
*/
class JUCE_API PropertySet
{
public:
//==============================================================================
/** Creates an empty PropertySet.
@param ignoreCaseOfKeyNames if true, the names of properties are compared in a
case-insensitive way
*/
PropertySet (bool ignoreCaseOfKeyNames = false);
/** Creates a copy of another PropertySet. */
PropertySet (const PropertySet& other);
/** Copies another PropertySet over this one. */
PropertySet& operator= (const PropertySet& other);
/** Destructor. */
virtual ~PropertySet();
//==============================================================================
/** Returns one of the properties as a string.
If the value isn't found in this set, then this will look for it in a fallback
property set (if you've specified one with the setFallbackPropertySet() method),
and if it can't find one there, it'll return the default value passed-in.
@param keyName the name of the property to retrieve
@param defaultReturnValue a value to return if the named property doesn't actually exist
*/
String getValue (StringRef keyName, const String& defaultReturnValue = String()) const noexcept;
/** Returns one of the properties as an integer.
If the value isn't found in this set, then this will look for it in a fallback
property set (if you've specified one with the setFallbackPropertySet() method),
and if it can't find one there, it'll return the default value passed-in.
@param keyName the name of the property to retrieve
@param defaultReturnValue a value to return if the named property doesn't actually exist
*/
int getIntValue (StringRef keyName, int defaultReturnValue = 0) const noexcept;
/** Returns one of the properties as an double.
If the value isn't found in this set, then this will look for it in a fallback
property set (if you've specified one with the setFallbackPropertySet() method),
and if it can't find one there, it'll return the default value passed-in.
@param keyName the name of the property to retrieve
@param defaultReturnValue a value to return if the named property doesn't actually exist
*/
double getDoubleValue (StringRef keyName, double defaultReturnValue = 0.0) const noexcept;
/** Returns one of the properties as an boolean.
The result will be true if the string found for this key name can be parsed as a non-zero
integer.
If the value isn't found in this set, then this will look for it in a fallback
property set (if you've specified one with the setFallbackPropertySet() method),
and if it can't find one there, it'll return the default value passed-in.
@param keyName the name of the property to retrieve
@param defaultReturnValue a value to return if the named property doesn't actually exist
*/
bool getBoolValue (StringRef keyName, bool defaultReturnValue = false) const noexcept;
/** Returns one of the properties as an XML element.
The result will a new XMLElement object. It may return nullptr if the key isn't found,
or if the entry contains an string that isn't valid XML.
If the value isn't found in this set, then this will look for it in a fallback
property set (if you've specified one with the setFallbackPropertySet() method),
and if it can't find one there, it'll return the default value passed-in.
@param keyName the name of the property to retrieve
*/
std::unique_ptr<XmlElement> getXmlValue (StringRef keyName) const;
//==============================================================================
/** Sets a named property.
@param keyName the name of the property to set. (This mustn't be an empty string)
@param value the new value to set it to
*/
void setValue (StringRef keyName, const var& value);
/** Sets a named property to an XML element.
@param keyName the name of the property to set. (This mustn't be an empty string)
@param xml the new element to set it to. If this is a nullptr, the value will
be set to an empty string
@see getXmlValue
*/
void setValue (StringRef keyName, const XmlElement* xml);
/** This copies all the values from a source PropertySet to this one.
This won't remove any existing settings, it just adds any that it finds in the source set.
*/
void addAllPropertiesFrom (const PropertySet& source);
//==============================================================================
/** Deletes a property.
@param keyName the name of the property to delete. (This mustn't be an empty string)
*/
void removeValue (StringRef keyName);
/** Returns true if the properties include the given key. */
bool containsKey (StringRef keyName) const noexcept;
/** Removes all values. */
void clear();
//==============================================================================
/** Returns the keys/value pair array containing all the properties. */
StringPairArray& getAllProperties() noexcept { return properties; }
/** Returns the lock used when reading or writing to this set */
const CriticalSection& getLock() const noexcept { return lock; }
//==============================================================================
/** Returns an XML element which encapsulates all the items in this property set.
The string parameter is the tag name that should be used for the node.
@see restoreFromXml
*/
std::unique_ptr<XmlElement> createXml (const String& nodeName) const;
/** Reloads a set of properties that were previously stored as XML.
The node passed in must have been created by the createXml() method.
@see createXml
*/
void restoreFromXml (const XmlElement& xml);
//==============================================================================
/** Sets up a second PopertySet that will be used to look up any values that aren't
set in this one.
If you set this up to be a pointer to a second property set, then whenever one
of the getValue() methods fails to find an entry in this set, it will look up that
value in the fallback set, and if it finds it, it will return that.
Make sure that you don't delete the fallback set while it's still being used by
another set! To remove the fallback set, just call this method with a null pointer.
@see getFallbackPropertySet
*/
void setFallbackPropertySet (PropertySet* fallbackProperties) noexcept;
/** Returns the fallback property set.
@see setFallbackPropertySet
*/
PropertySet* getFallbackPropertySet() const noexcept { return fallbackProperties; }
protected:
/** Subclasses can override this to be told when one of the properties has been changed. */
virtual void propertyChanged();
private:
StringPairArray properties;
PropertySet* fallbackProperties;
CriticalSection lock;
bool ignoreCaseOfKeys;
JUCE_LEAK_DETECTOR (PropertySet)
};
} // namespace juce

View File

@ -0,0 +1,179 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
#if JUCE_UNIT_TESTS
class ReferenceCountedArrayTests : public UnitTest
{
public:
ReferenceCountedArrayTests()
: UnitTest ("ReferenceCountedArray", UnitTestCategories::containers)
{}
//==============================================================================
void runTest() override
{
beginTest ("Add derived objects");
{
ReferenceCountedArray<TestDerivedObj> derivedArray;
derivedArray.add (static_cast<TestDerivedObj*> (new TestBaseObj()));
expectEquals (derivedArray.size(), 1);
expectEquals (derivedArray.getObjectPointer (0)->getReferenceCount(), 1);
expectEquals (derivedArray[0]->getReferenceCount(), 2);
for (auto o : derivedArray)
expectEquals (o->getReferenceCount(), 1);
ReferenceCountedArray<TestBaseObj> baseArray;
baseArray.addArray (derivedArray);
for (auto o : baseArray)
expectEquals (o->getReferenceCount(), 2);
derivedArray.clearQuick();
baseArray.clearQuick();
auto* baseObject = new TestBaseObj();
TestBaseObj::Ptr baseObjectPtr = baseObject;
expectEquals (baseObject->getReferenceCount(), 1);
auto* derivedObject = new TestDerivedObj();
TestDerivedObj::Ptr derivedObjectPtr = derivedObject;
expectEquals (derivedObject->getReferenceCount(), 1);
baseArray.add (baseObject);
baseArray.add (derivedObject);
for (auto o : baseArray)
expectEquals (o->getReferenceCount(), 2);
expectEquals (baseObject->getReferenceCount(), 2);
expectEquals (derivedObject->getReferenceCount(), 2);
derivedArray.add (derivedObject);
for (auto o : derivedArray)
expectEquals (o->getReferenceCount(), 3);
derivedArray.clearQuick();
baseArray.clearQuick();
expectEquals (baseObject->getReferenceCount(), 1);
expectEquals (derivedObject->getReferenceCount(), 1);
baseArray.add (baseObjectPtr);
baseArray.add (derivedObjectPtr.get());
for (auto o : baseArray)
expectEquals (o->getReferenceCount(), 2);
derivedArray.add (derivedObjectPtr);
for (auto o : derivedArray)
expectEquals (o->getReferenceCount(), 3);
}
beginTest ("Iterate in destructor");
{
{
ReferenceCountedArray<DestructorObj> arr;
for (int i = 0; i < 2; ++i)
arr.add (new DestructorObj (*this, arr));
}
ReferenceCountedArray<DestructorObj> arr;
for (int i = 0; i < 1025; ++i)
arr.add (new DestructorObj (*this, arr));
while (! arr.isEmpty())
arr.remove (0);
for (int i = 0; i < 1025; ++i)
arr.add (new DestructorObj (*this, arr));
arr.removeRange (1, arr.size() - 3);
for (int i = 0; i < 1025; ++i)
arr.add (new DestructorObj (*this, arr));
arr.set (500, new DestructorObj (*this, arr));
}
}
private:
struct TestBaseObj : public ReferenceCountedObject
{
using Ptr = ReferenceCountedObjectPtr<TestBaseObj>;
TestBaseObj() = default;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestBaseObj)
};
struct TestDerivedObj : public TestBaseObj
{
using Ptr = ReferenceCountedObjectPtr<TestDerivedObj>;
TestDerivedObj() = default;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestDerivedObj)
};
struct DestructorObj : public ReferenceCountedObject
{
DestructorObj (ReferenceCountedArrayTests& p,
ReferenceCountedArray<DestructorObj>& arr)
: parent (p), objectArray (arr)
{}
~DestructorObj()
{
data = 0;
for (auto* o : objectArray)
{
parent.expect (o != nullptr);
parent.expect (o != this);
if (o != nullptr)
parent.expectEquals (o->data, 374);
}
}
ReferenceCountedArrayTests& parent;
ReferenceCountedArray<DestructorObj>& objectArray;
int data = 374;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DestructorObj)
};
};
static ReferenceCountedArrayTests referenceCountedArrayTests;
#endif
} // namespace juce

View File

@ -0,0 +1,907 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
//==============================================================================
/**
Holds a list of objects derived from ReferenceCountedObject, or which implement basic
reference-count handling methods.
The template parameter specifies the class of the object you want to point to - the easiest
way to make a class reference-countable is to simply make it inherit from ReferenceCountedObject
or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable
class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and
decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods
should behave.
A ReferenceCountedArray holds objects derived from ReferenceCountedObject,
and takes care of incrementing and decrementing their ref counts when they
are added and removed from the array.
To make all the array's methods thread-safe, pass in "CriticalSection" as the templated
TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection.
@see Array, OwnedArray, StringArray
@tags{Core}
*/
template <class ObjectClass, class TypeOfCriticalSectionToUse = DummyCriticalSection>
class ReferenceCountedArray
{
public:
using ObjectClassPtr = ReferenceCountedObjectPtr<ObjectClass>;
//==============================================================================
/** Creates an empty array.
@see ReferenceCountedObject, Array, OwnedArray
*/
ReferenceCountedArray() = default;
/** Creates a copy of another array */
ReferenceCountedArray (const ReferenceCountedArray& other) noexcept
{
const ScopedLockType lock (other.getLock());
values.addArray (other.begin(), other.size());
for (auto* o : *this)
if (o != nullptr)
o->incReferenceCount();
}
/** Moves from another array */
ReferenceCountedArray (ReferenceCountedArray&& other) noexcept
: values (std::move (other.values))
{
}
/** Creates a copy of another array */
template <class OtherObjectClass, class OtherCriticalSection>
ReferenceCountedArray (const ReferenceCountedArray<OtherObjectClass, OtherCriticalSection>& other) noexcept
{
const typename ReferenceCountedArray<OtherObjectClass, OtherCriticalSection>::ScopedLockType lock (other.getLock());
values.addArray (other.begin(), other.size());
for (auto* o : *this)
if (o != nullptr)
o->incReferenceCount();
}
/** Copies another array into this one.
Any existing objects in this array will first be released.
*/
ReferenceCountedArray& operator= (const ReferenceCountedArray& other) noexcept
{
releaseAllObjects();
auto otherCopy = other;
swapWith (otherCopy);
return *this;
}
/** Copies another array into this one.
Any existing objects in this array will first be released.
*/
template <class OtherObjectClass>
ReferenceCountedArray& operator= (const ReferenceCountedArray<OtherObjectClass, TypeOfCriticalSectionToUse>& other) noexcept
{
auto otherCopy = other;
swapWith (otherCopy);
return *this;
}
/** Moves from another array */
ReferenceCountedArray& operator= (ReferenceCountedArray&& other) noexcept
{
releaseAllObjects();
values = std::move (other.values);
return *this;
}
/** Destructor.
Any objects in the array will be released, and may be deleted if not referenced from elsewhere.
*/
~ReferenceCountedArray()
{
releaseAllObjects();
}
//==============================================================================
/** Removes all objects from the array.
Any objects in the array whose reference counts drop to zero will be deleted.
*/
void clear()
{
const ScopedLockType lock (getLock());
clearQuick();
values.setAllocatedSize (0);
}
/** Removes all objects from the array without freeing the array's allocated storage.
Any objects in the array that whose reference counts drop to zero will be deleted.
@see clear
*/
void clearQuick()
{
const ScopedLockType lock (getLock());
releaseAllObjects();
}
/** Returns the current number of objects in the array. */
inline int size() const noexcept
{
return values.size();
}
/** Returns true if the array is empty, false otherwise. */
inline bool isEmpty() const noexcept
{
return size() == 0;
}
/** Returns a pointer to the object at this index in the array.
If the index is out-of-range, this will return a null pointer, (and
it could be null anyway, because it's ok for the array to hold null
pointers as well as objects).
@see getUnchecked
*/
inline ObjectClassPtr operator[] (int index) const noexcept
{
return ObjectClassPtr (getObjectPointer (index));
}
/** Returns a pointer to the object at this index in the array, without checking
whether the index is in-range.
This is a faster and less safe version of operator[] which doesn't check the index passed in, so
it can be used when you're sure the index is always going to be legal.
*/
inline ObjectClassPtr getUnchecked (int index) const noexcept
{
return ObjectClassPtr (getObjectPointerUnchecked (index));
}
/** Returns a raw pointer to the object at this index in the array.
If the index is out-of-range, this will return a null pointer, (and
it could be null anyway, because it's ok for the array to hold null
pointers as well as objects).
@see getUnchecked
*/
inline ObjectClass* getObjectPointer (int index) const noexcept
{
const ScopedLockType lock (getLock());
return values.getValueWithDefault (index);
}
/** Returns a raw pointer to the object at this index in the array, without checking
whether the index is in-range.
*/
inline ObjectClass* getObjectPointerUnchecked (int index) const noexcept
{
const ScopedLockType lock (getLock());
return values[index];
}
/** Returns a pointer to the first object in the array.
This will return a null pointer if the array's empty.
@see getLast
*/
inline ObjectClassPtr getFirst() const noexcept
{
const ScopedLockType lock (getLock());
return values.getFirst();
}
/** Returns a pointer to the last object in the array.
This will return a null pointer if the array's empty.
@see getFirst
*/
inline ObjectClassPtr getLast() const noexcept
{
const ScopedLockType lock (getLock());
return values.getLast();
}
/** Returns a pointer to the actual array data.
This pointer will only be valid until the next time a non-const method
is called on the array.
*/
inline ObjectClass** getRawDataPointer() const noexcept
{
return values.begin();
}
//==============================================================================
/** Returns a pointer to the first element in the array.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline ObjectClass** begin() noexcept
{
return values.begin();
}
/** Returns a pointer to the first element in the array.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline ObjectClass* const* begin() const noexcept
{
return values.begin();
}
/** Returns a pointer to the element which follows the last element in the array.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline ObjectClass** end() noexcept
{
return values.end();
}
/** Returns a pointer to the element which follows the last element in the array.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline ObjectClass* const* end() const noexcept
{
return values.end();
}
/** Returns a pointer to the first element in the array.
This method is provided for compatibility with the standard C++ containers.
*/
inline ObjectClass** data() noexcept
{
return begin();
}
/** Returns a pointer to the first element in the array.
This method is provided for compatibility with the standard C++ containers.
*/
inline ObjectClass* const* data() const noexcept
{
return begin();
}
//==============================================================================
/** Finds the index of the first occurrence of an object in the array.
@param objectToLookFor the object to look for
@returns the index at which the object was found, or -1 if it's not found
*/
int indexOf (const ObjectClass* objectToLookFor) const noexcept
{
const ScopedLockType lock (getLock());
auto* e = values.begin();
auto* endPointer = values.end();
while (e != endPointer)
{
if (objectToLookFor == *e)
return static_cast<int> (e - values.begin());
++e;
}
return -1;
}
/** Finds the index of the first occurrence of an object in the array.
@param objectToLookFor the object to look for
@returns the index at which the object was found, or -1 if it's not found
*/
int indexOf (const ObjectClassPtr& objectToLookFor) const noexcept { return indexOf (objectToLookFor.get()); }
/** Returns true if the array contains a specified object.
@param objectToLookFor the object to look for
@returns true if the object is in the array
*/
bool contains (const ObjectClass* objectToLookFor) const noexcept
{
const ScopedLockType lock (getLock());
auto* e = values.begin();
auto* endPointer = values.end();
while (e != endPointer)
{
if (objectToLookFor == *e)
return true;
++e;
}
return false;
}
/** Returns true if the array contains a specified object.
@param objectToLookFor the object to look for
@returns true if the object is in the array
*/
bool contains (const ObjectClassPtr& objectToLookFor) const noexcept { return contains (objectToLookFor.get()); }
/** Appends a new object to the end of the array.
This will increase the new object's reference count.
@param newObject the new object to add to the array
@see set, insert, addIfNotAlreadyThere, addSorted, addArray
*/
ObjectClass* add (ObjectClass* newObject)
{
const ScopedLockType lock (getLock());
values.add (newObject);
if (newObject != nullptr)
newObject->incReferenceCount();
return newObject;
}
/** Appends a new object to the end of the array.
This will increase the new object's reference count.
@param newObject the new object to add to the array
@see set, insert, addIfNotAlreadyThere, addSorted, addArray
*/
ObjectClass* add (const ObjectClassPtr& newObject) { return add (newObject.get()); }
/** Inserts a new object into the array at the given index.
If the index is less than 0 or greater than the size of the array, the
element will be added to the end of the array.
Otherwise, it will be inserted into the array, moving all the later elements
along to make room.
This will increase the new object's reference count.
@param indexToInsertAt the index at which the new element should be inserted
@param newObject the new object to add to the array
@see add, addSorted, addIfNotAlreadyThere, set
*/
ObjectClass* insert (int indexToInsertAt, ObjectClass* newObject)
{
values.insert (indexToInsertAt, newObject, 1);
if (newObject != nullptr)
newObject->incReferenceCount();
return newObject;
}
/** Inserts a new object into the array at the given index.
If the index is less than 0 or greater than the size of the array, the
element will be added to the end of the array.
Otherwise, it will be inserted into the array, moving all the later elements
along to make room.
This will increase the new object's reference count.
@param indexToInsertAt the index at which the new element should be inserted
@param newObject the new object to add to the array
@see add, addSorted, addIfNotAlreadyThere, set
*/
ObjectClass* insert (int indexToInsertAt, const ObjectClassPtr& newObject) { return insert (indexToInsertAt, newObject.get()); }
/** Appends a new object at the end of the array as long as the array doesn't
already contain it.
If the array already contains a matching object, nothing will be done.
@param newObject the new object to add to the array
@returns true if the object has been added, false otherwise
*/
bool addIfNotAlreadyThere (ObjectClass* newObject)
{
const ScopedLockType lock (getLock());
if (contains (newObject))
return false;
add (newObject);
return true;
}
/** Appends a new object at the end of the array as long as the array doesn't
already contain it.
If the array already contains a matching object, nothing will be done.
@param newObject the new object to add to the array
@returns true if the object has been added, false otherwise
*/
bool addIfNotAlreadyThere (const ObjectClassPtr& newObject) { return addIfNotAlreadyThere (newObject.get()); }
/** Replaces an object in the array with a different one.
If the index is less than zero, this method does nothing.
If the index is beyond the end of the array, the new object is added to the end of the array.
The object being added has its reference count increased, and if it's replacing
another object, then that one has its reference count decreased, and may be deleted.
@param indexToChange the index whose value you want to change
@param newObject the new value to set for this index.
@see add, insert, remove
*/
void set (int indexToChange, ObjectClass* newObject)
{
if (indexToChange >= 0)
{
const ScopedLockType lock (getLock());
if (newObject != nullptr)
newObject->incReferenceCount();
if (indexToChange < values.size())
{
auto* e = values[indexToChange];
values[indexToChange] = newObject;
releaseObject (e);
}
else
{
values.add (newObject);
}
}
}
/** Replaces an object in the array with a different one.
If the index is less than zero, this method does nothing.
If the index is beyond the end of the array, the new object is added to the end of the array.
The object being added has its reference count increased, and if it's replacing
another object, then that one has its reference count decreased, and may be deleted.
@param indexToChange the index whose value you want to change
@param newObject the new value to set for this index.
@see add, insert, remove
*/
void set (int indexToChange, const ObjectClassPtr& newObject) { set (indexToChange, newObject.get()); }
/** Adds elements from another array to the end of this array.
@param arrayToAddFrom the array from which to copy the elements
@param startIndex the first element of the other array to start copying from
@param numElementsToAdd how many elements to add from the other array. If this
value is negative or greater than the number of available elements,
all available elements will be copied.
@see add
*/
void addArray (const ReferenceCountedArray& arrayToAddFrom,
int startIndex = 0,
int numElementsToAdd = -1) noexcept
{
const ScopedLockType lock1 (arrayToAddFrom.getLock());
{
const ScopedLockType lock2 (getLock());
auto numElementsAdded = values.addArray (arrayToAddFrom.values, startIndex, numElementsToAdd);
auto** e = values.end();
for (int i = 0; i < numElementsAdded; ++i)
(*(--e))->incReferenceCount();
}
}
/** Inserts a new object into the array assuming that the array is sorted.
This will use a comparator to find the position at which the new object
should go. If the array isn't sorted, the behaviour of this
method will be unpredictable.
@param comparator the comparator object to use to compare the elements - see the
sort() method for details about this object's form
@param newObject the new object to insert to the array
@returns the index at which the new object was added
@see add, sort
*/
template <class ElementComparator>
int addSorted (ElementComparator& comparator, ObjectClass* newObject) noexcept
{
const ScopedLockType lock (getLock());
auto index = findInsertIndexInSortedArray (comparator, values.begin(), newObject, 0, values.size());
insert (index, newObject);
return index;
}
/** Inserts or replaces an object in the array, assuming it is sorted.
This is similar to addSorted, but if a matching element already exists, then it will be
replaced by the new one, rather than the new one being added as well.
*/
template <class ElementComparator>
void addOrReplaceSorted (ElementComparator& comparator, ObjectClass* newObject) noexcept
{
const ScopedLockType lock (getLock());
auto index = findInsertIndexInSortedArray (comparator, values.begin(), newObject, 0, values.size());
if (index > 0 && comparator.compareElements (newObject, values[index - 1]) == 0)
set (index - 1, newObject); // replace an existing object that matches
else
insert (index, newObject); // no match, so insert the new one
}
/** Finds the index of an object in the array, assuming that the array is sorted.
This will use a comparator to do a binary-chop to find the index of the given
element, if it exists. If the array isn't sorted, the behaviour of this
method will be unpredictable.
@param comparator the comparator to use to compare the elements - see the sort()
method for details about the form this object should take
@param objectToLookFor the object to search for
@returns the index of the element, or -1 if it's not found
@see addSorted, sort
*/
template <class ElementComparator>
int indexOfSorted (ElementComparator& comparator,
const ObjectClass* objectToLookFor) const noexcept
{
ignoreUnused (comparator);
const ScopedLockType lock (getLock());
int s = 0, e = values.size();
while (s < e)
{
if (comparator.compareElements (objectToLookFor, values[s]) == 0)
return s;
auto halfway = (s + e) / 2;
if (halfway == s)
break;
if (comparator.compareElements (objectToLookFor, values[halfway]) >= 0)
s = halfway;
else
e = halfway;
}
return -1;
}
//==============================================================================
/** Removes an object from the array.
This will remove the object at a given index and move back all the
subsequent objects to close the gap.
If the index passed in is out-of-range, nothing will happen.
The object that is removed will have its reference count decreased,
and may be deleted if not referenced from elsewhere.
@param indexToRemove the index of the element to remove
@see removeObject, removeRange
*/
void remove (int indexToRemove)
{
const ScopedLockType lock (getLock());
if (isPositiveAndBelow (indexToRemove, values.size()))
{
auto* e = *(values.begin() + indexToRemove);
values.removeElements (indexToRemove, 1);
releaseObject (e);
if ((values.size() << 1) < values.capacity())
minimiseStorageOverheads();
}
}
/** Removes and returns an object from the array.
This will remove the object at a given index and return it, moving back all
the subsequent objects to close the gap. If the index passed in is out-of-range,
nothing will happen and a null pointer will be returned.
@param indexToRemove the index of the element to remove
@see remove, removeObject, removeRange
*/
ObjectClassPtr removeAndReturn (int indexToRemove)
{
ObjectClassPtr removedItem;
const ScopedLockType lock (getLock());
if (isPositiveAndBelow (indexToRemove, values.size()))
{
auto* e = *(values.begin() + indexToRemove);
removedItem = e;
values.removeElements (indexToRemove, 1);
releaseObject (e);
if ((values.size() << 1) < values.capacity())
minimiseStorageOverheads();
}
return removedItem;
}
/** Removes the first occurrence of a specified object from the array.
If the item isn't found, no action is taken. If it is found, it is
removed and has its reference count decreased.
@param objectToRemove the object to try to remove
@see remove, removeRange
*/
void removeObject (ObjectClass* objectToRemove)
{
const ScopedLockType lock (getLock());
remove (indexOf (objectToRemove));
}
/** Removes the first occurrence of a specified object from the array.
If the item isn't found, no action is taken. If it is found, it is
removed and has its reference count decreased.
@param objectToRemove the object to try to remove
@see remove, removeRange
*/
void removeObject (const ObjectClassPtr& objectToRemove) { removeObject (objectToRemove.get()); }
/** Removes a range of objects from the array.
This will remove a set of objects, starting from the given index,
and move any subsequent elements down to close the gap.
If the range extends beyond the bounds of the array, it will
be safely clipped to the size of the array.
The objects that are removed will have their reference counts decreased,
and may be deleted if not referenced from elsewhere.
@param startIndex the index of the first object to remove
@param numberToRemove how many objects should be removed
@see remove, removeObject
*/
void removeRange (int startIndex,
int numberToRemove)
{
const ScopedLockType lock (getLock());
startIndex = jlimit (0, values.size(), startIndex);
auto endIndex = jlimit (0, values.size(), startIndex + numberToRemove);
numberToRemove = endIndex - startIndex;
if (numberToRemove > 0)
{
Array<ObjectClass*> objectsToRemove;
objectsToRemove.addArray (values.begin() + startIndex, numberToRemove);
values.removeElements (startIndex, numberToRemove);
for (auto& o : objectsToRemove)
releaseObject (o);
if ((values.size() << 1) < values.capacity())
minimiseStorageOverheads();
}
}
/** Removes the last n objects from the array.
The objects that are removed will have their reference counts decreased,
and may be deleted if not referenced from elsewhere.
@param howManyToRemove how many objects to remove from the end of the array
@see remove, removeObject, removeRange
*/
void removeLast (int howManyToRemove = 1)
{
const ScopedLockType lock (getLock());
if (howManyToRemove > values.size())
howManyToRemove = values.size();
while (--howManyToRemove >= 0)
remove (values.size() - 1);
}
/** Swaps a pair of objects in the array.
If either of the indexes passed in is out-of-range, nothing will happen,
otherwise the two objects at these positions will be exchanged.
*/
void swap (int index1, int index2) noexcept
{
const ScopedLockType lock (getLock());
if (isPositiveAndBelow (index1, values.size())
&& isPositiveAndBelow (index2, values.size()))
{
std::swap (values[index1], values[index2]);
}
}
/** Moves one of the objects to a different position.
This will move the object to a specified index, shuffling along
any intervening elements as required.
So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling
move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }.
@param currentIndex the index of the object to be moved. If this isn't a
valid index, then nothing will be done
@param newIndex the index at which you'd like this object to end up. If this
is less than zero, it will be moved to the end of the array
*/
void move (int currentIndex, int newIndex) noexcept
{
if (currentIndex != newIndex)
{
const ScopedLockType lock (getLock());
values.move (currentIndex, newIndex);
}
}
//==============================================================================
/** This swaps the contents of this array with those of another array.
If you need to exchange two arrays, this is vastly quicker than using copy-by-value
because it just swaps their internal pointers.
*/
template <class OtherArrayType>
void swapWith (OtherArrayType& otherArray) noexcept
{
const ScopedLockType lock1 (getLock());
const typename OtherArrayType::ScopedLockType lock2 (otherArray.getLock());
values.swapWith (otherArray.values);
}
//==============================================================================
/** Compares this array to another one.
@returns true only if the other array contains the same objects in the same order
*/
bool operator== (const ReferenceCountedArray& other) const noexcept
{
const ScopedLockType lock2 (other.getLock());
const ScopedLockType lock1 (getLock());
return values == other.values;
}
/** Compares this array to another one.
@see operator==
*/
bool operator!= (const ReferenceCountedArray<ObjectClass, TypeOfCriticalSectionToUse>& other) const noexcept
{
return ! operator== (other);
}
//==============================================================================
/** Sorts the elements in the array.
This will use a comparator object to sort the elements into order. The object
passed must have a method of the form:
@code
int compareElements (ElementType first, ElementType second);
@endcode
..and this method must return:
- a value of < 0 if the first comes before the second
- a value of 0 if the two objects are equivalent
- a value of > 0 if the second comes before the first
To improve performance, the compareElements() method can be declared as static or const.
@param comparator the comparator to use for comparing elements.
@param retainOrderOfEquivalentItems if this is true, then items
which the comparator says are equivalent will be
kept in the order in which they currently appear
in the array. This is slower to perform, but may
be important in some cases. If it's false, a faster
algorithm is used, but equivalent elements may be
rearranged.
@see sortArray
*/
template <class ElementComparator>
void sort (ElementComparator& comparator,
bool retainOrderOfEquivalentItems = false) noexcept
{
// If you pass in an object with a static compareElements() method, this
// avoids getting warning messages about the parameter being unused
ignoreUnused (comparator);
const ScopedLockType lock (getLock());
sortArray (comparator, values.begin(), 0, values.size() - 1, retainOrderOfEquivalentItems);
}
//==============================================================================
/** Reduces the amount of storage being used by the array.
Arrays typically allocate slightly more storage than they need, and after
removing elements, they may have quite a lot of unused space allocated.
This method will reduce the amount of allocated storage to a minimum.
*/
void minimiseStorageOverheads() noexcept
{
const ScopedLockType lock (getLock());
values.shrinkToNoMoreThan (values.size());
}
/** Increases the array's internal storage to hold a minimum number of elements.
Calling this before adding a large known number of elements means that
the array won't have to keep dynamically resizing itself as the elements
are added, and it'll therefore be more efficient.
*/
void ensureStorageAllocated (const int minNumElements)
{
const ScopedLockType lock (getLock());
values.ensureAllocatedSize (minNumElements);
}
//==============================================================================
/** Returns the CriticalSection that locks this array.
To lock, you can call getLock().enter() and getLock().exit(), or preferably use
an object of ScopedLockType as an RAII lock for it.
*/
inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return values; }
/** Returns the type of scoped lock to use for locking this array */
using ScopedLockType = typename TypeOfCriticalSectionToUse::ScopedLockType;
//==============================================================================
#ifndef DOXYGEN
[[deprecated ("This method has been replaced by a more flexible templated version and renamed "
"to swapWith to be more consistent with the names used in other classes.")]]
void swapWithArray (ReferenceCountedArray& other) noexcept { swapWith (other); }
#endif
private:
//==============================================================================
ArrayBase<ObjectClass*, TypeOfCriticalSectionToUse> values;
void releaseAllObjects()
{
auto i = values.size();
while (--i >= 0)
{
auto* e = values[i];
values.removeElements (i, 1);
releaseObject (e);
}
}
static void releaseObject (ObjectClass* o)
{
if (o != nullptr && o->decReferenceCountWithoutDeleting())
ContainerDeletePolicy<ObjectClass>::destroy (o);
}
};
} // namespace juce

View File

@ -0,0 +1,93 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
//==============================================================================
/**
Helper class providing an RAII-based mechanism for temporarily setting and
then re-setting a value.
E.g. @code
int x = 1;
{
ScopedValueSetter setter (x, 2);
// x is now 2
}
// x is now 1 again
{
ScopedValueSetter setter (x, 3, 4);
// x is now 3
}
// x is now 4
@endcode
@tags{Core}
*/
template <typename ValueType>
class ScopedValueSetter
{
public:
/** Creates a ScopedValueSetter that will immediately change the specified value to the
given new value, and will then reset it to its original value when this object is deleted.
*/
ScopedValueSetter (ValueType& valueToSet,
ValueType newValue)
: value (valueToSet),
originalValue (valueToSet)
{
valueToSet = newValue;
}
/** Creates a ScopedValueSetter that will immediately change the specified value to the
given new value, and will then reset it to be valueWhenDeleted when this object is deleted.
*/
ScopedValueSetter (ValueType& valueToSet,
ValueType newValue,
ValueType valueWhenDeleted)
: value (valueToSet),
originalValue (valueWhenDeleted)
{
valueToSet = newValue;
}
~ScopedValueSetter()
{
value = originalValue;
}
private:
//==============================================================================
ValueType& value;
const ValueType originalValue;
JUCE_DECLARE_NON_COPYABLE (ScopedValueSetter)
};
} // namespace juce

View File

@ -0,0 +1,126 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
//==============================================================================
/**
Encapsulates the logic for a single-threaded FIFO.
This might be useful for building buffers which can be written and read in
blocks of different sizes. For example, in an audio effect we might wish to
run some processing on fixed-size blocks of audio input, but the host may
provide input blocks of varying sizes. In this situation, we might want to
store the previous input in a buffer, and extract a fixed-size block
whenever there are enough samples available. The SingleThreadedAbstractFifo
implements logic suitable for this use-case.
This class is quite similar to AbstractFifo, in that it only keeps track of
the current read/write locations. The user is responsible for providing the
actual buffer that will be read/written.
The intended usage of this class is as follows:
- Create some backing storage in a vector, AudioBuffer etc.
- Construct a SingleThreadedAbstractFifo to manage the buffer, passing the
number of items in the buffer.
- Each time new input is ready, call write(), passing the number of items
you wish to write into the buffer. This function returns a pair of ranges
describing which indices in the backing storage should be written.
- Call getNumReadable() to find out how many items are ready to read from
the buffer.
- If there are enough items ready to read, call read(), passing the number
of items you require. This function returns a pair of ranges describing
which indices in the backing storage may be read.
Unlike AbstractFifo, the SingleThreadedAbstractFifo is intended for use
from a single thread. It is not safe to call any non-const member function
of SingleThreadedAbstractFifo concurrently with any other member function.
@see AbstractFifo
@tags{Core}
*/
class SingleThreadedAbstractFifo
{
public:
/** Creates a SingleThreadedAbstractFifo with no size. */
SingleThreadedAbstractFifo() = default;
/** Creates a SingleThreadedAbstractFifo that can manage a buffer of the specified size. */
explicit SingleThreadedAbstractFifo (int sizeIn)
: size (sizeIn)
{
// This class only works properly when the size is a power of two.
// Use nextPowerOfTwo() to find a good size, and ensure that your
// backing storage is the same size.
jassert (isPowerOfTwo (sizeIn));
}
/** Returns the number of unused elements present in the buffer. */
int getRemainingSpace() const { return size - numReadable; }
/** Returns the number of pending elements present in the buffer. */
int getNumReadable() const { return numReadable; }
/** Returns the size of the managed buffer. */
int getSize() const { return size; }
/** Returns two blocks in the buffer where new items may be written.
Note that if the buffer is running low on free space, the sum of the lengths of
the returned ranges may be less than num!
*/
std::array<Range<int>, 2> write (int num)
{
const auto startPos = (readPos + numReadable) & (size - 1);
const auto maxToWrite = jmin (getRemainingSpace(), num);
const auto firstBlockSize = jmin (maxToWrite, size - startPos);
numReadable += maxToWrite;
return { { { startPos, startPos + firstBlockSize }, { 0, maxToWrite - firstBlockSize } } };
}
/** Returns two blocks in the buffer from which new items may be read.
Note that if the buffer doesn't have the requested number of items available,
the sum of the lengths of the returned ranges may be less than num!
*/
std::array<Range<int>, 2> read (int num)
{
const auto startPos = readPos;
const auto maxToRead = jmin (numReadable, num);
const auto firstBlockSize = jmin (maxToRead, size - startPos);
readPos = (startPos + maxToRead) & (size - 1);
numReadable -= maxToRead;
return { { { startPos, startPos + firstBlockSize }, { 0, maxToRead - firstBlockSize } } };
}
private:
int size = 0, readPos = 0, numReadable = 0;
};
} // namespace juce

View File

@ -0,0 +1,489 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4512)
//==============================================================================
/**
Holds a set of unique primitive objects, such as ints or doubles.
A set can only hold one item with a given value, so if for example it's a
set of integers, attempting to add the same integer twice will do nothing
the second time.
Internally, the list of items is kept sorted (which means that whatever
kind of primitive type is used must support the ==, <, >, <= and >= operators
to determine the order), and searching the set for known values is very fast
because it uses a binary-chop method.
Note that if you're using a class or struct as the element type, it must be
capable of being copied or moved with a straightforward memcpy, rather than
needing construction and destruction code.
To make all the set's methods thread-safe, pass in "CriticalSection" as the templated
TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection.
@see Array, OwnedArray, ReferenceCountedArray, StringArray, CriticalSection
@tags{Core}
*/
template <class ElementType, class TypeOfCriticalSectionToUse = DummyCriticalSection>
class SortedSet
{
public:
//==============================================================================
/** Creates an empty set. */
SortedSet() = default;
/** Creates a copy of another set. */
SortedSet (const SortedSet&) = default;
/** Creates a copy of another set. */
SortedSet (SortedSet&&) noexcept = default;
/** Makes a copy of another set. */
SortedSet& operator= (const SortedSet&) = default;
/** Makes a copy of another set. */
SortedSet& operator= (SortedSet&&) noexcept = default;
/** Destructor. */
~SortedSet() = default;
//==============================================================================
/** Compares this set to another one.
Two sets are considered equal if they both contain the same set of elements.
@param other the other set to compare with
*/
bool operator== (const SortedSet<ElementType>& other) const noexcept
{
return data == other.data;
}
/** Compares this set to another one.
Two sets are considered equal if they both contain the same set of elements.
@param other the other set to compare with
*/
bool operator!= (const SortedSet<ElementType>& other) const noexcept
{
return ! operator== (other);
}
//==============================================================================
/** Removes all elements from the set.
This will remove all the elements, and free any storage that the set is
using. To clear it without freeing the storage, use the clearQuick()
method instead.
@see clearQuick
*/
void clear() noexcept
{
data.clear();
}
/** Removes all elements from the set without freeing the array's allocated storage.
@see clear
*/
void clearQuick() noexcept
{
data.clearQuick();
}
//==============================================================================
/** Returns the current number of elements in the set. */
inline int size() const noexcept
{
return data.size();
}
/** Returns true if the set is empty, false otherwise. */
inline bool isEmpty() const noexcept
{
return size() == 0;
}
/** Returns one of the elements in the set.
If the index passed in is beyond the range of valid elements, this
will return zero.
If you're certain that the index will always be a valid element, you
can call getUnchecked() instead, which is faster.
@param index the index of the element being requested (0 is the first element in the set)
@see getUnchecked, getFirst, getLast
*/
inline ElementType operator[] (const int index) const noexcept
{
return data [index];
}
/** Returns one of the elements in the set, without checking the index passed in.
Unlike the operator[] method, this will try to return an element without
checking that the index is within the bounds of the set, so should only
be used when you're confident that it will always be a valid index.
@param index the index of the element being requested (0 is the first element in the set)
@see operator[], getFirst, getLast
*/
inline ElementType getUnchecked (const int index) const noexcept
{
return data.getUnchecked (index);
}
/** Returns a direct reference to one of the elements in the set, without checking the index passed in.
This is like getUnchecked, but returns a direct reference to the element, so that
you can alter it directly. Obviously this can be dangerous, so only use it when
absolutely necessary.
@param index the index of the element being requested (0 is the first element in the array)
*/
inline ElementType& getReference (const int index) noexcept
{
return data.getReference (index);
}
/** Returns a direct reference to one of the elements in the set, without checking the index passed in.
@param index the index of the element being requested (0 is the first element in the array)
*/
inline const ElementType& getReference (const int index) const noexcept
{
return data.getReference (index);
}
/** Returns the first element in the set, or 0 if the set is empty.
@see operator[], getUnchecked, getLast
*/
inline ElementType getFirst() const noexcept
{
return data.getFirst();
}
/** Returns the last element in the set, or 0 if the set is empty.
@see operator[], getUnchecked, getFirst
*/
inline ElementType getLast() const noexcept
{
return data.getLast();
}
//==============================================================================
/** Returns a pointer to the first element in the set.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline const ElementType* begin() const noexcept
{
return data.begin();
}
/** Returns a pointer to the element which follows the last element in the set.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline const ElementType* end() const noexcept
{
return data.end();
}
//==============================================================================
/** Finds the index of the first element which matches the value passed in.
This will search the set for the given object, and return the index
of its first occurrence. If the object isn't found, the method will return -1.
@param elementToLookFor the value or object to look for
@returns the index of the object, or -1 if it's not found
*/
int indexOf (const ElementType& elementToLookFor) const noexcept
{
const ScopedLockType lock (data.getLock());
int s = 0;
int e = data.size();
for (;;)
{
if (s >= e)
return -1;
if (elementToLookFor == data.getReference (s))
return s;
auto halfway = (s + e) / 2;
if (halfway == s)
return -1;
if (elementToLookFor < data.getReference (halfway))
e = halfway;
else
s = halfway;
}
}
/** Returns true if the set contains at least one occurrence of an object.
@param elementToLookFor the value or object to look for
@returns true if the item is found
*/
bool contains (const ElementType& elementToLookFor) const noexcept
{
return indexOf (elementToLookFor) >= 0;
}
//==============================================================================
/** Adds a new element to the set, (as long as it's not already in there).
Note that if a matching element already exists, the new value will be assigned
to the existing one using operator=, so that if there are any differences between
the objects which were not recognised by the object's operator==, then the
set will always contain a copy of the most recently added one.
@param newElement the new object to add to the set
@returns true if the value was added, or false if it already existed
@see set, insert, addIfNotAlreadyThere, addSorted, addSet, addArray
*/
bool add (const ElementType& newElement) noexcept
{
const ScopedLockType lock (getLock());
int s = 0;
int e = data.size();
while (s < e)
{
auto& elem = data.getReference (s);
if (newElement == elem)
{
elem = newElement; // force an update in case operator== permits differences.
return false;
}
auto halfway = (s + e) / 2;
bool isBeforeHalfway = (newElement < data.getReference (halfway));
if (halfway == s)
{
if (! isBeforeHalfway)
++s;
break;
}
if (isBeforeHalfway)
e = halfway;
else
s = halfway;
}
data.insert (s, newElement);
return true;
}
/** Adds elements from an array to this set.
@param elementsToAdd the array of elements to add
@param numElementsToAdd how many elements are in this other array
@see add
*/
void addArray (const ElementType* elementsToAdd,
int numElementsToAdd) noexcept
{
const ScopedLockType lock (getLock());
while (--numElementsToAdd >= 0)
add (*elementsToAdd++);
}
/** Adds elements from another set to this one.
@param setToAddFrom the set from which to copy the elements
@param startIndex the first element of the other set to start copying from
@param numElementsToAdd how many elements to add from the other set. If this
value is negative or greater than the number of available elements,
all available elements will be copied.
@see add
*/
template <class OtherSetType>
void addSet (const OtherSetType& setToAddFrom,
int startIndex = 0,
int numElementsToAdd = -1) noexcept
{
const typename OtherSetType::ScopedLockType lock1 (setToAddFrom.getLock());
const ScopedLockType lock2 (getLock());
jassert (this != &setToAddFrom);
if (this != &setToAddFrom)
{
if (startIndex < 0)
{
jassertfalse;
startIndex = 0;
}
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > setToAddFrom.size())
numElementsToAdd = setToAddFrom.size() - startIndex;
if (numElementsToAdd > 0)
addArray (&setToAddFrom.data.getReference (startIndex), numElementsToAdd);
}
}
//==============================================================================
/** Removes an element from the set.
This will remove the element at a given index.
If the index passed in is out-of-range, nothing will happen.
@param indexToRemove the index of the element to remove
@returns the element that has been removed
@see removeValue, removeRange
*/
ElementType remove (const int indexToRemove) noexcept
{
return data.removeAndReturn (indexToRemove);
}
/** Removes an item from the set.
This will remove the given element from the set, if it's there.
@param valueToRemove the object to try to remove
@see remove, removeRange
*/
void removeValue (const ElementType& valueToRemove) noexcept
{
const ScopedLockType lock (getLock());
data.remove (indexOf (valueToRemove));
}
/** Removes any elements which are also in another set.
@param otherSet the other set in which to look for elements to remove
@see removeValuesNotIn, remove, removeValue, removeRange
*/
template <class OtherSetType>
void removeValuesIn (const OtherSetType& otherSet) noexcept
{
const typename OtherSetType::ScopedLockType lock1 (otherSet.getLock());
const ScopedLockType lock2 (getLock());
if (this == &otherSet)
{
clear();
}
else if (! otherSet.isEmpty())
{
for (int i = data.size(); --i >= 0;)
if (otherSet.contains (data.getReference (i)))
remove (i);
}
}
/** Removes any elements which are not found in another set.
Only elements which occur in this other set will be retained.
@param otherSet the set in which to look for elements NOT to remove
@see removeValuesIn, remove, removeValue, removeRange
*/
template <class OtherSetType>
void removeValuesNotIn (const OtherSetType& otherSet) noexcept
{
const typename OtherSetType::ScopedLockType lock1 (otherSet.getLock());
const ScopedLockType lock2 (getLock());
if (this != &otherSet)
{
if (otherSet.isEmpty())
{
clear();
}
else
{
for (int i = data.size(); --i >= 0;)
if (! otherSet.contains (data.getReference (i)))
remove (i);
}
}
}
/** This swaps the contents of this array with those of another array.
If you need to exchange two arrays, this is vastly quicker than using copy-by-value
because it just swaps their internal pointers.
*/
template <class OtherSetType>
void swapWith (OtherSetType& otherSet) noexcept
{
data.swapWith (otherSet.data);
}
//==============================================================================
/** Reduces the amount of storage being used by the set.
Sets typically allocate slightly more storage than they need, and after
removing elements, they may have quite a lot of unused space allocated.
This method will reduce the amount of allocated storage to a minimum.
*/
void minimiseStorageOverheads() noexcept
{
data.minimiseStorageOverheads();
}
/** Increases the set's internal storage to hold a minimum number of elements.
Calling this before adding a large known number of elements means that
the set won't have to keep dynamically resizing itself as the elements
are added, and it'll therefore be more efficient.
*/
void ensureStorageAllocated (const int minNumElements)
{
data.ensureStorageAllocated (minNumElements);
}
//==============================================================================
/** Returns the CriticalSection that locks this array.
To lock, you can call getLock().enter() and getLock().exit(), or preferably use
an object of ScopedLockType as an RAII lock for it.
*/
inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data.getLock(); }
/** Returns the type of scoped lock to use for locking this array */
using ScopedLockType = typename TypeOfCriticalSectionToUse::ScopedLockType;
private:
//==============================================================================
Array<ElementType, TypeOfCriticalSectionToUse> data;
};
JUCE_END_IGNORE_WARNINGS_MSVC
} // namespace juce

View File

@ -0,0 +1,206 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
#if JUCE_UNIT_TESTS
class SparseSetTests : public UnitTest
{
public:
SparseSetTests()
: UnitTest ("SparseSet class", UnitTestCategories::containers)
{}
void runTest() override
{
beginTest ("basic operations");
{
SparseSet<int> set;
expect (set.isEmpty());
expectEquals (set.size(), 0);
expectEquals (set.getNumRanges(), 0);
expect (set.getTotalRange().isEmpty());
set.addRange ({0, 10});
expect (! set.isEmpty());
expectEquals (set.size(), 10);
expectEquals (set.getNumRanges(), 1);
expect (! set.getTotalRange().isEmpty());
expect (set.getRange (0) == Range<int> (0, 10));
expectEquals (set[0], 0);
expectEquals (set[5], 5);
expectEquals (set[9], 9);
// Index out of range yields a default value for a type
expectEquals (set[10], 0);
expect (set.contains (0));
expect (set.contains (9));
expect (! set.contains (10));
}
beginTest ("adding ranges");
{
SparseSet<int> set;
// Adding same range twice should yield just a single range
set.addRange ({0, 10});
set.addRange ({0, 10});
expectEquals (set.getNumRanges(), 1);
expect (set.getRange (0) == Range<int> (0, 10));
// Adding already included range does not increase num ranges
set.addRange ({0, 2});
expectEquals (set.getNumRanges(), 1);
set.addRange ({8, 10});
expectEquals (set.getNumRanges(), 1);
set.addRange ({2, 5});
expectEquals (set.getNumRanges(), 1);
// Adding non adjacent range includes total number of ranges
set.addRange ({-10, -5});
expectEquals (set.getNumRanges(), 2);
expect (set.getRange (0) == Range<int> (-10, -5));
expect (set.getRange (1) == Range<int> (0, 10));
expect (set.getTotalRange() == Range<int> (-10, 10));
set.addRange ({15, 20});
expectEquals (set.getNumRanges(), 3);
expect (set.getRange (0) == Range<int> (-10, -5));
expect (set.getRange (1) == Range<int> (0, 10));
expect (set.getRange (2) == Range<int> (15, 20));
expect (set.getTotalRange() == Range<int> (-10, 20));
// Adding adjacent ranges merges them.
set.addRange ({-5, -3});
expectEquals (set.getNumRanges(), 3);
expect (set.getRange (0) == Range<int> (-10, -3));
expect (set.getRange (1) == Range<int> (0, 10));
expect (set.getRange (2) == Range<int> (15, 20));
expect (set.getTotalRange() == Range<int> (-10, 20));
set.addRange ({20, 25});
expectEquals (set.getNumRanges(), 3);
expect (set.getRange (0) == Range<int> (-10, -3));
expect (set.getRange (1) == Range<int> (0, 10));
expect (set.getRange (2) == Range<int> (15, 25));
expect (set.getTotalRange() == Range<int> (-10, 25));
// Adding range containing other ranges merges them
set.addRange ({-50, 50});
expectEquals (set.getNumRanges(), 1);
expect (set.getRange (0) == Range<int> (-50, 50));
expect (set.getTotalRange() == Range<int> (-50, 50));
}
beginTest ("removing ranges");
{
SparseSet<int> set;
set.addRange ({-20, -10});
set.addRange ({0, 10});
set.addRange ({20, 30});
expectEquals (set.getNumRanges(), 3);
// Removing ranges not included in the set has no effect
set.removeRange ({-5, 5});
expectEquals (set.getNumRanges(), 3);
// Removing partially overlapping range
set.removeRange ({-15, 5});
expectEquals (set.getNumRanges(), 3);
expect (set.getRange (0) == Range<int> (-20, -15));
expect (set.getRange (1) == Range<int> (5, 10));
expect (set.getRange (2) == Range<int> (20, 30));
// Removing subrange of existing range
set.removeRange ({20, 22});
expectEquals (set.getNumRanges(), 3);
expect (set.getRange (2) == Range<int> (22, 30));
set.removeRange ({28, 30});
expectEquals (set.getNumRanges(), 3);
expect (set.getRange (2) == Range<int> (22, 28));
set.removeRange ({24, 26});
expectEquals (set.getNumRanges(), 4);
expect (set.getRange (0) == Range<int> (-20, -15));
expect (set.getRange (1) == Range<int> (5, 10));
expect (set.getRange (2) == Range<int> (22, 24));
expect (set.getRange (3) == Range<int> (26, 28));
}
beginTest ("XORing ranges");
{
SparseSet<int> set;
set.addRange ({0, 10});
set.invertRange ({0, 10});
expectEquals (set.getNumRanges(), 0);
set.invertRange ({0, 10});
expectEquals (set.getNumRanges(), 1);
set.invertRange ({4, 6});
expectEquals (set.getNumRanges(), 2);
expect (set.getRange (0) == Range<int> (0, 4));
expect (set.getRange (1) == Range<int> (6, 10));
set.invertRange ({-2, 2});
expectEquals (set.getNumRanges(), 3);
expect (set.getRange (0) == Range<int> (-2, 0));
expect (set.getRange (1) == Range<int> (2, 4));
expect (set.getRange (2) == Range<int> (6, 10));
}
beginTest ("range contains & overlaps checks");
{
SparseSet<int> set;
set.addRange ({0, 10});
expect (set.containsRange (Range<int> (0, 2)));
expect (set.containsRange (Range<int> (8, 10)));
expect (set.containsRange (Range<int> (0, 10)));
expect (! set.containsRange (Range<int> (-2, 0)));
expect (! set.containsRange (Range<int> (-2, 10)));
expect (! set.containsRange (Range<int> (10, 12)));
expect (! set.containsRange (Range<int> (0, 12)));
expect (set.overlapsRange (Range<int> (0, 2)));
expect (set.overlapsRange (Range<int> (8, 10)));
expect (set.overlapsRange (Range<int> (0, 10)));
expect (! set.overlapsRange (Range<int> (-2, 0)));
expect ( set.overlapsRange (Range<int> (-2, 10)));
expect (! set.overlapsRange (Range<int> (10, 12)));
expect ( set.overlapsRange (Range<int> (0, 12)));
}
}
};
static SparseSetTests sparseSetTests;
#endif
} // namespace juce

View File

@ -0,0 +1,268 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
//==============================================================================
/**
Holds a set of primitive values, storing them as a set of ranges.
This container acts like an array, but can efficiently hold large contiguous
ranges of values. It's quite a specialised class, mostly useful for things
like keeping the set of selected rows in a listbox.
The type used as a template parameter must be an integer type, such as int, short,
int64, etc.
@tags{Core}
*/
template <class Type>
class SparseSet
{
public:
//==============================================================================
SparseSet() = default;
SparseSet (const SparseSet&) = default;
SparseSet& operator= (const SparseSet&) = default;
SparseSet (SparseSet&& other) noexcept : ranges (std::move (other.ranges)) {}
SparseSet& operator= (SparseSet&& other) noexcept { ranges = std::move (other.ranges); return *this; }
//==============================================================================
/** Clears the set. */
void clear() { ranges.clear(); }
/** Checks whether the set is empty.
This is much quicker than using (size() == 0).
*/
bool isEmpty() const noexcept { return ranges.isEmpty(); }
/** Returns the number of values in the set.
Because of the way the data is stored, this method can take longer if there
are a lot of items in the set. Use isEmpty() for a quick test of whether there
are any items.
*/
Type size() const noexcept
{
Type total = {};
for (auto& r : ranges)
total += r.getLength();
return total;
}
/** Returns one of the values in the set.
@param index the index of the value to retrieve, in the range 0 to (size() - 1).
@returns the value at this index, or 0 if it's out-of-range
*/
Type operator[] (Type index) const noexcept
{
Type total = {};
for (auto& r : ranges)
{
auto end = total + r.getLength();
if (index < end)
return r.getStart() + (index - total);
total = end;
}
return {};
}
/** Checks whether a particular value is in the set. */
bool contains (Type valueToLookFor) const noexcept
{
for (auto& r : ranges)
{
if (r.getStart() > valueToLookFor)
break;
if (r.getEnd() > valueToLookFor)
return true;
}
return false;
}
//==============================================================================
/** Returns the number of contiguous blocks of values.
@see getRange
*/
int getNumRanges() const noexcept { return ranges.size(); }
/** Returns one of the contiguous ranges of values stored.
@param rangeIndex the index of the range to look up, between 0
and (getNumRanges() - 1)
@see getTotalRange
*/
Range<Type> getRange (int rangeIndex) const noexcept { return ranges[rangeIndex]; }
/** Returns the range between the lowest and highest values in the set.
@see getRange
*/
Range<Type> getTotalRange() const noexcept
{
if (ranges.isEmpty())
return {};
return { ranges.getFirst().getStart(),
ranges.getLast().getEnd() };
}
//==============================================================================
/** Adds a range of contiguous values to the set.
e.g. addRange (Range \<int\> (10, 14)) will add (10, 11, 12, 13) to the set.
*/
void addRange (Range<Type> range)
{
if (! range.isEmpty())
{
removeRange (range);
ranges.add (range);
std::sort (ranges.begin(), ranges.end(),
[] (Range<Type> a, Range<Type> b) { return a.getStart() < b.getStart(); });
simplify();
}
}
/** Removes a range of values from the set.
e.g. removeRange (Range\<int\> (10, 14)) will remove (10, 11, 12, 13) from the set.
*/
void removeRange (Range<Type> rangeToRemove)
{
if (getTotalRange().intersects (rangeToRemove) && ! rangeToRemove.isEmpty())
{
for (int i = ranges.size(); --i >= 0;)
{
auto& r = ranges.getReference(i);
if (r.getEnd() <= rangeToRemove.getStart())
break;
if (r.getStart() >= rangeToRemove.getEnd())
continue;
if (rangeToRemove.contains (r))
{
ranges.remove (i);
}
else if (r.contains (rangeToRemove))
{
auto r1 = r.withEnd (rangeToRemove.getStart());
auto r2 = r.withStart (rangeToRemove.getEnd());
// this should be covered in if (rangeToRemove.contains (r))
jassert (! r1.isEmpty() || ! r2.isEmpty());
r = r1;
if (r.isEmpty())
r = r2;
if (! r1.isEmpty() && ! r2.isEmpty())
ranges.insert (i + 1, r2);
}
else if (rangeToRemove.getEnd() > r.getEnd())
{
r.setEnd (rangeToRemove.getStart());
}
else
{
r.setStart (rangeToRemove.getEnd());
}
}
}
}
/** Does an XOR of the values in a given range. */
void invertRange (Range<Type> range)
{
SparseSet newItems;
newItems.addRange (range);
for (auto& r : ranges)
newItems.removeRange (r);
removeRange (range);
for (auto& r : newItems.ranges)
addRange (r);
}
/** Checks whether any part of a given range overlaps any part of this set. */
bool overlapsRange (Range<Type> range) const noexcept
{
if (! range.isEmpty())
for (auto& r : ranges)
if (r.intersects (range))
return true;
return false;
}
/** Checks whether the whole of a given range is contained within this one. */
bool containsRange (Range<Type> range) const noexcept
{
if (! range.isEmpty())
for (auto& r : ranges)
if (r.contains (range))
return true;
return false;
}
/** Returns the set as a list of ranges, which you may want to iterate over. */
const Array<Range<Type>>& getRanges() const noexcept { return ranges; }
//==============================================================================
bool operator== (const SparseSet& other) const noexcept { return ranges == other.ranges; }
bool operator!= (const SparseSet& other) const noexcept { return ranges != other.ranges; }
private:
//==============================================================================
Array<Range<Type>> ranges;
void simplify()
{
for (int i = ranges.size(); --i > 0;)
{
auto& r1 = ranges.getReference (i - 1);
auto& r2 = ranges.getReference (i);
if (r1.getEnd() == r2.getStart())
{
r1.setEnd (r2.getEnd());
ranges.remove (i);
}
}
}
};
} // namespace juce

View File

@ -0,0 +1,909 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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
{
enum VariantStreamMarkers
{
varMarker_Int = 1,
varMarker_BoolTrue = 2,
varMarker_BoolFalse = 3,
varMarker_Double = 4,
varMarker_String = 5,
varMarker_Int64 = 6,
varMarker_Array = 7,
varMarker_Binary = 8,
varMarker_Undefined = 9
};
//==============================================================================
struct var::VariantType
{
struct VoidTag {};
struct UndefinedTag {};
struct IntTag {};
struct Int64Tag {};
struct DoubleTag {};
struct BoolTag {};
struct StringTag {};
struct ObjectTag {};
struct ArrayTag {};
struct BinaryTag {};
struct MethodTag {};
// members =====================================================================
bool isVoid = false;
bool isUndefined = false;
bool isInt = false;
bool isInt64 = false;
bool isBool = false;
bool isDouble = false;
bool isString = false;
bool isObject = false;
bool isArray = false;
bool isBinary = false;
bool isMethod = false;
bool isComparable = false;
int (*toInt) (const ValueUnion&) = defaultToInt;
int64 (*toInt64) (const ValueUnion&) = defaultToInt64;
double (*toDouble) (const ValueUnion&) = defaultToDouble;
String (*toString) (const ValueUnion&) = defaultToString;
bool (*toBool) (const ValueUnion&) = defaultToBool;
ReferenceCountedObject* (*toObject) (const ValueUnion&) = defaultToObject;
Array<var>* (*toArray) (const ValueUnion&) = defaultToArray;
MemoryBlock* (*toBinary) (const ValueUnion&) = defaultToBinary;
var (*clone) (const var&) = defaultClone;
void (*cleanUp) (ValueUnion&) = defaultCleanUp;
void (*createCopy) (ValueUnion&, const ValueUnion&) = defaultCreateCopy;
bool (*equals) (const ValueUnion&, const ValueUnion&, const VariantType&) = nullptr;
void (*writeToStream) (const ValueUnion&, OutputStream&) = nullptr;
// defaults ====================================================================
static int defaultToInt (const ValueUnion&) { return 0; }
static int64 defaultToInt64 (const ValueUnion&) { return 0; }
static double defaultToDouble (const ValueUnion&) { return 0; }
static String defaultToString (const ValueUnion&) { return {}; }
static bool defaultToBool (const ValueUnion&) { return false; }
static ReferenceCountedObject* defaultToObject (const ValueUnion&) { return nullptr; }
static Array<var>* defaultToArray (const ValueUnion&) { return nullptr; }
static MemoryBlock* defaultToBinary (const ValueUnion&) { return nullptr; }
static var defaultClone (const var& other) { return other; }
static void defaultCleanUp (ValueUnion&) {}
static void defaultCreateCopy (ValueUnion& dest, const ValueUnion& source) { dest = source; }
// void ========================================================================
static bool voidEquals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) noexcept
{
return otherType.isVoid || otherType.isUndefined;
}
static void voidWriteToStream (const ValueUnion&, OutputStream& output)
{
output.writeCompressedInt (0);
}
constexpr explicit VariantType (VoidTag) noexcept
: isVoid (true),
isComparable (true),
equals (voidEquals),
writeToStream (voidWriteToStream) {}
// undefined ===================================================================
static String undefinedToString (const ValueUnion&) { return "undefined"; }
static bool undefinedEquals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) noexcept
{
return otherType.isVoid || otherType.isUndefined;
}
static void undefinedWriteToStream (const ValueUnion&, OutputStream& output)
{
output.writeCompressedInt (1);
output.writeByte (varMarker_Undefined);
}
constexpr explicit VariantType (UndefinedTag) noexcept
: isUndefined (true),
toString (undefinedToString),
equals (undefinedEquals),
writeToStream (undefinedWriteToStream) {}
// int =========================================================================
static int intToInt (const ValueUnion& data) noexcept { return data.intValue; }
static int64 intToInt64 (const ValueUnion& data) noexcept { return (int64) data.intValue; }
static double intToDouble (const ValueUnion& data) noexcept { return (double) data.intValue; }
static String intToString (const ValueUnion& data) { return String (data.intValue); }
static bool intToBool (const ValueUnion& data) noexcept { return data.intValue != 0; }
static bool intEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept
{
if (otherType.isDouble || otherType.isInt64 || otherType.isString)
return otherType.equals (otherData, data, VariantType { IntTag{} });
return otherType.toInt (otherData) == data.intValue;
}
static void intWriteToStream (const ValueUnion& data, OutputStream& output)
{
output.writeCompressedInt (5);
output.writeByte (varMarker_Int);
output.writeInt (data.intValue);
}
constexpr explicit VariantType (IntTag) noexcept
: isInt (true),
isComparable (true),
toInt (intToInt),
toInt64 (intToInt64),
toDouble (intToDouble),
toString (intToString),
toBool (intToBool),
equals (intEquals),
writeToStream (intWriteToStream) {}
// int64 =======================================================================
static int int64ToInt (const ValueUnion& data) noexcept { return (int) data.int64Value; }
static int64 int64ToInt64 (const ValueUnion& data) noexcept { return data.int64Value; }
static double int64ToDouble (const ValueUnion& data) noexcept { return (double) data.int64Value; }
static String int64ToString (const ValueUnion& data) { return String (data.int64Value); }
static bool int64ToBool (const ValueUnion& data) noexcept { return data.int64Value != 0; }
static bool int64Equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept
{
if (otherType.isDouble || otherType.isString)
return otherType.equals (otherData, data, VariantType { Int64Tag{} });
return otherType.toInt64 (otherData) == data.int64Value;
}
static void int64WriteToStream (const ValueUnion& data, OutputStream& output)
{
output.writeCompressedInt (9);
output.writeByte (varMarker_Int64);
output.writeInt64 (data.int64Value);
}
constexpr explicit VariantType (Int64Tag) noexcept
: isInt64 (true),
isComparable (true),
toInt (int64ToInt),
toInt64 (int64ToInt64),
toDouble (int64ToDouble),
toString (int64ToString),
toBool (int64ToBool),
equals (int64Equals),
writeToStream (int64WriteToStream) {}
// double ======================================================================
static int doubleToInt (const ValueUnion& data) noexcept { return (int) data.doubleValue; }
static int64 doubleToInt64 (const ValueUnion& data) noexcept { return (int64) data.doubleValue; }
static double doubleToDouble (const ValueUnion& data) noexcept { return data.doubleValue; }
static String doubleToString (const ValueUnion& data) { return serialiseDouble (data.doubleValue); }
static bool doubleToBool (const ValueUnion& data) noexcept { return data.doubleValue != 0.0; }
static bool doubleEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept
{
return std::abs (otherType.toDouble (otherData) - data.doubleValue) < std::numeric_limits<double>::epsilon();
}
static void doubleWriteToStream (const ValueUnion& data, OutputStream& output)
{
output.writeCompressedInt (9);
output.writeByte (varMarker_Double);
output.writeDouble (data.doubleValue);
}
constexpr explicit VariantType (DoubleTag) noexcept
: isDouble (true),
isComparable (true),
toInt (doubleToInt),
toInt64 (doubleToInt64),
toDouble (doubleToDouble),
toString (doubleToString),
toBool (doubleToBool),
equals (doubleEquals),
writeToStream (doubleWriteToStream) {}
// bool ========================================================================
static int boolToInt (const ValueUnion& data) noexcept { return data.boolValue ? 1 : 0; }
static int64 boolToInt64 (const ValueUnion& data) noexcept { return data.boolValue ? 1 : 0; }
static double boolToDouble (const ValueUnion& data) noexcept { return data.boolValue ? 1.0 : 0.0; }
static String boolToString (const ValueUnion& data) { return String::charToString (data.boolValue ? (juce_wchar) '1' : (juce_wchar) '0'); }
static bool boolToBool (const ValueUnion& data) noexcept { return data.boolValue; }
static bool boolEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept
{
return otherType.toBool (otherData) == data.boolValue;
}
static void boolWriteToStream (const ValueUnion& data, OutputStream& output)
{
output.writeCompressedInt (1);
output.writeByte (data.boolValue ? (char) varMarker_BoolTrue : (char) varMarker_BoolFalse);
}
constexpr explicit VariantType (BoolTag) noexcept
: isBool (true),
isComparable (true),
toInt (boolToInt),
toInt64 (boolToInt64),
toDouble (boolToDouble),
toString (boolToString),
toBool (boolToBool),
equals (boolEquals),
writeToStream (boolWriteToStream) {}
// string ======================================================================
static const String* getString (const ValueUnion& data) noexcept { return unalignedPointerCast<const String*> (data.stringValue); }
static String* getString ( ValueUnion& data) noexcept { return unalignedPointerCast<String*> (data.stringValue); }
static int stringToInt (const ValueUnion& data) noexcept { return getString (data)->getIntValue(); }
static int64 stringToInt64 (const ValueUnion& data) noexcept { return getString (data)->getLargeIntValue(); }
static double stringToDouble (const ValueUnion& data) noexcept { return getString (data)->getDoubleValue(); }
static String stringToString (const ValueUnion& data) { return *getString (data); }
static bool stringToBool (const ValueUnion& data) noexcept
{
return getString (data)->getIntValue() != 0
|| getString (data)->trim().equalsIgnoreCase ("true")
|| getString (data)->trim().equalsIgnoreCase ("yes");
}
static void stringCleanUp (ValueUnion& data) noexcept { getString (data)-> ~String(); }
static void stringCreateCopy (ValueUnion& dest, const ValueUnion& source) { new (dest.stringValue) String (*getString (source)); }
static bool stringEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept
{
return otherType.toString (otherData) == *getString (data);
}
static void stringWriteToStream (const ValueUnion& data, OutputStream& output)
{
auto* s = getString (data);
const size_t len = s->getNumBytesAsUTF8() + 1;
HeapBlock<char> temp (len);
s->copyToUTF8 (temp, len);
output.writeCompressedInt ((int) (len + 1));
output.writeByte (varMarker_String);
output.write (temp, len);
}
constexpr explicit VariantType (StringTag) noexcept
: isString (true),
isComparable (true),
toInt (stringToInt),
toInt64 (stringToInt64),
toDouble (stringToDouble),
toString (stringToString),
toBool (stringToBool),
cleanUp (stringCleanUp),
createCopy (stringCreateCopy),
equals (stringEquals),
writeToStream (stringWriteToStream) {}
// object ======================================================================
static String objectToString (const ValueUnion& data)
{
return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue);
}
static bool objectToBool (const ValueUnion& data) noexcept { return data.objectValue != nullptr; }
static ReferenceCountedObject* objectToObject (const ValueUnion& data) noexcept { return data.objectValue; }
static var objectClone (const var& original)
{
if (auto* d = original.getDynamicObject())
return d->clone().get();
jassertfalse; // can only clone DynamicObjects!
return {};
}
static void objectCleanUp (ValueUnion& data) noexcept { if (data.objectValue != nullptr) data.objectValue->decReferenceCount(); }
static void objectCreateCopy (ValueUnion& dest, const ValueUnion& source)
{
dest.objectValue = source.objectValue;
if (dest.objectValue != nullptr)
dest.objectValue->incReferenceCount();
}
static bool objectEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept
{
return otherType.toObject (otherData) == data.objectValue;
}
static void objectWriteToStream (const ValueUnion&, OutputStream& output)
{
jassertfalse; // Can't write an object to a stream!
output.writeCompressedInt (0);
}
constexpr explicit VariantType (ObjectTag) noexcept
: isObject (true),
toString (objectToString),
toBool (objectToBool),
toObject (objectToObject),
clone (objectClone),
cleanUp (objectCleanUp),
createCopy (objectCreateCopy),
equals (objectEquals),
writeToStream (objectWriteToStream) {}
// array =======================================================================
static String arrayToString (const ValueUnion&) { return "[Array]"; }
static ReferenceCountedObject* arrayToObject (const ValueUnion&) noexcept { return nullptr; }
static Array<var>* arrayToArray (const ValueUnion& data) noexcept
{
if (auto* a = dynamic_cast<RefCountedArray*> (data.objectValue))
return &(a->array);
return nullptr;
}
static bool arrayEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept
{
auto* thisArray = arrayToArray (data);
auto* otherArray = otherType.toArray (otherData);
return thisArray == otherArray || (thisArray != nullptr && otherArray != nullptr && *otherArray == *thisArray);
}
static var arrayClone (const var& original)
{
Array<var> arrayCopy;
if (auto* array = arrayToArray (original.value))
{
arrayCopy.ensureStorageAllocated (array->size());
for (auto& i : *array)
arrayCopy.add (i.clone());
}
return var (arrayCopy);
}
static void arrayWriteToStream (const ValueUnion& data, OutputStream& output)
{
if (auto* array = arrayToArray (data))
{
MemoryOutputStream buffer (512);
buffer.writeCompressedInt (array->size());
for (auto& i : *array)
i.writeToStream (buffer);
output.writeCompressedInt (1 + (int) buffer.getDataSize());
output.writeByte (varMarker_Array);
output << buffer;
}
}
struct RefCountedArray : public ReferenceCountedObject
{
RefCountedArray (const Array<var>& a) : array (a) { incReferenceCount(); }
RefCountedArray (Array<var>&& a) : array (std::move (a)) { incReferenceCount(); }
Array<var> array;
};
constexpr explicit VariantType (ArrayTag) noexcept
: isObject (true),
isArray (true),
toString (arrayToString),
toBool (objectToBool),
toObject (arrayToObject),
toArray (arrayToArray),
clone (arrayClone),
cleanUp (objectCleanUp),
createCopy (objectCreateCopy),
equals (arrayEquals),
writeToStream (arrayWriteToStream) {}
// binary ======================================================================
static void binaryCleanUp (ValueUnion& data) noexcept { delete data.binaryValue; }
static void binaryCreateCopy (ValueUnion& dest, const ValueUnion& source) { dest.binaryValue = new MemoryBlock (*source.binaryValue); }
static String binaryToString (const ValueUnion& data) { return data.binaryValue->toBase64Encoding(); }
static MemoryBlock* binaryToBinary (const ValueUnion& data) noexcept { return data.binaryValue; }
static bool binaryEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept
{
const MemoryBlock* const otherBlock = otherType.toBinary (otherData);
return otherBlock != nullptr && *otherBlock == *data.binaryValue;
}
static void binaryWriteToStream (const ValueUnion& data, OutputStream& output)
{
output.writeCompressedInt (1 + (int) data.binaryValue->getSize());
output.writeByte (varMarker_Binary);
output << *data.binaryValue;
}
constexpr explicit VariantType (BinaryTag) noexcept
: isBinary (true),
toString (binaryToString),
toBinary (binaryToBinary),
cleanUp (binaryCleanUp),
createCopy (binaryCreateCopy),
equals (binaryEquals),
writeToStream (binaryWriteToStream) {}
// method ======================================================================
static void methodCleanUp (ValueUnion& data) noexcept { if (data.methodValue != nullptr ) delete data.methodValue; }
static void methodCreateCopy (ValueUnion& dest, const ValueUnion& source) { dest.methodValue = new NativeFunction (*source.methodValue); }
static String methodToString (const ValueUnion&) { return "Method"; }
static bool methodToBool (const ValueUnion& data) noexcept { return data.methodValue != nullptr; }
static bool methodEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept
{
return otherType.isMethod && otherData.methodValue == data.methodValue;
}
static void methodWriteToStream (const ValueUnion&, OutputStream& output)
{
jassertfalse; // Can't write a method to a stream!
output.writeCompressedInt (0);
}
constexpr explicit VariantType (MethodTag) noexcept
: isMethod (true),
toString (methodToString),
toBool (methodToBool),
cleanUp (methodCleanUp),
createCopy (methodCreateCopy),
equals (methodEquals),
writeToStream (methodWriteToStream) {}
};
struct var::Instance
{
static constexpr VariantType attributesVoid { VariantType::VoidTag{} };
static constexpr VariantType attributesUndefined { VariantType::UndefinedTag{} };
static constexpr VariantType attributesInt { VariantType::IntTag{} };
static constexpr VariantType attributesInt64 { VariantType::Int64Tag{} };
static constexpr VariantType attributesBool { VariantType::BoolTag{} };
static constexpr VariantType attributesDouble { VariantType::DoubleTag{} };
static constexpr VariantType attributesMethod { VariantType::MethodTag{} };
static constexpr VariantType attributesArray { VariantType::ArrayTag{} };
static constexpr VariantType attributesString { VariantType::StringTag{} };
static constexpr VariantType attributesBinary { VariantType::BinaryTag{} };
static constexpr VariantType attributesObject { VariantType::ObjectTag{} };
};
constexpr var::VariantType var::Instance::attributesVoid;
constexpr var::VariantType var::Instance::attributesUndefined;
constexpr var::VariantType var::Instance::attributesInt;
constexpr var::VariantType var::Instance::attributesInt64;
constexpr var::VariantType var::Instance::attributesBool;
constexpr var::VariantType var::Instance::attributesDouble;
constexpr var::VariantType var::Instance::attributesMethod;
constexpr var::VariantType var::Instance::attributesArray;
constexpr var::VariantType var::Instance::attributesString;
constexpr var::VariantType var::Instance::attributesBinary;
constexpr var::VariantType var::Instance::attributesObject;
//==============================================================================
var::var() noexcept : type (&Instance::attributesVoid) {}
var::var (const VariantType& t) noexcept : type (&t) {}
var::~var() noexcept { type->cleanUp (value); }
//==============================================================================
var::var (const var& valueToCopy) : type (valueToCopy.type)
{
type->createCopy (value, valueToCopy.value);
}
var::var (const int v) noexcept : type (&Instance::attributesInt) { value.intValue = v; }
var::var (const int64 v) noexcept : type (&Instance::attributesInt64) { value.int64Value = v; }
var::var (const bool v) noexcept : type (&Instance::attributesBool) { value.boolValue = v; }
var::var (const double v) noexcept : type (&Instance::attributesDouble) { value.doubleValue = v; }
var::var (NativeFunction m) noexcept : type (&Instance::attributesMethod) { value.methodValue = new NativeFunction (m); }
var::var (const Array<var>& v) : type (&Instance::attributesArray) { value.objectValue = new VariantType::RefCountedArray (v); }
var::var (const String& v) : type (&Instance::attributesString) { new (value.stringValue) String (v); }
var::var (const char* const v) : type (&Instance::attributesString) { new (value.stringValue) String (v); }
var::var (const wchar_t* const v) : type (&Instance::attributesString) { new (value.stringValue) String (v); }
var::var (const void* v, size_t sz) : type (&Instance::attributesBinary) { value.binaryValue = new MemoryBlock (v, sz); }
var::var (const MemoryBlock& v) : type (&Instance::attributesBinary) { value.binaryValue = new MemoryBlock (v); }
var::var (const StringArray& v) : type (&Instance::attributesArray)
{
Array<var> strings;
strings.ensureStorageAllocated (v.size());
for (auto& i : v)
strings.add (var (i));
value.objectValue = new VariantType::RefCountedArray (strings);
}
var::var (ReferenceCountedObject* const object) : type (&Instance::attributesObject)
{
value.objectValue = object;
if (object != nullptr)
object->incReferenceCount();
}
var var::undefined() noexcept { return var (Instance::attributesUndefined); }
//==============================================================================
bool var::isVoid() const noexcept { return type->isVoid; }
bool var::isUndefined() const noexcept { return type->isUndefined; }
bool var::isInt() const noexcept { return type->isInt; }
bool var::isInt64() const noexcept { return type->isInt64; }
bool var::isBool() const noexcept { return type->isBool; }
bool var::isDouble() const noexcept { return type->isDouble; }
bool var::isString() const noexcept { return type->isString; }
bool var::isObject() const noexcept { return type->isObject; }
bool var::isArray() const noexcept { return type->isArray; }
bool var::isBinaryData() const noexcept { return type->isBinary; }
bool var::isMethod() const noexcept { return type->isMethod; }
var::operator int() const noexcept { return type->toInt (value); }
var::operator int64() const noexcept { return type->toInt64 (value); }
var::operator bool() const noexcept { return type->toBool (value); }
var::operator float() const noexcept { return (float) type->toDouble (value); }
var::operator double() const noexcept { return type->toDouble (value); }
String var::toString() const { return type->toString (value); }
var::operator String() const { return type->toString (value); }
ReferenceCountedObject* var::getObject() const noexcept { return type->toObject (value); }
Array<var>* var::getArray() const noexcept { return type->toArray (value); }
MemoryBlock* var::getBinaryData() const noexcept { return type->toBinary (value); }
DynamicObject* var::getDynamicObject() const noexcept { return dynamic_cast<DynamicObject*> (getObject()); }
//==============================================================================
void var::swapWith (var& other) noexcept
{
std::swap (type, other.type);
std::swap (value, other.value);
}
var& var::operator= (const var& v) { type->cleanUp (value); type = v.type; type->createCopy (value, v.value); return *this; }
var& var::operator= (const int v) { type->cleanUp (value); type = &Instance::attributesInt; value.intValue = v; return *this; }
var& var::operator= (const int64 v) { type->cleanUp (value); type = &Instance::attributesInt64; value.int64Value = v; return *this; }
var& var::operator= (const bool v) { type->cleanUp (value); type = &Instance::attributesBool; value.boolValue = v; return *this; }
var& var::operator= (const double v) { type->cleanUp (value); type = &Instance::attributesDouble; value.doubleValue = v; return *this; }
var& var::operator= (const char* const v) { type->cleanUp (value); type = &Instance::attributesString; new (value.stringValue) String (v); return *this; }
var& var::operator= (const wchar_t* const v) { type->cleanUp (value); type = &Instance::attributesString; new (value.stringValue) String (v); return *this; }
var& var::operator= (const String& v) { type->cleanUp (value); type = &Instance::attributesString; new (value.stringValue) String (v); return *this; }
var& var::operator= (const MemoryBlock& v) { type->cleanUp (value); type = &Instance::attributesBinary; value.binaryValue = new MemoryBlock (v); return *this; }
var& var::operator= (const Array<var>& v) { var v2 (v); swapWith (v2); return *this; }
var& var::operator= (ReferenceCountedObject* v) { var v2 (v); swapWith (v2); return *this; }
var& var::operator= (NativeFunction v) { var v2 (v); swapWith (v2); return *this; }
var::var (var&& other) noexcept
: type (other.type),
value (other.value)
{
other.type = &Instance::attributesVoid;
}
var& var::operator= (var&& other) noexcept
{
swapWith (other);
return *this;
}
var::var (String&& v) : type (&Instance::attributesString)
{
new (value.stringValue) String (std::move (v));
}
var::var (MemoryBlock&& v) : type (&Instance::attributesBinary)
{
value.binaryValue = new MemoryBlock (std::move (v));
}
var::var (Array<var>&& v) : type (&Instance::attributesArray)
{
value.objectValue = new VariantType::RefCountedArray (std::move (v));
}
var& var::operator= (String&& v)
{
type->cleanUp (value);
type = &Instance::attributesString;
new (value.stringValue) String (std::move (v));
return *this;
}
//==============================================================================
bool var::equals (const var& other) const noexcept
{
return type->equals (value, other.value, *other.type);
}
bool var::equalsWithSameType (const var& other) const noexcept
{
return hasSameTypeAs (other) && equals (other);
}
bool var::hasSameTypeAs (const var& other) const noexcept
{
return type == other.type;
}
bool canCompare (const var& v1, const var& v2)
{
return v1.type->isComparable && v2.type->isComparable;
}
static int compare (const var& v1, const var& v2)
{
if (v1.isString() && v2.isString())
return v1.toString().compare (v2.toString());
auto diff = static_cast<double> (v1) - static_cast<double> (v2);
return diff == 0 ? 0 : (diff < 0 ? -1 : 1);
}
bool operator== (const var& v1, const var& v2) { return v1.equals (v2); }
bool operator!= (const var& v1, const var& v2) { return ! v1.equals (v2); }
bool operator< (const var& v1, const var& v2) { return canCompare (v1, v2) && compare (v1, v2) < 0; }
bool operator> (const var& v1, const var& v2) { return canCompare (v1, v2) && compare (v1, v2) > 0; }
bool operator<= (const var& v1, const var& v2) { return canCompare (v1, v2) && compare (v1, v2) <= 0; }
bool operator>= (const var& v1, const var& v2) { return canCompare (v1, v2) && compare (v1, v2) >= 0; }
bool operator== (const var& v1, const String& v2) { return v1.toString() == v2; }
bool operator!= (const var& v1, const String& v2) { return v1.toString() != v2; }
bool operator== (const var& v1, const char* v2) { return v1.toString() == v2; }
bool operator!= (const var& v1, const char* v2) { return v1.toString() != v2; }
//==============================================================================
var var::clone() const noexcept
{
return type->clone (*this);
}
//==============================================================================
const var& var::operator[] (const Identifier& propertyName) const
{
if (auto* o = getDynamicObject())
return o->getProperty (propertyName);
return getNullVarRef();
}
const var& var::operator[] (const char* const propertyName) const
{
return operator[] (Identifier (propertyName));
}
var var::getProperty (const Identifier& propertyName, const var& defaultReturnValue) const
{
if (auto* o = getDynamicObject())
return o->getProperties().getWithDefault (propertyName, defaultReturnValue);
return defaultReturnValue;
}
bool var::hasProperty (const Identifier& propertyName) const noexcept
{
if (auto* o = getDynamicObject())
return o->hasProperty (propertyName);
return false;
}
var::NativeFunction var::getNativeFunction() const
{
return isMethod() && (value.methodValue != nullptr) ? *value.methodValue : nullptr;
}
var var::invoke (const Identifier& method, const var* arguments, int numArguments) const
{
if (auto* o = getDynamicObject())
return o->invokeMethod (method, var::NativeFunctionArgs (*this, arguments, numArguments));
return {};
}
var var::call (const Identifier& method) const
{
return invoke (method, nullptr, 0);
}
var var::call (const Identifier& method, const var& arg1) const
{
return invoke (method, &arg1, 1);
}
var var::call (const Identifier& method, const var& arg1, const var& arg2) const
{
var args[] = { arg1, arg2 };
return invoke (method, args, 2);
}
var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3)
{
var args[] = { arg1, arg2, arg3 };
return invoke (method, args, 3);
}
var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const
{
var args[] = { arg1, arg2, arg3, arg4 };
return invoke (method, args, 4);
}
var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const
{
var args[] = { arg1, arg2, arg3, arg4, arg5 };
return invoke (method, args, 5);
}
//==============================================================================
int var::size() const
{
if (auto array = getArray())
return array->size();
return 0;
}
const var& var::operator[] (int arrayIndex) const
{
auto array = getArray();
// When using this method, the var must actually be an array, and the index
// must be in-range!
jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size()));
return array->getReference (arrayIndex);
}
var& var::operator[] (int arrayIndex)
{
auto array = getArray();
// When using this method, the var must actually be an array, and the index
// must be in-range!
jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size()));
return array->getReference (arrayIndex);
}
Array<var>* var::convertToArray()
{
if (auto array = getArray())
return array;
Array<var> tempVar;
if (! isVoid())
tempVar.add (*this);
*this = tempVar;
return getArray();
}
void var::append (const var& n)
{
convertToArray()->add (n);
}
void var::remove (const int index)
{
if (auto array = getArray())
array->remove (index);
}
void var::insert (const int index, const var& n)
{
convertToArray()->insert (index, n);
}
void var::resize (const int numArrayElementsWanted)
{
convertToArray()->resize (numArrayElementsWanted);
}
int var::indexOf (const var& n) const
{
if (auto array = getArray())
return array->indexOf (n);
return -1;
}
//==============================================================================
void var::writeToStream (OutputStream& output) const
{
type->writeToStream (value, output);
}
var var::readFromStream (InputStream& input)
{
const int numBytes = input.readCompressedInt();
if (numBytes > 0)
{
switch (input.readByte())
{
case varMarker_Int: return var (input.readInt());
case varMarker_Int64: return var (input.readInt64());
case varMarker_BoolTrue: return var (true);
case varMarker_BoolFalse: return var (false);
case varMarker_Double: return var (input.readDouble());
case varMarker_String:
{
MemoryOutputStream mo;
mo.writeFromInputStream (input, numBytes - 1);
return var (mo.toUTF8());
}
case varMarker_Binary:
{
MemoryBlock mb ((size_t) numBytes - 1);
if (numBytes > 1)
{
const int numRead = input.read (mb.getData(), numBytes - 1);
mb.setSize ((size_t) numRead);
}
return var (mb);
}
case varMarker_Array:
{
var v;
auto* destArray = v.convertToArray();
for (int i = input.readCompressedInt(); --i >= 0;)
destArray->add (readFromStream (input));
return v;
}
default:
input.skipNextBytes (numBytes - 1); break;
}
}
return {};
}
var::NativeFunctionArgs::NativeFunctionArgs (const var& t, const var* args, int numArgs) noexcept
: thisObject (t), arguments (args), numArguments (numArgs)
{
}
//==============================================================================
#if JUCE_ALLOW_STATIC_NULL_VARIABLES
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996)
const var var::null;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
JUCE_END_IGNORE_WARNINGS_MSVC
#endif
} // namespace juce

View File

@ -0,0 +1,354 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
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 variant class, that can be used to hold a range of primitive values.
A var object can hold a range of simple primitive values, strings, or
any kind of ReferenceCountedObject. The var class is intended to act like
the kind of values used in dynamic scripting languages.
You can save/load var objects either in a small, proprietary binary format
using writeToStream()/readFromStream(), or as JSON by using the JSON class.
@see JSON, DynamicObject
@tags{Core}
*/
class JUCE_API var
{
public:
//==============================================================================
/** This structure is passed to a NativeFunction callback, and contains invocation
details about the function's arguments and context.
*/
struct JUCE_API NativeFunctionArgs
{
NativeFunctionArgs (const var& thisObject, const var* args, int numArgs) noexcept;
const var& thisObject;
const var* arguments;
int numArguments;
};
using NativeFunction = std::function<var (const NativeFunctionArgs&)>;
//==============================================================================
/** Creates a void variant. */
var() noexcept;
/** Destructor. */
~var() noexcept;
var (const var& valueToCopy);
var (int value) noexcept;
var (int64 value) noexcept;
var (bool value) noexcept;
var (double value) noexcept;
var (const char* value);
var (const wchar_t* value);
var (const String& value);
var (const Array<var>& value);
var (const StringArray& value);
var (ReferenceCountedObject* object);
var (NativeFunction method) noexcept;
var (const void* binaryData, size_t dataSize);
var (const MemoryBlock& binaryData);
var& operator= (const var& valueToCopy);
var& operator= (int value);
var& operator= (int64 value);
var& operator= (bool value);
var& operator= (double value);
var& operator= (const char* value);
var& operator= (const wchar_t* value);
var& operator= (const String& value);
var& operator= (const MemoryBlock& value);
var& operator= (const Array<var>& value);
var& operator= (ReferenceCountedObject* object);
var& operator= (NativeFunction method);
var (var&&) noexcept;
var (String&&);
var (MemoryBlock&&);
var (Array<var>&&);
var& operator= (var&&) noexcept;
var& operator= (String&&);
void swapWith (var& other) noexcept;
/** Returns a var object that can be used where you need the javascript "undefined" value. */
static var undefined() noexcept;
//==============================================================================
operator int() const noexcept;
operator int64() const noexcept;
operator bool() const noexcept;
operator float() const noexcept;
operator double() const noexcept;
operator String() const;
String toString() const;
/** If this variant holds an array, this provides access to it.
NOTE: Beware when you use this - the array pointer is only valid for the lifetime
of the variant that returned it, so be very careful not to call this method on temporary
var objects that are the return-value of a function, and which may go out of scope before
you use the array!
*/
Array<var>* getArray() const noexcept;
/** If this variant holds a memory block, this provides access to it.
NOTE: Beware when you use this - the MemoryBlock pointer is only valid for the lifetime
of the variant that returned it, so be very careful not to call this method on temporary
var objects that are the return-value of a function, and which may go out of scope before
you use the MemoryBlock!
*/
MemoryBlock* getBinaryData() const noexcept;
ReferenceCountedObject* getObject() const noexcept;
DynamicObject* getDynamicObject() const noexcept;
//==============================================================================
bool isVoid() const noexcept;
bool isUndefined() const noexcept;
bool isInt() const noexcept;
bool isInt64() const noexcept;
bool isBool() const noexcept;
bool isDouble() const noexcept;
bool isString() const noexcept;
bool isObject() const noexcept;
bool isArray() const noexcept;
bool isBinaryData() const noexcept;
bool isMethod() const noexcept;
/** Returns true if this var has the same value as the one supplied.
Note that this ignores the type, so a string var "123" and an integer var with the
value 123 are considered to be equal.
@see equalsWithSameType
*/
bool equals (const var& other) const noexcept;
/** Returns true if this var has the same value and type as the one supplied.
This differs from equals() because e.g. "123" and 123 will be considered different.
@see equals
*/
bool equalsWithSameType (const var& other) const noexcept;
/** Returns true if this var has the same type as the one supplied. */
bool hasSameTypeAs (const var& other) const noexcept;
/** Returns a deep copy of this object.
For simple types this just returns a copy, but if the object contains any arrays
or DynamicObjects, they will be cloned (recursively).
*/
var clone() const noexcept;
//==============================================================================
/** If the var is an array, this returns the number of elements.
If the var isn't actually an array, this will return 0.
*/
int size() const;
/** If the var is an array, this can be used to return one of its elements.
To call this method, you must make sure that the var is actually an array, and
that the index is a valid number. If these conditions aren't met, behaviour is
undefined.
For more control over the array's contents, you can call getArray() and manipulate
it directly as an Array\<var\>.
*/
const var& operator[] (int arrayIndex) const;
/** If the var is an array, this can be used to return one of its elements.
To call this method, you must make sure that the var is actually an array, and
that the index is a valid number. If these conditions aren't met, behaviour is
undefined.
For more control over the array's contents, you can call getArray() and manipulate
it directly as an Array\<var\>.
*/
var& operator[] (int arrayIndex);
/** Appends an element to the var, converting it to an array if it isn't already one.
If the var isn't an array, it will be converted to one, and if its value was non-void,
this value will be kept as the first element of the new array. The parameter value
will then be appended to it.
For more control over the array's contents, you can call getArray() and manipulate
it directly as an Array\<var\>.
*/
void append (const var& valueToAppend);
/** Inserts an element to the var, converting it to an array if it isn't already one.
If the var isn't an array, it will be converted to one, and if its value was non-void,
this value will be kept as the first element of the new array. The parameter value
will then be inserted into it.
For more control over the array's contents, you can call getArray() and manipulate
it directly as an Array\<var\>.
*/
void insert (int index, const var& value);
/** If the var is an array, this removes one of its elements.
If the index is out-of-range or the var isn't an array, nothing will be done.
For more control over the array's contents, you can call getArray() and manipulate
it directly as an Array\<var\>.
*/
void remove (int index);
/** Treating the var as an array, this resizes it to contain the specified number of elements.
If the var isn't an array, it will be converted to one, and if its value was non-void,
this value will be kept as the first element of the new array before resizing.
For more control over the array's contents, you can call getArray() and manipulate
it directly as an Array\<var\>.
*/
void resize (int numArrayElementsWanted);
/** If the var is an array, this searches it for the first occurrence of the specified value,
and returns its index.
If the var isn't an array, or if the value isn't found, this returns -1.
*/
int indexOf (const var& value) const;
//==============================================================================
/** If this variant is an object, this returns one of its properties. */
const var& operator[] (const Identifier& propertyName) const;
/** If this variant is an object, this returns one of its properties. */
const var& operator[] (const char* propertyName) const;
/** If this variant is an object, this returns one of its properties, or a default
fallback value if the property is not set. */
var getProperty (const Identifier& propertyName, const var& defaultReturnValue) const;
/** Returns true if this variant is an object and if it has the given property. */
bool hasProperty (const Identifier& propertyName) const noexcept;
/** Invokes a named method call with no arguments. */
var call (const Identifier& method) const;
/** Invokes a named method call with one argument. */
var call (const Identifier& method, const var& arg1) const;
/** Invokes a named method call with 2 arguments. */
var call (const Identifier& method, const var& arg1, const var& arg2) const;
/** Invokes a named method call with 3 arguments. */
var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3);
/** Invokes a named method call with 4 arguments. */
var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const;
/** Invokes a named method call with 5 arguments. */
var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const;
/** Invokes a named method call with a list of arguments. */
var invoke (const Identifier& method, const var* arguments, int numArguments) const;
/** If this object is a method, this returns the function pointer. */
NativeFunction getNativeFunction() const;
//==============================================================================
/** Writes a binary representation of this value to a stream.
The data can be read back later using readFromStream().
@see JSON
*/
void writeToStream (OutputStream& output) const;
/** Reads back a stored binary representation of a value.
The data in the stream must have been written using writeToStream(), or this
will have unpredictable results.
@see JSON
*/
static var readFromStream (InputStream& input);
//==============================================================================
#if JUCE_ALLOW_STATIC_NULL_VARIABLES && ! defined (DOXYGEN)
[[deprecated ("This was a static empty var object, but is now deprecated as it's too easy to accidentally "
"use it indirectly during a static constructor leading to hard-to-find order-of-initialisation "
"problems. Use var() or {} instead. For returning an empty var from a function by reference, "
"use a function-local static var and return that.")]]
static const var null;
#endif
private:
//==============================================================================
struct VariantType;
struct Instance;
union ValueUnion
{
int intValue;
int64 int64Value;
bool boolValue;
double doubleValue;
char stringValue[sizeof (String)];
ReferenceCountedObject* objectValue;
MemoryBlock* binaryValue;
NativeFunction* methodValue;
};
friend bool canCompare (const var&, const var&);
const VariantType* type;
ValueUnion value;
Array<var>* convertToArray();
var (const VariantType&) noexcept;
// This is needed to prevent the wrong constructor/operator being called
var (const ReferenceCountedObject*) = delete;
var& operator= (const ReferenceCountedObject*) = delete;
var (const void*) = delete;
var& operator= (const void*) = delete;
};
/** Compares the values of two var objects, using the var::equals() comparison. */
JUCE_API bool operator== (const var&, const var&);
/** Compares the values of two var objects, using the var::equals() comparison. */
JUCE_API bool operator!= (const var&, const var&);
/** Compares the values of two var objects, using the var::equals() comparison. */
JUCE_API bool operator< (const var&, const var&);
/** Compares the values of two var objects, using the var::equals() comparison. */
JUCE_API bool operator<= (const var&, const var&);
/** Compares the values of two var objects, using the var::equals() comparison. */
JUCE_API bool operator> (const var&, const var&);
/** Compares the values of two var objects, using the var::equals() comparison. */
JUCE_API bool operator>= (const var&, const var&);
JUCE_API bool operator== (const var&, const String&);
JUCE_API bool operator!= (const var&, const String&);
JUCE_API bool operator== (const var&, const char*);
JUCE_API bool operator!= (const var&, const char*);
//==============================================================================
/** This template-overloaded class can be used to convert between var and custom types.
@tags{Core}
*/
template <typename Type>
struct VariantConverter
{
static Type fromVar (const var& v) { return static_cast<Type> (v); }
static var toVar (const Type& t) { return t; }
};
#ifndef DOXYGEN
template <>
struct VariantConverter<String>
{
static String fromVar (const var& v) { return v.toString(); }
static var toVar (const String& s) { return s; }
};
#endif
} // namespace juce