Skip to content

Commit

Permalink
Add a graph for the content of the ROM 📉.
Browse files Browse the repository at this point in the history
  • Loading branch information
gmarty committed Apr 23, 2024
1 parent 069d756 commit 5303c0e
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/components/ColourSwatch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const ColourSwatch = ({ colour }) => (
<span
className="inline-block h-6 w-8 rounded border border-gray-700"
className="inline-block h-6 w-8 rounded border border-slate-700"
style={{ backgroundColor: colour }}
/>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const navigation = [
{ name: 'Rooms', href: '/rooms/1' },
{ name: 'Room Gfx', href: '/roomgfx/0' },
{ name: 'Prepositions', href: '/preps' },
{ name: 'ROM map', href: '/rom' },
{ name: 'ROM map', href: '/rom-map' },
];

export default function Header() {
Expand Down
17 changes: 17 additions & 0 deletions src/components/RomMap.js
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;
21 changes: 21 additions & 0 deletions src/components/RomMapCaption.js
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;
36 changes: 36 additions & 0 deletions src/components/RomMapTable.js
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;
25 changes: 3 additions & 22 deletions src/containers/ResourceExplorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,14 @@ const ResourceExplorer = ({ rom, res, resources }) => {
}
/>
<Route
path="/rom"
path="/rom-map"
element={
<RomMapContainer
rom={rom}
res={res}
rooms={resources.rooms}
/>
}>
<Route
path="rooms"
element={
<RomMapContainer
rom={rom}
rooms={resources.rooms}
/>
}>
<Route
path=":roomId"
element={
<RomMapContainer
rom={rom}
rooms={resources.rooms}
/>
}
/>
</Route>
</Route>
}
/>
</Routes>
);
};
Expand Down
77 changes: 77 additions & 0 deletions src/containers/RomMapCanvasContainer.js
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;
75 changes: 74 additions & 1 deletion src/containers/RomMapContainer.js
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;
15 changes: 15 additions & 0 deletions src/lib/resourceUtils.js
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 };

0 comments on commit 5303c0e

Please sign in to comment.