]> git.tdb.fi Git - r2c2.git/blobdiff - source/libmarklin/train.cpp
Add TrackIter and BlockIter classes
[r2c2.git] / source / libmarklin / train.cpp
index 0c1748ac7d4286a2d0f220fdbaa3d0a4c8bf259a..cdb8e67d169297925a627c4b7556338316c0d9b6 100644 (file)
@@ -16,6 +16,7 @@ Distributed under the GPL
 #include "route.h"
 #include "simplecontroller.h"
 #include "timetable.h"
+#include "trackiter.h"
 #include "tracktype.h"
 #include "train.h"
 #include "vehicle.h"
@@ -55,8 +56,6 @@ Train::Train(Layout &l, const VehicleType &t, unsigned a):
        speed_changing(false),
        reverse(false),
        functions(0),
-       route(0),
-       next_route(0),
        end_of_route(false),
        status("Unplaced"),
        travel_dist(0),
@@ -207,61 +206,166 @@ void Train::set_timetable(Timetable *tt)
 
 void Train::set_route(const Route *r)
 {
-       if(!rsv_blocks.empty())
-       {
-               for(list<BlockRef>::iterator i=rsv_blocks.begin(); i!=rsv_blocks.end(); ++i)
-                       if(i->block->get_sensor_id())
-                       {
-                               release_blocks(rsv_blocks, ++i, rsv_blocks.end());
-                               break;
-                       }
-       }
+       free_noncritical_blocks();
 
-       route = r;
-       next_route = 0;
+       routes.clear();
+       if(r)
+               routes.push_back(r);
        end_of_route = false;
 
-       if(route && !cur_blocks.empty())
+       if(r && !cur_blocks.empty())
        {
-               BlockRef &last = (rsv_blocks.empty() ? cur_blocks.back() : rsv_blocks.back());
-               BlockRef next = last.next();
-               const Block::Endpoint &ep = next.block->get_endpoints()[next.entry];
-               if(!route->get_tracks().count(ep.track))
+               TrackIter first = cur_blocks.front().track_iter();
+               TrackIter next = (rsv_blocks.empty() ? cur_blocks : rsv_blocks).back().next().track_iter();
+               if(!r->has_track(*next))
                {
-                       next_route = route;
-                       route = Route::find(*ep.track, ep.track_ep, *next_route);
+                       Route *lead = Route::find(*next, next.entry(), *r);
+                       create_lead_route(lead, lead);
+                       routes.push_front(lead);
                }
+               else if(!r->has_track(*first))
+                       routes.push_front(create_lead_route(0, r));
        }
 
        reserve_more();
 
-       signal_route_changed.emit(route);
+       signal_route_changed.emit(get_route());
 }
 
-void Train::go_to(const Track &to)
+void Train::go_to(Track &to)
 {
-       for(list<BlockRef>::const_iterator i=cur_blocks.begin(); i!=cur_blocks.end(); ++i)
-               if(i->block->get_tracks().count(const_cast<Track *>(&to)))
+       for(BlockList::const_iterator i=cur_blocks.begin(); i!=cur_blocks.end(); ++i)
+               if((*i)->has_track(to))
                {
                        signal_arrived.emit();
                        set_route(0);
                        return;
                }
 
-       BlockRef *last = 0;
-       if(rsv_blocks.empty())
-               last = &cur_blocks.back();
+       free_noncritical_blocks();
+
+       TrackIter next = (rsv_blocks.empty() ? cur_blocks : rsv_blocks).back().next().track_iter();
+
+       Route *route = Route::find(*next, next.entry(), to);
+       create_lead_route(route, route);
+       set_route(route);
+}
+
+bool Train::divert(Track &from)
+{
+       if(!from.get_turnout_id())
+               throw InvalidParameterValue("Can't divert from a non-turnout");
+       if(routes.empty())
+               return false;
+
+       int path = -1;
+       unsigned from_ep = 0;
+       list<RouteRef>::iterator route = routes.begin();
+       BlockIter block = cur_blocks.back();
+       set<const Track *> visited;
+
+       // Follow our routes to find out where we're entering the turnout
+       while(1)
+       {
+               block = block.next(route->route);
+
+               const Block::Endpoint &entry_ep = block->get_endpoints()[block.entry()];
+
+               if(visited.count(entry_ep.track))
+                       return false;
+               visited.insert(entry_ep.track);
+
+               if(!advance_route(route, *entry_ep.track))
+                       return false;
+
+               if(entry_ep.track==&from)
+               {
+                       if(block->get_train()==this && !free_block(*block))
+                               return false;
+
+                       from_ep = entry_ep.track_ep;
+                       path = route->route->get_turnout(from.get_turnout_id());
+                       break;
+               }
+       }
+
+       // Check that more than one path is available
+       unsigned ep_paths = from.get_type().get_endpoints()[from_ep].paths;
+       if(!(ep_paths&(ep_paths-1)))
+               return false;
+
+       // Choose some other path
+       for(int i=0; ep_paths>>i; ++i)
+               if((ep_paths&(1<<i)) && i!=path)
+               {
+                       path = i;
+                       break;
+               }
+
+       TrackIter track = TrackIter(&from, from_ep).next(path);
+       if(!track)
+               return false;
+
+       unsigned ep = track->get_endpoint_by_link(from);
+
+       set<Track *> tracks;
+       for(list<RouteRef>::iterator i=routes.begin(); i!=routes.end(); ++i)
+               tracks.insert(i->route->get_tracks().begin(), i->route->get_tracks().end());
+       Route *diversion = 0;
+       try
+       {
+               diversion = Route::find(*track, ep, tracks);
+       }
+       catch(const Msp::Exception &)
+       {
+               return false;
+       }
+
+       diversion->set_name("Diversion");
+       diversion->add_track(from);
+       diversion->set_turnout(from.get_turnout_id(), path);
+
+       if(!is_valid_diversion(*diversion, from, from_ep))
+       {
+               delete diversion;
+               return false;
+       }
+
+       // Follow the diversion route until we get back to the original route
+       list<RouteRef>::iterator end = routes.end();
+       while(1)
+       {
+               for(list<RouteRef>::iterator i=route; (end==routes.end() && i!=routes.end()); ++i)
+                       if(i->route->has_track(*track))
+                               end = i;
+
+               if(end!=routes.end())
+                       break;
+               else if(!diversion->has_track(*track))
+                       throw Exception("Pathfinder returned a bad route");
+
+               unsigned tid = track->get_turnout_id();
+               track = track.next(tid ? diversion->get_turnout(tid) : 0);
+       }
+
+       if(route==end)
+               // We are rejoining the same route we diverted from, duplicate it
+               routes.insert(end, *route);
        else
        {
-               for(list<BlockRef>::iterator i=rsv_blocks.begin(); (i!=rsv_blocks.end() && !last); ++i)
-                       if(i->block->get_sensor_id())
-                               last = &*i;
+               ++route;
+               routes.erase(route, end);
        }
+       routes.insert(end, RouteRef(diversion, from.get_turnout_id()));
 
-       BlockRef next = last->next();
-       const Block::Endpoint &ep = next.block->get_endpoints()[next.entry];
+       return true;
+}
 
