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 tracerestrict.cpp Main file for Trace Restrict */
11 #include "tracerestrict.h"
13 #include "core/bitmath_func.hpp"
14 #include "core/pool_func.hpp"
15 #include "command_func.h"
16 #include "company_func.h"
17 #include "viewport_func.h"
18 #include "window_func.h"
19 #include "order_base.h"
20 #include "cargotype.h"
22 #include "string_func.h"
23 #include "pathfinder/yapf/yapf_cache.h"
25 #include "safeguards.h"
32 * Trace Restrict Data Storage Model Notes:
34 * Signals may have 0, 1 or 2 trace restrict programs attached to them,
35 * up to one for each track. Two-way signals share the same program.
37 * The mapping between signals and programs is defined in terms of
38 * TraceRestrictRefId to TraceRestrictProgramID,
39 * where TraceRestrictRefId is formed of the tile index and track,
40 * and TraceRestrictProgramID is an index into the program pool.
42 * If one or more mappings exist for a given signal tile, bit 12 of M3 will be set to 1.
43 * This is updated whenever mappings are added/removed for that tile. This is to avoid
44 * needing to do a mapping lookup for the common case where there is no trace restrict
45 * program mapping for the given tile.
47 * Programs in the program pool are refcounted based on the number of mappings which exist.
48 * When this falls to 0, the program is deleted from the pool.
49 * If a program has a refcount greater than 1, it is a shared program.
51 * In all cases, an empty program is evaluated the same as the absence of a program.
52 * Therefore it is not necessary to store mappings to empty unshared programs.
53 * Any editing action which would otherwise result in a mapping to an empty program
54 * which has no other references, instead removes the mapping.
55 * This is not done for shared programs as this would delete the shared aspect whenever
56 * the program became empty.
58 * Special case: In the case where an empty program with refcount 2 has one of its
59 * mappings removed, the other mapping is left pointing to an empty unshared program.
60 * This other mapping is then removed by performing a linear search of the mappings,
61 * and removing the reference to that program ID.
64 TraceRestrictProgramPool
_tracerestrictprogram_pool("TraceRestrictProgram");
65 INSTANTIATE_POOL_METHODS(TraceRestrictProgram
)
67 TraceRestrictSlotPool
_tracerestrictslot_pool("TraceRestrictSlot");
68 INSTANTIATE_POOL_METHODS(TraceRestrictSlot
)
71 * TraceRestrictRefId --> TraceRestrictProgramID (Pool ID) mapping
72 * The indirection is mainly to enable shared programs
73 * TODO: use a more efficient container/indirection mechanism
75 TraceRestrictMapping _tracerestrictprogram_mapping
;
78 * List of pre-defined pathfinder penalty values
79 * This is indexed by TraceRestrictPathfinderPenaltyPresetIndex
81 const uint16 _tracerestrict_pathfinder_penalty_preset_values
[] = {
87 assert_compile(lengthof(_tracerestrict_pathfinder_penalty_preset_values
) == TRPPPI_END
);
90 * This should be used when all pools have been or are immediately about to be also cleared
91 * Calling this at other times will leave dangling refcounts
93 void ClearTraceRestrictMapping() {
94 _tracerestrictprogram_mapping
.clear();
98 * Flags used for the program execution condition stack
99 * Each 'if' pushes onto the stack
100 * Each 'end if' pops from the stack
101 * Elif/orif/else may modify the stack top
103 enum TraceRestrictCondStackFlags
{
104 TRCSF_DONE_IF
= 1<<0, ///< The if/elif/else is "done", future elif/else branches will not be executed
105 TRCSF_SEEN_ELSE
= 1<<1, ///< An else branch has been seen already, error if another is seen afterwards
106 TRCSF_ACTIVE
= 1<<2, ///< The condition is currently active
107 TRCSF_PARENT_INACTIVE
= 1<<3, ///< The parent condition is not active, thus this condition is also not active
109 DECLARE_ENUM_AS_BIT_SET(TraceRestrictCondStackFlags
)
112 * Helper function to handle condition stack manipulatoin
114 static void HandleCondition(std::vector
<TraceRestrictCondStackFlags
> &condstack
, TraceRestrictCondFlags condflags
, bool value
)
116 if (condflags
& TRCF_OR
) {
117 assert(!condstack
.empty());
118 if (condstack
.back() & TRCSF_ACTIVE
) {
119 // leave TRCSF_ACTIVE set
124 if (condflags
& (TRCF_OR
| TRCF_ELSE
)) {
125 assert(!condstack
.empty());
126 if (condstack
.back() & (TRCSF_DONE_IF
| TRCSF_PARENT_INACTIVE
)) {
127 condstack
.back() &= ~TRCSF_ACTIVE
;
131 if (!condstack
.empty() && !(condstack
.back() & TRCSF_ACTIVE
)) {
132 //this is a 'nested if', the 'parent if' is not active
133 condstack
.push_back(TRCSF_PARENT_INACTIVE
);
136 condstack
.push_back(static_cast<TraceRestrictCondStackFlags
>(0));
140 condstack
.back() |= TRCSF_DONE_IF
| TRCSF_ACTIVE
;
142 condstack
.back() &= ~TRCSF_ACTIVE
;
147 * Integer condition testing
148 * Test value op condvalue
150 static bool TestCondition(int value
, TraceRestrictCondOp condop
, int condvalue
)
154 return value
== condvalue
;
156 return value
!= condvalue
;
158 return value
< condvalue
;
160 return value
<= condvalue
;
162 return value
> condvalue
;
164 return value
>= condvalue
;
172 * Binary condition testing helper function
174 static bool TestBinaryConditionCommon(TraceRestrictItem item
, bool input
)
176 switch (GetTraceRestrictCondOp(item
)) {
190 * Test order condition
191 * @p order may be NULL
193 static bool TestOrderCondition(const Order
*order
, TraceRestrictItem item
)
198 DestinationID condvalue
= GetTraceRestrictValue(item
);
199 switch (static_cast<TraceRestrictOrderCondAuxField
>(GetTraceRestrictAuxField(item
))) {
201 result
= order
->IsType(OT_GOTO_STATION
) && order
->GetDestination() == condvalue
;
204 case TROCAF_WAYPOINT
:
205 result
= order
->IsType(OT_GOTO_WAYPOINT
) && order
->GetDestination() == condvalue
;
209 result
= order
->IsType(OT_GOTO_DEPOT
) && order
->GetDestination() == condvalue
;
216 return TestBinaryConditionCommon(item
, result
);
220 * Test station condition
222 static bool TestStationCondition(StationID station
, TraceRestrictItem item
)
224 bool result
= (GetTraceRestrictAuxField(item
) == TROCAF_STATION
) && (station
== GetTraceRestrictValue(item
));
225 return TestBinaryConditionCommon(item
, result
);
230 * Execute program on train and store results in out
231 * @p v may not be NULL
232 * @p out should be zero-initialised
234 void TraceRestrictProgram::Execute(const Train
* v
, const TraceRestrictProgramInput
&input
, TraceRestrictProgramResult
& out
) const
236 // static to avoid needing to re-alloc/resize on each execution
237 static std::vector
<TraceRestrictCondStackFlags
> condstack
;
240 bool have_previous_signal
= false;
241 TileIndex previous_signal_tile
= INVALID_TILE
;
243 size_t size
= this->items
.size();
244 for (size_t i
= 0; i
< size
; i
++) {
245 TraceRestrictItem item
= this->items
[i
];
246 TraceRestrictItemType type
= GetTraceRestrictType(item
);
248 if (IsTraceRestrictConditional(item
)) {
249 TraceRestrictCondFlags condflags
= GetTraceRestrictCondFlags(item
);
250 TraceRestrictCondOp condop
= GetTraceRestrictCondOp(item
);
252 if (type
== TRIT_COND_ENDIF
) {
253 assert(!condstack
.empty());
254 if (condflags
& TRCF_ELSE
) {
256 assert(!(condstack
.back() & TRCSF_SEEN_ELSE
));
257 HandleCondition(condstack
, condflags
, true);
258 condstack
.back() |= TRCSF_SEEN_ELSE
;
261 condstack
.pop_back();
264 uint16 condvalue
= GetTraceRestrictValue(item
);
267 case TRIT_COND_UNDEFINED
:
271 case TRIT_COND_TRAIN_LENGTH
:
272 result
= TestCondition(CeilDiv(v
->gcache
.cached_total_length
, TILE_SIZE
), condop
, condvalue
);
275 case TRIT_COND_MAX_SPEED
:
276 result
= TestCondition(v
->GetDisplayMaxSpeed(), condop
, condvalue
);
279 case TRIT_COND_CURRENT_ORDER
:
280 result
= TestOrderCondition(&(v
->current_order
), item
);
283 case TRIT_COND_NEXT_ORDER
: {
284 if (v
->orders
.list
== NULL
) break;
285 if (v
->orders
.list
->GetNumOrders() == 0) break;
287 const Order
*current_order
= v
->GetOrder(v
->cur_real_order_index
);
288 for (const Order
*order
= v
->orders
.list
->GetNext(current_order
); order
!= current_order
; order
= v
->orders
.list
->GetNext(order
)) {
289 if (order
->IsGotoOrder()) {
290 result
= TestOrderCondition(order
, item
);
297 case TRIT_COND_LAST_STATION
:
298 result
= TestStationCondition(v
->last_station_visited
, item
);
301 case TRIT_COND_CARGO
: {
302 bool have_cargo
= false;
303 for (const Vehicle
*v_iter
= v
; v_iter
!= NULL
; v_iter
= v_iter
->Next()) {
304 if (v_iter
->cargo_type
== GetTraceRestrictValue(item
) && v_iter
->cargo_cap
> 0) {
309 result
= TestBinaryConditionCommon(item
, have_cargo
);
313 case TRIT_COND_ENTRY_DIRECTION
: {
314 bool direction_match
;
315 switch (GetTraceRestrictValue(item
)) {
320 direction_match
= (static_cast<DiagDirection
>(GetTraceRestrictValue(item
)) == TrackdirToExitdir(ReverseTrackdir(input
.trackdir
)));
324 direction_match
= IsTileType(input
.tile
, MP_RAILWAY
) && HasSignalOnTrackdir(input
.tile
, input
.trackdir
);
328 direction_match
= IsTileType(input
.tile
, MP_RAILWAY
) && !HasSignalOnTrackdir(input
.tile
, input
.trackdir
);
335 result
= TestBinaryConditionCommon(item
, direction_match
);
339 case TRIT_COND_PBS_ENTRY_SIGNAL
: {
340 // TRVT_TILE_INDEX value type uses the next slot
342 uint32_t signal_tile
= this->items
[i
];
343 if (!have_previous_signal
) {
344 if (input
.previous_signal_callback
) {
345 previous_signal_tile
= input
.previous_signal_callback(v
, input
.previous_signal_ptr
);
347 have_previous_signal
= true;
349 bool match
= (signal_tile
!= INVALID_TILE
)
350 && (previous_signal_tile
== signal_tile
);
351 result
= TestBinaryConditionCommon(item
, match
);
355 case TRIT_COND_TRAIN_GROUP
: {
356 result
= TestBinaryConditionCommon(item
, GroupIsInGroup(v
->group_id
, GetTraceRestrictValue(item
)));
360 case TRIT_COND_TRAIN_IN_SLOT
: {
361 const TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item
));
362 result
= TestBinaryConditionCommon(item
, slot
!= NULL
&& slot
->IsOccupant(v
->index
));
366 case TRIT_COND_SLOT_OCCUPANCY
: {
367 // TRIT_COND_SLOT_OCCUPANCY value type uses the next slot
369 uint32_t value
= this->items
[i
];
370 const TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item
));
371 switch (static_cast<TraceRestrictSlotOccupancyCondAuxField
>(GetTraceRestrictAuxField(item
))) {
372 case TRSOCAF_OCCUPANTS
:
373 result
= TestCondition(slot
!= NULL
? slot
->occupants
.size() : 0, condop
, value
);
376 case TRSOCAF_REMAINING
:
377 result
= TestCondition(slot
!= NULL
? slot
->max_occupancy
- slot
->occupants
.size() : 0, condop
, value
);
390 HandleCondition(condstack
, condflags
, result
);
393 if (condstack
.empty() || condstack
.back() & TRCSF_ACTIVE
) {
396 if (GetTraceRestrictValue(item
)) {
397 out
.flags
&= ~TRPRF_DENY
;
399 out
.flags
|= TRPRF_DENY
;
403 case TRIT_PF_PENALTY
:
404 switch (static_cast<TraceRestrictPathfinderPenaltyAuxField
>(GetTraceRestrictAuxField(item
))) {
406 out
.penalty
+= GetTraceRestrictValue(item
);
409 case TRPPAF_PRESET
: {
410 uint16 index
= GetTraceRestrictValue(item
);
411 assert(index
< TRPPPI_END
);
412 out
.penalty
+= _tracerestrict_pathfinder_penalty_preset_values
[index
];
421 case TRIT_RESERVE_THROUGH
:
422 if (GetTraceRestrictValue(item
)) {
423 out
.flags
&= ~TRPRF_RESERVE_THROUGH
;
425 out
.flags
|= TRPRF_RESERVE_THROUGH
;
429 case TRIT_LONG_RESERVE
:
430 if (GetTraceRestrictValue(item
)) {
431 out
.flags
&= ~TRPRF_LONG_RESERVE
;
433 out
.flags
|= TRPRF_LONG_RESERVE
;
437 case TRIT_WAIT_AT_PBS
:
438 if (GetTraceRestrictValue(item
)) {
439 out
.flags
&= ~TRPRF_WAIT_AT_PBS
;
441 out
.flags
|= TRPRF_WAIT_AT_PBS
;
446 if (!input
.permitted_slot_operations
) break;
447 TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item
));
448 if (slot
== NULL
) break;
449 switch (static_cast<TraceRestrictSlotCondOpField
>(GetTraceRestrictCondOp(item
))) {
450 case TRSCOF_ACQUIRE_WAIT
:
451 if (input
.permitted_slot_operations
& TRPISP_ACQUIRE
) {
452 if (!slot
->Occupy(v
->index
)) out
.flags
|= TRPRF_WAIT_AT_PBS
;
456 case TRSCOF_ACQUIRE_TRY
:
457 if (input
.permitted_slot_operations
& TRPISP_ACQUIRE
) slot
->Occupy(v
->index
);
460 case TRSCOF_RELEASE_BACK
:
461 if (input
.permitted_slot_operations
& TRPISP_RELEASE_BACK
) slot
->Vacate(v
->index
);
464 case TRSCOF_RELEASE_FRONT
:
465 if (input
.permitted_slot_operations
& TRPISP_RELEASE_FRONT
) slot
->Vacate(v
->index
);
481 assert(condstack
.empty());
485 * Decrement ref count, only use when removing a mapping
487 void TraceRestrictProgram::DecrementRefCount() {
488 assert(this->refcount
> 0);
490 if (this->refcount
== 0) {
496 * Validate a instruction list
497 * Returns successful result if program seems OK
498 * This only validates that conditional nesting is correct,
499 * and that all instructions have a known type, at present
501 CommandCost
TraceRestrictProgram::Validate(const std::vector
<TraceRestrictItem
> &items
, TraceRestrictProgramActionsUsedFlags
&actions_used_flags
) {
502 // static to avoid needing to re-alloc/resize on each execution
503 static std::vector
<TraceRestrictCondStackFlags
> condstack
;
505 actions_used_flags
= static_cast<TraceRestrictProgramActionsUsedFlags
>(0);
507 size_t size
= items
.size();
508 for (size_t i
= 0; i
< size
; i
++) {
509 TraceRestrictItem item
= items
[i
];
510 TraceRestrictItemType type
= GetTraceRestrictType(item
);
512 // check multi-word instructions
513 if (IsTraceRestrictDoubleItem(item
)) {
516 return_cmd_error(STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE
); // instruction ran off end
520 if (IsTraceRestrictConditional(item
)) {
521 TraceRestrictCondFlags condflags
= GetTraceRestrictCondFlags(item
);
523 if (type
== TRIT_COND_ENDIF
) {
524 if (condstack
.empty()) {
525 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_NO_IF
); // else/endif with no starting if
527 if (condflags
& TRCF_ELSE
) {
529 if (condstack
.back() & TRCSF_SEEN_ELSE
) {
530 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE
); // Two else clauses
532 HandleCondition(condstack
, condflags
, true);
533 condstack
.back() |= TRCSF_SEEN_ELSE
;
536 condstack
.pop_back();
539 if (condflags
& (TRCF_OR
| TRCF_ELSE
)) { // elif/orif
540 if (condstack
.empty()) {
541 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_ELIF_NO_IF
); // Pre-empt assertions in HandleCondition
543 if (condstack
.back() & TRCSF_SEEN_ELSE
) {
544 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE
); // else clause followed by elif/orif
547 HandleCondition(condstack
, condflags
, true);
550 switch (GetTraceRestrictType(item
)) {
551 case TRIT_COND_ENDIF
:
552 case TRIT_COND_UNDEFINED
:
553 case TRIT_COND_TRAIN_LENGTH
:
554 case TRIT_COND_MAX_SPEED
:
555 case TRIT_COND_CURRENT_ORDER
:
556 case TRIT_COND_NEXT_ORDER
:
557 case TRIT_COND_LAST_STATION
:
558 case TRIT_COND_CARGO
:
559 case TRIT_COND_ENTRY_DIRECTION
:
560 case TRIT_COND_PBS_ENTRY_SIGNAL
:
561 case TRIT_COND_TRAIN_GROUP
:
562 case TRIT_COND_TRAIN_IN_SLOT
:
563 case TRIT_COND_SLOT_OCCUPANCY
:
567 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_UNKNOWN_INSTRUCTION
);
570 switch (GetTraceRestrictType(item
)) {
572 case TRIT_PF_PENALTY
:
573 actions_used_flags
|= TRPAUF_PF
;
576 case TRIT_RESERVE_THROUGH
:
577 actions_used_flags
|= TRPAUF_RESERVE_THROUGH
;
580 case TRIT_LONG_RESERVE
:
581 actions_used_flags
|= TRPAUF_LONG_RESERVE
;
584 case TRIT_WAIT_AT_PBS
:
585 actions_used_flags
|= TRPAUF_WAIT_AT_PBS
;
589 switch (static_cast<TraceRestrictSlotCondOpField
>(GetTraceRestrictCondOp(item
))) {
590 case TRSCOF_ACQUIRE_WAIT
:
591 actions_used_flags
|= TRPAUF_SLOT_ACQUIRE
| TRPAUF_WAIT_AT_PBS
;
594 case TRSCOF_ACQUIRE_TRY
:
595 actions_used_flags
|= TRPAUF_SLOT_ACQUIRE
;
598 case TRSCOF_RELEASE_BACK
:
599 actions_used_flags
|= TRPAUF_SLOT_RELEASE_BACK
;
602 case TRSCOF_RELEASE_FRONT
:
603 actions_used_flags
|= TRPAUF_SLOT_RELEASE_FRONT
;
613 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_UNKNOWN_INSTRUCTION
);
617 if (!condstack
.empty()) {
618 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_END_CONDSTACK
);
620 return CommandCost();
624 * Convert an instruction index into an item array index
626 size_t TraceRestrictProgram::InstructionOffsetToArrayOffset(const std::vector
<TraceRestrictItem
> &items
, size_t offset
)
628 size_t output_offset
= 0;
629 size_t size
= items
.size();
630 for (size_t i
= 0; i
< offset
&& output_offset
< size
; i
++, output_offset
++) {
631 if (IsTraceRestrictDoubleItem(items
[output_offset
])) {
635 return output_offset
;
639 * Convert an item array index into an instruction index
641 size_t TraceRestrictProgram::ArrayOffsetToInstructionOffset(const std::vector
<TraceRestrictItem
> &items
, size_t offset
)
643 size_t output_offset
= 0;
644 for (size_t i
= 0; i
< offset
; i
++, output_offset
++) {
645 if (IsTraceRestrictDoubleItem(items
[i
])) {
649 return output_offset
;
653 * Set the value and aux field of @p item, as per the value type in @p value_type
655 void SetTraceRestrictValueDefault(TraceRestrictItem
&item
, TraceRestrictValueType value_type
)
657 switch (value_type
) {
662 case TRVT_TILE_INDEX
:
663 case TRVT_RESERVE_THROUGH
:
664 case TRVT_LONG_RESERVE
:
665 case TRVT_WAIT_AT_PBS
:
666 SetTraceRestrictValue(item
, 0);
667 if (!IsTraceRestrictTypeAuxSubtype(GetTraceRestrictType(item
))) {
668 SetTraceRestrictAuxField(item
, 0);
673 SetTraceRestrictValue(item
, INVALID_STATION
);
674 SetTraceRestrictAuxField(item
, TROCAF_STATION
);
678 assert(_sorted_standard_cargo_specs_size
> 0);
679 SetTraceRestrictValue(item
, _sorted_cargo_specs
[0]->Index());
680 SetTraceRestrictAuxField(item
, 0);
684 SetTraceRestrictValue(item
, TRDTSV_FRONT
);
685 SetTraceRestrictAuxField(item
, 0);
688 case TRVT_PF_PENALTY
:
689 SetTraceRestrictValue(item
, TRPPPI_SMALL
);
690 SetTraceRestrictAuxField(item
, TRPPAF_PRESET
);
693 case TRVT_GROUP_INDEX
:
694 SetTraceRestrictValue(item
, INVALID_GROUP
);
695 SetTraceRestrictAuxField(item
, 0);
698 case TRVT_SLOT_INDEX
:
699 SetTraceRestrictValue(item
, INVALID_TRACE_RESTRICT_SLOT_ID
);
700 SetTraceRestrictAuxField(item
, 0);
703 case TRVT_SLOT_INDEX_INT
:
704 SetTraceRestrictValue(item
, INVALID_TRACE_RESTRICT_SLOT_ID
);
714 * Set the type field of a TraceRestrictItem, and resets any other fields which are no longer valid/meaningful to sensible defaults
716 void SetTraceRestrictTypeAndNormalise(TraceRestrictItem
&item
, TraceRestrictItemType type
, uint8 aux_data
)
719 assert(GetTraceRestrictType(item
) != TRIT_NULL
);
720 assert(IsTraceRestrictConditional(item
) == IsTraceRestrictTypeConditional(type
));
722 assert(type
!= TRIT_NULL
);
724 TraceRestrictTypePropertySet old_properties
= GetTraceRestrictTypeProperties(item
);
725 SetTraceRestrictType(item
, type
);
726 bool set_aux_field
= false;
727 if (IsTraceRestrictTypeAuxSubtype(type
)) {
728 SetTraceRestrictAuxField(item
, aux_data
);
729 set_aux_field
= true;
732 assert(aux_data
== 0);
734 TraceRestrictTypePropertySet new_properties
= GetTraceRestrictTypeProperties(item
);
736 if (old_properties
.cond_type
!= new_properties
.cond_type
||
737 old_properties
.value_type
!= new_properties
.value_type
) {
738 SetTraceRestrictCondOp(item
, TRCO_IS
);
739 SetTraceRestrictValueDefault(item
, new_properties
.value_type
);
741 SetTraceRestrictAuxField(item
, aux_data
);
744 if (GetTraceRestrictType(item
) == TRIT_COND_LAST_STATION
&& GetTraceRestrictAuxField(item
) != TROCAF_STATION
) {
745 // if changing type from another order type to last visited station, reset value if not currently a station
746 SetTraceRestrictValueDefault(item
, TRVT_ORDER
);
751 * Sets the "signal has a trace restrict mapping" bit
752 * This looks for mappings with that tile index
754 void SetIsSignalRestrictedBit(TileIndex t
)
756 // First mapping for this tile, or later
757 TraceRestrictMapping::iterator lower_bound
= _tracerestrictprogram_mapping
.lower_bound(MakeTraceRestrictRefId(t
, static_cast<Track
>(0)));
759 // First mapping for next tile, or later
760 TraceRestrictMapping::iterator upper_bound
= _tracerestrictprogram_mapping
.lower_bound(MakeTraceRestrictRefId(t
+ 1, static_cast<Track
>(0)));
762 // If iterators are the same, there are no mappings for this tile
763 SetRestrictedSignal(t
, lower_bound
!= upper_bound
);
767 * Create a new program mapping to an existing program
768 * If a mapping already exists, it is removed
770 void TraceRestrictCreateProgramMapping(TraceRestrictRefId ref
, TraceRestrictProgram
*prog
)
772 std::pair
<TraceRestrictMapping::iterator
, bool> insert_result
=
773 _tracerestrictprogram_mapping
.insert(std::make_pair(ref
, TraceRestrictMappingItem(prog
->index
)));
775 if (!insert_result
.second
) {
776 // value was not inserted, there is an existing mapping
777 // unref the existing mapping before updating it
778 _tracerestrictprogram_pool
.Get(insert_result
.first
->second
.program_id
)->DecrementRefCount();
779 insert_result
.first
->second
= prog
->index
;
781 prog
->IncrementRefCount();
783 TileIndex tile
= GetTraceRestrictRefIdTileIndex(ref
);
784 Track track
= GetTraceRestrictRefIdTrack(ref
);
785 SetIsSignalRestrictedBit(tile
);
786 MarkTileDirtyByTile(tile
);
787 YapfNotifyTrackLayoutChange(tile
, track
);
791 * Remove a program mapping
792 * @return true if a mapping was actually removed
794 bool TraceRestrictRemoveProgramMapping(TraceRestrictRefId ref
)
796 TraceRestrictMapping::iterator iter
= _tracerestrictprogram_mapping
.find(ref
);
797 if (iter
!= _tracerestrictprogram_mapping
.end()) {
799 TraceRestrictProgram
*prog
= _tracerestrictprogram_pool
.Get(iter
->second
.program_id
);
801 // check to see if another mapping needs to be removed as well
802 // do this before decrementing the refcount
803 bool remove_other_mapping
= prog
->refcount
== 2 && prog
->items
.empty();
805 prog
->DecrementRefCount();
806 _tracerestrictprogram_mapping
.erase(iter
);
808 TileIndex tile
= GetTraceRestrictRefIdTileIndex(ref
);
809 Track track
= GetTraceRestrictRefIdTrack(ref
);
810 SetIsSignalRestrictedBit(tile
);
811 MarkTileDirtyByTile(tile
);
812 YapfNotifyTrackLayoutChange(tile
, track
);
814 if (remove_other_mapping
) {
815 TraceRestrictProgramID id
= prog
->index
;
816 for (TraceRestrictMapping::iterator rm_iter
= _tracerestrictprogram_mapping
.begin();
817 rm_iter
!= _tracerestrictprogram_mapping
.end(); ++rm_iter
) {
818 if (rm_iter
->second
.program_id
== id
) {
819 TraceRestrictRemoveProgramMapping(rm_iter
->first
);
832 * Gets the signal program for the tile ref @p ref
833 * An empty program will be constructed if none exists, and @p create_new is true, unless the pool is full
835 TraceRestrictProgram
*GetTraceRestrictProgram(TraceRestrictRefId ref
, bool create_new
)
837 // Optimise for lookup, creating doesn't have to be that fast
839 TraceRestrictMapping::iterator iter
= _tracerestrictprogram_mapping
.find(ref
);
840 if (iter
!= _tracerestrictprogram_mapping
.end()) {
842 return _tracerestrictprogram_pool
.Get(iter
->second
.program_id
);
843 } else if (create_new
) {
846 // Create new pool item
847 if (!TraceRestrictProgram::CanAllocateItem()) {
850 TraceRestrictProgram
*prog
= new TraceRestrictProgram();
852 // Create new mapping to pool item
853 TraceRestrictCreateProgramMapping(ref
, prog
);
861 * Notify that a signal is being removed
862 * Remove any trace restrict mappings associated with it
864 void TraceRestrictNotifySignalRemoval(TileIndex tile
, Track track
)
866 TraceRestrictRefId ref
= MakeTraceRestrictRefId(tile
, track
);
867 bool removed
= TraceRestrictRemoveProgramMapping(ref
);
868 DeleteWindowById(WC_TRACE_RESTRICT
, ref
);
869 if (removed
) InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
873 * Helper function to perform parameter bit-packing and call DoCommandP, for instruction modification actions
875 void TraceRestrictDoCommandP(TileIndex tile
, Track track
, TraceRestrictDoCommandType type
, uint32 offset
, uint32 value
, StringID error_msg
)
880 assert(offset
< (1 << 16));
881 SB(p1
, 8, 16, offset
);
882 DoCommandP(tile
, p1
, value
, CMD_PROGRAM_TRACERESTRICT_SIGNAL
| CMD_MSG(error_msg
));
886 * Check whether a tile/tracl pair contains a usable signal
888 static CommandCost
TraceRestrictCheckTileIsUsable(TileIndex tile
, Track track
)
890 // Check that there actually is a signal here
891 if (!IsPlainRailTile(tile
) || !HasTrack(tile
, track
)) {
892 return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK
);
894 if (!HasSignalOnTrack(tile
, track
)) {
895 return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS
);
898 // Check tile ownership, do this afterwards to avoid tripping up on house/industry tiles
899 CommandCost ret
= CheckTileOwnership(tile
);
904 return CommandCost();
908 * Returns an appropriate default value for the second item of a dual-item instruction
909 * @p item is the first item of the instruction
911 static uint32
GetDualInstructionInitialValue(TraceRestrictItem item
)
913 switch (GetTraceRestrictType(item
)) {
914 case TRIT_COND_PBS_ENTRY_SIGNAL
:
917 case TRIT_COND_SLOT_OCCUPANCY
:
925 template <typename T
> T
InstructionIteratorNext(T iter
)
927 return IsTraceRestrictDoubleItem(*iter
) ? iter
+ 2 : iter
+ 1;
930 template <typename T
> void InstructionIteratorAdvance(T
&iter
)
932 iter
= InstructionIteratorNext(iter
);
935 CommandCost
TraceRestrictProgramRemoveItemAt(std::vector
<TraceRestrictItem
> &items
, uint32 offset
, bool shallow_mode
)
937 TraceRestrictItem old_item
= *TraceRestrictProgram::InstructionAt(items
, offset
);
938 if (IsTraceRestrictConditional(old_item
) && GetTraceRestrictCondFlags(old_item
) != TRCF_OR
) {
939 bool remove_whole_block
= false;
940 if (GetTraceRestrictCondFlags(old_item
) == 0) {
941 if (GetTraceRestrictType(old_item
) == TRIT_COND_ENDIF
) {
942 // this is an end if, can't remove these
943 return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_REMOVE_ENDIF
);
945 // this is an opening if
946 remove_whole_block
= true;
950 uint32 recursion_depth
= 1;
951 std::vector
<TraceRestrictItem
>::iterator remove_start
= TraceRestrictProgram::InstructionAt(items
, offset
);
952 std::vector
<TraceRestrictItem
>::iterator remove_end
= InstructionIteratorNext(remove_start
);
954 // iterate until matching end block found
955 for (; remove_end
!= items
.end(); InstructionIteratorAdvance(remove_end
)) {
956 TraceRestrictItem current_item
= *remove_end
;
957 if (IsTraceRestrictConditional(current_item
)) {
958 if (GetTraceRestrictCondFlags(current_item
) == 0) {
959 if (GetTraceRestrictType(current_item
) == TRIT_COND_ENDIF
) {
962 if (recursion_depth
== 0) {
963 if (remove_whole_block
) {
965 // must erase endif first, as it is later in the vector
966 items
.erase(remove_end
, InstructionIteratorNext(remove_end
));
968 // inclusively remove up to here
969 InstructionIteratorAdvance(remove_end
);
973 // exclusively remove up to here
978 // this is an opening if
982 // this is an else/or type block
983 if (recursion_depth
== 1 && !remove_whole_block
) {
984 // exclusively remove up to here
988 if (recursion_depth
== 1 && remove_whole_block
&& shallow_mode
) {
989 // shallow-removing whole if block, and it contains an else/or if, bail out
990 return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_SHALLOW_REMOVE_IF_ELIF
);
995 if (recursion_depth
!= 0) return CMD_ERROR
; // ran off the end
997 items
.erase(remove_start
, InstructionIteratorNext(remove_start
));
999 items
.erase(remove_start
, remove_end
);
1002 std::vector
<TraceRestrictItem
>::iterator remove_start
= TraceRestrictProgram::InstructionAt(items
, offset
);
1003 std::vector
<TraceRestrictItem
>::iterator remove_end
= InstructionIteratorNext(remove_start
);
1005 items
.erase(remove_start
, remove_end
);
1007 return CommandCost();
1010 CommandCost
TraceRestrictProgramMoveItemAt(std::vector
<TraceRestrictItem
> &items
, uint32
&offset
, bool up
, bool shallow_mode
)
1012 std::vector
<TraceRestrictItem
>::iterator move_start
= TraceRestrictProgram::InstructionAt(items
, offset
);
1013 std::vector
<TraceRestrictItem
>::iterator move_end
= InstructionIteratorNext(move_start
);
1015 TraceRestrictItem old_item
= *move_start
;
1016 if (!shallow_mode
) {
1017 if (IsTraceRestrictConditional(old_item
)) {
1018 if (GetTraceRestrictCondFlags(old_item
) != 0) {
1019 // can't move or/else blocks
1020 return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_MOVE_ITEM
);
1022 if (GetTraceRestrictType(old_item
) == TRIT_COND_ENDIF
) {
1023 // this is an end if, can't move these
1024 return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_MOVE_ITEM
);
1027 uint32 recursion_depth
= 1;
1028 // iterate until matching end block found
1029 for (; move_end
!= items
.end(); InstructionIteratorAdvance(move_end
)) {
1030 TraceRestrictItem current_item
= *move_end
;
1031 if (IsTraceRestrictConditional(current_item
)) {
1032 if (GetTraceRestrictCondFlags(current_item
) == 0) {
1033 if (GetTraceRestrictType(current_item
) == TRIT_COND_ENDIF
) {
1034 // this is an end if
1036 if (recursion_depth
== 0) {
1037 // inclusively remove up to here
1038 InstructionIteratorAdvance(move_end
);
1042 // this is an opening if
1048 if (recursion_depth
!= 0) return CMD_ERROR
; // ran off the end
1053 if (move_start
== items
.begin()) return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_MOVE_ITEM
);
1054 std::rotate(TraceRestrictProgram::InstructionAt(items
, offset
- 1), move_start
, move_end
);
1057 if (move_end
== items
.end()) return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_MOVE_ITEM
);
1058 std::rotate(move_start
, move_end
, InstructionIteratorNext(move_end
));
1061 return CommandCost();
1065 * The main command for editing a signal tracerestrict program.
1066 * @param tile The tile which contains the signal.
1067 * @param flags Internal command handler stuff.
1068 * Below apply for instruction modification actions only
1069 * @param p1 Bitstuffed items
1070 * @param p2 Item, for insert and modify operations. Flags for instruction move operations
1071 * @return the cost of this operation (which is free), or an error
1073 CommandCost
CmdProgramSignalTraceRestrict(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1075 TraceRestrictDoCommandType type
= static_cast<TraceRestrictDoCommandType
>(GB(p1
, 3, 5));
1077 if (type
>= TRDCT_PROG_COPY
) {
1078 return CmdProgramSignalTraceRestrictProgMgmt(tile
, flags
, p1
, p2
, text
);
1081 Track track
= static_cast<Track
>(GB(p1
, 0, 3));
1082 uint32 offset
= GB(p1
, 8, 16);
1083 TraceRestrictItem item
= static_cast<TraceRestrictItem
>(p2
);
1085 CommandCost ret
= TraceRestrictCheckTileIsUsable(tile
, track
);
1090 bool can_make_new
= (type
== TRDCT_INSERT_ITEM
) && (flags
& DC_EXEC
);
1091 bool need_existing
= (type
!= TRDCT_INSERT_ITEM
);
1092 TraceRestrictProgram
*prog
= GetTraceRestrictProgram(MakeTraceRestrictRefId(tile
, track
), can_make_new
);
1093 if (need_existing
&& !prog
) {
1094 return_cmd_error(STR_TRACE_RESTRICT_ERROR_NO_PROGRAM
);
1097 uint32 offset_limit_exclusive
= ((type
== TRDCT_INSERT_ITEM
) ? 1 : 0);
1098 if (prog
) offset_limit_exclusive
+= (uint32
)prog
->items
.size();
1100 if (offset
>= offset_limit_exclusive
) {
1101 return_cmd_error(STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE
);
1105 std::vector
<TraceRestrictItem
> items
;
1106 if (prog
) items
= prog
->items
;
1109 case TRDCT_INSERT_ITEM
:
1110 items
.insert(TraceRestrictProgram::InstructionAt(items
, offset
), item
);
1111 if (IsTraceRestrictConditional(item
) &&
1112 GetTraceRestrictCondFlags(item
) == 0 &&
1113 GetTraceRestrictType(item
) != TRIT_COND_ENDIF
) {
1114 // this is an opening if block, insert a corresponding end if
1115 TraceRestrictItem endif_item
= 0;
1116 SetTraceRestrictType(endif_item
, TRIT_COND_ENDIF
);
1117 items
.insert(TraceRestrictProgram::InstructionAt(items
, offset
) + 1, endif_item
);
1118 } else if (IsTraceRestrictDoubleItem(item
)) {
1119 items
.insert(TraceRestrictProgram::InstructionAt(items
, offset
) + 1, GetDualInstructionInitialValue(item
));
1123 case TRDCT_MODIFY_ITEM
: {
1124 std::vector
<TraceRestrictItem
>::iterator old_item
= TraceRestrictProgram::InstructionAt(items
, offset
);
1125 if (IsTraceRestrictConditional(*old_item
) != IsTraceRestrictConditional(item
)) {
1126 return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_CHANGE_CONDITIONALITY
);
1128 bool old_is_dual
= IsTraceRestrictDoubleItem(*old_item
);
1129 bool new_is_dual
= IsTraceRestrictDoubleItem(item
);
1131 if (old_is_dual
&& !new_is_dual
) {
1132 items
.erase(old_item
+ 1);
1133 } else if (!old_is_dual
&& new_is_dual
) {
1134 items
.insert(old_item
+ 1, GetDualInstructionInitialValue(item
));
1139 case TRDCT_MODIFY_DUAL_ITEM
: {
1140 std::vector
<TraceRestrictItem
>::iterator old_item
= TraceRestrictProgram::InstructionAt(items
, offset
);
1141 if (!IsTraceRestrictDoubleItem(*old_item
)) {
1144 *(old_item
+ 1) = p2
;
1148 case TRDCT_REMOVE_ITEM
:
1149 case TRDCT_SHALLOW_REMOVE_ITEM
: {
1150 CommandCost res
= TraceRestrictProgramRemoveItemAt(items
, offset
, type
== TRDCT_SHALLOW_REMOVE_ITEM
);
1151 if (res
.Failed()) return res
;
1155 case TRDCT_MOVE_ITEM
: {
1156 CommandCost res
= TraceRestrictProgramMoveItemAt(items
, offset
, p2
& 1, p2
& 2);
1157 if (res
.Failed()) return res
;
1166 TraceRestrictProgramActionsUsedFlags actions_used_flags
;
1167 CommandCost validation_result
= TraceRestrictProgram::Validate(items
, actions_used_flags
);
1168 if (validation_result
.Failed()) {
1169 return validation_result
;
1172 if (flags
& DC_EXEC
) {
1175 // move in modified program
1176 prog
->items
.swap(items
);
1177 prog
->actions_used_flags
= actions_used_flags
;
1179 if (prog
->items
.size() == 0 && prog
->refcount
== 1) {
1180 // program is empty, and this tile is the only reference to it
1181 // so delete it, as it's redundant
1182 TraceRestrictRemoveProgramMapping(MakeTraceRestrictRefId(tile
, track
));
1186 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1189 return CommandCost();
1193 * Helper function to perform parameter bit-packing and call DoCommandP, for program management actions
1195 void TraceRestrictProgMgmtWithSourceDoCommandP(TileIndex tile
, Track track
, TraceRestrictDoCommandType type
,
1196 TileIndex source_tile
, Track source_track
, StringID error_msg
)
1199 SB(p1
, 0, 3, track
);
1201 SB(p1
, 8, 3, source_track
);
1202 DoCommandP(tile
, p1
, source_tile
, CMD_PROGRAM_TRACERESTRICT_SIGNAL
| CMD_MSG(error_msg
));
1206 * Sub command for copy/share/unshare operations on signal tracerestrict programs.
1207 * @param tile The tile which contains the signal.
1208 * @param flags Internal command handler stuff.
1209 * @param p1 Bitstuffed items
1210 * @param p2 Source tile, for share/copy operations
1211 * @return the cost of this operation (which is free), or an error
1213 CommandCost
CmdProgramSignalTraceRestrictProgMgmt(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1215 TraceRestrictDoCommandType type
= static_cast<TraceRestrictDoCommandType
>(GB(p1
, 3, 5));
1216 Track track
= static_cast<Track
>(GB(p1
, 0, 3));
1217 Track source_track
= static_cast<Track
>(GB(p1
, 8, 3));
1218 TileIndex source_tile
= static_cast<TileIndex
>(p2
);
1220 TraceRestrictRefId self
= MakeTraceRestrictRefId(tile
, track
);
1221 TraceRestrictRefId source
= MakeTraceRestrictRefId(source_tile
, source_track
);
1223 assert(type
>= TRDCT_PROG_COPY
);
1225 CommandCost ret
= TraceRestrictCheckTileIsUsable(tile
, track
);
1230 if (type
== TRDCT_PROG_SHARE
|| type
== TRDCT_PROG_COPY
) {
1231 if (self
== source
) {
1232 return_cmd_error(STR_TRACE_RESTRICT_ERROR_SOURCE_SAME_AS_TARGET
);
1235 if (type
== TRDCT_PROG_SHARE
|| type
== TRDCT_PROG_COPY
|| type
== TRDCT_PROG_COPY_APPEND
) {
1236 ret
= TraceRestrictCheckTileIsUsable(source_tile
, source_track
);
1242 if (type
!= TRDCT_PROG_RESET
&& !TraceRestrictProgram::CanAllocateItem()) {
1246 if (!(flags
& DC_EXEC
)) {
1247 return CommandCost();
1251 case TRDCT_PROG_COPY
: {
1252 TraceRestrictRemoveProgramMapping(self
);
1253 TraceRestrictProgram
*source_prog
= GetTraceRestrictProgram(source
, false);
1254 if (source_prog
&& !source_prog
->items
.empty()) {
1255 TraceRestrictProgram
*prog
= GetTraceRestrictProgram(self
, true);
1257 // allocation failed
1260 prog
->items
= source_prog
->items
; // copy
1266 case TRDCT_PROG_COPY_APPEND
: {
1267 TraceRestrictProgram
*source_prog
= GetTraceRestrictProgram(source
, false);
1268 if (source_prog
&& !source_prog
->items
.empty()) {
1269 TraceRestrictProgram
*prog
= GetTraceRestrictProgram(self
, true);
1271 // allocation failed
1274 prog
->items
.reserve(prog
->items
.size() + source_prog
->items
.size()); // this is in case prog == source_prog
1275 prog
->items
.insert(prog
->items
.end(), source_prog
->items
.begin(), source_prog
->items
.end()); // append
1281 case TRDCT_PROG_SHARE
: {
1282 TraceRestrictRemoveProgramMapping(self
);
1283 TraceRestrictProgram
*source_prog
= GetTraceRestrictProgram(source
, true);
1285 // allocation failed
1289 TraceRestrictCreateProgramMapping(self
, source_prog
);
1293 case TRDCT_PROG_UNSHARE
: {
1294 std::vector
<TraceRestrictItem
> items
;
1295 TraceRestrictProgram
*prog
= GetTraceRestrictProgram(self
, false);
1297 // copy program into temporary
1298 items
= prog
->items
;
1300 // remove old program
1301 TraceRestrictRemoveProgramMapping(self
);
1304 // if prog is non-empty, create new program and move temporary in
1305 TraceRestrictProgram
*new_prog
= GetTraceRestrictProgram(self
, true);
1307 // allocation failed
1311 new_prog
->items
.swap(items
);
1312 new_prog
->Validate();
1317 case TRDCT_PROG_RESET
: {
1318 TraceRestrictRemoveProgramMapping(self
);
1328 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1330 return CommandCost();
1334 * This is called when a station, waypoint or depot is about to be deleted
1335 * Scan program pool and change any references to it to the invalid station ID, to avoid dangling references
1337 void TraceRestrictRemoveDestinationID(TraceRestrictOrderCondAuxField type
, uint16 index
)
1339 TraceRestrictProgram
*prog
;
1341 FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog
) {
1342 for (size_t i
= 0; i
< prog
->items
.size(); i
++) {
1343 TraceRestrictItem
&item
= prog
->items
[i
]; // note this is a reference,
1344 if (GetTraceRestrictType(item
) == TRIT_COND_CURRENT_ORDER
||
1345 GetTraceRestrictType(item
) == TRIT_COND_NEXT_ORDER
||
1346 GetTraceRestrictType(item
) == TRIT_COND_LAST_STATION
) {
1347 if (GetTraceRestrictAuxField(item
) == type
&& GetTraceRestrictValue(item
) == index
) {
1348 SetTraceRestrictValueDefault(item
, TRVT_ORDER
); // this updates the instruction in-place
1351 if (IsTraceRestrictDoubleItem(item
)) i
++;
1356 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1360 * This is called when a group is about to be deleted
1361 * Scan program pool and change any references to it to the invalid group ID, to avoid dangling references
1363 void TraceRestrictRemoveGroupID(GroupID index
)
1365 TraceRestrictProgram
*prog
;
1367 FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog
) {
1368 for (size_t i
= 0; i
< prog
->items
.size(); i
++) {
1369 TraceRestrictItem
&item
= prog
->items
[i
]; // note this is a reference,
1370 if (GetTraceRestrictType(item
) == TRIT_COND_TRAIN_GROUP
&& GetTraceRestrictValue(item
) == index
) {
1371 SetTraceRestrictValueDefault(item
, TRVT_GROUP_INDEX
); // this updates the instruction in-place
1373 if (IsTraceRestrictDoubleItem(item
)) i
++;
1378 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1381 static std::unordered_multimap
<VehicleID
, TraceRestrictSlotID
> slot_vehicle_index
;
1384 * Add vehicle ID to occupants if possible and not already an occupant
1385 * @param id Vehicle ID
1386 * @param force Add the vehicle even if the slot is at/over capacity
1387 * @return whether vehicle ID is now an occupant
1389 bool TraceRestrictSlot::Occupy(VehicleID id
, bool force
)
1391 if (this->IsOccupant(id
)) return true;
1392 if (this->occupants
.size() >= this->max_occupancy
&& !force
) return false;
1393 this->occupants
.push_back(id
);
1394 slot_vehicle_index
.emplace(id
, this->index
);
1395 SetBit(Train::Get(id
)->flags
, VRF_HAVE_SLOT
);
1396 SetWindowDirty(WC_VEHICLE_DETAILS
, id
);
1397 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1402 * Remove vehicle ID from occupants
1403 * @param id Vehicle ID
1405 void TraceRestrictSlot::Vacate(VehicleID id
)
1407 if (container_unordered_remove(this->occupants
, id
)) {
1412 /** Remove all occupants */
1413 void TraceRestrictSlot::Clear()
1415 for (VehicleID id
: this->occupants
) {
1418 this->occupants
.clear();
1421 void TraceRestrictSlot::DeIndex(VehicleID id
)
1423 auto range
= slot_vehicle_index
.equal_range(id
);
1424 for (auto it
= range
.first
; it
!= range
.second
; ++it
) {
1425 if (it
->second
== this->index
) {
1426 auto next
= slot_vehicle_index
.erase(it
);
1427 if (it
== range
.first
&& next
== range
.second
) {
1428 /* Only one item, which we've just erased, clear the vehicle flag */
1429 ClrBit(Train::Get(id
)->flags
, VRF_HAVE_SLOT
);
1434 SetWindowDirty(WC_VEHICLE_DETAILS
, id
);
1435 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1438 /** Rebuild slot vehicle index after loading */
1439 void TraceRestrictSlot::RebuildVehicleIndex()
1441 slot_vehicle_index
.clear();
1442 const TraceRestrictSlot
*slot
;
1443 FOR_ALL_TRACE_RESTRICT_SLOTS(slot
) {
1444 for (VehicleID id
: slot
->occupants
) {
1445 slot_vehicle_index
.emplace(id
, slot
->index
);
1450 /** Slot pool is about to be cleared */
1451 void TraceRestrictSlot::PreCleanPool()
1453 slot_vehicle_index
.clear();
1456 /** Remove vehicle ID from all slot occupants */
1457 void TraceRestrictRemoveVehicleFromAllSlots(VehicleID id
)
1459 auto range
= slot_vehicle_index
.equal_range(id
);
1460 for (auto it
= range
.first
; it
!= range
.second
; ++it
) {
1461 TraceRestrictSlot
*slot
= TraceRestrictSlot::Get(it
->second
);
1462 container_unordered_remove(slot
->occupants
, id
);
1464 slot_vehicle_index
.erase(range
.first
, range
.second
);
1465 if (range
.first
!= range
.second
) InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1468 /** Replace all instance of a vehicle ID with another, in all slot occupants */
1469 void TraceRestrictTransferVehicleOccupantInAllSlots(VehicleID from
, VehicleID to
)
1471 auto range
= slot_vehicle_index
.equal_range(from
);
1472 std::vector
<TraceRestrictSlotID
> slots
;
1473 for (auto it
= range
.first
; it
!= range
.second
; ++it
) {
1474 slots
.push_back(it
->second
);
1476 slot_vehicle_index
.erase(range
.first
, range
.second
);
1477 for (TraceRestrictSlotID slot_id
: slots
) {
1478 TraceRestrictSlot
*slot
= TraceRestrictSlot::Get(slot_id
);
1479 for (VehicleID
&id
: slot
->occupants
) {
1482 slot_vehicle_index
.emplace(to
, slot_id
);
1486 if (!slots
.empty()) InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1489 /** Get list of slots occupied by a vehicle ID */
1490 void TraceRestrictGetVehicleSlots(VehicleID id
, std::vector
<TraceRestrictSlotID
> &out
)
1492 auto range
= slot_vehicle_index
.equal_range(id
);
1493 for (auto it
= range
.first
; it
!= range
.second
; ++it
) {
1494 out
.push_back(it
->second
);
1499 * This is called when a slot is about to be deleted
1500 * Scan program pool and change any references to it to the invalid group ID, to avoid dangling references
1502 void TraceRestrictRemoveSlotID(TraceRestrictSlotID index
)
1504 TraceRestrictProgram
*prog
;
1506 FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog
) {
1507 for (size_t i
= 0; i
< prog
->items
.size(); i
++) {
1508 TraceRestrictItem
&item
= prog
->items
[i
]; // note this is a reference,
1509 if ((GetTraceRestrictType(item
) == TRIT_SLOT
|| GetTraceRestrictType(item
) == TRIT_COND_TRAIN_IN_SLOT
) && GetTraceRestrictValue(item
) == index
) {
1510 SetTraceRestrictValueDefault(item
, TRVT_SLOT_INDEX
); // this updates the instruction in-place
1512 if ((GetTraceRestrictType(item
) == TRIT_COND_SLOT_OCCUPANCY
) && GetTraceRestrictValue(item
) == index
) {
1513 SetTraceRestrictValueDefault(item
, TRVT_SLOT_INDEX_INT
); // this updates the instruction in-place
1515 if (IsTraceRestrictDoubleItem(item
)) i
++;
1520 static bool IsUniqueSlotName(const char *name
)
1522 const TraceRestrictSlot
*slot
;
1523 FOR_ALL_TRACE_RESTRICT_SLOTS(slot
) {
1524 if (slot
->name
== name
) return false;
1530 * Create a new slot.
1531 * @param tile unused
1532 * @param flags type of operation
1535 * @param text new slot name
1536 * @return the cost of this operation or an error
1538 CommandCost
CmdCreateTraceRestrictSlot(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1540 if (!TraceRestrictSlot::CanAllocateItem()) return CMD_ERROR
;
1541 if (StrEmpty(text
)) return CMD_ERROR
;
1543 size_t length
= Utf8StringLength(text
);
1544 if (length
<= 0) return CMD_ERROR
;
1545 if (length
>= MAX_LENGTH_TRACE_RESTRICT_SLOT_NAME_CHARS
) return CMD_ERROR
;
1546 if (!IsUniqueSlotName(text
)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE
);
1548 if (flags
& DC_EXEC
) {
1549 TraceRestrictSlot
*slot
= new TraceRestrictSlot(_current_company
);
1553 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1554 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1557 return CommandCost();
1563 * @param tile unused
1564 * @param flags type of operation
1565 * @param p1 index of array group
1566 * - p1 bit 0-15 : Slot ID
1568 * @param text unused
1569 * @return the cost of this operation or an error
1571 CommandCost
CmdDeleteTraceRestrictSlot(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1573 TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(p1
);
1574 if (slot
== NULL
|| slot
->owner
!= _current_company
) return CMD_ERROR
;
1576 if (flags
& DC_EXEC
) {
1577 /* notify tracerestrict that group is about to be deleted */
1578 TraceRestrictRemoveSlotID(slot
->index
);
1582 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1583 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1586 return CommandCost();
1591 * @param tile unused
1592 * @param flags type of operation
1593 * @param p1 index of array group
1594 * - p1 bit 0-15 : GroupID
1595 * - p1 bit 16: 0 - Rename grouop
1596 * 1 - Change max occupancy
1597 * @param p2 new max occupancy
1598 * @param text the new name
1599 * @return the cost of this operation or an error
1601 CommandCost
CmdAlterTraceRestrictSlot(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1603 TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(GB(p1
, 0, 16));
1604 if (slot
== NULL
|| slot
->owner
!= _current_company
) return CMD_ERROR
;
1606 if (!HasBit(p1
, 16)) {
1609 if (StrEmpty(text
)) return CMD_ERROR
;
1610 size_t length
= Utf8StringLength(text
);
1611 if (length
<= 0) return CMD_ERROR
;
1612 if (length
>= MAX_LENGTH_TRACE_RESTRICT_SLOT_NAME_CHARS
) return CMD_ERROR
;
1613 if (!IsUniqueSlotName(text
)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE
);
1615 if (flags
& DC_EXEC
) {
1619 /* Change max occupancy */
1621 if (flags
& DC_EXEC
) {
1622 slot
->max_occupancy
= p2
;
1626 if (flags
& DC_EXEC
) {
1628 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1629 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1632 return CommandCost();
1636 * Add a vehicle to a slot
1637 * @param tile unused
1638 * @param flags type of operation
1639 * @param p1 index of array group
1640 * - p1 bit 0-15 : GroupID
1641 * @param p2 index of vehicle
1642 * - p2 bit 0-19 : VehicleID
1643 * @param text unused
1644 * @return the cost of this operation or an error
1646 CommandCost
CmdAddVehicleTraceRestrictSlot(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1648 TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(p1
);
1649 Vehicle
*v
= Vehicle::GetIfValid(p2
);
1650 if (slot
== NULL
|| slot
->owner
!= _current_company
) return CMD_ERROR
;
1651 if (v
== NULL
|| v
->owner
!= _current_company
) return CMD_ERROR
;
1653 if (flags
& DC_EXEC
) {
1654 slot
->Occupy(v
->index
, true);
1657 return CommandCost();
1661 * Remove a vehicle from a slot
1662 * @param tile unused
1663 * @param flags type of operation
1664 * @param p1 index of array group
1665 * - p1 bit 0-15 : GroupID
1666 * @param p2 index of vehicle
1667 * - p2 bit 0-19 : VehicleID
1668 * @param text unused
1669 * @return the cost of this operation or an error
1671 CommandCost
CmdRemoveVehicleTraceRestrictSlot(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1673 TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(p1
);
1674 Vehicle
*v
= Vehicle::GetIfValid(p2
);
1675 if (slot
== NULL
|| slot
->owner
!= _current_company
) return CMD_ERROR
;
1676 if (v
== NULL
) return CMD_ERROR
; // permit removing vehicles of other owners from your own slot
1678 if (flags
& DC_EXEC
) {
1679 slot
->Vacate(v
->index
);
1682 return CommandCost();