-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a graph for the content of the ROM 📉.
- Loading branch information
Showing
9 changed files
with
245 additions
and
25 deletions.
There are no files selected for viewing
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import RomMapCanvasContainer from '../containers/RomMapCanvasContainer'; | ||
import RomMapCaption from './RomMapCaption'; | ||
|
||
const RomMap = ({ rom, res, resourceList }) => { | ||
return ( | ||
<div className="space-y-4 md:flex md:gap-5 md:space-y-0 xl:gap-6"> | ||
<RomMapCanvasContainer | ||
rom={rom} | ||
res={res} | ||
resourceList={resourceList} | ||
/> | ||
<RomMapCaption resourceList={resourceList} /> | ||
</div> | ||
); | ||
}; | ||
|
||
export default RomMap; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { getResourceColour } from '../lib/resourceUtils'; | ||
import ColourSwatch from './ColourSwatch'; | ||
|
||
const RomMapCaption = ({ resourceList }) => { | ||
return ( | ||
<dl className="md:max-w-auto flex w-[512] max-w-full flex-wrap justify-between gap-4 text-xs md:w-auto md:flex-col md:text-sm"> | ||
{resourceList.map((label, i) => ( | ||
<div | ||
key={i} | ||
className="flex-no-wrap flex gap-x-2"> | ||
<dt className="h-6"> | ||
<ColourSwatch colour={getResourceColour(i, resourceList.length)} /> | ||
</dt> | ||
<dd className="first-letter:capitalize">{label}</dd> | ||
</div> | ||
))} | ||
</dl> | ||
); | ||
}; | ||
|
||
export default RomMapCaption; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { formatBytes, formatPercentage } from '../lib/utils'; | ||
|
||
const RomMapTable = ({ resourceSizes }) => { | ||
return ( | ||
<section> | ||
<table className="w-[512] max-w-full table-auto text-xs md:text-sm"> | ||
<thead> | ||
<tr className="bg-slate-500 text-white"> | ||
<th className="px-4 py-1 text-left font-normal">Resource</th> | ||
<th className="px-4 py-1 text-right font-normal">Size</th> | ||
<th className="px-4 py-1 text-right font-normal">%</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{resourceSizes.map(({ label, size, percentage }) => ( | ||
<tr | ||
key={label} | ||
className="even:bg-slate-100"> | ||
<td className="px-4 first-letter:capitalize">{label}</td> | ||
<td className="whitespace-nowrap px-4 py-1 text-right"> | ||
<span className="font-monocode">{formatBytes(size)}</span> | ||
</td> | ||
<td className="whitespace-nowrap px-4 py-1 text-right"> | ||
<span className="font-monocode"> | ||
{formatPercentage(percentage)} | ||
</span> | ||
</td> | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> | ||
</section> | ||
); | ||
}; | ||
|
||
export default RomMapTable; |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { useRef, useState, useEffect } from 'react'; | ||
import clsx from 'clsx'; | ||
import { getResourceColour } from '../lib/resourceUtils'; | ||
|
||
const WIDTH = 512; | ||
const HEIGHT = 512; | ||
|
||
const RomMapCanvasContainer = ({ rom, res, resourceList }) => { | ||
const canvasRef = useRef(null); | ||
const [isComputing, setIsComputing] = useState(true); | ||
|
||
useEffect(() => { | ||
setIsComputing(true); | ||
const canvas = canvasRef.current; | ||
const ctx = canvas.getContext('2d'); | ||
|
||
setTimeout(() => { | ||
draw(ctx, rom, res, resourceList); | ||
setIsComputing(false); | ||
}); | ||
}, [rom, res, resourceList]); | ||
|
||
return ( | ||
<canvas | ||
ref={canvasRef} | ||
width={WIDTH} | ||
height={HEIGHT} | ||
className={clsx( | ||
'aspect-[4/3] w-full rounded border border-slate-700', | ||
isComputing ? 'opacity-0' : 'opacity-100 transition-opacity', | ||
)} | ||
style={{ maxWidth: WIDTH }} | ||
/> | ||
); | ||
}; | ||
|
||
const draw = (ctx, rom, res, resourceList) => { | ||
// Background colour (for code). | ||
const codeColour = resourceList.indexOf('other'); | ||
ctx.fillStyle = getResourceColour(codeColour, resourceList.length); | ||
ctx.fillRect(0, 0, WIDTH, HEIGHT); | ||
|
||
// Mark padding (4 or more consecutive bytes of 0xff). | ||
const view = new DataView(rom); | ||
const paddingColour = resourceList.indexOf('padding'); | ||
ctx.fillStyle = getResourceColour(paddingColour, resourceList.length); | ||
for (let j = 0; j < rom.byteLength; j += 4) { | ||
if ( | ||
view.getUint8(j) === 0xff && | ||
view.getUint8(j + 1) === 0xff && | ||
view.getUint8(j + 2) === 0xff && | ||
view.getUint8(j + 3) === 0xff | ||
) { | ||
ctx.fillRect(j % WIDTH, Math.floor(j / WIDTH), 1, 1); | ||
ctx.fillRect((j + 1) % WIDTH, Math.floor((j + 1) / WIDTH), 1, 1); | ||
ctx.fillRect((j + 2) % WIDTH, Math.floor((j + 2) / WIDTH), 1, 1); | ||
ctx.fillRect((j + 3) % WIDTH, Math.floor((j + 3) / WIDTH), 1, 1); | ||
} | ||
} | ||
|
||
resourceList | ||
// Remove pseudo resources like 'other' or 'padding'. | ||
.filter((resourceLabel) => res[resourceLabel]) | ||
.forEach((resourceLabel, i) => { | ||
const resource = res[resourceLabel]; | ||
|
||
ctx.fillStyle = getResourceColour(i, resourceList.length); | ||
for (let i = 0; i < resource.length; i++) { | ||
const [offset, length] = resource[i]; | ||
for (let j = offset; j < offset + length; j++) { | ||
ctx.fillRect(j % WIDTH, Math.floor(j / WIDTH), 1, 1); | ||
} | ||
} | ||
}); | ||
}; | ||
|
||
export default RomMapCanvasContainer; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,85 @@ | ||
import Main from '../components/Main'; | ||
import MainHeader from '../components/MainHeader'; | ||
import RomMap from '../components/RomMap'; | ||
import RomMapTable from '../components/RomMapTable'; | ||
import { getResourceList } from '../lib/resourceUtils'; | ||
|
||
const RESOURCES_NUMBER_IN_GRAPH = 8; | ||
|
||
const RomMapContainer = ({ rom, res }) => { | ||
const resourceSizes = computeResourceSizes(rom, res); | ||
const resourceList = resourceSizes | ||
.slice(0, RESOURCES_NUMBER_IN_GRAPH) | ||
.map(({ label }) => label); | ||
|
||
const RomMapContainer = ({ rom, res, rooms }) => { | ||
return ( | ||
<Main> | ||
<MainHeader title="ROM Map" /> | ||
<RomMap | ||
rom={rom} | ||
res={res} | ||
resourceList={resourceList} | ||
/> | ||
<RomMapTable resourceSizes={resourceSizes} /> | ||
</Main> | ||
); | ||
}; | ||
|
||
const computeResourceSizes = (rom, res) => { | ||
const resourceList = getResourceList(); | ||
const paddingSize = getPaddingSize(rom); | ||
const romSize = rom.byteLength; | ||
const entries = []; | ||
let nonResourceSize = romSize; | ||
|
||
resourceList.forEach((label) => { | ||
const resource = res[label]; | ||
if (!resource) { | ||
return; | ||
} | ||
|
||
const size = resource.reduce((a, b) => a + b[1], 0); | ||
const percentage = size / romSize; | ||
entries.push({ | ||
label, | ||
size, | ||
percentage, | ||
}); | ||
nonResourceSize -= size; | ||
}); | ||
|
||
entries.push({ | ||
label: 'padding', | ||
size: paddingSize, | ||
percentage: paddingSize / romSize, | ||
}); | ||
|
||
entries.push({ | ||
label: 'other', | ||
size: nonResourceSize - paddingSize, | ||
percentage: (nonResourceSize - paddingSize) / romSize, | ||
}); | ||
|
||
entries.sort((a, b) => b.size - a.size); | ||
|
||
return entries; | ||
}; | ||
|
||
// @todo Exclude the resources here. | ||
const getPaddingSize = (rom) => { | ||
const view = new DataView(rom); | ||
let paddingSize = 0; | ||
for (let j = 0; j < rom.byteLength; j += 4) { | ||
if ( | ||
view.getUint8(j) === 0xff && | ||
view.getUint8(j + 1) === 0xff && | ||
view.getUint8(j + 2) === 0xff && | ||
view.getUint8(j + 3) === 0xff | ||
) { | ||
paddingSize += 4; | ||
} | ||
} | ||
return paddingSize; | ||
}; | ||
|
||
export default RomMapContainer; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import resources from './resources'; | ||
|
||
// Returns an array of strings containing resource labels. | ||
const getResourceList = () => | ||
Object.keys(resources[0]).filter((label) => | ||
Array.isArray(resources[0][label]), | ||
); | ||
|
||
// Returns a colour string to be displayed in a range. | ||
const getResourceColour = (step = 0, steps = 0) => | ||
`oklch(${60 + (step % 2 ? 0 : 30)}% ${ | ||
0.15 + (step % 2 ? 0.05 : 0) | ||
} ${(360 / steps) * step})`; | ||
|
||
export { getResourceList, getResourceColour }; |