1 /*
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/>.
6  */
7 
8 /** @file script_list.cpp Implementation of ScriptList. */
9 
10 #include "../../stdafx.h"
11 #include "script_list.hpp"
12 #include "script_controller.hpp"
13 #include "../../debug.h"
14 #include "../../script/squirrel.hpp"
15 
16 #include "../../safeguards.h"
17 
18 /**
19  * Base class for any ScriptList sorter.
20  */
21 class ScriptListSorter {
22 protected:
23 	ScriptList *list;       ///< The list that's being sorted.
24 	bool has_no_more_items; ///< Whether we have more items to iterate over.
25 	int64 item_next;        ///< The next item we will show.
26 
27 public:
28 	/**
29 	 * Virtual dtor, needed to mute warnings.
30 	 */
~ScriptListSorter()31 	virtual ~ScriptListSorter() { }
32 
33 	/**
34 	 * Get the first item of the sorter.
35 	 */
36 	virtual int64 Begin() = 0;
37 
38 	/**
39 	 * Stop iterating a sorter.
40 	 */
41 	virtual void End() = 0;
42 
43 	/**
44 	 * Get the next item of the sorter.
45 	 */
46 	virtual int64 Next() = 0;
47 
48 	/**
49 	 * See if the sorter has reached the end.
50 	 */
IsEnd()51 	bool IsEnd()
52 	{
53 		return this->list->buckets.empty() || this->has_no_more_items;
54 	}
55 
56 	/**
57 	 * Callback from the list if an item gets removed.
58 	 */
59 	virtual void Remove(int item) = 0;
60 
61 	/**
62 	 * Attach the sorter to a new list. This assumes the content of the old list has been moved to
63 	 * the new list, too, so that we don't have to invalidate any iterators. Note that std::swap
64 	 * doesn't invalidate iterators on lists and maps, so that should be safe.
65 	 * @param target New list to attach to.
66 	 */
Retarget(ScriptList * new_list)67 	virtual void Retarget(ScriptList *new_list)
68 	{
69 		this->list = new_list;
70 	}
71 };
72 
73 /**
74  * Sort by value, ascending.
75  */
76 class ScriptListSorterValueAscending : public ScriptListSorter {
77 private:
78 	ScriptList::ScriptListBucket::iterator bucket_iter;    ///< The iterator over the list to find the buckets.
79 	ScriptList::ScriptItemList *bucket_list;               ///< The current bucket list we're iterator over.
80 	ScriptList::ScriptItemList::iterator bucket_list_iter; ///< The iterator over the bucket list.
81 
82 public:
83 	/**
84 	 * Create a new sorter.
85 	 * @param list The list to sort.
86 	 */
ScriptListSorterValueAscending(ScriptList * list)87 	ScriptListSorterValueAscending(ScriptList *list)
88 	{
89 		this->list = list;
90 		this->End();
91 	}
92 
Begin()93 	int64 Begin()
94 	{
95 		if (this->list->buckets.empty()) return 0;
96 		this->has_no_more_items = false;
97 
98 		this->bucket_iter = this->list->buckets.begin();
99 		this->bucket_list = &(*this->bucket_iter).second;
100 		this->bucket_list_iter = this->bucket_list->begin();
101 		this->item_next = *this->bucket_list_iter;
102 
103 		int64 item_current = this->item_next;
104 		FindNext();
105 		return item_current;
106 	}
107 
End()108 	void End()
109 	{
110 		this->bucket_list = nullptr;
111 		this->has_no_more_items = true;
112 		this->item_next = 0;
113 	}
114 
115 	/**
116 	 * Find the next item, and store that information.
117 	 */
FindNext()118 	void FindNext()
119 	{
120 		if (this->bucket_list == nullptr) {
121 			this->has_no_more_items = true;
122 			return;
123 		}
124 
125 		this->bucket_list_iter++;
126 		if (this->bucket_list_iter == this->bucket_list->end()) {
127 			this->bucket_iter++;
128 			if (this->bucket_iter == this->list->buckets.end()) {
129 				this->bucket_list = nullptr;
130 				return;
131 			}
132 			this->bucket_list = &(*this->bucket_iter).second;
133 			this->bucket_list_iter = this->bucket_list->begin();
134 		}
135 		this->item_next = *this->bucket_list_iter;
136 	}
137 
Next()138 	int64 Next()
139 	{
140 		if (this->IsEnd()) return 0;
141 
142 		int64 item_current = this->item_next;
143 		FindNext();
144 		return item_current;
145 	}
146 
Remove(int item)147 	void Remove(int item)
148 	{
149 		if (this->IsEnd()) return;
150 
151 		/* If we remove the 'next' item, skip to the next */
152 		if (item == this->item_next) {
153 			FindNext();
154 			return;
155 		}
156 	}
157 };
158 
159 /**
160  * Sort by value, descending.
161  */
162 class ScriptListSorterValueDescending : public ScriptListSorter {
163 private:
164 	/* Note: We cannot use reverse_iterator.
165 	 *       The iterators must only be invalidated when the element they are pointing to is removed.
166 	 *       This only holds for forward iterators. */
167 	ScriptList::ScriptListBucket::iterator bucket_iter;    ///< The iterator over the list to find the buckets.
168 	ScriptList::ScriptItemList *bucket_list;               ///< The current bucket list we're iterator over.
169 	ScriptList::ScriptItemList::iterator bucket_list_iter; ///< The iterator over the bucket list.
170 
171 public:
172 	/**
173 	 * Create a new sorter.
174 	 * @param list The list to sort.
175 	 */
ScriptListSorterValueDescending(ScriptList * list)176 	ScriptListSorterValueDescending(ScriptList *list)
177 	{
178 		this->list = list;
179 		this->End();
180 	}
181 
Begin()182 	int64 Begin()
183 	{
184 		if (this->list->buckets.empty()) return 0;
185 		this->has_no_more_items = false;
186 
187 		/* Go to the end of the bucket-list */
188 		this->bucket_iter = this->list->buckets.end();
189 		--this->bucket_iter;
190 		this->bucket_list = &(*this->bucket_iter).second;
191 
192 		/* Go to the end of the items in the bucket */
193 		this->bucket_list_iter = this->bucket_list->end();
194 		--this->bucket_list_iter;
195 		this->item_next = *this->bucket_list_iter;
196 
197 		int64 item_current = this->item_next;
198 		FindNext();
199 		return item_current;
200 	}
201 
End()202 	void End()
203 	{
204 		this->bucket_list = nullptr;
205 		this->has_no_more_items = true;
206 		this->item_next = 0;
207 	}
208 
209 	/**
210 	 * Find the next item, and store that information.
211 	 */
FindNext()212 	void FindNext()
213 	{
214 		if (this->bucket_list == nullptr) {
215 			this->has_no_more_items = true;
216 			return;
217 		}
218 
219 		if (this->bucket_list_iter == this->bucket_list->begin()) {
220 			if (this->bucket_iter == this->list->buckets.begin()) {
221 				this->bucket_list = nullptr;
222 				return;
223 			}
224 			this->bucket_iter--;
225 			this->bucket_list = &(*this->bucket_iter).second;
226 			/* Go to the end of the items in the bucket */
227 			this->bucket_list_iter = this->bucket_list->end();
228 			--this->bucket_list_iter;
229 		} else {
230 			this->bucket_list_iter--;
231 		}
232 		this->item_next = *this->bucket_list_iter;
233 	}
234 
Next()235 	int64 Next()
236 	{
237 		if (this->IsEnd()) return 0;
238 
239 		int64 item_current = this->item_next;
240 		FindNext();
241 		return item_current;
242 	}
243 
Remove(int item)244 	void Remove(int item)
245 	{
246 		if (this->IsEnd()) return;
247 
248 		/* If we remove the 'next' item, skip to the next */
249 		if (item == this->item_next) {
250 			FindNext();
251 			return;
252 		}
253 	}
254 };
255 
256 /**
257  * Sort by item, ascending.
258  */
259 class ScriptListSorterItemAscending : public ScriptListSorter {
260 private:
261 	ScriptList::ScriptListMap::iterator item_iter; ///< The iterator over the items in the map.
262 
263 public:
264 	/**
265 	 * Create a new sorter.
266 	 * @param list The list to sort.
267 	 */
ScriptListSorterItemAscending(ScriptList * list)268 	ScriptListSorterItemAscending(ScriptList *list)
269 	{
270 		this->list = list;
271 		this->End();
272 	}
273 
Begin()274 	int64 Begin()
275 	{
276 		if (this->list->items.empty()) return 0;
277 		this->has_no_more_items = false;
278 
279 		this->item_iter = this->list->items.begin();
280 		this->item_next = (*this->item_iter).first;
281 
282 		int64 item_current = this->item_next;
283 		FindNext();
284 		return item_current;
285 	}
286 
End()287 	void End()
288 	{
289 		this->has_no_more_items = true;
290 	}
291 
292 	/**
293 	 * Find the next item, and store that information.
294 	 */
FindNext()295 	void FindNext()
296 	{
297 		if (this->item_iter == this->list->items.end()) {
298 			this->has_no_more_items = true;
299 			return;
300 		}
301 		this->item_iter++;
302 		if (this->item_iter != this->list->items.end()) item_next = (*this->item_iter).first;
303 	}
304 
Next()305 	int64 Next()
306 	{
307 		if (this->IsEnd()) return 0;
308 
309 		int64 item_current = this->item_next;
310 		FindNext();
311 		return item_current;
312 	}
313 
Remove(int item)314 	void Remove(int item)
315 	{
316 		if (this->IsEnd()) return;
317 
318 		/* If we remove the 'next' item, skip to the next */
319 		if (item == this->item_next) {
320 			FindNext();
321 			return;
322 		}
323 	}
324 };
325 
326 /**
327  * Sort by item, descending.
328  */
329 class ScriptListSorterItemDescending : public ScriptListSorter {
330 private:
331 	/* Note: We cannot use reverse_iterator.
332 	 *       The iterators must only be invalidated when the element they are pointing to is removed.
333 	 *       This only holds for forward iterators. */
334 	ScriptList::ScriptListMap::iterator item_iter; ///< The iterator over the items in the map.
335 
336 public:
337 	/**
338 	 * Create a new sorter.
339 	 * @param list The list to sort.
340 	 */
ScriptListSorterItemDescending(ScriptList * list)341 	ScriptListSorterItemDescending(ScriptList *list)
342 	{
343 		this->list = list;
344 		this->End();
345 	}
346 
Begin()347 	int64 Begin()
348 	{
349 		if (this->list->items.empty()) return 0;
350 		this->has_no_more_items = false;
351 
352 		this->item_iter = this->list->items.end();
353 		--this->item_iter;
354 		this->item_next = (*this->item_iter).first;
355 
356 		int64 item_current = this->item_next;
357 		FindNext();
358 		return item_current;
359 	}
360 
End()361 	void End()
362 	{
363 		this->has_no_more_items = true;
364 	}
365 
366 	/**
367 	 * Find the next item, and store that information.
368 	 */
FindNext()369 	void FindNext()
370 	{
371 		if (this->item_iter == this->list->items.end()) {
372 			this->has_no_more_items = true;
373 			return;
374 		}
375 		if (this->item_iter == this->list->items.begin()) {
376 			/* Use 'end' as marker for 'beyond begin' */
377 			this->item_iter = this->list->items.end();
378 		} else {
379 			this->item_iter--;
380 		}
381 		if (this->item_iter != this->list->items.end()) item_next = (*this->item_iter).first;
382 	}
383 
Next()384 	int64 Next()
385 	{
386 		if (this->IsEnd()) return 0;
387 
388 		int64 item_current = this->item_next;
389 		FindNext();
390 		return item_current;
391 	}
392 
Remove(int item)393 	void Remove(int item)
394 	{
395 		if (this->IsEnd()) return;
396 
397 		/* If we remove the 'next' item, skip to the next */
398 		if (item == this->item_next) {
399 			FindNext();
400 			return;
401 		}
402 	}
403 };
404 
405 
406 
ScriptList()407 ScriptList::ScriptList()
408 {
409 	/* Default sorter */
410 	this->sorter         = new ScriptListSorterValueDescending(this);
411 	this->sorter_type    = SORT_BY_VALUE;
412 	this->sort_ascending = false;
413 	this->initialized    = false;
414 	this->modifications  = 0;
415 }
416 
~ScriptList()417 ScriptList::~ScriptList()
418 {
419 	delete this->sorter;
420 }
421 
HasItem(int64 item)422 bool ScriptList::HasItem(int64 item)
423 {
424 	return this->items.count(item) == 1;
425 }
426 
Clear()427 void ScriptList::Clear()
428 {
429 	this->modifications++;
430 
431 	this->items.clear();
432 	this->buckets.clear();
433 	this->sorter->End();
434 }
435 
AddItem(int64 item,int64 value)436 void ScriptList::AddItem(int64 item, int64 value)
437 {
438 	this->modifications++;
439 
440 	if (this->HasItem(item)) return;
441 
442 	this->items[item] = value;
443 	this->buckets[value].insert(item);
444 }
445 
RemoveItem(int64 item)446 void ScriptList::RemoveItem(int64 item)
447 {
448 	this->modifications++;
449 
450 	ScriptListMap::iterator item_iter = this->items.find(item);
451 	if (item_iter == this->items.end()) return;
452 
453 	int64 value = item_iter->second;
454 
455 	this->sorter->Remove(item);
456 	ScriptListBucket::iterator bucket_iter = this->buckets.find(value);
457 	assert(bucket_iter != this->buckets.end());
458 	bucket_iter->second.erase(item);
459 	if (bucket_iter->second.empty()) this->buckets.erase(bucket_iter);
460 	this->items.erase(item_iter);
461 }
462 
Begin()463 int64 ScriptList::Begin()
464 {
465 	this->initialized = true;
466 	return this->sorter->Begin();
467 }
468 
Next()469 int64 ScriptList::Next()
470 {
471 	if (!this->initialized) {
472 		Debug(script, 0, "Next() is invalid as Begin() is never called");
473 		return 0;
474 	}
475 	return this->sorter->Next();
476 }
477 
IsEmpty()478 bool ScriptList::IsEmpty()
479 {
480 	return this->items.empty();
481 }
482 
IsEnd()483 bool ScriptList::IsEnd()
484 {
485 	if (!this->initialized) {
486 		Debug(script, 0, "IsEnd() is invalid as Begin() is never called");
487 		return true;
488 	}
489 	return this->sorter->IsEnd();
490 }
491 
Count()492 int32 ScriptList::Count()
493 {
494 	return (int32)this->items.size();
495 }
496 
GetValue(int64 item)497 int64 ScriptList::GetValue(int64 item)
498 {
499 	ScriptListMap::const_iterator item_iter = this->items.find(item);
500 	return item_iter == this->items.end() ? 0 : item_iter->second;
501 }
502 
SetValue(int64 item,int64 value)503 bool ScriptList::SetValue(int64 item, int64 value)
504 {
505 	this->modifications++;
506 
507 	ScriptListMap::iterator item_iter = this->items.find(item);
508 	if (item_iter == this->items.end()) return false;
509 
510 	int64 value_old = item_iter->second;
511 	if (value_old == value) return true;
512 
513 	this->sorter->Remove(item);
514 	ScriptListBucket::iterator bucket_iter = this->buckets.find(value_old);
515 	assert(bucket_iter != this->buckets.end());
516 	bucket_iter->second.erase(item);
517 	if (bucket_iter->second.empty()) this->buckets.erase(bucket_iter);
518 	item_iter->second = value;
519 	this->buckets[value].insert(item);
520 
521 	return true;
522 }
523 
Sort(SorterType sorter,bool ascending)524 void ScriptList::Sort(SorterType sorter, bool ascending)
525 {
526 	this->modifications++;
527 
528 	if (sorter != SORT_BY_VALUE && sorter != SORT_BY_ITEM) return;
529 	if (sorter == this->sorter_type && ascending == this->sort_ascending) return;
530 
531 	delete this->sorter;
532 	switch (sorter) {
533 		case SORT_BY_ITEM:
534 			if (ascending) {
535 				this->sorter = new ScriptListSorterItemAscending(this);
536 			} else {
537 				this->sorter = new ScriptListSorterItemDescending(this);
538 			}
539 			break;
540 
541 		case SORT_BY_VALUE:
542 			if (ascending) {
543 				this->sorter = new ScriptListSorterValueAscending(this);
544 			} else {
545 				this->sorter = new ScriptListSorterValueDescending(this);
546 			}
547 			break;
548 
549 		default: NOT_REACHED();
550 	}
551 	this->sorter_type    = sorter;
552 	this->sort_ascending = ascending;
553 	this->initialized    = false;
554 }
555 
AddList(ScriptList * list)556 void ScriptList::AddList(ScriptList *list)
557 {
558 	if (list == this) return;
559 
560 	if (this->IsEmpty()) {
561 		/* If this is empty, we can just take the items of the other list as is. */
562 		this->items = list->items;
563 		this->buckets = list->buckets;
564 		this->modifications++;
565 	} else {
566 		ScriptListMap *list_items = &list->items;
567 		for (ScriptListMap::iterator iter = list_items->begin(); iter != list_items->end(); iter++) {
568 			this->AddItem((*iter).first);
569 			this->SetValue((*iter).first, (*iter).second);
570 		}
571 	}
572 }
573 
SwapList(ScriptList * list)574 void ScriptList::SwapList(ScriptList *list)
575 {
576 	if (list == this) return;
577 
578 	this->items.swap(list->items);
579 	this->buckets.swap(list->buckets);
580 	Swap(this->sorter, list->sorter);
581 	Swap(this->sorter_type, list->sorter_type);
582 	Swap(this->sort_ascending, list->sort_ascending);
583 	Swap(this->initialized, list->initialized);
584 	Swap(this->modifications, list->modifications);
585 	this->sorter->Retarget(this);
586 	list->sorter->Retarget(list);
587 }
588 
RemoveAboveValue(int64 value)589 void ScriptList::RemoveAboveValue(int64 value)
590 {
591 	this->modifications++;
592 
593 	for (ScriptListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) {
594 		next_iter = iter; next_iter++;
595 		if ((*iter).second > value) this->RemoveItem((*iter).first);
596 	}
597 }
598 
RemoveBelowValue(int64 value)599 void ScriptList::RemoveBelowValue(int64 value)
600 {
601 	this->modifications++;
602 
603 	for (ScriptListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) {
604 		next_iter = iter; next_iter++;
605 		if ((*iter).second < value) this->RemoveItem((*iter).first);
606 	}
607 }
608 
RemoveBetweenValue(int64 start,int64 end)609 void ScriptList::RemoveBetweenValue(int64 start, int64 end)
610 {
611 	this->modifications++;
612 
613 	for (ScriptListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) {
614 		next_iter = iter; next_iter++;
615 		if ((*iter).second > start && (*iter).second < end) this->RemoveItem((*iter).first);
616 	}
617 }
618 
RemoveValue(int64 value)619 void ScriptList::RemoveValue(int64 value)
620 {
621 	this->modifications++;
622 
623 	for (ScriptListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) {
624 		next_iter = iter; next_iter++;
625 		if ((*iter).second == value) this->RemoveItem((*iter).first);
626 	}
627 }
628 
RemoveTop(int32 count)629 void ScriptList::RemoveTop(int32 count)
630 {
631 	this->modifications++;
632 
633 	if (!this->sort_ascending) {
634 		this->Sort(this->sorter_type, !this->sort_ascending);
635 		this->RemoveBottom(count);
636 		this->Sort(this->sorter_type, !this->sort_ascending);
637 		return;
638 	}
639 
640 	switch (this->sorter_type) {
641 		default: NOT_REACHED();
642 		case SORT_BY_VALUE:
643 			for (ScriptListBucket::iterator iter = this->buckets.begin(); iter != this->buckets.end(); iter = this->buckets.begin()) {
644 				ScriptItemList *items = &(*iter).second;
645 				size_t size = items->size();
646 				for (ScriptItemList::iterator iter = items->begin(); iter != items->end(); iter = items->begin()) {
647 					if (--count < 0) return;
648 					this->RemoveItem(*iter);
649 					/* When the last item is removed from the bucket, the bucket itself is removed.
650 					 * This means that the iterators can be invalid after a call to RemoveItem.
651 					 */
652 					if (--size == 0) break;
653 				}
654 			}
655 			break;
656 
657 		case SORT_BY_ITEM:
658 			for (ScriptListMap::iterator iter = this->items.begin(); iter != this->items.end(); iter = this->items.begin()) {
659 				if (--count < 0) return;
660 				this->RemoveItem((*iter).first);
661 			}
662 			break;
663 	}
664 }
665 
RemoveBottom(int32 count)666 void ScriptList::RemoveBottom(int32 count)
667 {
668 	this->modifications++;
669 
670 	if (!this->sort_ascending) {
671 		this->Sort(this->sorter_type, !this->sort_ascending);
672 		this->RemoveTop(count);
673 		this->Sort(this->sorter_type, !this->sort_ascending);
674 		return;
675 	}
676 
677 	switch (this->sorter_type) {
678 		default: NOT_REACHED();
679 		case SORT_BY_VALUE:
680 			for (ScriptListBucket::reverse_iterator iter = this->buckets.rbegin(); iter != this->buckets.rend(); iter = this->buckets.rbegin()) {
681 				ScriptItemList *items = &(*iter).second;
682 				size_t size = items->size();
683 				for (ScriptItemList::reverse_iterator iter = items->rbegin(); iter != items->rend(); iter = items->rbegin()) {
684 					if (--count < 0) return;
685 					this->RemoveItem(*iter);
686 					/* When the last item is removed from the bucket, the bucket itself is removed.
687 					 * This means that the iterators can be invalid after a call to RemoveItem.
688 					 */
689 					if (--size == 0) break;
690 				}
691 			}
692 			break;
693 
694 		case SORT_BY_ITEM:
695 			for (ScriptListMap::reverse_iterator iter = this->items.rbegin(); iter != this->items.rend(); iter = this->items.rbegin()) {
696 				if (--count < 0) return;
697 				this->RemoveItem((*iter).first);
698 			}
699 			break;
700 	}
701 }
702 
RemoveList(ScriptList * list)703 void ScriptList::RemoveList(ScriptList *list)
704 {
705 	this->modifications++;
706 
707 	if (list == this) {
708 		Clear();
709 	} else {
710 		ScriptListMap *list_items = &list->items;
711 		for (ScriptListMap::iterator iter = list_items->begin(); iter != list_items->end(); iter++) {
712 			this->RemoveItem((*iter).first);
713 		}
714 	}
715 }
716 
KeepAboveValue(int64 value)717 void ScriptList::KeepAboveValue(int64 value)
718 {
719 	this->modifications++;
720 
721 	for (ScriptListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) {
722 		next_iter = iter; next_iter++;
723 		if ((*iter).second <= value) this->RemoveItem((*iter).first);
724 	}
725 }
726 
KeepBelowValue(int64 value)727 void ScriptList::KeepBelowValue(int64 value)
728 {
729 	this->modifications++;
730 
731 	for (ScriptListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) {
732 		next_iter = iter; next_iter++;
733 		if ((*iter).second >= value) this->RemoveItem((*iter).first);
734 	}
735 }
736 
KeepBetweenValue(int64 start,int64 end)737 void ScriptList::KeepBetweenValue(int64 start, int64 end)
738 {
739 	this->modifications++;
740 
741 	for (ScriptListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) {
742 		next_iter = iter; next_iter++;
743 		if ((*iter).second <= start || (*iter).second >= end) this->RemoveItem((*iter).first);
744 	}
745 }
746 
KeepValue(int64 value)747 void ScriptList::KeepValue(int64 value)
748 {
749 	this->modifications++;
750 
751 	for (ScriptListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) {
752 		next_iter = iter; next_iter++;
753 		if ((*iter).second != value) this->RemoveItem((*iter).first);
754 	}
755 }
756 
KeepTop(int32 count)757 void ScriptList::KeepTop(int32 count)
758 {
759 	this->modifications++;
760 
761 	this->RemoveBottom(this->Count() - count);
762 }
763 
KeepBottom(int32 count)764 void ScriptList::KeepBottom(int32 count)
765 {
766 	this->modifications++;
767 
768 	this->RemoveTop(this->Count() - count);
769 }
770 
KeepList(ScriptList * list)771 void ScriptList::KeepList(ScriptList *list)
772 {
773 	if (list == this) return;
774 
775 	this->modifications++;
776 
777 	ScriptList tmp;
778 	tmp.AddList(this);
779 	tmp.RemoveList(list);
780 	this->RemoveList(&tmp);
781 }
782 
_get(HSQUIRRELVM vm)783 SQInteger ScriptList::_get(HSQUIRRELVM vm)
784 {
785 	if (sq_gettype(vm, 2) != OT_INTEGER) return SQ_ERROR;
786 
787 	SQInteger idx;
788 	sq_getinteger(vm, 2, &idx);
789 
790 	ScriptListMap::const_iterator item_iter = this->items.find(idx);
791 	if (item_iter == this->items.end()) return SQ_ERROR;
792 
793 	sq_pushinteger(vm, item_iter->second);
794 	return 1;
795 }
796 
_set(HSQUIRRELVM vm)797 SQInteger ScriptList::_set(HSQUIRRELVM vm)
798 {
799 	if (sq_gettype(vm, 2) != OT_INTEGER) return SQ_ERROR;
800 	if (sq_gettype(vm, 3) != OT_INTEGER && sq_gettype(vm, 3) != OT_NULL) {
801 		return sq_throwerror(vm, "you can only assign integers to this list");
802 	}
803 
804 	SQInteger idx, val;
805 	sq_getinteger(vm, 2, &idx);
806 	if (sq_gettype(vm, 3) == OT_NULL) {
807 		this->RemoveItem(idx);
808 		return 0;
809 	}
810 
811 	sq_getinteger(vm, 3, &val);
812 	if (!this->HasItem(idx)) {
813 		this->AddItem(idx, val);
814 		return 0;
815 	}
816 
817 	this->SetValue(idx, val);
818 	return 0;
819 }
820 
_nexti(HSQUIRRELVM vm)821 SQInteger ScriptList::_nexti(HSQUIRRELVM vm)
822 {
823 	if (sq_gettype(vm, 2) == OT_NULL) {
824 		if (this->IsEmpty()) {
825 			sq_pushnull(vm);
826 			return 1;
827 		}
828 		sq_pushinteger(vm, this->Begin());
829 		return 1;
830 	}
831 
832 	SQInteger idx;
833 	sq_getinteger(vm, 2, &idx);
834 
835 	SQInteger val = this->Next();
836 	if (this->IsEnd()) {
837 		sq_pushnull(vm);
838 		return 1;
839 	}
840 
841 	sq_pushinteger(vm, val);
842 	return 1;
843 }
844 
Valuate(HSQUIRRELVM vm)845 SQInteger ScriptList::Valuate(HSQUIRRELVM vm)
846 {
847 	this->modifications++;
848 
849 	/* The first parameter is the instance of ScriptList. */
850 	int nparam = sq_gettop(vm) - 1;
851 
852 	if (nparam < 1) {
853 		return sq_throwerror(vm, "You need to give at least a Valuator as parameter to ScriptList::Valuate");
854 	}
855 
856 	/* Make sure the valuator function is really a function, and not any
857 	 * other type. It's parameter 2 for us, but for the user it's the
858 	 * first parameter they give. */
859 	SQObjectType valuator_type = sq_gettype(vm, 2);
860 	if (valuator_type != OT_CLOSURE && valuator_type != OT_NATIVECLOSURE) {
861 		return sq_throwerror(vm, "parameter 1 has an invalid type (expected function)");
862 	}
863 
864 	/* Don't allow docommand from a Valuator, as we can't resume in
865 	 * mid C++-code. */
866 	bool backup_allow = ScriptObject::GetAllowDoCommand();
867 	ScriptObject::SetAllowDoCommand(false);
868 
869 	/* Push the function to call */
870 	sq_push(vm, 2);
871 
872 	for (ScriptListMap::iterator iter = this->items.begin(); iter != this->items.end(); iter++) {
873 		/* Check for changing of items. */
874 		int previous_modification_count = this->modifications;
875 
876 		/* Push the root table as instance object, this is what squirrel does for meta-functions. */
877 		sq_pushroottable(vm);
878 		/* Push all arguments for the valuator function. */
879 		sq_pushinteger(vm, (*iter).first);
880 		for (int i = 0; i < nparam - 1; i++) {
881 			sq_push(vm, i + 3);
882 		}
883 
884 		/* Call the function. Squirrel pops all parameters and pushes the return value. */
885 		if (SQ_FAILED(sq_call(vm, nparam + 1, SQTrue, SQTrue))) {
886 			ScriptObject::SetAllowDoCommand(backup_allow);
887 			return SQ_ERROR;
888 		}
889 
890 		/* Retrieve the return value */
891 		SQInteger value;
892 		switch (sq_gettype(vm, -1)) {
893 			case OT_INTEGER: {
894 				sq_getinteger(vm, -1, &value);
895 				break;
896 			}
897 
898 			case OT_BOOL: {
899 				SQBool v;
900 				sq_getbool(vm, -1, &v);
901 				value = v ? 1 : 0;
902 				break;
903 			}
904 
905 			default: {
906 				/* See below for explanation. The extra pop is the return value. */
907 				sq_pop(vm, nparam + 4);
908 
909 				ScriptObject::SetAllowDoCommand(backup_allow);
910 				return sq_throwerror(vm, "return value of valuator is not valid (not integer/bool)");
911 			}
912 		}
913 
914 		/* Kill the script when the valuator call takes way too long.
915 		 * Triggered by nesting valuators, which then take billions of iterations. */
916 		if (ScriptController::GetOpsTillSuspend() < -1000000) {
917 			/* See below for explanation. The extra pop is the return value. */
918 			sq_pop(vm, nparam + 4);
919 
920 			ScriptObject::SetAllowDoCommand(backup_allow);
921 			return sq_throwerror(vm, "excessive CPU usage in valuator function");
922 		}
923 
924 		/* Was something changed? */
925 		if (previous_modification_count != this->modifications) {
926 			/* See below for explanation. The extra pop is the return value. */
927 			sq_pop(vm, nparam + 4);
928 
929 			ScriptObject::SetAllowDoCommand(backup_allow);
930 			return sq_throwerror(vm, "modifying valuated list outside of valuator function");
931 		}
932 
933 		this->SetValue((*iter).first, value);
934 
935 		/* Pop the return value. */
936 		sq_poptop(vm);
937 
938 		Squirrel::DecreaseOps(vm, 5);
939 	}
940 	/* Pop from the squirrel stack:
941 	 * 1. The root stable (as instance object).
942 	 * 2. The valuator function.
943 	 * 3. The parameters given to this function.
944 	 * 4. The ScriptList instance object. */
945 	sq_pop(vm, nparam + 3);
946 
947 	ScriptObject::SetAllowDoCommand(backup_allow);
948 	return 0;
949 }
950