-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8d0732b
commit 4f2b892
Showing
26 changed files
with
520 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"extends": ["../.eslintrc.json"], | ||
"ignorePatterns": ["!**/*"], | ||
"overrides": [ | ||
{ | ||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.ts", "*.tsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.js", "*.jsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.json"], | ||
"parser": "jsonc-eslint-parser", | ||
"rules": { | ||
"@nx/dependency-checks": "error" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
stateful adapters for fsm; to be a main convenient OO interface for lib users |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/* eslint-disable */ | ||
export default { | ||
displayName: 'adapters', | ||
preset: '../jest.preset.js', | ||
testEnvironment: 'node', | ||
transform: { | ||
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }], | ||
}, | ||
moduleFileExtensions: ['ts', 'js', 'html'], | ||
coverageDirectory: '../coverage/adapters', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"name": "@jikan/adapters", | ||
"version": "0.0.1", | ||
"dependencies": { | ||
"tslib": "^2.3.0", | ||
"@jikan/fsm": "*", | ||
"@jikan/utils": "*" | ||
}, | ||
"type": "commonjs", | ||
"main": "./src/index.js", | ||
"typings": "./src/index.d.ts" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "adapters", | ||
"$schema": "../node_modules/nx/schemas/project-schema.json", | ||
"sourceRoot": "adapters/src", | ||
"projectType": "library", | ||
"targets": { | ||
"build": { | ||
"executor": "@nx/js:tsc", | ||
"outputs": ["{options.outputPath}"], | ||
"options": { | ||
"outputPath": "dist/adapters", | ||
"main": "adapters/src/index.ts", | ||
"tsConfig": "adapters/tsconfig.lib.json", | ||
"assets": ["adapters/*.md"] | ||
} | ||
}, | ||
"lint": { | ||
"executor": "@nx/eslint:lint", | ||
"outputs": ["{options.outputFile}"] | ||
}, | ||
"test": { | ||
"executor": "@nx/jest:jest", | ||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"], | ||
"options": { | ||
"jestConfig": "adapters/jest.config.ts" | ||
} | ||
} | ||
}, | ||
"tags": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { StatefulSimulation } from './lib/statefulSimulation'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
jest.useFakeTimers(); | ||
|
||
import { StatefulSimulation } from './statefulSimulation'; | ||
import { BASIC_EXERCISE_PROGRAM } from '@jikan/test-utils'; | ||
|
||
describe('statefulSimulation', () => { | ||
it('runs a basic program', () => { | ||
const onChangeSpy = jest.fn(); | ||
const sim = new StatefulSimulation(BASIC_EXERCISE_PROGRAM, { | ||
onChange: onChangeSpy, | ||
stopOnEmpty: true, | ||
}); | ||
expect(sim.isRunning()).toBe(false); | ||
sim.start(); | ||
expect(sim.isRunning()).toBe(true); | ||
while (sim.isRunning()) { | ||
jest.runOnlyPendingTimers(); | ||
} | ||
const initAndEnd = 2; | ||
const totalCalls = | ||
BASIC_EXERCISE_PROGRAM.map(({ duration }) => duration).reduce( | ||
(a, b) => a + b, | ||
0 | ||
) / sim.leniency; | ||
expect(onChangeSpy).toHaveBeenCalledTimes(totalCalls + initAndEnd); | ||
}); | ||
it('resets to the initial state on done', () => { | ||
const sim = new StatefulSimulation(BASIC_EXERCISE_PROGRAM, { | ||
stopOnEmpty: true, | ||
}); | ||
sim.start(); | ||
const l = sim.length(); | ||
while (sim.isRunning()) { | ||
jest.runOnlyPendingTimers(); | ||
} | ||
expect(sim.length()).toBe(l); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import { | ||
empty, | ||
QueueItem, | ||
tick, | ||
push, | ||
restart, | ||
State, | ||
isEmpty, | ||
current, | ||
eqQueueItem, | ||
} from '@jikan/fsm'; | ||
import { assertExists } from '@jikan/utils'; | ||
|
||
const DEFAULT_OPTS = { leniency: 100 /*ms*/, stopOnEmpty: true } as const; | ||
const SUSPICIOUSLY_TOO_MANY_LISTENERS = 100; | ||
const SUSPICIOUSLY_TOO_MANY_LISTENERS_MSG = (n: number) => | ||
`Suspiciously many listeners: ${n}. Please check that you clean up listener functions calling the cleanup function returned from onChange`; | ||
let isSuspiciouslyTooManyListenersReported = false; | ||
const checkTooManyListeners = (n: number) => { | ||
if ( | ||
n >= SUSPICIOUSLY_TOO_MANY_LISTENERS && | ||
!isSuspiciouslyTooManyListenersReported | ||
) { | ||
console.warn(SUSPICIOUSLY_TOO_MANY_LISTENERS_MSG(n)); | ||
isSuspiciouslyTooManyListenersReported = true; | ||
} | ||
}; | ||
|
||
const areQueueItemsEqual = <QueueItemType extends string>( | ||
a: QueueItem<QueueItemType> | null, | ||
b: QueueItem<QueueItemType> | null | ||
): boolean => { | ||
if (a === null && b === null) return true; | ||
if (a === null || b === null) return false; | ||
return eqQueueItem(b)(a); | ||
}; | ||
|
||
export const StatefulSimulation = class<QueueItemType extends string = string> { | ||
// set only thru #setState | ||
#state = empty<QueueItemType>(); | ||
#setState = (state1: State<QueueItemType>) => { | ||
const queueItem = current(this.#state); | ||
const nextQueueItem = current(state1); | ||
this.#state = state1; | ||
if (!areQueueItemsEqual(queueItem, nextQueueItem)) { | ||
this.#reportQueueItem(nextQueueItem); | ||
} | ||
}; | ||
readonly #state0: State<QueueItemType>; | ||
#intervalHandle: ReturnType<typeof setInterval> | null = null; | ||
readonly leniency: number; | ||
readonly stopOnEmpty: boolean; | ||
isRunning = | ||
() /*: this is {#intervalHandle: number} - not with ts classes.*/ => | ||
this.#intervalHandle !== null; | ||
constructor( | ||
queue: QueueItem<QueueItemType>[], | ||
opts: { | ||
leniency?: number; | ||
onChange?: (next: QueueItem | null) => void; | ||
stopOnEmpty?: boolean; | ||
} = DEFAULT_OPTS | ||
) { | ||
const opts_ = { ...DEFAULT_OPTS, ...opts }; | ||
if (opts_.leniency <= 0) throw new Error('leniency must be positive'); | ||
this.push(queue); | ||
this.#state0 = this.#state; | ||
this.leniency = opts_.leniency; | ||
this.stopOnEmpty = opts_.stopOnEmpty; | ||
// nb! not cleanable, should be fine as it exists together with the object lifetime and semantics seem to match the Constructor assumptions | ||
if (opts_.onChange) | ||
this.onChange(opts_.onChange, { | ||
withCurrent: true, | ||
}); | ||
} | ||
#changeListeners = new Map<number, (next: QueueItem | null) => void>(); | ||
#nextListenerId = 1; | ||
|
||
onChange = ( | ||
f: (next: QueueItem | null) => void, | ||
opts: { | ||
withCurrent: boolean; | ||
} = { | ||
withCurrent: true, | ||
} | ||
): (() => void) => { | ||
if (opts.withCurrent) f(this.current()); | ||
const listenerId = this.#nextListenerId; | ||
this.#changeListeners.set(listenerId, f); | ||
this.#nextListenerId = listenerId + 1; | ||
checkTooManyListeners(this.#changeListeners.size); | ||
return () => { | ||
this.#changeListeners.delete(listenerId); | ||
}; | ||
}; | ||
#reportQueueItem = (next: QueueItem<QueueItemType> | null) => { | ||
// called before #state change; TODO don't depend on execution order as much | ||
this.#changeListeners.forEach((f) => f(next)); | ||
}; | ||
#tick = (step: number) => { | ||
const [state1, queueItems] = tick(step)(this.#state); | ||
this.#setState(state1); | ||
return queueItems; | ||
}; | ||
push = (queueItems: QueueItem<QueueItemType>[]) => { | ||
this.#setState(push(queueItems)(this.#state)); | ||
}; | ||
restart = () => { | ||
this.#setState(restart(this.#state)); | ||
}; | ||
reset = () => { | ||
this.#setState(this.#state0); | ||
}; | ||
pause = () => { | ||
if (!this.isRunning()) return; | ||
clearInterval(assertExists(this.#intervalHandle)); | ||
this.#intervalHandle = null; | ||
}; | ||
start = () => { | ||
if (this.isRunning()) return; | ||
let lastMs = Date.now(); | ||
this.#intervalHandle = setInterval(() => { | ||
const now = Date.now(); | ||
const delta = now - lastMs; | ||
lastMs = now; | ||
this.#tick(delta); | ||
if (this.isEmpty() && this.stopOnEmpty) { | ||
this.stop(); | ||
} | ||
}, this.leniency); | ||
}; | ||
stop = () => { | ||
this.pause(); | ||
this.reset(); | ||
}; | ||
isEmpty = () => { | ||
return isEmpty(this.#state); | ||
}; | ||
length = () => { | ||
return this.#state.queue.length; | ||
}; | ||
current = () => { | ||
return current(this.#state); | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"extends": "../tsconfig.base.json", | ||
"compilerOptions": { | ||
"module": "commonjs", | ||
"forceConsistentCasingInFileNames": true, | ||
"strict": true, | ||
"noImplicitOverride": true, | ||
"noPropertyAccessFromIndexSignature": true, | ||
"noImplicitReturns": true, | ||
"noFallthroughCasesInSwitch": true | ||
}, | ||
"files": [], | ||
"include": [], | ||
"references": [ | ||
{ | ||
"path": "./tsconfig.lib.json" | ||
}, | ||
{ | ||
"path": "./tsconfig.spec.json" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"extends": "./tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": "../dist/out-tsc", | ||
"declaration": true, | ||
"types": ["node"] | ||
}, | ||
"include": ["src/**/*.ts"], | ||
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"extends": "./tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": "../dist/out-tsc", | ||
"module": "commonjs", | ||
"types": ["jest", "node"] | ||
}, | ||
"include": [ | ||
"jest.config.ts", | ||
"src/**/*.test.ts", | ||
"src/**/*.spec.ts", | ||
"src/**/*.d.ts" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,5 @@ export { | |
restart, | ||
reset, | ||
} from '@jikan/fsm'; | ||
|
||
export { StatefulSimulation as Timer } from '@jikan/adapters'; |
Oops, something went wrong.