The Engine class is the heart of the simulator. It manages all registered threads, executes instructions step-by-step, and maintains the global simulation state.
import { Instructions } from "./instructions.js";export class Engine { constructor() { this.threads = []; // Lista de hilos registrados. this.tick = 0; // Ciclo global del simulador. } // Registra un hilo en el motor. addThread(thread) { this.threads.push(thread); } // Ejecuta un ciclo completo sobre todos los hilos. step(context) { this.tick++; const logs = []; for (const thread of this.threads) { // Ignora hilos que no pueden avanzar. if (thread.state === "blocked" || thread.state === "finished") continue; thread.state = "running"; const inst = thread.currentInstruction(); // Si no quedan instrucciones, marca fin. if (!inst) { thread.state = "finished"; continue; } // Ejecuta la instruccion y guarda mensaje para timeline. const resultLog = this.execute(thread, inst, context); if (resultLog) logs.push(resultLog); } return logs; }}
case Instructions.ACQUIRE: if (mutex.acquire(thread)) { thread.nextInstruction(); return `π ${thread.name} entro en Seccion Critica`; } return `π ${thread.name} esta esperando el Mutex`;
When an operation fails (e.g., mutex not available), the thread does NOT advance its program counter. The same instruction will be attempted again in the next step.
case Instructions.WAIT_FOOD: if (ctx.restaurant.availableDishes > 0) { ctx.restaurant.availableDishes -= 1; thread.hasReservedDish = true; thread.nextInstruction(); return `π½οΈ ${thread.name} recibio un plato y pasa a comer`; } // Explicitly block the thread thread.state = "blocked"; thread.blockedBy = ctx.restaurant.foodCondition; ctx.restaurant.foodCondition.wait(thread); return `πͺ ${thread.name} espera comida en el restaurante`;
engine.addThread(thread1); // Will execute firstengine.addThread(thread2); // Will execute secondengine.addThread(thread3); // Will execute thirdengine.step(context); // Processes thread1, thread2, thread3 in order
Some instructions wake up blocked threads by changing their state:
engine.js
case Instructions.SIGNAL_FOOD: // Wake up a waiting customer const awakened = ctx.restaurant.foodCondition.signal(); thread.nextInstruction(); if (!awakened) { return `π£ ${thread.name} anuncio comida, pero no habia clientes esperando`; } awakened.state = "ready"; // Thread can now execute awakened.blockedBy = null; return `π ${thread.name} llamo a ${awakened.name}: comida lista`;
The execute() method uses a large switch statement to handle all instruction types:
execute(thread, inst, ctx) { const { mutex, account } = ctx; switch (inst.type) { case Instructions.ACQUIRE: // Mutex operations case Instructions.WITHDRAW: case Instructions.DEPOSIT: // Bank operations case Instructions.WAIT_SEM: case Instructions.PRINT: case Instructions.SIGNAL_SEM: // Semaphore operations case Instructions.WAIT_FOOD: case Instructions.EAT_FOOD: case Instructions.COOK_DISH: case Instructions.SIGNAL_FOOD: // Condition variable operations case Instructions.ENTER_READ: case Instructions.READ_BOOK: case Instructions.EXIT_READ: case Instructions.ENTER_WRITE: case Instructions.UPDATE_CATALOG: case Instructions.EXIT_WRITE: // Monitor operations case Instructions.BARRIER_WAIT: // Barrier operations case Instructions.JOIN_THREAD: case Instructions.AWAIT_ALL: // Thread coordination case Instructions.PETERSON_LOCK: case Instructions.PETERSON_UNLOCK: // Peterson's algorithm case Instructions.END: // Thread termination default: return null; }}