-       set_route(Route::find(*ep.track, ep.track_ep, to));
+const Route *Train::get_route() const
+{
+       if(routes.empty())
+               return 0;
+       return routes.front().route;
 }
 
 void Train::place(Block &block, unsigned entry)
@@ -281,102 +385,141 @@ void Train::place(Block &block, unsigned entry)
                return;
        }
 
-       cur_blocks.push_back(BlockRef(&block, entry));
+       cur_blocks.push_back(BlockIter(&block, entry));
        if(reverse)
        {
-               unsigned exit = block.traverse(entry);
-               const Block::Endpoint &bep = block.get_endpoints()[exit];
-               Track *track = bep.track->get_link(bep.track_ep);
-               unsigned ep = track->get_endpoint_by_link(*bep.track);
-               vehicles.front()->place(track, ep, 0, Vehicle::FRONT_BUFFER);
+               TrackIter track = BlockIter(&block, entry).reverse().track_iter();
+               vehicles.front()->place(*track, track.entry(), 0, Vehicle::FRONT_BUFFER);
        }
        else
        {
                const Block::Endpoint &bep = block.get_endpoints()[entry];
-               vehicles.back()->place(bep.track, bep.track_ep, 0, Vehicle::BACK_BUFFER);
+               vehicles.back()->place(*bep.track, bep.track_ep, 0, Vehicle::BACK_BUFFER);
        }
 }
 
+void Train::unplace()
+{
+       if(controller->get_speed())
+               throw InvalidState("Must be stopped before unplacing");
+
+       release_blocks(rsv_blocks);
+       release_blocks(cur_blocks);
+
+       set_active(false);
+       accurate_position = false;
+
+       for(vector<Vehicle *>::iterator i=vehicles.begin(); i!=vehicles.end(); ++i)
+               (*i)->unplace();
+
+       set_status("Unplaced");
+}
+
 bool Train::free_block(Block &block)
 {
+       float margin = 10*layout.get_catalogue().get_scale();
+       if(get_reserved_distance_until(&block, false)<controller->get_braking_distance()*1.3+margin)
+               return false;
+
        unsigned nsens = 0;
-       for(list<BlockRef>::iterator i=rsv_blocks.begin(); i!=rsv_blocks.end(); ++i)
+       for(BlockList::iterator i=rsv_blocks.begin(); i!=rsv_blocks.end(); ++i)
        {
-               if(i->block==&block)
+               if(i->block()==&block)
                {
                        if(nsens<1)
                                return false;
                        release_blocks(rsv_blocks, i, rsv_blocks.end());
                        return true;
                }
-               else if(i->block->get_sensor_id())
+               else if((*i)->get_sensor_id())
                        ++nsens;
        }
 
        return false;
 }
 
-int Train::get_entry_to_block(Block &block) const
+void Train::free_noncritical_blocks()
 {
-       for(list<BlockRef>::const_iterator i=cur_blocks.begin(); i!=cur_blocks.end(); ++i)
-               if(i->block==&block)
-                       return i->entry;
-       for(list<BlockRef>::const_iterator i=rsv_blocks.begin(); i!=rsv_blocks.end(); ++i)
-               if(i->block==&block)
-                       return i->entry;
-       return -1;
-}
+       if(cur_blocks.empty() || rsv_blocks.empty())
+               return;
 
