Files
pss/src/components/ControlBar/ControlBar.tsx
T
Lucas Guerra 5761dfe70a feat: add MLFQ scheduling algorithm and thread scheduler
- Implemented MLFQ (Multilevel Feedback Queue) algorithm with dynamic priority levels and quantum management.
- Created unit tests for MLFQ algorithm covering initialization, demotion, selection, priority boost, preemption, and quantum expiration.
- Developed a ThreadScheduler class supporting FCFS, RR, and PRIORITY scheduling policies for intra-process thread management.
- Added unit tests for ThreadScheduler to validate thread selection and quantum expiration logic.
2026-03-24 23:09:16 -03:00

168 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
SkipBack, StepForward, Play, Pause, RotateCcw
} from 'lucide-react';
import { useProcessStore } from '../../store/processStore';
import { useSimulationStore, type SimStatus } from '../../store/simulationStore';
import { useUiStore, SPEED_OPTIONS } from '../../store/uiStore';
import IconButton from '../shared/IconButton';
const SPEED_LABELS: Record<number, string> = {
0.25: '0.25×',
0.5: '0.5×',
1: '1×',
2: '2×',
4: '4×',
};
const statusLabel: Record<SimStatus, string> = {
idle: 'Ready',
paused: 'Paused',
running: 'Running',
finished: 'Finished',
};
const statusDotClass: Record<SimStatus, string> = {
idle: 'bg-slate-500',
paused: 'bg-amber-400',
running: 'bg-emerald-400 animate-pulse',
finished: 'bg-blue-400',
};
const ControlBar = () => {
const processes = useProcessStore(s => s.processes);
const config = useProcessStore(s => s.config);
const status = useSimulationStore(s => s.status);
const ticks = useSimulationStore(s => s.ticks);
const { init, stepOnce, play, pause, reset } = useSimulationStore();
const speed = useUiStore(s => s.speed);
const setSpeed = useUiStore(s => s.setSpeed);
const currentTick = ticks.length;
const isIdle = status === 'idle';
const isRunning = status === 'running';
const isFinished = status === 'finished';
const canPlay = (status === 'paused' || status === 'idle') && !isFinished && processes.length > 0;
const handlePlay = () => {
if (isIdle) {
init(processes, config);
}
play();
};
const handleStep = () => {
if (isIdle) {
init(processes, config);
}
stepOnce();
};
const handleReset = () => {
if (isIdle) return;
reset();
};
return (
<div
className="h-16 py-2 flex items-center px-5 gap-3 bg-slate-800 border-b border-slate-700 shadow-sm"
role="toolbar"
aria-label="Simulation controls"
>
{/* Left group: simulation controls */}
<div className="flex items-center gap-1.5">
{/* Reset */}
<IconButton
label="Reset"
variant="ghost"
onClick={handleReset}
disabled={isIdle}
data-testid="btn-reset"
>
<RotateCcw size={16} />
</IconButton>
{/* Step */}
<IconButton
label="Advance 1 tick (Step)"
variant="default"
onClick={handleStep}
disabled={isFinished || processes.length === 0 || isRunning}
data-testid="btn-step"
>
<StepForward size={18} />
</IconButton>
{/* Play / Pause */}
{isRunning ? (
<IconButton
label="Pause simulation"
variant="primary"
onClick={pause}
data-testid="btn-pause"
>
<Pause size={18} />
</IconButton>
) : (
<IconButton
label="Start / Resume simulation"
variant="primary"
onClick={handlePlay}
disabled={!canPlay && !isRunning}
data-testid="btn-play"
>
<Play size={18} />
</IconButton>
)}
</div>
{/* Divider */}
<div className="h-6 w-px bg-slate-700 mx-1" aria-hidden="true" />
{/* Tick display */}
<div
className="flex items-center gap-2 px-3 py-1 rounded-md bg-slate-900 border border-slate-700"
aria-live="polite"
aria-label={`Current tick: ${currentTick}`}
>
<span className="text-[10px] text-slate-500 uppercase tracking-wide font-medium">Tick</span>
<span
className="font-mono font-bold text-blue-300 text-sm min-w-[3ch] text-right"
data-testid="tick-display"
>
{String(currentTick).padStart(4, '0')}
</span>
</div>
{/* Status badge */}
<div className="flex items-center gap-1.5">
<span className={`h-2 w-2 rounded-full ${statusDotClass[status]}`} aria-hidden="true" />
<span className="text-xs text-slate-400">{statusLabel[status]}</span>
</div>
{/* Right group: speed selector */}
<div className="ml-auto flex items-center gap-2">
<SkipBack size={14} className="text-slate-500" aria-hidden="true" />
<label htmlFor="speed-select" className="text-xs text-slate-400 sr-only">
Speed
</label>
<select
id="speed-select"
value={speed}
onChange={e => setSpeed(Number(e.target.value))}
className="rounded-md bg-slate-700 border border-slate-600 text-slate-200 text-xs px-2 py-1 focus:outline-none focus:ring-1 focus:ring-blue-500 cursor-pointer"
aria-label="Simulation speed"
data-testid="speed-select"
>
{SPEED_OPTIONS.map(s => (
<option key={s} value={s}>{SPEED_LABELS[s]}</option>
))}
</select>
</div>
</div>
);
};
export default ControlBar;