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
->GetNumOrders() == 0) break;
286 const Order
*current_order
= v
->GetOrder(v
->cur_real_order_index
);
287 for (const Order
*order
= v
->GetNextOrder(current_order
); order
!= current_order
; order
= v
->GetNextOrder(order
)) {
288 if (order
->IsGotoOrder()) {
289 result
= TestOrderCondition(order
, item
);
296 case TRIT_COND_LAST_STATION
:
297 result
= TestStationCondition(v
->last_station_visited
, item
);
300 case TRIT_COND_CARGO
: {
301 bool have_cargo
= false;
302 for (const Vehicle
*v_iter
= v
; v_iter
!= NULL
; v_iter
= v_iter
->Next()) {
303 if (v_iter
->cargo_type
== GetTraceRestrictValue(item
) && v_iter
->cargo_cap
> 0) {
308 result
= TestBinaryConditionCommon(item
, have_cargo
);
312 case TRIT_COND_ENTRY_DIRECTION
: {
313 bool direction_match
;
314 switch (GetTraceRestrictValue(item
)) {
319 direction_match
= (static_cast<DiagDirection
>(GetTraceRestrictValue(item
)) == TrackdirToExitdir(ReverseTrackdir(input
.trackdir
)));
323 direction_match
= IsTileType(input
.tile
, MP_RAILWAY
) && HasSignalOnTrackdir(input
.tile
, input
.trackdir
);
327 direction_match
= IsTileType(input
.tile
, MP_RAILWAY
) && !HasSignalOnTrackdir(input
.tile
, input
.trackdir
);
334 result
= TestBinaryConditionCommon(item
, direction_match
);
338 case TRIT_COND_PBS_ENTRY_SIGNAL
: {
339 // TRVT_TILE_INDEX value type uses the next slot
341 uint32_t signal_tile
= this->items
[i
];
342 if (!have_previous_signal
) {
343 if (input
.previous_signal_callback
) {
344 previous_signal_tile
= input
.previous_signal_callback(v
, input
.previous_signal_ptr
);
346 have_previous_signal
= true;
348 bool match
= (signal_tile
!= INVALID_TILE
)
349 && (previous_signal_tile
== signal_tile
);
350 result
= TestBinaryConditionCommon(item
, match
);
354 case TRIT_COND_TRAIN_GROUP
: {
355 result
= TestBinaryConditionCommon(item
, GroupIsInGroup(v
->group_id
, GetTraceRestrictValue(item
)));
359 case TRIT_COND_TRAIN_IN_SLOT
: {
360 const TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item
));
361 result
= TestBinaryConditionCommon(item
, slot
!= NULL
&& slot
->IsOccupant(v
->index
));
365 case TRIT_COND_SLOT_OCCUPANCY
: {
366 // TRIT_COND_SLOT_OCCUPANCY value type uses the next slot
368 uint32_t value
= this->items
[i
];
369 const TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item
));
370 switch (static_cast<TraceRestrictSlotOccupancyCondAuxField
>(GetTraceRestrictAuxField(item
))) {
371 case TRSOCAF_OCCUPANTS
:
372 result
= TestCondition(slot
!= NULL
? slot
->occupants
.size() : 0, condop
, value
);
375 case TRSOCAF_REMAINING
:
376 result
= TestCondition(slot
!= NULL
? slot
->max_occupancy
- slot
->occupants
.size() : 0, condop
, value
);
389 HandleCondition(condstack
, condflags
, result
);
392 if (condstack
.empty() || condstack
.back() & TRCSF_ACTIVE
) {
395 if (GetTraceRestrictValue(item
)) {
396 out
.flags
&= ~TRPRF_DENY
;
398 out
.flags
|= TRPRF_DENY
;
402 case TRIT_PF_PENALTY
:
403 switch (static_cast<TraceRestrictPathfinderPenaltyAuxField
>(GetTraceRestrictAuxField(item
))) {
405 out
.penalty
+= GetTraceRestrictValue(item
);
408 case TRPPAF_PRESET
: {
409 uint16 index
= GetTraceRestrictValue(item
);
410 assert(index
< TRPPPI_END
);
411 out
.penalty
+= _tracerestrict_pathfinder_penalty_preset_values
[index
];
420 case TRIT_RESERVE_THROUGH
:
421 if (GetTraceRestrictValue(item
)) {
422 out
.flags
&= ~TRPRF_RESERVE_THROUGH
;
424 out
.flags
|= TRPRF_RESERVE_THROUGH
;
428 case TRIT_LONG_RESERVE
:
429 if (GetTraceRestrictValue(item
)) {
430 out
.flags
&= ~TRPRF_LONG_RESERVE
;
432 out
.flags
|= TRPRF_LONG_RESERVE
;
436 case TRIT_WAIT_AT_PBS
:
437 if (GetTraceRestrictValue(item
)) {
438 out
.flags
&= ~TRPRF_WAIT_AT_PBS
;
440 out
.flags
|= TRPRF_WAIT_AT_PBS
;
445 if (!input
.permitted_slot_operations
) break;
446 TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item
));
447 if (slot
== NULL
) break;
448 switch (static_cast<TraceRestrictSlotCondOpField
>(GetTraceRestrictCondOp(item
))) {
449 case TRSCOF_ACQUIRE_WAIT
:
450 if (input
.permitted_slot_operations
& TRPISP_ACQUIRE
) {
451 if (!slot
->Occupy(v
->index
)) out
.flags
|= TRPRF_WAIT_AT_PBS
;
455 case TRSCOF_ACQUIRE_TRY
:
456 if (input
.permitted_slot_operations
& TRPISP_ACQUIRE
) slot
->Occupy(v
->index
);
459 case TRSCOF_RELEASE_BACK
:
460 if (input
.permitted_slot_operations
& TRPISP_RELEASE_BACK
) slot
->Vacate(v
->index
);
463 case TRSCOF_RELEASE_FRONT
:
464 if (input
.permitted_slot_operations
& TRPISP_RELEASE_FRONT
) slot
->Vacate(v
->index
);
480 assert(condstack
.empty());
484 * Decrement ref count, only use when removing a mapping
486 void TraceRestrictProgram::DecrementRefCount() {
487 assert(this->refcount
> 0);
489 if (this->refcount
== 0) {
495 * Validate a instruction list
496 * Returns successful result if program seems OK
497 * This only validates that conditional nesting is correct,
498 * and that all instructions have a known type, at present
500 CommandCost
TraceRestrictProgram::Validate(const std::vector
<TraceRestrictItem
> &items
, TraceRestrictProgramActionsUsedFlags
&actions_used_flags
) {
501 // static to avoid needing to re-alloc/resize on each execution
502 static std::vector
<TraceRestrictCondStackFlags
> condstack
;
504 actions_used_flags
= static_cast<TraceRestrictProgramActionsUsedFlags
>(0);
506 size_t size
= items
.size();
507 for (size_t i
= 0; i
< size
; i
++) {
508 TraceRestrictItem item
= items
[i
];
509 TraceRestrictItemType type
= GetTraceRestrictType(item
);
511 // check multi-word instructions
512 if (IsTraceRestrictDoubleItem(item
)) {
515 return_cmd_error(STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE
); // instruction ran off end
519 if (IsTraceRestrictConditional(item
)) {
520 TraceRestrictCondFlags condflags
= GetTraceRestrictCondFlags(item
);
522 if (type
== TRIT_COND_ENDIF
) {
523 if (condstack
.empty()) {
524 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_NO_IF
); // else/endif with no starting if
526 if (condflags
& TRCF_ELSE
) {
528 if (condstack
.back() & TRCSF_SEEN_ELSE
) {
529 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE
); // Two else clauses
531 HandleCondition(condstack
, condflags
, true);
532 condstack
.back() |= TRCSF_SEEN_ELSE
;
535 condstack
.pop_back();
538 if (condflags
& (TRCF_OR
| TRCF_ELSE
)) { // elif/orif
539 if (condstack
.empty()) {
540 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_ELIF_NO_IF
); // Pre-empt assertions in HandleCondition
542 if (condstack
.back() & TRCSF_SEEN_ELSE
) {
543 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE
); // else clause followed by elif/orif
546 HandleCondition(condstack
, condflags
, true);
549 switch (GetTraceRestrictType(item
)) {
550 case TRIT_COND_ENDIF
:
551 case TRIT_COND_UNDEFINED
:
552 case TRIT_COND_TRAIN_LENGTH
:
553 case TRIT_COND_MAX_SPEED
:
554 case TRIT_COND_CURRENT_ORDER
:
555 case TRIT_COND_NEXT_ORDER
:
556 case TRIT_COND_LAST_STATION
:
557 case TRIT_COND_CARGO
:
558 case TRIT_COND_ENTRY_DIRECTION
:
559 case TRIT_COND_PBS_ENTRY_SIGNAL
:
560 case TRIT_COND_TRAIN_GROUP
:
561 case TRIT_COND_TRAIN_IN_SLOT
:
562 case TRIT_COND_SLOT_OCCUPANCY
:
566 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_UNKNOWN_INSTRUCTION
);
569 switch (GetTraceRestrictType(item
)) {
571 case TRIT_PF_PENALTY
:
572 actions_used_flags
|= TRPAUF_PF
;
575 case TRIT_RESERVE_THROUGH
:
576 actions_used_flags
|= TRPAUF_RESERVE_THROUGH
;
579 case TRIT_LONG_RESERVE
:
580 actions_used_flags
|= TRPAUF_LONG_RESERVE
;
583 case TRIT_WAIT_AT_PBS
:
584 actions_used_flags
|= TRPAUF_WAIT_AT_PBS
;
588 switch (static_cast<TraceRestrictSlotCondOpField
>(GetTraceRestrictCondOp(item
))) {
589 case TRSCOF_ACQUIRE_WAIT
:
590 actions_used_flags
|= TRPAUF_SLOT_ACQUIRE
| TRPAUF_WAIT_AT_PBS
;
593 case TRSCOF_ACQUIRE_TRY
:
594 actions_used_flags
|= TRPAUF_SLOT_ACQUIRE
;
597 case TRSCOF_RELEASE_BACK
:
598 actions_used_flags
|= TRPAUF_SLOT_RELEASE_BACK
;
601 case TRSCOF_RELEASE_FRONT
:
602 actions_used_flags
|= TRPAUF_SLOT_RELEASE_FRONT
;
612 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_UNKNOWN_INSTRUCTION
);
616 if (!condstack
.empty()) {
617 return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_END_CONDSTACK
);
619 return CommandCost();
623 * Convert an instruction index into an item array index
625 size_t TraceRestrictProgram::InstructionOffsetToArrayOffset(const std::vector
<TraceRestrictItem
> &items
, size_t offset
)
627 size_t output_offset
= 0;
628 size_t size
= items
.size();
629 for (size_t i
= 0; i
< offset
&& output_offset
< size
; i
++, output_offset
++) {
630 if (IsTraceRestrictDoubleItem(items
[output_offset
])) {
634 return output_offset
;
638 * Convert an item array index into an instruction index
640 size_t TraceRestrictProgram::ArrayOffsetToInstructionOffset(const std::vector
<TraceRestrictItem
> &items
, size_t offset
)
642 size_t output_offset
= 0;
643 for (size_t i
= 0; i
< offset
; i
++, output_offset
++) {
644 if (IsTraceRestrictDoubleItem(items
[i
])) {
648 return output_offset
;
652 * Set the value and aux field of @p item, as per the value type in @p value_type
654 void SetTraceRestrictValueDefault(TraceRestrictItem
&item
, TraceRestrictValueType value_type
)
656 switch (value_type
) {
661 case TRVT_TILE_INDEX
:
662 case TRVT_RESERVE_THROUGH
:
663 case TRVT_LONG_RESERVE
:
664 case TRVT_WAIT_AT_PBS
:
665 SetTraceRestrictValue(item
, 0);
666 if (!IsTraceRestrictTypeAuxSubtype(GetTraceRestrictType(item
))) {
667 SetTraceRestrictAuxField(item
, 0);
672 SetTraceRestrictValue(item
, INVALID_STATION
);
673 SetTraceRestrictAuxField(item
, TROCAF_STATION
);
677 assert(_sorted_standard_cargo_specs_size
> 0);
678 SetTraceRestrictValue(item
, _sorted_cargo_specs
[0]->Index());
679 SetTraceRestrictAuxField(item
, 0);
683 SetTraceRestrictValue(item
, TRDTSV_FRONT
);
684 SetTraceRestrictAuxField(item
, 0);
687 case TRVT_PF_PENALTY
:
688 SetTraceRestrictValue(item
, TRPPPI_SMALL
);
689 SetTraceRestrictAuxField(item
, TRPPAF_PRESET
);
692 case TRVT_GROUP_INDEX
:
693 SetTraceRestrictValue(item
, INVALID_GROUP
);
694 SetTraceRestrictAuxField(item
, 0);
697 case TRVT_SLOT_INDEX
:
698 SetTraceRestrictValue(item
, INVALID_TRACE_RESTRICT_SLOT_ID
);
699 SetTraceRestrictAuxField(item
, 0);
702 case TRVT_SLOT_INDEX_INT
:
703 SetTraceRestrictValue(item
, INVALID_TRACE_RESTRICT_SLOT_ID
);
713 * Set the type field of a TraceRestrictItem, and resets any other fields which are no longer valid/meaningful to sensible defaults
715 void SetTraceRestrictTypeAndNormalise(TraceRestrictItem
&item
, TraceRestrictItemType type
, uint8 aux_data
)
718 assert(GetTraceRestrictType(item
) != TRIT_NULL
);
719 assert(IsTraceRestrictConditional(item
) == IsTraceRestrictTypeConditional(type
));
721 assert(type
!= TRIT_NULL
);
723 TraceRestrictTypePropertySet old_properties
= GetTraceRestrictTypeProperties(item
);
724 SetTraceRestrictType(item
, type
);
725 bool set_aux_field
= false;
726 if (IsTraceRestrictTypeAuxSubtype(type
)) {
727 SetTraceRestrictAuxField(item
, aux_data
);
728 set_aux_field
= true;
731 assert(aux_data
== 0);
733 TraceRestrictTypePropertySet new_properties
= GetTraceRestrictTypeProperties(item
);
735 if (old_properties
.cond_type
!= new_properties
.cond_type
||
736 old_properties
.value_type
!= new_properties
.value_type
) {
737 SetTraceRestrictCondOp(item
, TRCO_IS
);
738 SetTraceRestrictValueDefault(item
, new_properties
.value_type
);
740 SetTraceRestrictAuxField(item
, aux_data
);
743 if (GetTraceRestrictType(item
) == TRIT_COND_LAST_STATION
&& GetTraceRestrictAuxField(item
) != TROCAF_STATION
) {
744 // if changing type from another order type to last visited station, reset value if not currently a station
745 SetTraceRestrictValueDefault(item
, TRVT_ORDER
);
750 * Sets the "signal has a trace restrict mapping" bit
751 * This looks for mappings with that tile index
753 void SetIsSignalRestrictedBit(TileIndex t
)
755 // First mapping for this tile, or later
756 TraceRestrictMapping::iterator lower_bound
= _tracerestrictprogram_mapping
.lower_bound(MakeTraceRestrictRefId(t
, static_cast<Track
>(0)));
758 // First mapping for next tile, or later
759 TraceRestrictMapping::iterator upper_bound
= _tracerestrictprogram_mapping
.lower_bound(MakeTraceRestrictRefId(t
+ 1, static_cast<Track
>(0)));
761 // If iterators are the same, there are no mappings for this tile
762 SetRestrictedSignal(t
, lower_bound
!= upper_bound
);
766 * Create a new program mapping to an existing program
767 * If a mapping already exists, it is removed
769 void TraceRestrictCreateProgramMapping(TraceRestrictRefId ref
, TraceRestrictProgram
*prog
)
771 std::pair
<TraceRestrictMapping::iterator
, bool> insert_result
=
772 _tracerestrictprogram_mapping
.insert(std::make_pair(ref
, TraceRestrictMappingItem(prog
->index
)));
774 if (!insert_result
.second
) {
775 // value was not inserted, there is an existing mapping
776 // unref the existing mapping before updating it
777 _tracerestrictprogram_pool
.Get(insert_result
.first
->second
.program_id
)->DecrementRefCount();
778 insert_result
.first
->second
= prog
->index
;
780 prog
->IncrementRefCount();
782 TileIndex tile
= GetTraceRestrictRefIdTileIndex(ref
);
783 Track track
= GetTraceRestrictRefIdTrack(ref
);
784 SetIsSignalRestrictedBit(tile
);
785 MarkTileDirtyByTile(tile
);
786 YapfNotifyTrackLayoutChange(tile
, track
);
790 * Remove a program mapping
791 * @return true if a mapping was actually removed
793 bool TraceRestrictRemoveProgramMapping(TraceRestrictRefId ref
)
795 TraceRestrictMapping::iterator iter
= _tracerestrictprogram_mapping
.find(ref
);
796 if (iter
!= _tracerestrictprogram_mapping
.end()) {
798 TraceRestrictProgram
*prog
= _tracerestrictprogram_pool
.Get(iter
->second
.program_id
);
800 // check to see if another mapping needs to be removed as well
801 // do this before decrementing the refcount
802 bool remove_other_mapping
= prog
->refcount
== 2 && prog
->items
.empty();
804 prog
->DecrementRefCount();
805 _tracerestrictprogram_mapping
.erase(iter
);
807 TileIndex tile
= GetTraceRestrictRefIdTileIndex(ref
);
808 Track track
= GetTraceRestrictRefIdTrack(ref
);
809 SetIsSignalRestrictedBit(tile
);
810 MarkTileDirtyByTile(tile
);
811 YapfNotifyTrackLayoutChange(tile
, track
);
813 if (remove_other_mapping
) {
814 TraceRestrictProgramID id
= prog
->index
;
815 for (TraceRestrictMapping::iterator rm_iter
= _tracerestrictprogram_mapping
.begin();
816 rm_iter
!= _tracerestrictprogram_mapping
.end(); ++rm_iter
) {
817 if (rm_iter
->second
.program_id
== id
) {
818 TraceRestrictRemoveProgramMapping(rm_iter
->first
);
831 * Gets the signal program for the tile ref @p ref
832 * An empty program will be constructed if none exists, and @p create_new is true, unless the pool is full
834 TraceRestrictProgram
*GetTraceRestrictProgram(TraceRestrictRefId ref
, bool create_new
)
836 // Optimise for lookup, creating doesn't have to be that fast
838 TraceRestrictMapping::iterator iter
= _tracerestrictprogram_mapping
.find(ref
);
839 if (iter
!= _tracerestrictprogram_mapping
.end()) {
841 return _tracerestrictprogram_pool
.Get(iter
->second
.program_id
);
842 } else if (create_new
) {
845 // Create new pool item
846 if (!TraceRestrictProgram::CanAllocateItem()) {
849 TraceRestrictProgram
*prog
= new TraceRestrictProgram();
851 // Create new mapping to pool item
852 TraceRestrictCreateProgramMapping(ref
, prog
);
860 * Notify that a signal is being removed
861 * Remove any trace restrict mappings associated with it
863 void TraceRestrictNotifySignalRemoval(TileIndex tile
, Track track
)
865 TraceRestrictRefId ref
= MakeTraceRestrictRefId(tile
, track
);
866 bool removed
= TraceRestrictRemoveProgramMapping(ref
);
867 DeleteWindowById(WC_TRACE_RESTRICT
, ref
);
868 if (removed
) InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
872 * Helper function to perform parameter bit-packing and call DoCommandP, for instruction modification actions
874 void TraceRestrictDoCommandP(TileIndex tile
, Track track
, TraceRestrictDoCommandType type
, uint32 offset
, uint32 value
, StringID error_msg
)
879 assert(offset
< (1 << 16));
880 SB(p1
, 8, 16, offset
);
881 DoCommandP(tile
, p1
, value
, CMD_PROGRAM_TRACERESTRICT_SIGNAL
| CMD_MSG(error_msg
));
885 * Check whether a tile/tracl pair contains a usable signal
887 static CommandCost
TraceRestrictCheckTileIsUsable(TileIndex tile
, Track track
)
889 // Check that there actually is a signal here
890 if (!IsPlainRailTile(tile
) || !HasTrack(tile
, track
)) {
891 return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK
);
893 if (!HasSignalOnTrack(tile
, track
)) {
894 return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS
);
897 // Check tile ownership, do this afterwards to avoid tripping up on house/industry tiles
898 CommandCost ret
= CheckTileOwnership(tile
);
903 return CommandCost();
907 * Returns an appropriate default value for the second item of a dual-item instruction
908 * @p item is the first item of the instruction
910 static uint32
GetDualInstructionInitialValue(TraceRestrictItem item
)
912 switch (GetTraceRestrictType(item
)) {
913 case TRIT_COND_PBS_ENTRY_SIGNAL
:
916 case TRIT_COND_SLOT_OCCUPANCY
:
924 template <typename T
> T
InstructionIteratorNext(T iter
)
926 return IsTraceRestrictDoubleItem(*iter
) ? iter
+ 2 : iter
+ 1;
929 template <typename T
> void InstructionIteratorAdvance(T
&iter
)
931 iter
= InstructionIteratorNext(iter
);
934 CommandCost
TraceRestrictProgramRemoveItemAt(std::vector
<TraceRestrictItem
> &items
, uint32 offset
, bool shallow_mode
)
936 TraceRestrictItem old_item
= *TraceRestrictProgram::InstructionAt(items
, offset
);
937 if (IsTraceRestrictConditional(old_item
) && GetTraceRestrictCondFlags(old_item
) != TRCF_OR
) {
938 bool remove_whole_block
= false;
939 if (GetTraceRestrictCondFlags(old_item
) == 0) {
940 if (GetTraceRestrictType(old_item
) == TRIT_COND_ENDIF
) {
941 // this is an end if, can't remove these
942 return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_REMOVE_ENDIF
);
944 // this is an opening if
945 remove_whole_block
= true;
949 uint32 recursion_depth
= 1;
950 std::vector
<TraceRestrictItem
>::iterator remove_start
= TraceRestrictProgram::InstructionAt(items
, offset
);
951 std::vector
<TraceRestrictItem
>::iterator remove_end
= InstructionIteratorNext(remove_start
);
953 // iterate until matching end block found
954 for (; remove_end
!= items
.end(); InstructionIteratorAdvance(remove_end
)) {
955 TraceRestrictItem current_item
= *remove_end
;
956 if (IsTraceRestrictConditional(current_item
)) {
957 if (GetTraceRestrictCondFlags(current_item
) == 0) {
958 if (GetTraceRestrictType(current_item
) == TRIT_COND_ENDIF
) {
961 if (recursion_depth
== 0) {
962 if (remove_whole_block
) {
964 // must erase endif first, as it is later in the vector
965 items
.erase(remove_end
, InstructionIteratorNext(remove_end
));
967 // inclusively remove up to here
968 InstructionIteratorAdvance(remove_end
);
972 // exclusively remove up to here
977 // this is an opening if
981 // this is an else/or type block
982 if (recursion_depth
== 1 && !remove_whole_block
) {
983 // exclusively remove up to here
987 if (recursion_depth
== 1 && remove_whole_block
&& shallow_mode
) {
988 // shallow-removing whole if block, and it contains an else/or if, bail out
989 return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_SHALLOW_REMOVE_IF_ELIF
);
994 if (recursion_depth
!= 0) return CMD_ERROR
; // ran off the end
996 items
.erase(remove_start
, InstructionIteratorNext(remove_start
));
998 items
.erase(remove_start
, remove_end
);
1001 std::vector
<TraceRestrictItem
>::iterator remove_start
= TraceRestrictProgram::InstructionAt(items
, offset
);
1002 std::vector
<TraceRestrictItem
>::iterator remove_end
= InstructionIteratorNext(remove_start
);
1004 items
.erase(remove_start
, remove_end
);
1006 return CommandCost();
1009 CommandCost
TraceRestrictProgramMoveItemAt(std::vector
<TraceRestrictItem
> &items
, uint32
&offset
, bool up
, bool shallow_mode
)
1011 std::vector
<TraceRestrictItem
>::iterator move_start
= TraceRestrictProgram::InstructionAt(items
, offset
);
1012 std::vector
<TraceRestrictItem
>::iterator move_end
= InstructionIteratorNext(move_start
);
1014 TraceRestrictItem old_item
= *move_start
;
1015 if (!shallow_mode
) {
1016 if (IsTraceRestrictConditional(old_item
)) {
1017 if (GetTraceRestrictCondFlags(old_item
) != 0) {
1018 // can't move or/else blocks
1019 return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_MOVE_ITEM
);
1021 if (GetTraceRestrictType(old_item
) == TRIT_COND_ENDIF
) {
1022 // this is an end if, can't move these
1023 return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_MOVE_ITEM
);
1026 uint32 recursion_depth
= 1;
1027 // iterate until matching end block found
1028 for (; move_end
!= items
.end(); InstructionIteratorAdvance(move_end
)) {
1029 TraceRestrictItem current_item
= *move_end
;
1030 if (IsTraceRestrictConditional(current_item
)) {
1031 if (GetTraceRestrictCondFlags(current_item
) == 0) {
1032 if (GetTraceRestrictType(current_item
) == TRIT_COND_ENDIF
) {
1033 // this is an end if
1035 if (recursion_depth
== 0) {
1036 // inclusively remove up to here
1037 InstructionIteratorAdvance(move_end
);
1041 // this is an opening if
1047 if (recursion_depth
!= 0) return CMD_ERROR
; // ran off the end
1052 if (move_start
== items
.begin()) return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_MOVE_ITEM
);
1053 std::rotate(TraceRestrictProgram::InstructionAt(items
, offset
- 1), move_start
, move_end
);
1056 if (move_end
== items
.end()) return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_MOVE_ITEM
);
1057 std::rotate(move_start
, move_end
, InstructionIteratorNext(move_end
));
1060 return CommandCost();
1064 * The main command for editing a signal tracerestrict program.
1065 * @param tile The tile which contains the signal.
1066 * @param flags Internal command handler stuff.
1067 * Below apply for instruction modification actions only
1068 * @param p1 Bitstuffed items
1069 * @param p2 Item, for insert and modify operations. Flags for instruction move operations
1070 * @return the cost of this operation (which is free), or an error
1072 CommandCost
CmdProgramSignalTraceRestrict(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1074 TraceRestrictDoCommandType type
= static_cast<TraceRestrictDoCommandType
>(GB(p1
, 3, 5));
1076 if (type
>= TRDCT_PROG_COPY
) {
1077 return CmdProgramSignalTraceRestrictProgMgmt(tile
, flags
, p1
, p2
, text
);
1080 Track track
= static_cast<Track
>(GB(p1
, 0, 3));
1081 uint32 offset
= GB(p1
, 8, 16);
1082 TraceRestrictItem item
= static_cast<TraceRestrictItem
>(p2
);
1084 CommandCost ret
= TraceRestrictCheckTileIsUsable(tile
, track
);
1089 bool can_make_new
= (type
== TRDCT_INSERT_ITEM
) && (flags
& DC_EXEC
);
1090 bool need_existing
= (type
!= TRDCT_INSERT_ITEM
);
1091 TraceRestrictProgram
*prog
= GetTraceRestrictProgram(MakeTraceRestrictRefId(tile
, track
), can_make_new
);
1092 if (need_existing
&& !prog
) {
1093 return_cmd_error(STR_TRACE_RESTRICT_ERROR_NO_PROGRAM
);
1096 uint32 offset_limit_exclusive
= ((type
== TRDCT_INSERT_ITEM
) ? 1 : 0);
1097 if (prog
) offset_limit_exclusive
+= (uint32
)prog
->items
.size();
1099 if (offset
>= offset_limit_exclusive
) {
1100 return_cmd_error(STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE
);
1104 std::vector
<TraceRestrictItem
> items
;
1105 if (prog
) items
= prog
->items
;
1108 case TRDCT_INSERT_ITEM
:
1109 items
.insert(TraceRestrictProgram::InstructionAt(items
, offset
), item
);
1110 if (IsTraceRestrictConditional(item
) &&
1111 GetTraceRestrictCondFlags(item
) == 0 &&
1112 GetTraceRestrictType(item
) != TRIT_COND_ENDIF
) {
1113 // this is an opening if block, insert a corresponding end if
1114 TraceRestrictItem endif_item
= 0;
1115 SetTraceRestrictType(endif_item
, TRIT_COND_ENDIF
);
1116 items
.insert(TraceRestrictProgram::InstructionAt(items
, offset
) + 1, endif_item
);
1117 } else if (IsTraceRestrictDoubleItem(item
)) {
1118 items
.insert(TraceRestrictProgram::InstructionAt(items
, offset
) + 1, GetDualInstructionInitialValue(item
));
1122 case TRDCT_MODIFY_ITEM
: {
1123 std::vector
<TraceRestrictItem
>::iterator old_item
= TraceRestrictProgram::InstructionAt(items
, offset
);
1124 if (IsTraceRestrictConditional(*old_item
) != IsTraceRestrictConditional(item
)) {
1125 return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_CHANGE_CONDITIONALITY
);
1127 bool old_is_dual
= IsTraceRestrictDoubleItem(*old_item
);
1128 bool new_is_dual
= IsTraceRestrictDoubleItem(item
);
1130 if (old_is_dual
&& !new_is_dual
) {
1131 items
.erase(old_item
+ 1);
1132 } else if (!old_is_dual
&& new_is_dual
) {
1133 items
.insert(old_item
+ 1, GetDualInstructionInitialValue(item
));
1138 case TRDCT_MODIFY_DUAL_ITEM
: {
1139 std::vector
<TraceRestrictItem
>::iterator old_item
= TraceRestrictProgram::InstructionAt(items
, offset
);
1140 if (!IsTraceRestrictDoubleItem(*old_item
)) {
1143 *(old_item
+ 1) = p2
;
1147 case TRDCT_REMOVE_ITEM
:
1148 case TRDCT_SHALLOW_REMOVE_ITEM
: {
1149 CommandCost res
= TraceRestrictProgramRemoveItemAt(items
, offset
, type
== TRDCT_SHALLOW_REMOVE_ITEM
);
1150 if (res
.Failed()) return res
;
1154 case TRDCT_MOVE_ITEM
: {
1155 CommandCost res
= TraceRestrictProgramMoveItemAt(items
, offset
, p2
& 1, p2
& 2);
1156 if (res
.Failed()) return res
;
1165 TraceRestrictProgramActionsUsedFlags actions_used_flags
;
1166 CommandCost validation_result
= TraceRestrictProgram::Validate(items
, actions_used_flags
);
1167 if (validation_result
.Failed()) {
1168 return validation_result
;
1171 if (flags
& DC_EXEC
) {
1174 // move in modified program
1175 prog
->items
.swap(items
);
1176 prog
->actions_used_flags
= actions_used_flags
;
1178 if (prog
->items
.size() == 0 && prog
->refcount
== 1) {
1179 // program is empty, and this tile is the only reference to it
1180 // so delete it, as it's redundant
1181 TraceRestrictRemoveProgramMapping(MakeTraceRestrictRefId(tile
, track
));
1185 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1188 return CommandCost();
1192 * Helper function to perform parameter bit-packing and call DoCommandP, for program management actions
1194 void TraceRestrictProgMgmtWithSourceDoCommandP(TileIndex tile
, Track track
, TraceRestrictDoCommandType type
,
1195 TileIndex source_tile
, Track source_track
, StringID error_msg
)
1198 SB(p1
, 0, 3, track
);
1200 SB(p1
, 8, 3, source_track
);
1201 DoCommandP(tile
, p1
, source_tile
, CMD_PROGRAM_TRACERESTRICT_SIGNAL
| CMD_MSG(error_msg
));
1205 * Sub command for copy/share/unshare operations on signal tracerestrict programs.
1206 * @param tile The tile which contains the signal.
1207 * @param flags Internal command handler stuff.
1208 * @param p1 Bitstuffed items
1209 * @param p2 Source tile, for share/copy operations
1210 * @return the cost of this operation (which is free), or an error
1212 CommandCost
CmdProgramSignalTraceRestrictProgMgmt(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1214 TraceRestrictDoCommandType type
= static_cast<TraceRestrictDoCommandType
>(GB(p1
, 3, 5));
1215 Track track
= static_cast<Track
>(GB(p1
, 0, 3));
1216 Track source_track
= static_cast<Track
>(GB(p1
, 8, 3));
1217 TileIndex source_tile
= static_cast<TileIndex
>(p2
);
1219 TraceRestrictRefId self
= MakeTraceRestrictRefId(tile
, track
);
1220 TraceRestrictRefId source
= MakeTraceRestrictRefId(source_tile
, source_track
);
1222 assert(type
>= TRDCT_PROG_COPY
);
1224 CommandCost ret
= TraceRestrictCheckTileIsUsable(tile
, track
);
1229 if (type
== TRDCT_PROG_SHARE
|| type
== TRDCT_PROG_COPY
) {
1230 if (self
== source
) {
1231 return_cmd_error(STR_TRACE_RESTRICT_ERROR_SOURCE_SAME_AS_TARGET
);
1234 if (type
== TRDCT_PROG_SHARE
|| type
== TRDCT_PROG_COPY
|| type
== TRDCT_PROG_COPY_APPEND
) {
1235 ret
= TraceRestrictCheckTileIsUsable(source_tile
, source_track
);
1241 if (type
!= TRDCT_PROG_RESET
&& !TraceRestrictProgram::CanAllocateItem()) {
1245 if (!(flags
& DC_EXEC
)) {
1246 return CommandCost();
1250 case TRDCT_PROG_COPY
: {
1251 TraceRestrictRemoveProgramMapping(self
);
1252 TraceRestrictProgram
*source_prog
= GetTraceRestrictProgram(source
, false);
1253 if (source_prog
&& !source_prog
->items
.empty()) {
1254 TraceRestrictProgram
*prog
= GetTraceRestrictProgram(self
, true);
1256 // allocation failed
1259 prog
->items
= source_prog
->items
; // copy
1265 case TRDCT_PROG_COPY_APPEND
: {
1266 TraceRestrictProgram
*source_prog
= GetTraceRestrictProgram(source
, false);
1267 if (source_prog
&& !source_prog
->items
.empty()) {
1268 TraceRestrictProgram
*prog
= GetTraceRestrictProgram(self
, true);
1270 // allocation failed
1273 prog
->items
.reserve(prog
->items
.size() + source_prog
->items
.size()); // this is in case prog == source_prog
1274 prog
->items
.insert(prog
->items
.end(), source_prog
->items
.begin(), source_prog
->items
.end()); // append
1280 case TRDCT_PROG_SHARE
: {
1281 TraceRestrictRemoveProgramMapping(self
);
1282 TraceRestrictProgram
*source_prog
= GetTraceRestrictProgram(source
, true);
1284 // allocation failed
1288 TraceRestrictCreateProgramMapping(self
, source_prog
);
1292 case TRDCT_PROG_UNSHARE
: {
1293 std::vector
<TraceRestrictItem
> items
;
1294 TraceRestrictProgram
*prog
= GetTraceRestrictProgram(self
, false);
1296 // copy program into temporary
1297 items
= prog
->items
;
1299 // remove old program
1300 TraceRestrictRemoveProgramMapping(self
);
1303 // if prog is non-empty, create new program and move temporary in
1304 TraceRestrictProgram
*new_prog
= GetTraceRestrictProgram(self
, true);
1306 // allocation failed
1310 new_prog
->items
.swap(items
);
1311 new_prog
->Validate();
1316 case TRDCT_PROG_RESET
: {
1317 TraceRestrictRemoveProgramMapping(self
);
1327 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1329 return CommandCost();
1333 * This is called when a station, waypoint or depot is about to be deleted
1334 * Scan program pool and change any references to it to the invalid station ID, to avoid dangling references
1336 void TraceRestrictRemoveDestinationID(TraceRestrictOrderCondAuxField type
, uint16 index
)
1338 TraceRestrictProgram
*prog
;
1340 FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog
) {
1341 for (size_t i
= 0; i
< prog
->items
.size(); i
++) {
1342 TraceRestrictItem
&item
= prog
->items
[i
]; // note this is a reference,
1343 if (GetTraceRestrictType(item
) == TRIT_COND_CURRENT_ORDER
||
1344 GetTraceRestrictType(item
) == TRIT_COND_NEXT_ORDER
||
1345 GetTraceRestrictType(item
) == TRIT_COND_LAST_STATION
) {
1346 if (GetTraceRestrictAuxField(item
) == type
&& GetTraceRestrictValue(item
) == index
) {
1347 SetTraceRestrictValueDefault(item
, TRVT_ORDER
); // this updates the instruction in-place
1350 if (IsTraceRestrictDoubleItem(item
)) i
++;
1355 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1359 * This is called when a group is about to be deleted
1360 * Scan program pool and change any references to it to the invalid group ID, to avoid dangling references
1362 void TraceRestrictRemoveGroupID(GroupID index
)
1364 TraceRestrictProgram
*prog
;
1366 FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog
) {
1367 for (size_t i
= 0; i
< prog
->items
.size(); i
++) {
1368 TraceRestrictItem
&item
= prog
->items
[i
]; // note this is a reference,
1369 if (GetTraceRestrictType(item
) == TRIT_COND_TRAIN_GROUP
&& GetTraceRestrictValue(item
) == index
) {
1370 SetTraceRestrictValueDefault(item
, TRVT_GROUP_INDEX
); // this updates the instruction in-place
1372 if (IsTraceRestrictDoubleItem(item
)) i
++;
1377 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1380 static std::unordered_multimap
<VehicleID
, TraceRestrictSlotID
> slot_vehicle_index
;
1383 * Add vehicle ID to occupants if possible and not already an occupant
1384 * @param id Vehicle ID
1385 * @param force Add the vehicle even if the slot is at/over capacity
1386 * @return whether vehicle ID is now an occupant
1388 bool TraceRestrictSlot::Occupy(VehicleID id
, bool force
)
1390 if (this->IsOccupant(id
)) return true;
1391 if (this->occupants
.size() >= this->max_occupancy
&& !force
) return false;
1392 this->occupants
.push_back(id
);
1393 slot_vehicle_index
.emplace(id
, this->index
);
1394 SetBit(Train::Get(id
)->flags
, VRF_HAVE_SLOT
);
1395 SetWindowDirty(WC_VEHICLE_DETAILS
, id
);
1396 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1401 * Remove vehicle ID from occupants
1402 * @param id Vehicle ID
1404 void TraceRestrictSlot::Vacate(VehicleID id
)
1406 if (container_unordered_remove(this->occupants
, id
)) {
1411 /** Remove all occupants */
1412 void TraceRestrictSlot::Clear()
1414 for (VehicleID id
: this->occupants
) {
1417 this->occupants
.clear();
1420 void TraceRestrictSlot::DeIndex(VehicleID id
)
1422 auto range
= slot_vehicle_index
.equal_range(id
);
1423 for (auto it
= range
.first
; it
!= range
.second
; ++it
) {
1424 if (it
->second
== this->index
) {
1425 bool is_first_in_range
= (it
== range
.first
);
1427 auto next
= slot_vehicle_index
.erase(it
);
1429 if (is_first_in_range
&& next
== range
.second
) {
1430 /* Only one item, which we've just erased, clear the vehicle flag */
1431 ClrBit(Train::Get(id
)->flags
, VRF_HAVE_SLOT
);
1436 SetWindowDirty(WC_VEHICLE_DETAILS
, id
);
1437 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1440 /** Rebuild slot vehicle index after loading */
1441 void TraceRestrictSlot::RebuildVehicleIndex()
1443 slot_vehicle_index
.clear();
1444 const TraceRestrictSlot
*slot
;
1445 FOR_ALL_TRACE_RESTRICT_SLOTS(slot
) {
1446 for (VehicleID id
: slot
->occupants
) {
1447 slot_vehicle_index
.emplace(id
, slot
->index
);
1452 /** Slot pool is about to be cleared */
1453 void TraceRestrictSlot::PreCleanPool()
1455 slot_vehicle_index
.clear();
1458 /** Remove vehicle ID from all slot occupants */
1459 void TraceRestrictRemoveVehicleFromAllSlots(VehicleID id
)
1461 auto range
= slot_vehicle_index
.equal_range(id
);
1462 for (auto it
= range
.first
; it
!= range
.second
; ++it
) {
1463 TraceRestrictSlot
*slot
= TraceRestrictSlot::Get(it
->second
);
1464 container_unordered_remove(slot
->occupants
, id
);
1466 slot_vehicle_index
.erase(range
.first
, range
.second
);
1467 if (range
.first
!= range
.second
) InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1470 /** Replace all instance of a vehicle ID with another, in all slot occupants */
1471 void TraceRestrictTransferVehicleOccupantInAllSlots(VehicleID from
, VehicleID to
)
1473 auto range
= slot_vehicle_index
.equal_range(from
);
1474 std::vector
<TraceRestrictSlotID
> slots
;
1475 for (auto it
= range
.first
; it
!= range
.second
; ++it
) {
1476 slots
.push_back(it
->second
);
1478 slot_vehicle_index
.erase(range
.first
, range
.second
);
1479 for (TraceRestrictSlotID slot_id
: slots
) {
1480 TraceRestrictSlot
*slot
= TraceRestrictSlot::Get(slot_id
);
1481 for (VehicleID
&id
: slot
->occupants
) {
1484 slot_vehicle_index
.emplace(to
, slot_id
);
1488 if (!slots
.empty()) InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1491 /** Get list of slots occupied by a vehicle ID */
1492 void TraceRestrictGetVehicleSlots(VehicleID id
, std::vector
<TraceRestrictSlotID
> &out
)
1494 auto range
= slot_vehicle_index
.equal_range(id
);
1495 for (auto it
= range
.first
; it
!= range
.second
; ++it
) {
1496 out
.push_back(it
->second
);
1501 * This is called when a slot is about to be deleted
1502 * Scan program pool and change any references to it to the invalid group ID, to avoid dangling references
1504 void TraceRestrictRemoveSlotID(TraceRestrictSlotID index
)
1506 TraceRestrictProgram
*prog
;
1508 FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog
) {
1509 for (size_t i
= 0; i
< prog
->items
.size(); i
++) {
1510 TraceRestrictItem
&item
= prog
->items
[i
]; // note this is a reference,
1511 if ((GetTraceRestrictType(item
) == TRIT_SLOT
|| GetTraceRestrictType(item
) == TRIT_COND_TRAIN_IN_SLOT
) && GetTraceRestrictValue(item
) == index
) {
1512 SetTraceRestrictValueDefault(item
, TRVT_SLOT_INDEX
); // this updates the instruction in-place
1514 if ((GetTraceRestrictType(item
) == TRIT_COND_SLOT_OCCUPANCY
) && GetTraceRestrictValue(item
) == index
) {
1515 SetTraceRestrictValueDefault(item
, TRVT_SLOT_INDEX_INT
); // this updates the instruction in-place
1517 if (IsTraceRestrictDoubleItem(item
)) i
++;
1522 static bool IsUniqueSlotName(const char *name
)
1524 const TraceRestrictSlot
*slot
;
1525 FOR_ALL_TRACE_RESTRICT_SLOTS(slot
) {
1526 if (slot
->name
== name
) return false;
1532 * Create a new slot.
1533 * @param tile unused
1534 * @param flags type of operation
1537 * @param text new slot name
1538 * @return the cost of this operation or an error
1540 CommandCost
CmdCreateTraceRestrictSlot(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1542 if (!TraceRestrictSlot::CanAllocateItem()) return CMD_ERROR
;
1543 if (StrEmpty(text
)) return CMD_ERROR
;
1545 size_t length
= Utf8StringLength(text
);
1546 if (length
<= 0) return CMD_ERROR
;
1547 if (length
>= MAX_LENGTH_TRACE_RESTRICT_SLOT_NAME_CHARS
) return CMD_ERROR
;
1548 if (!IsUniqueSlotName(text
)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE
);
1550 if (flags
& DC_EXEC
) {
1551 TraceRestrictSlot
*slot
= new TraceRestrictSlot(_current_company
);
1555 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1556 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1559 return CommandCost();
1565 * @param tile unused
1566 * @param flags type of operation
1567 * @param p1 index of array group
1568 * - p1 bit 0-15 : Slot ID
1570 * @param text unused
1571 * @return the cost of this operation or an error
1573 CommandCost
CmdDeleteTraceRestrictSlot(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1575 TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(p1
);
1576 if (slot
== NULL
|| slot
->owner
!= _current_company
) return CMD_ERROR
;
1578 if (flags
& DC_EXEC
) {
1579 /* notify tracerestrict that group is about to be deleted */
1580 TraceRestrictRemoveSlotID(slot
->index
);
1584 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1585 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1586 InvalidateWindowClassesData(WC_VEHICLE_ORDERS
);
1589 return CommandCost();
1594 * @param tile unused
1595 * @param flags type of operation
1596 * @param p1 index of array group
1597 * - p1 bit 0-15 : GroupID
1598 * - p1 bit 16: 0 - Rename grouop
1599 * 1 - Change max occupancy
1600 * @param p2 new max occupancy
1601 * @param text the new name
1602 * @return the cost of this operation or an error
1604 CommandCost
CmdAlterTraceRestrictSlot(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1606 TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(GB(p1
, 0, 16));
1607 if (slot
== NULL
|| slot
->owner
!= _current_company
) return CMD_ERROR
;
1609 if (!HasBit(p1
, 16)) {
1612 if (StrEmpty(text
)) return CMD_ERROR
;
1613 size_t length
= Utf8StringLength(text
);
1614 if (length
<= 0) return CMD_ERROR
;
1615 if (length
>= MAX_LENGTH_TRACE_RESTRICT_SLOT_NAME_CHARS
) return CMD_ERROR
;
1616 if (!IsUniqueSlotName(text
)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE
);
1618 if (flags
& DC_EXEC
) {
1622 /* Change max occupancy */
1624 if (flags
& DC_EXEC
) {
1625 slot
->max_occupancy
= p2
;
1629 if (flags
& DC_EXEC
) {
1631 InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
1632 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1633 InvalidateWindowClassesData(WC_VEHICLE_ORDERS
);
1636 return CommandCost();
1640 * Add a vehicle to a slot
1641 * @param tile unused
1642 * @param flags type of operation
1643 * @param p1 index of array group
1644 * - p1 bit 0-15 : GroupID
1645 * @param p2 index of vehicle
1646 * - p2 bit 0-19 : VehicleID
1647 * @param text unused
1648 * @return the cost of this operation or an error
1650 CommandCost
CmdAddVehicleTraceRestrictSlot(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1652 TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(p1
);
1653 Vehicle
*v
= Vehicle::GetIfValid(p2
);
1654 if (slot
== NULL
|| slot
->owner
!= _current_company
) return CMD_ERROR
;
1655 if (v
== NULL
|| v
->owner
!= _current_company
) return CMD_ERROR
;
1657 if (flags
& DC_EXEC
) {
1658 slot
->Occupy(v
->index
, true);
1661 return CommandCost();
1665 * Remove a vehicle from a slot
1666 * @param tile unused
1667 * @param flags type of operation
1668 * @param p1 index of array group
1669 * - p1 bit 0-15 : GroupID
1670 * @param p2 index of vehicle
1671 * - p2 bit 0-19 : VehicleID
1672 * @param text unused
1673 * @return the cost of this operation or an error
1675 CommandCost
CmdRemoveVehicleTraceRestrictSlot(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
1677 TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(p1
);
1678 Vehicle
*v
= Vehicle::GetIfValid(p2
);
1679 if (slot
== NULL
|| slot
->owner
!= _current_company
) return CMD_ERROR
;
1680 if (v
== NULL
) return CMD_ERROR
; // permit removing vehicles of other owners from your own slot
1682 if (flags
& DC_EXEC
) {
1683 slot
->Vacate(v
->index
);
1686 return CommandCost();