Skip to content

Commit

Permalink
More timecode file replay work
Browse files Browse the repository at this point in the history
  • Loading branch information
mayfield committed Apr 18, 2024
1 parent 4082d56 commit ca0c650
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 33 deletions.
4 changes: 3 additions & 1 deletion src/headless.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import os from 'node:os';
import fs from 'node:fs';
import path from 'node:path';
import childProcess from 'node:child_process';
import {EventEmitter} from 'node:events';
import * as storage from './storage.mjs';
import * as rpc from './rpc.mjs';
import * as zwift from './zwift.mjs';
Expand Down Expand Up @@ -123,10 +124,11 @@ async function main() {
zwiftAPI.authenticate(args.mainUsername, args.mainPassword),
zwiftMonitorAPI.authenticate(args.monitorUsername, args.monitorPassword),
]);
await mods.init(path.join(os.homedir(), 'Documents', 'SauceMods'));
await mods.init(path.join(os.homedir(), 'Documents', 'SauceMods'), path.join(appPath, 'mods'));
const sauceApp = new NodeSauceApp({appPath});
sauceApp.rpcEventEmitters.set('logs', logEmitter);
sauceApp.rpcEventEmitters.set('mods', mods.eventEmitter);
sauceApp.rpcEventEmitters.set('windows', new EventEmitter());
rpc.register(() => logQueue, {name: 'getLogs'});
rpc.register(() => logQueue.length = 0, {name: 'clearLogs'});
rpc.register(() => () => console.warn("File logging disabled for headless mode"),
Expand Down
85 changes: 53 additions & 32 deletions src/stats.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ class ActivityReplay extends events.EventEmitter {
return;
}
this.playing = true;
this.emitTimeSync();
this._stopEvent.clear();
this._playPromise = this._playLoop();
}
Expand All @@ -433,18 +434,22 @@ class ActivityReplay extends events.EventEmitter {
this.playing = false;
this._stopEvent.set();
await this._playPromise;
this.emit('timesync', null);
this.emitTimeSync();
}

emitNextRecord() {
if (this.pos >= this.streams.time.length) {
this.emit('timesync', null);
return;
}
const i = this.pos++;
const time = this.streams.time[i];
emitTimeSync() {
const ts = this.streams.time[Math.max(0, Math.min(this.streams.time.length - 1, this.pos))];
this.emit('timesync', {
time: ts ? ts - this.startTime : ts,
playing: this.playing,
position: this.pos,
});
}

emitRecord() {
const i = this.pos;
this.emit('record', {
time,
time: this.streams.time[i],
power: this.streams.power && this.streams.power[i],
cadence: this.streams.cadence && this.streams.cadence[i],
latlng: this.streams.latlng && this.streams.latlng[i],
Expand All @@ -453,20 +458,21 @@ class ActivityReplay extends events.EventEmitter {
heartrate: this.streams.hr && this.streams.hr[i],
altitude: this.streams.altitude && this.streams.altitude[i],
});
this.emit('timesync', time - this.startTime);
return i;
}

async _playLoop() {
while (this.playing) {
const pos = this.emitNextRecord();
if (pos === undefined) {
if (this.pos >= this.streams.time.length) {
this.playing = false;
break;
this.emitTimeSync('playloop exit');
return;
}
const nextTime = this.streams.time[pos + 1];
this.emitRecord();
this.emitTimeSync('playloop outer');
this.pos++;
const nextTime = this.streams.time[this.pos];
if (nextTime !== undefined) {
const next = (nextTime - this.streams.time[pos]) * 1000 + monotonic();
const next = (nextTime - this.streams.time[this.pos - 1]) * 1000 + monotonic();
await Promise.race([
highResSleepTill(next),
this._stopEvent.wait(),
Expand All @@ -480,23 +486,29 @@ class ActivityReplay extends events.EventEmitter {
if (this.pos < 0) {
this.pos = 0;
}
this.emit('timesync', this.streams.time[this.pos] - this.startTime);
this.emitTimeSync('rewind');
}

forward(steps) {
this.pos += steps;
if (this.pos > this.streams.time.length) {
if (this.pos >= this.streams.time.length) {
this.playing = false;
this.pos = this.streams.time.length;
}
this.emit('timesync', this.streams.time[this.pos] - this.startTime);
this.emitTimeSync('forward');
}

fastForward(steps) {
for (let i = 0; i < steps; i++) {
if (this.emitNextRecord() === undefined) {
for (let i = 0; i < steps && this.pos < this.streams.time.length; i++) {
this.emitRecord();
this.pos++;
if (this.pos >= this.streams.time.length) {
this.playing = false;
this.pos = this.streams.time.length;
break;
}
}
this.emitTimeSync();
}
}

Expand Down Expand Up @@ -645,8 +657,10 @@ export class StatsProcessor extends events.EventEmitter {
const ad = this._athleteData.get(athleteId);
if (this._preprocessState(fakeState, ad) !== false) {
this._recordAthleteStats(fakeState, ad);
this._pendingEgressStates.set(fakeState.athleteId, fakeState);
this._schedStatesEmit();
if (this.listenerCount('states')) {
this._pendingEgressStates.set(fakeState.athleteId, fakeState);
this._schedStatesEmit();
}
}
});
this._activityReplay.on('timesync', (...args) => this.emit('file-replay-timesync', ...args));
Expand Down Expand Up @@ -1525,13 +1539,6 @@ export class StatsProcessor extends events.EventEmitter {
if (updatedEvents.length) {
queueMicrotask(() => this._loadEvents(updatedEvents));
}
for (let i = 0; i < packet.playerStates.length; i++) {
const x = packet.playerStates[i];
if (this.processState(x) === false) {
continue;
}
this._pendingEgressStates.set(x.athleteId, x);
}
if (packet.eventPositions) {
const ep = packet.eventPositions;
// There are several groups of fields on eventPositions, but I don't understand/see them.
Expand All @@ -1554,7 +1561,19 @@ export class StatsProcessor extends events.EventEmitter {
ad.eventParticipants = ep.activeAthleteCount;
}
}
this._schedStatesEmit();
const hasStatesListeners = !!this.listenerCount('states');
for (let i = 0; i < packet.playerStates.length; i++) {
const x = packet.playerStates[i];
if (this.processState(x) === false) {
continue;
}
if (hasStatesListeners) {
this._pendingEgressStates.set(x.athleteId, x);
}
}
if (hasStatesListeners) {
this._schedStatesEmit();
}
}

_schedStatesEmit() {
Expand All @@ -1565,7 +1584,6 @@ export class StatsProcessor extends events.EventEmitter {
}

_flushPendingEgressStates() {
console.log("emit");
const states = Array.from(this._pendingEgressStates.values()).map(x => this._cleanState(x));
this._pendingEgressStates.clear();
this._lastEgressStates = monotonic();
Expand Down Expand Up @@ -2411,6 +2429,9 @@ export class StatsProcessor extends events.EventEmitter {
const b = bData.roadHistory;
const aTail = a.timeline[a.timeline.length - 1];
const bTail = b.timeline[b.timeline.length - 1];
if (aTail === undefined || bTail === undefined) {
return null;
}
const aComp = aTail.roadCompletion;
const bComp = bTail.roadCompletion;
// Is A currently leading B or vice versa...
Expand Down

0 comments on commit ca0c650

Please sign in to comment.