Skip to content

Commit

Permalink
Merge pull request traintastic#115 from gfgit/work/gfgit/path_release
Browse files Browse the repository at this point in the history
Path reservation fixes
  • Loading branch information
reinder authored Aug 1, 2024
2 parents b551146 + e66f108 commit 99a3fb5
Show file tree
Hide file tree
Showing 24 changed files with 850 additions and 69 deletions.
91 changes: 69 additions & 22 deletions client/src/board/boardwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
#include <QApplication>
#include <QKeyEvent>
#include <QPainter>
#include <QTimer>
#include <traintastic/locale/locale.hpp>
#include "getboardcolorscheme.hpp"
#include "tilepainter.hpp"
Expand Down Expand Up @@ -92,6 +91,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
m_editActions{new QActionGroup(this)}
, m_tileMoveStarted{false}
, m_tileResizeStarted{false}
, m_nxButtonTimerId(0)
{
setWindowIcon(Theme::getIconForClassId(object->classId));
setFocusPolicy(Qt::StrongFocus);
Expand Down Expand Up @@ -447,6 +447,8 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :

BoardWidget::~BoardWidget()
{
stopTimerAndReleaseButtons();

if(m_nxManagerRequestId != Connection::invalidRequestId)
{
m_object->connection()->cancelRequest(m_nxManagerRequestId);
Expand All @@ -459,6 +461,10 @@ void BoardWidget::worldEditChanged(bool value)
m_editActionNone->activate(QAction::Trigger);
m_toolbarEdit->setVisible(value);
m_statusBar->setVisible(value);

// Stop timers in edit mode
if(value)
stopTimerAndReleaseButtons();
}

void BoardWidget::gridChanged(BoardAreaWidget::Grid value)
Expand Down Expand Up @@ -650,7 +656,8 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)

if(nxButton->isPressed())
{
releaseNXButton(nxButton);
stopTimerAndReleaseButtons();
return;
}
else
{
Expand All @@ -669,31 +676,13 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)

m_nxButtonPressed.reset();

QTimer::singleShot(nxButtonReleaseDelay, this,
[this, weak1=std::weak_ptr<NXButtonRailTile>(firstButton), weak2=std::weak_ptr<NXButtonRailTile>(nxButton)]()
{
if(auto btn = weak1.lock())
{
releaseNXButton(btn);
}
if(auto btn = weak2.lock())
{
releaseNXButton(btn);
}
});
startReleaseTimer(firstButton, nxButton);
}
else
{
m_nxButtonPressed = nxButton;

QTimer::singleShot(nxButtonHoldTime, this,
[this, weak=std::weak_ptr<NXButtonRailTile>(nxButton)]()
{
if(auto btn = weak.lock())
{
releaseNXButton(btn);
}
});
startHoldTimer(nxButton);
}
}
}
Expand Down Expand Up @@ -778,6 +767,53 @@ void BoardWidget::rightClicked()
rotateTile(true);
}

void BoardWidget::startHoldTimer(const std::shared_ptr<NXButtonRailTile>& nxButton)
{
stopTimerAndReleaseButtons();

m_releaseButton1 = nxButton;

assert(m_nxButtonTimerId == 0);
m_nxButtonTimerId = startTimer(nxButtonHoldTime);
}

void BoardWidget::startReleaseTimer(const std::shared_ptr<NXButtonRailTile> &firstButton,
const std::shared_ptr<NXButtonRailTile> &nxButton)
{
// Do not release first button yet
m_releaseButton1.reset();

stopTimerAndReleaseButtons();

m_releaseButton1 = firstButton;
m_releaseButton2 = nxButton;

assert(m_nxButtonTimerId == 0);
m_nxButtonTimerId = startTimer(nxButtonReleaseDelay);
}

void BoardWidget::stopTimerAndReleaseButtons()
{
if(m_nxButtonTimerId)
{
// Instantly release buttons
if(auto btn = m_releaseButton1.lock())
{
releaseNXButton(btn);
}
m_releaseButton1.reset();

if(auto btn = m_releaseButton2.lock())
{
releaseNXButton(btn);
}
m_releaseButton2.reset();

killTimer(m_nxButtonTimerId);
m_nxButtonTimerId = 0;
}
}

void BoardWidget::actionSelected(const Board::TileInfo* info)
{
m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::None);
Expand Down Expand Up @@ -825,6 +861,17 @@ void BoardWidget::keyPressEvent(QKeyEvent* event)
}
}

void BoardWidget::timerEvent(QTimerEvent *e)
{
if(e->timerId() == m_nxButtonTimerId)
{
stopTimerAndReleaseButtons();
return;
}

QWidget::timerEvent(e);
}

