Skip to content

Commit b7b653c

Browse files
committed
performance optimizations & new delay predictions
1 parent b7b97f9 commit b7b653c

40 files changed

+1043
-206
lines changed

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ Service polling intervals
7777

7878
- `{SERVICE}_REFRESH_INTERVAL`: Polling interval per service. Accepts TimeSpan (e.g. `00:00:05`) or seconds (e.g. `5`).
7979
Services:
80-
- `SERVER_REFRESH_INTERVAL` (default `00:00:30`)
81-
- `TRAIN_REFRESH_INTERVAL` (default `00:00:05`)
82-
- `TRAIN-POS_REFRESH_INTERVAL` (default `00:00:01`)
83-
- `TIMETABLE_REFRESH_INTERVAL` (default `01:00:00` = 1 hour)
84-
- `TIME_REFRESH_INTERVAL` (default `00:05:00` = 5 minutes)
85-
- `STATION_REFRESH_INTERVAL` (default `00:00:05`)
80+
- `SERVER_REFRESH_INTERVAL` (default `00:00:30`)
81+
- `TRAIN_REFRESH_INTERVAL` (default `00:00:05`)
82+
- `TRAIN-POS_REFRESH_INTERVAL` (default `00:00:01`)
83+
- `TIMETABLE_REFRESH_INTERVAL` (default `01:00:00` = 1 hour)
84+
- `TIME_REFRESH_INTERVAL` (default `00:05:00` = 5 minutes)
85+
- `STATION_REFRESH_INTERVAL` (default `00:00:05`)
8686

8787
Signal analyzer
8888

@@ -102,7 +102,7 @@ Route point analyzer
102102

103103
- `ROUTE_POINT_ANALYZER_DISABLED`: Disable the route point analyzer (`false`)
104104
- `ROUTE_POINT_ANALYZER_ALLOWED_SERVERS`: Comma-separated whitelist of server codes (empty = all)
105-
- `ROUTE_POINT_CLEANUP_INTERVAL_HOURS`: Hours before old route lines are removed (`48`)
105+
- `ROUTE_POINT_CLEANUP_INTERVAL_HOURS`: Hours before old route lines are removed (`72` = 3 days)
106106
- `ROUTE_POINT_MAX_BATCH_SIZE`: Batch size for DB inserts (`500`)
107107
- `ROUTE_POINT_MAX_CONCURRENCY`: Parallelism for processing (`min(4, 2x CPU)`)
108108
- `ROUTE_POINT_MIN_DISTANCE_METERS`: Min distance between points on a route (`100.0`)
@@ -137,6 +137,13 @@ This project is licensed under the AGPL-3.0 license - see the [LICENSE](LICENSE)
137137

138138
- Nimród Glöckl - [gnimrodg](https://github.com/GNimrodG/)
139139

140+
## Contributors
141+
142+
- [EMREOYUN](https://github.com/EMREOYUN)
143+
- [AMelonInsideLemon](https://github.com/AMelonInsideLemon)
144+
145+
[Full list of contributors](https://github.com/GNimrodG/simrail-map-optimized/graphs/contributors)
146+
140147
## Acknowledgments
141148

142149
SMO is inspired by the SimRail railway simulator and aims to enhance the experience of players. Thanks to the SimRail community for their support and feedback.

smo-backend/.idea/.idea.smo-backend/.idea/.gitignore

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

smo-backend/.idea/.idea.smo-backend/.idea/dictionaries/project.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

smo-backend/Analytics/GeoUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public static List<Vector2> CatmullRomSpline(List<Vector2> points, int segmentsP
8282
}
8383

8484
// Add the last original point
85-
result.Add(points[points.Count - 1]);
85+
result.Add(points[^1]);
8686
return result;
8787
}
8888

smo-backend/Analytics/RoutePointAnalyzerService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ public class RoutePointAnalyzerService : IHostedService
5757
/// Cleanup interval for old route lines in hours.
5858
/// </summary>
5959
/// <remarks>
60-
/// This value can be configured via the environment variable ROUTE_POINT_CLEANUP_INTERVAL_HOURS. Defaults to 48 hours.
60+
/// This value can be configured via the environment variable ROUTE_POINT_CLEANUP_INTERVAL_HOURS. Defaults to 72 hours (3 days).
6161
/// </remarks>
62-
private readonly int _cleanupIntervalHours = StdUtils.GetEnvVar("ROUTE_POINT_CLEANUP_INTERVAL_HOURS", 48);
62+
private readonly int _cleanupIntervalHours = StdUtils.GetEnvVar("ROUTE_POINT_CLEANUP_INTERVAL_HOURS", 72);
6363

6464
/// <summary>
6565
/// Logger instance for this service.

smo-backend/Analytics/SignalAnalyzerService.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
8888
{
8989
_logger.LogInformation("Starting signal analyzer service...");
9090

91-
await UpdateSignals(cancellationToken);
91+
await UpdateSignals(cancellationToken).NoContext();
9292

9393
_trainDataService.DataReceived += OnTrainDataReceived;
9494
}
@@ -1132,4 +1132,5 @@ private readonly struct SignalConnectionData
11321132
public required string Name { get; init; }
11331133
public required short? Vmax { get; init; }
11341134
}
1135-
}
1135+
}
1136+

