
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
@playertwo/devtools
Advanced tools
Development tools for playertwo - state inspector, action history, network monitor
Development tools for playertwo multiplayer SDK - debug your multiplayer games with ease.
pnpm add -D @playertwo/devtools
import { StateInspector } from '@playertwo/devtools';
import { GameRuntime } from '@playertwo/core';
// Create your game runtime
const runtime = new GameRuntime(myGame, transport, config);
// Attach the inspector
const inspector = new StateInspector();
inspector.attach(runtime);
// View state snapshots
console.log(inspector.getSnapshots());
// View action history
console.log(inspector.getActionHistory());
// View statistics
console.log(inspector.getStats());
// Listen for changes
inspector.onStateChange((snapshot) => {
console.log('State changed:', snapshot.state);
});
inspector.onAction((action) => {
console.log('Action submitted:', action.actionName, action.input);
});
// Clean up when done
inspector.detach();
new StateInspector(options?: StateInspectorOptions)
Options:
maxSnapshots (default: 100) - Maximum number of state snapshots to keepmaxActions (default: 1000) - Maximum number of actions to keep in historysnapshotIntervalMs (default: 250) - Minimum time between automatic snapshotsactionAggregationWindowMs (default: 200) - Time window for grouping identical actionsignoreActions (default: []) - Array of action names to drop entirely (e.g. ['tick'])attach(runtime: GameRuntime): voidAttach the inspector to a GameRuntime instance. Only one runtime can be attached at a time.
inspector.attach(runtime);
detach(): voidDetach the inspector from the current runtime and stop tracking.
inspector.detach();
isAttached(): booleanCheck if the inspector is currently attached to a runtime.
if (inspector.isAttached()) {
console.log('Inspector is active');
}
getRuntime(): GameRuntime | nullGet the currently attached runtime, or null if not attached.
const runtime = inspector.getRuntime();
getSnapshots(): StateSnapshot[]Get all captured state snapshots.
const snapshots = inspector.getSnapshots();
snapshots.forEach(snapshot => {
console.log(snapshot.timestamp, snapshot.state);
});
Returns an array of:
interface StateSnapshot {
id: number; // Incrementing identifier
timestamp: number; // Unix timestamp in milliseconds
state?: any; // Deep clone of game state (only stored for baseline snapshots)
diff?: Patch[]; // Diffs relative to previous snapshot
lastActionId?: number;// Action id responsible for this snapshot
}
getActionHistory(): ActionRecord[]Get all tracked actions.
const history = inspector.getActionHistory();
history.forEach(action => {
console.log(action.actionName, action.input);
});
Returns an array of:
interface ActionRecord {
id: number; // Incrementing identifier
timestamp: number; // Unix timestamp in milliseconds
actionName: string; // Name of the action
input: any; // Action payload
playerId?: string; // Who submitted the action
targetId?: string; // Who was targeted
count?: number; // Number of aggregated occurrences
duration?: number; // Duration of aggregated burst in ms
snapshotId?: number; // Linked snapshot id
excludedActionsTotal?: number; // Total ignored actions so far
}
getStats(): InspectorStatsGet statistics about tracked actions and state changes.
const stats = inspector.getStats();
console.log(`Total actions: ${stats.totalActions}`);
console.log(`Total state changes: ${stats.totalStateChanges}`);
console.log(`Increment action called ${stats.actionsByName.increment} times`);
Returns:
interface InspectorStats {
totalActions: number; // Total actions submitted
totalStateChanges: number; // Total state updates
actionsByName: Record<string, number>; // Action frequency map
excludedActions: number; // Actions filtered via ignoreActions
}
onStateChange(listener: (snapshot: StateSnapshot) => void): () => voidListen for state changes. Returns an unsubscribe function.
const unsubscribe = inspector.onStateChange((snapshot) => {
console.log('New state:', snapshot.state);
});
// Later: stop listening
unsubscribe();
onAction(listener: (action: ActionRecord) => void): () => voidListen for actions. Returns an unsubscribe function.
const unsubscribe = inspector.onAction((action) => {
console.log(`${action.actionName} submitted by ${action.playerId}`);
});
// Later: stop listening
unsubscribe();
clear(): voidClear all snapshots, action history, and statistics.
inspector.clear();
const inspector = new StateInspector();
inspector.attach(runtime);
// Submit some actions
runtime.submitAction('move', { x: 100, y: 200 });
runtime.submitAction('attack', { targetId: 'enemy1' });
// View state evolution
const snapshots = inspector.getSnapshots();
console.log('Initial state:', snapshots[0].state);
console.log('Final state:', snapshots[snapshots.length - 1].state);
const inspector = new StateInspector();
inspector.attach(runtime);
let actionCount = 0;
inspector.onAction(() => {
actionCount++;
if (actionCount % 100 === 0) {
const stats = inspector.getStats();
console.log(`Actions per second: ${actionCount / ((Date.now() - startTime) / 1000)}`);
}
});
const inspector = new StateInspector({ maxSnapshots: 50 });
inspector.attach(runtime);
// React component example
function DevToolsPanel() {
const [snapshots, setSnapshots] = useState([]);
const [actions, setActions] = useState([]);
useEffect(() => {
const unsubState = inspector.onStateChange((snapshot) => {
setSnapshots(prev => [...prev, snapshot]);
});
const unsubAction = inspector.onAction((action) => {
setActions(prev => [...prev, action]);
});
return () => {
unsubState();
unsubAction();
};
}, []);
return (
<div>
<StateViewer snapshots={snapshots} />
<ActionHistory actions={actions} />
<Statistics stats={inspector.getStats()} />
</div>
);
}
const inspector = new StateInspector();
inspector.attach(runtime);
inspector.onAction((action) => {
console.log(`[${action.timestamp}] ${action.playerId} -> ${action.actionName}`);
});
inspector.onStateChange((snapshot) => {
// Check for state anomalies
if (snapshot.state.players[myId]?.health < 0) {
console.warn('Invalid state detected:', snapshot);
}
});
import { describe, it, expect } from 'vitest';
import { StateInspector } from '@playertwo/devtools';
describe('MyGame', () => {
it('should increment counter on action', () => {
const inspector = new StateInspector();
inspector.attach(runtime);
const initialState = inspector.getSnapshots()[0].state;
expect(initialState.count).toBe(0);
runtime.submitAction('increment', {});
const finalSnapshots = inspector.getSnapshots();
const finalState = finalSnapshots[finalSnapshots.length - 1].state;
expect(finalState.count).toBe(1);
inspector.detach();
});
});
maxSnapshots and maxActions to prevent memory issuesdetach() to clean up listenersMIT
FAQs
Development tools for playertwo - state inspector, action history, network monitor
The npm package @playertwo/devtools receives a total of 2 weekly downloads. As such, @playertwo/devtools popularity was classified as not popular.
We found that @playertwo/devtools demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.