@gigzen/populace 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.mjs +52 -4
- package/src/progress.mjs +13 -1
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -264,9 +264,24 @@ async function run() {
|
|
|
264
264
|
|
|
265
265
|
console.log(`\n Bringing ${agents} people to life across ${cities.join(", ")}…\n`);
|
|
266
266
|
|
|
267
|
+
// Sign-ups are counted here rather than in the engine, so the engine keeps
|
|
268
|
+
// knowing nothing about who is watching.
|
|
269
|
+
let joinedSoFar = 0;
|
|
267
270
|
const world = World.fromConfig(config, adapter, {
|
|
268
|
-
joined: (a) =>
|
|
269
|
-
|
|
271
|
+
joined: (a) => {
|
|
272
|
+
console.log(` ✓ ${a.persona.name} (${a.persona.city.name}, ${a.persona.platform})`);
|
|
273
|
+
progress.joining({
|
|
274
|
+
done: ++joinedSoFar, total: agents, ok: true,
|
|
275
|
+
name: a.persona.name, city: a.persona.city.name,
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
joinFailed: (p, e) => {
|
|
279
|
+
console.log(` ✖ ${p.name}: ${e.message}`);
|
|
280
|
+
progress.joining({
|
|
281
|
+
done: ++joinedSoFar, total: agents, ok: false,
|
|
282
|
+
name: p.name, city: p.city?.name,
|
|
283
|
+
});
|
|
284
|
+
},
|
|
270
285
|
// Said out loud, because waiting silently is indistinguishable from hanging.
|
|
271
286
|
// Retrying a throttled sign-up can add seconds per person, and a run that
|
|
272
287
|
// pauses without explanation is a run somebody kills.
|
|
@@ -286,10 +301,43 @@ async function run() {
|
|
|
286
301
|
},
|
|
287
302
|
});
|
|
288
303
|
|
|
289
|
-
|
|
304
|
+
const askedToStop = () => {
|
|
305
|
+
if (world.stopping) return;
|
|
290
306
|
world.stop();
|
|
291
307
|
console.log(`\n Stopping…\n`);
|
|
292
|
-
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
process.on("SIGINT", askedToStop);
|
|
311
|
+
|
|
312
|
+
/*
|
|
313
|
+
* A second way to ask, because the first one does not exist on Windows.
|
|
314
|
+
*
|
|
315
|
+
* `child.kill("SIGTERM")` there does not deliver a signal - Windows has no
|
|
316
|
+
* POSIX signals, so Node terminates the process outright whatever name is
|
|
317
|
+
* passed. A parent asking politely therefore killed the run mid-flight and
|
|
318
|
+
* stranded every account it had created, which is the one promise this tool
|
|
319
|
+
* makes loudest. Measured on 2026-08-26: pressing Stop during a 200-person
|
|
320
|
+
* run left all 200 behind.
|
|
321
|
+
*
|
|
322
|
+
* stdin works the same on every platform. A line containing "stop" asks for
|
|
323
|
+
* the same orderly wind-down SIGINT does: finish the tick, tear down, delete
|
|
324
|
+
* the accounts, write the report.
|
|
325
|
+
*
|
|
326
|
+
* unref() so simply having this listener never keeps the process alive, and
|
|
327
|
+
* resume() because a paused stdin emits nothing.
|
|
328
|
+
*/
|
|
329
|
+
if (!process.stdin.isTTY) {
|
|
330
|
+
let pending = "";
|
|
331
|
+
process.stdin.on("data", (chunk) => {
|
|
332
|
+
pending += String(chunk);
|
|
333
|
+
const lines = pending.split("\n");
|
|
334
|
+
pending = lines.pop() ?? "";
|
|
335
|
+
if (lines.some((l) => l.trim() === "stop")) askedToStop();
|
|
336
|
+
});
|
|
337
|
+
process.stdin.on("error", () => {}); // a closed pipe is not a reason to fail a run
|
|
338
|
+
process.stdin.resume();
|
|
339
|
+
process.stdin.unref();
|
|
340
|
+
}
|
|
293
341
|
|
|
294
342
|
await world.populate();
|
|
295
343
|
if (!world.agents.length) {
|
package/src/progress.mjs
CHANGED
|
@@ -71,7 +71,7 @@ function latencies(metrics) {
|
|
|
71
71
|
* @param {(line: string) => void} options.write
|
|
72
72
|
*/
|
|
73
73
|
export function createProgress({ enabled, everyLatency = 5, write = (l) => process.stdout.write(l) } = {}) {
|
|
74
|
-
if (!enabled) return { start() {}, tick() {}, done() {} };
|
|
74
|
+
if (!enabled) return { start() {}, joining() {}, tick() {}, done() {} };
|
|
75
75
|
|
|
76
76
|
const emit = (event) => {
|
|
77
77
|
try {
|
|
@@ -97,6 +97,18 @@ export function createProgress({ enabled, everyLatency = 5, write = (l) => proce
|
|
|
97
97
|
});
|
|
98
98
|
},
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* One event per person as they sign in, before any tick exists.
|
|
102
|
+
*
|
|
103
|
+
* Signing 250 people in takes minutes, and until this existed a watcher had
|
|
104
|
+
* nothing to show for it: every counter read zero, the progress bar stayed
|
|
105
|
+
* empty and the clock counted down as though the run were already under way.
|
|
106
|
+
* A window that looks identical to a hung one is a window people kill.
|
|
107
|
+
*/
|
|
108
|
+
joining({ done, total, name, city, ok }) {
|
|
109
|
+
emit({ type: "joining", done, total, name, city, ok });
|
|
110
|
+
},
|
|
111
|
+
|
|
100
112
|
tick(tickNo, totalTicks, world, metrics) {
|
|
101
113
|
const t = world.totals();
|
|
102
114
|
emit({
|