Created
April 6, 2022 10:39
-
-
Save soruh/54d788d4fc2d7cfdab2b40eb523a7fbd to your computer and use it in GitHub Desktop.
replay proof of concept
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
type Replay = { | |
when: Date, | |
resolve: () => void, | |
}; | |
// jobs that are waiting for a virtual timeout (for us to skip more time) | |
// this could be a btree set for more effieciency | |
let replay_buffer: Replay[] = []; | |
// current replay time. starts at the point the game was closed last time and ends when we have caught up to real-time. | |
let replay_time: Date | null = null; | |
// set when all jobs are waiting for something other than a delay and resolved when a job registers a delay. | |
let new_replay_promise: (() => void) | null = null; | |
// debugging function | |
let print_state: (() => void) | null = null; | |
async function start_replay() { | |
// if there is nothing to replay just skip the missed time | |
if (replay_buffer.length == 0) replay_time = null; | |
while (replay_time != null) { | |
if (replay_buffer.length == 0) { | |
// wait until a new delay is registered | |
await new Promise(resolve => { | |
new_replay_promise = resolve as () => void; | |
}); | |
} else { | |
// find the next job to replay | |
let index = -1; | |
let min = Infinity; | |
for (let i in replay_buffer) { | |
let dt = +replay_buffer[i].when - +replay_time; | |
if (dt < min) { | |
min = dt; | |
index = +i; | |
} | |
} | |
let now = Date.now(); | |
// if we have cought up. turn remaining replays into normal timeouts and clear the replay buffer | |
if (+replay_time + min > now) { | |
console.log("\ndone, turning remaining replays into timeouts."); | |
replay_time = null; | |
for (let replay of replay_buffer) setTimeout(replay.resolve, (+replay.when - now)); | |
replay_buffer = []; | |
return; | |
} | |
// replay the next job and remove it from the buffer | |
let replay = replay_buffer.splice(index, 1)[0]; | |
replay.resolve(); | |
let new_replay_state = new Date(+replay_time + min); | |
replay_time = new_replay_state; | |
} | |
} | |
} | |
function delay(dt: number): Promise<void> { | |
// print state for debugging | |
if (print_state != null) print_state(); | |
if (replay_time != null) { | |
// we are replaying. register a replay and wait for it to complete | |
let t = +replay_time; | |
return new Promise(resolve => { | |
replay_buffer.push({ when: new Date(t + dt), resolve }); | |
// notify the executor that there are new replays avaylable if it was previously starved | |
if (new_replay_promise != null) new_replay_promise(); | |
}); | |
} else { | |
// register a regular timeout | |
return new Promise(resolve => setTimeout(resolve, dt)); | |
} | |
} | |
type State = { | |
money: number, | |
}; | |
async function script(state: State, dm: number, dt: number) { | |
while (true) { | |
state.money += dm; | |
await delay(dt); | |
// fake async work to simulate non `delay` async operations during job execution | |
// (1ms if way to much but also the minumum timeout we can register) | |
await new Promise(resolve => setTimeout(resolve, 1)); | |
} | |
} | |
async function main(argv: string[]) { | |
let start_time: Date | null = null; | |
if (argv.length > 0) { | |
start_time = new Date(Date.now() - parseInt(argv[0])); | |
console.log(start_time); | |
replay_time = start_time; | |
} | |
let state = { | |
money: 0 | |
}; | |
// draw a pretty bar | |
print_state = () => { | |
if (start_time && replay_time) { | |
const now = Date.now(); | |
const bar_length = 100; | |
const remaining = (now - +replay_time) / 1000; | |
const total = (now - +start_time) / 1000; | |
const bar_fill = Math.ceil((1 - remaining / total) * bar_length) | |
const bar = "#".repeat(bar_fill) + "-".repeat(bar_length - bar_fill); | |
Deno.writeAllSync(Deno.stdout, new TextEncoder().encode(`\rcatching up: ${bar}`)); | |
} else { | |
console.log("new money:", state.money); | |
} | |
}; | |
// start scripts | |
script(state, 50, 5000); | |
script(state, 10, 500); | |
script(state, 200, 10000); | |
if (replay_time) await start_replay(); | |
} | |
main(Deno.args); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment