mirror of
https://github.com/felixrieseberg/windows95.git
synced 2026-05-14 18:31:59 +00:00
Merge pull request #345 from felixrieseberg/claude/kind-hermann
Improve VM info bar: live CPU/disk/net throughput + hover-to-reveal
This commit is contained in:
@@ -1,8 +1,19 @@
|
||||
#status-hotzone {
|
||||
position: absolute;
|
||||
z-index: 99;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
#status {
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
left: calc(50vw - 110px);
|
||||
left: 50vw;
|
||||
transform: translateX(-50%);
|
||||
white-space: nowrap;
|
||||
background: white;
|
||||
font-size: 10px;
|
||||
padding-bottom: 3px;
|
||||
@@ -13,4 +24,14 @@
|
||||
padding-right: 10px;
|
||||
max-height: 18px;
|
||||
top: 0;
|
||||
transition: transform 0.12s ease-out;
|
||||
|
||||
&.hidden {
|
||||
transform: translateX(-50%) translateY(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
#status-hotzone:hover + #status.hidden,
|
||||
#status.hidden:hover {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,15 @@ import * as React from "react";
|
||||
interface EmulatorInfoProps {
|
||||
toggleInfo: () => void;
|
||||
emulator: any;
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
interface EmulatorInfoState {
|
||||
cpu: number;
|
||||
disk: string;
|
||||
diskRead: number;
|
||||
diskWrite: number;
|
||||
netRx: number;
|
||||
netTx: number;
|
||||
lastCounter: number;
|
||||
lastTick: number;
|
||||
}
|
||||
@@ -16,33 +20,49 @@ export class EmulatorInfo extends React.Component<
|
||||
EmulatorInfoProps,
|
||||
EmulatorInfoState
|
||||
> {
|
||||
private cpuInterval = -1;
|
||||
private tickInterval = -1;
|
||||
private diskReadBytes = 0;
|
||||
private diskWriteBytes = 0;
|
||||
private netRxBytes = 0;
|
||||
private netTxBytes = 0;
|
||||
|
||||
constructor(props: EmulatorInfoProps) {
|
||||
super(props);
|
||||
|
||||
this.cpuCount = this.cpuCount.bind(this);
|
||||
this.onIDEReadStart = this.onIDEReadStart.bind(this);
|
||||
this.onIDEReadWriteEnd = this.onIDEReadWriteEnd.bind(this);
|
||||
this.tick = this.tick.bind(this);
|
||||
this.onIDEReadEnd = this.onIDEReadEnd.bind(this);
|
||||
this.onIDEWriteEnd = this.onIDEWriteEnd.bind(this);
|
||||
this.onEthReceiveEnd = this.onEthReceiveEnd.bind(this);
|
||||
this.onEthTransmitEnd = this.onEthTransmitEnd.bind(this);
|
||||
|
||||
this.state = {
|
||||
cpu: 0,
|
||||
disk: "Idle",
|
||||
diskRead: 0,
|
||||
diskWrite: 0,
|
||||
netRx: 0,
|
||||
netTx: 0,
|
||||
lastCounter: 0,
|
||||
lastTick: 0,
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { cpu, disk } = this.state;
|
||||
const { cpu, diskRead, diskWrite, netRx, netTx } = this.state;
|
||||
const { hidden, toggleInfo } = this.props;
|
||||
|
||||
return (
|
||||
<div id="status">
|
||||
Disk: <span>{disk}</span> | CPU Speed: <span>{cpu}</span> |{" "}
|
||||
<a href="#" onClick={this.props.toggleInfo}>
|
||||
Hide
|
||||
</a>
|
||||
</div>
|
||||
<>
|
||||
<div id="status-hotzone" />
|
||||
<div id="status" className={hidden ? "hidden" : ""}>
|
||||
CPU: <span>{cpu}M/s</span> | Disk:{" "}
|
||||
<span>R {this.rate(diskRead)}</span>{" "}
|
||||
<span>W {this.rate(diskWrite)}</span> | Net:{" "}
|
||||
<span>↓{this.rate(netRx)}</span> <span>↑{this.rate(netTx)}</span> |{" "}
|
||||
<a href="#" onClick={toggleInfo}>
|
||||
{hidden ? "Pin" : "Hide"}
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,21 +99,17 @@ export class EmulatorInfo extends React.Component<
|
||||
return;
|
||||
}
|
||||
|
||||
// CPU
|
||||
if (this.cpuInterval > -1) {
|
||||
clearInterval(this.cpuInterval);
|
||||
if (this.tickInterval > -1) {
|
||||
clearInterval(this.tickInterval);
|
||||
}
|
||||
|
||||
// TypeScript think's we're using a Node.js setInterval. We're not.
|
||||
this.cpuInterval = setInterval(this.cpuCount, 500) as unknown as number;
|
||||
this.tickInterval = setInterval(this.tick, 500) as unknown as number;
|
||||
|
||||
// Disk
|
||||
emulator.add_listener("ide-read-start", this.onIDEReadStart);
|
||||
emulator.add_listener("ide-read-end", this.onIDEReadWriteEnd);
|
||||
emulator.add_listener("ide-write-end", this.onIDEReadWriteEnd);
|
||||
|
||||
// Screen
|
||||
emulator.add_listener("screen-set-size-graphical", console.log);
|
||||
emulator.add_listener("ide-read-end", this.onIDEReadEnd);
|
||||
emulator.add_listener("ide-write-end", this.onIDEWriteEnd);
|
||||
emulator.add_listener("eth-receive-end", this.onEthReceiveEnd);
|
||||
emulator.add_listener("eth-transmit-end", this.onEthTransmitEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,58 +125,67 @@ export class EmulatorInfo extends React.Component<
|
||||
return;
|
||||
}
|
||||
|
||||
// CPU
|
||||
if (this.cpuInterval > -1) {
|
||||
clearInterval(this.cpuInterval);
|
||||
if (this.tickInterval > -1) {
|
||||
clearInterval(this.tickInterval);
|
||||
}
|
||||
|
||||
// Disk
|
||||
emulator.remove_listener("ide-read-start", this.onIDEReadStart);
|
||||
emulator.remove_listener("ide-read-end", this.onIDEReadWriteEnd);
|
||||
emulator.remove_listener("ide-write-end", this.onIDEReadWriteEnd);
|
||||
emulator.remove_listener("ide-read-end", this.onIDEReadEnd);
|
||||
emulator.remove_listener("ide-write-end", this.onIDEWriteEnd);
|
||||
emulator.remove_listener("eth-receive-end", this.onEthReceiveEnd);
|
||||
emulator.remove_listener("eth-transmit-end", this.onEthTransmitEnd);
|
||||
}
|
||||
|
||||
// Screen
|
||||
emulator.remove_listener("screen-set-size-graphical", console.log);
|
||||
private onIDEReadEnd(args: number[]) {
|
||||
this.diskReadBytes += args[1];
|
||||
}
|
||||
|
||||
private onIDEWriteEnd(args: number[]) {
|
||||
this.diskWriteBytes += args[1];
|
||||
}
|
||||
|
||||
private onEthReceiveEnd(args: number[]) {
|
||||
this.netRxBytes += args[0];
|
||||
}
|
||||
|
||||
private onEthTransmitEnd(args: number[]) {
|
||||
this.netTxBytes += args[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* The virtual IDE is handling read (start).
|
||||
* Format bytes/sec into a compact human string.
|
||||
*/
|
||||
private onIDEReadStart() {
|
||||
this.requestIdle(() => this.setState({ disk: "Read" }));
|
||||
private rate(bytesPerSec: number) {
|
||||
if (bytesPerSec <= 0) return "0";
|
||||
if (bytesPerSec < 1024) return `${bytesPerSec}B/s`;
|
||||
if (bytesPerSec < 1024 * 1024) return `${Math.round(bytesPerSec / 1024)}K/s`;
|
||||
return `${(bytesPerSec / 1024 / 1024).toFixed(1)}M/s`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The virtual IDE is handling read/write (end).
|
||||
* Once per interval, compute CPU speed and I/O throughput.
|
||||
*/
|
||||
private onIDEReadWriteEnd() {
|
||||
this.requestIdle(() => this.setState({ disk: "Idle" }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Request an idle callback with a 3s timeout.
|
||||
*
|
||||
* @param fn
|
||||
*/
|
||||
private requestIdle(fn: () => void) {
|
||||
(window as any).requestIdleCallback(fn, { timeout: 3000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates what's up with the virtual cpu.
|
||||
*/
|
||||
private cpuCount() {
|
||||
private tick() {
|
||||
const { lastCounter, lastTick } = this.state;
|
||||
|
||||
const now = Date.now();
|
||||
const instructionCounter = this.props.emulator.get_instruction_counter();
|
||||
const ips = instructionCounter - lastCounter;
|
||||
const deltaTime = now - lastTick;
|
||||
const deltaSec = deltaTime / 1000;
|
||||
|
||||
this.setState({
|
||||
lastTick: now,
|
||||
lastCounter: instructionCounter,
|
||||
cpu: Math.round(ips / deltaTime),
|
||||
cpu: Math.round(ips / deltaTime / 1000),
|
||||
diskRead: Math.round(this.diskReadBytes / deltaSec),
|
||||
diskWrite: Math.round(this.diskWriteBytes / deltaSec),
|
||||
netRx: Math.round(this.netRxBytes / deltaSec),
|
||||
netTx: Math.round(this.netTxBytes / deltaSec),
|
||||
});
|
||||
|
||||
this.diskReadBytes = 0;
|
||||
this.diskWriteBytes = 0;
|
||||
this.netRxBytes = 0;
|
||||
this.netTxBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,13 +271,10 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
* Render the little info thingy
|
||||
*/
|
||||
public renderInfo() {
|
||||
if (!this.state.isInfoDisplayed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EmulatorInfo
|
||||
emulator={this.state.emulator}
|
||||
hidden={!this.state.isInfoDisplayed}
|
||||
toggleInfo={() => {
|
||||
this.setState({ isInfoDisplayed: !this.state.isInfoDisplayed });
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user