Skip to main content

Overview

The Thread Synchronization Simulator uses a modular UI architecture where each synchronization scenario has its own dedicated UI module. All UI modules follow a consistent pattern for initialization, state management, and rendering.

UI Module Structure

The UI system is organized into specialized modules, each handling a different synchronization primitive:
  • UI (ui.js) - Mutex-based bank scenario
  • SemaphoreUI (semaphoreUI.js) - Semaphore-based printer scenario
  • ConditionUI (conditionUI.js) - Condition variables for restaurant scenario
  • BarrierUI (barrierUI.js) - Barrier synchronization for race scenario
  • MonitorUI (monitorUI.js) - Monitor pattern for library scenario
  • JoinUI (joinUI.js) - Join/await for house construction scenario
  • PetersonUI (petersonUI.js) - Peterson’s algorithm for robot station
Each module is a singleton object that encapsulates all UI logic for its scenario.

Common Initialization Pattern

All UI modules follow the init() pattern, which establishes the connection between the UI and the simulation engine:
ui.js
init(engine) {
  const timeline = new Timeline($("timeline"));
  let context = null;

  // Setup event handlers
  $("generateScenario").onclick = () => {
    this.stopAuto();
    this.simulationFinished = false;
    engine.threads = [];
    
    // Create scenario with user parameters
    context = createBankScenario(engine, num, operations, bal);
    timeline.clear();
    this.update(engine, context);
  };

  $("stepBtn").onclick = () => {
    this.runTick(engine, context, timeline);
  };

  $("autoBtn").onclick = () => {
    if (this.autoInterval) this.stopAuto();
    else this.startAuto(engine, context, timeline);
  };
}

Key Components

  1. Engine Reference - The engine parameter connects the UI to the simulation engine
  2. Timeline - Each module creates its own Timeline instance for event logging
  3. Context - Scenario-specific state (mutex, semaphore, etc.)
  4. Event Handlers - Connect DOM elements to simulation controls

UI Module Properties

Every UI module maintains these properties:
export const UI = {
  autoInterval: null,           // Interval ID for auto-execution
  simulationFinished: false,    // Prevents duplicate finish messages
  // ... methods
};

Core Methods

Each UI module implements these standard methods:

init(engine)

Initializes the UI module with a reference to the simulation engine. Parameters:
  • engine - The Engine instance that executes simulation steps
Example:
semaphoreUI.js
SemaphoreUI.init(engine);

runTick(engine, context, timeline)

Executes a single simulation step and updates the UI.
ui.js
runTick(engine, context, timeline) {
  if (!context) return;
  
  const events = engine.step(context);
  const active = engine.threads.some((t) => t.state !== "finished");
  
  if (events.length === 0) {
    if (active) {
      timeline.addEvent("Planificador: Sin tareas pendientes.");
    } else if (!this.simulationFinished) {
      timeline.addEvent("Simulacion terminada satisfactoriamente.");
      this.simulationFinished = true;
    }
  } else {
    events.forEach((e) => timeline.addEvent(e));
  }
  
  if (!active) this.stopAuto();
  this.update(engine, context);
}

startAuto(engine, context, timeline)

Starts automatic execution at 1-second intervals.
ui.js
startAuto(engine, context, timeline) {
  $("autoBtn").innerText = "Detener";
  $("autoBtn").classList.replace("bg-emerald-600", "bg-red-600");
  
  this.autoInterval = setInterval(() => {
    this.runTick(engine, context, timeline);
  }, 1000);
}

stopAuto()

Stops automatic execution and resets the UI.
ui.js
stopAuto() {
  clearInterval(this.autoInterval);
  this.autoInterval = null;
  $("autoBtn").innerText = "Auto";
  $("autoBtn").classList.replace("bg-red-600", "bg-emerald-600");
}

update(engine, context)

