40 for (
auto*
c :
other.children)
52 jassert (parent ==
nullptr);
54 for (
auto i = children.
size(); --i >= 0;)
56 const Ptr c (children.getObjectPointerUnchecked (i));
59 c->sendParentChangeMessage();
65 return parent ==
nullptr ? *
this : parent->getRoot();
68 template <
typename Function>
85 if (i == 0 || valueTreesWithListeners.
contains (
v))
91 template <
typename Function>
94 for (
auto*
t =
this;
t !=
nullptr;
t =
t->parent)
104 void sendChildAddedMessage (
ValueTree child)
107 callListenersForAllParents (
nullptr, [&] (
Listener&
l) {
l.valueTreeChildAdded (tree, child); });
110 void sendChildRemovedMessage (
ValueTree child,
int index)
113 callListenersForAllParents (
nullptr, [=, &tree, &child] (
Listener&
l) {
l.valueTreeChildRemoved (tree, child, index); });
122 void sendParentChangeMessage()
126 for (
auto j = children.
size(); --
j >= 0;)
127 if (
auto* child = children.getObjectPointer (
j))
128 child->sendParentChangeMessage();
130 callListeners (
nullptr, [&] (
Listener&
l) {
l.valueTreeParentChanged (tree); });
136 if (undoManager ==
nullptr)
138 if (properties.
set (name, newValue))
157 bool hasProperty (
const Identifier& name)
const noexcept
164 if (undoManager ==
nullptr)
166 if (properties.
remove (name))
167 sendPropertyChangeMessage (name);
176 void removeAllProperties (
UndoManager* undoManager)
178 if (undoManager ==
nullptr)
180 while (properties.
size() > 0)
182 auto name = properties.
getName (properties.
size() - 1);
184 sendPropertyChangeMessage (name);
189 for (
auto i = properties.
size(); --i >= 0;)
197 for (
auto i = properties.
size(); --i >= 0;)
199 removeProperty (properties.
getName (i), undoManager);
201 for (
int i = 0; i < source.properties.
size(); ++i)
202 setProperty (source.properties.
getName (i), source.properties.
getValueAt (i), undoManager);
207 for (
auto*
s : children)
216 for (
auto*
s : children)
227 for (
auto*
s : children)
236 for (
auto* p = parent; p !=
nullptr; p = p->parent)
243 int indexOf (
const ValueTree& child)
const noexcept
245 return children.
indexOf (child.object);
250 if (child !=
nullptr && child->parent !=
this)
252 if (child !=
this && ! isAChildOf (child))
257 jassert (child->parent ==
nullptr);
259 if (child->parent !=
nullptr)
261 jassert (child->parent->children.
indexOf (child) >= 0);
265 if (undoManager ==
nullptr)
267 children.
insert (index, child);
268 child->parent =
this;
269 sendChildAddedMessage (
ValueTree (*child));
270 child->sendParentChangeMessage();
274 if (! isPositiveAndBelow (index, children.
size()))
275 index = children.
size();
289 void removeChild (
int childIndex,
UndoManager* undoManager)
291 if (
auto child =
Ptr (children.getObjectPointer (childIndex)))
293 if (undoManager ==
nullptr)
295 children.
remove (childIndex);
296 child->parent =
nullptr;
297 sendChildRemovedMessage (
ValueTree (child), childIndex);
298 child->sendParentChangeMessage();
309 while (children.
size() > 0)
310 removeChild (children.
size() - 1, undoManager);
321 if (undoManager ==
nullptr)
340 for (
int i = 0; i < children.
size(); ++i)
344 if (children.getObjectPointerUnchecked (i) != child)
348 moveChild (
oldIndex, i, undoManager);
355 if (type !=
other.type
358 || properties !=
other.properties)
361 for (
int i = 0; i < children.
size(); ++i)
362 if (! children.getObjectPointerUnchecked (i)->isEquivalentTo (*
other.children.getObjectPointerUnchecked (i)))
374 for (
auto i = children.
size(); --i >= 0;)
375 xml->prependChildElement (children.getObjectPointerUnchecked (i)->createXml());
385 for (
int j = 0;
j < properties.
size(); ++
j)
393 for (
auto*
c : children)
394 writeObjectToStream (output,
c);
399 if (
object !=
nullptr)
401 object->writeToStream (output);
419 isAddingNewProperty (
isAdding), isDeletingProperty (isDeleting),
426 jassert (! (isAddingNewProperty && target->hasProperty (name)));
428 if (isDeletingProperty)
429 target->removeProperty (name,
nullptr);
431 target->setProperty (name, newValue,
nullptr, excludeListener);
438 if (isAddingNewProperty)
439 target->removeProperty (name,
nullptr);
441 target->setProperty (name, oldValue,
nullptr);
448 return (
int)
sizeof (*this);
453 if (! (isAddingNewProperty || isDeletingProperty))
456 if (next->target == target && next->name == name
457 && ! (next->isAddingNewProperty || next->isDeletingProperty))
458 return new SetPropertyAction (*target, name, next->newValue, oldValue,
false,
false);
469 const bool isAddingNewProperty : 1, isDeletingProperty : 1;
480 child (
newChild !=
nullptr ?
newChild : target->children.getObjectPointer (index)),
484 jassert (child !=
nullptr);
490 target->removeChild (childIndex,
nullptr);
492 target->addChild (child.get(), childIndex,
nullptr);
501 target->addChild (child.get(), childIndex,
nullptr);
508 target->removeChild (childIndex,
nullptr);
516 return (
int)
sizeof (*this);
520 const Ptr target, child;
521 const int childIndex;
522 const bool isDeleting;
537 parent->moveChild (startIndex, endIndex,
nullptr);
543 parent->moveChild (endIndex, startIndex,
nullptr);
549 return (
int)
sizeof (*this);
555 if (next->parent == parent && next->startIndex == endIndex)
563 const int startIndex, endIndex;
583JUCE_DECLARE_DEPRECATED_STATIC (
const ValueTree ValueTree::invalid;)
587 jassert (type.
toString().isNotEmpty());
591 std::initializer_list<NamedValueSet::NamedValue> properties,
592 std::initializer_list<ValueTree>
subTrees)
610 if (
object !=
other.object)
614 object =
other.object;
618 if (
object !=
nullptr)
619 object->valueTreesWithListeners.removeValue (
this);
621 if (
other.object !=
nullptr)
622 other.object->valueTreesWithListeners.
add (
this);
624 object =
other.object;
626 listeners.call ([
this] (
Listener&
l) {
l.valueTreeRedirected (*
this); });
634 : object (std::move (
other.object))
636 if (
object !=
nullptr)
637 object->valueTreesWithListeners.removeValue (&
other);
642 if (! listeners.
isEmpty() &&
object !=
nullptr)
643 object->valueTreesWithListeners.removeValue (
this);
648 return object ==
other.object;
653 return object !=
other.object;
658 return object ==
other.object
659 || (
object !=
nullptr &&
other.object !=
nullptr
660 &&
object->isEquivalentTo (*
other.object));
665 if (
object !=
nullptr)
673 jassert (
object !=
nullptr || source.object ==
nullptr);
675 if (source.object ==
nullptr)
677 else if (
object !=
nullptr)
678 object->copyPropertiesFrom (*(source.object), undoManager);
683 jassert (
object !=
nullptr || source.object ==
nullptr);
688 if (
object !=
nullptr && source.object !=
nullptr)
689 for (
auto& child : source.object->children)
690 object->addChild (createCopyIfNotNull (child), -1, undoManager);
695 return object !=
nullptr &&
object->type == typeName;
700 return object !=
nullptr ?
object->type :
Identifier();
705 if (
object !=
nullptr)
706 if (
auto p = object->parent)
714 if (
object !=
nullptr)
722 if (
object !=
nullptr)
723 if (
auto* p = object->parent)
724 if (
auto*
c = p->children.getObjectPointer (p->indexOf (*
this) +
delta))
738 return object ==
nullptr ? getNullVarRef() :
object->properties[name];
743 return object ==
nullptr ? getNullVarRef() :
object->properties[name];
754 return object ==
nullptr ?
nullptr
755 :
object->properties.getVarPointer (name);
766 jassert (name.
toString().isNotEmpty());
767 jassert (
object !=
nullptr);
769 if (
object !=
nullptr)
777 return object !=
nullptr &&
object->hasProperty (name);
782 if (
object !=
nullptr)
783 object->removeProperty (name, undoManager);
788 if (
object !=
nullptr)
789 object->removeAllProperties (undoManager);
794 return object ==
nullptr ? 0 :
object->properties.
size();
800 :
object->properties.getName (index);
805 return object !=
nullptr ?
object->getReferenceCount() : 0;
813 : tree (
vt), property (
prop), undoManager (
um), updateSynchronously (
sync)
830 const bool updateSynchronously;
838 void valueTreeChildAdded (ValueTree&, ValueTree&)
override {}
839 void valueTreeChildRemoved (ValueTree&, ValueTree&,
int)
override {}
840 void valueTreeChildOrderChanged (ValueTree&,
int,
int)
override {}
841 void valueTreeParentChanged (ValueTree&)
override {}
843 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreePropertyValueSource)
854 return object ==
nullptr ? 0 :
object->children.
size();
859 if (
object !=
nullptr)
860 if (
auto*
c = object->children.getObjectPointer (index))
867 : internal (
v.object !=
nullptr ? (
isEnd ?
v.object->children.end() :
v.object->children.begin()) :
nullptr)
871ValueTree::Iterator& ValueTree::Iterator::operator++()
873 internal =
static_cast<SharedObject**
> (internal) + 1;
877bool ValueTree::Iterator::operator== (
const Iterator& other)
const {
return internal == other.internal; }
878bool ValueTree::Iterator::operator!= (
const Iterator& other)
const {
return internal != other.internal; }
880ValueTree ValueTree::Iterator::operator*()
const
882 return ValueTree (SharedObject::Ptr (*
static_cast<SharedObject**
> (internal)));
905 return object !=
nullptr &&
object->isAChildOf (
possibleParent.object.get());
910 return object !=
nullptr ?
object->
indexOf (child) : -1;
915 jassert (
object !=
nullptr);
917 if (
object !=
nullptr)
918 object->addChild (child.object.get(), index, undoManager);
928 if (
object !=
nullptr)
929 object->removeChild (childIndex, undoManager);
934 if (
object !=
nullptr)
935 object->removeChild (object->children.
indexOf (child.object), undoManager);
940 if (
object !=
nullptr)
941 object->removeAllChildren (undoManager);
946 if (
object !=
nullptr)
953 jassert (
object !=
nullptr);
955 for (
auto*
o : object->children)
957 jassert (
o !=
nullptr);
962void ValueTree::reorderChildren (
const OwnedArray<ValueTree>& newOrder, UndoManager* undoManager)
964 jassert (
object !=
nullptr);
965 object->reorderChildren (newOrder, undoManager);
971 if (listener !=
nullptr)
973 if (listeners.
isEmpty() &&
object !=
nullptr)
974 object->valueTreesWithListeners.
add (
this);
976 listeners.
add (listener);
982 listeners.
remove (listener);
984 if (listeners.
isEmpty() &&
object !=
nullptr)
985 object->valueTreesWithListeners.removeValue (
this);
990 if (
object !=
nullptr)
991 object->sendPropertyChangeMessage (property);
997 return object !=
nullptr ?
object->createXml() :
nullptr;
1002 if (!
xml.isTextElement())
1005 v.object->properties.setFromXmlAttributes (
xml);
1007 forEachXmlChildElement (
xml,
e)
1023 return xml->createDocument ({});
1031 SharedObject::writeObjectToStream (output,
object.get());
1055 if (name.isNotEmpty())
1068 if (! child.isValid())
1071 v.object->children.
add (child.object);
1072 child.object->parent =
v.object.get();
1103 char buffer[50] = { 0 };
1104 const char chars[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
1106 for (
int i = 1 + r.
nextInt (numElementsInArray (buffer) - 2); --i >= 0;)
1107 buffer[i] = chars[r.
nextInt (sizeof (chars) - 1)];
1109 String result (buffer);
1111 if (! XmlElement::isValidXmlName (result))
1112 result = createRandomIdentifier (r);
1117 static String createRandomWideCharString (Random& r)
1119 juce_wchar buffer[50] = { 0 };
1121 for (
int i = r.nextInt (numElementsInArray (buffer) - 1); --i >= 0;)
1127 buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
1129 while (! CharPointer_UTF16::canRepresent (buffer[i]));
1132 buffer[i] = (juce_wchar) (1 + r.nextInt (0x7e));
1135 return CharPointer_UTF32 (buffer);
1138 static ValueTree createRandomTree (UndoManager* undoManager,
int depth, Random& r)
1140 ValueTree v (createRandomIdentifier (r));
1142 for (
int i = r.nextInt (10); --i >= 0;)
1144 switch (r.nextInt (5))
1146 case 0: v.setProperty (createRandomIdentifier (r), createRandomWideCharString (r), undoManager);
break;
1147 case 1: v.setProperty (createRandomIdentifier (r), r.nextInt(), undoManager);
break;
1148 case 2:
if (depth < 5) v.addChild (createRandomTree (undoManager, depth + 1, r), r.nextInt (v.getNumChildren() + 1), undoManager);
break;
1149 case 3: v.setProperty (createRandomIdentifier (r), r.nextBool(), undoManager);
break;
1150 case 4: v.setProperty (createRandomIdentifier (r), r.nextDouble(), undoManager);
break;
1158 void runTest()
override
1161 beginTest (
"ValueTree");
1163 auto r = getRandom();
1165 for (
int i = 10; --i >= 0;)
1167 MemoryOutputStream mo;
1168 auto v1 = createRandomTree (
nullptr, 0, r);
1169 v1.writeToStream (mo);
1171 MemoryInputStream mi (mo.getData(), mo.getDataSize(),
false);
1172 auto v2 = ValueTree::readFromStream (mi);
1173 expect (v1.isEquivalentTo (v2));
1175 MemoryOutputStream zipped;
1177 GZIPCompressorOutputStream zippedOut (zipped);
1178 v1.writeToStream (zippedOut);
1180 expect (v1.isEquivalentTo (ValueTree::readFromGZIPData (zipped.getData(), zipped.getDataSize())));
1182 std::unique_ptr<XmlElement> xml1 (v1.createXml());
1183 std::unique_ptr<XmlElement> xml2 (v2.createCopy().createXml());
1184 expect (xml1->isEquivalentTo (xml2.get(),
false));
1186 auto v4 = v2.createCopy();
1187 expect (v1.isEquivalentTo (v4));
1192 beginTest (
"Float formatting");
1194 ValueTree testVT (
"Test");
1195 Identifier number (
"number");
1197 std::map<double, String> tests;
1200 tests[1.01] =
"1.01";
1201 tests[0.76378] =
"0.76378";
1202 tests[-10] =
"-10.0";
1203 tests[10.01] =
"10.01";
1204 tests[0.0123] =
"0.0123";
1205 tests[-3.7e-27] =
"-3.7e-27";
1206 tests[1e+40] =
"1.0e40";
1207 tests[-12345678901234567.0] =
"-1.234567890123457e16";
1208 tests[192000] =
"192000.0";
1209 tests[1234567] =
"1.234567e6";
1210 tests[0.00006] =
"0.00006";
1211 tests[0.000006] =
"6.0e-6";
1213 for (
auto& test : tests)
1215 testVT.setProperty (number, test.first,
nullptr);
1216 auto lines = StringArray::fromLines (testVT.toXmlString());
1217 lines.removeEmptyStrings();
1218 auto numLines = lines.size();
1219 expect (numLines > 1);
1220 expectEquals (lines[numLines - 1],
"<Test number=\"" + test.second +
"\"/>");
1226static ValueTreeTests valueTreeTests;
Holds a resizable array of primitive or copy-by-value objects.
ElementType getUnchecked(int index) const
Returns one of the elements in the array, without checking the index passed in.
bool isEmpty() const noexcept
Returns true if the array is empty, false otherwise.
void ensureStorageAllocated(int minNumElements)
Increases the array's internal storage to hold a minimum number of elements.
int size() const noexcept
Returns the current number of elements in the array.
void remove(int indexToRemove)
Removes an element from the array.
void insert(int indexToInsertAt, ParameterType newElement)
Inserts a new element into the array at a given position.
int indexOf(ParameterType elementToLookFor) const
Finds the index of the first element which matches the value passed in.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
void set(int indexToChange, ParameterType newValue)
Replaces an element with a new value.
bool contains(ParameterType elementToLookFor) const
Returns true if the array contains at least one occurrence of an object.
void move(int currentIndex, int newIndex) noexcept
Moves one of the values to a different position.
Represents a string identifier, designed for accessing properties by name.
const String & toString() const noexcept
Returns this identifier as a string.
Holds a set of named var objects.
bool set(const Identifier &name, const var &newValue)
Changes or adds a named value.
bool contains(const Identifier &name) const noexcept
Returns true if the set contains an item with the specified name.
bool remove(const Identifier &name)
Removes a value from the set.
const var & getValueAt(int index) const noexcept
Returns the value of the item at a given index.
var * getVarPointer(const Identifier &name) const noexcept
Returns a pointer to the var that holds a named value, or null if there is no value with this name.
Identifier getName(int index) const noexcept
Returns the name of the value at a given index.
int size() const noexcept
Returns the total number of values that the set contains.
void copyToXmlAttributes(XmlElement &xml) const
Sets attributes in an XML element corresponding to each of this object's properties.
The base class for streams that write data to some kind of destination.
virtual bool writeCompressedInt(int value)
Writes a condensed binary encoding of a 32-bit integer.
virtual bool writeString(const String &text)
Stores a string in the stream in a binary format.
A random number generator.
int nextInt() noexcept
Returns the next random 32 bit integer.
A smart-pointer class which points to a reference-counted object.
A base class which provides methods for reference-counting.
ReferenceCountedObject()=default
Creates the reference-counted object (with an initial ref count of zero).
Manages a list of undo/redo commands.
bool perform(UndoableAction *action)
Performs an action and adds it to the undo history list.
Used by the UndoManager class to store an action which can be done and undone.
This is a base class for classes that perform a unit test.
Listener class for events that happen to a ValueTree.
virtual void valueTreeRedirected(ValueTree &treeWhichHasBeenChanged)
This method is called when a tree is made to point to a different internal shared object.
A powerful tree structure that can be used to hold free-form data, and which can handle its own undo ...
Value getPropertyAsValue(const Identifier &name, UndoManager *undoManager, bool shouldUpdateSynchronously=false)
Returns a Value object that can be used to control and respond to one of the tree's properties.
Identifier getPropertyName(int index) const noexcept
Returns the identifier of the property with a given index.
Iterator begin() const noexcept
Returns a start iterator for the children in this tree.
String toXmlString() const
This returns a string containing an XML representation of the tree.
bool operator!=(const ValueTree &) const noexcept
Returns true if this and the other tree refer to different underlying structures.
bool hasType(const Identifier &typeName) const noexcept
Returns true if the tree has this type.
static ValueTree readFromStream(InputStream &input)
Reloads a tree from a stream that was written with writeToStream().
void removeChild(const ValueTree &child, UndoManager *undoManager)
Removes the specified child from this tree's child-list.
static ValueTree readFromGZIPData(const void *data, size_t numBytes)
Reloads a tree from a data block that was written with writeToStream() and then zipped using GZIPComp...
ValueTree getChild(int index) const
Returns one of this tree's sub-trees.
int getNumProperties() const noexcept
Returns the total number of properties that the tree contains.
int getNumChildren() const noexcept
Returns the number of child trees inside this one.
void copyPropertiesFrom(const ValueTree &source, UndoManager *undoManager)
Overwrites all the properties in this tree with the properties of the source tree.
XmlElement * createXml() const
Creates an XmlElement that holds a complete image of this tree and all its children.
int getReferenceCount() const noexcept
Returns the total number of references to the shared underlying data structure that this ValueTree is...
const var * getPropertyPointer(const Identifier &name) const noexcept
Returns a pointer to the value of a named property, or nullptr if the property doesn't exist.
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
Changes a named property of the tree.
void removeAllChildren(UndoManager *undoManager)
Removes all child-trees.
bool isAChildOf(const ValueTree &possibleParent) const noexcept
Returns true if this tree is a sub-tree (at any depth) of the given parent.
void removeAllProperties(UndoManager *undoManager)
Removes all properties from the tree.
void addListener(Listener *listener)
Adds a listener to receive callbacks when this tree is changed in some way.
void appendChild(const ValueTree &child, UndoManager *undoManager)
Appends a new child sub-tree to this tree.
int indexOf(const ValueTree &child) const noexcept
Returns the index of a child item in this parent.
bool operator==(const ValueTree &) const noexcept
Returns true if both this and the other tree refer to the same underlying structure.
ValueTree & setPropertyExcludingListener(Listener *listenerToExclude, const Identifier &name, const var &newValue, UndoManager *undoManager)
Changes a named property of the tree, but will not notify a specified listener of the change.
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
Adds a child to this tree.
ValueTree getParent() const noexcept
Returns the parent tree that contains this one.
const var & getProperty(const Identifier &name) const noexcept
Returns the value of a named property.
ValueTree() noexcept
Creates an empty, invalid ValueTree.
static ValueTree fromXml(const XmlElement &xml)
Tries to recreate a tree from its XML representation.
ValueTree createCopy() const
Returns a deep copy of this tree and all its sub-trees.
Identifier getType() const noexcept
Returns the type of this tree.
ValueTree getChildWithName(const Identifier &type) const
Returns the first sub-tree with the specified type name.
Iterator end() const noexcept
Returns an end iterator for the children in this tree.
ValueTree & operator=(const ValueTree &)
Changes this object to be a reference to the given tree.
void removeListener(Listener *listener)
Removes a listener that was previously added with addListener().
void writeToStream(OutputStream &output) const
Stores this tree (and all its children) in a binary format.
bool isEquivalentTo(const ValueTree &) const
Performs a deep comparison between the properties and children of two trees.
ValueTree getOrCreateChildWithName(const Identifier &type, UndoManager *undoManager)
Returns the first sub-tree with the specified type name, creating and adding a child with this name i...
void moveChild(int currentIndex, int newIndex, UndoManager *undoManager)
Moves one of the sub-trees to a different index.
void sendPropertyChangeMessage(const Identifier &property)
Causes a property-change callback to be triggered for the specified property, calling any listeners t...
void removeProperty(const Identifier &name, UndoManager *undoManager)
Removes a property from the tree.
const var & operator[](const Identifier &name) const noexcept
Returns the value of a named property.
ValueTree getSibling(int delta) const noexcept
Returns one of this tree's siblings in its parent's child list.
ValueTree getChildWithProperty(const Identifier &propertyName, const var &propertyValue) const
Looks for the first sub-tree that has the specified property value.
void copyPropertiesAndChildrenFrom(const ValueTree &source, UndoManager *undoManager)
Replaces all children and properties of this object with copies of those from the source object.
ValueTree getRoot() const noexcept
Recursively finds the highest-level parent tree that contains this one.
static ValueTree readFromData(const void *data, size_t numBytes)
Reloads a tree from a data block that was written with writeToStream().
bool hasProperty(const Identifier &name) const noexcept
Returns true if the tree contains a named property.
Used internally by the Value class as the base class for its shared value objects.
void sendChangeMessage(bool dispatchSynchronously)
Delivers a change message to all the listeners that are registered with this value.
Represents a shared variant value.
Used to build a tree of elements representing an XML document.
A variant class, that can be used to hold a range of primitive values.
static var readFromStream(InputStream &input)
Reads back a stored binary representation of a value.
var getValue() const override
Returns the current value of this object.
void setValue(const var &newValue) override
Changes the current value.
Iterator for a ValueTree.
int getSizeInUnits() override
Returns a value to indicate how much memory this object takes up.
bool undo() override
Overridden by a subclass to undo the action.
bool perform() override
Overridden by a subclass to perform the action.
bool perform() override
Overridden by a subclass to perform the action.
bool undo() override
Overridden by a subclass to undo the action.
int getSizeInUnits() override
Returns a value to indicate how much memory this object takes up.
UndoableAction * createCoalescedAction(UndoableAction *nextAction) override
Allows multiple actions to be coalesced into a single action object, to reduce storage space.
bool undo() override
Overridden by a subclass to undo the action.
int getSizeInUnits() override
Returns a value to indicate how much memory this object takes up.
UndoableAction * createCoalescedAction(UndoableAction *nextAction) override
Allows multiple actions to be coalesced into a single action object, to reduce storage space.
bool perform() override
Overridden by a subclass to perform the action.