smo-backend/Analytics/StationAnalyzerService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ private async Task ProcessStation(TimetableEntry station)
252252
}
253253

254254
_logger.LogInformation("Fetching OSM data for station {StationName}", station.NameOfPoint);
255-
var osmData = await _osmApiClient.GetSignalBoxByNameAsync(station.NameOfPoint);
255+
var osmData = await _osmApiClient.GetSignalBoxByNameAsync(station.NameOfPoint).NoContext();
256256

257257
if (osmData == null)
258258
{
@@ -284,7 +284,7 @@ private async Task ProcessStation(TimetableEntry station)
284284
_logger.LogInformation("Trying alternative name {AltName} for station {StationName}",
285285
altName, station.NameOfPoint);
286286

287-
osmData = await _osmApiClient.GetSignalBoxByNameAsync(altName);
287+
osmData = await _osmApiClient.GetSignalBoxByNameAsync(altName).NoContext();
288288
}
289289
}
290290

smo-backend/Analytics/TimetableAnalyzerService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public async Task StopAsync(CancellationToken cancellationToken)
7474

7575
try
7676
{
77-
await _timetableDataCache.SaveToFileAsync(StationTimetableDataFile);
77+
await _timetableDataCache.SaveToFileAsync(StationTimetableDataFile).NoContext();
7878
logger.LogInformation("Saved timetable data to file");
7979
}
8080
catch (Exception e)

smo-backend/Analytics/TrainDelayAnalyzerService.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
7171
logger.LogInformation("Timetable data service started");
7272

7373
logger.LogInformation("Waiting for time data...");
74-
await timeDataService.FirstDataReceived;
74+
await timeDataService.FirstDataReceived.NoContext();
7575
logger.LogInformation("Time data is now available, starting train delay analyzer...");
7676

