Skip to main content

Overview

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.

Engine Class

The engine is defined in engine.js:
engine.js
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;
  }
}

Execution Cycle

The step() method implements the core execution loop:
1

Increment Tick Counter

The global tick counter increments to track simulation progress:
this.tick++;
2

Iterate Through Threads

The engine processes each registered thread in order:
for (const thread of this.threads) {
  // Process thread...
}
3

Skip Inactive Threads

Blocked and finished threads are skipped:
if (thread.state === "blocked" || thread.state === "finished") continue;
This ensures only ready threads execute in each step
4

Set Thread to Running

Mark the thread as currently executing:
thread.state = "running";
5

Get Current Instruction

Retrieve the instruction at the thread’s program counter:
const inst = thread.currentInstruction();
6

Handle End of Instructions

If no instruction exists, mark thread as finished:
if (!inst) {
  thread.state = "finished";
  continue;
}
7

Execute Instruction

Process the instruction and collect log output:
const resultLog = this.execute(thread, inst, context);
if (resultLog) logs.push(resultLog);
8

Return Logs

Return all execution logs for UI display:
return logs;

The execute() Method

The execute() method is a large dispatcher that handles all instruction types. It receives:
  • thread - The thread executing the instruction
  • inst - The instruction object with type and parameters
  • ctx - The scenario context (shared resources, state)

Execution Pattern

Most instructions follow this pattern:
switch (inst.type) {
  case Instructions.WITHDRAW:
    // 1. Perform the operation
    const requested = Number(inst.amount) || 0;
    const allowed = Math.min(requested, Math.max(0, account.balance));
    account.balance -= allowed;
    
    // 2. Advance program counter
    thread.nextInstruction();
    
    // 3. Return log message
    return `πŸ’Έ ${thread.name} modifico el saldo: -$${allowed}`;
}

Blocking Operations

Some instructions may block the thread:
engine.js
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.

State-Changing Instructions

Instructions that explicitly block threads:
engine.js
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`;

Thread Scheduling

The simulator uses round-robin scheduling - threads execute in the order they were added to the engine.
This deterministic scheduling makes it easier to understand synchronization behavior and reproduce specific scenarios.

Execution Order Example

engine.addThread(thread1); // Will execute first
engine.addThread(thread2); // Will execute second
engine.addThread(thread3); // Will execute third

engine.step(context); // Processes thread1, thread2, thread3 in order

Context Object

The context parameter passed to step() and execute() contains all shared resources and state for the current scenario:
const context = {
  mutex: new Mutex(),
  account: { balance: 1000 }
};

Waking Blocked Threads

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`;

Instruction Dispatcher Structure

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;
  }
}

Example: Complete Step Execution

Here’s what happens during a single step() call:
const engine = new Engine();
engine.addThread(new Thread("T1", [
  { type: Instructions.ACQUIRE },
  { type: Instructions.WITHDRAW, amount: 100 },
  { type: Instructions.RELEASE },
  { type: Instructions.END }
]));

const context = {
  mutex: new Mutex(),
  account: { balance: 1000 }
};

// First step: T1 acquires mutex
const logs1 = engine.step(context);
// logs1: ["πŸ”‘ T1 entro en Seccion Critica"]
// T1.pc = 1

// Second step: T1 withdraws money
const logs2 = engine.step(context);
// logs2: ["πŸ’Έ T1 modifico el saldo: -$100"]
// account.balance = 900
// T1.pc = 2

// Third step: T1 releases mutex
const logs3 = engine.step(context);
// logs3: ["πŸ”“ T1 salio y libero el Mutex"]
// T1.pc = 3

// Fourth step: T1 ends
const logs4 = engine.step(context);
// logs4: ["🏁 T1 finalizo sus tareas"]
// T1.state = "finished"

Next Steps

Instruction Set

Learn about all available instruction types

Scenarios

See how scenarios configure threads and context