Change LinkGraph::EdgeMatrix to a sparse storage format
Use a btree_map instead of a SmallMatrix. This avoids excessive storage requirements for large graphs, and overly expensive edge matrix resize operations. Remove next_edge iteration mechanism.
This commit is contained in:
@@ -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 },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<SaveLoad> _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<Edge> temp_edges;
|
||||
std::vector<NodeID> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SlLinkgraphEdge, Node> {
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user