Skip to content

Instantly share code, notes, and snippets.

@rndmcnlly
Last active March 15, 2025 22:35
Show Gist options
  • Save rndmcnlly/bc89604b8d056c8d6e50683cedd4b177 to your computer and use it in GitHub Desktop.
Save rndmcnlly/bc89604b8d056c8d6e50683cedd4b177 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Block Deduplication Visualization</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.frame {
display: flex;
margin-bottom: 20px;
align-items: center;
}
.time-label {
width: 50px;
font-weight: bold;
text-align: right;
margin-right: 10px;
}
.blocks {
display: flex;
gap: 5px;
}
.block {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
position: relative;
border: 1px solid #333;
}
.gold {
background-color: #ffd700;
}
.dark-gray {
background-color: #777;
color: white;
}
.light-gray {
background-color: #ddd;
}
button {
padding: 8px 16px;
margin: 10px 0;
cursor: pointer;
}
svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
.controls {
margin-bottom: 20px;
}
.legend {
display: flex;
margin: 20px;
gap: 20px;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
}
.legend-box {
width: 20px;
height: 20px;
border: 1px solid #333;
}
</style>
</head>
<body>
<div class="container">
<h1>Block Deduplication Over Time</h1>
<p>This procedural diagram offers a cartoon illustration of how we can deduplicate block-aligned data within and across incrementally changing frames over time.</p>
<h2>
Notes
</h2>
<p>On the first frame, some blocks are simply the default (zero) block while others range in value. In later frames, most blocks are simply copies of the previous frame, but some change. Changed blocks might duplicate a block seen in previous frames or elsewhere in the current frame.</p>
<h2>
Diagram
</h2>
<button id="regenerate">Regenerate Random Data</button>
<div class="legend">
<div class="legend-item">
<div class="legend-box gold"></div>
<div>New Unique Block</div>
</div>
<div class="legend-item">
<div class="legend-box dark-gray"></div>
<div>Duplicate Changed Block</div>
</div>
<div class="legend-item">
<div class="legend-box light-gray"></div>
<div>Duplicate Unchanged Block</div>
</div>
</div>
<div id="visualization"></div>
</div>
<script>
// Parameters - Edit these to change the visualization
const NUM_BLOCKS = 16; // Number of memory blocks
const NUM_FRAMES = 8; // Number of frames/timesteps
const INITIAL_CHANGE_PERCENT = 50; // % of blocks changed at t=0
const SUBSEQUENT_CHANGE_PERCENT = 20; // % of blocks changed at each subsequent frame
// Data structures
let memoryData = []; // Raw memory values
let blockStatus = []; // Classification of blocks (new, dup-changed, dup-unchanged)
let blockSources = []; // Where each duplicate block points to
function generateData() {
memoryData = [];
blockStatus = [];
blockSources = [];
// Initialize all memory to zeros for t=0
let initialMemory = Array(NUM_BLOCKS).fill(0);
// Randomly change some percentage of blocks at t=0
const initialChanges = Math.floor(NUM_BLOCKS * INITIAL_CHANGE_PERCENT / 100);
let indices = getRandomIndices(NUM_BLOCKS, initialChanges);
for (let idx of indices) {
initialMemory[idx] = getRandomValue();
}
memoryData.push([...initialMemory]);
// Generate subsequent frames
for (let t = 1; t < NUM_FRAMES; t++) {
const prevMemory = [...memoryData[t-1]];
const newMemory = [...prevMemory];
// Randomly change some percentage of blocks
const numChanges = Math.floor(NUM_BLOCKS * SUBSEQUENT_CHANGE_PERCENT / 100);
indices = getRandomIndices(NUM_BLOCKS, numChanges);
for (let idx of indices) {
newMemory[idx] = getRandomValue();
}
memoryData.push(newMemory);
}
// Analyze data to determine block status and sources
analyzeData();
}
function analyzeData() {
// For each frame, analyze what blocks are new vs duplicates
for (let t = 0; t < NUM_FRAMES; t++) {
let frameStatus = [];
let frameSources = [];
for (let i = 0; i < NUM_BLOCKS; i++) {
const currentValue = memoryData[t][i];
// Check if this value exists in current frame's earlier blocks
let found = false;
let sourceFrame = -1;
let sourceIndex = -1;
// First check current frame (earlier blocks)
for (let j = 0; j < i; j++) {
if (memoryData[t][j] === currentValue) {
found = true;
sourceFrame = t;
sourceIndex = j;
break;
}
}
// If not found in current frame, check previous frames
if (t > 0) {
for (let prevT = t-1; prevT >= 0; prevT--) {
for (let j = 0; j < NUM_BLOCKS; j++) {
if (memoryData[prevT][j] === currentValue) {
found = true;
sourceFrame = prevT;
sourceIndex = j;
break;
}
}
}
}
// Check if changed from previous frame
let changed = t === 0 || memoryData[t][i] !== memoryData[t-1][i];
// Determine status
if (!found) {
// New unique block
frameStatus.push('new');
frameSources.push(null);
} else {
// Duplicate block
if (changed) {
frameStatus.push('dup-changed');
} else {
frameStatus.push('dup-unchanged');
}
frameSources.push({ frame: sourceFrame, index: sourceIndex });
}
}
blockStatus.push(frameStatus);
blockSources.push(frameSources);
}
}
function renderVisualization() {
const visElement = document.getElementById('visualization');
visElement.innerHTML = '';
// Create an SVG overlay for arrows
const svgOverlay = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgOverlay.style.position = 'absolute';
svgOverlay.style.top = '0';
svgOverlay.style.left = '0';
svgOverlay.style.width = '100%';
svgOverlay.style.height = '100%';
svgOverlay.style.pointerEvents = 'none';
document.body.appendChild(svgOverlay);
// For each frame
for (let t = 0; t < NUM_FRAMES; t++) {
const frameDiv = document.createElement('div');
frameDiv.className = 'frame';
// Add time label
const timeLabel = document.createElement('div');
timeLabel.className = 'time-label';
timeLabel.textContent = `t=${t}`;
frameDiv.appendChild(timeLabel);
// Add blocks container
const blocksDiv = document.createElement('div');
blocksDiv.className = 'blocks';
// Add individual blocks
for (let i = 0; i < NUM_BLOCKS; i++) {
const blockDiv = document.createElement('div');
blockDiv.className = 'block';
blockDiv.id = `block-${t}-${i}`;
blockDiv.textContent = '0x' + memoryData[t][i].toString(16).toUpperCase();
// Set block color based on status
if (blockStatus[t][i] === 'new') {
blockDiv.classList.add('gold');
} else if (blockStatus[t][i] === 'dup-changed') {
blockDiv.classList.add('dark-gray');
} else {
blockDiv.classList.add('light-gray');
}
blocksDiv.appendChild(blockDiv);
}
frameDiv.appendChild(blocksDiv);
visElement.appendChild(frameDiv);
}
// Draw arrows after all blocks are rendered and positioned
setTimeout(drawArrows, 0);
}
function drawArrows() {
const svgOverlay = document.body.querySelector('svg');
svgOverlay.innerHTML = '';
// For each frame
for (let t = 0; t < NUM_FRAMES; t++) {
// For each block
for (let i = 0; i < NUM_BLOCKS; i++) {
// Only draw arrows for duplicate blocks
if (blockStatus[t][i] !== 'new') {
const source = blockSources[t][i];
if (source) {
const sourceBlock = document.getElementById(`block-${source.frame}-${source.index}`);
const currentBlock = document.getElementById(`block-${t}-${i}`);
if (sourceBlock && currentBlock) {
drawCurvedArrow(sourceBlock, currentBlock, svgOverlay);
}
}
}
}
}
}
function drawCurvedArrow(sourceElem, targetElem, svgContainer) {
const sourceRect = sourceElem.getBoundingClientRect();
const targetRect = targetElem.getBoundingClientRect();
const svgRect = svgContainer.getBoundingClientRect();
// Calculate positions relative to SVG container
const sourceX = sourceRect.left + sourceRect.width/2 - svgRect.left;
const sourceY = sourceRect.top + sourceRect.height/2 - svgRect.top;
const targetX = targetRect.left + targetRect.width/2 - svgRect.left;
const targetY = targetRect.top + targetRect.height/2 - svgRect.top;
// Create the curved path
const dx = targetX - sourceX;
const dy = targetY - sourceY;
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
// Determine control points for the curve
const controlX = sourceX;
const controlY = sourceY + targetRect.height;
path.setAttribute('d', `M ${sourceX},${sourceY} Q ${controlX},${controlY} ${targetX},${targetY}`);
path.setAttribute('fill', 'none');
path.setAttribute('stroke', '#666');
path.setAttribute('opacity', '0.5');
path.setAttribute('stroke-width', '1.5');
path.setAttribute('marker-end', 'url(#arrowhead)');
// Add arrow marker if it doesn't exist
if (!document.getElementById('arrowhead')) {
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
const marker = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
marker.setAttribute('id', 'arrowhead');
marker.setAttribute('markerWidth', '10');
marker.setAttribute('markerHeight', '7');
marker.setAttribute('refX', '9');
marker.setAttribute('refY', '3.5');
marker.setAttribute('orient', 'auto');
const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
polygon.setAttribute('points', '0 0, 10 3.5, 0 7');
polygon.setAttribute('fill', '#666');
marker.appendChild(polygon);
defs.appendChild(marker);
svgContainer.appendChild(defs);
}
svgContainer.appendChild(path);
}
// Helper functions
function getRandomIndices(max, count) {
const indices = [];
while (indices.length < count) {
const idx = Math.floor(Math.random() * max);
if (!indices.includes(idx)) {
indices.push(idx);
}
}
return indices;
}
function getRandomValue() {
return Math.floor(Math.random() * 16); // Values from 0x0 to 0xF
}
// Initialize
document.getElementById('regenerate').addEventListener('click', () => {
generateData();
renderVisualization();
});
// Initial generation
generateData();
renderVisualization();
</script>
</body>
</html>
@rndmcnlly
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment