js-dojo

2 real event emitter probe

GOAL

Do it against real code. Take Node's ACTUAL EventEmitter (the base class of every game bus, stream and process object in the runtime), install your prototype perf probe on EventEmitter.prototype.emit, fire 10 'hit' events through a live GameBus, prove the probe saw all 10, uninstall, prove the prototype is restored BY REFERENCE.

CONCEPT

the same installPrototypeProbe as capstone 01 — but aimed at a library you do not own and cannot edit. One extra mechanic shows up right away: our bus is NOT a bare EventEmitter — it is a GameBus whose prototype CHAIN merely inherits emit from EventEmitter.prototype. Patching the BASE prototype still catches every call, because no subclass shadows emit. (Two asserts below pin down both halves of that claim before you touch anything.)

HINT

same recipe as capstone 01 TODO 1: capture proto[methodName], swap in a `function (...args)` (never an arrow — `this` must stay dynamic) that times original.apply(this, args) inside try/finally and bumps stats; uninstall() writes the captured original back.

MIRRORS

this is how a perf harness or a debugging plugin hooks a game engine's event bus, a UI toolkit's render method or a web framework's request handler without forking it: wrap the shared prototype method, measure, uninstall, leave no trace. Every subclass instance in the process rides through the same slot.

NOTE

needs nothing but Node — `node:events` ships with the runtime, so there is no library to install and no path to fix. Remember the probe is process-wide while it is installed: keep the window tight.

Run

node 02-real-event-emitter-probe.cjs   (fails until you fix the TODO)

Source

/*
 * proto-dojo 06-capstone / 02-real-event-emitter-probe  (STRETCH)
 * ------------------------------------------------------------
 * GOAL: Do it against real code. Take Node's ACTUAL EventEmitter (the
 *       base class of every game bus, stream and process object in the
 *       runtime), install your prototype perf probe on
 *       EventEmitter.prototype.emit, fire 10 'hit' events through a
 *       live GameBus, prove the probe saw all 10, uninstall, prove the
 *       prototype is restored BY REFERENCE.
 *
 * CONCEPT: the same installPrototypeProbe as capstone 01 — but aimed at
 *          a library you do not own and cannot edit. One extra mechanic
 *          shows up right away: our bus is NOT a bare EventEmitter — it
 *          is a GameBus whose prototype CHAIN merely inherits emit from
 *          EventEmitter.prototype. Patching the BASE prototype still
 *          catches every call, because no subclass shadows emit. (Two
 *          asserts below pin down both halves of that claim before you
 *          touch anything.)
 *
 * HINT: same recipe as capstone 01 TODO 1: capture proto[methodName],
 *       swap in a `function (...args)` (never an arrow — `this` must
 *       stay dynamic) that times original.apply(this, args) inside
 *       try/finally and bumps stats; uninstall() writes the captured
 *       original back.
 *
 * MIRRORS: this is how a perf harness or a debugging plugin hooks a
 *          game engine's event bus, a UI toolkit's render method or a
 *          web framework's request handler without forking it: wrap the
 *          shared prototype method, measure, uninstall, leave no trace.
 *          Every subclass instance in the process rides through the
 *          same slot.
 *
 * NOTE: needs nothing but Node — `node:events` ships with the runtime,
 *       so there is no library to install and no path to fix. Remember
 *       the probe is process-wide while it is installed: keep the
 *       window tight.
 *
 * Run: node 02-real-event-emitter-probe.cjs   (fails until you fix the TODO)
 */
'use strict';
const assert = require('node:assert');

/* ---------------- the library you don't own ------------------------- */
const { EventEmitter } = require('node:events');

// The engine's bus subclasses the runtime's emitter and adds nothing
// that shadows emit — exactly the shape a third-party engine hands you.
class GameBus extends EventEmitter {}

console.log('EventEmitter loaded from node:events on', process.version);

/* ------------------------------------------------------------------ */
/* TODO 1: same probe as capstone 01 — wrap proto[methodName] with a    */
/*         timing wrapper feeding `stats`; uninstall() restores the     */
/*         captured original by reference.                              */
/* ------------------------------------------------------------------ */
function installProbe(proto, methodName) {
    const stats = { calls: 0, totalMs: 0 };
    // TODO 1: capture proto[methodName], swap in a wrapper that counts,
    //         times and forwards; make uninstall() put the capture back
    return {
        stats,
        uninstall() {},
    };
}

/* ------------------------- live bus -------------------------------- */
const bus = new GameBus();
let hitsLanded = 0;
bus.on('hit', (hit) => { hitsLanded += hit.damage > 0 ? 1 : 0; });

// The match is already under way: two hits landed before any probing.
bus.emit('hit', { at: 0, damage: 4 });
bus.emit('hit', { at: 16, damage: 4 });

// The subclass wrinkle, stated as facts before you probe anything:
assert.notStrictEqual(Object.getPrototypeOf(bus), EventEmitter.prototype,
    'bus is a GameBus — its DIRECT prototype is not EventEmitter.prototype');
assert.ok(!Object.getPrototypeOf(bus).hasOwnProperty('emit'),
    'but the subclass does not shadow emit — it inherits it from the base, ' +
    'which is exactly why patching EventEmitter.prototype works');

/* ------------------------- probe + fire ----------------------------- */
const EmitterProto = EventEmitter.prototype;
const originalEmit = EmitterProto.emit; // captured BEFORE install

const probe = installProbe(EmitterProto, 'emit');

for (let i = 0; i < 10; i++) {
    bus.emit('hit', { at: 100 + i * 16, damage: 7 + i }); // one frame's worth
}                                                         // of incoming events

assert.strictEqual(probe.stats.calls, 10,
    `TODO 1: the probe counted ${probe.stats.calls} of 10 fired hits — ` +
    'swap EmitterProto.emit for a wrapper that counts, times, and forwards');
assert.strictEqual(hitsLanded, 12,
    'the REAL emit must still run: 2 opening + 10 fired = 12 hits landed');
assert.ok(Number.isFinite(probe.stats.totalMs) && probe.stats.totalMs >= 0,
    'totalMs must hold accumulated performance.now() deltas');

/* ------------------------- uninstall -------------------------------- */
probe.uninstall();
assert.strictEqual(EmitterProto.emit, originalEmit,
    'TODO 1: uninstall must put back the ORIGINAL function by reference — ' +
    '=== to the pre-install capture, no lookalikes');

bus.emit('hit', { at: 1000, damage: 9 }); // the listener still fires — the
                                          // engine never noticed the
                                          // probe
assert.strictEqual(probe.stats.calls, 10,
    'after uninstall the probe must be deaf — no more counting');
assert.strictEqual(hitsLanded, 13,
    'and the bus keeps working untouched');

console.log('OK 02-real-event-emitter-probe — %d calls, %s ms inside real emit',
    probe.stats.calls, probe.stats.totalMs.toFixed(3));

Solution