Skip to main content

Overview

The renderer module provides functions for visualizing thread states, synchronization primitives, and simulation progress. Each synchronization scenario has its own specialized renderer that knows how to display its specific state.

Core Renderer: renderClients()

The primary rendering function for the mutex-based bank scenario.

Function Signature

renderer.js
export function renderClients(
  container,
  threads,
  mutexOwner,
  account,
  initialBalance,
)

Parameters

  • container - DOM element to render into
  • threads - Array of Thread objects from the engine
  • mutexOwner - The thread currently holding the mutex (or null)
  • account - Object with balance property
  • initialBalance - Initial account balance for calculating percentage

Example Usage

ui.js
renderClients(
  $("threads"),
  engine.threads,
  context.mutex.owner,
  context.account,
  1000,
);

Rendered Output

The function renders:
  1. Balance Status Bar - Visual progress bar showing remaining balance percentage
  2. Thread Cards - Individual cards for each thread showing their state

Balance Bar

renderer.js
const safeBalance = Number(account?.balance ?? 0);
const safeInitialBalance = Math.max(1, Number(initialBalance) || 1);
const percentage = Math.max(0, (safeBalance / safeInitialBalance) * 100);
const barColor =
  percentage > 50
    ? "bg-green-500"
    : percentage > 20
      ? "bg-yellow-500"
      : "bg-red-500";
The balance bar changes color based on remaining funds:
  • Green - More than 50% remaining
  • Yellow - 20-50% remaining
  • Red - Less than 20% remaining

Thread State Visualization

renderer.js
threads.forEach((thread) => {
  let border = "border-gray-600";
  let icon = "👤";
  let bg = "bg-gray-800";
  
  if (thread.state === "blocked") {
    border = "border-red-500 shadow-[0_0_10px_rgba(239,68,68,0.5)]";
    icon = "🔒";
  } else if (thread.state === "finished") {
    card.style.opacity = "0.4";
    icon = "✅";
  } else if (mutexOwner === thread) {
    border = "border-yellow-400 shadow-[0_0_15px_rgba(250,204,21,0.6)]";
    bg = "bg-gray-700";
    icon = "💰";
  }
  // ... render card
});
Thread States:
  • Running (with mutex) - Yellow border, glowing effect, 💰 icon
  • Blocked - Red border, pulsing glow, 🔒 icon
  • Finished - Faded opacity, ✅ icon
  • Ready - Default gray, 👤 icon

Semaphore Renderer: renderSemaphoreView()

Renders the printer scenario with semaphore-controlled resources.

Function Signature

semaphoreRenderer.js
export function renderSemaphoreView({
  printersContainer,
  threadsContainer,
  queueContainer,
  semaphoreCountNode,
  context,
  threads,
})

Parameters (Object Destructuring)

  • printersContainer - DOM element for printer cards
  • threadsContainer - DOM element for job/thread cards
  • queueContainer - DOM element for semaphore queue
  • semaphoreCountNode - DOM element showing available tokens
  • context - Scenario context with semaphore and printers
  • threads - Array of thread objects

Example Usage

semaphoreUI.js
renderSemaphoreView({
  printersContainer: $("semPrinters"),
  threadsContainer: $("semThreads"),
  queueContainer: $("semQueue"),
  semaphoreCountNode: $("semTokens"),
  context,
  threads: engine.threads,
});

Printer Card Rendering

semaphoreRenderer.js
context.printers.forEach((printer) => {
  const owner = printer.owner;
  const isBusy = Boolean(owner);
  const pages = owner ? Math.max(1, Number(owner.jobPages) || 1) : 0;
  const currentInst = owner?.currentInstruction?.();
  const phase = getPrinterPhase(owner, currentInst);
  
  // ... render printer card with:
  // - Status (ACTIVA/LIBRE)
  // - Current job name
  // - Page count
  // - Current phase
  // - Completed jobs count
  // - Total pages printed
  // - Last completed job
});

Printer Phase Detection

semaphoreRenderer.js
function getPrinterPhase(owner, currentInstruction) {
  if (!owner) return "En espera de trabajo";
  if (!currentInstruction) return "Finalizado";
  
  if (currentInstruction.type === Instructions.PRINT) return "Imprimiendo";
  if (currentInstruction.type === Instructions.SIGNAL_SEM) return "Liberando recurso";
  if (currentInstruction.type === Instructions.END) return "Cerrando trabajo";
  return "Preparando impresion";
}

Condition Renderer: renderConditionView()

Renders the restaurant scenario with condition variables.

Function Signature

conditionRenderer.js
export function renderConditionView({
  context,
  threads,
  chefThread,
  dishesNode,
  cookedNode,
  eatenNode,
  waitingNode,
  chefPanelNode,
  customersContainer,
})

Rendered Components

  1. Global Stats
    • Available dishes
    • Total cooked
    • Total eaten
  2. Waiting Queue - Customers blocked on condition variable
  3. Chef Panel - Dedicated visualization for the chef thread
  4. Customer Cards - Individual cards for each customer thread

