2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file script_list.cpp Implementation of ScriptList. */
10 #include "../../stdafx.h"
11 #include "script_list.hpp"
12 #include "../../debug.h"
13 #include "../../script/squirrel.hpp"
15 #include "../../safeguards.h"
18 * Base class for any ScriptList sorter.
20 class ScriptListSorter
{
22 ScriptList
*list
; ///< The list that's being sorted.
23 bool has_no_more_items
; ///< Whether we have more items to iterate over.
24 SQInteger item_next
; ///< The next item we will show.
28 * Virtual dtor, needed to mute warnings.
30 virtual ~ScriptListSorter() = default;
33 * Get the first item of the sorter.
35 virtual SQInteger
Begin() = 0;
38 * Stop iterating a sorter.
40 virtual void End() = 0;
43 * Get the next item of the sorter.
45 virtual SQInteger
Next() = 0;
48 * See if the sorter has reached the end.
52 return this->list
->buckets
.empty() || this->has_no_more_items
;
56 * Callback from the list if an item gets removed.
58 virtual void Remove(SQInteger item
) = 0;
61 * Attach the sorter to a new list. This assumes the content of the old list has been moved to
62 * the new list, too, so that we don't have to invalidate any iterators. Note that std::swap
63 * doesn't invalidate iterators on lists and maps, so that should be safe.
64 * @param target New list to attach to.
66 virtual void Retarget(ScriptList
*new_list
)
68 this->list
= new_list
;
73 * Sort by value, ascending.
75 class ScriptListSorterValueAscending
: public ScriptListSorter
{
77 ScriptList::ScriptListBucket::iterator bucket_iter
; ///< The iterator over the list to find the buckets.
78 ScriptList::ScriptItemList
*bucket_list
; ///< The current bucket list we're iterator over.
79 ScriptList::ScriptItemList::iterator bucket_list_iter
; ///< The iterator over the bucket list.
83 * Create a new sorter.
84 * @param list The list to sort.
86 ScriptListSorterValueAscending(ScriptList
*list
)
92 SQInteger
Begin() override
94 if (this->list
->buckets
.empty()) return 0;
95 this->has_no_more_items
= false;
97 this->bucket_iter
= this->list
->buckets
.begin();
98 this->bucket_list
= &(*this->bucket_iter
).second
;
99 this->bucket_list_iter
= this->bucket_list
->begin();
100 this->item_next
= *this->bucket_list_iter
;
102 SQInteger item_current
= this->item_next
;
109 this->bucket_list
= nullptr;
110 this->has_no_more_items
= true;
115 * Find the next item, and store that information.
119 if (this->bucket_list
== nullptr) {
120 this->has_no_more_items
= true;
124 this->bucket_list_iter
++;
125 if (this->bucket_list_iter
== this->bucket_list
->end()) {
127 if (this->bucket_iter
== this->list
->buckets
.end()) {
128 this->bucket_list
= nullptr;
131 this->bucket_list
= &(*this->bucket_iter
).second
;
132 this->bucket_list_iter
= this->bucket_list
->begin();
134 this->item_next
= *this->bucket_list_iter
;
137 SQInteger
Next() override
139 if (this->IsEnd()) return 0;
141 SQInteger item_current
= this->item_next
;
146 void Remove(SQInteger item
) override
148 if (this->IsEnd()) return;
150 /* If we remove the 'next' item, skip to the next */
151 if (item
== this->item_next
) {
159 * Sort by value, descending.
161 class ScriptListSorterValueDescending
: public ScriptListSorter
{
163 /* Note: We cannot use reverse_iterator.
164 * The iterators must only be invalidated when the element they are pointing to is removed.
165 * This only holds for forward iterators. */
166 ScriptList::ScriptListBucket::iterator bucket_iter
; ///< The iterator over the list to find the buckets.
167 ScriptList::ScriptItemList
*bucket_list
; ///< The current bucket list we're iterator over.
168 ScriptList::ScriptItemList::iterator bucket_list_iter
; ///< The iterator over the bucket list.
172 * Create a new sorter.
173 * @param list The list to sort.
175 ScriptListSorterValueDescending(ScriptList
*list
)
181 SQInteger
Begin() override
183 if (this->list
->buckets
.empty()) return 0;
184 this->has_no_more_items
= false;
186 /* Go to the end of the bucket-list */
187 this->bucket_iter
= this->list
->buckets
.end();
189 this->bucket_list
= &(*this->bucket_iter
).second
;
191 /* Go to the end of the items in the bucket */
192 this->bucket_list_iter
= this->bucket_list
->end();
193 --this->bucket_list_iter
;
194 this->item_next
= *this->bucket_list_iter
;
196 SQInteger item_current
= this->item_next
;
203 this->bucket_list
= nullptr;
204 this->has_no_more_items
= true;
209 * Find the next item, and store that information.
213 if (this->bucket_list
== nullptr) {
214 this->has_no_more_items
= true;
218 if (this->bucket_list_iter
== this->bucket_list
->begin()) {
219 if (this->bucket_iter
== this->list
->buckets
.begin()) {
220 this->bucket_list
= nullptr;
224 this->bucket_list
= &(*this->bucket_iter
).second
;
225 /* Go to the end of the items in the bucket */
226 this->bucket_list_iter
= this->bucket_list
->end();
227 --this->bucket_list_iter
;
229 this->bucket_list_iter
--;
231 this->item_next
= *this->bucket_list_iter
;
234 SQInteger
Next() override
236 if (this->IsEnd()) return 0;
238 SQInteger item_current
= this->item_next
;
243 void Remove(SQInteger item
) override
245 if (this->IsEnd()) return;
247 /* If we remove the 'next' item, skip to the next */
248 if (item
== this->item_next
) {
256 * Sort by item, ascending.
258 class ScriptListSorterItemAscending
: public ScriptListSorter
{
260 ScriptList::ScriptListMap::iterator item_iter
; ///< The iterator over the items in the map.
264 * Create a new sorter.
265 * @param list The list to sort.
267 ScriptListSorterItemAscending(ScriptList
*list
)
273 SQInteger
Begin() override
275 if (this->list
->items
.empty()) return 0;
276 this->has_no_more_items
= false;
278 this->item_iter
= this->list
->items
.begin();
279 this->item_next
= (*this->item_iter
).first
;
281 SQInteger item_current
= this->item_next
;
288 this->has_no_more_items
= true;
292 * Find the next item, and store that information.
296 if (this->item_iter
== this->list
->items
.end()) {
297 this->has_no_more_items
= true;
301 if (this->item_iter
!= this->list
->items
.end()) item_next
= (*this->item_iter
).first
;
304 SQInteger
Next() override
306 if (this->IsEnd()) return 0;
308 SQInteger item_current
= this->item_next
;
313 void Remove(SQInteger item
) override
315 if (this->IsEnd()) return;
317 /* If we remove the 'next' item, skip to the next */
318 if (item
== this->item_next
) {
326 * Sort by item, descending.
328 class ScriptListSorterItemDescending
: public ScriptListSorter
{
330 /* Note: We cannot use reverse_iterator.
331 * The iterators must only be invalidated when the element they are pointing to is removed.
332 * This only holds for forward iterators. */
333 ScriptList::ScriptListMap::iterator item_iter
; ///< The iterator over the items in the map.
337 * Create a new sorter.
338 * @param list The list to sort.
340 ScriptListSorterItemDescending(ScriptList
*list
)
346 SQInteger
Begin() override
348 if (this->list
->items
.empty()) return 0;
349 this->has_no_more_items
= false;
351 this->item_iter
= this->list
->items
.end();
353 this->item_next
= (*this->item_iter
).first
;
355 SQInteger item_current
= this->item_next
;
362 this->has_no_more_items
= true;
366 * Find the next item, and store that information.
370 if (this->item_iter
== this->list
->items
.end()) {
371 this->has_no_more_items
= true;
374 if (this->item_iter
== this->list
->items
.begin()) {
375 /* Use 'end' as marker for 'beyond begin' */
376 this->item_iter
= this->list
->items
.end();
380 if (this->item_iter
!= this->list
->items
.end()) item_next
= (*this->item_iter
).first
;
383 SQInteger
Next() override
385 if (this->IsEnd()) return 0;
387 SQInteger item_current
= this->item_next
;
392 void Remove(SQInteger item
) override
394 if (this->IsEnd()) return;
396 /* If we remove the 'next' item, skip to the next */
397 if (item
== this->item_next
) {
406 ScriptList::ScriptList()
409 this->sorter
= new ScriptListSorterValueDescending(this);
410 this->sorter_type
= SORT_BY_VALUE
;
411 this->sort_ascending
= false;
412 this->initialized
= false;
413 this->modifications
= 0;
416 ScriptList::~ScriptList()
421 bool ScriptList::HasItem(SQInteger item
)
423 return this->items
.count(item
) == 1;
426 void ScriptList::Clear()
428 this->modifications
++;
431 this->buckets
.clear();
435 void ScriptList::AddItem(SQInteger item
, SQInteger value
)
437 this->modifications
++;
439 if (this->HasItem(item
)) return;
441 this->items
[item
] = value
;
442 this->buckets
[value
].insert(item
);
445 void ScriptList::RemoveItem(SQInteger item
)
447 this->modifications
++;
449 ScriptListMap::iterator item_iter
= this->items
.find(item
);
450 if (item_iter
== this->items
.end()) return;
452 SQInteger value
= item_iter
->second
;
454 this->sorter
->Remove(item
);
455 ScriptListBucket::iterator bucket_iter
= this->buckets
.find(value
);
456 assert(bucket_iter
!= this->buckets
.end());
457 bucket_iter
->second
.erase(item
);
458 if (bucket_iter
->second
.empty()) this->buckets
.erase(bucket_iter
);
459 this->items
.erase(item_iter
);
462 SQInteger
ScriptList::Begin()
464 this->initialized
= true;
465 return this->sorter
->Begin();
468 SQInteger
ScriptList::Next()
470 if (!this->initialized
) {
471 Debug(script
, 0, "Next() is invalid as Begin() is never called");
474 return this->sorter
->Next();
477 bool ScriptList::IsEmpty()
479 return this->items
.empty();
482 bool ScriptList::IsEnd()
484 if (!this->initialized
) {
485 Debug(script
, 0, "IsEnd() is invalid as Begin() is never called");
488 return this->sorter
->IsEnd();
491 SQInteger
ScriptList::Count()
493 return this->items
.size();
496 SQInteger
ScriptList::GetValue(SQInteger item
)
498 ScriptListMap::const_iterator item_iter
= this->items
.find(item
);
499 return item_iter
== this->items
.end() ? 0 : item_iter
->second
;
502 bool ScriptList::SetValue(SQInteger item
, SQInteger value
)
504 this->modifications
++;
506 ScriptListMap::iterator item_iter
= this->items
.find(item
);
507 if (item_iter
== this->items
.end()) return false;
509 SQInteger value_old
= item_iter
->second
;
510 if (value_old
== value
) return true;
512 this->sorter
->Remove(item
);
513 ScriptListBucket::iterator bucket_iter
= this->buckets
.find(value_old
);
514 assert(bucket_iter
!= this->buckets
.end());
515 bucket_iter
->second
.erase(item
);
516 if (bucket_iter
->second
.empty()) this->buckets
.erase(bucket_iter
);
517 item_iter
->second
= value
;
518 this->buckets
[value
].insert(item
);
523 void ScriptList::Sort(SorterType sorter
, bool ascending
)
525 this->modifications
++;
527 if (sorter
!= SORT_BY_VALUE
&& sorter
!= SORT_BY_ITEM
) return;
528 if (sorter
== this->sorter_type
&& ascending
== this->sort_ascending
) return;
534 this->sorter
= new ScriptListSorterItemAscending(this);
536 this->sorter
= new ScriptListSorterItemDescending(this);
542 this->sorter
= new ScriptListSorterValueAscending(this);
544 this->sorter
= new ScriptListSorterValueDescending(this);
548 default: NOT_REACHED();
550 this->sorter_type
= sorter
;
551 this->sort_ascending
= ascending
;
552 this->initialized
= false;
555 void ScriptList::AddList(ScriptList
*list
)
557 if (list
== this) return;
559 if (this->IsEmpty()) {
560 /* If this is empty, we can just take the items of the other list as is. */
561 this->items
= list
->items
;
562 this->buckets
= list
->buckets
;
563 this->modifications
++;
565 ScriptListMap
*list_items
= &list
->items
;
566 for (auto &it
: *list_items
) {
567 this->AddItem(it
.first
);
568 this->SetValue(it
.first
, it
.second
);
573 void ScriptList::SwapList(ScriptList
*list
)
575 if (list
== this) return;
577 this->items
.swap(list
->items
);
578 this->buckets
.swap(list
->buckets
);
579 Swap(this->sorter
, list
->sorter
);
580 Swap(this->sorter_type
, list
->sorter_type
);
581 Swap(this->sort_ascending
, list
->sort_ascending
);
582 Swap(this->initialized
, list
->initialized
);
583 Swap(this->modifications
, list
->modifications
);
584 this->sorter
->Retarget(this);
585 list
->sorter
->Retarget(list
);
588 void ScriptList::RemoveAboveValue(SQInteger value
)
590 this->modifications
++;
592 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
593 next_iter
= iter
; next_iter
++;
594 if ((*iter
).second
> value
) this->RemoveItem((*iter
).first
);
598 void ScriptList::RemoveBelowValue(SQInteger value
)
600 this->modifications
++;
602 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
603 next_iter
= iter
; next_iter
++;
604 if ((*iter
).second
< value
) this->RemoveItem((*iter
).first
);
608 void ScriptList::RemoveBetweenValue(SQInteger start
, SQInteger end
)
610 this->modifications
++;
612 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
613 next_iter
= iter
; next_iter
++;
614 if ((*iter
).second
> start
&& (*iter
).second
< end
) this->RemoveItem((*iter
).first
);
618 void ScriptList::RemoveValue(SQInteger value
)
620 this->modifications
++;
622 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
623 next_iter
= iter
; next_iter
++;
624 if ((*iter
).second
== value
) this->RemoveItem((*iter
).first
);
628 void ScriptList::RemoveTop(SQInteger count
)
630 this->modifications
++;
632 if (!this->sort_ascending
) {
633 this->Sort(this->sorter_type
, !this->sort_ascending
);
634 this->RemoveBottom(count
);
635 this->Sort(this->sorter_type
, !this->sort_ascending
);
639 switch (this->sorter_type
) {
640 default: NOT_REACHED();
642 for (ScriptListBucket::iterator iter
= this->buckets
.begin(); iter
!= this->buckets
.end(); iter
= this->buckets
.begin()) {
643 ScriptItemList
*items
= &(*iter
).second
;
644 size_t size
= items
->size();
645 for (ScriptItemList::iterator iter
= items
->begin(); iter
!= items
->end(); iter
= items
->begin()) {
646 if (--count
< 0) return;
647 this->RemoveItem(*iter
);
648 /* When the last item is removed from the bucket, the bucket itself is removed.
649 * This means that the iterators can be invalid after a call to RemoveItem.
651 if (--size
== 0) break;
657 for (ScriptListMap::iterator iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= this->items
.begin()) {
658 if (--count
< 0) return;
659 this->RemoveItem((*iter
).first
);
665 void ScriptList::RemoveBottom(SQInteger count
)
667 this->modifications
++;
669 if (!this->sort_ascending
) {
670 this->Sort(this->sorter_type
, !this->sort_ascending
);
671 this->RemoveTop(count
);
672 this->Sort(this->sorter_type
, !this->sort_ascending
);
676 switch (this->sorter_type
) {
677 default: NOT_REACHED();
679 for (ScriptListBucket::reverse_iterator iter
= this->buckets
.rbegin(); iter
!= this->buckets
.rend(); iter
= this->buckets
.rbegin()) {
680 ScriptItemList
*items
= &(*iter
).second
;
681 size_t size
= items
->size();
682 for (ScriptItemList::reverse_iterator iter
= items
->rbegin(); iter
!= items
->rend(); iter
= items
->rbegin()) {
683 if (--count
< 0) return;
684 this->RemoveItem(*iter
);
685 /* When the last item is removed from the bucket, the bucket itself is removed.
686 * This means that the iterators can be invalid after a call to RemoveItem.
688 if (--size
== 0) break;
694 for (ScriptListMap::reverse_iterator iter
= this->items
.rbegin(); iter
!= this->items
.rend(); iter
= this->items
.rbegin()) {
695 if (--count
< 0) return;
696 this->RemoveItem((*iter
).first
);
702 void ScriptList::RemoveList(ScriptList
*list
)
704 this->modifications
++;
709 ScriptListMap
*list_items
= &list
->items
;
710 for (ScriptListMap::iterator iter
= list_items
->begin(); iter
!= list_items
->end(); iter
++) {
711 this->RemoveItem((*iter
).first
);
716 void ScriptList::KeepAboveValue(SQInteger value
)
718 this->modifications
++;
720 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
721 next_iter
= iter
; next_iter
++;
722 if ((*iter
).second
<= value
) this->RemoveItem((*iter
).first
);
726 void ScriptList::KeepBelowValue(SQInteger value
)
728 this->modifications
++;
730 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
731 next_iter
= iter
; next_iter
++;
732 if ((*iter
).second
>= value
) this->RemoveItem((*iter
).first
);
736 void ScriptList::KeepBetweenValue(SQInteger start
, SQInteger end
)
738 this->modifications
++;
740 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
741 next_iter
= iter
; next_iter
++;
742 if ((*iter
).second
<= start
|| (*iter
).second
>= end
) this->RemoveItem((*iter
).first
);
746 void ScriptList::KeepValue(SQInteger value
)
748 this->modifications
++;
750 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
751 next_iter
= iter
; next_iter
++;
752 if ((*iter
).second
!= value
) this->RemoveItem((*iter
).first
);
756 void ScriptList::KeepTop(SQInteger count
)
758 this->modifications
++;
760 this->RemoveBottom(this->Count() - count
);
763 void ScriptList::KeepBottom(SQInteger count
)
765 this->modifications
++;
767 this->RemoveTop(this->Count() - count
);
770 void ScriptList::KeepList(ScriptList
*list
)
772 if (list
== this) return;
774 this->modifications
++;
778 tmp
.RemoveList(list
);
779 this->RemoveList(&tmp
);
782 SQInteger
ScriptList::_get(HSQUIRRELVM vm
)
784 if (sq_gettype(vm
, 2) != OT_INTEGER
) return SQ_ERROR
;
787 sq_getinteger(vm
, 2, &idx
);
789 ScriptListMap::const_iterator item_iter
= this->items
.find(idx
);
790 if (item_iter
== this->items
.end()) return SQ_ERROR
;
792 sq_pushinteger(vm
, item_iter
->second
);
796 SQInteger
ScriptList::_set(HSQUIRRELVM vm
)
798 if (sq_gettype(vm
, 2) != OT_INTEGER
) return SQ_ERROR
;
799 if (sq_gettype(vm
, 3) != OT_INTEGER
&& sq_gettype(vm
, 3) != OT_NULL
) {
800 return sq_throwerror(vm
, "you can only assign integers to this list");
804 sq_getinteger(vm
, 2, &idx
);
805 if (sq_gettype(vm
, 3) == OT_NULL
) {
806 this->RemoveItem(idx
);
810 sq_getinteger(vm
, 3, &val
);
811 if (!this->HasItem(idx
)) {
812 this->AddItem(idx
, val
);
816 this->SetValue(idx
, val
);
820 SQInteger
ScriptList::_nexti(HSQUIRRELVM vm
)
822 if (sq_gettype(vm
, 2) == OT_NULL
) {
823 if (this->IsEmpty()) {
827 sq_pushinteger(vm
, this->Begin());
832 sq_getinteger(vm
, 2, &idx
);
834 SQInteger val
= this->Next();
840 sq_pushinteger(vm
, val
);
844 SQInteger
ScriptList::Valuate(HSQUIRRELVM vm
)
846 this->modifications
++;
848 /* The first parameter is the instance of ScriptList. */
849 int nparam
= sq_gettop(vm
) - 1;
852 return sq_throwerror(vm
, "You need to give at least a Valuator as parameter to ScriptList::Valuate");
855 /* Make sure the valuator function is really a function, and not any
856 * other type. It's parameter 2 for us, but for the user it's the
857 * first parameter they give. */
858 SQObjectType valuator_type
= sq_gettype(vm
, 2);
859 if (valuator_type
!= OT_CLOSURE
&& valuator_type
!= OT_NATIVECLOSURE
) {
860 return sq_throwerror(vm
, "parameter 1 has an invalid type (expected function)");
863 /* Don't allow docommand from a Valuator, as we can't resume in
865 bool backup_allow
= ScriptObject::GetAllowDoCommand();
866 ScriptObject::SetAllowDoCommand(false);
868 /* Limit the total number of ops that can be consumed by a valuate operation */
869 SQOpsLimiter
limiter(vm
, MAX_VALUATE_OPS
, "valuator function");
871 /* Push the function to call */
874 for (ScriptListMap::iterator iter
= this->items
.begin(); iter
!= this->items
.end(); iter
++) {
875 /* Check for changing of items. */
876 int previous_modification_count
= this->modifications
;
878 /* Push the root table as instance object, this is what squirrel does for meta-functions. */
879 sq_pushroottable(vm
);
880 /* Push all arguments for the valuator function. */
881 sq_pushinteger(vm
, (*iter
).first
);
882 for (int i
= 0; i
< nparam
- 1; i
++) {
886 /* Call the function. Squirrel pops all parameters and pushes the return value. */
887 if (SQ_FAILED(sq_call(vm
, nparam
+ 1, SQTrue
, SQTrue
))) {
888 ScriptObject::SetAllowDoCommand(backup_allow
);
892 /* Retrieve the return value */
894 switch (sq_gettype(vm
, -1)) {
896 sq_getinteger(vm
, -1, &value
);
902 sq_getbool(vm
, -1, &v
);
908 /* See below for explanation. The extra pop is the return value. */
909 sq_pop(vm
, nparam
+ 4);
911 ScriptObject::SetAllowDoCommand(backup_allow
);
912 return sq_throwerror(vm
, "return value of valuator is not valid (not integer/bool)");
916 /* Was something changed? */
917 if (previous_modification_count
!= this->modifications
) {
918 /* See below for explanation. The extra pop is the return value. */
919 sq_pop(vm
, nparam
+ 4);
921 ScriptObject::SetAllowDoCommand(backup_allow
);
922 return sq_throwerror(vm
, "modifying valuated list outside of valuator function");
925 this->SetValue((*iter
).first
, value
);
927 /* Pop the return value. */
930 Squirrel::DecreaseOps(vm
, 5);
932 /* Pop from the squirrel stack:
933 * 1. The root stable (as instance object).
934 * 2. The valuator function.
935 * 3. The parameters given to this function.
936 * 4. The ScriptList instance object. */
937 sq_pop(vm
, nparam
+ 3);
939 ScriptObject::SetAllowDoCommand(backup_allow
);