Files
openttd/src/programmable_signals.h
Jonathan G Rennison 769b8ae096 progsig: Fix remove and clone program functions being completely broken.
Remove function only removed instructions from the local machine, and
was therefore not MP safe.
Clone function failed to work correctly for non-trivial cases,
and sometimes caused an array out of bounds assertion.

These are replaced by a new commandproc which does each operation
as a single action, which is therefore MP safe.

Remove an unused struct field.
2015-11-24 01:03:09 +00:00

401 lines
14 KiB
C++

/* $Id$ */
/*
* This file is part of OpenTTD.
* 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.
* 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.
* 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/>.
*/
/** @file programmable_signals.h Programmable Signals */
#ifndef PROGRAMMABLE_SIGNALS_H
#define PROGRAMMABLE_SIGNALS_H
#include "rail_map.h"
#include "core/smallvec_type.hpp"
#include <map>
/** @defgroup progsigs Programmable Signals */
///@{
/** The Programmable Signal virtual machine.
*
* This structure contains the state of the currently executing signal program.
*/
struct SignalVM;
class SignalInstruction;
class SignalSpecial;
typedef SmallVector<SignalInstruction*, 4> InstructionList;
enum SignalProgramMgmtCode {
SPMC_REMOVE, ///< Remove program
SPMC_CLONE, ///< Clone program
};
/** The actual programmable signal information */
struct SignalProgram {
SignalProgram(TileIndex tile, Track track, bool raw = false);
~SignalProgram();
void DebugPrintProgram();
TileIndex tile;
Track track;
SignalSpecial *first_instruction;
SignalSpecial *last_instruction;
InstructionList instructions;
};
/** Programmable Signal opcode.
*
* Opcode types are discriminated by this enumeration. It is primarily used for
* code which must be able to inspect the type of a signal operation, rather than
* evaluate it (such as the programming GUI)
*/
enum SignalOpcode {
PSO_FIRST = 0, ///< Start pseudo instruction
PSO_LAST = 1, ///< End pseudo instruction
PSO_IF = 2, ///< If instruction
PSO_IF_ELSE = 3, ///< If Else pseudo instruction
PSO_IF_ENDIF = 4, ///< If Endif pseudo instruction
PSO_SET_SIGNAL = 5, ///< Set signal instruction
PSO_END,
PSO_INVALID = 0xFF
};
template <> struct EnumPropsT<SignalOpcode> : MakeEnumPropsT<SignalOpcode, byte, PSO_FIRST, PSO_END, PSO_INVALID, 8> {};
/** Signal instruction base class. All instructions must derive from this. */
class SignalInstruction {
public:
/// Get the instruction's opcode
inline SignalOpcode Opcode() const { return this->opcode; }
/// Get the previous instruction. If this is NULL, then this is the first
/// instruction.
inline SignalInstruction *Previous() const { return this->previous; }
/// Get the Id of this instruction
inline int Id() const
// Const cast is safe (perculiarity of SmallVector)
{ return program->instructions.FindIndex(const_cast<SignalInstruction*>(this)); }
/// Insert this instruction, placing it before @p before_insn
virtual void Insert(SignalInstruction *before_insn);
/// Evaluate the instruction. The instruction should update the VM state.
virtual void Evaluate(SignalVM &vm) = 0;
/// Remove the instruction. When removing itself, an instruction should
/// <ul>
/// <li>Set next->previous to previous
/// <li>Set previous->next to next
/// <li>Destroy any other children
/// </ul>
virtual void Remove() = 0;
/// Gets a reference to the previous member. This is only intended for use by
/// the saveload code.
inline SignalInstruction *&GetPrevHandle()
{ return previous; }
/// Sets the previous instruction of this instruction. This is only intended
/// to be used by instructions to update links during insertion and removal.
inline void SetPrevious(SignalInstruction *prev)
{ previous = prev; }
/// Set the next instruction. This is only intended to be used by instructions
/// to update links during insertion and removal
virtual void SetNext(SignalInstruction *next_insn) = 0;
protected:
/// Constructs an instruction
/// @param prog the program to add this instruction to
/// @param op the opcode of the instruction
SignalInstruction(SignalProgram *prog, SignalOpcode op) ;
virtual ~SignalInstruction();
const SignalOpcode opcode;
SignalInstruction *previous;
SignalProgram *program;
};
/** Programmable Signal condition code.
*
* These discriminate conditions in much the same way that SignalOpcode
* discriminates instructions.
*/
enum SignalConditionCode {
PSC_ALWAYS = 0, ///< Always true
PSC_NEVER = 1, ///< Always false
PSC_NUM_GREEN = 2, ///< Number of green signals behind this signal
PSC_NUM_RED = 3, ///< Number of red signals behind this signal
PSC_SIGNAL_STATE = 4, ///< State of another signal
PSC_MAX = PSC_SIGNAL_STATE
};
class SignalCondition {
public:
/// Get the condition's code
inline SignalConditionCode ConditionCode() const { return this->cond_code; }
/// Evaluate the condition
virtual bool Evaluate(SignalVM& vm) = 0;
/// Destroy the condition. Any children should also be destroyed
virtual ~SignalCondition();
protected:
SignalCondition(SignalConditionCode code) : cond_code(code) {}
const SignalConditionCode cond_code;
};
// -- Condition codes --
/** Simple condition code. These conditions have no complex inputs, and can be
* evaluated directly from VM state and their condition code.
*/
class SignalSimpleCondition: public SignalCondition {
public:
SignalSimpleCondition(SignalConditionCode code);
virtual bool Evaluate(SignalVM& vm);
};
/** Comparator to use for variable conditions. */
enum SignalComparator {
SGC_EQUALS = 0, ///< the variable is equal to the specified value
SGC_NOT_EQUALS = 1, ///< the variable is not equal to the specified value
SGC_LESS_THAN = 2, ///< the variable is less than specified value
SGC_LESS_THAN_EQUALS = 3, ///< the variable is less than or equal to the specified value
SGC_MORE_THAN = 4, ///< the variable is greater than the specified value
SGC_MORE_THAN_EQUALS = 5, ///< the variable is grater than or equal to the specified value
SGC_IS_TRUE = 6, ///< the variable is true (non-zero)
SGC_IS_FALSE = 7, ///< the variable is false (zero)
SGC_LAST = SGC_IS_FALSE
};
/** Which field to modify in a condition. A parameter to CMD_MODIFY_SIGNAL_INSTRUCTION */
enum SignalConditionField {
SCF_COMPARATOR = 0, ///< the comparator (value from SignalComparator enum)
SCF_VALUE = 1, ///< the value (integer value)
};
/** A conditon based upon comparing a variable and a value. This condition can be
* considered similar to the conditonal jumps in vehicle orders.
*
* The variable is specified by the conditon code, the comparison by @p comparator, and
* the value to compare against by @p value. The condition returns the result of that value.
*/
class SignalVariableCondition: public SignalCondition {
public:
/// Constructs a condition refering to the value @p code refers to. Sets the
/// comparator and value to sane defaults.
SignalVariableCondition(SignalConditionCode code);
SignalComparator comparator;
uint32 value;
/// Evaluates the condition
virtual bool Evaluate(SignalVM &vm);
};
/** A condition which is based upon the state of another signal. */
class SignalStateCondition: public SignalCondition {
public:
SignalStateCondition(SignalReference this_sig, TileIndex sig_tile, Trackdir sig_track);
void SetSignal(TileIndex tile, Trackdir track);
bool IsSignalValid() const;
bool CheckSignalValid();
void Invalidate();
virtual bool Evaluate(SignalVM& vm);
virtual ~SignalStateCondition();
SignalReference this_sig;
TileIndex sig_tile;
Trackdir sig_track;
};
// -- Instructions
/** The special start and end pseudo instructions.
*
* These instructions serve two purposes:
* <ol>
* <li>They permit every other instruction to assume that there is another
* following it. This makes the code much simpler (and by extension less
* error prone)</li>
* <li>Particularly in the case of the End instruction, they provide an
* instruction in the user interface that can be clicked on to add
* instructions at the end of a program</li>
* </ol>
*/
class SignalSpecial: public SignalInstruction {
public:
/** Constructs a special signal of the opcode @p op in program @p prog.
*
* Generally you should not need to call this; it will be called by the
* program's constructor. An exception is in the saveload code, which needs
* to construct raw objects to deserialize into
*/
SignalSpecial(SignalProgram *prog, SignalOpcode op);
/** Evaluates the instruction. If this is an Start instruction, flow will be
* vectored to the first instruction; if it is an End instruction, the program
* will terminate and the signal will be left red.
*/
virtual void Evaluate(SignalVM &vm);
/** Links the first and last instructions in the program. Generally only to be
* called from the SignalProgram constructor.
*/
static void link(SignalSpecial *first, SignalSpecial *last);
/** Removes this instruction. If this is the start instruction, then all of
* the other instructions in the program will be successively removed,
* (emptying it). If this is the End instruction, then it will do nothing.
*
* This operation, unlike when executed on most instructions, does not destroy
* the instruction.
*/
virtual void Remove();
/** The next instruction after this one. On the End instruction, this should
* be NULL.
*/
SignalInstruction *next;
virtual void SetNext(SignalInstruction *next_insn);
};
/** If signal instruction. This is perhaps the most important, as without it,
* programmable signals are pretty useless.
*
* It's also the most complex!
*/
class SignalIf: public SignalInstruction {
public:
/** The If-Else and If-Endif pseudo instructions. The Else instruction
* follows the Then block, and the Endif instruction follows the Else block.
*
* These serve two purposes:
* <ul>
* <li>They correctly vector the execution to after the if block
* (if needed)
* <li>They provide an instruction for the GUI to insert other instructions
* before.
* </ul>
*/
class PseudoInstruction: public SignalInstruction {
public:
/** Normal constructor. The pseudo instruction will be constructed as
* belonging to @p block.
*/
PseudoInstruction(SignalProgram *prog, SignalIf *block, SignalOpcode op);
/** Constructs an empty instruction of type @p op. This should only be used
* by the saveload code during deserialization. The instruction must have
* its block field set correctly before the program is run.
*/
PseudoInstruction(SignalProgram *prog, SignalOpcode op);
/** Removes the pseudo instruction. Unless you are also removing the If it
* belongs to, this is nonsense and dangerous.
*/
virtual void Remove();
/** Evaluate the pseudo instruction. This involves vectoring execution to
* the instruction after the if.
*/
virtual void Evaluate(SignalVM &vm);
/** The block to which this instruction belongs */
SignalIf *block;
virtual void SetNext(SignalInstruction *next_insn);
};
public:
/** Constructs an If instruction belonging to program @p prog. If @p raw is
* true, then the instruction is constructed raw (in order for the
* deserializer to be able to correctly deserialize the instruction).
*/
SignalIf(SignalProgram *prog, bool raw = false);
/** Sets the instruction's condition, and releases the old condition */
void SetCondition(SignalCondition *cond);
/** Evaluates the If and takes the appropriate branch */
virtual void Evaluate(SignalVM &vm);
virtual void Insert(SignalInstruction *before_insn);
/** Removes the If and all of its children */
virtual void Remove();
SignalCondition *condition; ///< The if conditon
SignalInstruction *if_true; ///< The branch to take if true
SignalInstruction *if_false; ///< The branch to take if false
SignalInstruction *after; ///< The branch to take after the If
virtual void SetNext(SignalInstruction *next_insn);
};
/** Set signal instruction. This sets the state of the signal and terminates execution */
class SignalSet: public SignalInstruction {
public:
/// Constructs the instruction and sets the state the signal is to be set to
SignalSet(SignalProgram *prog, SignalState = SIGNAL_STATE_RED);
virtual void Evaluate(SignalVM &vm);
virtual void Remove();
/// The state to set the signal to
SignalState to_state;
/// The instruction following this one (for the editor)
SignalInstruction *next;
virtual void SetNext(SignalInstruction *next_insn);
};
/// The map type used for looking up signal programs
typedef std::map<SignalReference, SignalProgram*> ProgramList;
/// The global signal program list
extern ProgramList _signal_programs;
/// Verifies that a SignalReference refers to a signal which has a program.
static inline bool HasProgrammableSignals(SignalReference ref)
{
return IsTileType(ref.tile, MP_RAILWAY) && GetRailTileType(ref.tile) == RAIL_TILE_SIGNALS
&& IsPresignalProgrammable(ref.tile, ref.track);
}
/// Shows the programming window for the signal identified by @p tile and
/// @p track.
void ShowSignalProgramWindow(SignalReference ref);
/// Gets the signal program for the tile identified by @p t and @p track.
/// An empty program will be constructed if none is specified
SignalProgram *GetSignalProgram(SignalReference ref);
SignalProgram *GetExistingSignalProgram(SignalReference ref);
/// Frees a signal program by tile and track
void FreeSignalProgram(SignalReference ref);
/// Frees all signal programs (For use when creating a new game)
void FreeSignalPrograms();
/// Runs the signal program, specifying the following parameters.
SignalState RunSignalProgram(SignalReference ref, uint num_exits, uint num_green);
/// Remove dependencies on signal @p on from @p by
void RemoveProgramDependencies(SignalReference dependency_target, SignalReference signal_to_update);
///@}
#endif