Chef Phase Detection

conditionRenderer.js
function getChefPhase(thread, currentInstruction) {
  if (!thread) return "Sin chef";
  if (thread.state === "finished") return "Termino jornada";
  if (!currentInstruction) return "Sin instruccion";
  if (currentInstruction.type === Instructions.COOK_DISH) return "Cocinando plato";
  if (currentInstruction.type === Instructions.SIGNAL_FOOD) return "Llamando cliente";
  if (currentInstruction.type === Instructions.END) return "Cerrando cocina";
  return "Preparando siguiente paso";
}

Customer Phase Detection

conditionRenderer.js
function getCustomerPhase(thread) {
  const inst = thread.currentInstruction?.();
  if (!inst) return "Terminado";
  if (inst.type === Instructions.WAIT_FOOD) return "Esperando comida";
  if (inst.type === Instructions.EAT_FOOD) return "Comiendo";
  if (inst.type === Instructions.END) return "Saliendo del restaurante";
  return "En proceso";
}

Barrier Renderer: renderBarrierView()

Renders the race scenario with barrier synchronization.

Function Signature

barrierRenderer.js
export function renderBarrierView({
  context,
  threads,
  totalNode,
  checkpointNode,
  finishNode,
  waitingNode,
  racersContainer,
})

Racer State Tracking

barrierRenderer.js
threads.forEach((racer) => {
  const tone =
    racer.state === "finished"
      ? "border-emerald-500/40 bg-emerald-900/20"
      : racer.state === "blocked"
        ? "border-amber-500/40 bg-amber-900/20"
        : "border-cyan-500/30 bg-cyan-900/20";
  
  // Render with:
  // - State (running/blocked/finished)
  // - Current phase
  // - Checkpoint passed (yes/no)
  // - Finished race (yes/no)
});

Racer Phase Detection

barrierRenderer.js
function getRacerPhase(racer) {
  const inst = racer.currentInstruction?.();
  if (!inst) return "Termino";
  if (inst.type === Instructions.RUN_STAGE && inst.stage === "to-checkpoint") {
    return "Corriendo hacia checkpoint";
  }
  if (inst.type === Instructions.BARRIER_WAIT) return "Esperando barrera";
  if (inst.type === Instructions.RUN_STAGE && inst.stage === "to-finish") {
    return "Corriendo hacia meta";
  }
  if (inst.type === Instructions.END) return "Finalizando";
  return "En carrera";
}

Rendering Patterns

All renderer functions follow these common patterns:

1. Container Clearing

import { clear } from "../utils/dom.js";

clear(container);
// Now safe to append new elements

2. State-Based Styling

Thread state determines visual appearance:
const tone =
  thread.state === "finished"
    ? "border-emerald-500/40 bg-emerald-900/20"  // Green
    : thread.state === "blocked"
      ? "border-red-500/40 bg-red-900/20"        // Red
      : "border-white/10 bg-slate-950/60";       // Gray

3. Safe Value Handling

const safeBalance = Number(account?.balance ?? 0);
const pages = owner ? Math.max(1, Number(owner.jobPages) || 1) : 0;
Always protect against null, undefined, and invalid values.

4. Dynamic Content

card.innerHTML = `
  <div class="text-sm font-semibold">${thread.name}</div>
  <div class="text-[11px] text-gray-300 mt-1 uppercase">${thread.state}</div>
  <div class="text-[11px] text-gray-400 mt-1">PC: ${thread.pc}</div>
`;

Thread State Reference

Threads can be in one of these states:
  • "ready" - Ready to run
  • "running" - Currently executing
  • "blocked" - Waiting for a resource
  • "finished" - Completed execution
The renderer visualizes these states with:
  • Border colors
  • Background colors
  • Icons/emojis
  • Opacity
  • Glow effects

Update Cycle

Renderers are called from the UI module’s update() method:
// 1. Engine executes step
const events = engine.step(context);

// 2. UI updates timeline
events.forEach(e => timeline.addEvent(e));

// 3. UI calls renderer
renderClients(
  $("threads"),
  engine.threads,
  context.mutex.owner,
  context.account,
  initialBalance
);
This ensures the visualization stays synchronized with simulation state.

Performance Considerations

  1. Clear Before Render - Always clear containers to prevent memory leaks
  2. Batch DOM Updates - Build HTML strings then set innerHTML once
  3. Minimal Reflows - Use CSS classes for styling instead of inline styles where possible
  4. Safe Calculations - Protect against division by zero and null values

Styling with Tailwind CSS

All renderers use Tailwind CSS utility classes:
card.className = `
  ${bg} 
  p-4 
  rounded-xl 
  border-2 
  ${border} 
  transition-all 
  duration-300 
  w-32 
  flex 
  flex-col 
  items-center
`;
This provides:
  • Responsive design
  • Consistent spacing
  • Smooth transitions
  • Dark mode support

Next Steps