Synchronizes the visual state with the simulation state. Each module implements this differently based on its scenario.
ui.js
update(engine, context) {
  $("balance").innerText = "$" + context.account.balance;
  $("owner").innerText = context.mutex.owner
    ? context.mutex.owner.name
    : "Libre";
  
  // Update mutex queue visualization
  clear($("queue"));
  context.mutex.queue.forEach((t) => {
    const node = document.createElement("div");
    node.className = "bg-red-900/50 border border-red-500 text-[10px] px-2 py-1 rounded text-red-200 animate-pulse";
    node.innerText = t.name;
    $("queue").appendChild(node);
  });
  
  // Render thread cards
  renderClients(
    $("threads"),
    engine.threads,
    context.mutex.owner,
    context.account,
    Number($("initialBalance").value) || 1,
  );
}

Connecting UI to Engine

The UI modules connect to the simulation engine through a well-defined interface:

Engine Integration

import { Engine } from "./core/engine.js";
import { UI } from "./ui/ui.js";
import { SemaphoreUI } from "./ui/semaphoreUI.js";

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

// Initialize UI modules
UI.init(engine);
SemaphoreUI.init(engine);
ConditionUI.init(engine);
// ... other modules

Engine Methods Used by UI

  • engine.step(context) - Executes one simulation tick, returns event logs
  • engine.threads - Array of all threads in the simulation
  • engine.tick - Current simulation tick counter

Scenario Context

Each scenario creates a context object that contains:
bankScenario.js
context = {
  mutex: new Mutex(),
  account: { balance: initialBalance },
  // ... scenario-specific properties
};
The UI passes this context to engine.step() on each tick.

Scenario Generation Pattern

UI modules typically follow this pattern for generating scenarios:
semaphoreUI.js
$("semGenerateScenario").onclick = () => {
  // 1. Stop any running simulation
  this.stopAuto();
  this.simulationFinished = false;
  engine.threads = [];
  
  // 2. Read parameters from UI
  const jobs = Math.max(1, Number($("semJobCount").value) || 1);
  const printers = this.getAutoPrinterCount(jobs);
  const jobConfig = this.getJobsFromUI(jobs);
  
  // 3. Create scenario
  context = createPrinterScenario(engine, jobs, printers, jobConfig);
  
  // 4. Initialize timeline
  timeline.clear();
  timeline.addEvent(`Sistema listo: ${jobs} trabajos y ${printers} impresoras.`);
  
  // 5. Update UI
  this.update(engine, context);
};

UI State Management

The UI modules manage state through:
  1. Simulation State - Stored in engine.threads and context
  2. UI State - Stored in module properties (autoInterval, simulationFinished)
  3. DOM State - Reflected in the rendered HTML elements
The update() method ensures these states stay synchronized.

Render Functions

UI modules delegate rendering to specialized renderer functions:
  • renderClients() - Renders thread cards with state visualization (source/js/ui/renderer.js:4)
  • renderSemaphoreView() - Renders printer and job state (source/js/ui/semaphoreRenderer.js:5)
  • renderConditionView() - Renders restaurant with chef and customers (source/js/ui/conditionRenderer.js:5)
  • renderBarrierView() - Renders race checkpoint state (source/js/ui/barrierRenderer.js:5)
  • renderMonitorView() - Renders library with readers and writer
  • renderJoinView() - Renders house construction stages
  • renderPetersonView() - Renders robot station state
See Renderer API for detailed documentation.

Example: Complete UI Flow

Here’s how the UI system works from start to finish:
// 1. User clicks "Generate Scenario"
// UI creates context and initializes simulation
context = createBankScenario(engine, 3, operations, 1000);

// 2. User clicks "Step" button
// UI executes one simulation tick
const events = engine.step(context);
// Returns: ["🔑 Cliente-1 entro en Seccion Critica"]

// 3. UI logs events to timeline
timeline.addEvent(events[0]);

// 4. UI updates visualization
UI.update(engine, context);
// Renders updated thread states, mutex queue, account balance

// 5. User clicks "Auto" button
// UI starts automatic execution
UI.startAuto(engine, context, timeline);
// Executes runTick() every 1000ms

Best Practices

  1. Always stop auto-execution before generating a new scenario
  2. Clear the timeline when starting a new scenario
  3. Check for null context before running ticks
  4. Use simulationFinished flag to prevent duplicate completion messages
  5. Delegate rendering to specialized renderer functions
  6. Follow the init() pattern for consistency across modules

Next Steps