Skip to content

Commit f57cf11

Browse files
authored
Merge pull request #1 from boatmeme/initial-experimentation
Initial experimentation + implementation
2 parents cd31434 + 80404ba commit f57cf11

22 files changed

+1877
-253
lines changed

.eslintignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
*.config.js
2-
*.config.ts
3-
*.test.js
4-
*.test.ts
51
dist

.github/workflows/node.js.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3+
4+
name: Node.js CI
5+
6+
on:
7+
push:
8+
branches: ['main']
9+
pull_request:
10+
branches: ['main']
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
16+
strategy:
17+
matrix:
18+
node-version: [12.x, 14.x, 16.x, 18.x]
19+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
20+
21+
steps:
22+
- uses: actions/checkout@v3
23+
- name: Use Node.js ${{ matrix.node-version }}
24+
uses: actions/setup-node@v3
25+
with:
26+
node-version: ${{ matrix.node-version }}
27+
cache: 'npm'
28+
- run: npm ci
29+
- run: npm run build --if-present
30+
- run: npm run test:ci

.github/workflows/npm-publish.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3+
4+
name: Publish npm Package
5+
6+
on:
7+
release:
8+
types: [created]
9+
10+
jobs:
11+
build:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v3
15+
- uses: actions/setup-node@v3
16+
with:
17+
node-version: 18
18+
- run: npm ci
19+
- run: npm run build --if-present
20+
- run: npm test
21+
22+
publish-npm:
23+
needs: build
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v3
27+
- uses: actions/setup-node@v3
28+
with:
29+
node-version: 18
30+
registry-url: https://registry.npmjs.org/
31+
- run: npm ci
32+
- run: npm run build --if-present
33+
- run: npm publish
34+
env:
35+
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
registry=https://registry.npmjs.org

