mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-01 20:19:39 +00:00
Migration to new Rust-based engine (#401)
* wasm first steps * parsing start * parsing continued * progress * progress * progress * progress * progress * progress * progress * progress * fixes * reorganize * change type * fix * progress * refactoring * progress * progress * progress * keep style info * progress * progress * remove (copy) in name * progress * merge methods into 1 * splice * progress * progress * progress * cleaning * progress * progress * progress * visibility * progress * progress * progress * progress * progress * fixes * progress * fix markers * fix * progress * progress * progress * progress * progress * progress * small fix * use engine info * fix popups * small fix * progress * progress * progress * clean tool * handle self closing elements * reduce data exchanges across boundary * copy on wasm side to avoid any data corruption * speed fix * routing * refactoring * reverse * reverse * crop * split * merge * progress * time tool * fix bounds for waypoints * reduce * fix reactivity * handle corrupt time data * export * progress * remove gpx js package * remove todo * update readme * cleaning * progress * fix link fields * remove unused fields * refactoring and tests * import and export of routes, ref #192 * per-chunk categories storage * cross-tab sync * outdated state * refactoring * reorder methods * refactoring * fix * improve flow * renaming * progress * progress * fix * progress * per file history * keep original flag * use history variant * fix * progress * file explorer * progress * fix * sticky folders * improved sticky folders * fix sortable * fix sortable * fix * fix tab size * no file delete on desktop * fix routing on other file * fix sorting * remember language * open links in browser * commit missing changes * avoid selecting on resize * clarify filename * explorer actions * fix folder styling * drop anywhere in folder * cleaning * fixes * remove focus-ring * full screen * small fix * consistent highlight color * improved preview * performance improvements * sparser data transfers * sparser data transfers * show popup once data is loaded * fix routing control size * fix * sparse desktop build * remove custom ign styles * use new endpoint for openhikingmap * strip native menu * reorder context menu items * route full support * route - track conversion * create route file action * route export, filter out anchors at high zoom level * clarify name handling * move hide & center to view * routes continued * update docs * fix power format, closes #378 * lint fixes * fix display of additional data with gaps, closes #361 * fix linting issues * fix import modules * fix multidrag * change base to resolve * fix type errors * fix layer typings * fix last type error * each keys * improve typings * deploy to cloudflare --------- Co-authored-by: vcoppe <vcoppe@hexaly.com>
This commit is contained in:
1 parent
6a0d434371
commit
8fe7d8d64d
484 files changed
+53949
-41152
No files matched your search
@@ -0,0 +1,214 @@
|
||||
import { get, writable, type Readable } from 'svelte/store';
|
||||
import type { CategoryIntervals } from '$lib/engine/trackpoint-categories';
|
||||
import type { Backend } from './backend';
|
||||
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>();
|
||||
|
||||
/** The engine, as of the last time the statistics were read. */
|
||||
private _backend: Backend | null = null;
|
||||
|
||||
/** @param enqueue Does a task after the calls to the engine that were made before it. */
|
||||
constructor(private enqueue: <T>(task: () => Promise<T>) => Promise<T>) {}
|
||||
|
||||
/**
|
||||
* Reads the statistics of the selection again, after it or the files changed. Resolves to what
|
||||
* puts them in the store, for the caller to do it together with its other updates.
|
||||
*/
|
||||
async refresh(backend: Backend): Promise<() => void> {
|
||||
this._backend = backend;
|
||||
const statistics = await this.read(backend);
|
||||
return () => this._store.set(statistics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the statistics buffers of the engine (the getters copy them), but only the metrics
|
||||
* that were requested.
|
||||
*/
|
||||
private async read(backend: Backend): Promise<SelectionStatistics> {
|
||||
const version = ++this._version;
|
||||
this._loaded.clear();
|
||||
const [global, totalDistance, lng, lat, ele] = await Promise.all([
|
||||
backend.selection_statistics(),
|
||||
backend.total_distance(),
|
||||
backend.lng(),
|
||||
backend.lat(),
|
||||
backend.ele(),
|
||||
]);
|
||||
const current = <T>(read: () => Promise<T | undefined>) =>
|
||||
this.enqueue(async () => (version === this._version ? read() : undefined));
|
||||
const statistics: SelectionStatistics = {
|
||||
global: global ?? EMPTY_STATISTICS,
|
||||
length: totalDistance.length,
|
||||
totalDistance,
|
||||
lng,
|
||||
lat,
|
||||
ele,
|
||||
slice: (start, end) => current(() => backend.slice_statistics(start, end)),
|
||||
point: (index) => current(() => backend.selection_point(index)),
|
||||
};
|
||||
await this.readMetrics(backend, 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 async readMetrics(
|
||||
backend: Backend,
|
||||
statistics: SelectionStatistics,
|
||||
metrics: Set<StatisticsMetric>
|
||||
) {
|
||||
const categories = async (
|
||||
starts: Promise<Uint32Array>,
|
||||
values: Promise<Uint8Array>,
|
||||
names: Promise<string[]>
|
||||
): Promise<CategoryIntervals> => {
|
||||
const [s, v, n] = await Promise.all([starts, values, names]);
|
||||
return { starts: s, values: v, names: n };
|
||||
};
|
||||
|
||||
const missing = [...metrics].filter((metric) => !this._loaded.has(metric));
|
||||
missing.forEach((metric) => this._loaded.add(metric));
|
||||
await Promise.all(
|
||||
missing.map(async (metric) => {
|
||||
switch (metric) {
|
||||
case 'speed':
|
||||
statistics.speed = await backend.speed();
|
||||
break;
|
||||
case 'hr':
|
||||
statistics.hr = await backend.hr();
|
||||
break;
|
||||
case 'cad':
|
||||
statistics.cad = await backend.cad();
|
||||
break;
|
||||
case 'atemp':
|
||||
statistics.atemp = await backend.atemp();
|
||||
break;
|
||||
case 'power':
|
||||
statistics.power = await backend.power();
|
||||
break;
|
||||
case 'slopeSegment': {
|
||||
const [starts, slopes] = await Promise.all([
|
||||
backend.slope_segment_starts(),
|
||||
backend.slope_segment_slopes(),
|
||||
]);
|
||||
statistics.slopeSegments = { starts, slopes };
|
||||
break;
|
||||
}
|
||||
case 'surface':
|
||||
statistics.surface = await categories(
|
||||
backend.surface_starts(),
|
||||
backend.surface_values(),
|
||||
backend.surfaces()
|
||||
);
|
||||
break;
|
||||
case 'anchors': {
|
||||
const [indices, zooms, segmentStarts, segmentIds, revision] =
|
||||
await Promise.all([
|
||||
backend.anchor_indices(),
|
||||
backend.anchor_zooms(),
|
||||
backend.segment_starts(),
|
||||
backend.segment_ids(),
|
||||
backend.routing_revision(),
|
||||
]);
|
||||
statistics.anchors = {
|
||||
indices,
|
||||
zooms,
|
||||
segmentStarts,
|
||||
segmentIds,
|
||||
revision,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'highway':
|
||||
[statistics.highway, statistics.sacScale, statistics.mtbScale] =
|
||||
await Promise.all([
|
||||
categories(
|
||||
backend.highway_starts(),
|
||||
backend.highway_values(),
|
||||
backend.highways()
|
||||
),
|
||||
categories(
|
||||
backend.sac_scale_starts(),
|
||||
backend.sac_scale_values(),
|
||||
backend.sac_scales()
|
||||
),
|
||||
categories(
|
||||
backend.mtb_scale_starts(),
|
||||
backend.mtb_scale_values(),
|
||||
backend.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);
|
||||
}
|
||||
return this.loadMissing();
|
||||
},
|
||||
release: () => {
|
||||
this._requests.delete(wanted);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Reads the requested metrics that are not in the current statistics yet. */
|
||||
private async loadMissing() {
|
||||
const metrics = this.requestedMetrics();
|
||||
if ([...metrics].every((metric) => this._loaded.has(metric))) {
|
||||
return;
|
||||
}
|
||||
// queued, so that the buffers of the engine still are the ones of the current statistics:
|
||||
// they are read after every action
|
||||
await this.enqueue(async () => {
|
||||
const backend = this._backend;
|
||||
const requested = this.requestedMetrics();
|
||||
if (!backend || [...requested].every((metric) => this._loaded.has(metric))) {
|
||||
return;
|
||||
}
|
||||
const statistics = { ...get(this._store) };
|
||||
await this.readMetrics(backend, statistics, requested);
|
||||
this._store.set(statistics);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user