sparkletown/sparkle

View on GitHub
scripts/simulation/experience.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import { strict as assert } from "assert";

import chalk from "chalk";

import { addBotReaction as actualReactToExperience } from "../lib/bot";
import { withErrorReporter } from "../lib/log";
import { SimContext } from "../lib/types";
import { sleep } from "../lib/utils";

export const DEFAULT_EXPERIENCE_CHUNK_SIZE = 100;
export const DEFAULT_EXPERIENCE_TICK_MS = 1000;
export const DEFAULT_EXPERIENCE_AFFINITY = 0.005;

export const simExperience: (options: SimContext) => Promise<void> = async (
  options
) => {
  const { userRefs, conf, stop } = options;

  const affinity =
    conf.experience?.affinity ?? conf.affinity ?? DEFAULT_EXPERIENCE_AFFINITY;
  const tick = conf.experience?.tick ?? conf.tick ?? DEFAULT_EXPERIENCE_TICK_MS;
  const chunkSize =
    conf.experience?.chunkSize ??
    conf.chunkSize ??
    DEFAULT_EXPERIENCE_CHUNK_SIZE;

  assert.ok(
    Number.isSafeInteger(chunkSize) && chunkSize > 0,
    chalk`${simExperience.name}(): {magenta chunkCount} must be integer {yellow > 0}`
  );
  assert.ok(
    Number.isFinite(tick) && tick >= 10,
    chalk`${simExperience.name}(): {magenta tick} must be integer {yellow >= 10}`
  );
  assert.ok(
    0 <= affinity && affinity <= 1,
    chalk`${simExperience.name}(): {magenta affinity} must be a number {yellow from 0 to 1}`
  );

  const reactToExperience = withErrorReporter(
    conf.log,
    actualReactToExperience
  );

  // flag that will not let loop going on when user pressed CTRL+C
  let isStopped = false;
  stop.then(() => (isStopped = true));

  const loop = async () => {
    for (let i = 0, j = userRefs.length; !isStopped && i < j; i += chunkSize) {
      await Promise.all(
        userRefs
          .slice(i, i + chunkSize)
          .map(
            async (userRef) =>
              Math.random() < affinity &&
              reactToExperience({ ...options, userRef })
          )
      );
      // explicit sleep between the chunks
      !isStopped && (await sleep(tick));
    }

    // implicit sleep between the loops
    !isStopped && setTimeout(loop, tick);
  };

  // start looping the chat updates
  await loop();
};