Skip to content

Commit fd36555

Browse files
authored
Add doom example. (#441)
1 parent e32b101 commit fd36555

4 files changed

Lines changed: 175 additions & 149 deletions

File tree

src/components/ProgramLoader/Examples.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ import { RegistersArray } from "@/types/pvm";
22
import { ProgramUploadFileOutput } from "./types";
33
import { Badge } from "@/components/ui/badge.tsx";
44
import { programs } from "./examplePrograms";
5+
import { useState } from "react";
6+
import doomUrl from "./doom.bin?url";
7+
import { loadFileFromUint8Array } from "./loadingUtils";
58

69
export const Examples = ({ onProgramLoad }: { onProgramLoad: (val: ProgramUploadFileOutput) => void }) => {
10+
const [isDoomLoading, setIsDoomLoading] = useState(false);
11+
712
return (
813
<div>
914
{Object.keys(programs).map((key) => {
@@ -35,6 +40,23 @@ export const Examples = ({ onProgramLoad }: { onProgramLoad: (val: ProgramUpload
3540
</Badge>
3641
);
3742
})}
43+
<Badge
44+
id="doom"
45+
variant={isDoomLoading ? "outline" : "brand"}
46+
className={`mb-2 mr-2 text-xs sm:text-md ${isDoomLoading ? "cursor-wait" : "cursor-pointer"}`}
47+
onClick={async () => {
48+
if (isDoomLoading) {
49+
return;
50+
}
51+
setIsDoomLoading(true);
52+
const data = await fetch(doomUrl);
53+
const blob = await data.bytes();
54+
setIsDoomLoading(false);
55+
loadFileFromUint8Array("doom.bin", blob, new Uint8Array(), () => {}, onProgramLoad, {});
56+
}}
57+
>
58+
{isDoomLoading ? "..." : "Doom"}
59+
</Badge>
3860
</div>
3961
);
4062
};

src/components/ProgramLoader/ProgramFileUpload.tsx

Lines changed: 4 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import { useDropzone } from "react-dropzone";
2-
import { ProgramUploadFileInput, ProgramUploadFileOutput } from "./types";
3-
import { mapUploadFileInputToOutput } from "./utils";
4-
import { bytes, pvm } from "@typeberry/lib";
5-
import { ExpectedState, MemoryChunkItem, PageMapItem, RegistersArray } from "@/types/pvm.ts";
6-
import { SafeParseReturnType, z } from "zod";
2+
import { ProgramUploadFileOutput } from "./types";
3+
import { bytes } from "@typeberry/lib";
74
import { useAppSelector } from "@/store/hooks";
85
import { selectInitialState } from "@/store/debugger/debuggerSlice";
9-
import { cn, getAsChunks, getAsPageMap } from "@/lib/utils.ts";
6+
import { cn } from "@/lib/utils.ts";
107
import { UploadCloud } from "lucide-react";
118
import { Button } from "../ui/button";
129
import { useCallback, useEffect, useMemo, useState } from "react";
1310
import { Input } from "../ui/input";
14-
import { decodeSpiWithMetadata } from "@/utils/spi";
11+
import { loadFileFromUint8Array } from "./loadingUtils";
1512

1613
type ProgramFileUploadProps = {
1714
onFileUpload: (val: ProgramUploadFileOutput) => void;
@@ -20,148 +17,6 @@ type ProgramFileUploadProps = {
2017
close?: () => void;
2118
};
2219

23-
type RawProgramUploadFileInput = unknown;
24-
type ValidationResult = SafeParseReturnType<RawProgramUploadFileInput, ProgramUploadFileInput>;
25-
26-
const validateJsonTestCaseSchema = (json: RawProgramUploadFileInput): ValidationResult => {
27-
const pageMapSchema = z.object({
28-
address: z.number(),
29-
length: z.number(),
30-
"is-writable": z.boolean(),
31-
});
32-
33-
const memorySchema = z.object({
34-
address: z.number(),
35-
contents: z.array(z.number()),
36-
});
37-
38-
const schema = z.object({
39-
name: z.string(),
40-
"initial-regs": z.array(z.number()).length(13),
41-
"initial-pc": z.number(),
42-
"initial-page-map": z.array(pageMapSchema),
43-
"initial-memory": z.array(memorySchema),
44-
"initial-gas": z.number(),
45-
program: z.array(z.number()),
46-
"expected-status": z.string(),
47-
"expected-regs": z.array(z.number()),
48-
"expected-pc": z.number(),
49-
"expected-memory": z.array(memorySchema),
50-
"expected-gas": z.number(),
51-
});
52-
53-
return schema.safeParse(json);
54-
};
55-
56-
const generateErrorMessageFromZodValidation = (result: ValidationResult): string => {
57-
if (!result.error) return "Unknown error occurred";
58-
59-
const formattedErrors = result.error.errors.map((err) => {
60-
const path = err.path.join(" > ") || "root";
61-
return `Field: "${path}" - ${err.message}`;
62-
});
63-
64-
return `File validation failed with the following errors:\n\n${formattedErrors.join("\n")}`;
65-
};
66-
67-
function loadFileFromUint8Array(
68-
loadedFileName: string,
69-
uint8Array: Uint8Array,
70-
spiArgsAsBytes: Uint8Array,
71-
setError: (e?: string) => void,
72-
onFileUpload: (d: ProgramUploadFileOutput) => void,
73-
initialState: ExpectedState,
74-
) {
75-
// reset error state
76-
setError(undefined);
77-
78-
// Try to parse file as a JSON first
79-
let jsonFile = null;
80-
let stringContent = "";
81-
let rawBytes = uint8Array;
82-
83-
try {
84-
stringContent = new TextDecoder("utf-8").decode(uint8Array);
85-
} catch (e) {
86-
console.warn("not a string", e);
87-
}
88-
89-
if (stringContent.startsWith("0x")) {
90-
try {
91-
rawBytes = bytes.BytesBlob.parseBlob(stringContent).raw;
92-
} catch (e) {
93-
console.warn("not a bytes blob", e);
94-
}
95-
} else {
96-
try {
97-
jsonFile = JSON.parse(stringContent);
98-
} catch (e) {
99-
console.warn("not a JSON", e);
100-
}
101-
}
102-
103-
if (jsonFile !== null) {
104-
const result = validateJsonTestCaseSchema(jsonFile);
105-
if (!result.success) {
106-
console.warn("not a valid JSON", result.error);
107-
const errorMessage = generateErrorMessageFromZodValidation(result);
108-
setError(errorMessage || "");
109-
return;
110-
}
111-
112-
onFileUpload(mapUploadFileInputToOutput(jsonFile, "JSON test"));
113-
return;
114-
}
115-
116-
// Try to decode the program as an SPI
117-
let spi = null;
118-
try {
119-
spi = decodeSpiWithMetadata(rawBytes, spiArgsAsBytes);
120-
} catch (e) {
121-
console.warn("not an SPI blob", e);
122-
}
123-
if (spi !== null) {
124-
const { code, memory, registers } = spi;
125-
const pageMap: PageMapItem[] = getAsPageMap(memory);
126-
const chunks: MemoryChunkItem[] = getAsChunks(memory);
127-
128-
onFileUpload({
129-
program: Array.from(code),
130-
name: `${loadedFileName} [spi]`,
131-
isSpi: true,
132-
kind: "JAM SPI",
133-
initial: {
134-
regs: Array.from(registers).map((x) => BigInt(x as number | bigint)) as RegistersArray,
135-
pc: 0,
136-
pageMap,
137-
memory: chunks,
138-
gas: 10000n,
139-
},
140-
});
141-
return;
142-
}
143-
144-
// Finally try to load program as a Generic
145-
let program = null;
146-
try {
147-
program = new pvm.ProgramDecoder(rawBytes);
148-
} catch (e) {
149-
console.warn("not a generic PVM", e);
150-
}
151-
152-
if (program !== null) {
153-
onFileUpload({
154-
program: Array.from(rawBytes),
155-
name: `${loadedFileName} [generic]`,
156-
isSpi: false,
157-
kind: "Generic PVM",
158-
initial: initialState,
159-
});
160-
} else {
161-
setError("Unrecognized program format (see console).");
162-
}
163-
}
164-
16520
export const ProgramFileUpload: React.FC<ProgramFileUploadProps> = ({ isError, onFileUpload, close, setError }) => {
16621
const initialState = useAppSelector(selectInitialState);
16722
const spiArgs = useAppSelector((state) => state.debugger.spiArgs);
615 KB
Binary file not shown.
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { mapUploadFileInputToOutput } from "./utils";
2+
import { bytes, pvm } from "@typeberry/lib";
3+
import { ExpectedState, MemoryChunkItem, PageMapItem, RegistersArray } from "@/types/pvm.ts";
4+
import { SafeParseReturnType, z } from "zod";
5+
import { getAsChunks, getAsPageMap } from "@/lib/utils.ts";
6+
import { decodeSpiWithMetadata } from "@/utils/spi";
7+
import { ProgramUploadFileInput, ProgramUploadFileOutput } from "./types";
8+
9+
type RawProgramUploadFileInput = unknown;
10+
type ValidationResult = SafeParseReturnType<RawProgramUploadFileInput, ProgramUploadFileInput>;
11+
12+
const validateJsonTestCaseSchema = (json: RawProgramUploadFileInput): ValidationResult => {
13+
const pageMapSchema = z.object({
14+
address: z.number(),
15+
length: z.number(),
16+
"is-writable": z.boolean(),
17+
});
18+
19+
const memorySchema = z.object({
20+
address: z.number(),
21+
contents: z.array(z.number()),
22+
});
23+
24+
const schema = z.object({
25+
name: z.string(),
26+
"initial-regs": z.array(z.number()).length(13),
27+
"initial-pc": z.number(),
28+
"initial-page-map": z.array(pageMapSchema),
29+
"initial-memory": z.array(memorySchema),
30+
"initial-gas": z.number(),
31+
program: z.array(z.number()),
32+
"expected-status": z.string(),
33+
"expected-regs": z.array(z.number()),
34+
"expected-pc": z.number(),
35+
"expected-memory": z.array(memorySchema),
36+
"expected-gas": z.number(),
37+
});
38+
39+
return schema.safeParse(json);
40+
};
41+
42+
export function loadFileFromUint8Array(
43+
loadedFileName: string,
44+
uint8Array: Uint8Array,
45+
spiArgsAsBytes: Uint8Array,
46+
setError: (e?: string) => void,
47+
onFileUpload: (d: ProgramUploadFileOutput) => void,
48+
initialState: ExpectedState,
49+
) {
50+
// reset error state
51+
setError(undefined);
52+
53+
// Try to parse file as a JSON first
54+
let jsonFile = null;
55+
let stringContent = "";
56+
let rawBytes = uint8Array;
57+
58+
try {
59+
stringContent = new TextDecoder("utf-8").decode(uint8Array);
60+
} catch (e) {
61+
console.warn("not a string", e);
62+
}
63+
64+
if (stringContent.startsWith("0x")) {
65+
try {
66+
rawBytes = bytes.BytesBlob.parseBlob(stringContent).raw;
67+
} catch (e) {
68+
console.warn("not a bytes blob", e);
69+
}
70+
} else {
71+
try {
72+
jsonFile = JSON.parse(stringContent);
73+
} catch (e) {
74+
console.warn("not a JSON", e);
75+
}
76+
}
77+
78+
if (jsonFile !== null) {
79+
const result = validateJsonTestCaseSchema(jsonFile);
80+
if (!result.success) {
81+
console.warn("not a valid JSON", result.error);
82+
const errorMessage = generateErrorMessageFromZodValidation(result);
83+
setError(errorMessage || "");
84+
return;
85+
}
86+
87+
onFileUpload(mapUploadFileInputToOutput(jsonFile, "JSON test"));
88+
return;
89+
}
90+
91+
// Try to decode the program as an SPI
92+
let spi = null;
93+
try {
94+
spi = decodeSpiWithMetadata(rawBytes, spiArgsAsBytes);
95+
} catch (e) {
96+
console.warn("not an SPI blob", e);
97+
}
98+
if (spi !== null) {
99+
const { code, memory, registers } = spi;
100+
const pageMap: PageMapItem[] = getAsPageMap(memory);
101+
const chunks: MemoryChunkItem[] = getAsChunks(memory);
102+
103+
onFileUpload({
104+
program: Array.from(code),
105+
name: `${loadedFileName} [spi]`,
106+
isSpi: true,
107+
kind: "JAM SPI",
108+
initial: {
109+
regs: Array.from(registers).map((x) => BigInt(x as number | bigint)) as RegistersArray,
110+
pc: 0,
111+
pageMap,
112+
memory: chunks,
113+
gas: 10000n,
114+
},
115+
});
116+
return;
117+
}
118+
119+
// Finally try to load program as a Generic
120+
let program = null;
121+
try {
122+
program = new pvm.ProgramDecoder(rawBytes);
123+
} catch (e) {
124+
console.warn("not a generic PVM", e);
125+
}
126+
127+
if (program !== null) {
128+
onFileUpload({
129+
program: Array.from(rawBytes),
130+
name: `${loadedFileName} [generic]`,
131+
isSpi: false,
132+
kind: "Generic PVM",
133+
initial: initialState,
134+
});
135+
} else {
136+
setError("Unrecognized program format (see console).");
137+
}
138+
}
139+
140+
const generateErrorMessageFromZodValidation = (result: ValidationResult): string => {
141+
if (!result.error) return "Unknown error occurred";
142+
143+
const formattedErrors = result.error.errors.map((err) => {
144+
const path = err.path.join(" > ") || "root";
145+
return `Field: "${path}" - ${err.message}`;
146+
});
147+
148+
return `File validation failed with the following errors:\n\n${formattedErrors.join("\n")}`;
149+
};

0 commit comments

Comments
 (0)