-float Train::get_reserved_distance() const
-{
-       if(cur_blocks.empty())
-               return 0;
+       if(controller->get_speed()==0)
+       {
+               release_blocks(rsv_blocks);
+               return;
+       }
 
-       Vehicle &veh = *(reverse ? vehicles.back() : vehicles.front());
-       const VehicleType &vtype = veh.get_type();
+       float margin = 10*layout.get_catalogue().get_scale();
+       float min_dist = controller->get_braking_distance()*1.3+margin;
 
-       Track *track = veh.get_track();
-       if(!track)
-               return 0;
-       unsigned entry = veh.get_entry();
+       Vehicle &veh = *(reverse ? vehicles.back() : vehicles.front());
 
-       float result = -vtype.get_length()/2;
-       if(reverse)
+       TrackIter track(veh.get_track(), veh.get_entry());
+       BlockList::iterator block = cur_blocks.begin();
+       bool in_rsv = false;
+       while(block!=rsv_blocks.end() && !(*block)->has_track(*track))
        {
-               entry = track->traverse(entry);
-               result += veh.get_offset();
+               ++block;
+               if(block==cur_blocks.end())
+               {
+                       block = rsv_blocks.begin();
+                       in_rsv = true;
+               }
        }
+
+       float dist = veh.get_offset();
+       if(reverse)
+               track.reverse();
        else
-               result -= veh.get_offset();
+               dist = track->get_type().get_path_length(track->get_active_path())-dist;
+       dist -= veh.get_type().get_length()/2;
 
-       bool first = true;
-       list<BlockRef>::const_iterator block = cur_blocks.begin();
+       bool nsens = 0;
        while(1)
        {
-               if(!first || !reverse)
-                       result += track->get_type().get_path_length(track->get_active_path());
-               first = false;
-
-               if(track->get_type().get_endpoints().size()<2)
-                       return result;
-
-               unsigned exit = track->traverse(entry);
-               Track *next = track->get_link(exit);
+               track = track.next();
 
-               while(!block->block->get_tracks().count(next))
+               if(!(*block)->has_track(*track))
                {
                        ++block;
                        if(block==cur_blocks.end())
+                       {
                                block = rsv_blocks.begin();
+                               in_rsv = true;
+                       }
                        if(block==rsv_blocks.end())
-                               return result;
+                               return;
+
+                       if(dist>min_dist && nsens>0)
+                       {
+                               release_blocks(rsv_blocks, block, rsv_blocks.end());
+                               return;
+                       }
+
+                       if(in_rsv && (*block)->get_sensor_id())
+                               ++nsens;
                }
 
-               entry = next->get_endpoint_by_link(*track);
-               track = next;
+               dist += track->get_type().get_path_length(track->get_active_path());
        }
 }
 
+int Train::get_entry_to_block(Block &block) const
+{
+       for(BlockList::const_iterator i=cur_blocks.begin(); i!=cur_blocks.end(); ++i)
+               if(i->block()==&block)
+                       return i->entry();
+       for(BlockList::const_iterator i=rsv_blocks.begin(); i!=rsv_blocks.end(); ++i)
+               if(i->block()==&block)
+                       return i->entry();
+       return -1;
+}
+
+float Train::get_reserved_distance() const
+{
+       return get_reserved_distance_until(0, false);
+}
+
 void Train::tick(const Time::TimeStamp &t, const Time::TimeDelta &dt)
 {
        if(!active && stop_timeout && t>=stop_timeout)
@@ -426,8 +569,8 @@ void Train::tick(const Time::TimeStamp &t, const Time::TimeDelta &dt)
                Track *track = vehicle.get_track();
 
                bool ok = false;
-               for(list<BlockRef>::const_iterator i=cur_blocks.begin(); (!ok && i!=cur_blocks.end()); ++i)
-                       ok = i->block->get_tracks().count(track);
+               for(BlockList::const_iterator i=cur_blocks.begin(); (!ok && i!=cur_blocks.end()); ++i)
+                       ok = (*i)->has_track(*track);
 
                float d = get_real_speed(current_speed)*(dt/Time::sec);
                if(ok)
@@ -447,56 +590,19 @@ void Train::tick(const Time::TimeStamp &t, const Time::TimeDelta &dt)
        }
        else if(end_of_route && rsv_blocks.empty())
        {
+               set_active(false);
                signal_arrived.emit();
                set_route(0);
        }
 
-       if(!cur_blocks.empty() && !cur_blocks.front().block->get_sensor_id())
+       if(!cur_blocks.empty() && !cur_blocks.front()->get_sensor_id())
        {
-               Vehicle &veh = *(reverse ? vehicles.front() : vehicles.back());
-
-               list<BlockRef>::iterator i = cur_blocks.begin();
-               const Block::Endpoint &bep = i->block->get_endpoints()[i->entry];
-
-               Track *track = bep.track;
-               unsigned entry = bep.track_ep;
+               float dist = get_reserved_distance_until(&*cur_blocks.front(), true);
 
-               bool found = false;
-               float dist = veh.get_offset();
-               if(reverse)
-                       dist = veh.get_track()->get_type().get_path_length(veh.get_track()->get_active_path())-dist;
-               dist -= veh.get_type().get_length()/2;
-               while(1)
+               if(dist>10*layout.get_catalogue().get_scale())
                {
-                       if(track==veh.get_track())
-                       {
-                               found = true;
-                               break;
-                       }
-
-                       if(i!=cur_blocks.begin())
-                       {
-                               float path_len = track->get_type().get_path_length(track->get_active_path());
-                               dist += path_len;
-                       }
-
-                       unsigned exit = track->traverse(entry);
-                       Track *next = track->get_link(exit);
-                       entry = next->get_endpoint_by_link(*track);
-                       track = next;
-
-                       if(!i->block->get_tracks().count(track))
-                       {
-                               ++i;
-                               if(i==cur_blocks.end())
-                                       break;
-                       }
-               }
-
-               if(found && i!=cur_blocks.begin() && dist>10*layout.get_catalogue().get_scale())
-               {
-                       cur_blocks.front().block->reserve(0);
-                       cur_blocks.erase(cur_blocks.begin());
+                       cur_blocks.front()->reserve(0);
+                       cur_blocks.pop_front();
                }
        }
 }
@@ -517,23 +623,23 @@ void Train::save(list<DataFile::Statement> &st) const
 
        if(!cur_blocks.empty())
        {
-               list<BlockRef> blocks = cur_blocks;
+               BlockList blocks = cur_blocks;
                if(reverse)
                        reverse_blocks(blocks);
 
-               Block *prev = blocks.front().block->get_endpoints()[blocks.front().entry].link;
+               BlockIter prev = blocks.front().flip();
                st.push_back((DataFile::Statement("block_hint"), prev->get_id()));
 
-               for(list<BlockRef>::const_iterator i=blocks.begin(); i!=blocks.end(); ++i)
-                       st.push_back((DataFile::Statement("block"), i->block->get_id()));
+               for(BlockList::const_iterator i=blocks.begin(); i!=blocks.end(); ++i)
+                       st.push_back((DataFile::Statement("block"), (*i)->get_id()));
        }
 
-       if(route)
+       if(!routes.empty())
        {
-               if(!route->is_temporary())
-                       st.push_back((DataFile::Statement("route"), route->get_name()));
-               else if(next_route && !next_route->is_temporary())
-                       st.push_back((DataFile::Statement("route"), next_route->get_name()));
+               list<RouteRef>::const_iterator i = routes.begin();
+               for(; (i!=routes.end() && i->route->is_temporary()); ++i) ;
+               if(i!=routes.end())
+                       st.push_back((DataFile::Statement("route"), i->route->get_name()));
        }
 
        if(timetable)
