mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-09 23:26:31 +00:00
refactoring and tests
This commit is contained in:
1 parent
3bb553d6b5
commit
da9abbae5e
45 files changed
+3558
-1564
No files matched your search
@@ -0,0 +1,179 @@
|
||||
import { get, writable, type Readable } from 'svelte/store';
|
||||
import { categoryIntervals, type CategoryIntervals } from '$lib/trackpoint-categories';
|
||||
import type { Wasm } from './convert';
|
||||
import {
|
||||
EMPTY_SELECTION_STATISTICS,
|
||||
EMPTY_STATISTICS,
|
||||
type SelectionStatistics,
|
||||
type StatisticsMetric,
|
||||
type StatisticsRequest,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* The statistics of the selection, read from the buffers of the engine. Only the metrics that a
|
||||
* consumer asked for are read (see `request`).
|
||||
*/
|
||||
export class StatisticsReader {
|
||||
private _store = writable<SelectionStatistics>(EMPTY_SELECTION_STATISTICS);
|
||||
readonly store: Readable<SelectionStatistics> = { subscribe: this._store.subscribe };
|
||||
/** Identifies the statistics currently in the engine's buffers. */
|
||||
private _version = 0;
|
||||
/** What each consumer of the statistics asked for. */
|
||||
private _requests = new Set<Set<StatisticsMetric>>();
|
||||
/** The metrics that were read for the current statistics. */
|
||||
private _loaded = new Set<StatisticsMetric>();
|
||||
|
||||
/** @param wasm The engine, `null` before it is loaded. */
|
||||
constructor(private wasm: () => Wasm | null) {}
|
||||
|
||||
/** Reads the statistics of the selection again, after it or the files changed. */
|
||||
refresh(wasm: Wasm) {
|
||||
this._store.set(this.read(wasm));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the statistics buffers of the engine (the getters copy them), but only the metrics
|
||||
* that were requested.
|
||||
*/
|
||||
private read(wasm: Wasm): SelectionStatistics {
|
||||
const version = ++this._version;
|
||||
this._loaded.clear();
|
||||
const totalDistance = wasm.total_distance();
|
||||
const statistics: SelectionStatistics = {
|
||||
global: wasm.selection_statistics() ?? EMPTY_STATISTICS,
|
||||
length: totalDistance.length,
|
||||
totalDistance,
|
||||
slope: wasm.slope(),
|
||||
lng: wasm.lng(),
|
||||
lat: wasm.lat(),
|
||||
ele: wasm.ele(),
|
||||
timestamps: wasm.timestamps(),
|
||||
slice: (start, end) =>
|
||||
version === this._version ? wasm.slice_statistics(start, end) : undefined,
|
||||
};
|
||||
this.readMetrics(wasm, statistics, this.requestedMetrics());
|
||||
return statistics;
|
||||
}
|
||||
|
||||
private requestedMetrics(): Set<StatisticsMetric> {
|
||||
const metrics = new Set<StatisticsMetric>();
|
||||
this._requests.forEach((request) => request.forEach((m) => metrics.add(m)));
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/** Reads the metrics that were not read yet into `statistics`, which is the current one. */
|
||||
private readMetrics(
|
||||
wasm: Wasm,
|
||||
statistics: SelectionStatistics,
|
||||
metrics: Set<StatisticsMetric>
|
||||
) {
|
||||
const categories = (
|
||||
starts: Uint32Array,
|
||||
values: Uint8Array,
|
||||
names: () => string[]
|
||||
): CategoryIntervals => ({
|
||||
starts,
|
||||
values,
|
||||
names: names(),
|
||||
});
|
||||
|
||||
for (const metric of metrics) {
|
||||
if (this._loaded.has(metric)) {
|
||||
continue;
|
||||
}
|
||||
this._loaded.add(metric);
|
||||
switch (metric) {
|
||||
case 'speed':
|
||||
statistics.speed = wasm.speed();
|
||||
break;
|
||||
case 'hr':
|
||||
statistics.hr = wasm.hr();
|
||||
break;
|
||||
case 'cad':
|
||||
statistics.cad = wasm.cad();
|
||||
break;
|
||||
case 'atemp':
|
||||
statistics.atemp = wasm.atemp();
|
||||
break;
|
||||
case 'power':
|
||||
statistics.power = wasm.power();
|
||||
break;
|
||||
case 'slopeSegment':
|
||||
statistics.slopeSegmentSlope = wasm.slope_segment_slope();
|
||||
statistics.slopeSegmentDistance = wasm.slope_segment_distance();
|
||||
break;
|
||||
case 'surface':
|
||||
statistics.surface = categories(
|
||||
wasm.surface_starts(),
|
||||
wasm.surface_values(),
|
||||
wasm.surfaces
|
||||
);
|
||||
break;
|
||||
case 'anchors':
|
||||
statistics.anchors = {
|
||||
indices: wasm.anchor_indices(),
|
||||
zooms: wasm.anchor_zooms(),
|
||||
segmentStarts: wasm.segment_starts(),
|
||||
segmentIds: wasm.segment_ids(),
|
||||
revision: wasm.routing_revision(),
|
||||
};
|
||||
break;
|
||||
case 'highway':
|
||||
statistics.highway = categories(
|
||||
wasm.highway_starts(),
|
||||
wasm.highway_values(),
|
||||
wasm.highways
|
||||
);
|
||||
statistics.sacScale = categories(
|
||||
wasm.sac_scale_starts(),
|
||||
wasm.sac_scale_values(),
|
||||
wasm.sac_scales
|
||||
);
|
||||
statistics.mtbScale = categories(
|
||||
wasm.mtb_scale_starts(),
|
||||
wasm.mtb_scale_values(),
|
||||
wasm.mtb_scales
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a consumer of the statistics that needs some of the metrics that are not part of
|
||||
* them by default (see `StatisticsMetric`). The metrics asked for by all the consumers are
|
||||
* added to `statistics`: the ones that are missing are read right away, and then after each
|
||||
* change of the selection or of the files, and the store is updated. Call `release` when the
|
||||
* consumer goes away.
|
||||
*/
|
||||
request(): StatisticsRequest {
|
||||
const wanted = new Set<StatisticsMetric>();
|
||||
this._requests.add(wanted);
|
||||
return {
|
||||
set: (metrics) => {
|
||||
wanted.clear();
|
||||
for (const metric of metrics) {
|
||||
wanted.add(metric);
|
||||
}
|
||||
this.loadMissing();
|
||||
},
|
||||
release: () => {
|
||||
this._requests.delete(wanted);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Reads the requested metrics that are not in the current statistics yet. */
|
||||
private loadMissing() {
|
||||
const wasm = this.wasm();
|
||||
const metrics = this.requestedMetrics();
|
||||
if (!wasm || [...metrics].every((metric) => this._loaded.has(metric))) {
|
||||
return;
|
||||
}
|
||||
// the buffers of the engine still are the ones of the current statistics, as they are
|
||||
// read after every action
|
||||
const statistics = { ...get(this._store) };
|
||||
this.readMetrics(wasm, statistics, metrics);
|
||||
this._store.set(statistics);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user