7777
trainDataService.DataReceived += AnalyzeTrains;
@@ -106,13 +106,17 @@ public async Task StartAsync(CancellationToken cancellationToken)
106106
public async Task StopAsync(CancellationToken cancellationToken)
107107
{
108108
trainDataService.DataReceived -= AnalyzeTrains;
109-
await _lastCancellationTokenSource?.CancelAsync()!;
110-
_lastCancellationTokenSource?.Dispose();
109+
if (_lastCancellationTokenSource != null)
110+
{
111+
await _lastCancellationTokenSource.CancelAsync().NoContext();
112+
_lastCancellationTokenSource.Dispose();
113+
}
114+
111115
logger.LogInformation("Train delay analyzer stopped");
112116

113117
logger.LogInformation("Saving last timetable index and train delays to file...");
114-
await _lastTimetableIndex.SaveToFileAsync(LastTimetableIndexFile);
115-
await _trainDelays.SaveToFileAsync(TrainDelaysFile);
118+
await _lastTimetableIndex.SaveToFileAsync(LastTimetableIndexFile).NoContext();
119+
await _trainDelays.SaveToFileAsync(TrainDelaysFile).NoContext();
116120
logger.LogInformation("Saved last timetable index and train delays to file");
117121
}
118122

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
using SMOBackend.Models;
2+
using SMOBackend.Services;
3+
using SMOBackend.Utils;
4+
5+
namespace SMOBackend.Analytics;
6+
7+
/// <summary>
8+
/// Service that maintains a dictionary of train numbers to their train types.
9+
/// </summary>
10+
public class TrainTypeService : IHostedService
11+
{
12+
private static readonly string DataDirectory = Path.Combine(AppContext.BaseDirectory, "data", "train-types");
13+
private static readonly string TrainTypesFile = Path.Combine(DataDirectory, "train-types.bin");
14+
15+
private readonly ILogger<TrainTypeService> _logger;
16+
private readonly TimetableDataService _timetableDataService;
17+
18+
private readonly TtlCache<string, string> _trainTypeCache =
19+
new(TimeSpan.FromHours(24), "TrainTypeCache", maxEntries: 10000);
20+
21+
private TimedFunction? _autoSaveFunction;
22+
23+
public TrainTypeService(
24+
ILogger<TrainTypeService> logger,
25+
TimetableDataService timetableDataService)
26+
{
27+
_logger = logger;
28+
_timetableDataService = timetableDataService;
29+
}
30+
31+
/// <inheritdoc />
32+
public Task StartAsync(CancellationToken cancellationToken)
33+
{
34+
_logger.LogInformation("Starting train type service...");
35+
36+
try
37+
{
38+
Directory.CreateDirectory(DataDirectory);
39+
if (File.Exists(TrainTypesFile))
40+
{
41+
_trainTypeCache.LoadFromFile(TrainTypesFile);
42+
_logger.LogInformation("Loaded {Count} train types from {FilePath}",
43+
_trainTypeCache.Count, TrainTypesFile);
44+
}
45+
}
46+
catch (Exception e)
47+
{
48+
_logger.LogError(e, "Failed to load train types from file");
49+
}
50+
51+
_timetableDataService.PerServerDataReceived += OnTimetableDataReceived;
52+
53+
// Auto-save every 5 minutes
54+
_autoSaveFunction = new(SaveTrainTypes, TimeSpan.FromMinutes(5));
55+
56+
return Task.CompletedTask;
57+
}
58+
59+
/// <inheritdoc />
60+
public async Task StopAsync(CancellationToken cancellationToken)
61+
{
62+
_logger.LogInformation("Stopping train type service...");
63+
64+
_timetableDataService.PerServerDataReceived -= OnTimetableDataReceived;
65+
_autoSaveFunction?.Dispose();
66+
67+
await SaveTrainTypesAsync().NoContext();
68+
}
69+
70+
private void OnTimetableDataReceived(PerServerData<Timetable[]> data)
71+
{
72+
try
73+
{
74+
var added = 0;
75+
var updated = 0;
76+
77+
foreach (var timetable in data.Data)
78+
{
79+
if (string.IsNullOrWhiteSpace(timetable.TrainNoLocal))
80+
continue;
81+
82+
// Extract train type from the first timetable entry
83+
var trainType = timetable.TimetableEntries.FirstOrDefault()?.TrainType;
84+
85+
if (string.IsNullOrWhiteSpace(trainType))
86+
continue;
87+
88+
if (_trainTypeCache.TryGetValue(timetable.TrainNoLocal, out var existingType))
89+
{
90+
if (existingType == trainType) continue;
91+
92+
_trainTypeCache.Set(timetable.TrainNoLocal, trainType);
93+
updated++;
94+
}
95+
else
96+
{
97+
_trainTypeCache.Add(timetable.TrainNoLocal, trainType);
98+
added++;
99+
}
100+
}
101+
102+
if (added > 0 || updated > 0)
103+
{
104+
_logger.LogInformation(
105+
"Updated train type cache for server {ServerCode}: {Added} added, {Updated} updated",
106+
data.ServerCode, added, updated);
107+
}
108+
}
109+
catch (Exception ex)
110+
{
111+
_logger.LogError(ex, "Error processing timetable data for train types");
112+
}
113+
}
114+
115+
/// <summary>
116+
/// Gets the train type for a given train number.
117+
/// </summary>
118+
/// <param name="trainNoLocal">The local train number.</param>
119+
/// <returns>The train type, or null if not found.</returns>
120+
public string? GetTrainType(string trainNoLocal) =>
121+
_trainTypeCache.TryGetValue(trainNoLocal, out var trainType) ? trainType : null;
122+
123+
private void SaveTrainTypes()
124+
{
125+
try
126+
{
127+
_trainTypeCache.SaveToFileAsync(TrainTypesFile).Wait();
128+
_logger.LogDebug("Saved {Count} train types to {FilePath}",
129+
_trainTypeCache.Count, TrainTypesFile);
130+
}
131+
catch (Exception ex)
132+
{
133+
_logger.LogError(ex, "Failed to save train types to file");
134+
}
135+
}
136+
137+
private async Task SaveTrainTypesAsync()
138+
{
139+
try
140+
{
141+
await _trainTypeCache.SaveToFileAsync(TrainTypesFile).NoContext();
142+
_logger.LogInformation("Saved {Count} train types to {FilePath}",
143+
_trainTypeCache.Count, TrainTypesFile);
144+
}
145+
catch (Exception ex)
146+
{
147+
_logger.LogError(ex, "Failed to save train types to file");
148+
}
149+
}
150+
}

0 commit comments

Comments
 (0)