Last active
March 15, 2025 22:35
-
-
Save rndmcnlly/bc89604b8d056c8d6e50683cedd4b177 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Preview: https://gistpreview.github.io/?bc89604b8d056c8d6e50683cedd4b177