diff --git a/dist.tar.gz b/dist.tar.gz index ef4c93b..a3835f3 100644 Binary files a/dist.tar.gz and b/dist.tar.gz differ diff --git a/package-lock.json b/package-lock.json index 0ecf887..d97c9fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -147,6 +147,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -519,6 +520,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -567,6 +569,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } @@ -1892,8 +1895,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -2034,6 +2036,7 @@ "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -2051,6 +2054,7 @@ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -2062,6 +2066,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -2373,6 +2378,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2725,8 +2731,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/eastasianwidth": { "version": "0.2.0", @@ -3544,7 +3549,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -3760,6 +3764,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3802,7 +3807,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -3818,7 +3822,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -3829,7 +3832,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -3852,6 +3854,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -3864,6 +3867,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -3884,6 +3888,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -3960,7 +3965,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -4546,6 +4552,7 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -4644,6 +4651,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", diff --git a/src/components/GanttChart/GanttRow.tsx b/src/components/GanttChart/GanttRow.tsx index 85a3098..cc75674 100644 --- a/src/components/GanttChart/GanttRow.tsx +++ b/src/components/GanttChart/GanttRow.tsx @@ -21,9 +21,9 @@ const GanttRow = React.memo(function GanttRow({
{ticks.map((tick) => { const state: StateLabel = tick.states[process.id] ?? "New"; - // A context-switch tick affects the process currently on CPU + // A context-switch tick is shown on the incoming (next-to-run) process const isCtxSwitch = - tick.contextSwitching && tick.cpuProcess === process.id; + tick.contextSwitching && tick.ctxSwitchForProcess === process.id; return ( { readyQueue: [], states: { p1: "Running" }, contextSwitching: false, + ctxSwitchForProcess: null, }; const noop = () => {}; diff --git a/src/components/GanttChart/__tests__/GanttRow.test.tsx b/src/components/GanttChart/__tests__/GanttRow.test.tsx new file mode 100644 index 0000000..9a1a28b --- /dev/null +++ b/src/components/GanttChart/__tests__/GanttRow.test.tsx @@ -0,0 +1,77 @@ +import { describe, it, expect, vi } from "vitest"; +import { render } from "@testing-library/react"; +import GanttRow from "../GanttRow"; +import type { Process, SimTick } from "@core/types"; + +describe("GanttRow – isCtxSwitch propagation", () => { + const p1: Process = { + id: "p1", name: "P1", arrivalTime: 0, priority: 5, + color: "#4FC3F7", bursts: [{ type: "cpu", duration: 2 }], + }; + const p2: Process = { + id: "p2", name: "P2", arrivalTime: 0, priority: 5, + color: "#FF7043", bursts: [{ type: "cpu", duration: 2 }], + }; + + const ctxTick: SimTick = { + tick: 2, + cpuProcess: null, + ioProcesses: [], + readyQueue: ["p2"], + states: { p1: "Ready", p2: "Ready" }, + contextSwitching: true, + ctxSwitchForProcess: "p2", + }; + + const noop = vi.fn(); + + it("renders the context-switch color on the incoming process (p2)", () => { + const { container } = render( + + ); + // The outer div is the row; its first child is the GanttCell div + const cell = container.firstChild?.firstChild as HTMLElement; + expect(cell.className).toContain("bg-[var(--color-state-ctx)]"); + }); + + it("does NOT render context-switch color on the outgoing process (p1)", () => { + const { container } = render( + + ); + const cell = container.firstChild?.firstChild as HTMLElement; + expect(cell.className).not.toContain("bg-[var(--color-state-ctx)]"); + }); + + it("does not mark ctx-switch when contextSwitching is false", () => { + const normalTick: SimTick = { + tick: 0, + cpuProcess: "p1", + ioProcesses: [], + readyQueue: [], + states: { p1: "Running", p2: "Ready" }, + contextSwitching: false, + ctxSwitchForProcess: null, + }; + const { container } = render( + + ); + const cell = container.firstChild?.firstChild as HTMLElement; + expect(cell.className).not.toContain("bg-[var(--color-state-ctx)]"); + expect(cell.className).toContain("bg-[var(--color-state-running)]"); + }); +}); diff --git a/src/components/Layout/AppHeader.tsx b/src/components/Layout/AppHeader.tsx index 38b5a04..5aae59c 100644 --- a/src/components/Layout/AppHeader.tsx +++ b/src/components/Layout/AppHeader.tsx @@ -55,7 +55,7 @@ const AppHeader = () => { return (
{/* Title */} -

+

Process Scheduler Simulator

@@ -81,10 +81,11 @@ const AppHeader = () => { onClick={() => setPresetsOpen(false)} /> {/* Dropdown */} -
-
+
+
Cenários pré-definidos
+
{presets.map((preset, i) => (
))} +
)} diff --git a/src/core/__tests__/engine.test.ts b/src/core/__tests__/engine.test.ts index 0f1205c..b551b97 100644 --- a/src/core/__tests__/engine.test.ts +++ b/src/core/__tests__/engine.test.ts @@ -276,6 +276,25 @@ describe("SimulationEngine general", () => { expect(ticks[1]?.cpuProcess).toBe("p1"); expect(ticks[2]?.contextSwitching).toBe(true); expect(ticks[2]?.cpuProcess).toBeNull(); + expect(ticks[2]?.ctxSwitchForProcess).toBe("p2"); expect(ticks[3]?.cpuProcess).toBe("p2"); }); + + it("ctxSwitchForProcess identifies incoming process across all overhead ticks", () => { + const engine = new SimulationEngine( + [ + p("p1", 0, [{ type: "cpu", duration: 2 }]), + p("p2", 0, [{ type: "cpu", duration: 2 }]), + ], + cfg({ algorithm: "FCFS", contextSwitchTime: 2 }), + ); + const ticks = engine.runAll(); + // P1 runs ticks 0-1, ctx-switch ticks 2-3, P2 runs ticks 4-5 + expect(ticks[2]?.contextSwitching).toBe(true); + expect(ticks[2]?.ctxSwitchForProcess).toBe("p2"); + expect(ticks[3]?.contextSwitching).toBe(true); + expect(ticks[3]?.ctxSwitchForProcess).toBe("p2"); + expect(ticks[4]?.contextSwitching).toBe(false); + expect(ticks[4]?.ctxSwitchForProcess).toBeNull(); + }); }); diff --git a/src/core/engine.ts b/src/core/engine.ts index 50f9af3..7e0659d 100644 --- a/src/core/engine.ts +++ b/src/core/engine.ts @@ -239,6 +239,9 @@ export class SimulationEngine { readyQueue: this._readyQueue.map((rt) => rt.processId), states, contextSwitching, + ctxSwitchForProcess: contextSwitching + ? (this._readyQueue[0]?.processId ?? null) + : null, }; } } diff --git a/src/core/presets.ts b/src/core/presets.ts index bb3df64..1677c55 100644 --- a/src/core/presets.ts +++ b/src/core/presets.ts @@ -98,6 +98,16 @@ export const presets: Preset[] = [ ], config: defaultConfig({ algorithm: "FCFS" }), }, + { + name: "context_switch_demo", + description: "3 processos FCFS com 2 ticks de overhead — veja as células vermelhas de Context Switch", + processes: [ + { id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 3 }] }, + { id: "p2", name: "P2", arrivalTime: 0, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 3 }] }, + { id: "p3", name: "P3", arrivalTime: 0, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 3 }] }, + ], + config: defaultConfig({ algorithm: "FCFS", contextSwitchTime: 2 }), + }, { name: "multilevel_demo", description: "Priority RR with 3 priority tiers — demonstrates multilevel scheduling", diff --git a/src/core/types.ts b/src/core/types.ts index b42eb21..cb09458 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -76,6 +76,7 @@ export interface SimTick { readyQueue: string[]; // processIds in ready queue (in queue order) states: Record; // all processId → state contextSwitching: boolean; // true during context-switch overhead + ctxSwitchForProcess: string | null; // incoming processId during ctx-switch overhead (null otherwise) } // --------------- Metrics ------------------------------------