refactor: cleanup store (#727)

* refactor: isolate clock

* refactor: isolate private

* refactor: remove duplicate

* chore: rename loaded > runtime

* fix: handle no events loaded
This commit is contained in:
Carlos Valente
2024-01-24 00:20:10 +01:00
committed by GitHub
parent 0cd2ca4191
commit b4ad533e6a
31 changed files with 411 additions and 384 deletions
+4 -19
View File
@@ -157,28 +157,13 @@ export const startServer = async () => {
/**
* Module initialises the services and provides initial payload for the store
* Currently registered objects in store
*
* - Message Service timerMessage
* - Message Service publicMessage
* - Message Service lowerMessage
* - Message Service externalMessage
*
* - Runtime Service onAir (derived from playback)
* - Runtime Service timer
* - Runtime Service playback
* - Runtime Service loaded // TODO: rename to runtime ??
* - Runtime Service eventNow
* - Runtime Service publicEventNow
* - Runtime Service eventNext
* - Runtime Service publicEventNext
*/
eventStore.init({
message: messageService,
onAir: state.playback !== Playback.Stop,
clock: state.clock,
timer: state.timer,
playback: state.playback,
loaded: state.runtime,
onAir: state.timer.playback !== Playback.Stop,
message: messageService.getState(),
runtime: state.runtime,
eventNow: state.eventNow,
publicEventNow: state.publicEventNow,
eventNext: state.eventNext,
@@ -66,7 +66,7 @@ export class EventLoader {
* finds the previous event
* @return {object | undefined}
*/
static findPrevious(currentEventId: string | null): OntimeEvent | null {
static findPrevious(currentEventId?: string): OntimeEvent | null {
const timedEvents = EventLoader.getPlayableEvents();
if (!timedEvents || !timedEvents.length) {
return null;
@@ -87,7 +87,7 @@ export class EventLoader {
* finds the next event
* @return {object | undefined}
*/
static findNext(currentEventId: string | null): OntimeEvent | null {
static findNext(currentEventId?: string): OntimeEvent | null {
const timedEvents = EventLoader.getPlayableEvents();
if (!timedEvents || !timedEvents.length) {
return null;
+5 -5
View File
@@ -22,11 +22,11 @@ export class TimerService {
}
start() {
if (!state.runtime.selectedEventId) {
if (!state.eventNow) {
return;
}
if (state.playback === Playback.Play) {
if (state.timer.playback === Playback.Play) {
return;
}
@@ -36,14 +36,14 @@ export class TimerService {
}
pause() {
if (state.playback !== Playback.Play) {
if (state.timer.playback !== Playback.Play) {
return;
}
stateMutations.timer.pause();
}
stop() {
if (state.playback === Playback.Stop) {
if (state.timer.playback === Playback.Stop) {
return;
}
stateMutations.timer.stop();
@@ -54,7 +54,7 @@ export class TimerService {
* @param {number} amount
*/
addTime(amount: number) {
if (state.runtime.selectedEventId === null) {
if (state.eventNow === null) {
return;
}
stateMutations.timer.addTime(amount);
@@ -16,14 +16,16 @@ describe('getExpectedFinish()', () => {
const state = {
eventNow: {
timeEnd: 10,
timerType: TimerType.CountDown,
},
timer: {
addedTime: 0,
duration: 10,
finishedAt: null,
pausedAt: null,
startedAt: null,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
const calculatedFinish = getExpectedFinish(state);
@@ -33,14 +35,16 @@ describe('getExpectedFinish()', () => {
const state = {
eventNow: {
timeEnd: 20,
timerType: TimerType.CountDown,
},
timer: {
addedTime: 0,
duration: 10,
finishedAt: 20, // <---- finished at
pausedAt: null,
startedAt: 10,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
const calculatedFinish = getExpectedFinish(state);
@@ -50,14 +54,16 @@ describe('getExpectedFinish()', () => {
const state = {
eventNow: {
timeEnd: 11,
timerType: TimerType.CountDown,
},
timer: {
addedTime: 0,
duration: 10,
finishedAt: null,
pausedAt: null,
startedAt: 1,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
const calculatedFinish = getExpectedFinish(state);
@@ -67,14 +73,16 @@ describe('getExpectedFinish()', () => {
const state = {
eventNow: {
timeEnd: 11,
timerType: TimerType.CountDown,
},
timer: {
addedTime: 20,
duration: 10,
finishedAt: null,
pausedAt: null,
startedAt: 1,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
@@ -85,14 +93,16 @@ describe('getExpectedFinish()', () => {
const state = {
eventNow: {
timeEnd: 11,
timerType: TimerType.CountDown,
},
timer: {
addedTime: -10,
duration: 10,
finishedAt: null,
pausedAt: null,
startedAt: 1,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
@@ -103,14 +113,16 @@ describe('getExpectedFinish()', () => {
const state = {
eventNow: {
timeEnd: 11,
timerType: TimerType.CountDown,
},
timer: {
addedTime: -100,
duration: 10,
finishedAt: null,
pausedAt: null,
startedAt: 1,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
@@ -121,14 +133,16 @@ describe('getExpectedFinish()', () => {
const state = {
eventNow: {
timeEnd: 0,
timerType: TimerType.CountDown,
},
timer: {
addedTime: 0,
duration: 0,
finishedAt: null,
pausedAt: null,
startedAt: 1,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
@@ -139,14 +153,16 @@ describe('getExpectedFinish()', () => {
const state = {
eventNow: {
timeEnd: 10,
timerType: TimerType.CountDown,
},
timer: {
addedTime: 0,
duration: dayInMs,
finishedAt: null,
pausedAt: null,
startedAt: 10,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
@@ -158,14 +174,16 @@ describe('getExpectedFinish()', () => {
const state = {
eventNow: {
timeEnd: 30,
timerType: TimerType.TimeToEnd,
},
timer: {
addedTime: 10,
duration: dayInMs,
finishedAt: null,
pausedAt: null,
startedAt: 10,
timerType: TimerType.TimeToEnd,
},
_timer: {
pausedAt: null,
},
} as TState;
@@ -176,13 +194,15 @@ describe('getExpectedFinish()', () => {
const state = {
eventNow: {
timeEnd: 600000, // 00:10:00
timerType: TimerType.TimeToEnd,
},
timer: {
addedTime: 0,
finishedAt: null,
pausedAt: null,
startedAt: 79200000, // 22:00:00
timerType: TimerType.TimeToEnd,
},
_timer: {
pausedAt: null,
},
} as TState;
@@ -198,15 +218,17 @@ describe('getCurrent()', () => {
const state = {
eventNow: {
timeEnd: 30,
timerType: TimerType.CountDown,
},
clock: 0,
timer: {
addedTime: 10,
clock: 0,
duration: 111, // <-- we take the duration value
startedAt: null,
finishedAt: null,
},
_timer: {
pausedAt: null,
timerType: TimerType.CountDown,
},
} as TState;
@@ -217,14 +239,17 @@ describe('getCurrent()', () => {
const state = {
eventNow: {
timeEnd: 10,
timerType: TimerType.CountDown,
},
clock: 1,
timer: {
addedTime: 0,
clock: 1,
duration: 10,
startedAt: 0,
finishedAt: null,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
@@ -235,15 +260,17 @@ describe('getCurrent()', () => {
const state = {
eventNow: {
timeEnd: 10,
timerType: TimerType.CountDown,
},
clock: 1,
timer: {
addedTime: 10,
clock: 1,
duration: 10,
startedAt: 0,
finishedAt: null,
},
_timer: {
pausedAt: null,
timerType: TimerType.CountDown,
},
} as TState;
@@ -254,15 +281,17 @@ describe('getCurrent()', () => {
const state = {
eventNow: {
timeEnd: 20,
timerType: TimerType.CountDown,
},
clock: 10,
timer: {
addedTime: 0,
clock: 10,
duration: dayInMs + 10,
startedAt: 10,
finishedAt: null,
},
_timer: {
pausedAt: null,
timerType: TimerType.CountDown,
},
} as TState;
@@ -273,15 +302,17 @@ describe('getCurrent()', () => {
const state = {
eventNow: {
timeEnd: 20,
timerType: TimerType.CountDown,
},
clock: 5,
timer: {
addedTime: 0,
clock: 5,
duration: dayInMs + 10,
startedAt: 10,
finishedAt: null,
},
_timer: {
pausedAt: null,
timerType: TimerType.CountDown,
},
} as TState;
@@ -292,15 +323,17 @@ describe('getCurrent()', () => {
const state = {
eventNow: {
timeEnd: 20,
timerType: TimerType.CountDown,
},
clock: 5,
timer: {
addedTime: 20,
clock: 5,
duration: dayInMs + 10,
startedAt: 10,
finishedAt: null,
},
_timer: {
pausedAt: null,
timerType: TimerType.CountDown,
},
} as TState;
@@ -312,15 +345,17 @@ describe('getCurrent()', () => {
const state = {
eventNow: {
timeEnd: 100,
timerType: TimerType.TimeToEnd,
},
clock: 30,
timer: {
addedTime: 0,
clock: 30,
duration: 100,
startedAt: 10,
finishedAt: null,
},
_timer: {
pausedAt: null,
timerType: TimerType.TimeToEnd,
},
} as TState;
@@ -331,15 +366,17 @@ describe('getCurrent()', () => {
const state = {
eventNow: {
timeEnd: 100,
timerType: TimerType.TimeToEnd,
},
clock: 30,
timer: {
addedTime: 7,
clock: 30,
duration: 100,
startedAt: 10,
finishedAt: null,
},
_timer: {
pausedAt: null,
timerType: TimerType.TimeToEnd,
},
} as TState;
@@ -350,15 +387,17 @@ describe('getCurrent()', () => {
const state = {
eventNow: {
timeEnd: 600000, // 00:10:00
timerType: TimerType.TimeToEnd,
},
clock: 79500000, // 22:05:00
timer: {
addedTime: 0,
clock: 79500000, // 22:05:00
duration: Infinity, // not relevant,
startedAt: 79200000, // 22:00:00
finishedAt: null,
},
_timer: {
pausedAt: null,
timerType: TimerType.TimeToEnd,
},
} as TState;
@@ -369,15 +408,17 @@ describe('getCurrent()', () => {
const state = {
eventNow: {
timeEnd: 600000, // 00:10:00
timerType: TimerType.TimeToEnd,
},
clock: 79500000, // 22:05:00
timer: {
addedTime: 0,
clock: 79500000, // 22:05:00
duration: Infinity, // not relevant,
startedAt: 79200000, // 22:00:00
finishedAt: null,
},
_timer: {
pausedAt: null,
timerType: TimerType.TimeToEnd,
},
} as TState;
@@ -393,15 +434,17 @@ describe('getExpectedFinish() and getCurrentTime() combined', () => {
const state = {
eventNow: {
timeEnd: 10,
timerType: TimerType.CountDown,
},
clock: 0,
timer: {
addedTime: 0,
clock: 0,
duration,
startedAt: 0,
pausedAt: null,
finishedAt: null,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
@@ -419,15 +462,17 @@ describe('getExpectedFinish() and getCurrentTime() combined', () => {
const state = {
eventNow: {
timeEnd: 10,
timerType: TimerType.CountDown,
},
clock: 5,
timer: {
addedTime: 3,
clock: 5,
duration,
startedAt: 0,
pausedAt: null,
finishedAt: null,
timerType: TimerType.CountDown,
},
_timer: {
pausedAt: null,
},
} as TState;
@@ -449,8 +494,8 @@ describe('skippedOutOfEvent()', () => {
const expectedFinish = startedAt + duration;
const previousTime = expectedFinish - testSkipLimit / 2;
const state = {
clock: previousTime,
timer: {
clock: previousTime,
expectedFinish,
startedAt,
},
@@ -459,7 +504,7 @@ describe('skippedOutOfEvent()', () => {
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
// @ts-expect-error -- cheating in tests
state.timer.clock += testSkipLimit;
state.clock += testSkipLimit;
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
});
@@ -470,8 +515,8 @@ describe('skippedOutOfEvent()', () => {
const previousTime = startedAt + testSkipLimit / 2;
const state = {
clock: previousTime,
timer: {
clock: previousTime,
expectedFinish,
startedAt,
},
@@ -480,7 +525,7 @@ describe('skippedOutOfEvent()', () => {
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
// @ts-expect-error -- cheating in tests
state.timer.clock += testSkipLimit;
state.clock += testSkipLimit;
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
});
@@ -490,8 +535,8 @@ describe('skippedOutOfEvent()', () => {
const previousTime = dayInMs - 1;
const state = {
clock: previousTime,
timer: {
clock: previousTime,
expectedFinish,
startedAt,
},
@@ -500,7 +545,7 @@ describe('skippedOutOfEvent()', () => {
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
// @ts-expect-error -- cheating in tests
state.timer.clock = testSkipLimit - 2;
state.clock = testSkipLimit - 2;
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
});
@@ -510,8 +555,8 @@ describe('skippedOutOfEvent()', () => {
const previousTime = startedAt + 1;
const state = {
clock: previousTime,
timer: {
clock: previousTime,
expectedFinish,
startedAt,
},
@@ -520,7 +565,7 @@ describe('skippedOutOfEvent()', () => {
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
// @ts-expect-error -- cheating in tests
state.timer.clock -= testSkipLimit;
state.clock -= testSkipLimit;
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
});
@@ -531,8 +576,8 @@ describe('skippedOutOfEvent()', () => {
const previousTime = expectedFinish - testSkipLimit / 2;
const state = {
clock: previousTime,
timer: {
clock: previousTime,
expectedFinish,
startedAt,
},
@@ -541,7 +586,7 @@ describe('skippedOutOfEvent()', () => {
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
// @ts-expect-error -- cheating in tests
state.timer.clock += testSkipLimit + 1;
state.clock += testSkipLimit + 1;
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(true);
});
@@ -552,8 +597,8 @@ describe('skippedOutOfEvent()', () => {
const previousTime = startedAt + testSkipLimit / 2;
const state = {
clock: previousTime,
timer: {
clock: previousTime,
expectedFinish,
startedAt,
},
@@ -562,7 +607,7 @@ describe('skippedOutOfEvent()', () => {
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
// @ts-expect-error -- cheating in tests
state.timer.clock -= testSkipLimit + 1;
state.clock -= testSkipLimit + 1;
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(true);
});
@@ -572,8 +617,8 @@ describe('skippedOutOfEvent()', () => {
const previousTime = dayInMs - 3;
const state = {
clock: previousTime,
timer: {
clock: previousTime,
expectedFinish,
startedAt,
},
@@ -582,7 +627,7 @@ describe('skippedOutOfEvent()', () => {
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
// @ts-expect-error -- cheating in tests
state.timer.clock = testSkipLimit - 2;
state.clock = testSkipLimit - 2;
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(true);
});
@@ -592,8 +637,8 @@ describe('skippedOutOfEvent()', () => {
const previousTime = startedAt + 1;
const state = {
clock: previousTime,
timer: {
clock: previousTime,
expectedFinish,
startedAt,
},
@@ -602,7 +647,7 @@ describe('skippedOutOfEvent()', () => {
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
// @ts-expect-error -- cheating in tests
state.timer.clock -= testSkipLimit + 1;
state.clock -= testSkipLimit + 1;
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(true);
});
});
@@ -1124,14 +1169,17 @@ test('normaliseEndTime()', () => {
describe('updateRoll()', () => {
it('it updates running events correctly', () => {
const timers = {
runtime: {
selectedEventId: '1',
eventNow: {
id: '1',
},
clock: 11,
timer: {
current: 10,
expectedFinish: 15,
clock: 11,
secondaryTimer: null,
startedAt: 1,
},
_timer: {
secondaryTarget: null,
},
} as TState;
@@ -1149,7 +1197,7 @@ describe('updateRoll()', () => {
// @ts-expect-error -- cheating for tests
timers.timer.expectedFinish = 1000;
// @ts-expect-error -- cheating for tests
timers.timer.clock = 600;
timers.clock = 600;
expected.updatedTimer = 1000 - 600;
expect(updateRoll(timers)).toStrictEqual(expected);
@@ -1157,14 +1205,14 @@ describe('updateRoll()', () => {
it('it updates secondary timer', () => {
const timers = {
runtime: {
selectedEventId: null,
},
eventNow: null,
clock: 11,
timer: {
current: null,
expectedFinish: null,
clock: 11,
secondaryTimer: 1,
},
_timer: {
secondaryTarget: 15,
},
} as TState;
@@ -1181,15 +1229,17 @@ describe('updateRoll()', () => {
it('flags an event end', () => {
const timers = {
runtime: {
selectedEventId: '1',
eventNow: {
id: '1',
},
clock: 12,
timer: {
startedAt: 0,
current: 10,
expectedFinish: 11,
clock: 12,
secondaryTimer: null,
},
_timer: {
secondaryTarget: null,
},
} as TState;
@@ -1206,15 +1256,15 @@ describe('updateRoll()', () => {
it('secondary events do not trigger event ends', () => {
const timers = {
runtime: {
selectedEventId: null,
},
eventNow: null,
clock: 16,
timer: {
startedAt: null,
current: null,
expectedFinish: null,
clock: 16,
secondaryTimer: 1,
},
_timer: {
secondaryTarget: 15,
},
} as TState;
@@ -1231,14 +1281,14 @@ describe('updateRoll()', () => {
it('when a secondary timer is finished, it prompts for new event load', () => {
const timers = {
runtime: {
selectedEventId: null,
},
eventNow: null,
clock: 15,
timer: {
current: null,
expectedFinish: null,
clock: 15,
secondaryTimer: 0,
},
_timer: {
secondaryTarget: 15,
},
} as TState;
@@ -1255,15 +1305,17 @@ describe('updateRoll()', () => {
it('counts over midnight', () => {
const timers = {
runtime: {
selectedEventId: '1',
eventNow: {
id: '1',
},
clock: dayInMs - 10,
timer: {
current: 25,
expectedFinish: 10,
startedAt: 1000,
clock: dayInMs - 10,
secondaryTimer: null,
},
_timer: {
secondaryTarget: null,
},
} as TState;
@@ -1280,15 +1332,17 @@ describe('updateRoll()', () => {
it('rolls over midnight', () => {
const timers = {
runtime: {
selectedEventId: '1',
eventNow: {
id: '1',
},
clock: 10,
timer: {
current: dayInMs,
expectedFinish: 10,
startedAt: 1000,
clock: 10,
secondaryTimer: null,
},
_timer: {
secondaryTarget: null,
},
} as TState;
@@ -36,10 +36,10 @@ class RuntimeService {
* Checks if a list of IDs is in the current selection
*/
private affectsLoaded(affectedIds: string[]): boolean {
const now = state.runtime.selectedEventId;
const nowPublic = state.runtime.selectedPublicEventId;
const next = state.runtime.nextEventId;
const nextPublic = state.runtime.nextPublicEventId;
const now = state.eventNow?.id;
const nowPublic = state.publicEventNow?.id;
const next = state.eventNext?.id;
const nextPublic = state.publicEventNext?.id;
return (
affectedIds.includes(now) ||
affectedIds.includes(nowPublic) ||
@@ -50,8 +50,8 @@ class RuntimeService {
private isNewNext() {
const timedEvents = EventLoader.getPlayableEvents();
const now = state.runtime.selectedEventId;
const next = state.runtime.nextEventId;
const now = state.eventNow?.id;
const next = state.eventNext?.id;
// check whether the index of now and next are consecutive
const indexNow = timedEvents.findIndex((event) => event.id === now);
@@ -61,8 +61,8 @@ class RuntimeService {
return true;
}
// iterate through timed events and see if there are public events between nowPublic and nextPublic
const nowPublic = state.runtime.selectedPublicEventId;
const nextPublic = state.runtime.nextPublicEventId;
const nowPublic = state.publicEventNow?.id;
const nextPublic = state.publicEventNext?.id;
let foundNew = false;
let isAfter = false;
@@ -93,7 +93,7 @@ class RuntimeService {
* check whether underlying data of runtime has changed
*/
update(affectedIds?: string[]) {
const hasLoadedElements = state.runtime.selectedEventId && state.runtime.nextEventId;
const hasLoadedElements = state.eventNow && state.eventNext;
if (!hasLoadedElements) {
return;
}
@@ -107,11 +107,11 @@ class RuntimeService {
let isNext = false;
if (safeOption || eventInMemory) {
if (state.playback === Playback.Roll) {
if (state.timer.playback === Playback.Roll) {
this.roll();
}
// load stuff again, but keep running if our events still exist
const eventNow = EventLoader.getEventWithId(state.runtime.selectedEventId);
const eventNow = EventLoader.getEventWithId(state.eventNow.id);
if (eventNow) {
stateMutations.reload(eventNow);
}
@@ -139,7 +139,7 @@ class RuntimeService {
const timedEvents = EventLoader.getPlayableEvents();
stateMutations.load(event, timedEvents);
const success = event.id === state.runtime.selectedEventId;
const success = event.id === state.eventNow?.id;
if (success) {
logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`);
@@ -227,7 +227,7 @@ class RuntimeService {
* @return {boolean} success - whether an event was loaded
*/
loadPrevious(): boolean {
const previousEvent = EventLoader.findPrevious(state.runtime.selectedEventId);
const previousEvent = EventLoader.findPrevious(state.eventNow?.id);
if (previousEvent) {
const success = this.loadEvent(previousEvent);
return success;
@@ -240,7 +240,7 @@ class RuntimeService {
* @return {boolean} success
*/
loadNext(): boolean {
const nextEvent = EventLoader.findNext(state.runtime.selectedEventId);
const nextEvent = EventLoader.findNext(state.eventNow?.id);
if (nextEvent) {
const success = this.loadEvent(nextEvent);
return success;
@@ -254,10 +254,10 @@ class RuntimeService {
* Starts playback on selected event
*/
start() {
const canStart = validatePlayback(state.playback).start;
const canStart = validatePlayback(state.timer.playback).start;
if (canStart) {
this.eventTimer.start();
logger.info(LogOrigin.Playback, `Play Mode ${state.playback.toUpperCase()}`);
logger.info(LogOrigin.Playback, `Play Mode ${state.timer.playback.toUpperCase()}`);
}
}
@@ -275,9 +275,9 @@ class RuntimeService {
* Pauses playback on selected event
*/
pause() {
if (validatePlayback(state.playback).pause) {
if (validatePlayback(state.timer.playback).pause) {
this.eventTimer.pause();
const newState = state.playback;
const newState = state.timer.playback;
logger.info(LogOrigin.Playback, `Play Mode ${newState.toUpperCase()}`);
}
}
@@ -286,9 +286,9 @@ class RuntimeService {
* Stops timer and unloads any events
*/
stop() {
if (validatePlayback(state.playback).stop) {
if (validatePlayback(state.timer.playback).stop) {
this.eventTimer.stop();
const newState = state.playback;
const newState = state.timer.playback;
logger.info(LogOrigin.Playback, `Play Mode ${newState.toUpperCase()}`);
}
}
@@ -297,7 +297,7 @@ class RuntimeService {
* Reloads current event
*/
reload() {
if (state.runtime.selectedEventId) {
if (state.eventNow) {
stateMutations.reload();
}
}
@@ -313,7 +313,7 @@ class RuntimeService {
logger.warning(LogOrigin.Server, `Roll: ${error}`);
}
const newState = state.playback;
const newState = state.timer.playback;
logger.info(LogOrigin.Playback, `Play Mode ${newState.toUpperCase()}`);
}
+15 -5
View File
@@ -14,8 +14,11 @@ export const normaliseEndTime = (start: number, end: number) => (end < start ? e
* @returns {number | null} new current time or null if nothing is running
*/
export function getExpectedFinish(state: TState): MaybeNumber {
const { startedAt, clock, finishedAt, duration, addedTime, timerType, pausedAt } = state.timer;
const { startedAt, finishedAt, duration, addedTime } = state.timer;
const { timerType } = state.eventNow;
const { pausedAt } = state._timer;
const { timeEnd } = state.eventNow;
const { clock } = state;
if (startedAt === null) {
return null;
@@ -47,8 +50,11 @@ export function getExpectedFinish(state: TState): MaybeNumber {
* @returns {number} current time for timer
*/
export function getCurrent(state: TState): number {
const { startedAt, duration, addedTime, clock, timerType, pausedAt } = state.timer;
const { startedAt, duration, addedTime } = state.timer;
const { timerType } = state.eventNow;
const { pausedAt } = state._timer;
const { timeEnd } = state.eventNow;
const { clock } = state;
if (timerType === TimerType.TimeToEnd) {
const isNextDay = startedAt > timeEnd;
@@ -77,7 +83,9 @@ export function getCurrent(state: TState): number {
* @returns {boolean}
*/
export function skippedOutOfEvent(state: TState, previousTime: number, skipLimit: number): boolean {
const { clock, startedAt, expectedFinish } = state.timer;
const { startedAt, expectedFinish } = state.timer;
const { clock } = state;
const hasPassedMidnight = previousTime > dayInMs - skipLimit && clock < skipLimit;
const adjustedClock = hasPassedMidnight ? clock + dayInMs : clock;
@@ -224,8 +232,10 @@ export const getRollTimers = (rundown: OntimeEvent[], timeNow: number) => {
* @returns object with selection variables
*/
export const updateRoll = (state: TState) => {
const { selectedEventId } = state.runtime;
const { current, expectedFinish, startedAt, clock, secondaryTimer, secondaryTarget } = state.timer;
const { current, expectedFinish, startedAt, secondaryTimer } = state.timer;
const { secondaryTarget } = state._timer;
const { clock } = state;
const selectedEventId = state.eventNow?.id ?? null;
// timers
let updatedTimer = current;
+60 -102
View File
@@ -25,38 +25,31 @@ const timeSkipLimit = 1000;
const initialRuntime: Runtime = {
selectedEventIndex: null,
selectedEventId: null, // TODO: remove
selectedPublicEventId: null, // TODO: remove
nextEventId: null, // TODO: remove
nextPublicEventId: null, // TODO: remove
numEvents: 0,
};
const initialTimer: TimerState = {
clock: clock.timeNow(),
addedTime: 0,
current: null,
duration: null,
elapsed: null,
expectedFinish: null, // TODO: expected finish could account for midnight, we cleanup in the clients
addedTime: 0,
startedAt: null,
finishedAt: null,
playback: Playback.Stop,
secondaryTimer: null,
duration: null,
timerType: null, // TODO: remove
endAction: null, // TODO: remove
timeWarning: null, // TODO: remove
timeDanger: null, // TODO: remove
startedAt: null,
};
export type TState = DeepReadonly<{
clock: number; // realtime clock
eventNow: OntimeEvent | null;
publicEventNow: OntimeEvent | null;
eventNext: OntimeEvent | null;
publicEventNext: OntimeEvent | null;
runtime: Runtime;
playback: Playback; // TODO: merge into timer?
// TODO: these are private state and should not be emitted
timer: TimerState & {
timer: TimerState;
// private properties of the timer calculations
_timer: {
pausedAt: MaybeNumber;
finishedNow: boolean;
lastUpdate: MaybeNumber;
@@ -65,14 +58,14 @@ export type TState = DeepReadonly<{
}>;
export const state: TState = {
clock: clock.timeNow(),
eventNow: null,
publicEventNow: null,
eventNext: null,
publicEventNext: null,
runtime: initialRuntime,
playback: Playback.Stop, // TODO: merge into timer?
timer: {
...initialTimer,
timer: { ...initialTimer },
_timer: {
pausedAt: null,
lastUpdate: null,
secondaryTarget: null,
@@ -90,22 +83,16 @@ export const stateMutations = {
const eventIndex = rundown.findIndex((eventInMemory) => eventInMemory.id === event.id);
state.runtime.selectedEventIndex = eventIndex;
state.runtime.selectedEventId = event.id;
state.runtime.numEvents = rundown.length;
this.loadNow(event, rundown);
this.loadNext(rundown);
state.timer.clock = clock.timeNow();
state.playback = Playback.Armed;
state.clock = clock.timeNow();
state.timer.playback = Playback.Armed;
state.timer.duration = calculateDuration(event.timeStart, event.timeEnd);
state.timer.current = getCurrent(state);
state.timer.timerType = event.timerType;
state.timer.endAction = event.endAction;
state.timer.timeWarning = event.timeWarning;
state.timer.timeDanger = event.timeDanger;
if (initialData) {
stateMutations.timer.patch(initialData);
}
@@ -118,11 +105,9 @@ export const stateMutations = {
// check if current is also public
if (event.isPublic) {
state.publicEventNow = event;
state.runtime.selectedPublicEventId = event.id;
} else {
// assume there is no public event
state.publicEventNow = null;
state.runtime.selectedPublicEventId = null;
// if there is nothing before, return
if (!state.runtime.selectedEventIndex) {
@@ -133,7 +118,6 @@ export const stateMutations = {
for (let i = state.runtime.selectedEventIndex; i >= 0; i--) {
if (playableEvents[i].isPublic) {
state.publicEventNow = playableEvents[i];
state.runtime.selectedPublicEventId = playableEvents[i].id;
break;
}
}
@@ -145,8 +129,6 @@ export const stateMutations = {
// assume there are no next events
state.eventNext = null;
state.publicEventNext = null;
state.runtime.nextEventId = null;
state.runtime.nextPublicEventId = null;
if (state.runtime.selectedEventIndex === null) {
return;
@@ -162,14 +144,12 @@ export const stateMutations = {
// if we have not set private
if (!nextProduction) {
state.eventNext = playableEvents[i];
state.runtime.nextEventId = playableEvents[i].id;
nextProduction = true;
}
// if event is public
if (playableEvents[i].isPublic) {
state.publicEventNext = playableEvents[i];
state.runtime.nextPublicEventId = playableEvents[i].id;
nextPublic = true;
}
@@ -180,14 +160,8 @@ export const stateMutations = {
});
},
resume(restorePoint: RestorePoint, event: OntimeEvent, rundown: OntimeEvent[]) {
mutate((state) => {
const { playback, ...patch } = restorePoint;
// TODO: this.load gets typed as any?
stateMutations.load(event, rundown, patch);
// TODO: send as part of the patch when playback is merged to timer
state.playback = playback;
mutate((_state) => {
stateMutations.load(event, rundown, restorePoint);
});
},
/**
@@ -202,21 +176,17 @@ export const stateMutations = {
// update data which is duplicate between eventNow and timer objects
state.timer.duration = calculateDuration(state.eventNow.timeStart, state.eventNow.timeEnd);
state.timer.expectedFinish = getExpectedFinish(state);
state.timer.timerType = state.eventNow.timerType;
state.timer.endAction = state.eventNow.endAction;
return;
}
state.playback = Playback.Armed;
state.timer.playback = Playback.Armed;
state.timer.duration = calculateDuration(state.eventNow.timeStart, state.eventNow.timeEnd);
state.timer.current = state.timer.duration;
state.timer.elapsed = null;
state.timer.timerType = state.eventNow.timerType;
state.timer.endAction = state.eventNow.endAction;
state.timer.startedAt = null;
state.timer.finishedAt = null;
state.timer.pausedAt = null;
state._timer.pausedAt = null;
state.timer.addedTime = 0;
state.timer.expectedFinish = getExpectedFinish(state);
@@ -242,11 +212,10 @@ export const stateMutations = {
// TODO: could we avoid having this dependency?
state.runtime.numEvents = EventLoader.getPlayableEvents().length;
state.playback = Playback.Stop;
state.timer = {
...initialTimer,
clock: clock.timeNow(),
state.timer.playback = Playback.Stop;
state.clock = clock.timeNow();
state.timer = { ...initialTimer };
state._timer = {
pausedAt: null,
lastUpdate: null,
secondaryTarget: null,
@@ -267,22 +236,22 @@ export const stateMutations = {
start() {
mutate(
(state) => {
state.timer.clock = clock.timeNow();
state.clock = clock.timeNow();
state.timer.secondaryTimer = null;
state.timer.secondaryTarget = null;
state._timer.secondaryTarget = null;
// add paused time if it exists
if (state.timer.pausedAt) {
const timeToAdd = state.timer.clock - state.timer.pausedAt;
if (state._timer.pausedAt) {
const timeToAdd = state.clock - state._timer.pausedAt;
state.timer.addedTime += timeToAdd;
state.timer.pausedAt = null;
state._timer.pausedAt = null;
}
if (state.timer.startedAt === null) {
state.timer.startedAt = state.timer.clock;
state.timer.startedAt = state.clock;
}
state.playback = Playback.Play;
state.timer.playback = Playback.Play;
state.timer.expectedFinish = getExpectedFinish(state);
},
{
@@ -307,13 +276,13 @@ export const stateMutations = {
pause() {
mutate(
(state) => {
if (state.playback !== Playback.Play) {
if (state.timer.playback !== Playback.Play) {
return false;
}
state.playback = Playback.Pause;
state.timer.clock = clock.timeNow();
state.timer.pausedAt = state.timer.clock;
state.timer.playback = Playback.Pause;
state.clock = clock.timeNow();
state._timer.pausedAt = state.clock;
return true;
},
{
@@ -327,24 +296,18 @@ export const stateMutations = {
},
resume(event: OntimeEvent, restorePoint: RestorePoint) {
mutate((state) => {
state.timer.clock = clock.timeNow();
state.clock = clock.timeNow();
// TODO: the duplication of timer data would not be necessary
// once event loader is merged here
state.runtime.selectedEventId = event.id;
state.timer.startedAt = restorePoint.startedAt;
state.timer.duration = calculateDuration(event.timeStart, event.timeEnd);
state.timer.current = state.timer.duration;
state.timer.timerType = event.timerType;
state.timer.endAction = event.endAction;
state.playback = restorePoint.playback;
state.timer.pausedAt = restorePoint.pausedAt;
state.timer.playback = restorePoint.playback;
state._timer.pausedAt = restorePoint.pausedAt;
state.timer.addedTime = restorePoint.addedTime;
// check if event finished meanwhile
if (state.timer.timerType === TimerType.TimeToEnd) {
if (event.timerType === TimerType.TimeToEnd) {
state.timer.current = getCurrent(state);
}
});
@@ -388,10 +351,6 @@ export const stateMutations = {
state.timer.secondaryTimer = updatedSecondaryTimer;
state.timer.elapsed = state.timer.duration - state.timer.current;
if (isFinished) {
state.runtime.selectedEventId = null;
}
return { doRoll: doRollLoad, isFinished };
}
@@ -399,8 +358,8 @@ export const stateMutations = {
let isFinished = false;
state.timer.current = getCurrent(state);
if (state.playback === Playback.Play && state.timer.finishedNow) {
state.timer.finishedAt = state.timer.clock;
if (state.timer.playback === Playback.Play && state._timer.finishedNow) {
state.timer.finishedAt = state.clock;
isFinished = true;
} else {
state.timer.expectedFinish = getExpectedFinish(state);
@@ -419,16 +378,16 @@ export const stateMutations = {
let _isFinished = false;
let _shouldNotify = false;
const previousTime = state.timer.clock;
state.timer.clock = clock.timeNow();
const hasSkippedBack = previousTime > state.timer.clock;
const previousTime = state.clock;
state.clock = clock.timeNow();
const hasSkippedBack = previousTime > state.clock;
if (hasSkippedBack) {
_force = true;
}
// we call integrations if we update timers
if (state.playback === Playback.Roll) {
if (state.timer.playback === Playback.Roll) {
const result = roll();
_shouldNotify = true;
_doRoll = result.doRoll;
@@ -442,9 +401,9 @@ export const stateMutations = {
// we only update the store at the updateInterval
// side effects such as onFinish will still be triggered in the update functions
const isTimeToUpdate = state.timer.clock > state.timer.lastUpdate + updateInterval;
const isTimeToUpdate = state.clock > state._timer.lastUpdate + updateInterval;
if (_force || isTimeToUpdate) {
state.timer.lastUpdate = state.timer.clock;
state._timer.lastUpdate = state.clock;
// TODO: can we simplify the didUpdate and shouldNotify
_didUpdate = true;
}
@@ -471,13 +430,13 @@ export const stateMutations = {
integrationService.dispatch(TimerLifeCycle.onFinish);
// handle end action if there was a timer playing
if (newState.playback === Playback.Play) {
if (newState.timer.endAction === EndAction.Stop) {
if (newState.timer.playback === Playback.Play) {
if (newState.eventNow.endAction === EndAction.Stop) {
runtimeService.stop();
} else if (newState.timer.endAction === EndAction.LoadNext) {
} else if (newState.eventNow.endAction === EndAction.LoadNext) {
// we need to delay here to put this action in the queue stack. otherwise it won't be executed properly
setTimeout(runtimeService.loadNext, 0);
} else if (newState.timer.endAction === EndAction.PlayNext) {
} else if (newState.eventNow.endAction === EndAction.PlayNext) {
runtimeService.startNext();
}
}
@@ -490,12 +449,12 @@ export const stateMutations = {
mutate((state) => {
stateMutations.timer.clear();
const { nextEvent, currentEvent } = getRollTimers(rundown, state.timer.clock);
const { nextEvent, currentEvent } = getRollTimers(rundown, state.clock);
if (currentEvent) {
// there is something running, load
state.timer.secondaryTimer = null;
state.timer.secondaryTarget = null;
state._timer.secondaryTarget = null;
// account for event that finishes the day after
const endTime =
@@ -506,17 +465,16 @@ export const stateMutations = {
stateMutations.load(currentEvent, rundown, {
startedAt: currentEvent.timeStart,
expectedFinish: currentEvent.timeEnd,
current: endTime - state.timer.clock,
current: endTime - state.clock,
});
} else if (nextEvent) {
// account for day after
const nextStart =
nextEvent.timeStart < state.timer.clock ? nextEvent.timeStart + dayInMs : nextEvent.timeStart;
const nextStart = nextEvent.timeStart < state.clock ? nextEvent.timeStart + dayInMs : nextEvent.timeStart;
// nothing now, but something coming up
state.timer.secondaryTimer = nextStart - state.timer.clock;
state.timer.secondaryTarget = nextStart;
state.timer.secondaryTimer = nextStart - state.clock;
state._timer.secondaryTarget = nextStart;
}
state.playback = Playback.Roll;
state.timer.playback = Playback.Roll;
});
},
},
@@ -559,22 +517,22 @@ export function mutate<R>(
// set eventStore any time a mutation happens
// this means we are pushing this data to the client every 32ms
eventStore.batchSet({
clock: newState.clock,
eventNow: newState.eventNow,
publicEventNow: newState.publicEventNow,
eventNext: newState.eventNext,
publicEventNext: newState.publicEventNext,
loaded: newState.runtime, // TODO: rename to runtime
playback: newState.playback,
runtime: newState.runtime,
timer: newState.timer,
});
// we write to restore service if the underlying data changes
restoreService.save({
playback: state.playback,
selectedEventId: state.runtime.selectedEventId,
playback: state.timer.playback,
selectedEventId: state.eventNow?.id ?? null,
startedAt: state.timer.startedAt,
addedTime: state.timer.addedTime,
pausedAt: state.timer.pausedAt,
pausedAt: state._timer.pausedAt,
});
return result;