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"
12 #include "viewport_func.h"
13 #include "overlay_cmd.h"
17 * Return the opposite of the given signal state.
18 * @param state The original signal state to revert.
19 * @return Green if the given argument is Red and vice versa.
21 static inline SignalState
OppositeSignalState(SignalState state
)
23 return state
== SIGNAL_STATE_RED
? SIGNAL_STATE_GREEN
: SIGNAL_STATE_RED
;
27 * The maximum number of signal programs which are evaluated in succession.
29 static const int MAX_LOGIC_SIGNAL_RECURSIONS
= 5;
32 * Default constructor, used by the save/load handler.
34 SignalProgram::SignalProgram()
40 * The constructor for creating a new signal program.
41 * @param t The tile where the logic signal for this program is located.
42 * @param tr The track where the logic signal for this program is located.
44 SignalProgram::SignalProgram(TileIndex t
, Track tr
)
49 /* Default to a priority signal: if any of the linked input
50 * signals are red, this one goes red. */
51 this->own_default_state
= SIGNAL_STATE_GREEN
;
52 this->trigger_state
= SIGNAL_STATE_RED
;
53 this->signal_op
= SIGNAL_OP_OR
;
55 this->blocked_by_train
= false;
59 * Add a new signal as input for this signal program.
60 * @param tile The tile of the signal to be linked.
61 * @param track The track of the signal to be linked.
63 void SignalProgram::AddLink(TileIndex tile
, Track track
, bool remove_first
)
65 SignalReference source
= GetSignalReference(tile
, track
);
67 if (!this->linked_signals
.Contains(source
)) {
68 /* Remove any existing link first, because we can only have one per signal (for now) */
69 if (remove_first
) RemoveSignalLink(tile
, track
);
71 *(this->linked_signals
.Append()) = source
;
72 _signal_link_list
[source
] = GetSignalReference(this->tile
, this->track
);
75 Overlays::Instance()->RefreshLogicSignalOverlay();
79 * Remove a linked signal from this program. The link must exist.
80 * @param tile The tile of the signal to be removed.
81 * @param track The track of the signal to be removed.
83 void SignalProgram::RemoveLink(TileIndex tile
, Track track
)
85 // Refresh BEFORE because we need to know what tiles to refresh before we remove the reference to them.
86 Overlays::Instance()->RefreshLogicSignalOverlay();
88 SignalReference key
= GetSignalReference(tile
, track
);
89 SignalReference
*value
= this->linked_signals
.Find(key
);
90 assert(value
!= this->linked_signals
.End());
91 this->linked_signals
.Erase(value
);
95 * Remove all links that this signal program has.
97 void SignalProgram::ClearAllLinks()
99 // Refresh BEFORE because we need to know what tiles to refresh before we remove the reference to them.
100 Overlays::Instance()->RefreshLogicSignalOverlay();
102 /* Delete all links from the global list too */
103 for (SignalReference
*sref
= this->linked_signals
.Begin(); sref
!= this->linked_signals
.End(); sref
++) {
104 _signal_link_list
.erase(*sref
);
107 this->linked_signals
.Clear();
110 * Return all signal input references.
112 const std::list
<SignalReference
> SignalProgram::GetSignalReferences() const
114 std::list
<SignalReference
> reference_list
;
116 for (const SignalReference
* ref
= this->linked_signals
.Begin(); ref
!= this->linked_signals
.End(); ++ref
) {
117 assert(ref
!= nullptr);
118 reference_list
.push_back(*ref
);
121 return reference_list
;
125 * The number of signals linked to this signal program.
127 uint
SignalProgram::LinkCount()
129 return this->linked_signals
.Length();
133 * This function is run when one of the signals linked to this program has changed.
134 * It will (possibly) change the state of the signal, and then recursively change
135 * the state of any signal linked to it.
136 * @param depth The recursion depth, which starts at 1.
138 void SignalProgram::InputChanged(int depth
)
140 /* If this signal is blocked by a train, we can't do anything */
141 if (this->blocked_by_train
) {
145 SignalState new_state
= this->Evaluate();
147 if (new_state
!= DetermineSignalState(this->tile
, this->track
)) {
148 SetSignalStateForTrack(this->tile
, this->track
, new_state
);
149 MarkTileDirtyByTile(tile
);
151 /* Recursively update any signals that have this one as input */
152 if (depth
< MAX_LOGIC_SIGNAL_RECURSIONS
) {
153 SignalStateChanged(this->tile
, this->track
, depth
+ 1);
159 * The main evaluation function which determines the state of the signal linked
160 * to this signal program. It should be short, simple and readable.
162 SignalState
SignalProgram::Evaluate()
164 int trigger_states
= 0, not_trigger_states
= 0;
166 /* We need at least one linked signal to evaluate anything */
167 if (this->LinkCount() == 0) return this->own_default_state
;
169 /* Loop through all linked signals and count the states */
170 for (SignalReference
*sref
= this->linked_signals
.Begin(); sref
!= this->linked_signals
.End(); sref
++) {
171 TileIndex target_tile
= GetTileFromSignalReference(*sref
);
172 Track target_track
= GetTrackFromSignalReference(*sref
);
173 SignalState target_state
= DetermineSignalState(target_tile
, target_track
);
175 if (target_state
== trigger_state
) {
178 not_trigger_states
++;
182 switch (this->signal_op
) {
184 /* OR is triggered if we have at least one signal of trigger color */
185 if (trigger_states
> 0) return OppositeSignalState(this->own_default_state
);
188 /* AND is triggered if no signals were of the 'wrong' color */
189 if (not_trigger_states
== 0) return OppositeSignalState(this->own_default_state
);
192 /* NAND is triggered if we have at least one signal of the 'wrong' color */
193 if (not_trigger_states
> 0) return OppositeSignalState(this->own_default_state
);
196 /* XOR is triggered if the number of signals in trigger color is uneven */
197 if ((trigger_states
% 2) > 0) return OppositeSignalState(this->own_default_state
);
201 /* Not triggered, return default color */
202 return this->own_default_state
;