Skip to content

Instantly share code, notes, and snippets.

@soruh
Created April 6, 2022 10:39
Show Gist options
  • Save soruh/54d788d4fc2d7cfdab2b40eb523a7fbd to your computer and use it in GitHub Desktop.
Save soruh/54d788d4fc2d7cfdab2b40eb523a7fbd to your computer and use it in GitHub Desktop.
replay proof of concept
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