diff --git a/src/linkgraph/demands.cpp b/src/linkgraph/demands.cpp index 4008a33523..b7afe51737 100644 --- a/src/linkgraph/demands.cpp +++ b/src/linkgraph/demands.cpp @@ -427,10 +427,9 @@ DemandCalculator::DemandCalculator(LinkGraphJob &job) : }; std::vector symmetric_edges(se_index(0, size)); - for (NodeID node_id = 0; node_id < size; ++node_id) { - Node from = job[node_id]; - for (EdgeIterator it(from.Begin()); it != from.End(); ++it) { - symmetric_edges[se_index(node_id, it->first)] = true; + for (auto &it : job.Graph().GetEdges()) { + if (it.first.first != it.first.second) { + symmetric_edges[se_index(it.first.first, it.first.second)] = true; } } uint first_unseen = 0; diff --git a/src/linkgraph/linkgraph.cpp b/src/linkgraph/linkgraph.cpp index 15b2495f95..764bb3a562 100644 --- a/src/linkgraph/linkgraph.cpp +++ b/src/linkgraph/linkgraph.cpp @@ -32,20 +32,6 @@ inline void LinkGraph::BaseNode::Init(TileIndex xy, StationID st, uint demand) this->last_update = INVALID_DATE; } -/** - * Create an edge. - */ -inline void LinkGraph::BaseEdge::Init() -{ - this->capacity = 0; - this->usage = 0; - this->travel_time_sum = 0; - this->last_unrestricted_update = INVALID_DATE; - this->last_restricted_update = INVALID_DATE; - this->last_aircraft_update = INVALID_DATE; - this->next_edge = INVALID_NODE; -} - /** * Shift all dates by given interval. * This is useful if the date has been modified with the cheat menu. @@ -57,12 +43,12 @@ void LinkGraph::ShiftDates(int interval) for (NodeID node1 = 0; node1 < this->Size(); ++node1) { BaseNode &source = this->nodes[node1]; if (source.last_update != INVALID_DATE) source.last_update += interval; - for (NodeID node2 = 0; node2 < this->Size(); ++node2) { - BaseEdge &edge = this->edges[node1][node2]; - if (edge.last_unrestricted_update != INVALID_DATE) edge.last_unrestricted_update += interval; - if (edge.last_restricted_update != INVALID_DATE) edge.last_restricted_update += interval; - if (edge.last_aircraft_update != INVALID_DATE) edge.last_aircraft_update += interval; - } + } + for (auto &it : this->edges) { + BaseEdge &edge = it.second; + if (edge.last_unrestricted_update != INVALID_DATE) edge.last_unrestricted_update += interval; + if (edge.last_restricted_update != INVALID_DATE) edge.last_restricted_update += interval; + if (edge.last_aircraft_update != INVALID_DATE) edge.last_aircraft_update += interval; } } @@ -71,18 +57,18 @@ void LinkGraph::Compress() this->last_compression = (_date + this->last_compression) / 2; for (NodeID node1 = 0; node1 < this->Size(); ++node1) { this->nodes[node1].supply /= 2; - for (NodeID node2 = 0; node2 < this->Size(); ++node2) { - BaseEdge &edge = this->edges[node1][node2]; - if (edge.capacity > 0) { - uint new_capacity = std::max(1U, edge.capacity / 2); - if (edge.capacity < (1 << 16)) { - edge.travel_time_sum = edge.travel_time_sum * new_capacity / edge.capacity; - } else if (edge.travel_time_sum != 0) { - edge.travel_time_sum = std::max(1ULL, edge.travel_time_sum / 2); - } - edge.capacity = new_capacity; - edge.usage /= 2; + } + for (auto &it : this->edges) { + BaseEdge &edge = it.second; + if (edge.capacity > 0) { + uint new_capacity = std::max(1U, edge.capacity / 2); + if (edge.capacity < (1 << 16)) { + edge.travel_time_sum = edge.travel_time_sum * new_capacity / edge.capacity; + } else if (edge.travel_time_sum != 0) { + edge.travel_time_sum = std::max(1ULL, edge.travel_time_sum / 2); } + edge.capacity = new_capacity; + edge.usage /= 2; } } } @@ -96,29 +82,23 @@ void LinkGraph::Merge(LinkGraph *other) Date age = _date - this->last_compression + 1; Date other_age = _date - other->last_compression + 1; NodeID first = this->Size(); + this->nodes.reserve(first + other->Size()); for (NodeID node1 = 0; node1 < other->Size(); ++node1) { Station *st = Station::Get(other->nodes[node1].station); NodeID new_node = this->AddNode(st); this->nodes[new_node].supply = LinkGraph::Scale(other->nodes[node1].supply, age, other_age); st->goods[this->cargo].link_graph = this->index; st->goods[this->cargo].node = new_node; - for (NodeID node2 = 0; node2 < node1; ++node2) { - BaseEdge &forward = this->edges[new_node][first + node2]; - BaseEdge &backward = this->edges[first + node2][new_node]; - forward = other->edges[node1][node2]; - backward = other->edges[node2][node1]; - forward.capacity = LinkGraph::Scale(forward.capacity, age, other_age); - forward.usage = LinkGraph::Scale(forward.usage, age, other_age); - forward.travel_time_sum = LinkGraph::Scale(forward.travel_time_sum, age, other_age); - if (forward.next_edge != INVALID_NODE) forward.next_edge += first; - backward.capacity = LinkGraph::Scale(backward.capacity, age, other_age); - backward.usage = LinkGraph::Scale(backward.usage, age, other_age); - backward.travel_time_sum = LinkGraph::Scale(backward.travel_time_sum, age, other_age); - if (backward.next_edge != INVALID_NODE) backward.next_edge += first; + } + for (const auto &iter : other->edges) { + std::pair key = std::make_pair(iter.first.first + first, iter.first.second + first); + BaseEdge edge = iter.second; + if (key.first != key.second) { + edge.capacity = LinkGraph::Scale(edge.capacity, age, other_age); + edge.usage = LinkGraph::Scale(edge.usage, age, other_age); + edge.travel_time_sum = LinkGraph::Scale(edge.travel_time_sum, age, other_age); } - BaseEdge &new_start = this->edges[new_node][new_node]; - new_start = other->edges[node1][node1]; - if (new_start.next_edge != INVALID_NODE) new_start.next_edge += first; + this->edges[key] = edge; } delete other; } @@ -131,31 +111,31 @@ void LinkGraph::RemoveNode(NodeID id) { assert(id < this->Size()); + std::vector, BaseEdge>> saved_nodes; + NodeID last_node = this->Size() - 1; - for (NodeID i = 0; i <= last_node; ++i) { - (*this)[i].RemoveEdge(id); - BaseEdge *node_edges = this->edges[i]; - NodeID prev = i; - NodeID next = node_edges[i].next_edge; - while (next != INVALID_NODE) { - if (next == last_node) { - node_edges[prev].next_edge = id; - break; - } - prev = next; - next = node_edges[prev].next_edge; + + for (auto iter = this->edges.begin(); iter != this->edges.end();) { + if (iter->first.first == id || iter->first.second == id) { + /* Erase this node */ + iter = this->edges.erase(iter); + } else if (iter->first.first == last_node || iter->first.second == last_node) { + /* The edge refers to the last node, remove and save to be re-added later with the updated id */ + saved_nodes.push_back(std::make_pair(std::make_pair(iter->first.first == last_node ? id : iter->first.first, iter->first.second == last_node ? id : iter->first.second), iter->second)); + iter = this->edges.erase(iter); + } else { + ++iter; } - node_edges[id] = node_edges[last_node]; } + for (const auto &it : saved_nodes) { + this->edges.insert(it); + } + Station::Get(this->nodes[last_node].station)->goods[this->cargo].node = id; /* Erase node by swapping with the last element. Node index is referenced * directly from station goods entries so the order and position must remain. */ this->nodes[id] = this->nodes.back(); this->nodes.pop_back(); - this->edges.EraseColumn(id); - /* Not doing EraseRow here, as having the extra invalid row doesn't hurt - * and removing it would trigger a lot of memmove. The data has already - * been copied around in the loop above. */ } /** @@ -172,43 +152,26 @@ NodeID LinkGraph::AddNode(const Station *st) NodeID new_node = this->Size(); this->nodes.emplace_back(); - /* Avoid reducing the height of the matrix as that is expensive and we - * most likely will increase it again later which is again expensive. */ - this->edges.Resize(new_node + 1U, std::max(new_node + 1U, this->edges.Height())); this->nodes[new_node].Init(st->xy, st->index, HasBit(good.status, GoodsEntry::GES_ACCEPTANCE)); - BaseEdge *new_edges = this->edges[new_node]; - - /* Reset the first edge starting at the new node */ - new_edges[new_node].next_edge = INVALID_NODE; - - for (NodeID i = 0; i <= new_node; ++i) { - new_edges[i].Init(); - this->edges[i][new_node].Init(); - } return new_node; } /** * Fill an edge with values from a link. Set the restricted or unrestricted * update timestamp according to the given update mode. - * @param to Destination node of the link. + * @param edge Edge to fill. * @param capacity Capacity of the link. * @param usage Usage to be added. * @param mode Update mode to be used. */ -void LinkGraph::Node::AddEdge(NodeID to, uint capacity, uint usage, uint32 travel_time, EdgeUpdateMode mode) +static void AddEdge(LinkGraph::BaseEdge &edge, uint capacity, uint usage, uint32 travel_time, EdgeUpdateMode mode) { - assert(this->index != to); - BaseEdge &edge = this->edges[to]; - BaseEdge &first = this->edges[this->index]; edge.capacity = capacity; edge.usage = usage; edge.travel_time_sum = travel_time * capacity; - edge.next_edge = first.next_edge; - first.next_edge = to; if (mode & EUM_UNRESTRICTED) edge.last_unrestricted_update = _date; if (mode & EUM_RESTRICTED) edge.last_restricted_update = _date; if (mode & EUM_AIRCRAFT) edge.last_aircraft_update = _date; @@ -216,50 +179,34 @@ void LinkGraph::Node::AddEdge(NodeID to, uint capacity, uint usage, uint32 trave /** * Creates an edge if none exists yet or updates an existing edge. + * @param from Source node. * @param to Target node. * @param capacity Capacity of the link. * @param usage Usage to be added. * @param mode Update mode to be used. */ -void LinkGraph::Node::UpdateEdge(NodeID to, uint capacity, uint usage, uint32 travel_time, EdgeUpdateMode mode) +void LinkGraph::UpdateEdge(NodeID from, NodeID to, uint capacity, uint usage, uint32 travel_time, EdgeUpdateMode mode) { assert(capacity > 0); assert(usage <= capacity); - if (this->edges[to].capacity == 0) { - this->AddEdge(to, capacity, usage, travel_time, mode); + BaseEdge &edge = this->edges[std::make_pair(from, to)]; + if (edge.capacity == 0) { + assert(from != to); + AddEdge(edge, capacity, usage, travel_time, mode); } else { - (*this)[to].Update(capacity, usage, travel_time, mode); + Edge(edge).Update(capacity, usage, travel_time, mode); } } /** * Remove an outgoing edge from this node. + * @param from ID of source node. * @param to ID of destination node. */ -void LinkGraph::Node::RemoveEdge(NodeID to) +void LinkGraph::RemoveEdge(NodeID from, NodeID to) { - if (this->index == to) return; - BaseEdge &edge = this->edges[to]; - edge.capacity = 0; - edge.last_unrestricted_update = INVALID_DATE; - edge.last_restricted_update = INVALID_DATE; - edge.last_aircraft_update = INVALID_DATE; - edge.usage = 0; - edge.travel_time_sum = 0; - - NodeID prev = this->index; - NodeID next = this->edges[this->index].next_edge; - while (next != INVALID_NODE) { - if (next == to) { - /* Will be removed, skip it. */ - this->edges[prev].next_edge = edge.next_edge; - edge.next_edge = INVALID_NODE; - break; - } else { - prev = next; - next = this->edges[next].next_edge; - } - } + if (from == to) return; + this->edges.erase(std::make_pair(from, to)); } /** @@ -274,34 +221,35 @@ void LinkGraph::Node::RemoveEdge(NodeID to) */ void LinkGraph::Edge::Update(uint capacity, uint usage, uint32 travel_time, EdgeUpdateMode mode) { - assert(this->edge.capacity > 0); + BaseEdge &edge = *(this->edge); + assert(edge.capacity > 0); assert(capacity >= usage); if (mode & EUM_INCREASE) { - if (this->edge.travel_time_sum == 0) { - this->edge.travel_time_sum = (this->edge.capacity + capacity) * travel_time; + if (edge.travel_time_sum == 0) { + edge.travel_time_sum = (edge.capacity + capacity) * travel_time; } else if (travel_time == 0) { - this->edge.travel_time_sum += this->edge.travel_time_sum / this->edge.capacity * capacity; + edge.travel_time_sum += edge.travel_time_sum / edge.capacity * capacity; } else { - this->edge.travel_time_sum += travel_time * capacity; + edge.travel_time_sum += travel_time * capacity; } - this->edge.capacity += capacity; - this->edge.usage += usage; + edge.capacity += capacity; + edge.usage += usage; } else if (mode & EUM_REFRESH) { /* If travel time is not provided, we scale the stored time based on * the capacity increase. */ - if (capacity > this->edge.capacity && travel_time == 0) { - this->edge.travel_time_sum = this->edge.travel_time_sum / this->edge.capacity * capacity; - this->edge.capacity = capacity; + if (capacity > edge.capacity && travel_time == 0) { + edge.travel_time_sum = edge.travel_time_sum / edge.capacity * capacity; + edge.capacity = capacity; } else { - this->edge.capacity = std::max(this->edge.capacity, capacity); - this->edge.travel_time_sum = std::max(this->edge.travel_time_sum, travel_time * capacity); + edge.capacity = std::max(edge.capacity, capacity); + edge.travel_time_sum = std::max(edge.travel_time_sum, travel_time * capacity); } - this->edge.usage = std::max(this->edge.usage, usage); + edge.usage = std::max(edge.usage, usage); } - if (mode & EUM_UNRESTRICTED) this->edge.last_unrestricted_update = _date; - if (mode & EUM_RESTRICTED) this->edge.last_restricted_update = _date; - if (mode & EUM_AIRCRAFT) this->edge.last_aircraft_update = _date; + if (mode & EUM_UNRESTRICTED) edge.last_unrestricted_update = _date; + if (mode & EUM_RESTRICTED) edge.last_restricted_update = _date; + if (mode & EUM_AIRCRAFT) edge.last_aircraft_update = _date; } /** @@ -312,12 +260,5 @@ void LinkGraph::Edge::Update(uint capacity, uint usage, uint32 travel_time, Edge void LinkGraph::Init(uint size) { assert(this->Size() == 0); - this->edges.Resize(size, size); this->nodes.resize(size); - - for (uint i = 0; i < size; ++i) { - this->nodes[i].Init(); - BaseEdge *column = this->edges[i]; - for (uint j = 0; j < size; ++j) column[j].Init(); - } } diff --git a/src/linkgraph/linkgraph.h b/src/linkgraph/linkgraph.h index 2e093bd3bf..cc64999ebf 100644 --- a/src/linkgraph/linkgraph.h +++ b/src/linkgraph/linkgraph.h @@ -19,6 +19,7 @@ #include "../date_func.h" #include "../saveload/saveload_common.h" #include "linkgraph_type.h" +#include "../3rdparty/cpp-btree/btree_map.h" #include class LinkGraph; @@ -74,10 +75,23 @@ public: Date last_unrestricted_update; ///< When the unrestricted part of the link was last updated. Date last_restricted_update; ///< When the restricted part of the link was last updated. Date last_aircraft_update; ///< When aircraft capacity of the link was last updated. - NodeID next_edge; ///< Destination of next valid edge starting at the same source node. - void Init(); + + void Init() + { + this->capacity = 0; + this->usage = 0; + this->travel_time_sum = 0; + this->last_unrestricted_update = INVALID_DATE; + this->last_restricted_update = INVALID_DATE; + this->last_aircraft_update = INVALID_DATE; + } + + BaseEdge() { this->Init(); } }; + typedef std::vector NodeVector; + typedef btree::btree_map, BaseEdge> EdgeMatrix; + /** * Wrapper for an edge (const or not) allowing retrieval, but no modification. * @tparam Tedge Actual edge class, may be "const BaseEdge" or just "BaseEdge". @@ -85,7 +99,7 @@ public: template class EdgeWrapper { protected: - Tedge &edge; ///< Actual edge to be used. + Tedge *edge; ///< Actual edge to be used. public: @@ -93,73 +107,69 @@ public: * Wrap a an edge. * @param edge Edge to be wrapped. */ - EdgeWrapper (Tedge &edge) : edge(edge) {} + EdgeWrapper (Tedge &edge) : edge(&edge) {} /** * Get edge's capacity. * @return Capacity. */ - uint Capacity() const { return this->edge.capacity; } + uint Capacity() const { return this->edge->capacity; } /** * Get edge's usage. * @return Usage. */ - uint Usage() const { return this->edge.usage; } + uint Usage() const { return this->edge->usage; } /** * Get edge's average travel time. * @return Travel time, in ticks. */ - uint32 TravelTime() const { return this->edge.travel_time_sum / this->edge.capacity; } + uint32 TravelTime() const { return this->edge->travel_time_sum / this->edge->capacity; } /** * Get the date of the last update to the edge's unrestricted capacity. * @return Last update. */ - Date LastUnrestrictedUpdate() const { return this->edge.last_unrestricted_update; } + Date LastUnrestrictedUpdate() const { return this->edge->last_unrestricted_update; } /** * Get the date of the last update to the edge's restricted capacity. * @return Last update. */ - Date LastRestrictedUpdate() const { return this->edge.last_restricted_update; } + Date LastRestrictedUpdate() const { return this->edge->last_restricted_update; } /** * Get the date of the last update to the edge's aircraft capacity. * @return Last update. */ - Date LastAircraftUpdate() const { return this->edge.last_aircraft_update; } + Date LastAircraftUpdate() const { return this->edge->last_aircraft_update; } /** * Get the date of the last update to any part of the edge's capacity. * @return Last update. */ - Date LastUpdate() const { return std::max(this->edge.last_unrestricted_update, this->edge.last_restricted_update); } + Date LastUpdate() const { return std::max(this->edge->last_unrestricted_update, this->edge->last_restricted_update); } }; /** * Wrapper for a node (const or not) allowing retrieval, but no modification. * @tparam Tedge Actual node class, may be "const BaseNode" or just "BaseNode". - * @tparam Tedge Actual edge class, may be "const BaseEdge" or just "BaseEdge". */ - template + template class NodeWrapper { protected: - Tnode &node; ///< Node being wrapped. - Tedge *edges; ///< Outgoing edges for wrapped node. - NodeID index; ///< ID of wrapped node. + Tnode &node; ///< Node being wrapped. + NodeID index; ///< ID of wrapped node. public: /** * Wrap a node. * @param node Node to be wrapped. - * @param edges Outgoing edges for node to be wrapped. * @param index ID of node to be wrapped. */ - NodeWrapper(Tnode &node, Tedge *edges, NodeID index) : node(node), - edges(edges), index(index) {} + NodeWrapper(Tnode &node, NodeID index) : node(node), index(index) {} /** * Get supply of wrapped node. @@ -190,117 +200,8 @@ public: * @return Location of the station. */ TileIndex XY() const { return this->node.xy; } - }; - /** - * Base class for iterating across outgoing edges of a node. Only the real - * edges (those with capacity) are iterated. The ones with only distance - * information are skipped. - * @tparam Tedge Actual edge class. May be "BaseEdge" or "const BaseEdge". - * @tparam Titer Actual iterator class. - */ - template - class BaseEdgeIterator { - protected: - Tedge *base; ///< Array of edges being iterated. - NodeID current; ///< Current offset in edges array. - - /** - * A "fake" pointer to enable operator-> on temporaries. As the objects - * returned from operator* aren't references but real objects, we have - * to return something that implements operator->, but isn't a pointer - * from operator->. A fake pointer. - */ - class FakePointer : public std::pair { - public: - - /** - * Construct a fake pointer from a pair of NodeID and edge. - * @param pair Pair to be "pointed" to (in fact shallow-copied). - */ - FakePointer(const std::pair &pair) : std::pair(pair) {} - - /** - * Retrieve the pair by operator->. - * @return Pair being "pointed" to. - */ - std::pair *operator->() { return this; } - }; - - public: - /** - * Constructor. - * @param base Array of edges to be iterated. - * @param current ID of current node (to locate the first edge). - */ - BaseEdgeIterator (Tedge *base, NodeID current) : - base(base), - current(current == INVALID_NODE ? current : base[current].next_edge) - {} - - /** - * Prefix-increment. - * @return This. - */ - Titer &operator++() - { - this->current = this->base[this->current].next_edge; - return static_cast(*this); - } - - /** - * Postfix-increment. - * @return Version of this before increment. - */ - Titer operator++(int) - { - Titer ret(static_cast(*this)); - this->current = this->base[this->current].next_edge; - return ret; - } - - /** - * Compare with some other edge iterator. The other one may be of a - * child class. - * @tparam Tother Class of other iterator. - * @param other Instance of other iterator. - * @return If the iterators have the same edge array and current node. - */ - template - bool operator==(const Tother &other) - { - return this->base == other.base && this->current == other.current; - } - - /** - * Compare for inequality with some other edge iterator. The other one - * may be of a child class. - * @tparam Tother Class of other iterator. - * @param other Instance of other iterator. - * @return If either the edge arrays or the current nodes differ. - */ - template - bool operator!=(const Tother &other) - { - return this->base != other.base || this->current != other.current; - } - - /** - * Dereference with operator*. - * @return Pair of current target NodeID and edge object. - */ - std::pair operator*() const - { - return std::pair(this->current, Tedge_wrapper(this->base[this->current])); - } - - /** - * Dereference with operator->. - * @return Fake pointer to Pair of current target NodeID and edge object. - */ - FakePointer operator->() const { - return FakePointer(this->operator*()); - } + NodeID GetNodeID() const { return this->index; } }; /** @@ -319,46 +220,16 @@ public: */ Edge(BaseEdge &edge) : EdgeWrapper(edge) {} void Update(uint capacity, uint usage, uint32 time, EdgeUpdateMode mode); - void Restrict() { this->edge.last_unrestricted_update = INVALID_DATE; } - void Release() { this->edge.last_restricted_update = INVALID_DATE; } - void ClearAircraft() { this->edge.last_aircraft_update = INVALID_DATE; } - }; - - /** - * An iterator for const edges. Cannot be typedef'ed because of - * template-reference to ConstEdgeIterator itself. - */ - class ConstEdgeIterator : public BaseEdgeIterator { - public: - /** - * Constructor. - * @param edges Array of edges to be iterated over. - * @param current ID of current edge's end node. - */ - ConstEdgeIterator(const BaseEdge *edges, NodeID current) : - BaseEdgeIterator(edges, current) {} - }; - - /** - * An iterator for non-const edges. Cannot be typedef'ed because of - * template-reference to EdgeIterator itself. - */ - class EdgeIterator : public BaseEdgeIterator { - public: - /** - * Constructor. - * @param edges Array of edges to be iterated over. - * @param current ID of current edge's end node. - */ - EdgeIterator(BaseEdge *edges, NodeID current) : - BaseEdgeIterator(edges, current) {} + void Restrict() { this->edge->last_unrestricted_update = INVALID_DATE; } + void Release() { this->edge->last_restricted_update = INVALID_DATE; } + void ClearAircraft() { this->edge->last_aircraft_update = INVALID_DATE; } }; /** * Constant node class. Only retrieval operations are allowed on both the * node itself and its edges. */ - class ConstNode : public NodeWrapper { + class ConstNode : public NodeWrapper { public: /** * Constructor. @@ -366,34 +237,14 @@ public: * @param node ID of the node. */ ConstNode(const LinkGraph *lg, NodeID node) : - NodeWrapper(lg->nodes[node], lg->edges[node], node) + NodeWrapper(lg->nodes[node], node) {} - - /** - * Get a ConstEdge. This is not a reference as the wrapper objects are - * not actually persistent. - * @param to ID of end node of edge. - * @return Constant edge wrapper. - */ - ConstEdge operator[](NodeID to) const { return ConstEdge(this->edges[to]); } - - /** - * Get an iterator pointing to the start of the edges array. - * @return Constant edge iterator. - */ - ConstEdgeIterator Begin() const { return ConstEdgeIterator(this->edges, this->index); } - - /** - * Get an iterator pointing beyond the end of the edges array. - * @return Constant edge iterator. - */ - ConstEdgeIterator End() const { return ConstEdgeIterator(this->edges, INVALID_NODE); } }; /** * Updatable node class. The node itself as well as its edges can be modified. */ - class Node : public NodeWrapper { + class Node : public NodeWrapper { public: /** * Constructor. @@ -401,29 +252,9 @@ public: * @param node ID of the node. */ Node(LinkGraph *lg, NodeID node) : - NodeWrapper(lg->nodes[node], lg->edges[node], node) + NodeWrapper(lg->nodes[node], node) {} - /** - * Get an Edge. This is not a reference as the wrapper objects are not - * actually persistent. - * @param to ID of end node of edge. - * @return Edge wrapper. - */ - Edge operator[](NodeID to) { return Edge(this->edges[to]); } - - /** - * Get an iterator pointing to the start of the edges array. - * @return Edge iterator. - */ - EdgeIterator Begin() { return EdgeIterator(this->edges, this->index); } - - /** - * Get an iterator pointing beyond the end of the edges array. - * @return Constant edge iterator. - */ - EdgeIterator End() { return EdgeIterator(this->edges, INVALID_NODE); } - /** * Update the node's supply and set last_update to the current date. * @param supply Supply to be added. @@ -451,15 +282,8 @@ public: { this->node.demand = demand; } - - void AddEdge(NodeID to, uint capacity, uint usage, uint32 time, EdgeUpdateMode mode); - void UpdateEdge(NodeID to, uint capacity, uint usage, uint32 time, EdgeUpdateMode mode); - void RemoveEdge(NodeID to); }; - typedef std::vector NodeVector; - typedef SmallMatrix EdgeMatrix; - /** Minimum effective distance for timeout calculation. */ static const uint MIN_TIMEOUT_DISTANCE = 32; @@ -548,6 +372,9 @@ public: NodeID AddNode(const Station *st); void RemoveNode(NodeID id); + void UpdateEdge(NodeID from, NodeID to, uint capacity, uint usage, uint32 time, EdgeUpdateMode mode); + void RemoveEdge(NodeID from, NodeID to); + inline uint64 CalculateCostEstimate() const { uint64 size_squared = this->Size() * this->Size(); return size_squared * FindLastBit(size_squared * size_squared); // N^2 * 4log_2(N) @@ -570,6 +397,89 @@ protected: Date last_compression; ///< Last time the capacities and supplies were compressed. NodeVector nodes; ///< Nodes in the component. EdgeMatrix edges; ///< Edges in the component. + +public: + const EdgeMatrix &GetEdges() const { return this->edges; } + + const BaseEdge &GetBaseEdge(NodeID from, NodeID to) const + { + auto iter = this->edges.find(std::make_pair(from, to)); + if (iter != this->edges.end()) return iter->second; + + static LinkGraph::BaseEdge empty_edge = {}; + return empty_edge; + } + + ConstEdge GetConstEdge(NodeID from, NodeID to) const { return ConstEdge(this->GetBaseEdge(from, to)); } + + template + void IterateEdgesFromNode(NodeID from_id, F proc) const + { + auto iter = this->edges.lower_bound(std::make_pair(from_id, (NodeID)0)); + while (iter != this->edges.end()) { + NodeID from = iter->first.first; + NodeID to = iter->first.second; + if (from != from_id) return; + if (from != to) { + proc(from, to, ConstEdge(iter->second)); + } + ++iter; + } + } + + enum class EdgeIterationResult { + None, + EraseEdge, + }; + + struct EdgeIterationHelper { + EdgeMatrix &edges; + EdgeMatrix::iterator &iter; + const NodeID from_id; + const NodeID to_id; + size_t expected_size; + + EdgeIterationHelper(EdgeMatrix &edges, EdgeMatrix::iterator &iter, NodeID from_id, NodeID to_id) : + edges(edges), iter(iter), from_id(from_id), to_id(to_id), expected_size(0) {} + + Edge GetEdge() { return Edge(this->iter->second); } + + void RecordSize() { this->expected_size = this->edges.size(); } + + bool RefreshIterationIfSizeChanged() + { + if (this->expected_size != this->edges.size()) { + /* Edges container has resized, our iterator is now invalid, so find it again */ + this->iter = this->edges.find(std::make_pair(this->from_id, this->to_id)); + return true; + } else { + return false; + } + } + }; + + template + void MutableIterateEdgesFromNode(NodeID from_id, F proc) + { + EdgeMatrix::iterator iter = this->edges.lower_bound(std::make_pair(from_id, (NodeID)0)); + while (iter != this->edges.end()) { + NodeID from = iter->first.first; + NodeID to = iter->first.second; + if (from != from_id) return; + EdgeIterationResult result = EdgeIterationResult::None; + if (from != to) { + result = proc(EdgeIterationHelper(this->edges, iter, from, to)); + } + switch (result) { + case EdgeIterationResult::None: + ++iter; + break; + case EdgeIterationResult::EraseEdge: + iter = this->edges.erase(iter); + break; + } + } + } }; #endif /* LINKGRAPH_H */ diff --git a/src/linkgraph/linkgraph_base.h b/src/linkgraph/linkgraph_base.h index eb86c0c64e..251abcd3ee 100644 --- a/src/linkgraph/linkgraph_base.h +++ b/src/linkgraph/linkgraph_base.h @@ -15,11 +15,9 @@ typedef LinkGraph::Node Node; typedef LinkGraph::Edge Edge; -typedef LinkGraph::EdgeIterator EdgeIterator; typedef LinkGraph::ConstNode ConstNode; typedef LinkGraph::ConstEdge ConstEdge; -typedef LinkGraph::ConstEdgeIterator ConstEdgeIterator; #endif /* LINKGRAPH_BASE_H */ diff --git a/src/linkgraph/linkgraph_gui.cpp b/src/linkgraph/linkgraph_gui.cpp index 86f3bd2923..1397e5cd98 100644 --- a/src/linkgraph/linkgraph_gui.cpp +++ b/src/linkgraph/linkgraph_gui.cpp @@ -163,7 +163,7 @@ void LinkGraphOverlay::RebuildCache(bool incremental) continue; } const LinkGraph &lg = *LinkGraph::Get(ge.link_graph); - ConstEdge edge = lg[ge.node][to->goods[c].node]; + ConstEdge edge = lg.GetConstEdge(ge.node, to->goods[c].node); if (edge.Capacity() > 0) { if (!item) { auto iter = link_cache_map.insert(insert_iter, std::make_pair(std::make_pair(from->index, to->index), LinkCacheItem())); @@ -198,33 +198,33 @@ void LinkGraphOverlay::RebuildCache(bool incremental) ConstNode from_node = lg[sta->goods[c].node]; supply += lg.Monthly(from_node.Supply()); - for (ConstEdgeIterator i = from_node.Begin(); i != from_node.End(); ++i) { - StationID to = lg[i->first].Station(); + lg.IterateEdgesFromNode(from_node.GetNodeID(), [&](NodeID from_id, NodeID to_id, ConstEdge edge) { + StationID to = lg[to_id].Station(); assert(from != to); - if (!Station::IsValidID(to)) continue; + if (!Station::IsValidID(to)) return; const Station *stb = Station::Get(to); assert(sta != stb); /* Show links between stations of selected companies or "neutral" ones like oilrigs. */ - if (stb->owner != OWNER_NONE && sta->owner != OWNER_NONE && !HasBit(this->company_mask, stb->owner)) continue; - if (stb->rect.IsEmpty()) continue; + if (stb->owner != OWNER_NONE && sta->owner != OWNER_NONE && !HasBit(this->company_mask, stb->owner)) return; + if (stb->rect.IsEmpty()) return; - if (incremental && std::binary_search(incremental_station_exclude.begin(), incremental_station_exclude.end(), to)) continue; - if (incremental && std::binary_search(incremental_link_exclude.begin(), incremental_link_exclude.end(), std::make_pair(from, to))) continue; + if (incremental && std::binary_search(incremental_station_exclude.begin(), incremental_station_exclude.end(), to)) return; + if (incremental && std::binary_search(incremental_link_exclude.begin(), incremental_link_exclude.end(), std::make_pair(from, to))) return; auto key = std::make_pair(from, to); auto iter = link_cache_map.lower_bound(key); if (iter != link_cache_map.end() && !(link_cache_map.key_comp()(key, iter->first))) { - continue; + return; } Point ptb = this->GetStationMiddle(stb); - if (!cache_all && !this->IsLinkVisible(pta, ptb, &dpi)) continue; + if (!cache_all && !this->IsLinkVisible(pta, ptb, &dpi)) return; AddLinks(sta, stb, pta, ptb, iter); - } + }); } if (cache_all || this->IsPointVisible(pta, &dpi)) { this->cached_stations.push_back({ from, supply, pta }); diff --git a/src/linkgraph/linkgraphjob.cpp b/src/linkgraph/linkgraphjob.cpp index d9c4fe4ab7..ad3e55cb8f 100644 --- a/src/linkgraph/linkgraphjob.cpp +++ b/src/linkgraph/linkgraphjob.cpp @@ -125,24 +125,25 @@ void LinkGraphJob::FinaliseJob() LinkGraph *lg = LinkGraph::Get(ge.link_graph); FlowStatMap &flows = from.Flows(); - for (EdgeIterator it(from.Begin()); it != from.End(); ++it) { - if (from[it->first].Flow() == 0) continue; - StationID to = (*this)[it->first].Station(); + this->IterateEdgesFromNode(from, [&](NodeID from_id, NodeID to_id, Edge edge) { + if (edge.Flow() == 0) return; + StationID to = (*this)[to_id].Station(); Station *st2 = Station::GetIfValid(to); + LinkGraph::ConstEdge lg_edge = lg->GetConstEdge(from_id, to_id); if (st2 == nullptr || st2->goods[this->Cargo()].link_graph != this->link_graph.index || - st2->goods[this->Cargo()].node != it->first || - (*lg)[node_id][it->first].LastUpdate() == INVALID_DATE) { + st2->goods[this->Cargo()].node != to_id || + lg_edge.LastUpdate() == INVALID_DATE) { /* Edge has been removed. Delete flows. */ StationIDStack erased = flows.DeleteFlows(to); /* Delete old flows for source stations which have been deleted * from the new flows. This avoids flow cycles between old and * new flows. */ while (!erased.IsEmpty()) ge.flows.erase(erased.Pop()); - } else if ((*lg)[node_id][it->first].LastUnrestrictedUpdate() == INVALID_DATE) { + } else if (lg_edge.LastUnrestrictedUpdate() == INVALID_DATE) { /* Edge is fully restricted. */ flows.RestrictFlows(to); } - } + }); /* Swap shares and invalidate ones that are completely deleted. Don't * really delete them as we could then end up with unroutable cargo @@ -260,7 +261,7 @@ void Path::Fork(Path *base, uint cap, int free_cap, uint dist) uint Path::AddFlow(uint new_flow, LinkGraphJob &job, uint max_saturation) { if (this->GetParent() != nullptr) { - LinkGraphJob::Edge edge = job[this->GetParent()->node][this->node]; + LinkGraphJob::Edge edge = job[this->GetParent()->node].MakeEdge(job, this->node); if (max_saturation != UINT_MAX) { uint usable_cap = edge.Capacity() * max_saturation / 100; if (usable_cap > edge.Flow()) { diff --git a/src/linkgraph/linkgraphjob.h b/src/linkgraph/linkgraphjob.h index 888068a393..1b0341021b 100644 --- a/src/linkgraph/linkgraphjob.h +++ b/src/linkgraph/linkgraphjob.h @@ -82,20 +82,19 @@ public: DynUniformArenaAllocator path_allocator; ///< Arena allocator used for paths /** - * A job edge. Wraps a link graph edge and an edge annotation. The - * annotation can be modified, the edge is constant. + * An annotation-only job edge. Wraps an edge annotation. The + * annotation can be modified. */ - class Edge : public LinkGraph::ConstEdge { + class AnnoEdge { private: EdgeAnnotation &anno; ///< Annotation being wrapped. public: /** * Constructor. - * @param edge Link graph edge to be wrapped. * @param anno Annotation to be wrapped. */ - Edge(const LinkGraph::BaseEdge &edge, EdgeAnnotation &anno) : - LinkGraph::ConstEdge(edge), anno(anno) {} + AnnoEdge(EdgeAnnotation &anno) : + anno(anno) {} /** * Get the transport demand between end the points of the edge. @@ -153,39 +152,18 @@ public: }; /** - * Iterator for job edges. + * A job edge. Wraps a link graph edge and an edge annotation. The + * annotation can be modified, the edge is constant. */ - class EdgeIterator : public LinkGraph::BaseEdgeIterator { - EdgeAnnotation *base_anno; ///< Array of annotations to be (indirectly) iterated. + class Edge : public LinkGraph::ConstEdge, public AnnoEdge { public: /** * Constructor. - * @param base Array of edges to be iterated. - * @param base_anno Array of annotations to be iterated. - * @param current Start offset of iteration. + * @param edge Link graph edge to be wrapped. + * @param anno Annotation to be wrapped. */ - EdgeIterator(const LinkGraph::BaseEdge *base, EdgeAnnotation *base_anno, NodeID current) : - LinkGraph::BaseEdgeIterator(base, current), - base_anno(base_anno) {} - - /** - * Dereference. - * @return Pair of the edge currently pointed to and the ID of its - * other end. - */ - std::pair operator*() const - { - return std::pair(this->current, Edge(this->base[this->current], this->base_anno[this->current])); - } - - /** - * Dereference. Has to be repeated here as operator* is different than - * in LinkGraph::EdgeWrapper. - * @return Fake pointer to pair of NodeID/Edge. - */ - FakePointer operator->() const { - return FakePointer(this->operator*()); - } + Edge(const LinkGraph::BaseEdge &edge, EdgeAnnotation &anno) : + LinkGraph::ConstEdge(edge), AnnoEdge(anno) {} }; /** @@ -214,21 +192,11 @@ public: * @param to Remote end of the edge. * @return Edge between this node and "to". */ - Edge operator[](NodeID to) const { return Edge(this->edges[to], this->edge_annos[to]); } + AnnoEdge operator[](NodeID to) const { return AnnoEdge(this->edge_annos[to]); } - /** - * Iterator for the "begin" of the edge array. Only edges with capacity - * are iterated. The others are skipped. - * @return Iterator pointing to the first edge. - */ - EdgeIterator Begin() const { return EdgeIterator(this->edges, this->edge_annos, index); } + Edge MakeEdge(const LinkGraph::BaseEdge &base_edge, NodeID to) const { return Edge(base_edge, this->edge_annos[to]); } - /** - * Iterator for the "end" of the edge array. Only edges with capacity - * are iterated. The others are skipped. - * @return Iterator pointing beyond the last edge. - */ - EdgeIterator End() const { return EdgeIterator(this->edges, this->edge_annos, INVALID_NODE); } + Edge MakeEdge(const LinkGraphJob &lgj, NodeID to) const { return this->MakeEdge(lgj.GetBaseEdge(this->GetNodeID(), to), to); } /** * Get amount of supply that hasn't been delivered, yet. @@ -390,6 +358,26 @@ public: * @return Link graph. */ inline const LinkGraph &Graph() const { return this->link_graph; } + + const LinkGraph::BaseEdge &GetBaseEdge(NodeID from, NodeID to) const + { + return this->link_graph.GetBaseEdge(from, to); + } + + template + void IterateEdgesFromNode(const Node &from_node, F proc) + { + auto iter = this->link_graph.GetEdges().lower_bound(std::make_pair(from_node.GetNodeID(), (NodeID)0)); + while (iter != this->link_graph.GetEdges().end()) { + NodeID from = iter->first.first; + NodeID to = iter->first.second; + if (from != from_node.GetNodeID()) return; + if (from != to) { + proc(from, to, from_node.MakeEdge(iter->second, to)); + } + ++iter; + } + } }; /** diff --git a/src/linkgraph/linkgraphjob_base.h b/src/linkgraph/linkgraphjob_base.h index a05d6ef8d6..a28c3bbcfc 100644 --- a/src/linkgraph/linkgraphjob_base.h +++ b/src/linkgraph/linkgraphjob_base.h @@ -16,6 +16,6 @@ typedef LinkGraphJob::Node Node; typedef LinkGraphJob::Edge Edge; -typedef LinkGraphJob::EdgeIterator EdgeIterator; +typedef LinkGraphJob::AnnoEdge AnnoEdge; #endif /* LINKGRAPHJOB_BASE_H */ diff --git a/src/linkgraph/mcf.cpp b/src/linkgraph/mcf.cpp index bdf4761222..fa024bdd26 100644 --- a/src/linkgraph/mcf.cpp +++ b/src/linkgraph/mcf.cpp @@ -112,8 +112,10 @@ public: class GraphEdgeIterator { private: LinkGraphJob &job; ///< Job being executed - EdgeIterator i; ///< Iterator pointing to current edge. - EdgeIterator end; ///< Iterator pointing beyond last edge. + LinkGraph::EdgeMatrix::const_iterator i; ///< Iterator pointing to current edge. + LinkGraph::EdgeMatrix::const_iterator end; ///< Iterator pointing beyond last edge. + NodeID node; ///< Source node + const LinkGraph::BaseEdge *saved; ///< Saved edge public: @@ -122,7 +124,7 @@ public: * @param job Job to iterate on. */ GraphEdgeIterator(LinkGraphJob &job) : job(job), - i(nullptr, nullptr, INVALID_NODE), end(nullptr, nullptr, INVALID_NODE) + i(LinkGraph::EdgeMatrix::const_iterator()), end(LinkGraph::EdgeMatrix::const_iterator()), node(INVALID_NODE), saved(nullptr) {} /** @@ -132,8 +134,9 @@ public: */ void SetNode(NodeID source, NodeID node) { - this->i = this->job[node].Begin(); - this->end = this->job[node].End(); + this->i = this->job.Graph().GetEdges().lower_bound(std::make_pair(node, (NodeID)0)); + this->end = this->job.Graph().GetEdges().end(); + this->node = node; } /** @@ -142,8 +145,18 @@ public: */ NodeID Next() { - return this->i != this->end ? (this->i++)->first : INVALID_NODE; + if (this->i == this->end) return INVALID_NODE; + NodeID from = this->i->first.first; + NodeID to = this->i->first.second; + if (from != this->node) return INVALID_NODE; + this->saved = &(this->i->second); + ++this->i; + return to; } + + bool SavedEdge() const { return true; } + + const LinkGraph::BaseEdge &GetSavedEdge() { return *(this->saved); } }; /** @@ -205,6 +218,10 @@ public: if (this->it == this->end) return INVALID_NODE; return this->station_to_node[(this->it++)->second]; } + + bool SavedEdge() const { return false; } + + const LinkGraph::BaseEdge &GetSavedEdge() { NOT_REACHED(); } }; /** @@ -303,7 +320,8 @@ void MultiCommodityFlow::Dijkstra(NodeID source_node, PathVector &paths) iter.SetNode(source_node, from); for (NodeID to = iter.Next(); to != INVALID_NODE; to = iter.Next()) { if (to == from) continue; // Not a real edge but a consumption sign. - Edge edge = this->job[from][to]; + const LinkGraph::BaseEdge &base_edge = iter.SavedEdge() ? iter.GetSavedEdge() : this->job.GetBaseEdge(from, to); + Edge edge = this->job[from].MakeEdge(base_edge, to); uint capacity = edge.Capacity(); if (this->max_saturation != UINT_MAX) { capacity *= this->max_saturation; @@ -379,7 +397,7 @@ void MultiCommodityFlow::CleanupPaths(NodeID source_id, PathVector &paths) * @param max_saturation If < UINT_MAX only push flow up to the given * saturation, otherwise the path can be "overloaded". */ -uint MultiCommodityFlow::PushFlow(Edge &edge, Path *path, uint accuracy, +uint MultiCommodityFlow::PushFlow(AnnoEdge &edge, Path *path, uint accuracy, uint max_saturation) { assert(edge.UnsatisfiedDemand() > 0); @@ -428,7 +446,7 @@ void MCF1stPass::EliminateCycle(PathVector &path, Path *cycle_begin, uint flow) } } cycle_begin = path[prev]; - Edge edge = this->job[prev][cycle_begin->GetNode()]; + AnnoEdge edge = this->job[prev][cycle_begin->GetNode()]; edge.RemoveFlow(flow); } while (cycle_begin != cycle_end); } @@ -556,7 +574,7 @@ MCF1stPass::MCF1stPass(LinkGraphJob &job) : MultiCommodityFlow(job) bool source_demand_left = false; for (NodeID dest = 0; dest < size; ++dest) { - Edge edge = job[source][dest]; + AnnoEdge edge = job[source][dest]; if (edge.UnsatisfiedDemand() > 0) { Path *path = paths[dest]; assert(path != nullptr); @@ -603,7 +621,7 @@ MCF2ndPass::MCF2ndPass(LinkGraphJob &job) : MultiCommodityFlow(job) bool source_demand_left = false; for (NodeID dest = 0; dest < size; ++dest) { - Edge edge = this->job[source][dest]; + AnnoEdge edge = this->job[source][dest]; Path *path = paths[dest]; if (edge.UnsatisfiedDemand() > 0 && path->GetFreeCapacity() > INT_MIN) { this->PushFlow(edge, path, accuracy, UINT_MAX); diff --git a/src/linkgraph/mcf.h b/src/linkgraph/mcf.h index 0f43fca110..f696e96fc3 100644 --- a/src/linkgraph/mcf.h +++ b/src/linkgraph/mcf.h @@ -24,7 +24,7 @@ protected: template void Dijkstra(NodeID from, PathVector &paths); - uint PushFlow(Edge &edge, Path *path, uint accuracy, uint max_saturation); + uint PushFlow(AnnoEdge &edge, Path *path, uint accuracy, uint max_saturation); void CleanupPaths(NodeID source, PathVector &paths); diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index e4028f18ff..cda70ffc77 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -176,6 +176,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_NEW_SIGNAL_STYLES, XSCF_NULL, 2, 2, "new_signal_styles", nullptr, nullptr, "XBST,NSID" }, { XSLFI_NO_TREE_COUNTER, XSCF_IGNORABLE_ALL, 1, 1, "no_tree_counter", nullptr, nullptr, nullptr }, { XSLFI_TOWN_SETTING_OVERRIDE, XSCF_NULL, 1, 1, "town_setting_override", nullptr, nullptr, nullptr }, + { XSLFI_LINKGRAPH_SPARSE_EDGES, XSCF_NULL, 1, 1, "linkgraph_sparse_edges", nullptr, nullptr, nullptr }, { XSLFI_SCRIPT_INT64, XSCF_NULL, 1, 1, "script_int64", nullptr, nullptr, nullptr }, { XSLFI_U64_TICK_COUNTER, XSCF_NULL, 1, 1, "u64_tick_counter", nullptr, nullptr, nullptr }, { XSLFI_LINKGRAPH_TRAVEL_TIME, XSCF_NULL, 1, 1, "linkgraph_travel_time", nullptr, nullptr, nullptr }, diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index fcb0e97c43..8ee5178f15 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -129,6 +129,7 @@ enum SlXvFeatureIndex { XSLFI_NEW_SIGNAL_STYLES, ///< New signal styles XSLFI_NO_TREE_COUNTER, ///< No tree counter XSLFI_TOWN_SETTING_OVERRIDE, ///< Town setting overrides + XSLFI_LINKGRAPH_SPARSE_EDGES, ///< Link graph edge matrix is stored in sparse format, and saved in order XSLFI_SCRIPT_INT64, ///< See: SLV_SCRIPT_INT64 XSLFI_U64_TICK_COUNTER, ///< See: SLV_U64_TICK_COUNTER diff --git a/src/saveload/linkgraph_sl.cpp b/src/saveload/linkgraph_sl.cpp index 0b934ee6e5..dae5f47da4 100644 --- a/src/saveload/linkgraph_sl.cpp +++ b/src/saveload/linkgraph_sl.cpp @@ -130,7 +130,7 @@ static const SaveLoad _edge_desc[] = { SLE_VAR(Edge, last_unrestricted_update, SLE_INT32), SLE_CONDVAR(Edge, last_restricted_update, SLE_INT32, SLV_187, SL_MAX_VERSION), SLE_CONDVAR_X(Edge, last_aircraft_update, SLE_INT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_LINKGRAPH_AIRCRAFT)), - SLE_VAR(Edge, next_edge, SLE_UINT16), +// SLE_VAR(Edge, next_edge, SLE_UINT16), // Removed since XSLFI_LINKGRAPH_SPARSE_EDGES }; std::vector _filtered_node_desc; @@ -151,13 +151,19 @@ static void FilterDescs() void Save_LinkGraph(LinkGraph &lg) { uint16 size = lg.Size(); + auto edge_iter = lg.edges.begin(); + auto edge_end = lg.edges.end(); for (NodeID from = 0; from < size; ++from) { Node *node = &lg.nodes[from]; SlObjectSaveFiltered(node, _filtered_node_desc); - /* ... but as that wasted a lot of space we save a sparse matrix now. */ - for (NodeID to = from; to != INVALID_NODE; to = lg.edges[from][to].next_edge) { - SlObjectSaveFiltered(&lg.edges[from][to], _filtered_edge_desc); + + while (edge_iter != edge_end && edge_iter->first.first == from) { + SlWriteUint16(edge_iter->first.second); + Edge *edge = &edge_iter->second; + SlObjectSaveFiltered(edge, _filtered_edge_desc); + ++edge_iter; } + SlWriteUint16(INVALID_NODE); } } @@ -168,19 +174,42 @@ void Save_LinkGraph(LinkGraph &lg) void Load_LinkGraph(LinkGraph &lg) { uint size = lg.Size(); - for (NodeID from = 0; from < size; ++from) { - Node *node = &lg.nodes[from]; - SlObjectLoadFiltered(node, _filtered_node_desc); - if (IsSavegameVersionBefore(SLV_191)) { + if (SlXvIsFeaturePresent(XSLFI_LINKGRAPH_SPARSE_EDGES)) { + for (NodeID from = 0; from < size; ++from) { + Node *node = &lg.nodes[from]; + SlObjectLoadFiltered(node, _filtered_node_desc); + while (true) { + NodeID to = SlReadUint16(); + if (to == INVALID_NODE) break; + SlObjectLoadFiltered(&lg.edges[std::make_pair(from, to)], _filtered_edge_desc); + } + } + } else if (IsSavegameVersionBefore(SLV_191)) { + std::vector temp_edges; + std::vector temp_next_edges; + temp_edges.resize(size); + temp_next_edges.resize(size); + for (NodeID from = 0; from < size; ++from) { + Node *node = &lg.nodes[from]; + SlObjectLoadFiltered(node, _filtered_node_desc); /* We used to save the full matrix ... */ for (NodeID to = 0; to < size; ++to) { - SlObjectLoadFiltered(&lg.edges[from][to], _filtered_edge_desc); + SlObjectLoadFiltered(&temp_edges[to], _filtered_edge_desc); + temp_next_edges[to] = SlReadUint16(); } - } else { + for (NodeID to = from; to != INVALID_NODE; to = temp_next_edges[to]) { + lg.edges[std::make_pair(from, to)] = temp_edges[to]; + } + } + } else { + for (NodeID from = 0; from < size; ++from) { + Node *node = &lg.nodes[from]; + SlObjectLoadFiltered(node, _filtered_node_desc); /* ... but as that wasted a lot of space we save a sparse matrix now. */ - for (NodeID to = from; to != INVALID_NODE; to = lg.edges[from][to].next_edge) { + for (NodeID to = from; to != INVALID_NODE;) { if (to >= size) SlErrorCorrupt("Link graph structure overflow"); - SlObjectLoadFiltered(&lg.edges[from][to], _filtered_edge_desc); + SlObjectLoadFiltered(&lg.edges[std::make_pair(from, to)], _filtered_edge_desc); + to = SlReadUint16(); } } } diff --git a/src/saveload/upstream/linkgraph_sl.cpp b/src/saveload/upstream/linkgraph_sl.cpp index db49921478..34369f0a1b 100644 --- a/src/saveload/upstream/linkgraph_sl.cpp +++ b/src/saveload/upstream/linkgraph_sl.cpp @@ -27,6 +27,7 @@ typedef LinkGraph::BaseEdge Edge; static uint16 _num_nodes; static LinkGraph *_linkgraph; ///< Contains the current linkgraph being saved/loaded. static NodeID _linkgraph_from; ///< Contains the current "from" node being saved/loaded. +static NodeID _edge_next_edge; class SlLinkgraphEdge : public DefaultSaveLoadHandler { public: @@ -36,21 +37,13 @@ public: SLE_CONDVAR(Edge, travel_time_sum, SLE_UINT64, SLV_LINKGRAPH_TRAVEL_TIME, SL_MAX_VERSION), SLE_VAR(Edge, last_unrestricted_update, SLE_INT32), SLE_CONDVAR(Edge, last_restricted_update, SLE_INT32, SLV_187, SL_MAX_VERSION), - SLE_VAR(Edge, next_edge, SLE_UINT16), + SLEG_VAR("next_edge", _edge_next_edge, SLE_UINT16), }; inline const static SaveLoadCompatTable compat_description = _linkgraph_edge_sl_compat; void Save(Node *bn) const override { - uint16 size = 0; - for (NodeID to = _linkgraph_from; to != INVALID_NODE; to = _linkgraph->edges[_linkgraph_from][to].next_edge) { - size++; - } - - SlSetStructListLength(size); - for (NodeID to = _linkgraph_from; to != INVALID_NODE; to = _linkgraph->edges[_linkgraph_from][to].next_edge) { - SlObject(&_linkgraph->edges[_linkgraph_from][to], this->GetDescription()); - } + NOT_REACHED(); } void Load(Node *bn) const override @@ -58,22 +51,18 @@ public: uint16 max_size = _linkgraph->Size(); if (IsSavegameVersionBefore(SLV_191)) { - /* We used to save the full matrix ... */ - for (NodeID to = 0; to < max_size; ++to) { - SlObject(&_linkgraph->edges[_linkgraph_from][to], this->GetLoadDescription()); - } - return; + NOT_REACHED(); } size_t used_size = IsSavegameVersionBefore(SLV_SAVELOAD_LIST_LENGTH) ? max_size : SlGetStructListLength(UINT16_MAX); /* ... but as that wasted a lot of space we save a sparse matrix now. */ - for (NodeID to = _linkgraph_from; to != INVALID_NODE; to = _linkgraph->edges[_linkgraph_from][to].next_edge) { + for (NodeID to = _linkgraph_from; to != INVALID_NODE; to = _edge_next_edge) { if (used_size == 0) SlErrorCorrupt("Link graph structure overflow"); used_size--; if (to >= max_size) SlErrorCorrupt("Link graph structure overflow"); - SlObject(&_linkgraph->edges[_linkgraph_from][to], this->GetLoadDescription()); + SlObject(&_linkgraph->edges[std::make_pair(_linkgraph_from, to)], this->GetLoadDescription()); } if (!IsSavegameVersionBefore(SLV_SAVELOAD_LIST_LENGTH) && used_size > 0) SlErrorCorrupt("Corrupted link graph"); diff --git a/src/station.cpp b/src/station.cpp index d1f7946215..4b0e08de03 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -114,7 +114,7 @@ Station::~Station() for (NodeID node = 0; node < lg->Size(); ++node) { Station *st = Station::Get((*lg)[node].Station()); st->goods[c].flows.erase(this->index); - if ((*lg)[node][this->goods[c].node].LastUpdate() != INVALID_DATE) { + if (lg->GetConstEdge(node, this->goods[c].node).LastUpdate() != INVALID_DATE) { st->goods[c].flows.DeleteFlows(this->index); RerouteCargo(st, c, this->index, st->index); } diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 49115ed331..ebb648b544 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -4405,12 +4405,14 @@ void DeleteStaleLinks(Station *from) GoodsEntry &ge = from->goods[c]; LinkGraph *lg = LinkGraph::GetIfValid(ge.link_graph); if (lg == nullptr) continue; - Node node = (*lg)[ge.node]; - for (EdgeIterator it(node.Begin()); it != node.End();) { - Edge edge = it->second; - Station *to = Station::Get((*lg)[it->first].Station()); - assert(to->goods[c].node == it->first); - ++it; // Do that before removing the edge. Anything else may crash. + lg->MutableIterateEdgesFromNode(ge.node, [&](LinkGraph::EdgeIterationHelper edge_helper) -> LinkGraph::EdgeIterationResult { + Edge edge = edge_helper.GetEdge(); + NodeID to_id = edge_helper.to_id; + + LinkGraph::EdgeIterationResult result = LinkGraph::EdgeIterationResult::None; + + Station *to = Station::Get((*lg)[to_id].Station()); + assert(to->goods[c].node == to_id); assert(_date >= edge.LastUpdate()); uint timeout = std::max((LinkGraph::MIN_TIMEOUT_DISTANCE + (DistanceManhattan(from->xy, to->xy) >> 3)) / _settings_game.economy.day_length_factor, 1); if (edge.LastAircraftUpdate() != INVALID_DATE && (uint)(_date - edge.LastAircraftUpdate()) > timeout) { @@ -4450,7 +4452,11 @@ void DeleteStaleLinks(Station *from) /* Do not refresh links of vehicles that have been stopped in depot for a long time. */ if (!v->IsStoppedInDepot() || static_cast(_date - v->date_of_last_service) <= LinkGraph::STALE_LINK_DEPOT_TIMEOUT) { + edge_helper.RecordSize(); LinkRefresher::Run(v, false); // Don't allow merging. Otherwise lg might get deleted. + if (edge_helper.RefreshIterationIfSizeChanged()) { + edge = edge_helper.GetEdge(); + } } } if (edge.LastUpdate() == _date) { @@ -4472,7 +4478,7 @@ void DeleteStaleLinks(Station *from) if (!updated) { /* If it's still considered dead remove it. */ - node.RemoveEdge(to->goods[c].node); + result = LinkGraph::EdgeIterationResult::EraseEdge; ge.flows.DeleteFlows(to->index); RerouteCargo(from, c, to->index, from->index); } @@ -4483,7 +4489,9 @@ void DeleteStaleLinks(Station *from) } else if (edge.LastRestrictedUpdate() != INVALID_DATE && (uint)(_date - edge.LastRestrictedUpdate()) > timeout) { edge.Release(); } - } + + return result; + }); assert(_date >= lg->LastCompression()); if ((uint)(_date - lg->LastCompression()) > std::max(LinkGraph::COMPRESSION_INTERVAL / _settings_game.economy.day_length_factor, 1)) { lg->Compress(); @@ -4542,7 +4550,7 @@ void IncreaseStats(Station *st, CargoID cargo, StationID next_station_id, uint c } } if (lg != nullptr) { - (*lg)[ge1.node].UpdateEdge(ge2.node, capacity, usage, time, mode); + lg->UpdateEdge(ge1.node, ge2.node, capacity, usage, time, mode); } }