README.md

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,35 @@
11
# (๑ᵕ⌓ᵕ̤) clockblocker
2-
A Typescript library for time manipulation, or for building deceitful clocks.
2+
[![Node.js CI](https://github.com/boatmeme/clockblocker/actions/workflows/node.js.yml/badge.svg)](https://github.com/boatmeme/clockblocker/actions/workflows/node.js.yml)
3+
4+
A Typescript library for warping the very fabric of space-time, or for building deceitful clocks.
35

46
## Overview
57

6-
#### Why should I use this library?
8+
Do you sometimes stare at the clock, watching the minutes tick by, wondering by what trick-of-perception some minutes seem to pass more slowly than the last?
9+
10+
"Watched pot, not boiling", you think...
11+
12+
...but still...could there be...some little relativistic, Einsteinian gremlins, toiling away behind the clock face, deeply invested in and hell-bent upon your personal descent into the mouth of madness?
13+
14+
Probably not.
15+
16+
But, hey, now *you* can build your own fraudulent clock to drive others _**completely bonkers**_!
17+
18+
With `clockblocker`, Time is but a rubber-band, subject to your every passing whimsy.
19+
20+
## Why should I use this library?
21+
22+
First, let's assume you've got a very good (read: not evil) reason for wanting the clock to tick more slowly during a given time period.
23+
24+
You're building some kind of device capable of displaying the time, a la a digital clock LED display. This clock will be used by a miniature-human who, whenever they awaken past 4:00am, is incapable of returning to sleep. If the clock reads 3:59, the small person rolls right back over and goes to sleep.
25+
26+
In order to ensure a later arrival of 4:00am, we might wish to make the seconds start ticking ever more slowly at some point earlier in the night, to ensure that the clock doesn't actually show 4:00am until - let's say - 7:00am.
27+
28+
We probably want some facilities for easing-into the slowing of time, so that it isn't immediately obvious what is happening under cursory, ambient observations. Likewise, we might want to ease-back into normal time, rather than snapping immediately from 4:00am to 7:01 am.
29+
30+
Also, the tiny human isn't stupid, so you're gonna just have to hide all the other clocks in the house. And windows! You've gotta black-out the windows or the jig is up!
31+
32+
Let's be honest, you'll only be able to get away with this once a year, on Christmas morning.
733

834
---
935
## Install
@@ -14,7 +40,141 @@ npm install clockblocker
1440

1541
## Usage
1642

43+
```
44+
import { Clock, ConstantTimeCompression, ConstantTimeDilation } from 'clockblocker';
45+
46+
const timeDilation = new ConstantTimeDilation(
47+
{ hour: 1 }, // start at 1am, reference ("real") time
48+
{ hours: 3 }, // Relative Time: By the time the "fake" time reads 4am...
49+
{ hours: 6 }, // Reference Time: 6 hours of "real" time will have passed
50+
);
51+
52+
// Fake clock will read: 4:00am, real clock: 7:00am
53+
54+
const timeCompression = new ConstantTimeCompression(
55+
{ hour: 7 }, // start at 7am reference ("real") time
56+
{ hours: 6 }, // Relative Time: In the time the "fake" time shows the passage of 6 hours
57+
{ hours: 3}, // Reference Time: Only 3 hours, real time will have elapsed
58+
),
59+
60+
// So, by the time 10:00am (reference) rolls-around, the clock is back to normal 1-to-1 time.
61+
// Fake clock will read: 10:00am, real clock: 10:00am
62+
63+
const clock = new Clock([
64+
timeDilation,
65+
timeCompression
66+
]);
67+
```
68+
69+
Now, there are two properties of the clock instance that are useful
70+
71+
- `clock.relativeTimeInMillis` is the fraudulent time in `epochMillis`
72+
- `clock.referenceTimeInMillis` is the "real" system clock time in `epochMillis`
73+
74+
Following the `clock` instance created in the prior example:
75+
76+
```
77+
// At 12:00am (real clock)
78+
clock.relativeTimeInMillis // returns epochMillis for 12:00am
79+
clock.referenceTimeInMillis // returns epochMillis for 12:00am
80+
81+
// At 1:00am (real clock)
82+
clock.relativeTimeInMillis // returns epochMillis for 1:00am
83+
clock.referenceTimeInMillis // returns epochMillis for 1:00am
84+
85+
// At 2:00am (real clock)
86+
clock.relativeTimeInMillis // returns epochMillis for 1:30am
87+
clock.referenceTimeInMillis // returns epochMillis for 2:00am
1788
89+
// At 3:00am (real clock)
90+
clock.relativeTimeInMillis // returns epochMillis for 2:00am
91+
clock.referenceTimeInMillis // returns epochMillis for 3:00am
92+
93+
// At 4:00am (real clock)
94+
clock.relativeTimeInMillis // returns epochMillis for 2:30am
95+
clock.referenceTimeInMillis // returns epochMillis for 4:00am
96+
97+
// At 5:00am (real clock)
98+
clock.relativeTimeInMillis // returns epochMillis for 3:00am
99+
clock.referenceTimeInMillis // returns epochMillis for 5:00am
100+
101+
// At 6:00am (real clock)
102+
clock.relativeTimeInMillis // returns epochMillis for 3:30am
103+
clock.referenceTimeInMillis // returns epochMillis for 6:00am
104+
105+
// At 7:00am (real clock)
106+
clock.relativeTimeInMillis // returns epochMillis for 4:00am
107+
clock.referenceTimeInMillis // returns epochMillis for 7:00am
108+
109+
// At 8:00am (real clock)
110+
clock.relativeTimeInMillis // returns epochMillis for 6:00am
111+
clock.referenceTimeInMillis // returns epochMillis for 8:00am
112+
113+
// At 9:00am (real clock)
114+
clock.relativeTimeInMillis // returns epochMillis for 8:00am
115+
clock.referenceTimeInMillis // returns epochMillis for 9:00am
116+
117+
// At 10:00am (real clock) **NOW WE'RE BACK IN SYNC**
118+
clock.relativeTimeInMillis // returns epochMillis for 10:00am
119+
clock.referenceTimeInMillis // returns epochMillis for 10:00am
120+
121+
// At 11:00am (real clock)
122+
clock.relativeTimeInMillis // returns epochMillis for 11:00am
123+
clock.referenceTimeInMillis // returns epochMillis for 11:00am
124+
```
125+
## Roadmap (as of August 2022)
126+
127+
There are plenty of improvements I can imagine implementing, but I need some time living with the API as it currently exists before going further. This is my top-o-the-head, tip-o-the-tongue wishlist:
128+
129+
1. Right now, when scheduling multiple time distortions, the API requires you to know the start and reference (real-time) end times if you would like to ensure non-overlapping distortion windows. This begs for a way to "chain" `RelativeTimeDistortion` instances, or perhaps pass another distortion into the constructor, and derive the next start-time from the end of the earlier one.
130+
2. We might want an abstraction for performing validation of the parameters in the `RelativeTimeDistortion` constructors. For example, it doesn't make a ton of sense to allow `relativeDuration` params to be smaller than `referenceDuration` params for the `ConstantTimeDilation`. Maybe there's some further refactoring that could avoid that, but I don't have any ideas on what that might look like. My intuition is that we wouldn't want to implicitly *disallow* overlapping if validation fails.
131+
3. I want something *smoother* than the `ConstantTime*` distortions. Let's get this shit rubber-banding across Gaussian roll-offs (hoping to have a PR for that soon). Sky is the limit, though, on extending `RelativeTimeDistortion` to implement some crazy behaviors. And to that end...
132+
4. Might need to rethink the extend `RelativeTimeDistortion` abstraction. Not yet sure whether we want to build further onto extending the class hierarchy and change that `distortTime` interface to an anonymous function that is passed-into the `RelativeTimeDistortion` instance? Maybe the function should take an `ratio` instead of direct access to protected attributes. I'm not sure yet, but I'm open to refactoring how that might work.
133+
5. I have a vague intuition that there are further levels of abstraction to be mined in building specific, pre-defined combinations of distortions, but I see that as further out on the roadmap.
134+
6. A case could probably be made for providing an interface for manipulating the distortions of an existing `Clock` instance, but right now, since the `referenceTime` is always based off of the underlying process' `Date.now()`, I'm content to just create new instances of `Clock`.
135+
136+
## API
137+
138+
TODO: Better docs
139+
140+
The most important thing is to understand the three paramters passed to an instance of `RelativeTimeDistortion`
141+
142+
- `referenceStartClockTime: ClockTimeDescriptor`,
143+
- `relativeDuration: Duration`,
144+
- `referenceDuration: Duration`,
145+
146+
`referenceStartClockTime` describes a (real) clock-time, at which the distortion should start, without reference to a specific date, or time-zone. Its type looks like this:
147+
148+
```
149+
export interface ClockTimeDescriptor {
150+
hour?: number;
151+
minute?: number;
152+
second?: number;
153+
millisecond?: number;
154+
}
155+
```
156+
157+
`relativeDuration` describes a duration of "fake" clock-time that should appear to pass during the distortion window. Its type looks like this:
158+
159+
```
160+
export interface Duration {
161+
hours?: number;
162+
minutes?: number;
163+
seconds?: number;
164+
milliseconds?: number;
165+
}
166+
```
167+
168+
`referenceDuration` describes a duration of "real" clock-time that actually will pass during the distortion window. Its type looks like this:
169+
170+
```
171+
export interface Duration {
172+
hours?: number;
173+
minutes?: number;
174+
seconds?: number;
175+
milliseconds?: number;
176+
}
177+
```
18178
## Contributing
19179

20180
1. Fork repo

jest.config.ci.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import sharedConfig from './jest.config';
1+
// eslint-disable-next-line @typescript-eslint/no-var-requires
2+
const sharedConfig = require('./jest.config');
23

3-
export default {
4+
module.exports = {
45
...sharedConfig,
56

67
// Indicates whether the coverage information should be collected while executing the test

jest.config.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
module.exports = {
22
roots: ['<rootDir>/test'],
3-
testMatch: [
4-
'**/__tests__/**/*.+(ts|tsx|js)',
5-
'**/?(*.)+(spec|test).+(ts|tsx|js)',
6-
],
3+
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
74
transform: {
85
'^.+\\.(ts|tsx)$': 'ts-jest',
96
},
107
globals: {
118
'ts-jest': {
12-
tsconfig: 'tsconfig.json'
13-
}
14-
}
15-
}
9+
tsconfig: 'tsconfig.json',
10+
},
11+
},
12+
};

0 commit comments

Comments
 (0)