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