From f79f016a894db866408dce71caa075ec876eb7ea Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Thu, 25 Apr 2024 12:37:08 +0200 Subject: [PATCH 01/21] test: added: Direction path reservation using NX and change direction state --- server/test/board/path.cpp | 98 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/server/test/board/path.cpp b/server/test/board/path.cpp index ac546d62..1c2015e0 100644 --- a/server/test/board/path.cpp +++ b/server/test/board/path.cpp @@ -29,6 +29,7 @@ #include "../../src/board/nx/nxmanager.hpp" #include "../../src/board/tile/rail/blockrailtile.hpp" #include "../../src/board/tile/rail/bridge90railtile.hpp" +#include "../../src/board/tile/rail/directioncontrolrailtile.hpp" #include "../../src/board/tile/rail/nxbuttonrailtile.hpp" #include "../../src/board/tile/rail/turnout/turnoutleft45railtile.hpp" #include "../../src/board/tile/rail/turnout/turnoutright45railtile.hpp" @@ -157,3 +158,100 @@ TEST_CASE("Board: Bridge path resevation using NX", "[board][board-path]") REQUIRE(train1.expired()); REQUIRE(train2.expired()); } + +TEST_CASE("Board: Direction path reservation using NX and change direction state", "[board][board-path]") +{ + auto world = World::create(); + std::weak_ptr worldWeak = world; + + // Board: + // +--------+ +--------+ + // | block1 |--(nx1)--(-)--(nx2)--| block2 | + // +--------+ +--------+ + std::weak_ptr boardWeak = world->boards->create(); + + REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg90, BlockRailTile::classId, false)); + REQUIRE(boardWeak.lock()->addTile(1, 0, TileRotate::Deg90, NXButtonRailTile::classId, false)); + REQUIRE(boardWeak.lock()->addTile(2, 0, TileRotate::Deg90, DirectionControlRailTile::classId, false)); + REQUIRE(boardWeak.lock()->addTile(3, 0, TileRotate::Deg90, NXButtonRailTile::classId, false)); + REQUIRE(boardWeak.lock()->addTile(4, 0, TileRotate::Deg90, BlockRailTile::classId, false)); + + std::weak_ptr block1 = std::dynamic_pointer_cast(boardWeak.lock()->getTile({0, 0})); + REQUIRE_FALSE(block1.expired()); + std::weak_ptr block2 = std::dynamic_pointer_cast(boardWeak.lock()->getTile({4, 0})); + REQUIRE_FALSE(block2.expired()); + + std::weak_ptr nx1 = std::dynamic_pointer_cast(boardWeak.lock()->getTile({1, 0})); + REQUIRE_FALSE(nx1.expired()); + std::weak_ptr nx2 = std::dynamic_pointer_cast(boardWeak.lock()->getTile({3, 0})); + REQUIRE_FALSE(nx2.expired()); + + std::weak_ptr directionControl = std::dynamic_pointer_cast(boardWeak.lock()->getTile({2, 0})); + REQUIRE_FALSE(directionControl.expired()); + REQUIRE(directionControl.lock()->state.value() == DirectionControlState::Both); + + // Change direction control states (no reservation, so all valuese are allowed): + REQUIRE(directionControl.lock()->setState(DirectionControlState::AtoB)); + REQUIRE(directionControl.lock()->state.value() == DirectionControlState::AtoB); + REQUIRE(directionControl.lock()->setState(DirectionControlState::BtoA)); + REQUIRE(directionControl.lock()->state.value() == DirectionControlState::BtoA); + REQUIRE(directionControl.lock()->setState(DirectionControlState::None)); + REQUIRE(directionControl.lock()->state.value() == DirectionControlState::None); + REQUIRE(directionControl.lock()->setState(DirectionControlState::Both)); + REQUIRE(directionControl.lock()->state.value() == DirectionControlState::Both); + + // Set block free: + REQUIRE(block2.lock()->state == BlockState::Unknown); + REQUIRE(block2.lock()->setStateFree()); + REQUIRE(block2.lock()->state == BlockState::Free); + + // Create train: + std::weak_ptr locomotive = world->railVehicles->create(Locomotive::classId); + std::weak_ptr train = world->trains->create(); + REQUIRE(train.lock()->vehicles->length == 0); + train.lock()->vehicles->add(locomotive.lock()); + REQUIRE(train.lock()->vehicles->length == 1); + + // Assign train to block 1: + block1.lock()->assignTrain(train.lock()); + REQUIRE(block1.lock()->state == BlockState::Reserved); + block1.lock()->flipTrain(); + + // Set world in RUN state (required for selecting paths using NX buttons): + world->run(); + + // Set path for train from block 1 to block 2: + world->nxManager->select(nx1.lock(), nx2.lock()); + REQUIRE(block2.lock()->state == BlockState::Reserved); + REQUIRE(block2.lock()->trains.size() == 1); + REQUIRE(block2.lock()->trains[0]->train.value() == train.lock()); + + // Change direction control state: + + // A -> B = allowed as that is the train direction. + REQUIRE(directionControl.lock()->setState(DirectionControlState::AtoB)); + REQUIRE(directionControl.lock()->state.value() == DirectionControlState::AtoB); + + // B -> A = not allowed as that is opposite of the the train direction. + REQUIRE_FALSE(directionControl.lock()->setState(DirectionControlState::BtoA)); + REQUIRE(directionControl.lock()->state.value() == DirectionControlState::AtoB); + + // None = not allowed when reserved. + REQUIRE_FALSE(directionControl.lock()->setState(DirectionControlState::None)); + REQUIRE(directionControl.lock()->state.value() == DirectionControlState::AtoB); + + // Both = always allowed. + REQUIRE(directionControl.lock()->setState(DirectionControlState::Both)); + REQUIRE(directionControl.lock()->state.value() == DirectionControlState::Both); + + world.reset(); + REQUIRE(worldWeak.expired()); + REQUIRE(boardWeak.expired()); + REQUIRE(block1.expired()); + REQUIRE(block2.expired()); + REQUIRE(nx1.expired()); + REQUIRE(nx2.expired()); + REQUIRE(directionControl.expired()); + REQUIRE(locomotive.expired()); + REQUIRE(train.expired()); +} From b63d071ddf0c798996cfe1d67882690006483714 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Mon, 22 Apr 2024 18:30:44 +0200 Subject: [PATCH 02/21] server: SignalRailTile fix external aspect change evaluation --- .../board/tile/rail/signal/signalrailtile.cpp | 2 +- .../tile/rail/turnout/turnoutrailtile.cpp | 21 ++++++++++++++++++- .../tile/rail/turnout/turnoutrailtile.hpp | 2 ++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/server/src/board/tile/rail/signal/signalrailtile.cpp b/server/src/board/tile/rail/signal/signalrailtile.cpp index bd779db5..edc63e65 100644 --- a/server/src/board/tile/rail/signal/signalrailtile.cpp +++ b/server/src/board/tile/rail/signal/signalrailtile.cpp @@ -203,7 +203,7 @@ void SignalRailTile::connectOutputMap() { outputMap->onOutputStateMatchFound.connect([this](SignalAspect value) { - bool changed = (value == aspect); + bool changed = (value != aspect); if(doSetAspect(value, true)) { // If we are in a signal path, re-evaluate our aspect diff --git a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp index 338215d4..d4caf7ed 100644 --- a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp +++ b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp @@ -33,7 +33,10 @@ TurnoutRailTile::TurnoutRailTile(World& world, std::string_view _id, TileId tile name{this, "name", std::string(_id), PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}, position{this, "position", TurnoutPosition::Unknown, PropertyFlags::ReadWrite | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}, outputMap{this, "output_map", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject | PropertyFlags::NoScript}, - setPosition{*this, "set_position", MethodFlags::ScriptCallable, [this](TurnoutPosition value) { return doSetPosition(value); }} + setPosition{*this, "set_position", MethodFlags::ScriptCallable, [this](TurnoutPosition value) + { + return doSetPosition(value); + }} { assert(isRailTurnout(tileId_)); @@ -60,6 +63,17 @@ bool TurnoutRailTile::reserve(TurnoutPosition turnoutPosition, bool dryRun) { return false; } + + const TurnoutPosition reservedPos = getReservedPosition(); + if(reservedPos != TurnoutPosition::Unknown && reservedPos != turnoutPosition) + { + // TODO: what if 2 path reserve same turnout for same position? + // Upon release one path it will make turnout free while it's still reserved by second path + + // Turnout is already reserved for another position + return false; + } + if(!dryRun) { if(!doSetPosition(turnoutPosition)) /*[[unlikely]]*/ @@ -95,6 +109,11 @@ void TurnoutRailTile::addToWorld() RailTile::addToWorld(); } +TurnoutPosition TurnoutRailTile::getReservedPosition() const +{ + return static_cast(RailTile::reservedState()); +} + void TurnoutRailTile::worldEvent(WorldState state, WorldEvent event) { RailTile::worldEvent(state, event); diff --git a/server/src/board/tile/rail/turnout/turnoutrailtile.hpp b/server/src/board/tile/rail/turnout/turnoutrailtile.hpp index 317c4291..e740663d 100644 --- a/server/src/board/tile/rail/turnout/turnoutrailtile.hpp +++ b/server/src/board/tile/rail/turnout/turnoutrailtile.hpp @@ -62,6 +62,8 @@ class TurnoutRailTile : public RailTile virtual bool reserve(TurnoutPosition turnoutPosition, bool dryRun = false); bool release(bool dryRun = false); + + TurnoutPosition getReservedPosition() const; }; #endif From dd55749777b0e1680230045f098ea43f597d3a91 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Fri, 19 Apr 2024 16:25:37 +0200 Subject: [PATCH 03/21] server: TurnoutRailTile lock position if reserved Also react to external output changes and revert to reserved position if sneeded. --- server/src/board/tile/rail/turnout/turnoutrailtile.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp index d4caf7ed..b5f0b53d 100644 --- a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp +++ b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp @@ -35,6 +35,9 @@ TurnoutRailTile::TurnoutRailTile(World& world, std::string_view _id, TileId tile outputMap{this, "output_map", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject | PropertyFlags::NoScript}, setPosition{*this, "set_position", MethodFlags::ScriptCallable, [this](TurnoutPosition value) { + TurnoutPosition reservedPosition = getReservedPosition(); + if(reservedPosition != TurnoutPosition::Unknown && reservedPosition != value) + return false; // Turnout is locked by reservation path return doSetPosition(value); }} { @@ -148,6 +151,11 @@ void TurnoutRailTile::connectOutputMap() outputMap->onOutputStateMatchFound.connect([this](TurnoutPosition pos) { doSetPosition(pos, true); + + // If turnout is inside a reserved path, force it to reserved position + TurnoutPosition reservedPosition = getReservedPosition(); + if(reservedPosition != TurnoutPosition::Unknown && reservedPosition != position.value()) + doSetPosition(reservedPosition, false); }); //TODO: disconnect somewhere? From c1480f15cb3945a080808603ddac9fbca1d01af2 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Fri, 19 Apr 2024 16:28:02 +0200 Subject: [PATCH 04/21] server: CrossRailTile clear state on path release --- server/src/board/tile/rail/crossrailtile.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/board/tile/rail/crossrailtile.cpp b/server/src/board/tile/rail/crossrailtile.cpp index e40d1952..4f218054 100644 --- a/server/src/board/tile/rail/crossrailtile.cpp +++ b/server/src/board/tile/rail/crossrailtile.cpp @@ -53,6 +53,7 @@ bool CrossRailTile::release(bool dryRun) if(!dryRun) { + m_crossState = CrossState::Unset; RailTile::release(); } return true; From 17a0a05fd8175424e2330aa9afb87109747ef301 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Fri, 19 Apr 2024 16:35:59 +0200 Subject: [PATCH 05/21] server: DirectionControlRailTile lock while in reserved path --- server/src/board/tile/rail/directioncontrolrailtile.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/board/tile/rail/directioncontrolrailtile.cpp b/server/src/board/tile/rail/directioncontrolrailtile.cpp index 3acbe6d1..74d74515 100644 --- a/server/src/board/tile/rail/directioncontrolrailtile.cpp +++ b/server/src/board/tile/rail/directioncontrolrailtile.cpp @@ -61,6 +61,8 @@ DirectionControlRailTile::DirectionControlRailTile(World& world, std::string_vie , setState{*this, "set_state", MethodFlags::ScriptCallable, [this](DirectionControlState newState) { + if(reservedState()) + return false; // Direction control is currently locked by reserved path const auto& states = setState.getVectorAttribute(AttributeName::Values); if(std::find(states.begin(), states.end(), newState) == states.end()) return false; From aa35bacfacdf0ac1ccf544f5717fc7f6456a2de8 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Mon, 22 Apr 2024 11:17:49 +0200 Subject: [PATCH 06/21] server: DirectionControlRailTile prevent double reservation - Allow setting to "Both" while reserved --- .../board/tile/rail/directioncontrolrailtile.cpp | 14 +++++++++++--- .../board/tile/rail/directioncontrolrailtile.hpp | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/server/src/board/tile/rail/directioncontrolrailtile.cpp b/server/src/board/tile/rail/directioncontrolrailtile.cpp index 74d74515..76ec9dd3 100644 --- a/server/src/board/tile/rail/directioncontrolrailtile.cpp +++ b/server/src/board/tile/rail/directioncontrolrailtile.cpp @@ -32,6 +32,7 @@ CREATE_IMPL(DirectionControlRailTile) DirectionControlRailTile::DirectionControlRailTile(World& world, std::string_view _id) : StraightRailTile(world, _id, TileId::RailDirectionControl) , m_node{*this, 2} + , m_reservedState(DirectionControlState::None) , name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly} , useNone{this, "use_none", true, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly, [this](const bool /*value*/) @@ -61,8 +62,14 @@ DirectionControlRailTile::DirectionControlRailTile(World& world, std::string_vie , setState{*this, "set_state", MethodFlags::ScriptCallable, [this](DirectionControlState newState) { - if(reservedState()) - return false; // Direction control is currently locked by reserved path + if(reservedState() && newState != m_reservedState) + { + // Direction control is currently locked by reserved path + // Allow setting to Both directions unless reserved direction is None + if(m_reservedState == DirectionControlState::None || newState != DirectionControlState::Both) + return false; + } + const auto& states = setState.getVectorAttribute(AttributeName::Values); if(std::find(states.begin(), states.end(), newState) == states.end()) return false; @@ -123,13 +130,14 @@ DirectionControlRailTile::DirectionControlRailTile(World& world, std::string_vie bool DirectionControlRailTile::reserve(DirectionControlState directionControlState, bool dryRun) { - if(state != directionControlState && state != DirectionControlState::Both) + if(reservedState() || (state != directionControlState && state != DirectionControlState::Both)) { return false; } if(!dryRun) { + m_reservedState = directionControlState; StraightRailTile::reserve(); } diff --git a/server/src/board/tile/rail/directioncontrolrailtile.hpp b/server/src/board/tile/rail/directioncontrolrailtile.hpp index 48b3ea56..63f43ced 100644 --- a/server/src/board/tile/rail/directioncontrolrailtile.hpp +++ b/server/src/board/tile/rail/directioncontrolrailtile.hpp @@ -36,6 +36,7 @@ class DirectionControlRailTile final : public StraightRailTile private: Node m_node; + DirectionControlState m_reservedState; void updateEnabled(); void updateStateValues(); From 40722ef433c008d6ab6525bb0307f3751cdd651b Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 11 Jun 2024 18:49:04 +0200 Subject: [PATCH 07/21] server: BlockRailTile always updateTrainMethodEnabled when adding train --- server/src/board/tile/rail/blockrailtile.cpp | 1 + server/src/train/traintracking.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/server/src/board/tile/rail/blockrailtile.cpp b/server/src/board/tile/rail/blockrailtile.cpp index 6ab7f4ff..64b99fb1 100644 --- a/server/src/board/tile/rail/blockrailtile.cpp +++ b/server/src/board/tile/rail/blockrailtile.cpp @@ -374,6 +374,7 @@ void BlockRailTile::identificationEvent(BlockInputMapItem& /*item*/, Identificat case IdentificationEventType::Present: //!< \todo assign train (if allowed and possible) trains.appendInternal(TrainBlockStatus::create(*this, std::string("#").append(std::to_string(identifier)), blockDirection)); + updateTrainMethodEnabled(); if(state == BlockState::Free || state == BlockState::Unknown) updateState(); break; diff --git a/server/src/train/traintracking.cpp b/server/src/train/traintracking.cpp index 544fd418..3e2baef9 100644 --- a/server/src/train/traintracking.cpp +++ b/server/src/train/traintracking.cpp @@ -29,6 +29,8 @@ void TrainTracking::reserve(const std::shared_ptr& train, const std::shared_ptr& block, BlockTrainDirection direction) { block->trains.appendInternal(TrainBlockStatus::create(*block, *train, direction)); + block->updateTrainMethodEnabled(); + train->fireBlockReserved(block, direction); block->fireTrainReserved(train, direction); } From bda6f4b495117f664829d63063b9b5c7a77e3bc5 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Fri, 19 Apr 2024 13:43:30 +0200 Subject: [PATCH 08/21] server: NXManager allow releasing a reserved path --- server/src/board/map/blockpath.cpp | 7 +++++++ server/src/board/map/blockpath.hpp | 6 ++++++ server/src/board/nx/nxmanager.cpp | 9 +++++++++ 3 files changed, 22 insertions(+) diff --git a/server/src/board/map/blockpath.cpp b/server/src/board/map/blockpath.cpp index d08b83a0..7dcc55cd 100644 --- a/server/src/board/map/blockpath.cpp +++ b/server/src/board/map/blockpath.cpp @@ -296,6 +296,7 @@ BlockPath::BlockPath(BlockRailTile& block, BlockSide side) : m_fromBlock{block} , m_fromSide{side} , m_toSide{static_cast(-1)} + , m_isReserved(false) { } @@ -489,6 +490,9 @@ bool BlockPath::reserve(const std::shared_ptr& train, bool dryRun) } } + if(!dryRun) + m_isReserved = true; + return true; } @@ -597,5 +601,8 @@ bool BlockPath::release(bool dryRun) } } + if(!dryRun) + m_isReserved = false; + return true; } diff --git a/server/src/board/map/blockpath.hpp b/server/src/board/map/blockpath.hpp index 6fa01afe..80982f68 100644 --- a/server/src/board/map/blockpath.hpp +++ b/server/src/board/map/blockpath.hpp @@ -63,6 +63,7 @@ class BlockPath : public Path, public std::enable_shared_from_this std::vector> m_signals; //!< signals in path std::weak_ptr m_nxButtonFrom; std::weak_ptr m_nxButtonTo; + bool m_isReserved; public: static std::vector> find(BlockRailTile& block); @@ -99,6 +100,11 @@ class BlockPath : public Path, public std::enable_shared_from_this return m_toSide; } + inline bool isReserved() const + { + return m_isReserved; + } + std::shared_ptr nxButtonFrom() const; std::shared_ptr nxButtonTo() const; diff --git a/server/src/board/nx/nxmanager.cpp b/server/src/board/nx/nxmanager.cpp index 341e4be2..979fa110 100644 --- a/server/src/board/nx/nxmanager.cpp +++ b/server/src/board/nx/nxmanager.cpp @@ -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 From 06a2d46ccc54b21085003dfd3c1ad284e965e182 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 11 Jun 2024 18:28:07 +0200 Subject: [PATCH 09/21] server: BlockPath remove block reservation on release TODO: this is HACKY and bypasses some logic --- server/src/board/map/blockpath.cpp | 11 +++ server/src/board/tile/rail/blockrailtile.cpp | 70 ++++++++++++-------- server/src/board/tile/rail/blockrailtile.hpp | 2 + 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/server/src/board/map/blockpath.cpp b/server/src/board/map/blockpath.cpp index 7dcc55cd..1a0c10bf 100644 --- a/server/src/board/map/blockpath.cpp +++ b/server/src/board/map/blockpath.cpp @@ -33,6 +33,7 @@ #include "../tile/rail/turnout/turnoutrailtile.hpp" #include "../tile/rail/linkrailtile.hpp" #include "../tile/rail/nxbuttonrailtile.hpp" +#include "../../train/trainblockstatus.hpp" #include "../../core/objectproperty.tpp" #include "../../enum/bridgepath.hpp" @@ -511,6 +512,16 @@ bool BlockPath::release(bool dryRun) if(auto toBlock = m_toBlock.lock()) /*[[likely]]*/ { + if(!dryRun && toBlock->state.value() == BlockState::Reserved) + { + if(toBlock->trains.size() == 1) + { + //TODO: this bypasses some checks + toBlock->removeTrainInternal(toBlock->trains[0]); + //TODO: dryRun? what if it fails? + } + } + if(!toBlock->release(m_toSide, dryRun)) { assert(dryRun); diff --git a/server/src/board/tile/rail/blockrailtile.cpp b/server/src/board/tile/rail/blockrailtile.cpp index 64b99fb1..22fdd79b 100644 --- a/server/src/board/tile/rail/blockrailtile.cpp +++ b/server/src/board/tile/rail/blockrailtile.cpp @@ -133,35 +133,7 @@ BlockRailTile::BlockRailTile(World& world, std::string_view _id) : throw LogMessageException(LogMessage::E3006_CANT_REMOVE_TRAIN_TRAIN_CAN_ONLY_BE_REMOVED_FROM_HEAD_OR_TAIL_BLOCK); } - status->destroy(); - status.reset(); - - updateTrainMethodEnabled(); - if(state == BlockState::Reserved) - updateState(); - Log::log(*this, LogMessage::N3002_REMOVED_TRAIN_X_FROM_BLOCK_X, oldTrain->name.value(), name.value()); - - if(oldTrain->blocks.empty()) - { - oldTrain->active = false; - } - - if(m_world.simulation) - { - for(const auto& item : *inputMap) - { - if(item->input && item->input->interface) - { - if(item->type == SensorType::OccupancyDetector) - item->input->simulateChange(item->invert.value() ? SimulateInputAction::SetTrue : SimulateInputAction::SetFalse); - else - assert(false); // not yet implemented - } - } - } - - oldTrain->fireBlockRemoved(shared_ptr()); - fireEvent(onTrainRemoved, oldTrain, self); + removeTrainInternal(status); } }} , flipTrain{*this, "flip_train", @@ -476,6 +448,46 @@ bool BlockRailTile::release(BlockSide side, bool dryRun) return true; } +bool BlockRailTile::removeTrainInternal(const std::shared_ptr &status) +{ + if(!status) + return false; + + const auto self = shared_ptr(); + const std::shared_ptr oldTrain = status->train.value(); + + status->destroy(); + + updateTrainMethodEnabled(); + if(state == BlockState::Reserved) + updateState(); + Log::log(*this, LogMessage::N3002_REMOVED_TRAIN_X_FROM_BLOCK_X, oldTrain->name.value(), name.value()); + + if(oldTrain->blocks.empty()) + { + oldTrain->active = false; + } + + if(m_world.simulation) + { + for(const auto& item : *inputMap) + { + if(item->input && item->input->interface) + { + if(item->type == SensorType::OccupancyDetector) + item->input->simulateChange(item->invert.value() ? SimulateInputAction::SetTrue : SimulateInputAction::SetFalse); + else + assert(false); // not yet implemented + } + } + } + + oldTrain->fireBlockRemoved(shared_ptr()); + fireEvent(onTrainRemoved, oldTrain, self); + + return true; +} + void BlockRailTile::updateState() { if(!inputMap->items.empty()) diff --git a/server/src/board/tile/rail/blockrailtile.hpp b/server/src/board/tile/rail/blockrailtile.hpp index 65788e4f..14984ca9 100644 --- a/server/src/board/tile/rail/blockrailtile.hpp +++ b/server/src/board/tile/rail/blockrailtile.hpp @@ -110,6 +110,8 @@ class BlockRailTile : public RailTile const std::shared_ptr getReservedPath(BlockSide side) const; bool reserve(const std::shared_ptr& blockPath, const std::shared_ptr& train, BlockSide side, bool dryRun = false); bool release(BlockSide side, bool dryRun = false); + + bool removeTrainInternal(const std::shared_ptr& status); }; #endif From 7315ed9fb332928e8989fa077ef75a30d49114da Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Fri, 19 Apr 2024 17:40:37 +0200 Subject: [PATCH 10/21] client: BoardWidget rework NXButton timer - Now release timer is stopped when pressing a third button which becomes first button of new pair. - Timer is also stopped when editing mode is enabled - Hold timer is stopped if same button is clicked again --- client/src/board/boardwidget.cpp | 87 ++++++++++++++++++++++++-------- client/src/board/boardwidget.hpp | 12 +++++ 2 files changed, 78 insertions(+), 21 deletions(-) diff --git a/client/src/board/boardwidget.cpp b/client/src/board/boardwidget.cpp index d24ea47a..a36daeeb 100644 --- a/client/src/board/boardwidget.cpp +++ b/client/src/board/boardwidget.cpp @@ -92,6 +92,7 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : m_editActions{new QActionGroup(this)} , m_tileMoveStarted{false} , m_tileResizeStarted{false} + , m_timer(nullptr) { setWindowIcon(Theme::getIconForClassId(object->classId)); setFocusPolicy(Qt::StrongFocus); @@ -447,6 +448,8 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : BoardWidget::~BoardWidget() { + stopTimerAndReleaseButtons(); + if(m_nxManagerRequestId != Connection::invalidRequestId) { m_object->connection()->cancelRequest(m_nxManagerRequestId); @@ -459,6 +462,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) @@ -650,7 +657,8 @@ void BoardWidget::tileClicked(int16_t x, int16_t y) if(nxButton->isPressed()) { - releaseNXButton(nxButton); + stopTimerAndReleaseButtons(); + return; } else { @@ -669,31 +677,13 @@ void BoardWidget::tileClicked(int16_t x, int16_t y) m_nxButtonPressed.reset(); - QTimer::singleShot(nxButtonReleaseDelay, this, - [this, weak1=std::weak_ptr(firstButton), weak2=std::weak_ptr(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(nxButton)]() - { - if(auto btn = weak.lock()) - { - releaseNXButton(btn); - } - }); + startHoldTimer(nxButton); } } } @@ -778,6 +768,61 @@ void BoardWidget::rightClicked() rotateTile(true); } +void BoardWidget::startHoldTimer(const std::shared_ptr& nxButton) +{ + stopTimerAndReleaseButtons(); + + m_releaseButton1 = nxButton; + + m_timer = new QTimer(this); + m_timer->setInterval(nxButtonHoldTime); + m_timer->setSingleShot(true); + + connect(m_timer, &QTimer::timeout, this, &BoardWidget::stopTimerAndReleaseButtons); + m_timer->start(); +} + +void BoardWidget::startReleaseTimer(const std::shared_ptr &firstButton, + const std::shared_ptr &nxButton) +{ + // Do not release first button yet + m_releaseButton1.reset(); + + stopTimerAndReleaseButtons(); + + m_releaseButton1 = firstButton; + m_releaseButton2 = nxButton; + + m_timer = new QTimer(this); + m_timer->setInterval(nxButtonReleaseDelay); + m_timer->setSingleShot(true); + + connect(m_timer, &QTimer::timeout, this, &BoardWidget::stopTimerAndReleaseButtons); + m_timer->start(); +} + +void BoardWidget::stopTimerAndReleaseButtons() +{ + if(m_timer) + { + // 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(); + + delete m_timer; + m_timer = nullptr; + } +} + void BoardWidget::actionSelected(const Board::TileInfo* info) { m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::None); diff --git a/client/src/board/boardwidget.hpp b/client/src/board/boardwidget.hpp index ff1c2eec..6767a1d7 100644 --- a/client/src/board/boardwidget.hpp +++ b/client/src/board/boardwidget.hpp @@ -36,6 +36,7 @@ class QStatusBar; class QLabel; struct TileInfo; class NXButtonRailTile; +class QTimer; class BoardWidget : public QWidget { @@ -78,11 +79,22 @@ class BoardWidget : public QWidget TileRotate m_tileRotateLast = TileRotate::Deg0; //!< Last used tile rotate for add/move std::weak_ptr m_nxButtonPressed; + QTimer *m_timer; + std::weak_ptr m_releaseButton1; + std::weak_ptr m_releaseButton2; + + void startHoldTimer(const std::shared_ptr &nxButton); + void startReleaseTimer(const std::shared_ptr &firstButton, + const std::shared_ptr &nxButton); + void actionSelected(const Board::TileInfo* tile); void keyPressEvent(QKeyEvent* event) override; void rotateTile(bool ccw = false); void releaseNXButton(const std::shared_ptr& nxButton); + protected slots: + void stopTimerAndReleaseButtons(); + protected slots: void worldEditChanged(bool value); void gridChanged(BoardAreaWidget::Grid value); From 7f8d16de0c31326584a9417a8051d3366390055e Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Fri, 19 Apr 2024 23:41:25 +0200 Subject: [PATCH 11/21] server: BlockPath prevent release while train is inside it --- server/src/board/map/blockpath.cpp | 42 +++++++++++++++++++----------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/server/src/board/map/blockpath.cpp b/server/src/board/map/blockpath.cpp index 1a0c10bf..2bf01424 100644 --- a/server/src/board/map/blockpath.cpp +++ b/server/src/board/map/blockpath.cpp @@ -504,32 +504,44 @@ bool BlockPath::release(bool dryRun) return false; } + 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(!dryRun && toBlock->state.value() == BlockState::Reserved) + if(toBlock->trains.size() == 1) { - if(toBlock->trains.size() == 1) - { - //TODO: this bypasses some checks - toBlock->removeTrainInternal(toBlock->trains[0]); - //TODO: dryRun? what if it fails? - } - } - - if(!toBlock->release(m_toSide, dryRun)) - { - 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; } From 96124b4419b99665dc544664ec4ec118957e2301 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 11 Jun 2024 18:40:19 +0200 Subject: [PATCH 12/21] server: BlockRailTile release path when train leaves --- server/src/board/tile/rail/blockrailtile.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/src/board/tile/rail/blockrailtile.cpp b/server/src/board/tile/rail/blockrailtile.cpp index 22fdd79b..e96c49df 100644 --- a/server/src/board/tile/rail/blockrailtile.cpp +++ b/server/src/board/tile/rail/blockrailtile.cpp @@ -317,6 +317,8 @@ void BlockRailTile::inputItemValueChanged(BlockInputMapItem& item) { const auto& blockStatus = trains.front(); + const auto blockTrainDirection = blockStatus->direction.value(); + // Train must be in at least two blocks, else we loose it. // Release tailing block of train only. (When using current detection not all wagons might consume power.) if(blockStatus->train && @@ -324,6 +326,21 @@ void BlockRailTile::inputItemValueChanged(BlockInputMapItem& item) blockStatus->train->blocks.back() == blockStatus) { TrainTracking::left(blockStatus); + + const auto pathA = m_reservedPaths[0].lock(); + const bool exitA = pathA && &pathA->fromBlock() == this; + + const auto pathB = m_reservedPaths[1].lock(); + const bool exitB = pathB && &pathB->fromBlock() == this; + + if(blockTrainDirection == BlockTrainDirection::TowardsA && exitA) + { + pathA->release(); + } + else if(blockTrainDirection == BlockTrainDirection::TowardsB && exitB) + { + pathB->release(); + } } } } From e28bf9f2f2778330f75511dc2e5c01b77b73b057 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Wed, 12 Jun 2024 13:16:40 +0200 Subject: [PATCH 13/21] BoardWidget: replace QTimer with QTimerEvent - This avoids heap allocation - Added asserts for timerId to be null before being created again --- client/src/board/boardwidget.cpp | 36 +++++++++++++++++--------------- client/src/board/boardwidget.hpp | 6 ++++-- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/client/src/board/boardwidget.cpp b/client/src/board/boardwidget.cpp index a36daeeb..055ca1a4 100644 --- a/client/src/board/boardwidget.cpp +++ b/client/src/board/boardwidget.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include "getboardcolorscheme.hpp" #include "tilepainter.hpp" @@ -92,7 +91,7 @@ BoardWidget::BoardWidget(std::shared_ptr object, QWidget* parent) : m_editActions{new QActionGroup(this)} , m_tileMoveStarted{false} , m_tileResizeStarted{false} - , m_timer(nullptr) + , m_nxButtonTimerId(0) { setWindowIcon(Theme::getIconForClassId(object->classId)); setFocusPolicy(Qt::StrongFocus); @@ -774,12 +773,8 @@ void BoardWidget::startHoldTimer(const std::shared_ptr& nxButt m_releaseButton1 = nxButton; - m_timer = new QTimer(this); - m_timer->setInterval(nxButtonHoldTime); - m_timer->setSingleShot(true); - - connect(m_timer, &QTimer::timeout, this, &BoardWidget::stopTimerAndReleaseButtons); - m_timer->start(); + assert(m_nxButtonTimerId == 0); + m_nxButtonTimerId = startTimer(nxButtonHoldTime); } void BoardWidget::startReleaseTimer(const std::shared_ptr &firstButton, @@ -793,17 +788,13 @@ void BoardWidget::startReleaseTimer(const std::shared_ptr &fir m_releaseButton1 = firstButton; m_releaseButton2 = nxButton; - m_timer = new QTimer(this); - m_timer->setInterval(nxButtonReleaseDelay); - m_timer->setSingleShot(true); - - connect(m_timer, &QTimer::timeout, this, &BoardWidget::stopTimerAndReleaseButtons); - m_timer->start(); + assert(m_nxButtonTimerId == 0); + m_nxButtonTimerId = startTimer(nxButtonReleaseDelay); } void BoardWidget::stopTimerAndReleaseButtons() { - if(m_timer) + if(m_nxButtonTimerId) { // Instantly release buttons if(auto btn = m_releaseButton1.lock()) @@ -818,8 +809,8 @@ void BoardWidget::stopTimerAndReleaseButtons() } m_releaseButton2.reset(); - delete m_timer; - m_timer = nullptr; + killTimer(m_nxButtonTimerId); + m_nxButtonTimerId = 0; } } @@ -870,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) diff --git a/client/src/board/boardwidget.hpp b/client/src/board/boardwidget.hpp index 6767a1d7..73e0b973 100644 --- a/client/src/board/boardwidget.hpp +++ b/client/src/board/boardwidget.hpp @@ -36,7 +36,6 @@ class QStatusBar; class QLabel; struct TileInfo; class NXButtonRailTile; -class QTimer; class BoardWidget : public QWidget { @@ -79,7 +78,7 @@ class BoardWidget : public QWidget TileRotate m_tileRotateLast = TileRotate::Deg0; //!< Last used tile rotate for add/move std::weak_ptr m_nxButtonPressed; - QTimer *m_timer; + int m_nxButtonTimerId; std::weak_ptr m_releaseButton1; std::weak_ptr m_releaseButton2; @@ -88,7 +87,10 @@ class BoardWidget : public QWidget const std::shared_ptr &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& nxButton); From 7bcffe3319ce25ee9af586d2cc7f26af56bc150f Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Mon, 22 Apr 2024 18:44:02 +0200 Subject: [PATCH 14/21] server: SignalRailTile implement basic retry count --- .../board/tile/rail/signal/signalrailtile.cpp | 16 +++++++++++++++- .../board/tile/rail/signal/signalrailtile.hpp | 5 +++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/server/src/board/tile/rail/signal/signalrailtile.cpp b/server/src/board/tile/rail/signal/signalrailtile.cpp index edc63e65..01b61530 100644 --- a/server/src/board/tile/rail/signal/signalrailtile.cpp +++ b/server/src/board/tile/rail/signal/signalrailtile.cpp @@ -79,6 +79,7 @@ std::optional SignalRailTile::getDefaultActionValue(SignalAsp SignalRailTile::SignalRailTile(World& world, std::string_view _id, TileId tileId_) : StraightRailTile(world, _id, tileId_), m_node{*this, 2}, + m_retryCount(0), name{this, "name", std::string(_id), PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}, requireReservation{this, "require_reservation", AutoYesNo::Auto, PropertyFlags::ReadWrite | PropertyFlags::Store}, aspect{this, "aspect", SignalAspect::Unknown, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}, @@ -210,7 +211,20 @@ void SignalRailTile::connectOutputMap() // This corrects accidental modifications of aspect done // by the user with an handset or command station. if(changed && m_signalPath) - evaluate(); + { + auto now = std::chrono::steady_clock::now(); + if((now - m_lastRetryStart) >= RETRY_DURATION) + { + m_lastRetryStart = now; + m_retryCount = 0; + } + + if(m_retryCount < MAX_RETRYCOUNT) + { + m_retryCount++; + evaluate(); + } + } } }); diff --git a/server/src/board/tile/rail/signal/signalrailtile.hpp b/server/src/board/tile/rail/signal/signalrailtile.hpp index 3b58d391..431a081f 100644 --- a/server/src/board/tile/rail/signal/signalrailtile.hpp +++ b/server/src/board/tile/rail/signal/signalrailtile.hpp @@ -23,6 +23,7 @@ #ifndef TRAINTASTIC_SERVER_BOARD_TILE_RAIL_SIGNAL_SIGNALRAILTILE_HPP #define TRAINTASTIC_SERVER_BOARD_TILE_RAIL_SIGNAL_SIGNALRAILTILE_HPP +#include #include "../straightrailtile.hpp" #include #include "../../../map/node.hpp" @@ -43,6 +44,10 @@ class SignalRailTile : public StraightRailTile Node m_node; std::unique_ptr m_signalPath; std::weak_ptr m_blockPath; + std::chrono::steady_clock::time_point m_lastRetryStart; + uint8_t m_retryCount; + static constexpr uint8_t MAX_RETRYCOUNT = 3; + static constexpr std::chrono::steady_clock::duration RETRY_DURATION = std::chrono::minutes(1); SignalRailTile(World& world, std::string_view _id, TileId tileId_); From a2687177a4dd3a00e9155226511167ae34b0b907 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Mon, 17 Jun 2024 13:44:29 +0200 Subject: [PATCH 15/21] server: TurnoutRailTile implement retry count - If position is externally changed while a path is reserve Turnout will try to reset it's position to reserved one If it fails and reaches maximum retry count it will stop trains in path. --- server/src/board/map/blockpath.cpp | 2 +- .../tile/rail/turnout/turnoutrailtile.cpp | 60 ++++++++++++++++--- .../tile/rail/turnout/turnoutrailtile.hpp | 11 +++- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/server/src/board/map/blockpath.cpp b/server/src/board/map/blockpath.cpp index 2bf01424..01d773b5 100644 --- a/server/src/board/map/blockpath.cpp +++ b/server/src/board/map/blockpath.cpp @@ -389,7 +389,7 @@ bool BlockPath::reserve(const std::shared_ptr& 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; diff --git a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp index b5f0b53d..abb121b2 100644 --- a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp +++ b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp @@ -26,6 +26,10 @@ #include "../../../../core/method.tpp" #include "../../../../world/world.hpp" #include "../../../../utils/displayname.hpp" +#include "../../../map/blockpath.hpp" +#include "../blockrailtile.hpp" +#include "../../../../train/trainblockstatus.hpp" +#include "../../../../train/train.hpp" TurnoutRailTile::TurnoutRailTile(World& world, std::string_view _id, TileId tileId_, size_t connectors) : RailTile(world, _id, tileId_), @@ -60,7 +64,7 @@ TurnoutRailTile::TurnoutRailTile(World& world, std::string_view _id, TileId tile // setPosition is added by sub class } -bool TurnoutRailTile::reserve(TurnoutPosition turnoutPosition, bool dryRun) +bool TurnoutRailTile::reserve(const std::shared_ptr &blockPath, TurnoutPosition turnoutPosition, bool dryRun) { if(!isValidPosition(turnoutPosition)) { @@ -84,6 +88,8 @@ bool TurnoutRailTile::reserve(TurnoutPosition turnoutPosition, bool dryRun) return false; } + m_reservedPath = blockPath; + RailTile::setReservedState(static_cast(turnoutPosition)); } return true; @@ -95,6 +101,8 @@ bool TurnoutRailTile::release(bool dryRun) if(!dryRun) { + m_reservedPath.reset(); + RailTile::release(); } return true; @@ -150,12 +158,50 @@ void TurnoutRailTile::connectOutputMap() { outputMap->onOutputStateMatchFound.connect([this](TurnoutPosition pos) { - doSetPosition(pos, true); - - // If turnout is inside a reserved path, force it to reserved position - TurnoutPosition reservedPosition = getReservedPosition(); - if(reservedPosition != TurnoutPosition::Unknown && reservedPosition != position.value()) - doSetPosition(reservedPosition, false); + bool changed = (pos != position); + if(doSetPosition(pos, true)) + { + // If turnout is inside a reserved path, force it to reserved position + // This corrects accidental modifications of position done + // by the user with an handset or command station. + TurnoutPosition reservedPosition = getReservedPosition(); + if(changed && reservedPosition != TurnoutPosition::Unknown && reservedPosition != position.value()) + { + auto now = std::chrono::steady_clock::now(); + if((now - m_lastRetryStart) >= RETRY_DURATION) + { + m_lastRetryStart = now; + m_retryCount = 0; + } + + if(m_retryCount < MAX_RETRYCOUNT) + { + // Try again + m_retryCount++; + doSetPosition(reservedPosition, false); + } + else + { + // We cannot lock this turnout. Stop all trains in this path + if(auto blockPath = m_reservedPath.lock()) + { + for(auto it : blockPath->fromBlock().trains) + { + it->train.value()->emergencyStop.setValue(true); + } + + auto toBlock = blockPath->toBlock(); + if(toBlock) + { + for(auto it : blockPath->toBlock()->trains) + { + it->train.value()->emergencyStop.setValue(true); + } + } + } + } + } + } }); //TODO: disconnect somewhere? diff --git a/server/src/board/tile/rail/turnout/turnoutrailtile.hpp b/server/src/board/tile/rail/turnout/turnoutrailtile.hpp index e740663d..4e82d018 100644 --- a/server/src/board/tile/rail/turnout/turnoutrailtile.hpp +++ b/server/src/board/tile/rail/turnout/turnoutrailtile.hpp @@ -23,6 +23,7 @@ #ifndef TRAINTASTIC_SERVER_BOARD_TILE_RAIL_TURNOUT_TURNOUTRAILTILE_HPP #define TRAINTASTIC_SERVER_BOARD_TILE_RAIL_TURNOUT_TURNOUTRAILTILE_HPP +#include #include "../railtile.hpp" #include "../../../map/node.hpp" #include "../../../../core/objectproperty.hpp" @@ -30,12 +31,20 @@ #include "../../../../enum/turnoutposition.hpp" #include "../../../../hardware/output/map/turnoutoutputmap.hpp" +class BlockPath; + class TurnoutRailTile : public RailTile { DEFAULT_ID("turnout") private: Node m_node; + std::weak_ptr m_reservedPath; + + std::chrono::steady_clock::time_point m_lastRetryStart; + uint8_t m_retryCount; + static constexpr uint8_t MAX_RETRYCOUNT = 3; + static constexpr std::chrono::steady_clock::duration RETRY_DURATION = std::chrono::minutes(1); protected: TurnoutRailTile(World& world, std::string_view _id, TileId tileId_, size_t connectors); @@ -60,7 +69,7 @@ class TurnoutRailTile : public RailTile std::optional> node() const final { return m_node; } std::optional> node() final { return m_node; } - virtual bool reserve(TurnoutPosition turnoutPosition, bool dryRun = false); + virtual bool reserve(const std::shared_ptr& blockPath, TurnoutPosition turnoutPosition, bool dryRun = false); bool release(bool dryRun = false); TurnoutPosition getReservedPosition() const; From 7590a82d39a11dcbfaac302b8ea46ca8147493e7 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Mon, 17 Jun 2024 13:45:28 +0200 Subject: [PATCH 16/21] server: SignalRailTile stop trains when retry is exceded - When locked and retry count is reached, stop trains in path - Evaluate only if a path is reserved --- .../board/tile/rail/signal/signalrailtile.cpp | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/server/src/board/tile/rail/signal/signalrailtile.cpp b/server/src/board/tile/rail/signal/signalrailtile.cpp index 01b61530..7ead814b 100644 --- a/server/src/board/tile/rail/signal/signalrailtile.cpp +++ b/server/src/board/tile/rail/signal/signalrailtile.cpp @@ -22,11 +22,15 @@ #include "signalrailtile.hpp" #include "../../../map/abstractsignalpath.hpp" +#include "../../../map/blockpath.hpp" #include "../../../../core/attributes.hpp" #include "../../../../core/method.tpp" #include "../../../../core/objectproperty.tpp" #include "../../../../world/getworld.hpp" #include "../../../../utils/displayname.hpp" +#include "../blockrailtile.hpp" +#include "../../../../train/trainblockstatus.hpp" +#include "../../../../train/train.hpp" std::optional SignalRailTile::getDefaultActionValue(SignalAspect signalAspect, OutputType outputType, size_t outputIndex) { @@ -210,7 +214,7 @@ void SignalRailTile::connectOutputMap() // If we are in a signal path, re-evaluate our aspect // This corrects accidental modifications of aspect done // by the user with an handset or command station. - if(changed && m_signalPath) + if(changed && m_signalPath && hasReservedPath()) { auto now = std::chrono::steady_clock::now(); if((now - m_lastRetryStart) >= RETRY_DURATION) @@ -224,6 +228,26 @@ void SignalRailTile::connectOutputMap() m_retryCount++; evaluate(); } + else + { + // We cannot lock this signal. Stop all trains in this path + if(auto blockPath = reservedPath()) + { + for(auto it : blockPath->fromBlock().trains) + { + it->train.value()->emergencyStop.setValue(true); + } + + auto toBlock = blockPath->toBlock(); + if(toBlock) + { + for(auto it : blockPath->toBlock()->trains) + { + it->train.value()->emergencyStop.setValue(true); + } + } + } + } } } }); From 76c545127512837393c2bc4da0aa406bc5a6f30a Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Mon, 17 Jun 2024 14:41:06 +0200 Subject: [PATCH 17/21] server: Add log warning when output is externally changed while locked --- server/src/board/tile/rail/signal/signalrailtile.cpp | 3 +++ server/src/board/tile/rail/turnout/turnoutrailtile.cpp | 3 +++ shared/src/traintastic/enum/logmessage.hpp | 1 + shared/translations/en-us.json | 4 ++++ shared/translations/it-it.json | 4 ++++ 5 files changed, 15 insertions(+) diff --git a/server/src/board/tile/rail/signal/signalrailtile.cpp b/server/src/board/tile/rail/signal/signalrailtile.cpp index 7ead814b..5cab3c3d 100644 --- a/server/src/board/tile/rail/signal/signalrailtile.cpp +++ b/server/src/board/tile/rail/signal/signalrailtile.cpp @@ -31,6 +31,7 @@ #include "../blockrailtile.hpp" #include "../../../../train/trainblockstatus.hpp" #include "../../../../train/train.hpp" +#include "../../../../log/log.hpp" std::optional SignalRailTile::getDefaultActionValue(SignalAspect signalAspect, OutputType outputType, size_t outputIndex) { @@ -230,6 +231,8 @@ void SignalRailTile::connectOutputMap() } else { + Log::log(id, LogMessage::W3003_LOCKED_OUTPUT_CHANGED); + // We cannot lock this signal. Stop all trains in this path if(auto blockPath = reservedPath()) { diff --git a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp index abb121b2..5d0c3231 100644 --- a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp +++ b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp @@ -30,6 +30,7 @@ #include "../blockrailtile.hpp" #include "../../../../train/trainblockstatus.hpp" #include "../../../../train/train.hpp" +#include "../../../../log/log.hpp" TurnoutRailTile::TurnoutRailTile(World& world, std::string_view _id, TileId tileId_, size_t connectors) : RailTile(world, _id, tileId_), @@ -182,6 +183,8 @@ void TurnoutRailTile::connectOutputMap() } else { + Log::log(id, LogMessage::W3003_LOCKED_OUTPUT_CHANGED); + // We cannot lock this turnout. Stop all trains in this path if(auto blockPath = m_reservedPath.lock()) { diff --git a/shared/src/traintastic/enum/logmessage.hpp b/shared/src/traintastic/enum/logmessage.hpp index 242cb79f..cb17d66e 100644 --- a/shared/src/traintastic/enum/logmessage.hpp +++ b/shared/src/traintastic/enum/logmessage.hpp @@ -149,6 +149,7 @@ enum class LogMessage : uint32_t W2020_DCCEXT_RCN213_IS_NOT_SUPPORTED = LogMessageOffset::warning + 2020, W3001_NX_BUTTON_CONNECTED_TO_TWO_BLOCKS = LogMessageOffset::warning + 3001, W3002_NX_BUTTON_NOT_CONNECTED_TO_ANY_BLOCK = LogMessageOffset::warning + 3002, + W3003_LOCKED_OUTPUT_CHANGED = LogMessageOffset::warning + 3003, W9001_EXECUTION_TOOK_X_US = LogMessageOffset::warning + 9001, W9999_X = LogMessageOffset::warning + 9999, diff --git a/shared/translations/en-us.json b/shared/translations/en-us.json index 4670fa25..6c15547c 100644 --- a/shared/translations/en-us.json +++ b/shared/translations/en-us.json @@ -5030,6 +5030,10 @@ "comment": "", "fuzzy": 0 }, + { + "term": "message:W3003", + "definition": "Position externally changed while locked in a path" + }, { "term": "auto_yes_no:auto", "definition": "Auto detect", diff --git a/shared/translations/it-it.json b/shared/translations/it-it.json index 6fbfcaff..fd4c482b 100644 --- a/shared/translations/it-it.json +++ b/shared/translations/it-it.json @@ -5030,6 +5030,10 @@ "comment": "", "fuzzy": 0 }, + { + "term": "message:W3003", + "definition": "Posizione modificata dall'esterno mentre era bloccato in un itinerario" + }, { "term": "auto_yes_no:auto", "definition": "Auto rileva", From 343a14ceb1d1d69f91075bf415d837289ae2cdeb Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Thu, 20 Jun 2024 15:45:24 +0200 Subject: [PATCH 18/21] server: Add better log messages to Signal and Turnouts - New messages when position/aspect is externally changed - New messages when position/aspect is corrected - New messages when train is stopped - Separate messages for Signals and Turnouts - Avoid stopping twice the same Train - Italian and English translations --- .../board/tile/rail/signal/signalrailtile.cpp | 16 +++++++++++--- .../tile/rail/turnout/turnoutrailtile.cpp | 12 +++++++++- shared/src/traintastic/enum/logmessage.hpp | 7 +++++- shared/translations/en-us.json | 22 ++++++++++++++++++- shared/translations/it-it.json | 22 ++++++++++++++++++- 5 files changed, 72 insertions(+), 7 deletions(-) diff --git a/server/src/board/tile/rail/signal/signalrailtile.cpp b/server/src/board/tile/rail/signal/signalrailtile.cpp index 5cab3c3d..a63178bd 100644 --- a/server/src/board/tile/rail/signal/signalrailtile.cpp +++ b/server/src/board/tile/rail/signal/signalrailtile.cpp @@ -217,6 +217,8 @@ void SignalRailTile::connectOutputMap() // by the user with an handset or command station. if(changed && m_signalPath && hasReservedPath()) { + Log::log(id, LogMessage::W3004_LOCKED_SIGNAL_CHANGED); + auto now = std::chrono::steady_clock::now(); if((now - m_lastRetryStart) >= RETRY_DURATION) { @@ -228,17 +230,21 @@ void SignalRailTile::connectOutputMap() { m_retryCount++; evaluate(); + + Log::log(id, LogMessage::N3004_SIGNAL_RESET_TO_RESERVED_ASPECT); } else { - Log::log(id, LogMessage::W3003_LOCKED_OUTPUT_CHANGED); - // We cannot lock this signal. Stop all trains in this path if(auto blockPath = reservedPath()) - { + { + std::vector> alreadyStoppedTrains; + for(auto it : blockPath->fromBlock().trains) { it->train.value()->emergencyStop.setValue(true); + alreadyStoppedTrains.push_back(it->train.value()); + Log::log(it->train->id, LogMessage::E3004_TRAIN_STOPPED_ON_SIGNAL_X_CHANGED, id.value()); } auto toBlock = blockPath->toBlock(); @@ -246,7 +252,11 @@ void SignalRailTile::connectOutputMap() { for(auto it : blockPath->toBlock()->trains) { + if(std::find(alreadyStoppedTrains.cbegin(), alreadyStoppedTrains.cend(), it->train.value()) != alreadyStoppedTrains.cend()) + continue; // Do not stop train twice + it->train.value()->emergencyStop.setValue(true); + Log::log(it->train->id, LogMessage::E3004_TRAIN_STOPPED_ON_SIGNAL_X_CHANGED, id.value()); } } } diff --git a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp index 5d0c3231..66941339 100644 --- a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp +++ b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp @@ -168,6 +168,8 @@ void TurnoutRailTile::connectOutputMap() TurnoutPosition reservedPosition = getReservedPosition(); if(changed && reservedPosition != TurnoutPosition::Unknown && reservedPosition != position.value()) { + Log::log(id, LogMessage::W3003_LOCKED_TURNOUT_CHANGED); + auto now = std::chrono::steady_clock::now(); if((now - m_lastRetryStart) >= RETRY_DURATION) { @@ -180,10 +182,12 @@ void TurnoutRailTile::connectOutputMap() // Try again m_retryCount++; doSetPosition(reservedPosition, false); + + Log::log(id, LogMessage::N3003_TURNOUT_RESET_TO_RESERVED_POSITION); } else { - Log::log(id, LogMessage::W3003_LOCKED_OUTPUT_CHANGED); + std::vector> alreadyStoppedTrains; // We cannot lock this turnout. Stop all trains in this path if(auto blockPath = m_reservedPath.lock()) @@ -191,6 +195,8 @@ void TurnoutRailTile::connectOutputMap() for(auto it : blockPath->fromBlock().trains) { it->train.value()->emergencyStop.setValue(true); + alreadyStoppedTrains.push_back(it->train.value()); + Log::log(it->train->id, LogMessage::E3003_TRAIN_STOPPED_ON_TURNOUT_X_CHANGED, id.value()); } auto toBlock = blockPath->toBlock(); @@ -198,7 +204,11 @@ void TurnoutRailTile::connectOutputMap() { for(auto it : blockPath->toBlock()->trains) { + if(std::find(alreadyStoppedTrains.cbegin(), alreadyStoppedTrains.cend(), it->train.value()) != alreadyStoppedTrains.cend()) + continue; // Do not stop train twice + it->train.value()->emergencyStop.setValue(true); + Log::log(it->train->id, LogMessage::E3003_TRAIN_STOPPED_ON_TURNOUT_X_CHANGED, id.value()); } } } diff --git a/shared/src/traintastic/enum/logmessage.hpp b/shared/src/traintastic/enum/logmessage.hpp index cb17d66e..ee981ec3 100644 --- a/shared/src/traintastic/enum/logmessage.hpp +++ b/shared/src/traintastic/enum/logmessage.hpp @@ -130,6 +130,8 @@ enum class LogMessage : uint32_t N2007_LISTEN_ONLY_MODE_DEACTIVATED = LogMessageOffset::notice + 2007, N3001_ASSIGNED_TRAIN_X_TO_BLOCK_X = LogMessageOffset::notice + 3001, N3002_REMOVED_TRAIN_X_FROM_BLOCK_X = LogMessageOffset::notice + 3002, + N3003_TURNOUT_RESET_TO_RESERVED_POSITION = LogMessageOffset::notice + 3003, + N3004_SIGNAL_RESET_TO_RESERVED_ASPECT = LogMessageOffset::notice + 3004, N9001_STARTING_SCRIPT = LogMessageOffset::notice + 9001, N9999_X = LogMessageOffset::notice + 9999, @@ -149,7 +151,8 @@ enum class LogMessage : uint32_t W2020_DCCEXT_RCN213_IS_NOT_SUPPORTED = LogMessageOffset::warning + 2020, W3001_NX_BUTTON_CONNECTED_TO_TWO_BLOCKS = LogMessageOffset::warning + 3001, W3002_NX_BUTTON_NOT_CONNECTED_TO_ANY_BLOCK = LogMessageOffset::warning + 3002, - W3003_LOCKED_OUTPUT_CHANGED = LogMessageOffset::warning + 3003, + W3003_LOCKED_TURNOUT_CHANGED = LogMessageOffset::warning + 3003, + W3004_LOCKED_SIGNAL_CHANGED = LogMessageOffset::warning + 3004, W9001_EXECUTION_TOOK_X_US = LogMessageOffset::warning + 9001, W9999_X = LogMessageOffset::warning + 9999, @@ -188,6 +191,8 @@ enum class LogMessage : uint32_t E2024_UNKNOWN_LOCOMOTIVE_MFX_UID_X = LogMessageOffset::error + 2024, E3001_CANT_DELETE_RAIL_VEHICLE_WHEN_IN_ACTIVE_TRAIN = LogMessageOffset::error + 3001, E3002_CANT_DELETE_ACTIVE_TRAIN = LogMessageOffset::error + 3002, + E3003_TRAIN_STOPPED_ON_TURNOUT_X_CHANGED = LogMessageOffset::error + 3003, + E3004_TRAIN_STOPPED_ON_SIGNAL_X_CHANGED = LogMessageOffset::error + 3004, E3005_CANT_REMOVE_TRAIN_TRAIN_MUST_BE_STOPPED_FIRST = LogMessageOffset::error + 3005, E3006_CANT_REMOVE_TRAIN_TRAIN_CAN_ONLY_BE_REMOVED_FROM_HEAD_OR_TAIL_BLOCK = LogMessageOffset::error + 3006, E9001_X_DURING_EXECUTION_OF_X_EVENT_HANDLER = LogMessageOffset::error + 9001, diff --git a/shared/translations/en-us.json b/shared/translations/en-us.json index 6c15547c..b3076392 100644 --- a/shared/translations/en-us.json +++ b/shared/translations/en-us.json @@ -2825,6 +2825,14 @@ "comment": "", "fuzzy": 0 }, + { + "term": "message:N3003", + "definition": "Turnout position was reset to reserved one" + }, + { + "term": "message:N3004", + "definition": "Signal aspect was reset to reserved one" + }, { "term": "message:N9001", "definition": "Starting script", @@ -4958,6 +4966,14 @@ "comment": "", "fuzzy": 0 }, + { + "term": "message:E3003", + "definition": "Train was emergency stopped due to turnout %1 position externally changed" + }, + { + "term": "message:E3004", + "definition": "Train was emergency stopped due to signal %1 aspect externally changed" + }, { "term": "message:C1014", "definition": "Invalid command", @@ -5032,7 +5048,11 @@ }, { "term": "message:W3003", - "definition": "Position externally changed while locked in a path" + "definition": "Turnout position externally changed while locked in a path" + }, + { + "term": "message:W3004", + "definition": "Signal aspect externally changed while locked in a path" }, { "term": "auto_yes_no:auto", diff --git a/shared/translations/it-it.json b/shared/translations/it-it.json index fd4c482b..18ae590e 100644 --- a/shared/translations/it-it.json +++ b/shared/translations/it-it.json @@ -2825,6 +2825,14 @@ "comment": "", "fuzzy": 0 }, + { + "term": "message:N3003", + "definition": "La posizione del deviatoio è stata riportata a quella riservata" + }, + { + "term": "message:N3004", + "definition": "L'aspetto del segnale è stato riportato a quello riservato" + }, { "term": "message:N9001", "definition": "Avvio script", @@ -4958,6 +4966,14 @@ "comment": "", "fuzzy": 0 }, + { + "term": "message:E3003", + "definition": "Il treno \u00e8 stato stoppato d'emergenza per la modifica esterna della posizione del deviatoio %1" + }, + { + "term": "message:E3004", + "definition": "Il treno \u00e8 stato stoppato d'emergenza per la modifica esterna dell'aspetto del segnale %1" + }, { "term": "message:C1014", "definition": "Comando non valido", @@ -5032,7 +5048,11 @@ }, { "term": "message:W3003", - "definition": "Posizione modificata dall'esterno mentre era bloccato in un itinerario" + "definition": "Posizione del deviatoio modificata dall'esterno mentre era bloccato in un itinerario" + }, + { + "term": "message:W3004", + "definition": "Aspetto del segnale modificato dall'esterno mentre era bloccato in un itinerario" }, { "term": "auto_yes_no:auto", From 4c6f80afa7994538aa3aa9ad43611982de255667 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Sat, 22 Jun 2024 13:20:30 +0200 Subject: [PATCH 19/21] server: World add new settings for external output change - Refactored code in TurnoutRailTile and SignalRailTile - Use early return pattern - Added Enlish and Italian translations - New Category::trains --- client/src/utils/enum.cpp | 2 + .../board/tile/rail/signal/signalrailtile.cpp | 112 ++++++++++------- .../tile/rail/turnout/turnoutrailtile.cpp | 115 +++++++++++------- server/src/utils/category.hpp | 1 + server/src/world/world.cpp | 12 ++ server/src/world/world.hpp | 4 + .../enum/externaloutputchangeaction.hpp | 53 ++++++++ shared/src/traintastic/enum/logmessage.hpp | 4 + shared/translations/en-us.json | 56 +++++++++ shared/translations/it-it.json | 58 ++++++++- 10 files changed, 332 insertions(+), 85 deletions(-) create mode 100644 shared/src/traintastic/enum/externaloutputchangeaction.hpp diff --git a/client/src/utils/enum.cpp b/client/src/utils/enum.cpp index 1c4d477a..afde06ba 100644 --- a/client/src/utils/enum.cpp +++ b/client/src/utils/enum.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -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) diff --git a/server/src/board/tile/rail/signal/signalrailtile.cpp b/server/src/board/tile/rail/signal/signalrailtile.cpp index a63178bd..5f789eab 100644 --- a/server/src/board/tile/rail/signal/signalrailtile.cpp +++ b/server/src/board/tile/rail/signal/signalrailtile.cpp @@ -210,58 +210,88 @@ void SignalRailTile::connectOutputMap() outputMap->onOutputStateMatchFound.connect([this](SignalAspect value) { bool changed = (value != aspect); - if(doSetAspect(value, true)) + if(!doSetAspect(value, true) || !changed) + return; // No change + + if(!m_signalPath || !hasReservedPath()) + return; // Not locked + + // If we are in a signal path, re-evaluate our aspect + // This corrects accidental modifications of aspect done + // by the user with an handset or command station. + Log::log(id, LogMessage::W3004_LOCKED_SIGNAL_CHANGED); + + if(m_world.correctOutputPosWhenLocked) + { + auto now = std::chrono::steady_clock::now(); + if((now - m_lastRetryStart) >= RETRY_DURATION) + { + // Reset retry count + m_lastRetryStart = now; + m_retryCount = 0; + } + + if(m_retryCount < MAX_RETRYCOUNT) + { + // Try to reset output to reseved state + m_retryCount++; + evaluate(); + + Log::log(id, LogMessage::N3004_SIGNAL_RESET_TO_RESERVED_ASPECT); + return; + } + } + + // We reached maximum retry count + // We cannot lock this signal. Take action. + switch (m_world.extOutputChangeAction.value()) + { + default: + case ExternalOutputChangeAction::DoNothing: + { + // Do nothing + break; + } + case ExternalOutputChangeAction::EmergencyStopTrain: { - // If we are in a signal path, re-evaluate our aspect - // This corrects accidental modifications of aspect done - // by the user with an handset or command station. - if(changed && m_signalPath && hasReservedPath()) + if(auto blockPath = reservedPath()) { - Log::log(id, LogMessage::W3004_LOCKED_SIGNAL_CHANGED); + std::vector> alreadyStoppedTrains; - auto now = std::chrono::steady_clock::now(); - if((now - m_lastRetryStart) >= RETRY_DURATION) + for(auto it : blockPath->fromBlock().trains) { - m_lastRetryStart = now; - m_retryCount = 0; + it->train.value()->emergencyStop.setValue(true); + alreadyStoppedTrains.push_back(it->train.value()); + Log::log(it->train->id, LogMessage::E3004_TRAIN_STOPPED_ON_SIGNAL_X_CHANGED, id.value()); } - if(m_retryCount < MAX_RETRYCOUNT) + auto toBlock = blockPath->toBlock(); + if(toBlock) { - m_retryCount++; - evaluate(); + for(auto it : blockPath->toBlock()->trains) + { + if(std::find(alreadyStoppedTrains.cbegin(), alreadyStoppedTrains.cend(), it->train.value()) != alreadyStoppedTrains.cend()) + continue; // Do not stop train twice - Log::log(id, LogMessage::N3004_SIGNAL_RESET_TO_RESERVED_ASPECT); - } - else - { - // We cannot lock this signal. Stop all trains in this path - if(auto blockPath = reservedPath()) - { - std::vector> alreadyStoppedTrains; - - for(auto it : blockPath->fromBlock().trains) - { - it->train.value()->emergencyStop.setValue(true); - alreadyStoppedTrains.push_back(it->train.value()); - Log::log(it->train->id, LogMessage::E3004_TRAIN_STOPPED_ON_SIGNAL_X_CHANGED, id.value()); - } - - auto toBlock = blockPath->toBlock(); - if(toBlock) - { - for(auto it : blockPath->toBlock()->trains) - { - if(std::find(alreadyStoppedTrains.cbegin(), alreadyStoppedTrains.cend(), it->train.value()) != alreadyStoppedTrains.cend()) - continue; // Do not stop train twice - - it->train.value()->emergencyStop.setValue(true); - Log::log(it->train->id, LogMessage::E3004_TRAIN_STOPPED_ON_SIGNAL_X_CHANGED, id.value()); - } - } + it->train.value()->emergencyStop.setValue(true); + Log::log(it->train->id, LogMessage::E3004_TRAIN_STOPPED_ON_SIGNAL_X_CHANGED, id.value()); } } } + break; + } + case ExternalOutputChangeAction::EmergencyStopWorld: + { + m_world.stop(); + Log::log(m_world, LogMessage::E3008_WORLD_STOPPED_ON_SIGNAL_X_CHANGED, id.value()); + break; + } + case ExternalOutputChangeAction::PowerOffWorld: + { + m_world.powerOff(); + Log::log(m_world, LogMessage::E3010_WORLD_POWER_OFF_ON_SIGNAL_X_CHANGED, id.value()); + break; + } } }); diff --git a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp index 66941339..389b0112 100644 --- a/server/src/board/tile/rail/turnout/turnoutrailtile.cpp +++ b/server/src/board/tile/rail/turnout/turnoutrailtile.cpp @@ -160,60 +160,89 @@ void TurnoutRailTile::connectOutputMap() outputMap->onOutputStateMatchFound.connect([this](TurnoutPosition pos) { bool changed = (pos != position); - if(doSetPosition(pos, true)) + if(!doSetPosition(pos, true) || !changed) + return; // No change + + TurnoutPosition reservedPosition = getReservedPosition(); + if(reservedPosition == TurnoutPosition::Unknown || reservedPosition == position.value()) + return; // Not locked + + // If turnout is inside a reserved path, force it to reserved position + // This corrects accidental modifications of position done + // by the user with an handset or command station. + Log::log(id, LogMessage::W3003_LOCKED_TURNOUT_CHANGED); + + if(m_world.correctOutputPosWhenLocked) { - // If turnout is inside a reserved path, force it to reserved position - // This corrects accidental modifications of position done - // by the user with an handset or command station. - TurnoutPosition reservedPosition = getReservedPosition(); - if(changed && reservedPosition != TurnoutPosition::Unknown && reservedPosition != position.value()) + auto now = std::chrono::steady_clock::now(); + if((now - m_lastRetryStart) >= RETRY_DURATION) { - Log::log(id, LogMessage::W3003_LOCKED_TURNOUT_CHANGED); + // Reset retry count + m_lastRetryStart = now; + m_retryCount = 0; + } - auto now = std::chrono::steady_clock::now(); - if((now - m_lastRetryStart) >= RETRY_DURATION) - { - m_lastRetryStart = now; - m_retryCount = 0; - } + if(m_retryCount < MAX_RETRYCOUNT) + { + // Try to reset output to reseved state + m_retryCount++; + doSetPosition(reservedPosition, false); - if(m_retryCount < MAX_RETRYCOUNT) - { - // Try again - m_retryCount++; - doSetPosition(reservedPosition, false); + Log::log(id, LogMessage::N3003_TURNOUT_RESET_TO_RESERVED_POSITION); + return; + } + } - Log::log(id, LogMessage::N3003_TURNOUT_RESET_TO_RESERVED_POSITION); - } - else + // We reached maximum retry count + // We cannot lock this turnout. Take action. + switch (m_world.extOutputChangeAction.value()) + { + default: + case ExternalOutputChangeAction::DoNothing: + { + // Do nothing + break; + } + case ExternalOutputChangeAction::EmergencyStopTrain: + { + if(auto blockPath = m_reservedPath.lock()) + { + std::vector> alreadyStoppedTrains; + + for(auto it : blockPath->fromBlock().trains) { - std::vector> alreadyStoppedTrains; + it->train.value()->emergencyStop.setValue(true); + alreadyStoppedTrains.push_back(it->train.value()); + Log::log(it->train->id, LogMessage::E3003_TRAIN_STOPPED_ON_TURNOUT_X_CHANGED, id.value()); + } - // We cannot lock this turnout. Stop all trains in this path - if(auto blockPath = m_reservedPath.lock()) + auto toBlock = blockPath->toBlock(); + if(toBlock) + { + for(auto it : blockPath->toBlock()->trains) { - for(auto it : blockPath->fromBlock().trains) - { - it->train.value()->emergencyStop.setValue(true); - alreadyStoppedTrains.push_back(it->train.value()); - Log::log(it->train->id, LogMessage::E3003_TRAIN_STOPPED_ON_TURNOUT_X_CHANGED, id.value()); - } - - auto toBlock = blockPath->toBlock(); - if(toBlock) - { - for(auto it : blockPath->toBlock()->trains) - { - if(std::find(alreadyStoppedTrains.cbegin(), alreadyStoppedTrains.cend(), it->train.value()) != alreadyStoppedTrains.cend()) - continue; // Do not stop train twice - - it->train.value()->emergencyStop.setValue(true); - Log::log(it->train->id, LogMessage::E3003_TRAIN_STOPPED_ON_TURNOUT_X_CHANGED, id.value()); - } - } + if(std::find(alreadyStoppedTrains.cbegin(), alreadyStoppedTrains.cend(), it->train.value()) != alreadyStoppedTrains.cend()) + continue; // Do not stop train twice + + it->train.value()->emergencyStop.setValue(true); + Log::log(it->train->id, LogMessage::E3003_TRAIN_STOPPED_ON_TURNOUT_X_CHANGED, id.value()); } } } + break; + } + case ExternalOutputChangeAction::EmergencyStopWorld: + { + m_world.stop(); + Log::log(m_world, LogMessage::E3007_WORLD_STOPPED_ON_TURNOUT_X_CHANGED, id.value()); + break; + } + case ExternalOutputChangeAction::PowerOffWorld: + { + m_world.powerOff(); + Log::log(m_world, LogMessage::E3009_WORLD_POWER_OFF_ON_TURNOUT_X_CHANGED, id.value()); + break; + } } }); diff --git a/server/src/utils/category.hpp b/server/src/utils/category.hpp index b462e174..6bbfdbad 100644 --- a/server/src/utils/category.hpp +++ b/server/src/utils/category.hpp @@ -32,6 +32,7 @@ namespace Category constexpr std::string_view info = "category:info"; constexpr std::string_view log = "category:log"; constexpr std::string_view network = "category:network"; + constexpr std::string_view trains = "category:trains"; } #endif diff --git a/server/src/world/world.cpp b/server/src/world/world.cpp index e200ed41..b7d8b5fd 100644 --- a/server/src/world/world.cpp +++ b/server/src/world/world.cpp @@ -67,6 +67,7 @@ #include "../train/trainlist.hpp" #include "../vehicle/rail/railvehiclelist.hpp" #include "../lua/scriptlist.hpp" +#include "../utils/category.hpp" using nlohmann::json; @@ -144,6 +145,8 @@ World::World(Private /*unused*/) : powerOnWhenLoaded = true; // can't run without power } }}, + correctOutputPosWhenLocked{this, "correct_output_pos_when_locked", true, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript}, + extOutputChangeAction{this, "ext_output_change_action", ExternalOutputChangeAction::EmergencyStopTrain, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript}, decoderControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, inputControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, outputControllers{this, "output_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, @@ -308,6 +311,15 @@ World::World(Private /*unused*/) : m_interfaceItems.add(powerOnWhenLoaded); m_interfaceItems.add(runWhenLoaded); + Attributes::addCategory(correctOutputPosWhenLocked, Category::trains); + Attributes::addEnabled(correctOutputPosWhenLocked, true); + m_interfaceItems.add(correctOutputPosWhenLocked); + + Attributes::addCategory(extOutputChangeAction, Category::trains); + Attributes::addEnabled(extOutputChangeAction, true); + Attributes::addValues(extOutputChangeAction, extOutputChangeActionValues); + m_interfaceItems.add(extOutputChangeAction); + Attributes::addObjectEditor(decoderControllers, false); m_interfaceItems.add(decoderControllers); Attributes::addObjectEditor(inputControllers, false); diff --git a/server/src/world/world.hpp b/server/src/world/world.hpp index d3ed3525..cf73e8ce 100644 --- a/server/src/world/world.hpp +++ b/server/src/world/world.hpp @@ -31,6 +31,7 @@ #include "../core/event.hpp" #include #include +#include #include #include "../enum/worldscale.hpp" #include "../status/status.hpp" @@ -103,6 +104,9 @@ class World : public Object Property powerOnWhenLoaded; Property runWhenLoaded; + Property correctOutputPosWhenLocked; + Property extOutputChangeAction; + ObjectProperty> decoderControllers; ObjectProperty> inputControllers; ObjectProperty> outputControllers; diff --git a/shared/src/traintastic/enum/externaloutputchangeaction.hpp b/shared/src/traintastic/enum/externaloutputchangeaction.hpp new file mode 100644 index 00000000..852b9faf --- /dev/null +++ b/shared/src/traintastic/enum/externaloutputchangeaction.hpp @@ -0,0 +1,53 @@ +/** + * shared/src/traintastic/enum/externaloutputchangeaction.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2024 Filippo Gentile + * + * This program 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; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_EXTERNALOUTPUTCHANGEACTION_HPP +#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_EXTERNALOUTPUTCHANGEACTION_HPP + +#include +#include +#include "enum.hpp" + +enum class ExternalOutputChangeAction : uint8_t +{ + DoNothing = 0, + EmergencyStopTrain = 1, + EmergencyStopWorld = 2, + PowerOffWorld = 3, +}; + +TRAINTASTIC_ENUM(ExternalOutputChangeAction, "ext_output_change_action", 4, +{ + {ExternalOutputChangeAction::DoNothing, "do_nothing"}, + {ExternalOutputChangeAction::EmergencyStopTrain, "estop_train"}, + {ExternalOutputChangeAction::EmergencyStopWorld, "estop_world"}, + {ExternalOutputChangeAction::PowerOffWorld, "poweroff_world"}, +}); + +inline constexpr std::array extOutputChangeActionValues{{ + ExternalOutputChangeAction::DoNothing, + ExternalOutputChangeAction::EmergencyStopTrain, + ExternalOutputChangeAction::EmergencyStopWorld, + ExternalOutputChangeAction::PowerOffWorld, +}}; + +#endif // TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_EXTERNALOUTPUTCHANGEACTION_HPP diff --git a/shared/src/traintastic/enum/logmessage.hpp b/shared/src/traintastic/enum/logmessage.hpp index ee981ec3..2d0e1142 100644 --- a/shared/src/traintastic/enum/logmessage.hpp +++ b/shared/src/traintastic/enum/logmessage.hpp @@ -195,6 +195,10 @@ enum class LogMessage : uint32_t E3004_TRAIN_STOPPED_ON_SIGNAL_X_CHANGED = LogMessageOffset::error + 3004, E3005_CANT_REMOVE_TRAIN_TRAIN_MUST_BE_STOPPED_FIRST = LogMessageOffset::error + 3005, E3006_CANT_REMOVE_TRAIN_TRAIN_CAN_ONLY_BE_REMOVED_FROM_HEAD_OR_TAIL_BLOCK = LogMessageOffset::error + 3006, + E3007_WORLD_STOPPED_ON_TURNOUT_X_CHANGED = LogMessageOffset::error + 3007, + E3008_WORLD_STOPPED_ON_SIGNAL_X_CHANGED = LogMessageOffset::error + 3008, + E3009_WORLD_POWER_OFF_ON_TURNOUT_X_CHANGED = LogMessageOffset::error + 3009, + E3010_WORLD_POWER_OFF_ON_SIGNAL_X_CHANGED = LogMessageOffset::error + 3010, E9001_X_DURING_EXECUTION_OF_X_EVENT_HANDLER = LogMessageOffset::error + 9001, E9999_X = LogMessageOffset::error + 9999, diff --git a/shared/translations/en-us.json b/shared/translations/en-us.json index b3076392..71d59dc3 100644 --- a/shared/translations/en-us.json +++ b/shared/translations/en-us.json @@ -242,6 +242,14 @@ "comment": "", "fuzzy": 0 }, + { + "term": "category:trains", + "definition": "Trains", + "context": "", + "term_plural": "", + "reference": "", + "comment": "" + }, { "term": "class_id:board_tile.misc.push_button", "definition": "Push button", @@ -953,6 +961,22 @@ "comment": "", "fuzzy": 0 }, + { + "term": "ext_output_change_action:do_nothing", + "definition": "Do nothing" + }, + { + "term": "ext_output_change_action:estop_train", + "definition": "Emergency stop trains in block path" + }, + { + "term": "ext_output_change_action:estop_world", + "definition": "Emergency stop World" + }, + { + "term": "ext_output_change_action:poweroff_world", + "definition": "Power off World" + }, { "term": "ecos_channel:ecos_detector", "definition": "ECoS Detector", @@ -4462,6 +4486,22 @@ "comment": "", "fuzzy": 0 }, + { + "term": "world:correct_output_pos_when_locked", + "definition": "Try to correct Ouput state when externally chaged while locked", + "context": "", + "term_plural": "", + "reference": "", + "comment": "" + }, + { + "term": "world:ext_output_change_action", + "definition": "External Output Change Action", + "context": "", + "term_plural": "", + "reference": "", + "comment": "" + }, { "term": "world_scale:custom", "definition": "Custom", @@ -4974,6 +5014,22 @@ "term": "message:E3004", "definition": "Train was emergency stopped due to signal %1 aspect externally changed" }, + { + "term": "message:E3007", + "definition": "World was emergency stopped due to turnout %1 position externally changed" + }, + { + "term": "message:E3008", + "definition": "World was emergency stopped due to signal %1 aspect externally changed" + }, + { + "term": "message:E3009", + "definition": "World was emergency powered off due to turnout %1 position externally changed" + }, + { + "term": "message:E3010", + "definition": "World was emergency powered off due to signal %1 aspect externally changed" + }, { "term": "message:C1014", "definition": "Invalid command", diff --git a/shared/translations/it-it.json b/shared/translations/it-it.json index 18ae590e..15a02bc0 100644 --- a/shared/translations/it-it.json +++ b/shared/translations/it-it.json @@ -242,6 +242,14 @@ "comment": "", "fuzzy": 0 }, + { + "term": "category:trains", + "definition": "Treni", + "context": "", + "term_plural": "", + "reference": "", + "comment": "" + }, { "term": "class_id:board_tile.misc.push_button", "definition": "Bottone", @@ -953,6 +961,22 @@ "comment": "", "fuzzy": 0 }, + { + "term": "ext_output_change_action:do_nothing", + "definition": "Non fare niente" + }, + { + "term": "ext_output_change_action:estop_train", + "definition": "Stop di emergenza per i treni nell'itinerario" + }, + { + "term": "ext_output_change_action:estop_world", + "definition": "Stop di emergenza Mondo" + }, + { + "term": "ext_output_change_action:poweroff_world", + "definition": "Spegni Mondo" + }, { "term": "ecos_channel:ecos_detector", "definition": "ECoS Detector", @@ -4462,6 +4486,22 @@ "comment": "", "fuzzy": 0 }, + { + "term": "world:correct_output_pos_when_locked", + "definition": "Prova a correggere lo stato di un output modificato esternamente mentre bloccato", + "context": "", + "term_plural": "", + "reference": "", + "comment": "" + }, + { + "term": "world:ext_output_change_action", + "definition": "Azione per Modifica Esterna di un Output", + "context": "", + "term_plural": "", + "reference": "", + "comment": "" + }, { "term": "world_scale:custom", "definition": "Custom", @@ -4974,6 +5014,22 @@ "term": "message:E3004", "definition": "Il treno \u00e8 stato stoppato d'emergenza per la modifica esterna dell'aspetto del segnale %1" }, + { + "term": "message:E3007", + "definition": "Il mondo \u00e8 stato stoppato d'emergenza per la modifica esterna della posizione del deviatoio %1" + }, + { + "term": "message:E3008", + "definition": "Il mondo \u00e8 stato stoppato d'emergenza per la modifica esterna dell'aspetto del segnale %1" + }, + { + "term": "message:E3009", + "definition": "Il mondo \u00e8 stato spento per la modifica esterna della posizione del deviatoio %1" + }, + { + "term": "message:E3010", + "definition": "Il mondo \u00e8 stato spento per la modifica esterna dell'aspetto del segnale %1" + }, { "term": "message:C1014", "definition": "Comando non valido", @@ -6332,4 +6388,4 @@ "comment": "", "fuzzy": 0 } -] \ No newline at end of file +] From 7510a10ef4799bbc18f913472a612da3e956a228 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Mon, 24 Jun 2024 13:12:50 +0200 Subject: [PATCH 20/21] BlockPath: implement delayed release This simulates train still occupying turnouts while it has already left the entrance ("from") block. Useful when turnouts do not have dedicate occupancy detectors. - New World property - English and Italian translations --- server/src/board/map/blockpath.cpp | 48 ++++++++++++++++++++ server/src/board/map/blockpath.hpp | 6 +++ server/src/board/tile/rail/blockrailtile.cpp | 4 +- server/src/world/world.cpp | 6 +++ server/src/world/world.hpp | 1 + shared/translations/en-us.json | 8 ++++ shared/translations/it-it.json | 8 ++++ 7 files changed, 79 insertions(+), 2 deletions(-) diff --git a/server/src/board/map/blockpath.cpp b/server/src/board/map/blockpath.cpp index 01d773b5..29115621 100644 --- a/server/src/board/map/blockpath.cpp +++ b/server/src/board/map/blockpath.cpp @@ -34,6 +34,7 @@ #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" @@ -297,10 +298,34 @@ BlockPath::BlockPath(BlockRailTile& block, BlockSide side) : m_fromBlock{block} , m_fromSide{side} , m_toSide{static_cast(-1)} + , m_delayReleaseTimer{EventLoop::ioContext} , m_isReserved(false) + , m_delayedReleaseScheduled(false) { } +BlockPath::BlockPath(const BlockPath &other) + : Path(other) + , std::enable_shared_from_this() + , 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 @@ -504,6 +529,9 @@ bool BlockPath::release(bool dryRun) return false; } + if(!dryRun) + m_delayReleaseTimer.cancel(); + auto toBlock = m_toBlock.lock(); if(!toBlock) /*[[unlikely]]*/ return false; @@ -629,3 +657,23 @@ bool BlockPath::release(bool dryRun) 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; +} diff --git a/server/src/board/map/blockpath.hpp b/server/src/board/map/blockpath.hpp index 80982f68..623e9451 100644 --- a/server/src/board/map/blockpath.hpp +++ b/server/src/board/map/blockpath.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "../../enum/blockside.hpp" class RailTile; @@ -63,12 +64,16 @@ class BlockPath : public Path, public std::enable_shared_from_this std::vector> m_signals; //!< signals in path std::weak_ptr m_nxButtonFrom; std::weak_ptr m_nxButtonTo; + + boost::asio::steady_timer m_delayReleaseTimer; bool m_isReserved; + bool m_delayedReleaseScheduled; public: static std::vector> find(BlockRailTile& block); BlockPath(BlockRailTile& block, BlockSide side); + BlockPath(const BlockPath& other); bool operator ==(const BlockPath& other) const noexcept; @@ -110,6 +115,7 @@ class BlockPath : public Path, public std::enable_shared_from_this bool reserve(const std::shared_ptr& train, bool dryRun = false); bool release(bool dryRun = false); + bool delayedRelease(uint16_t timeoutMillis); }; #endif diff --git a/server/src/board/tile/rail/blockrailtile.cpp b/server/src/board/tile/rail/blockrailtile.cpp index e96c49df..a400462c 100644 --- a/server/src/board/tile/rail/blockrailtile.cpp +++ b/server/src/board/tile/rail/blockrailtile.cpp @@ -335,11 +335,11 @@ void BlockRailTile::inputItemValueChanged(BlockInputMapItem& item) if(blockTrainDirection == BlockTrainDirection::TowardsA && exitA) { - pathA->release(); + pathA->delayedRelease(m_world.pathReleaseDelay); } else if(blockTrainDirection == BlockTrainDirection::TowardsB && exitB) { - pathB->release(); + pathB->delayedRelease(m_world.pathReleaseDelay); } } } diff --git a/server/src/world/world.cpp b/server/src/world/world.cpp index b7d8b5fd..2dc71297 100644 --- a/server/src/world/world.cpp +++ b/server/src/world/world.cpp @@ -147,6 +147,7 @@ World::World(Private /*unused*/) : }}, correctOutputPosWhenLocked{this, "correct_output_pos_when_locked", true, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript}, extOutputChangeAction{this, "ext_output_change_action", ExternalOutputChangeAction::EmergencyStopTrain, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript}, + pathReleaseDelay{this, "path_release_delay", 5000, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript}, decoderControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, inputControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, outputControllers{this, "output_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, @@ -320,6 +321,11 @@ World::World(Private /*unused*/) : Attributes::addValues(extOutputChangeAction, extOutputChangeActionValues); m_interfaceItems.add(extOutputChangeAction); + Attributes::addCategory(pathReleaseDelay, Category::trains); + Attributes::addEnabled(pathReleaseDelay, true); + Attributes::addMinMax(pathReleaseDelay, {0, 15000}); // Up to 15 seconds + m_interfaceItems.add(pathReleaseDelay); + Attributes::addObjectEditor(decoderControllers, false); m_interfaceItems.add(decoderControllers); Attributes::addObjectEditor(inputControllers, false); diff --git a/server/src/world/world.hpp b/server/src/world/world.hpp index cf73e8ce..d2dbdad9 100644 --- a/server/src/world/world.hpp +++ b/server/src/world/world.hpp @@ -106,6 +106,7 @@ class World : public Object Property correctOutputPosWhenLocked; Property extOutputChangeAction; + Property pathReleaseDelay; ObjectProperty> decoderControllers; ObjectProperty> inputControllers; diff --git a/shared/translations/en-us.json b/shared/translations/en-us.json index 71d59dc3..c34cdf44 100644 --- a/shared/translations/en-us.json +++ b/shared/translations/en-us.json @@ -4502,6 +4502,14 @@ "reference": "", "comment": "" }, + { + "term": "world:path_release_delay", + "definition": "Block Path release delay", + "context": "", + "term_plural": "", + "reference": "", + "comment": "" + }, { "term": "world_scale:custom", "definition": "Custom", diff --git a/shared/translations/it-it.json b/shared/translations/it-it.json index 15a02bc0..a733b734 100644 --- a/shared/translations/it-it.json +++ b/shared/translations/it-it.json @@ -4502,6 +4502,14 @@ "reference": "", "comment": "" }, + { + "term": "world:path_release_delay", + "definition": "Ritardo nella liberazione dell'itinerario", + "context": "", + "term_plural": "", + "reference": "", + "comment": "" + }, { "term": "world_scale:custom", "definition": "Custom", From e66f1083dfc9ed5e3d7bf407b89bdf6ab2931bd7 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Wed, 3 Jul 2024 14:39:40 +0200 Subject: [PATCH 21/21] server: BlockRailTile move path release to TrainTracking - This effectively reverts commit 0b22d1f7 - Logic from commit 0b22d1f7 is now in TrainTracking - This fixes case when short train releases "enter" block before occupying "exit" block. --- server/src/board/tile/rail/blockrailtile.cpp | 17 ----------------- server/src/train/traintracking.cpp | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/server/src/board/tile/rail/blockrailtile.cpp b/server/src/board/tile/rail/blockrailtile.cpp index a400462c..22fdd79b 100644 --- a/server/src/board/tile/rail/blockrailtile.cpp +++ b/server/src/board/tile/rail/blockrailtile.cpp @@ -317,8 +317,6 @@ void BlockRailTile::inputItemValueChanged(BlockInputMapItem& item) { const auto& blockStatus = trains.front(); - const auto blockTrainDirection = blockStatus->direction.value(); - // Train must be in at least two blocks, else we loose it. // Release tailing block of train only. (When using current detection not all wagons might consume power.) if(blockStatus->train && @@ -326,21 +324,6 @@ void BlockRailTile::inputItemValueChanged(BlockInputMapItem& item) blockStatus->train->blocks.back() == blockStatus) { TrainTracking::left(blockStatus); - - const auto pathA = m_reservedPaths[0].lock(); - const bool exitA = pathA && &pathA->fromBlock() == this; - - const auto pathB = m_reservedPaths[1].lock(); - const bool exitB = pathB && &pathB->fromBlock() == this; - - if(blockTrainDirection == BlockTrainDirection::TowardsA && exitA) - { - pathA->delayedRelease(m_world.pathReleaseDelay); - } - else if(blockTrainDirection == BlockTrainDirection::TowardsB && exitB) - { - pathB->delayedRelease(m_world.pathReleaseDelay); - } } } } diff --git a/server/src/train/traintracking.cpp b/server/src/train/traintracking.cpp index 3e2baef9..b3f10693 100644 --- a/server/src/train/traintracking.cpp +++ b/server/src/train/traintracking.cpp @@ -24,7 +24,9 @@ #include "train.hpp" #include "trainblockstatus.hpp" #include "../board/tile/rail/blockrailtile.hpp" +#include "../board/map/blockpath.hpp" #include "../core/objectproperty.tpp" +#include "../world/world.hpp" void TrainTracking::reserve(const std::shared_ptr& train, const std::shared_ptr& block, BlockTrainDirection direction) { @@ -90,6 +92,21 @@ void TrainTracking::left(std::shared_ptr status) train->fireBlockLeft(block, direction); block->fireTrainLeft(train, direction); + const auto pathA = block->getReservedPath(BlockSide::A); + const bool exitA = pathA && &pathA->fromBlock() == block.get(); + + const auto pathB = block->getReservedPath(BlockSide::B); + const bool exitB = pathB && &pathB->fromBlock() == block.get(); + + if(direction == BlockTrainDirection::TowardsA && exitA) + { + pathA->delayedRelease(block->world().pathReleaseDelay); + } + else if(direction == BlockTrainDirection::TowardsB && exitB) + { + pathB->delayedRelease(block->world().pathReleaseDelay); + } + status->destroy(); #ifndef NDEBUG std::weak_ptr statusWeak = status;