@@ -579,12 +685,12 @@ void Train::sensor_event(unsigned addr, bool state)
        if(state)
        {
                // Find the first sensor block from our reserved blocks that isn't this sensor
-               list<BlockRef>::iterator i;
+               BlockList::iterator i;
                unsigned result = 0;
                for(i=rsv_blocks.begin(); i!=rsv_blocks.end(); ++i)
-                       if(i->block->get_sensor_id())
+                       if((*i)->get_sensor_id())
                        {
-                               if(i->block->get_sensor_id()!=addr)
+                               if((*i)->get_sensor_id()!=addr)
                                {
                                        if(result==0)
                                                result = 2;
@@ -613,23 +719,20 @@ void Train::sensor_event(unsigned addr, bool state)
                        }
 
                        travel_dist = 0;
-                       float block_len;
-                       for(list<BlockRef>::iterator j=rsv_blocks.begin(); j!=i; ++j)
+                       for(BlockList::iterator j=rsv_blocks.begin(); j!=i; ++j)
                        {
-                               j->block->traverse(j->entry, &block_len);
-                               travel_dist += block_len;
+                               travel_dist += (*j)->get_path_length(j->entry());
 
-                               if(j->block->get_sensor_id()==addr && !advancing)
+                               if((*j)->get_sensor_id()==addr && !advancing)
                                {
-                                       const Block::Endpoint &bep = j->block->get_endpoints()[j->entry];
+                                       TrackIter track = j->track_iter();
                                        if(reverse)
                                        {
-                                               Track *track = bep.track->get_link(bep.track_ep);
-                                               unsigned ep = track->get_endpoint_by_link(*bep.track);
-                                               vehicles.back()->place(track, ep, 0, Vehicle::BACK_AXLE);
+                                               track = track.flip();
+                                               vehicles.back()->place(*track, track.entry(), 0, Vehicle::BACK_AXLE);
                                        }
                                        else
-                                               vehicles.front()->place(bep.track, bep.track_ep, 0, Vehicle::FRONT_AXLE);
+                                               vehicles.front()->place(*track, track.entry(), 0, Vehicle::FRONT_AXLE);
                                }
                        }
                        last_entry_time = Time::now();
@@ -638,16 +741,15 @@ void Train::sensor_event(unsigned addr, bool state)
                        overshoot_dist = 0;
 
                        // Check if we've reached the next route
-                       if(next_route)
+                       if(routes.size()>1)
                        {
-                               const set<const Track *> &rtracks = next_route->get_tracks();
-                               for(list<BlockRef>::iterator j=rsv_blocks.begin(); j!=i; ++j)
-                                       if(rtracks.count(j->block->get_endpoints()[j->entry].track))
+                               const set<Track *> &rtracks = (++routes.begin())->route->get_tracks();
+                               for(BlockList::iterator j=rsv_blocks.begin(); j!=i; ++j)
+                                       if(rtracks.count((*j)->get_endpoints()[j->entry()].track))
                                        {
-                                               route = next_route;
-                                               next_route = 0;
+                                               routes.pop_front();
                                                // XXX Exceptions?
-                                               signal_route_changed.emit(route);
+                                               signal_route_changed.emit(routes.front().route);
                                                break;
                                        }
                        }
@@ -664,12 +766,17 @@ void Train::sensor_event(unsigned addr, bool state)
        }
        else
        {
+               const Vehicle &veh = *(reverse ? vehicles.front() : vehicles.back());
+
                // Find the first sensor in our current blocks that's still active
-               list<BlockRef>::iterator end = cur_blocks.begin();
-               for(list<BlockRef>::iterator i=cur_blocks.begin(); i!=cur_blocks.end(); ++i)
-                       if(i->block->get_sensor_id())
+               BlockList::iterator end = cur_blocks.begin();
+               for(BlockList::iterator i=cur_blocks.begin(); i!=cur_blocks.end(); ++i)
+               {
+                       if((*i)->has_track(*veh.get_track()))
+                               break;
+                       if((*i)->get_sensor_id())
                        {
-                               if(layout.get_driver().get_sensor(i->block->get_sensor_id()))
+                               if(layout.get_driver().get_sensor((*i)->get_sensor_id()))
                                        break;
                                else
                                {
@@ -677,6 +784,7 @@ void Train::sensor_event(unsigned addr, bool state)
                                        ++end;
                                }
                        }
+               }
                
                if(end!=cur_blocks.begin() && end!=cur_blocks.end())
                        // Free blocks up to the last inactive sensor
@@ -686,7 +794,7 @@ void Train::sensor_event(unsigned addr, bool state)
 
 void Train::turnout_event(unsigned addr, bool)
 {
-       if(pending_block)
+       if(pending_block && (!pending_block->get_train() || pending_block->get_train()==this))
        {
                unsigned pending_addr = pending_block->get_turnout_id();
                bool double_addr = (*pending_block->get_tracks().begin())->get_type().is_double_address();
@@ -708,7 +816,7 @@ void Train::halt_event(bool h)
 
 void Train::block_reserved(const Block &block, const Train *train)
 {
-       if(&block==pending_block && !train)
+       if(&block==pending_block && !train && !reserving)
                reserve_more();
 }
 
@@ -717,97 +825,95 @@ unsigned Train::reserve_more()
        if(!active)
                return 0;
 
-       BlockRef *start = 0;
+       BlockIter start;
        if(!rsv_blocks.empty())
-               start = &rsv_blocks.back();
+               start = rsv_blocks.back();
        else if(!cur_blocks.empty())
-               start = &cur_blocks.back();
-       if(!start)
+               start = cur_blocks.back();
+       else
                return 0;
 
        pending_block = 0;
 
-       // See how many sensor blocks we already have
+       // See how many sensor blocks and how much track we already have
        unsigned nsens = 0;
-       for(list<BlockRef>::const_iterator i=rsv_blocks.begin(); i!=rsv_blocks.end(); ++i)
-               if(i->block->get_sensor_id())
+       float dist = 0;
+       for(BlockList::const_iterator i=rsv_blocks.begin(); i!=rsv_blocks.end(); ++i)
+       {
+               if((*i)->get_sensor_id())
                        ++nsens;
+               if(nsens>0)
+                       dist += (*i)->get_path_length(i->entry());
+       }
        
        if(end_of_route)
                return nsens;
 
-       const Route *cur_route = 0;
-       if(route)
-       {
-               const set<Track *> &tracks = start->block->get_tracks();
-               for(set<Track *>::const_iterator i=tracks.begin(); (cur_route!=route && i!=tracks.end()); ++i)
-               {
-                       if(route->get_tracks().count(*i))
-                               cur_route = route;
-                       else if(next_route && next_route->get_tracks().count(*i))
-                               cur_route = next_route;
-               }
-       }
+       list<RouteRef>::iterator cur_route = routes.begin();
+       advance_route(cur_route, *start.track_iter());
 
-       SetFlag setf(reserving);
+       float approach_margin = 50*layout.get_catalogue().get_scale();
+       float min_dist = controller->get_braking_distance()*1.3+approach_margin*2;
 
-       BlockRef *last = start;
-       BlockRef *good = start;
+       BlockIter block = start;
+       BlockIter good = start;
+       Track *divert_track = 0;
+       bool try_divert = false;
        unsigned good_sens = nsens;
+       float good_dist = dist;
        Train *blocking_train = 0;
-       std::list<BlockRef> contested_blocks;
-       while(good_sens<3 || !contested_blocks.empty())
+       BlockList contested_blocks;
+
+       SetFlag setf(reserving);
+
+       while(good_sens<3 || good_dist<min_dist || !contested_blocks.empty())
        {
-               // Traverse to the next block
-               unsigned exit = last->block->traverse(last->entry, cur_route);
-               Block *link = last->block->get_link(exit);
-               if(!link)
+               BlockIter last = block;
+               block = block.next(cur_route!=routes.end() ? cur_route->route : 0);
+               if(!block)
                        break;
 
-               int entry = link->get_endpoint_by_link(*last->block);
-               if(entry<0)
-                       throw LogicError("Block links are inconsistent!");
-
-               const Block::Endpoint &entry_ep = link->get_endpoints()[entry];
+               TrackIter track = block.track_iter();
 
-               if(cur_route)
+               if(cur_route!=routes.end())
                {
-                       if(cur_route!=next_route && next_route && next_route->get_tracks().count(entry_ep.track))
-                               cur_route = next_route;
-                       else if(!cur_route->get_tracks().count(entry_ep.track))
+                       if(!advance_route(cur_route, *track))
                        {
                                // Keep the blocks if we arrived at the end of the route
                                if(!blocking_train)
                                {
                                        good = last;
                                        good_sens = nsens;
+                                       good_dist = dist;
                                        end_of_route = true;
                                }
                                break;
                        }
                }
-               else if(route && route->get_tracks().count(entry_ep.track))
-                       cur_route = route;
+               else if(!routes.empty() && routes.front().route->has_track(*track))
+                       cur_route = routes.begin();
 
-               if(link->get_endpoints().size()<2)
+               if(block->get_endpoints().size()<2)
                {
                        if(!blocking_train)
                        {
                                good = last;
                                good_sens = nsens;
+                               good_dist = dist;
                        }
                        break;
                }
 
                if(blocking_train)
                {
-                       if(link->get_train()!=blocking_train)
+                       if(block->get_train()!=blocking_train)
                        {
-                               // XXX is it possible that this won't free all the blocks we want?
-                               if(blocking_train->free_block(*contested_blocks.back().block))
+                               if(blocking_train->free_block(*contested_blocks.back()))
                                {
                                        // Roll back and start actually reserving the blocks
-                                       last = &rsv_blocks.back();
+                                       block = rsv_blocks.back();
+                                       cur_route = routes.begin();
+                                       advance_route(cur_route, *block.track_iter().track());
                                        if(blocking_train->get_priority()==priority)
                                                blocking_train->yield_to(*this);
                                        blocking_train = 0;
@@ -815,127 +921,219 @@ unsigned Train::reserve_more()
                                }
                                else
                                {
-                                       pending_block = contested_blocks.front().block;
+                                       yield_to(*blocking_train);
+                                       pending_block = contested_blocks.front().block();
+                                       try_divert = divert_track;
                                        break;
                                }
                        }
                        else
                        {
-                               contested_blocks.push_back(BlockRef(link, entry));
-                               last = &contested_blocks.back();
+                               contested_blocks.push_back(block);
                                continue;
                        }
                }
 
-               bool reserved = link->reserve(this);
+               bool reserved = block->reserve(this);
                if(!reserved)
                {
                        /* We've found another train.  If it wants to exit the block from the
                        same endpoint we're trying to enter from or the other way around,
                        treat it as coming towards us.  Otherwise treat it as going in the
                        same direction. */
-                       Train *other_train = link->get_train();
-                       int other_entry = other_train->get_entry_to_block(*link);
+                       Train *other_train = block->get_train();
+                       int other_entry = other_train->get_entry_to_block(*block);
                        if(other_entry<0)
                                throw LogicError("Block reservation inconsistency");
 
-                       int other_prio = other_train->get_priority();
-
-                       bool entry_conflict = (static_cast<unsigned>(entry)==link->traverse(other_entry));
-                       bool exit_conflict = (link->traverse(entry)==static_cast<unsigned>(other_entry));
-                       if(!entry_conflict && !exit_conflict)
+                       unsigned exit = block.reverse().entry();
+                       unsigned other_exit = BlockIter(block.block(), other_entry).reverse().entry();
+                       bool entry_conflict = (block.entry()==other_exit);
+                       bool exit_conflict = (exit==static_cast<unsigned>(other_entry));
+                       if(!entry_conflict && !last->get_turnout_id())
                        {
-                               /* Same direction, keep the blocks we got so far and wait for the
-                               other train to pass */
+                               /* The other train is not coming to the blocks we're holding, so we
+                               can keep them. */
                                good = last;
                                good_sens = nsens;
+                               good_dist = dist;
+                       }
 
-                               // Ask a lesser priority train to free the block for us
-                               if(other_train->get_priority()<priority)
-                                       if(other_train->free_block(*link))
-                                               reserved = link->reserve(this);
+                       int other_prio = other_train->get_priority();
+
+                       if(!entry_conflict && !exit_conflict && other_prio<priority)
+                       {
+                               /* Ask a lesser priority train going to the same direction to free
+                               the block for us */
+                               if(other_train->free_block(*block))
+                                       reserved = block->reserve(this);
                        }
-                       else if(other_prio<priority || (other_prio==priority && other_train!=yielding_to && entry_conflict))
+                       else if(other_train!=yielding_to && (other_prio<priority || (other_prio==priority && entry_conflict)))
                        {
                                /* A lesser priority train is coming at us, we must ask it to free
                                enough blocks to get clear of it to avoid a potential deadlock */
                                blocking_train = other_train;
                                contested_blocks.clear();
-                               contested_blocks.push_back(BlockRef(link, entry));
-                               last = &contested_blocks.back();
+                               contested_blocks.push_back(block);
                                continue;
                        }
+                       else if(divert_track && (entry_conflict || exit_conflict))
+                               // We are blocked, but there's a diversion possibility
+                               try_divert = true;
 
                        if(!reserved)
                        {
-                               pending_block = link;
+                               pending_block = &*block;
                                break;
                        }
                }
 
-               if(link->get_turnout_id())
+               if(block->get_turnout_id())
                {
-                       const Endpoint &track_ep = entry_ep.track->get_type().get_endpoints()[entry_ep.track_ep];
+                       const Endpoint &track_ep = track->get_type().get_endpoints()[track.entry()];
+                       bool multiple_paths = (track_ep.paths&(track_ep.paths-1));
 
-                       // Keep the blocks reserved so far, as either us or the other train can diverge
-                       good = last;
-                       good_sens = nsens;
+                       if(multiple_paths || !last->get_turnout_id())
+                       {
+                               /* We can keep the blocks reserved so far if we are facing the
+                               points or if there was no turnout immediately before this one.
+                               With multiple successive turnouts (as is common in crossovers) it's
+                               best to hold at one we can divert from. */
+                               good = last;
+                               good_sens = nsens;
+                               good_dist = dist;
+                       }
 
                        // Figure out what path we'd like to take on the turnout
                        int path = -1;
-                       if(cur_route)
-                               path = cur_route->get_turnout(link->get_turnout_id());
+                       for(list<RouteRef>::iterator i=cur_route; (path<0 && i!=routes.end()); ++i)
+                               path = i->route->get_turnout(block->get_turnout_id());
                        if(path<0)
-                               path = entry_ep.track->get_active_path();
-                       if(!((track_ep.paths>>path)&1))
+                               path = track->get_active_path();
+                       if(!(track_ep.paths&(1<<path)))
                        {
                                for(unsigned i=0; track_ep.paths>>i; ++i)
-                                       if((track_ep.paths>>i)&1)
+                                       if(track_ep.paths&(1<<i))
                                                path = i;
                        }
 
-                       if(path!=static_cast<int>(entry_ep.track->get_active_path()))
+                       if(path!=static_cast<int>(track->get_active_path()))
                        {
                                // The turnout is set to wrong path - switch and wait for it
-                               pending_block = link;
-                               entry_ep.track->set_active_path(path);
+                               pending_block = &*block;
+                               track->set_active_path(path);
                                if(pending_block)
                                {
-                                       link->reserve(0);
+                                       block->reserve(0);
                                        break;
                                }
                        }
+
+                       if(multiple_paths && cur_route!=routes.end() && cur_route->diversion!=block->get_turnout_id())
+                               /* There's multiple paths to be taken and we are on a route - take
+                               note of the diversion possibility */
+                               divert_track = &*track;
                }
 
-               if(!contested_blocks.empty() && contested_blocks.front().block==link)
+               if(!contested_blocks.empty() && contested_blocks.front()==block)
                        contested_blocks.pop_front();
 
-               rsv_blocks.push_back(BlockRef(link, entry));
-               last = &rsv_blocks.back();
-               if(last->block->get_sensor_id())
+               rsv_blocks.push_back(block);
+               if(block->get_sensor_id())
                        ++nsens;
+               if(nsens>0)
+                       dist += block->get_path_length(block.entry());
        }
 
        // Unreserve blocks that were not good
-       while(!rsv_blocks.empty() && &rsv_blocks.back()!=good)
+       while(!rsv_blocks.empty() && rsv_blocks.back()!=good)
        {
-               rsv_blocks.back().block->reserve(0);
-               rsv_blocks.erase(--rsv_blocks.end());
+               rsv_blocks.back()->reserve(0);
+               rsv_blocks.pop_back();
        }
 
-       if(!rsv_blocks.empty() && &rsv_blocks.back()!=start)
+       if(!rsv_blocks.empty() && rsv_blocks.back()!=start)
                // We got some new blocks, so no longer need to yield
                yielding_to = 0;
 
        // Make any sensorless blocks at the beginning immediately current
-       list<BlockRef>::iterator i;
-       for(i=rsv_blocks.begin(); (i!=rsv_blocks.end() && !i->block->get_sensor_id()); ++i) ;
+       BlockList::iterator i;
+       for(i=rsv_blocks.begin(); (i!=rsv_blocks.end() && !(*i)->get_sensor_id()); ++i) ;
        if(i!=rsv_blocks.begin())
                cur_blocks.splice(cur_blocks.end(), rsv_blocks, rsv_blocks.begin(), i);
 
+       if(try_divert && divert(*divert_track))
+               return reserve_more();
+
        return good_sens;
 }
 
+float Train::get_reserved_distance_until(const Block *until_block, bool back) const
+{
+       if(cur_blocks.empty())
+               return 0;
+
+       Vehicle &veh = *(reverse!=back ? vehicles.back() : vehicles.front());
+       const VehicleType &vtype = veh.get_type();
+
+       TrackIter track(veh.get_track(), veh.get_entry());
+       if(!track)
+               return 0;
+
+       BlockList::const_iterator block = cur_blocks.begin();
+       while(block!=rsv_blocks.end() && !(*block)->has_track(*track))
+       {
+               ++block;
+               if(block==cur_blocks.end())
+               {
+                       if(back)
+                               return 0;
+                       block = rsv_blocks.begin();
+               }
+       }
+       if(block==rsv_blocks.end() || &**block==until_block)
+               return 0;
+
+       float result = veh.get_offset();
+       if(reverse!=back)
+               track = track.reverse();
+       else
+               result = track->get_type().get_path_length(track->get_active_path())-result;
+       result -= vtype.get_length()/2;
+
+       while(1)
+       {
+               track = track.next();
+               if(!track)
+                       break;
+
+               if(!(*block)->has_track(*track))
+               {
+                       if(back)
+                       {
+                               if(block==cur_blocks.begin())
+                                       break;
+                               --block;
+                       }
+                       else
+                       {
+                               ++block;
+                               if(block==cur_blocks.end())
+                                       block = rsv_blocks.begin();
+                               if(block==rsv_blocks.end())
+                                       break;
+                       }
+
+                       if(&**block==until_block)
+                               break;
+               }
+
+               result += track->get_type().get_path_length(track->get_active_path());
+       }
+
+       return result;
+}
+
 float Train::get_real_speed(unsigned i) const
 {
        if(real_speed[i].weight)
@@ -973,9 +1171,11 @@ unsigned Train::find_speed(float real) const
 
        unsigned low = 0;
        unsigned high = 0;
+       unsigned last = 0;
        for(unsigned i=0; (!high && i<=14); ++i)
                if(real_speed[i].weight)
                {
+                       last = i;
                        if(real_speed[i].speed<real)
                                low = i;
                        else
@@ -990,7 +1190,7 @@ unsigned Train::find_speed(float real) const
                        else
                                return 0;
                }
-               return min(static_cast<unsigned>(low*real/real_speed[low].speed), 14U);
+               return min(min(static_cast<unsigned>(low*real/real_speed[low].speed), 14U), last+3);
        }
 
        float f = (real-real_speed[low].speed)/(real_speed[high].speed-real_speed[low].speed);
@@ -1010,47 +1210,120 @@ void Train::set_status(const string &s)
        signal_status_changed.emit(s);
 }
 
-void Train::release_blocks(list<BlockRef> &blocks)
+void Train::release_blocks(BlockList &blocks)
 {
        release_blocks(blocks, blocks.begin(), blocks.end());
 }
 
-void Train::release_blocks(list<BlockRef> &blocks, list<BlockRef>::iterator begin, list<BlockRef>::iterator end)
+void Train::release_blocks(BlockList &blocks, BlockList::iterator begin, BlockList::iterator end)
 {
        while(begin!=end)
        {
-               Block *block = begin->block;
+               Block &block = **begin;
                blocks.erase(begin++);
-               block->reserve(0);
+               block.reserve(0);
        }
 }
 
-void Train::reverse_blocks(list<BlockRef> &blocks) const
+void Train::reverse_blocks(BlockList &blocks) const
 {
        blocks.reverse();
-       for(list<BlockRef>::iterator i=blocks.begin(); i!=blocks.end(); ++i)
-               i->entry = i->block->traverse(i->entry);
+       for(BlockList::iterator i=blocks.begin(); i!=blocks.end(); ++i)
+               *i = i->reverse();
 }
 
+bool Train::advance_route(list<RouteRef>::iterator &iter, Track &track)
+{
+       while(iter!=routes.end() && !iter->route->has_track(track))
+               ++iter;
+       if(iter==routes.end())
+               return false;
+
+       list<RouteRef>::iterator next = iter;
+       ++next;
+       if(next!=routes.end() && next->diversion && next->route->has_track(track))
+               iter = next;
 
-Train::BlockRef::BlockRef(Block *b, unsigned e):
-       block(b),
-       entry(e)
-{ }
+       return true;
+}
 
-Train::BlockRef Train::BlockRef::next() const
+Route *Train::create_lead_route(Route *lead, const Route *target)
 {
-       Block *blk = block->get_endpoints()[block->traverse(entry)].link;
-       if(!blk)
-               throw InvalidState("At end of line");
+       if(!lead)
+       {
+               lead = new Route(layout);
+               lead->set_name("Lead");
+               lead->set_temporary(true);
+       }
 
-       int ep = blk->get_endpoint_by_link(*block);
-       if(ep<0)
-               throw LogicError("Block links are inconsistent");
+       set<Track *> tracks;
+       for(BlockList::iterator i=cur_blocks.begin(); i!=rsv_blocks.end(); )
+       {
+               const set<Track *> &btracks = (*i)->get_tracks();
+               for(set<Track *>::const_iterator j=btracks.begin(); j!=btracks.end(); ++j)
+                       if(!target || !target->has_track(**j))
+                               tracks.insert(*j);
 
-       return BlockRef(blk, ep);
+               if(++i==cur_blocks.end())
+                       i = rsv_blocks.begin();
+       }
+
+       lead->add_tracks(tracks);
+
+       return lead;
 }
 
+bool Train::is_valid_diversion(const Route &diversion, Track &from, unsigned from_ep)
+{
+       float diversion_len = 0;
+       TrackIter track(&from, from_ep);
+       while(diversion.has_track(*track))
+       {
+               unsigned tid = track->get_turnout_id();
+               unsigned path = (tid ? diversion.get_turnout(tid) : 0);
+               diversion_len += track->get_type().get_path_length(path);
+
+               track = track.next(path);
+
+               if(&*track==&from)
+                       return false;
+       }
+
+       list<RouteRef>::iterator route = routes.begin();
+       if(!advance_route(route, from))
+               return false;
+
+       set<Track *> visited;
+       float route_len = 0;
+       track = TrackIter(&from, from_ep);
+       while(1)
+       {
+               unsigned tid = track->get_turnout_id();
+               unsigned path = (tid ? route->route->get_turnout(tid) : 0);
+               route_len += track->get_type().get_path_length(path);
+
+               if(&*track!=&from && diversion.has_track(*track))
+                       break;
+
+               if(visited.count(&*track))
+                       return false;
+               visited.insert(&*track);
+
+               track = track.next(path);
+
+               if(!advance_route(route, *track))
+                       return false;
+       }
+
+       return diversion_len<route_len*1.2;
+}
+
+
+Train::RouteRef::RouteRef(const Route *r, unsigned d):
+       route(r),
+       diversion(d)
+{ }
+
 
 Train::RealSpeed::RealSpeed():
        speed(0),
@@ -1083,9 +1356,9 @@ void Train::Loader::finish()
 {
        if(!obj.cur_blocks.empty())
        {
-               const BlockRef &blkref = obj.cur_blocks.front();
-               const Block::Endpoint &bep = blkref.block->get_endpoints()[blkref.entry];
-               obj.vehicles.back()->place(bep.track, bep.track_ep, 0, Vehicle::BACK_BUFFER);
+               TrackIter track = obj.cur_blocks.front().track_iter();
+               float offset = 2*obj.layout.get_catalogue().get_scale();
+               obj.vehicles.back()->place(*track, track.entry(), offset, Vehicle::BACK_BUFFER);
 
                obj.set_status("Stopped");
        }
@@ -1114,7 +1387,7 @@ void Train::Loader::block(unsigned id)
                entry = 0;
 
        blk->reserve(&obj);
-       obj.cur_blocks.push_back(BlockRef(blk, entry));
+       obj.cur_blocks.push_back(BlockIter(blk, entry));
 
        if(blk->get_sensor_id())
                obj.layout.get_driver().set_sensor(blk->get_sensor_id(), true);