Skip to main content

Engine Class

The Engine class is the heart of the simulator, responsible for managing threads and executing instructions in a tick-based cycle.

Constructor

Creates a new execution engine instance.
const engine = new Engine();

Properties

threads
Array<Thread>
List of registered threads that will be executed
tick
number
Global cycle counter that increments with each step

Methods

addThread()

Registers a thread for execution in the engine.
thread
Thread
required
The thread instance to add to the execution queue
const thread = new Thread("Client-1", [
  { type: Instructions.ACQUIRE },
  { type: Instructions.WITHDRAW, amount: 100 },
  { type: Instructions.RELEASE }
]);

engine.addThread(thread);

step()

Executes one complete cycle across all threads. This is the main execution loop.
context
object
required
Scenario-specific context object containing shared resources
logs
Array<string>
Array of log messages generated during execution
engine.js
step(context) {
  this.tick++;
  const logs = [];

  for (const thread of this.threads) {
    // Skip threads that cannot advance
    if (thread.state === "blocked" || thread.state === "finished") continue;

    thread.state = "running";
    const inst = thread.currentInstruction();

    // Mark as finished if no instructions remain
    if (!inst) {
      thread.state = "finished";
      continue;
    }

    // Execute instruction and collect log message
    const resultLog = this.execute(thread, inst, context);
    if (resultLog) logs.push(resultLog);
  }

  return logs;
}

execute()

Executes a single instruction for a specific thread. This method contains the dispatcher logic for all instruction types.
thread
Thread
required
The thread executing the instruction
inst
object
required
The instruction object with type and optional parameters
ctx
object
required
Context object with shared resources (mutex, account, semaphore, etc.)
logMessage
string | undefined
Human-readable message describing what happened during execution

Context Objects

The context parameter varies by scenario:

Mutex Scenario

{
  mutex: Mutex,        // Binary lock
  account: {           // Shared bank account
    balance: number
  }
}

Semaphore Scenario

{
  semaphore: Semaphore,          // Counting semaphore
  printers: Array<{              // Printer pool
    owner: Thread | null,
    completedJobs: number,
    totalPages: number
  }>
}

Condition Variable Scenario

{
  restaurant: {
    availableDishes: number,
    totalCooked: number,
    totalEaten: number,
    foodCondition: ConditionVariable
  }
}

Execution Example

import { Engine } from "./core/engine.js";
import { Thread } from "./core/thread.js";
import { Mutex } from "./core/mutex.js";
import { Instructions } from "./core/instructions.js";

// Create engine
const engine = new Engine();

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

// Create threads
const thread1 = new Thread("Client-1", [
  { type: Instructions.ACQUIRE },
  { type: Instructions.WITHDRAW, amount: 100 },
  { type: Instructions.RELEASE }
]);

const thread2 = new Thread("Client-2", [
  { type: Instructions.ACQUIRE },
  { type: Instructions.DEPOSIT, amount: 50 },
  { type: Instructions.RELEASE }
]);

engine.addThread(thread1);
engine.addThread(thread2);

// Execute steps
const logs1 = engine.step(context);
console.log(logs1); // ["🔑 Client-1 entro en Seccion Critica", ...]

const logs2 = engine.step(context);
console.log(logs2); // ["💸 Client-1 modifico el saldo: -$100", ...]

Instruction Execution

The execute() method uses a large switch statement to handle different instruction types:
engine.js
switch (inst.type) {
  case Instructions.ACQUIRE:
    if (mutex.acquire(thread)) {
      thread.nextInstruction();
      return `🔑 ${thread.name} entro en Seccion Critica`;
    }
    return `🔒 ${thread.name} esta esperando el Mutex`;

  case Instructions.WITHDRAW:
    const requested = Number(inst.amount) || 0;
    const allowed = Math.min(requested, Math.max(0, account.balance));
    account.balance -= allowed;
    thread.nextInstruction();
    return `💸 ${thread.name} modifico el saldo: -$${allowed}`;

  case Instructions.RELEASE:
    mutex.release(thread);
    thread.nextInstruction();
    return `🔓 ${thread.name} salio y libero el Mutex`;

  // ... 26 more instruction types
}

Thread Scheduling

The engine uses a simple round-robin scheduler:
  1. Increment global tick counter
  2. Iterate through all threads
  3. Skip threads that are blocked or finished
  4. Set thread state to running
  5. Get current instruction via thread.currentInstruction()
  6. Execute instruction via execute()
  7. Collect log messages
  8. Return logs for UI rendering
Only threads in ready state will execute. Blocked threads remain in their queues until released by synchronization primitives.