void BoardWidget::rotateTile(bool ccw)
{
if(m_tileRotates != 0)
Expand Down
14 changes: 14 additions & 0 deletions client/src/board/boardwidget.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,25 @@ class BoardWidget : public QWidget
TileRotate m_tileRotateLast = TileRotate::Deg0; //!< Last used tile rotate for add/move
std::weak_ptr<NXButtonRailTile> m_nxButtonPressed;

int m_nxButtonTimerId;
std::weak_ptr<NXButtonRailTile> m_releaseButton1;
std::weak_ptr<NXButtonRailTile> m_releaseButton2;

void startHoldTimer(const std::shared_ptr<NXButtonRailTile> &nxButton);
void startReleaseTimer(const std::shared_ptr<NXButtonRailTile> &firstButton,
const std::shared_ptr<NXButtonRailTile> &nxButton);

void actionSelected(const Board::TileInfo* tile);

void keyPressEvent(QKeyEvent* event) override;
void timerEvent(QTimerEvent *e) override;

void rotateTile(bool ccw = false);
void releaseNXButton(const std::shared_ptr<NXButtonRailTile>& nxButton);

protected slots:
void stopTimerAndReleaseButtons();

protected slots:
void worldEditChanged(bool value);
void gridChanged(BoardAreaWidget::Grid value);
Expand Down
2 changes: 2 additions & 0 deletions client/src/utils/enum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <traintastic/enum/decouplerstate.hpp>
#include <traintastic/enum/direction.hpp>
#include <traintastic/enum/directioncontrolstate.hpp>
#include <traintastic/enum/externaloutputchangeaction.hpp>
#include <traintastic/enum/lengthunit.hpp>
#include <traintastic/enum/loconetf9f28.hpp>
#include <traintastic/enum/loconetfastclock.hpp>
Expand Down Expand Up @@ -105,6 +106,7 @@ QString translateEnum(const QString& enumName, qint64 value)
TRANSLATE_ENUM(DecouplerState)
TRANSLATE_ENUM(Direction)
TRANSLATE_ENUM(DirectionControlState)
TRANSLATE_ENUM(ExternalOutputChangeAction)
TRANSLATE_ENUM(LengthUnit)
TRANSLATE_ENUM(LocoNetF9F28)
TRANSLATE_ENUM(LocoNetFastClock)
Expand Down
90 changes: 84 additions & 6 deletions server/src/board/map/blockpath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
#include "../tile/rail/turnout/turnoutrailtile.hpp"
#include "../tile/rail/linkrailtile.hpp"
#include "../tile/rail/nxbuttonrailtile.hpp"
#include "../../train/trainblockstatus.hpp"
#include "../../core/eventloop.hpp"
#include "../../core/objectproperty.tpp"
#include "../../enum/bridgepath.hpp"

Expand Down Expand Up @@ -296,9 +298,34 @@ BlockPath::BlockPath(BlockRailTile& block, BlockSide side)
: m_fromBlock{block}
, m_fromSide{side}
, m_toSide{static_cast<BlockSide>(-1)}
, m_delayReleaseTimer{EventLoop::ioContext}
, m_isReserved(false)
, m_delayedReleaseScheduled(false)
{
}

BlockPath::BlockPath(const BlockPath &other)
: Path(other)
, std::enable_shared_from_this<BlockPath>()
, m_fromBlock(other.m_fromBlock)
, m_fromSide(other.m_fromSide)
, m_toBlock(other.m_toBlock)
, m_toSide(other.m_toSide)
, m_tiles(other.m_tiles)
, m_turnouts(other.m_turnouts)
, m_directionControls(other.m_directionControls)
, m_crossings(other.m_crossings)
, m_bridges(other.m_bridges)
, m_signals(other.m_signals)
, m_nxButtonFrom(other.m_nxButtonFrom)
, m_nxButtonTo(other.m_nxButtonTo)
, m_delayReleaseTimer{EventLoop::ioContext}
, m_isReserved(false)
, m_delayedReleaseScheduled(false)
{

}

bool BlockPath::operator ==(const BlockPath& other) const noexcept
{
return
Expand Down Expand Up @@ -387,7 +414,7 @@ bool BlockPath::reserve(const std::shared_ptr<Train>& train, bool dryRun)
{
if(auto turnout = turnoutWeak.lock())
{
if(!turnout->reserve(position, dryRun))
if(!turnout->reserve(shared_from_this(), position, dryRun))
{
assert(dryRun);
return false;
Expand Down Expand Up @@ -489,6 +516,9 @@ bool BlockPath::reserve(const std::shared_ptr<Train>& train, bool dryRun)
}
}

if(!dryRun)
m_isReserved = true;

return true;
}

Expand All @@ -499,22 +529,47 @@ bool BlockPath::release(bool dryRun)
return false;
}

if(!dryRun)
m_delayReleaseTimer.cancel();

auto toBlock = m_toBlock.lock();
if(!toBlock) /*[[unlikely]]*/
return false;

BlockState fromState = m_fromBlock.state.value();
BlockState toState = toBlock->state.value();

if((fromState == BlockState::Occupied || fromState == BlockState::Unknown)
&& (toState == BlockState::Occupied || toState == BlockState::Unknown)
&& !m_fromBlock.trains.empty() && !toBlock->trains.empty())
{
// Check if train head is beyond toBlock while its end is still in fromBlock
const auto& status1 = fromSide() == BlockSide::A ? m_fromBlock.trains.front() : m_fromBlock.trains.back();
const auto& status2 = toSide() == BlockSide::A ? toBlock->trains.front() : toBlock->trains.back();

if(status1->train.value() == status2->train.value())
return false;
}

if(!m_fromBlock.release(m_fromSide, dryRun))
{
assert(dryRun);
return false;
}

if(auto toBlock = m_toBlock.lock()) /*[[likely]]*/
if(!dryRun && toBlock->state.value() == BlockState::Reserved)
{
if(!toBlock->release(m_toSide, dryRun))
if(toBlock->trains.size() == 1)
{
assert(dryRun);
return false;
//TODO: this bypasses some checks
toBlock->removeTrainInternal(toBlock->trains[0]);
//TODO: dryRun? what if it fails?
}
}
else

if(!toBlock->release(m_toSide, dryRun))
{
assert(dryRun);
return false;
}

Expand Down Expand Up @@ -597,5 +652,28 @@ bool BlockPath::release(bool dryRun)
}
}

