Created
November 11, 2021 02:24
-
-
Save trigger-segfault/cabe7a3dc5f8bf35d49e06efb0e10f5b to your computer and use it in GitHub Desktop.
C++ Templated ListSet management for LEGO Rock Raiders.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| #include "../../common.h" | |
| #include "Errors.h" | |
| #include "Memory.h" | |
| /// TEMPLATE REQUIREMENTS: | |
| // Note that the order of these fields is not important. | |
| // | |
| // struct TCont { | |
| // TItem* listSet[MAXLISTS]; | |
| // TItem* freeList; | |
| // uint32 listCount; | |
| // }; | |
| // | |
| // struct TItem { | |
| // TItem* nextFree; // Normally the last field, but there isn't any hard requirement. | |
| // }; | |
| // | |
| // bool FPredicate(const TItem* item); | |
| namespace ListSet | |
| {; // !<--- | |
| #pragma region ListSet Template Helpers | |
| /** | |
| * @brief Template to obtain a listSet container type's element type. | |
| * @param TCont The container type holding a listSet. | |
| */ | |
| template <class TCont> | |
| using container_value_t = typename std::remove_pointer_t<std::remove_extent_t<decltype(TCont::listSet)>>; | |
| /** | |
| * @brief Template filter function typedef that obtains its value_type from the listSet container type. | |
| */ | |
| template <class TCont> | |
| using predicate_f = bool(const container_value_t<TCont>*); | |
| #pragma endregion | |
| #pragma region ListSet Math | |
| /** | |
| * @brief Returns the maximum number of lists in a type's listSet. | |
| * @param TCont The container type holding a listSet. | |
| * @return The count of TCont's C array listSet. | |
| */ | |
| template <typename TCont> | |
| constexpr const size_t MaxLists() | |
| { | |
| return _countof(TCont::listSet); | |
| } | |
| /** | |
| * @brief Returns the total number of items available in a listSet with N lists. | |
| * @param listCount The number of lists in a listSet (total or currently allocated). | |
| * @return The combined size of listCount lists. | |
| */ | |
| constexpr const size_t CapacityOfLists(size_t listCount) | |
| { | |
| return ((static_cast<size_t>(1U) << listCount) - 1U); | |
| } | |
| /** | |
| * @brief Returns the number of items available in a single list at index N in a listSet. | |
| * @param listIndex The index of the list in a listSet. | |
| * @return The combined size of listCount lists. | |
| */ | |
| constexpr const size_t CountOfList(size_t listIndex) | |
| { | |
| return (static_cast<size_t>(1U) << listIndex); | |
| } | |
| /** | |
| * @brief Returns the index of an item in a specific listSet list. | |
| * Should only used when the item is known to exist in the specified list. | |
| * @param listIndex The index of the list in a listSet. | |
| * @return The index of the item from the start of the list (calculated with pointer math). | |
| */ | |
| template <typename TItem> | |
| constexpr const size_t IndexInList(const TItem* list, const TItem* item) | |
| { | |
| return (static_cast<size_t>(item - list) / sizeof(TItem)); | |
| } | |
| /** | |
| * @brief Returns the absolute index of an item in a listSet. | |
| * @param listIndex The index of the list in a listSet that holds the item. | |
| * @param itemIndex The index of the item in the list. | |
| * @return The itemIndex + the capacity of all previous lists. | |
| */ | |
| constexpr const size_t IDInListSet(size_t listIndex, size_t itemIndex) | |
| { | |
| // Capacity of all previous lists + index in current list | |
| return CapacityOfLists(listIndex) + itemIndex; | |
| } | |
| template <typename TItem> | |
| constexpr const size_t IDInListSet(size_t listIndex, const TItem* list, const TItem* item) | |
| { | |
| return IDInListSet(listIndex, IndexInList(list, item)); | |
| } | |
| #pragma endregion | |
| #pragma region ListSet Item State | |
| /** | |
| * @brief Tests if a listSet item is alive. | |
| * @param TItem The listSet item type. | |
| * @param item An item created by the listSet. | |
| * @return True if the item is alive, and managed by the user. | |
| */ | |
| template <typename TItem> | |
| bool IsAlive(const TItem* item) | |
| { | |
| return (item->nextFree == item); | |
| } | |
| /** | |
| * @brief Tests if a listSet item is dead. | |
| * @param TItem The listSet item type. | |
| * @param item An item created by the listSet. | |
| * @return True if the item is dead, and managed by the listSet. | |
| */ | |
| template <typename TItem> | |
| bool IsDead(const TItem* item) | |
| { | |
| return (item->nextFree != item); | |
| } | |
| /** | |
| * @brief Tests if a listSet item is null or dead (shorthand for !item && IsDead(item)). | |
| * @param TItem The listSet item type. | |
| * @param item An item created by the listSet. | |
| * @return True if the item is null, or is dead, and managed by the listSet. | |
| */ | |
| template <typename TItem> | |
| bool IsNullOrDead(const TItem* item) | |
| { | |
| return (!item || item->nextFree != item); | |
| } | |
| #if 0 | |
| enum class ItemState : uint32 | |
| { | |
| None = 0, | |
| Dead = 0x1, | |
| Alive = 0x2, | |
| Any = Dead | Alive, | |
| }; | |
| template <typename TItem> | |
| bool CheckItemState(const TItem* item, ItemState state) | |
| { | |
| #pragma region Bit fiddling truth table | |
| // Truth table for bit fiddling operation: | |
| // ((ItemState::None) 0x0 & (IsDead)(1 << false)0x1) -> 0x0 (false) | |
| // ((ItemState::None) 0x0 & (IsAlive)(1 << true)0x2) -> 0x0 (false) | |
| // ((ItemState::Dead) 0x1 & (IsDead)(1 << false)0x1) -> 0x1 (true) | |
| // ((ItemState::Dead) 0x1 & (IsAlive)(1 << true)0x2) -> 0x0 (false) | |
| // ((ItemState::Alive)0x2 & (IsDead)(1 << false)0x1) -> 0x0 (false) | |
| // ((ItemState::Alive)0x2 & (IsAlive)(1 << true)0x2) -> 0x2 (true) | |
| // ((ItemState::Any) 0x3 & (IsDead)(1 << false)0x1) -> 0x1 (true) | |
| // ((ItemState::Any) 0x3 & (IsAlive)(1 << true)0x2) -> 0x2 (true) | |
| #pragma endregion | |
| // Bit fiddling solution: | |
| return ((uint32)state & (1 << (uint32)ListSet::IsAlive(item))); | |
| // Identical to the following logic (assuming ItemState is a valid enum value): | |
| /* | |
| if (state == ItemState::Any) return true; | |
| if (state == ItemState::None) return false; | |
| return (state == ItemState::Alive) == ListSet::IsAlive(item); | |
| */ | |
| } | |
| #endif | |
| #pragma endregion | |
| #pragma region ListSet Filter Functions | |
| /** | |
| * @brief A listSet filter function to return all items, alive or dead. | |
| * @param TItem The value_type of a listSet container. | |
| */ | |
| template <typename TItem> | |
| bool NoFilter(const TItem* item) { return true; } | |
| /** | |
| * @brief A listSet filter function to only return items that are alive, and managed by the user. | |
| * @param TItem The value_type of a listSet container. | |
| */ | |
| template <typename TItem> | |
| bool AliveFilter(const TItem* item) { return ListSet::IsAlive(item); } | |
| /** | |
| * @brief A listSet filter function to only return items that are dead, and managed by the listSet. | |
| * @param TItem The value_type of a listSet container. | |
| */ | |
| template <typename TItem> | |
| bool DeadFilter(const TItem* item) { return !ListSet::IsAlive(item); } | |
| #pragma endregion | |
| #pragma region ListSet Iterator | |
| /** | |
| * @brief The base iterator type for ListSet collections. (use Iterator or ReverseIterator) | |
| * @param TCont The container type holding a listSet. | |
| * @param FPredicate A filter function to skip past items where false is returned. | |
| * @param Reverse Only use in combination with std::reverse_iterator<>. | |
| * Changes the search direction for the first valid item during the constructor. | |
| */ | |
| template <typename TCont, const predicate_f<TCont> FPredicate, const bool Reverse> | |
| class BaseIterator | |
| { | |
| public: | |
| using container = TCont; | |
| using difference_type = std::ptrdiff_t; | |
| using iterator_category = std::bidirectional_iterator_tag; | |
| using value_type = container_value_t<container>; | |
| using iterator = BaseIterator<container, FPredicate, Reverse>; | |
| static constexpr size_t npos = static_cast<size_t>(-1); | |
| /** | |
| * @brief Construct an iterator at the specified list/item index in a listSet. | |
| * @param cont The listSet container (i.e. Config_Globs configGlobs). | |
| * @param listIndex The index of the list in cont's listSet that holds the item. | |
| * @param itemIndex The index of the item in the list. | |
| */ | |
| BaseIterator(const container& cont, ptrdiff_t listIndex, ptrdiff_t itemIndex) | |
| : m_cont(cont), m_listIndex(listIndex), m_itemIndex(0), m_itemCount(0), m_ptr(nullptr) | |
| { | |
| // Only perform validation (and assign itemIndex) if we're inside the listSet bounds. | |
| // Otherwise this is a terminator iterator: i.e. end() or rend() | |
| if (listIndex >= 0 && listIndex < static_cast<ptrdiff_t>(ListSet::MaxLists<container>())) { | |
| // Pre-check for null list: | |
| // When a list is null, inner count and index are 0 so that we instantly skip during increment/decrement. | |
| value_type* list = m_cont.listSet[listIndex]; | |
| // Also check that we're searching within the bounds of listCount. | |
| // The bounds check for listCount is placed here rather than where MaxLists is in order to: | |
| // - increment up to end() (aka MaxLists) in the case of a forward iterator. | |
| // - decrement to the next valid item in the case of a reverse iterator. | |
| if (list && listIndex < static_cast<ptrdiff_t>(m_cont.listCount)) { | |
| m_itemCount = ListSet::CountOfList(listIndex); | |
| m_itemIndex = (itemIndex >= 0 ? itemIndex : (m_itemCount + itemIndex)); | |
| m_ptr = &list[m_itemIndex]; | |
| } | |
| // Go to our first valid item: | |
| // Null pointer means we automatically need to check for the next. | |
| // But we also need to filter towards our first valid item, even when non-null. | |
| if (!m_ptr || !FPredicate(m_ptr)) { | |
| if (Reverse) --(*this); | |
| else ++(*this); | |
| } | |
| } | |
| } | |
| // Iterator evaluation operator requirements: | |
| /** | |
| * @brief Returns a pointer to the current item in the listSet. | |
| * Note that operator `It->` also returns the same pointer level. | |
| * @return A pointer to the current item. | |
| */ | |
| value_type* operator*() const { return m_ptr; } | |
| /** | |
| * @brief Returns a pointer to the current item in the listSet. | |
| * Note that operator `*It` also returns the same pointer level. | |
| * @return A pointer to the current item to be accessed. | |
| */ | |
| value_type* operator->() const { return m_ptr; } | |
| // Forward directional increment operator requirements: | |
| // Prefix increment | |
| iterator& operator++() | |
| { | |
| do { | |
| // Increment our item index and check if we need to move to the next list. | |
| if (++m_itemIndex >= static_cast<ptrdiff_t>(m_itemCount)) { | |
| // Find next non-null list, and terminate inside if we reach the end of the listSet. | |
| do { | |
| // We can safely skip any remaining unused lists here. | |
| if (++m_listIndex >= static_cast<ptrdiff_t>(m_cont.listCount)) { | |
| // Always jump to end at MaxLists, rather than listCount. | |
| // This way we can preserve Gods98 list iteration, | |
| // where foreach loops will always iterate over newly-appended lists as it goes. | |
| m_listIndex = ListSet::MaxLists<container>(); | |
| m_itemIndex = 0; | |
| m_itemCount = 0; | |
| m_ptr = nullptr; | |
| return *this; // end of listSet | |
| } | |
| // We want to skip null lists. Realistically all remaining lists would also be null. | |
| } while (!m_cont.listSet[m_listIndex]); | |
| m_itemCount = ListSet::CountOfList(m_listIndex); | |
| m_itemIndex = 0; | |
| } | |
| // Update our pointer to the current item. (alternatively can be done after do-while loop) | |
| m_ptr = &m_cont.listSet[m_listIndex][m_itemIndex]; | |
| // Can we return our current item? (generally used to check if item is alive in the listSet). | |
| } while (!FPredicate(m_ptr)); | |
| return *this; | |
| } | |
| // Postfix increment | |
| iterator operator++(int) | |
| { | |
| iterator tmp = *this; | |
| ++(*this); | |
| return tmp; | |
| } | |
| // Bidirectional iterator decrement operator requirements: | |
| // Prefix decrement | |
| iterator& operator--() | |
| { | |
| do { | |
| // Decrement our item index and check if we need to move to the previous list. | |
| if (m_itemIndex-- <= 0) { | |
| // Find previous non-null listand terminate inside if we reach the beginning of the listSet. | |
| do { | |
| if (m_listIndex-- <= 0) { | |
| // Always end at list -1, to signal we've reached the beginning of the listSet. | |
| m_listIndex = -1; | |
| m_itemIndex = 0; | |
| m_itemCount = 0; | |
| m_ptr = nullptr; | |
| return *this; // pre-beginning of listSet | |
| } | |
| // We want to skip null lists, and also skip anything after listCount. | |
| } while (!m_cont.listSet[m_listIndex] || m_listIndex >= static_cast<ptrdiff_t>(m_cont.listCount)); | |
| m_itemCount = ListSet::CountOfList(m_listIndex); | |
| m_itemIndex = (m_itemCount - 1); | |
| } | |
| // Update our pointer to the current item. (could alternatively be done after the do-while loop) | |
| m_ptr = &m_cont.listSet[m_listIndex][m_itemIndex]; | |
| // Can we return our current item? (generally used to check if item is alive in the listSet). | |
| } while (!FPredicate(m_ptr)); | |
| return *this; | |
| } | |
| // Postfix decrement | |
| iterator operator--(int) | |
| { | |
| iterator tmp = *this; | |
| --(*this); | |
| return tmp; | |
| } | |
| /** | |
| * @brief Gets the index of the current list in the listSet. | |
| */ | |
| ptrdiff_t ListIndex() const { return m_listIndex; } | |
| /** | |
| * @brief Gets the index of the current item within the current list. | |
| */ | |
| ptrdiff_t ItemIndex() const { return m_itemIndex; } | |
| /** | |
| * @brief Gets the absolute index of the current item in the listSet. | |
| * @return The absolute index of the current item, or npos if an item is not currently pointed to (e.g. end()). | |
| */ | |
| size_t ID() const { return (!m_ptr ? npos : ListSet::IDInListSet(m_listIndex, m_itemIndex)); } | |
| /** | |
| * @brief Tests if the current item is alive, and managed by the user. | |
| */ | |
| bool IsAlive() const { return ListSet::IsAlive(m_ptr); } | |
| /** | |
| * @brief Tests if the current item is dead, and managed by the listSet. | |
| */ | |
| bool IsDead() const { return !ListSet::IsAlive(m_ptr); } | |
| // Foreach comparison operator requirements: | |
| friend bool operator== (const iterator& a, const iterator& b) { return a.m_itemIndex == b.m_itemIndex && a.m_listIndex == b.m_listIndex; }; | |
| friend bool operator!= (const iterator& a, const iterator& b) { return a.m_itemIndex != b.m_itemIndex || a.m_listIndex != b.m_listIndex; }; | |
| protected: | |
| const container& m_cont; | |
| ptrdiff_t m_listIndex; // Index of the current list in the listSet. | |
| ptrdiff_t m_itemIndex; // Index of the current item in the list. | |
| size_t m_itemCount; // Size of the current list. | |
| value_type* m_ptr; // Pointer to the current item. | |
| }; | |
| /** | |
| * @brief A forward iterator type for ListSet collections. | |
| * @param TCont The container type holding a listSet. | |
| * @param FPredicate A filter function to skip past items where false is returned. | |
| */ | |
| template <typename TCont, const predicate_f<TCont> FPredicate> | |
| using Iterator = BaseIterator<TCont, FPredicate, false>; | |
| /** | |
| * @brief A reverse iterator type for ListSet collections. | |
| * @param TCont The container type holding a listSet. | |
| * @param FPredicate A filter function to skip past items where false is returned. | |
| */ | |
| template <typename TCont, const predicate_f<TCont> FPredicate> | |
| using ReverseIterator = std::reverse_iterator<BaseIterator<TCont, FPredicate, true>>; | |
| #pragma endregion | |
| #pragma region ListSet Enumerable | |
| /** | |
| * @brief Template class for iterating over a listSet container's elements in a foreach loop. | |
| * @param TCont The container type holding a listSet. | |
| * @param FPredicate A filter function to skip past items where false is returned. | |
| */ | |
| template <typename TCont, const predicate_f<TCont> FPredicate> | |
| class Enumerable | |
| { | |
| public: | |
| using container = TCont; | |
| using value_type = container_value_t<container>; | |
| using iterator = Iterator<container, FPredicate>; | |
| using reverse_iterator = ReverseIterator<container, FPredicate>; | |
| /** | |
| * @brief Constructs an enumerable collection wrapper around an existing listSet struct. | |
| * @param cont The listSet container (i.e. Config_Globs configGlobs). | |
| */ | |
| Enumerable(container& cont) | |
| : m_cont(cont) | |
| { | |
| } | |
| /** | |
| * @brief Gets the number of lists currently allocated in the listSet. | |
| */ | |
| size_t ListCount() const { return m_cont.listCount; } | |
| /** | |
| * @brief Gets the total number of lists that can be allocated in the listSet. | |
| */ | |
| constexpr size_t const MaxLists() const { return ListSet::MaxLists<container>(); } | |
| /** | |
| * @brief Returns the capacity of the currently allocated lists in the listSet. | |
| */ | |
| size_t Capacity() const { return ListSet::CapacityOfLists(m_cont.listCount); } | |
| /** | |
| * @brief Returns the total capacity of the listSet if all lists are currently allocated. | |
| */ | |
| constexpr size_t const MaxCapacity() const { return ListSet::CapacityOfLists(ListSet::MaxLists<container>); } | |
| iterator begin() { return iterator(m_cont, 0, 0); } | |
| const iterator cbegin() const { return iterator(m_cont, 0, 0); } | |
| iterator end() { return iterator(m_cont, ListSet::MaxLists<container>(), 0); } | |
| const iterator cend() const { return iterator(m_cont, ListSet::MaxLists<container>(), 0); } | |
| reverse_iterator rbegin() { return reverse_iterator(m_cont, m_cont.listCount - 1, -1); } | |
| const reverse_iterator crbegin() const { return reverse_iterator(m_cont, m_cont.listCount - 1, -1); } | |
| reverse_iterator rend() { return reverse_iterator(m_cont, -1, -1); } | |
| const reverse_iterator crend() const { return reverse_iterator(m_cont, -1, -1); } | |
| protected: | |
| container& m_cont; | |
| }; | |
| /** | |
| * @brief Template class for iterating over a listSet container's elements in a foreach loop (with no filter). | |
| * @param TCont The container type holding a listSet. | |
| */ | |
| template <typename TCont> | |
| using DefaultEnumerable = Enumerable<TCont, NoFilter<container_value_t<TCont>>>; | |
| #pragma endregion | |
| #pragma region ListSet Collection | |
| /** | |
| * @brief Template class for managing a listSet collection. | |
| * @param TCont The container type holding a listSet. | |
| */ | |
| template <typename TCont> | |
| class Collection : public DefaultEnumerable<TCont> | |
| { | |
| public: | |
| using DefaultEnumerable<TCont>::m_cont; // Included so that `m_cont` member can be accessed without `this->`. | |
| using container = TCont; | |
| using value_type = container_value_t<container>; | |
| template <const predicate_f<container> FPredicate> | |
| using enumerable = Enumerable<container, FPredicate>; | |
| /** | |
| * @brief Constructs a listSet collection manager wrapped around an existing listSet struct. | |
| * @param cont The listSet container (i.e. Config_Globs configGlobs). | |
| */ | |
| Collection(container& cont) | |
| : DefaultEnumerable<TCont>(cont) | |
| { | |
| } | |
| /** | |
| * @brief Tests if a listSet item is alive. | |
| * @param item An item created by the listSet. | |
| * @return True if the item is alive, and managed by the user. | |
| */ | |
| bool IsAlive(value_type* item) { return ListSet::IsAlive(item); } | |
| /** | |
| * @brief Tests if a listSet item is dead. | |
| * @param item An item created by the listSet. | |
| * @return True if the item is dead, and managed by the listSet. | |
| */ | |
| bool IsDead(value_type* item) { return !ListSet::IsAlive(item); } | |
| /** | |
| * @brief Tests if a listSet item is null or dead (shorthand for !item && IsDead(item)). | |
| * @param item An item created by the listSet. | |
| * @return True if the item is null, or is dead, and managed by the listSet. | |
| */ | |
| bool IsNullOrDead(value_type* item) { return !item || !ListSet::IsAlive(item); } | |
| /** | |
| * @brief Returns an enumerable for iterating over filtered items in the listSet. | |
| * @param FPredicate A filter function to skip past items where false is returned. | |
| */ | |
| template <const predicate_f<container> FPredicate> | |
| enumerable<FPredicate> EnumerateWhere() { return enumerable<FPredicate>(m_cont); } | |
| /** | |
| * @brief Returns an enumerable for iterating over alive items in the listSet. | |
| */ | |
| enumerable<AliveFilter<value_type>> EnumerateAlive() { return enumerable<AliveFilter<value_type>>(m_cont); } | |
| /** | |
| * @brief Returns an enumerable for iterating over dead items in the listSet. | |
| */ | |
| enumerable<DeadFilter<value_type>> EnumerateDead() { return enumerable<DeadFilter<value_type>>(m_cont); } | |
| /** | |
| * @brief Returns an enumerable for iterating over every item in the listSet. | |
| */ | |
| enumerable<NoFilter<value_type>> Enumerate() { return enumerable<NoFilter<value_type>>(m_cont); } | |
| /** | |
| * @brief Returns the number of items that are currently alive in the listSet. | |
| */ | |
| size_t CountAlive() { return static_cast<size_t>(std::distance(this->begin(), this->end())); } | |
| /** | |
| * @brief Performs initial listSet setup by zeroing all appropriate fields. | |
| */ | |
| void Initialise() | |
| { | |
| for (size_t i = 0; i < this->MaxLists(); i++) { | |
| m_cont.listSet[i] = nullptr; | |
| } | |
| m_cont.listCount = 0; | |
| m_cont.freeList = nullptr; | |
| } | |
| /** | |
| * @brief Performs listSet cleanup by freeing allocated lists and zeroing all appropriate fields. | |
| * Note that the user should handle cleanup of individual list items beforehand. | |
| */ | |
| void Shutdown() | |
| { | |
| for (size_t i = 0; i < this->MaxLists(); i++) { | |
| if (m_cont.listSet[i]) { | |
| Gods98::Mem_Free(m_cont.listSet[i]); | |
| m_cont.listSet[i] = nullptr; | |
| } | |
| } | |
| // NOTE: LegoRR ListSet shutdowns often do not reset one or both these fields. | |
| m_cont.listCount = 0; | |
| m_cont.freeList = nullptr; | |
| } | |
| /** | |
| * @brief Reserves and returns a new item in the listSet. | |
| * @param memZero The item's memory will be zeroed out before being returned. | |
| * @return A new living item in the listSet. | |
| */ | |
| value_type* Add(bool memZero) | |
| { | |
| if (m_cont.freeList == nullptr) this->AddList(); | |
| value_type* newItem = m_cont.freeList; | |
| m_cont.freeList = newItem->nextFree; | |
| if (memZero) std::memset(newItem, 0, sizeof(value_type)); | |
| newItem->nextFree = newItem; | |
| return newItem; | |
| } | |
| /** | |
| * @brief Remove a living item from the listSet. | |
| * @param dead An item previously created by the listSet that has yet to be removed. | |
| */ | |
| void Remove(value_type* dead) | |
| { | |
| Error_Fatal(!dead, "NULL passed to ListSet::Remove"); | |
| Error_Fatal(!ListSet::IsAlive(dead), "Dead item passed to ListSet::Remove"); | |
| dead->nextFree = m_cont.freeList; | |
| m_cont.freeList = dead; | |
| } | |
| protected: | |
| /** | |
| * @brief Adds a new list to the listSet. Only call this when `m_cont.freeList == nullptr`. | |
| */ | |
| void AddList() | |
| { | |
| Error_Fatal(m_cont.listCount + 1 >= this->MaxLists(), "Run out of lists"); | |
| size_t count = ListSet::CountOfList(m_cont.listCount); | |
| value_type* list = m_cont.listSet[m_cont.listCount] = (value_type*)Gods98::Mem_Alloc(sizeof(value_type) * count); | |
| if (list) { | |
| m_cont.listCount++; | |
| // Connect all items in a forward-linked-list fashion. | |
| for (size_t i = 1; i < count; i++) { | |
| list[i - 1].nextFree = &list[i]; | |
| } | |
| // Finally, connect our newly allocated list to the remainder of the listSet. | |
| // NOTE: freeList is always null when calling AddList (under normal circumstances). | |
| list[count - 1].nextFree = m_cont.freeList; | |
| m_cont.freeList = list; | |
| } | |
| else Error_Fatal(true, Gods98::Error_Format("Unable to allocate %d bytes of memory for new list.\n", sizeof(value_type) * count)); | |
| } | |
| //protected: | |
| // container& m_cont; | |
| }; | |
| #pragma endregion | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "ListSet.hpp" | |
| // Subclass of ListSet::Collection since SkipIgnoreMes is the enumeration method used 95% of the time. | |
| class LegoObject_ListSet : public ListSet::Collection<LegoObject_Globs> | |
| { | |
| public: | |
| LegoObject_ListSet(LegoObject_Globs& cont) | |
| : ListSet::Collection<LegoObject_Globs>(cont) | |
| { | |
| } | |
| private: | |
| static bool FilterSkipIgnoreMes(const LegoObject* liveObj); | |
| { | |
| return ListSet::IsAlive(liveObj) && !(liveObj->flags3 & LIVEOBJ3_IGNOREME_UNK); | |
| } | |
| public: | |
| LegoObject_ListSet::enumerable<FilterSkipIgnoreMes> EnumerateSkipIgnoreMes(); | |
| { | |
| return this->EnumerateWhere<FilterSkipIgnoreMes>(); | |
| } | |
| }; | |
| // <LegoRR.exe @004df790> | |
| static LegoObject_Globs & objectGlobs = *(LegoObject_Globs*)0x004df790; | |
| static LegoObject_ListSet objectListSet = LegoObject_ListSet(objectGlobs); | |
| typedef bool32 (__cdecl* LegoObject_RunThroughListsCallback)(LegoObject* liveObj, void* data); | |
| // <LegoRR.exe @00437a70> | |
| bool32 __cdecl LegoRR::LegoObject_RunThroughListsSkipIgnoreMes(LegoObject_RunThroughListsCallback callback, void* data) | |
| { | |
| for (LegoObject* liveObj : objectListSet.EnumerateSkipIgnoreMes()) { | |
| if (callback(liveObj, data)) | |
| return true; // terminate run through listSet | |
| } | |
| return false; | |
| } | |
| // <LegoRR.exe @00437a90> | |
| bool32 __cdecl LegoRR::LegoObject_RunThroughLists(LegoObject_RunThroughListsCallback callback, void* data, bool32 skipIgnoreMeObjs) | |
| { | |
| for (LegoObject* liveObj : objectListSet.EnumerateAlive()) { | |
| // Secondary filter | |
| // Note that all remaining calls to this function pass false for skipIgnoreMeObjs, | |
| // so this can be removed, due to implementing LegoObject_RunThroughListsSkipIgnoreMes. | |
| if (!skipIgnoreMeObjs || !(liveObj->flags3 & LIVEOBJ3_IGNOREME_UNK)) { | |
| if (callback(liveObj, data)) | |
| return true; // terminate run through listSet | |
| } | |
| } | |
| return false; | |
| } | |
| // <LegoRR.exe @0043b5e0> | |
| void __cdecl LegoRR::LegoObject_RemoveAll(void) | |
| { | |
| objectGlobs.flags |= LegoObject_GlobFlags::OBJECT_GLOB_FLAG_REMOVING; | |
| for (LegoObject* liveObj : objectListSet.EnumerateSkipIgnoreMes()) { | |
| // We can skip past LegoObject_Callback_Remove(liveObj,nullptr) and just call LegoObject_Remove directly. | |
| LegoObject_Remove(liveObj); | |
| } | |
| // OLD: LegoObject_RunThroughListsSkipIgnoreMes(LegoObject_Callback_Remove, nullptr); | |
| objectGlobs.flags &= ~LegoObject_GlobFlags::OBJECT_GLOB_FLAG_REMOVING; | |
| } | |
| // <LegoRR.exe @00449ec0> | |
| void __cdecl LegoRR::LegoObject_AllCallback_FUN_00449ec0(void) | |
| { | |
| for (LegoObject* liveObj : objectListSet.EnumerateAlive()) { | |
| LiveObject_Callback_FUN_00449fb0(liveObj, nullptr); | |
| } | |
| // OLD: LegoObject_RunThroughLists(LiveObject_Callback_FUN_00449fb0, nullptr, false); | |
| } | |
| // <LegoRR.exe @0044c810> | |
| void __cdecl LegoRR::LegoObject_CameraCycleUnits(void) | |
| { | |
| // Cycle-able units have now been validated since level-start (once the function exits). | |
| objectGlobs.flags |= LegoObject_GlobFlags::OBJECT_GLOB_FLAG_CYCLEUNITS; | |
| // Start by trying to cycle through buildings. | |
| bool32 noBuildings = false; | |
| for (LegoObject* liveObj : objectListSet.EnumerateSkipIgnoreMes()) { | |
| if (LegoObject_Callback_CameraCycleFindUnit(liveObj, &noBuildings)) | |
| return; // New building added to cycle list and camera moved. We're done. | |
| } | |
| // If no buildings are constructed, then cycle through minifigure unit types instead. | |
| if (objectGlobs.cycleBuildingCount == 0) { | |
| // Change cycle unit type to minifigures. | |
| noBuildings = true; | |
| for (LegoObject* liveObj : objectListSet.EnumerateSkipIgnoreMes()) { | |
| if (LegoObject_Callback_CameraCycleFindUnit(liveObj, &noBuildings)) | |
| return; // New minifigure added to cycle list and camera moved. We're done. | |
| } | |
| } | |
| // If cycle-able units exist, then all valid units were already in the list. | |
| if (objectGlobs.cycleUnitCount != 0) { | |
| // Reset the cycle list, then jump to the first available unit of our cycle unit type (building or minifigure). | |
| // This behavior is used so that the cycle list is guaranteed to not repeat itself until all units | |
| // have been visited once. After all units have been visited, the list is started anew. | |
| // (This effectively functions like a music shuffle, without the intention of shuffling.) | |
| // Note that the order of units may change if new valid units were created in the middle of a cycle, | |
| // This is because the order of the cycle is first and foremost based on where the object is in the listSet, | |
| // And the listSet is not intended to be ordered based on when a unit is added, as freed up slots earlier in | |
| // the list may be used instead. | |
| objectGlobs.cycleUnitCount = 0; | |
| objectGlobs.cycleBuildingCount = 0; | |
| // Find first unit to cycle to. | |
| for (LegoObject* liveObj : objectListSet.EnumerateSkipIgnoreMes()) { | |
| if (LegoObject_Callback_CameraCycleFindUnit(liveObj, &noBuildings)) | |
| return; // First building/minifigure added to cycle list and camera moved. We're done. | |
| } | |
| } | |
| } | |
| // <LegoRR.exe @0044c8b0> | |
| bool32 __cdecl LegoRR::LegoObject_Callback_CameraCycleFindUnit(LegoObject* liveObj, OPTIONAL void* pNoBuildings) | |
| { | |
| // Only check for units of a specific object type: | |
| // (defaults to Buildings when null) | |
| if (pNoBuildings != nullptr && *(bool32*)pNoBuildings) { | |
| if (liveObj->type != LegoObject_MiniFigure) return false; | |
| } | |
| else { | |
| if (liveObj->type != LegoObject_Building) return false; | |
| } | |
| // Don't check for units already in cycleUnits: | |
| for (uint32 loop = 0; loop < objectGlobs.cycleUnitCount; loop++) { | |
| if (objectGlobs.cycleUnits[loop] == liveObj) | |
| return false; | |
| } | |
| if (objectGlobs.cycleUnitCount < _countof(objectGlobs.cycleUnits)) { | |
| objectGlobs.cycleUnits[objectGlobs.cycleUnitCount++] = liveObj; | |
| if (liveObj->type == LegoObject_Building) | |
| objectGlobs.cycleBuildingCount++; | |
| // Name is misleading, this just goes to a location using the same method as Info icons. | |
| Lego_GotoInfo(liveObj, nullptr, false); | |
| uint32 bx, by; // dummy outputs | |
| Vector3F worldPos; | |
| if (Map3D_GetIntersections(legoGlobs.currLevel->map, legoGlobs.viewMain, Gods98::msx(), Gods98::msy(), &bx, &by, &worldPos)) { | |
| Gods98::Container_SetPosition(legoGlobs.rootLight, nullptr, worldPos.x, worldPos.y, worldPos.z - 250.0f); | |
| return true; | |
| } | |
| } | |
| else { | |
| objectGlobs.cycleUnitCount = 0; | |
| objectGlobs.cycleBuildingCount = 0; | |
| } | |
| return true; // Is returning true here intentional? | |
| // So cycle functionality breaks after 256 building or minifigure units I guess...? | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment