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 logic_signal_program.cpp Implementation of the SignalProgram class functions. */
10 #include "logic_signals.h"
11 #include "overlay_cmd.h"
13 #include "viewport_func.h"
18 * Return the opposite of the given signal state.
19 * @param state The original signal state to revert.
20 * @return Green if the given argument is Red and vice versa.
22 static SignalState
OppositeSignalState(SignalState state
)
24 return state
== SIGNAL_STATE_RED
? SIGNAL_STATE_GREEN
: SIGNAL_STATE_RED
;
28 * The maximum number of signal programs which are evaluated in succession.
30 static const int MAX_LOGIC_SIGNAL_RECURSIONS
= 5;
33 * The constructor for creating a new signal program.
34 * @param tile The tile where the logic signal for this program is located.
35 * @param track The track where the logic signal for this program is located.
37 SignalProgram::SignalProgram(TileIndex tile
, Track track
)
42 // Default to a priority signal: if any of the linked input
43 // signals are red, this one goes red.
44 this->own_default_state
= SIGNAL_STATE_GREEN
;
45 this->trigger_state
= SIGNAL_STATE_RED
;
46 this->signal_op
= SIGNAL_OP_OR
;
48 this->blocked_by_train
= false;
52 * Add a new signal as input for this signal program.
53 * @param tile The tile of the signal to be linked.
54 * @param track The track of the signal to be linked.
56 void SignalProgram::AddLink(TileIndex tile
, Track track
)
58 const SignalReference input
= GetSignalReference(tile
, track
);
60 if (std::find(this->linked_signals
.begin(), this->linked_signals
.end(), input
) == this->linked_signals
.end()) {
61 this->linked_signals
.push_back(input
);
62 _signal_link_list
.push_back(std::make_pair(input
, GetSignalReference(this->tile
, this->track
)));
65 Overlays::Instance()->RefreshLogicSignalOverlay();
69 * Remove a linked signal from this program. The link must exist.
70 * @param tile The tile of the signal to be removed.
71 * @param track The track of the signal to be removed.
73 void SignalProgram::RemoveLink(TileIndex tile
, Track track
)
75 // Refresh BEFORE because we need to know what tiles to refresh before we remove the reference to them.
76 Overlays::Instance()->RefreshLogicSignalOverlay();
78 SignalReference input_to_remove
= GetSignalReference(tile
, track
);
79 const auto found_input
= std::find(this->linked_signals
.begin(), this->linked_signals
.end(), input_to_remove
);
81 assert(found_input
!= this->linked_signals
.end());
83 this->linked_signals
.erase(found_input
);
85 const auto link_to_remove
= std::make_pair(input_to_remove
, GetSignalReference(this->tile
, this->track
));
86 const auto found_link
= std::find(_signal_link_list
.begin(), _signal_link_list
.end(), link_to_remove
);
88 assert(found_link
!= _signal_link_list
.end());
90 _signal_link_list
.erase(found_link
);
94 * Remove all links that this signal program has.
96 void SignalProgram::ClearAllLinks()
98 // Refresh BEFORE because we need to know what tiles to refresh before we remove the reference to them.
99 Overlays::Instance()->RefreshLogicSignalOverlay();
100 auto this_signal
= GetSignalReference(this->tile
, this->track
);
102 _signal_link_list
.erase(std::remove_if(_signal_link_list
.begin(), _signal_link_list
.end(), [&](std::pair
<SignalReference
, SignalReference
> link
)
104 return link
.second
== this_signal
;
105 }), _signal_link_list
.end());
107 this->linked_signals
.clear();
111 * Return all signal input references.
113 const std::list
<SignalReference
>& SignalProgram::GetSignalReferences() const
115 return this->linked_signals
;
119 * The number of signals linked to this signal program.
121 size_t SignalProgram::LinkCount() const
123 return this->linked_signals
.size();
127 * This function is run when one of the signals linked to this program has changed.
128 * It will (possibly) change the state of the signal, and then recursively change
129 * the state of any signal linked to it.
130 * @param depth The recursion depth, which starts at 1.
132 void SignalProgram::InputChanged(int depth
)
134 // If this signal is blocked by a train, we can't do anything.
135 if (this->blocked_by_train
) {
139 const SignalState new_state
= this->Evaluate();
141 if (new_state
!= DetermineSignalState(this->tile
, this->track
)) {
142 SetSignalStateForTrack(this->tile
, this->track
, new_state
);
143 MarkTileDirtyByTile(tile
);
145 // Recursively update any signals that have this one as input.
146 if (depth
< MAX_LOGIC_SIGNAL_RECURSIONS
) {
147 SignalStateChanged(this->tile
, this->track
, depth
+ 1);
153 * The main evaluation function which determines the state of the signal linked
154 * to this signal program. It should be short, simple and readable.
156 SignalState
SignalProgram::Evaluate()
158 int trigger_states
= 0, not_trigger_states
= 0;
160 // We need at least one linked signal to evaluate anything.
161 if (this->LinkCount() == 0) return this->own_default_state
;
163 // Loop through all linked signals and count the states.
164 for (auto reference
: this->linked_signals
) {
165 const TileIndex target_tile
= GetTileFromSignalReference(reference
);
166 const Track target_track
= GetTrackFromSignalReference(reference
);
167 const SignalState target_state
= DetermineSignalState(target_tile
, target_track
);
169 if (target_state
== trigger_state
) {
172 not_trigger_states
++;
176 switch (this->signal_op
) {
178 // OR is triggered if we have at least one signal of trigger color.
179 if (trigger_states
> 0) return OppositeSignalState(this->own_default_state
);
183 // AND is triggered if no signals were of the 'wrong' color.
184 if (not_trigger_states
== 0) return OppositeSignalState(this->own_default_state
);
188 // NAND is triggered if we have at least one signal of the 'wrong' color.
189 if (not_trigger_states
> 0) return OppositeSignalState(this->own_default_state
);
193 // XOR is triggered if the number of signals in trigger color is uneven.
194 if ((trigger_states
% 2) > 0) return OppositeSignalState(this->own_default_state
);
201 // Not triggered, return default color.
202 return this->own_default_state
;