if(!dryRun)
m_isReserved = false;

return true;
}

bool BlockPath::delayedRelease(uint16_t timeoutMillis)
{
if(m_delayedReleaseScheduled)
return false;

m_delayedReleaseScheduled = true;

m_delayReleaseTimer.expires_after(boost::asio::chrono::milliseconds(timeoutMillis));
m_delayReleaseTimer.async_wait([this](const boost::system::error_code& ec)
{
m_delayedReleaseScheduled = false;

if(ec)
return;

release();
});
return true;
}
12 changes: 12 additions & 0 deletions server/src/board/map/blockpath.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <array>
#include <vector>
#include <utility>
#include <boost/asio/steady_timer.hpp>
#include "../../enum/blockside.hpp"

class RailTile;
Expand Down Expand Up @@ -64,10 +65,15 @@ class BlockPath : public Path, public std::enable_shared_from_this<BlockPath>
std::weak_ptr<NXButtonRailTile> m_nxButtonFrom;
std::weak_ptr<NXButtonRailTile> m_nxButtonTo;

boost::asio::steady_timer m_delayReleaseTimer;
bool m_isReserved;
bool m_delayedReleaseScheduled;

public:
static std::vector<std::shared_ptr<BlockPath>> find(BlockRailTile& block);

BlockPath(BlockRailTile& block, BlockSide side);
BlockPath(const BlockPath& other);

bool operator ==(const BlockPath& other) const noexcept;

Expand Down Expand Up @@ -99,11 +105,17 @@ class BlockPath : public Path, public std::enable_shared_from_this<BlockPath>
return m_toSide;
}

inline bool isReserved() const
{
return m_isReserved;
}

std::shared_ptr<NXButtonRailTile> nxButtonFrom() const;
std::shared_ptr<NXButtonRailTile> nxButtonTo() const;

bool reserve(const std::shared_ptr<Train>& train, bool dryRun = false);
bool release(bool dryRun = false);
bool delayedRelease(uint16_t timeoutMillis);
};

#endif
9 changes: 9 additions & 0 deletions server/src/board/nx/nxmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ bool NXManager::selectPath(const NXButtonRailTile& from, const NXButtonRailTile&
{
LOG_DEBUG("Path found:", path->fromBlock().name.value(), "->", path->toBlock()->name.value());

if(path->isReserved())
{
// If user clicked an already reserved path we release it.
// TODO: make some logic to prevent releasing a path while train is inside it?
// Also releasing a path when we already set signal to "Proceed" is dangerous because train might
// have already started moving towards out path
return path->release();
}

if(from.block->trains.empty())
{
continue; // no train in from block
Expand Down
Loading

0 comments on commit 99a3fb5

Please sign in to comment.