simutrans-0001: rebuild_linked_connections() append_unique O(C*H^2) MEDIUM 97x
- vector_tpl::append_unique linear scan inside double loop over
goods categories x connections to collect unique connected halts
- fix: inthashtable_tpl for O(1) membership test
simutrans-0002: add_grund() registered_convoys.is_contained O(C*R) MEDIUM 45x
- iterates ALL world convoys, each with linear scan of registered
convoy vector to check membership
- fix: pre-build hash set of registered convoy IDs for O(1) lookup
simutrans-0003: rebuild_connections() consecutive_halts append_unique O(S^2) MEDIUM 24x
- append_unique on consecutive halt vectors per category inside
nested loop over schedules x entries during halt reconnection
- fix: parallel inthashtable_tpl for O(1) dedup
All three defects are in simhalt.cc halt reconnection paths, triggered
whenever schedules change (line added/removed, schedule edited, station
built). In large games with hundreds of halts and convoys, these
compound during reconnection sweeps.
MOAD-0002: welt (karte_t) is a god object but standard Simutrans architecture
MOAD-0003: CLEAN (no thread_local usage)
MOAD-0004: CLEAN (nettool password printf is by-design tool output)
MOAD-0005: CLEAN (save cache uses hashtable, no unsynchronized pattern)
91 lines
3.1 KiB
C++
91 lines
3.1 KiB
C++
// Unit test for simutrans-0002: add_grund() registered_convoys.is_contained O(C*R)
|
|
// Simulates scanning all world convoys against a halt's registered convoy list.
|
|
// Defect: vector is_contained (linear scan) per world convoy = O(C * R).
|
|
// Fix: hash set for O(1) membership test = O(C + R).
|
|
|
|
#include <cstdio>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <vector>
|
|
#include <unordered_set>
|
|
#include <cassert>
|
|
|
|
// --- Defect version: linear scan is_contained ---
|
|
static int defect_scan_convoys(
|
|
const std::vector<uint16_t>& world_convoys,
|
|
const std::vector<uint16_t>& registered_convoys,
|
|
std::vector<uint16_t>& new_registrations)
|
|
{
|
|
int ops = 0;
|
|
for (uint16_t cnv : world_convoys) {
|
|
// simulate: is lineless convoy (skip half) and ownership match (always)
|
|
if (cnv % 2 != 0) continue;
|
|
|
|
// is_contained: linear scan of registered_convoys
|
|
bool found = false;
|
|
for (uint16_t reg : registered_convoys) {
|
|
ops++;
|
|
if (reg == cnv) { found = true; break; }
|
|
}
|
|
if (!found) {
|
|
// simulate: check schedule entries and register
|
|
new_registrations.push_back(cnv);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// --- Fixed version: hash set for O(1) lookup ---
|
|
static int fixed_scan_convoys(
|
|
const std::vector<uint16_t>& world_convoys,
|
|
const std::vector<uint16_t>& registered_convoys,
|
|
std::vector<uint16_t>& new_registrations)
|
|
{
|
|
int ops = 0;
|
|
std::unordered_set<uint16_t> reg_set(registered_convoys.begin(), registered_convoys.end());
|
|
for (uint16_t cnv : world_convoys) {
|
|
if (cnv % 2 != 0) continue;
|
|
ops++;
|
|
if (reg_set.find(cnv) == reg_set.end()) {
|
|
new_registrations.push_back(cnv);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
int main() {
|
|
// Simulate: 500 world convoys, 50 already registered at this halt
|
|
const int WORLD_CONVOYS = 500;
|
|
const int REGISTERED = 50;
|
|
|
|
std::vector<uint16_t> world_convoys(WORLD_CONVOYS);
|
|
for (int i = 0; i < WORLD_CONVOYS; i++) {
|
|
world_convoys[i] = (uint16_t)i;
|
|
}
|
|
|
|
// First 50 even-numbered convoys are already registered
|
|
std::vector<uint16_t> registered;
|
|
for (int i = 0; i < REGISTERED; i++) {
|
|
registered.push_back((uint16_t)(i * 2));
|
|
}
|
|
|
|
std::vector<uint16_t> new_reg_defect, new_reg_fixed;
|
|
int defect_ops = defect_scan_convoys(world_convoys, registered, new_reg_defect);
|
|
int fixed_ops = fixed_scan_convoys(world_convoys, registered, new_reg_fixed);
|
|
|
|
double ratio = (double)defect_ops / (double)fixed_ops;
|
|
|
|
printf("=== simutrans-0002: add_grund() convoy registration scan ===\n");
|
|
printf("World convoys: %d, registered: %d\n", WORLD_CONVOYS, REGISTERED);
|
|
printf("Defect ops (linear scan): %d\n", defect_ops);
|
|
printf("Fixed ops (hash set): %d\n", fixed_ops);
|
|
printf("Ratio: %.1fx\n", ratio);
|
|
|
|
// Both versions should find the same new registrations
|
|
assert(new_reg_defect.size() == new_reg_fixed.size());
|
|
printf("New registrations: %zu (both versions agree)\n", new_reg_defect.size());
|
|
|
|
assert(ratio > 5.0);
|
|
printf("PASS\n");
|
|
return 0;
|
|
}
|