1 /* $Id: script_list.cpp 26072 2013-11-23 18:13:30Z rubidium $ */
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file script_list.cpp Implementation of ScriptList. */
12 #include "../../stdafx.h"
13 #include "script_list.hpp"
14 #include "script_controller.hpp"
15 #include "../../debug.h"
16 #include "../../script/squirrel.hpp"
18 #include "../../safeguards.h"
21 * Base class for any ScriptList sorter.
23 class ScriptListSorter
{
25 ScriptList
*list
; ///< The list that's being sorted.
26 bool has_no_more_items
; ///< Whether we have more items to iterate over.
27 int64 item_next
; ///< The next item we will show.
31 * Virtual dtor, needed to mute warnings.
33 virtual ~ScriptListSorter() { }
36 * Get the first item of the sorter.
38 virtual int64
Begin() = 0;
41 * Stop iterating a sorter.
43 virtual void End() = 0;
46 * Get the next item of the sorter.
48 virtual int64
Next() = 0;
51 * See if the sorter has reached the end.
55 return this->list
->buckets
.empty() || this->has_no_more_items
;
59 * Callback from the list if an item gets removed.
61 virtual void Remove(int item
) = 0;
64 * Attach the sorter to a new list. This assumes the content of the old list has been moved to
65 * the new list, too, so that we don't have to invalidate any iterators. Note that std::swap
66 * doesn't invalidate iterators on lists and maps, so that should be safe.
67 * @param target New list to attach to.
69 virtual void Retarget(ScriptList
*new_list
)
71 this->list
= new_list
;
76 * Sort by value, ascending.
78 class ScriptListSorterValueAscending
: public ScriptListSorter
{
80 ScriptList::ScriptListBucket::iterator bucket_iter
; ///< The iterator over the list to find the buckets.
81 ScriptList::ScriptItemList
*bucket_list
; ///< The current bucket list we're iterator over.
82 ScriptList::ScriptItemList::iterator bucket_list_iter
; ///< The iterator over the bucket list.
86 * Create a new sorter.
87 * @param list The list to sort.
89 ScriptListSorterValueAscending(ScriptList
*list
)
97 if (this->list
->buckets
.empty()) return 0;
98 this->has_no_more_items
= false;
100 this->bucket_iter
= this->list
->buckets
.begin();
101 this->bucket_list
= &(*this->bucket_iter
).second
;
102 this->bucket_list_iter
= this->bucket_list
->begin();
103 this->item_next
= *this->bucket_list_iter
;
105 int64 item_current
= this->item_next
;
112 this->bucket_list
= NULL
;
113 this->has_no_more_items
= true;
118 * Find the next item, and store that information.
122 if (this->bucket_list
== NULL
) {
123 this->has_no_more_items
= true;
127 this->bucket_list_iter
++;
128 if (this->bucket_list_iter
== this->bucket_list
->end()) {
130 if (this->bucket_iter
== this->list
->buckets
.end()) {
131 this->bucket_list
= NULL
;
134 this->bucket_list
= &(*this->bucket_iter
).second
;
135 this->bucket_list_iter
= this->bucket_list
->begin();
137 this->item_next
= *this->bucket_list_iter
;
142 if (this->IsEnd()) return 0;
144 int64 item_current
= this->item_next
;
149 void Remove(int item
)
151 if (this->IsEnd()) return;
153 /* If we remove the 'next' item, skip to the next */
154 if (item
== this->item_next
) {
162 * Sort by value, descending.
164 class ScriptListSorterValueDescending
: public ScriptListSorter
{
166 /* Note: We cannot use reverse_iterator.
167 * The iterators must only be invalidated when the element they are pointing to is removed.
168 * This only holds for forward iterators. */
169 ScriptList::ScriptListBucket::iterator bucket_iter
; ///< The iterator over the list to find the buckets.
170 ScriptList::ScriptItemList
*bucket_list
; ///< The current bucket list we're iterator over.
171 ScriptList::ScriptItemList::iterator bucket_list_iter
; ///< The iterator over the bucket list.
175 * Create a new sorter.
176 * @param list The list to sort.
178 ScriptListSorterValueDescending(ScriptList
*list
)
186 if (this->list
->buckets
.empty()) return 0;
187 this->has_no_more_items
= false;
189 /* Go to the end of the bucket-list */
190 this->bucket_iter
= this->list
->buckets
.end();
192 this->bucket_list
= &(*this->bucket_iter
).second
;
194 /* Go to the end of the items in the bucket */
195 this->bucket_list_iter
= this->bucket_list
->end();
196 --this->bucket_list_iter
;
197 this->item_next
= *this->bucket_list_iter
;
199 int64 item_current
= this->item_next
;
206 this->bucket_list
= NULL
;
207 this->has_no_more_items
= true;
212 * Find the next item, and store that information.
216 if (this->bucket_list
== NULL
) {
217 this->has_no_more_items
= true;
221 if (this->bucket_list_iter
== this->bucket_list
->begin()) {
222 if (this->bucket_iter
== this->list
->buckets
.begin()) {
223 this->bucket_list
= NULL
;
227 this->bucket_list
= &(*this->bucket_iter
).second
;
228 /* Go to the end of the items in the bucket */
229 this->bucket_list_iter
= this->bucket_list
->end();
230 --this->bucket_list_iter
;
232 this->bucket_list_iter
--;
234 this->item_next
= *this->bucket_list_iter
;
239 if (this->IsEnd()) return 0;
241 int64 item_current
= this->item_next
;
246 void Remove(int item
)
248 if (this->IsEnd()) return;
250 /* If we remove the 'next' item, skip to the next */
251 if (item
== this->item_next
) {
259 * Sort by item, ascending.
261 class ScriptListSorterItemAscending
: public ScriptListSorter
{
263 ScriptList::ScriptListMap::iterator item_iter
; ///< The iterator over the items in the map.
267 * Create a new sorter.
268 * @param list The list to sort.
270 ScriptListSorterItemAscending(ScriptList
*list
)
278 if (this->list
->items
.empty()) return 0;
279 this->has_no_more_items
= false;
281 this->item_iter
= this->list
->items
.begin();
282 this->item_next
= (*this->item_iter
).first
;
284 int64 item_current
= this->item_next
;
291 this->has_no_more_items
= true;
295 * Find the next item, and store that information.
299 if (this->item_iter
== this->list
->items
.end()) {
300 this->has_no_more_items
= true;
304 if (this->item_iter
!= this->list
->items
.end()) item_next
= (*this->item_iter
).first
;
309 if (this->IsEnd()) return 0;
311 int64 item_current
= this->item_next
;
316 void Remove(int item
)
318 if (this->IsEnd()) return;
320 /* If we remove the 'next' item, skip to the next */
321 if (item
== this->item_next
) {
329 * Sort by item, descending.
331 class ScriptListSorterItemDescending
: public ScriptListSorter
{
333 /* Note: We cannot use reverse_iterator.
334 * The iterators must only be invalidated when the element they are pointing to is removed.
335 * This only holds for forward iterators. */
336 ScriptList::ScriptListMap::iterator item_iter
; ///< The iterator over the items in the map.
340 * Create a new sorter.
341 * @param list The list to sort.
343 ScriptListSorterItemDescending(ScriptList
*list
)
351 if (this->list
->items
.empty()) return 0;
352 this->has_no_more_items
= false;
354 this->item_iter
= this->list
->items
.end();
356 this->item_next
= (*this->item_iter
).first
;
358 int64 item_current
= this->item_next
;
365 this->has_no_more_items
= true;
369 * Find the next item, and store that information.
373 if (this->item_iter
== this->list
->items
.end()) {
374 this->has_no_more_items
= true;
377 if (this->item_iter
== this->list
->items
.begin()) {
378 /* Use 'end' as marker for 'beyond begin' */
379 this->item_iter
= this->list
->items
.end();
383 if (this->item_iter
!= this->list
->items
.end()) item_next
= (*this->item_iter
).first
;
388 if (this->IsEnd()) return 0;
390 int64 item_current
= this->item_next
;
395 void Remove(int item
)
397 if (this->IsEnd()) return;
399 /* If we remove the 'next' item, skip to the next */
400 if (item
== this->item_next
) {
409 ScriptList::ScriptList()
412 this->sorter
= new ScriptListSorterValueDescending(this);
413 this->sorter_type
= SORT_BY_VALUE
;
414 this->sort_ascending
= false;
415 this->initialized
= false;
416 this->modifications
= 0;
419 ScriptList::~ScriptList()
424 bool ScriptList::HasItem(int64 item
)
426 return this->items
.count(item
) == 1;
429 void ScriptList::Clear()
431 this->modifications
++;
434 this->buckets
.clear();
438 void ScriptList::AddItem(int64 item
, int64 value
)
440 this->modifications
++;
442 if (this->HasItem(item
)) return;
444 this->items
[item
] = value
;
445 this->buckets
[value
].insert(item
);
448 void ScriptList::RemoveItem(int64 item
)
450 this->modifications
++;
452 ScriptListMap::iterator item_iter
= this->items
.find(item
);
453 if (item_iter
== this->items
.end()) return;
455 int64 value
= item_iter
->second
;
457 this->sorter
->Remove(item
);
458 ScriptListBucket::iterator bucket_iter
= this->buckets
.find(value
);
459 assert(bucket_iter
!= this->buckets
.end());
460 bucket_iter
->second
.erase(item
);
461 if (bucket_iter
->second
.empty()) this->buckets
.erase(bucket_iter
);
462 this->items
.erase(item_iter
);
465 int64
ScriptList::Begin()
467 this->initialized
= true;
468 return this->sorter
->Begin();
471 int64
ScriptList::Next()
473 if (this->initialized
== false) {
474 DEBUG(script
, 0, "Next() is invalid as Begin() is never called");
477 return this->sorter
->Next();
480 bool ScriptList::IsEmpty()
482 return this->items
.empty();
485 bool ScriptList::IsEnd()
487 if (this->initialized
== false) {
488 DEBUG(script
, 0, "IsEnd() is invalid as Begin() is never called");
491 return this->sorter
->IsEnd();
494 int32
ScriptList::Count()
496 return (int32
)this->items
.size();
499 int64
ScriptList::GetValue(int64 item
)
501 ScriptListMap::const_iterator item_iter
= this->items
.find(item
);
502 return item_iter
== this->items
.end() ? 0 : item_iter
->second
;
505 bool ScriptList::SetValue(int64 item
, int64 value
)
507 this->modifications
++;
509 ScriptListMap::iterator item_iter
= this->items
.find(item
);
510 if (item_iter
== this->items
.end()) return false;
512 int64 value_old
= item_iter
->second
;
513 if (value_old
== value
) return true;
515 this->sorter
->Remove(item
);
516 ScriptListBucket::iterator bucket_iter
= this->buckets
.find(value_old
);
517 assert(bucket_iter
!= this->buckets
.end());
518 bucket_iter
->second
.erase(item
);
519 if (bucket_iter
->second
.empty()) this->buckets
.erase(bucket_iter
);
520 item_iter
->second
= value
;
521 this->buckets
[value
].insert(item
);
526 void ScriptList::Sort(SorterType sorter
, bool ascending
)
528 this->modifications
++;
530 if (sorter
!= SORT_BY_VALUE
&& sorter
!= SORT_BY_ITEM
) return;
531 if (sorter
== this->sorter_type
&& ascending
== this->sort_ascending
) return;
537 this->sorter
= new ScriptListSorterItemAscending(this);
539 this->sorter
= new ScriptListSorterItemDescending(this);
545 this->sorter
= new ScriptListSorterValueAscending(this);
547 this->sorter
= new ScriptListSorterValueDescending(this);
551 default: NOT_REACHED();
553 this->sorter_type
= sorter
;
554 this->sort_ascending
= ascending
;
555 this->initialized
= false;
558 void ScriptList::AddList(ScriptList
*list
)
560 if (list
== this) return;
562 ScriptListMap
*list_items
= &list
->items
;
563 for (ScriptListMap::iterator iter
= list_items
->begin(); iter
!= list_items
->end(); iter
++) {
564 this->AddItem((*iter
).first
);
565 this->SetValue((*iter
).first
, (*iter
).second
);
569 void ScriptList::SwapList(ScriptList
*list
)
571 if (list
== this) return;
573 this->items
.swap(list
->items
);
574 this->buckets
.swap(list
->buckets
);
575 Swap(this->sorter
, list
->sorter
);
576 Swap(this->sorter_type
, list
->sorter_type
);
577 Swap(this->sort_ascending
, list
->sort_ascending
);
578 Swap(this->initialized
, list
->initialized
);
579 Swap(this->modifications
, list
->modifications
);
580 this->sorter
->Retarget(this);
581 list
->sorter
->Retarget(list
);
584 void ScriptList::RemoveAboveValue(int64 value
)
586 this->modifications
++;
588 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
589 next_iter
= iter
; next_iter
++;
590 if ((*iter
).second
> value
) this->RemoveItem((*iter
).first
);
594 void ScriptList::RemoveBelowValue(int64 value
)
596 this->modifications
++;
598 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
599 next_iter
= iter
; next_iter
++;
600 if ((*iter
).second
< value
) this->RemoveItem((*iter
).first
);
604 void ScriptList::RemoveBetweenValue(int64 start
, int64 end
)
606 this->modifications
++;
608 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
609 next_iter
= iter
; next_iter
++;
610 if ((*iter
).second
> start
&& (*iter
).second
< end
) this->RemoveItem((*iter
).first
);
614 void ScriptList::RemoveValue(int64 value
)
616 this->modifications
++;
618 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
619 next_iter
= iter
; next_iter
++;
620 if ((*iter
).second
== value
) this->RemoveItem((*iter
).first
);
624 void ScriptList::RemoveTop(int32 count
)
626 this->modifications
++;
628 if (!this->sort_ascending
) {
629 this->Sort(this->sorter_type
, !this->sort_ascending
);
630 this->RemoveBottom(count
);
631 this->Sort(this->sorter_type
, !this->sort_ascending
);
635 switch (this->sorter_type
) {
636 default: NOT_REACHED();
638 for (ScriptListBucket::iterator iter
= this->buckets
.begin(); iter
!= this->buckets
.end(); iter
= this->buckets
.begin()) {
639 ScriptItemList
*items
= &(*iter
).second
;
640 size_t size
= items
->size();
641 for (ScriptItemList::iterator iter
= items
->begin(); iter
!= items
->end(); iter
= items
->begin()) {
642 if (--count
< 0) return;
643 this->RemoveItem(*iter
);
644 /* When the last item is removed from the bucket, the bucket itself is removed.
645 * This means that the iterators can be invalid after a call to RemoveItem.
647 if (--size
== 0) break;
653 for (ScriptListMap::iterator iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= this->items
.begin()) {
654 if (--count
< 0) return;
655 this->RemoveItem((*iter
).first
);
661 void ScriptList::RemoveBottom(int32 count
)
663 this->modifications
++;
665 if (!this->sort_ascending
) {
666 this->Sort(this->sorter_type
, !this->sort_ascending
);
667 this->RemoveTop(count
);
668 this->Sort(this->sorter_type
, !this->sort_ascending
);
672 switch (this->sorter_type
) {
673 default: NOT_REACHED();
675 for (ScriptListBucket::reverse_iterator iter
= this->buckets
.rbegin(); iter
!= this->buckets
.rend(); iter
= this->buckets
.rbegin()) {
676 ScriptItemList
*items
= &(*iter
).second
;
677 size_t size
= items
->size();
678 for (ScriptItemList::reverse_iterator iter
= items
->rbegin(); iter
!= items
->rend(); iter
= items
->rbegin()) {
679 if (--count
< 0) return;
680 this->RemoveItem(*iter
);
681 /* When the last item is removed from the bucket, the bucket itself is removed.
682 * This means that the iterators can be invalid after a call to RemoveItem.
684 if (--size
== 0) break;
690 for (ScriptListMap::reverse_iterator iter
= this->items
.rbegin(); iter
!= this->items
.rend(); iter
= this->items
.rbegin()) {
691 if (--count
< 0) return;
692 this->RemoveItem((*iter
).first
);
698 void ScriptList::RemoveList(ScriptList
*list
)
700 this->modifications
++;
705 ScriptListMap
*list_items
= &list
->items
;
706 for (ScriptListMap::iterator iter
= list_items
->begin(); iter
!= list_items
->end(); iter
++) {
707 this->RemoveItem((*iter
).first
);
712 void ScriptList::KeepAboveValue(int64 value
)
714 this->modifications
++;
716 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
717 next_iter
= iter
; next_iter
++;
718 if ((*iter
).second
<= value
) this->RemoveItem((*iter
).first
);
722 void ScriptList::KeepBelowValue(int64 value
)
724 this->modifications
++;
726 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
727 next_iter
= iter
; next_iter
++;
728 if ((*iter
).second
>= value
) this->RemoveItem((*iter
).first
);
732 void ScriptList::KeepBetweenValue(int64 start
, int64 end
)
734 this->modifications
++;
736 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
737 next_iter
= iter
; next_iter
++;
738 if ((*iter
).second
<= start
|| (*iter
).second
>= end
) this->RemoveItem((*iter
).first
);
742 void ScriptList::KeepValue(int64 value
)
744 this->modifications
++;
746 for (ScriptListMap::iterator next_iter
, iter
= this->items
.begin(); iter
!= this->items
.end(); iter
= next_iter
) {
747 next_iter
= iter
; next_iter
++;
748 if ((*iter
).second
!= value
) this->RemoveItem((*iter
).first
);
752 void ScriptList::KeepTop(int32 count
)
754 this->modifications
++;
756 this->RemoveBottom(this->Count() - count
);
759 void ScriptList::KeepBottom(int32 count
)
761 this->modifications
++;
763 this->RemoveTop(this->Count() - count
);
766 void ScriptList::KeepList(ScriptList
*list
)
768 if (list
== this) return;
770 this->modifications
++;
774 tmp
.RemoveList(list
);
775 this->RemoveList(&tmp
);
778 SQInteger
ScriptList::_get(HSQUIRRELVM vm
)
780 if (sq_gettype(vm
, 2) != OT_INTEGER
) return SQ_ERROR
;
783 sq_getinteger(vm
, 2, &idx
);
785 ScriptListMap::const_iterator item_iter
= this->items
.find(idx
);
786 if (item_iter
== this->items
.end()) return SQ_ERROR
;
788 sq_pushinteger(vm
, item_iter
->second
);
792 SQInteger
ScriptList::_set(HSQUIRRELVM vm
)
794 if (sq_gettype(vm
, 2) != OT_INTEGER
) return SQ_ERROR
;
795 if (sq_gettype(vm
, 3) != OT_INTEGER
&& sq_gettype(vm
, 3) != OT_NULL
) {
796 return sq_throwerror(vm
, "you can only assign integers to this list");
800 sq_getinteger(vm
, 2, &idx
);
801 if (sq_gettype(vm
, 3) == OT_NULL
) {
802 this->RemoveItem(idx
);
806 sq_getinteger(vm
, 3, &val
);
807 if (!this->HasItem(idx
)) {
808 this->AddItem(idx
, val
);
812 this->SetValue(idx
, val
);
816 SQInteger
ScriptList::_nexti(HSQUIRRELVM vm
)
818 if (sq_gettype(vm
, 2) == OT_NULL
) {
819 if (this->IsEmpty()) {
823 sq_pushinteger(vm
, this->Begin());
828 sq_getinteger(vm
, 2, &idx
);
830 int val
= this->Next();
836 sq_pushinteger(vm
, val
);
840 SQInteger
ScriptList::Valuate(HSQUIRRELVM vm
)
842 this->modifications
++;
844 /* The first parameter is the instance of ScriptList. */
845 int nparam
= sq_gettop(vm
) - 1;
848 return sq_throwerror(vm
, "You need to give a least a Valuator as parameter to ScriptList::Valuate");
851 /* Make sure the valuator function is really a function, and not any
852 * other type. It's parameter 2 for us, but for the user it's the
853 * first parameter they give. */
854 SQObjectType valuator_type
= sq_gettype(vm
, 2);
855 if (valuator_type
!= OT_CLOSURE
&& valuator_type
!= OT_NATIVECLOSURE
) {
856 return sq_throwerror(vm
, "parameter 1 has an invalid type (expected function)");
859 /* Don't allow docommand from a Valuator, as we can't resume in
861 bool backup_allow
= ScriptObject::GetAllowDoCommand();
862 ScriptObject::SetAllowDoCommand(false);
864 /* Push the function to call */
867 for (ScriptListMap::iterator iter
= this->items
.begin(); iter
!= this->items
.end(); iter
++) {
868 /* Check for changing of items. */
869 int previous_modification_count
= this->modifications
;
871 /* Push the root table as instance object, this is what squirrel does for meta-functions. */
872 sq_pushroottable(vm
);
873 /* Push all arguments for the valuator function. */
874 sq_pushinteger(vm
, (*iter
).first
);
875 for (int i
= 0; i
< nparam
- 1; i
++) {
879 /* Call the function. Squirrel pops all parameters and pushes the return value. */
880 if (SQ_FAILED(sq_call(vm
, nparam
+ 1, SQTrue
, SQTrue
))) {
881 ScriptObject::SetAllowDoCommand(backup_allow
);
885 /* Retrieve the return value */
887 switch (sq_gettype(vm
, -1)) {
889 sq_getinteger(vm
, -1, &value
);
895 sq_getbool(vm
, -1, &v
);
901 /* See below for explanation. The extra pop is the return value. */
902 sq_pop(vm
, nparam
+ 4);
904 ScriptObject::SetAllowDoCommand(backup_allow
);
905 return sq_throwerror(vm
, "return value of valuator is not valid (not integer/bool)");
909 /* Kill the script when the valuator call takes way too long.
910 * Triggered by nesting valuators, which then take billions of iterations. */
911 if (ScriptController::GetOpsTillSuspend() < -1000000) {
912 /* See below for explanation. The extra pop is the return value. */
913 sq_pop(vm
, nparam
+ 4);
915 ScriptObject::SetAllowDoCommand(backup_allow
);
916 return sq_throwerror(vm
, "excessive CPU usage in valuator function");
919 /* Was something changed? */
920 if (previous_modification_count
!= this->modifications
) {
921 /* See below for explanation. The extra pop is the return value. */
922 sq_pop(vm
, nparam
+ 4);
924 ScriptObject::SetAllowDoCommand(backup_allow
);
925 return sq_throwerror(vm
, "modifying valuated list outside of valuator function");
928 this->SetValue((*iter
).first
, value
);
930 /* Pop the return value. */
933 Squirrel::DecreaseOps(vm
, 5);
935 /* Pop from the squirrel stack:
936 * 1. The root stable (as instance object).
937 * 2. The valuator function.
938 * 3. The parameters given to this function.
939 * 4. The ScriptList instance object. */
940 sq_pop(vm
, nparam
+ 3);
942 ScriptObject::SetAllowDoCommand(backup_allow
);