Skip to content

Commit

Permalink
Memory page (#97)
Browse files Browse the repository at this point in the history
* feat: add memory preview component and hooks

Add a new component called MemoryPreview that displays memory pages and ranges. Also, include the necessary hooks for managing memory. This commit introduces changes to the following files:
- src/components/MemoryPreview/index.tsx
- src/hooks/useMemory.ts
- src/hooks/usePvmWorker.ts
- src/packages/web-worker/worker.ts

* Add memory

* add example

* radio

* text

* fix init page map

* Add store

* use store

* use store

* new pvm

* use builder to initialize pvm memory

* Add page mock

* wip

* Add ranges

* bump pvm and simplify memory initialization

* WIP

* Add page integration

* remove mock

* fix empty page

---------

Co-authored-by: Mateusz Sikora <ms1qaz@gmail.com>
  • Loading branch information
krystian50 and mateuszsikora authored Oct 3, 2024
1 parent 914788b commit ae0a5cb
Show file tree
Hide file tree
Showing 12 changed files with 661 additions and 164 deletions.
12 changes: 5 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@tanstack/react-table": "^8.19.3",
"@typeberry/pvm": "0.0.1-802cddc",
"@typeberry/pvm": "0.0.1-92a540f",
"@typeberry/spectool-wasm": "^0.11.0",
"class-variance-authority": "^0.7.0",
"classnames": "^2.5.1",
Expand Down
131 changes: 77 additions & 54 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "./App.css";
import { Button } from "@/components/ui/button";
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import { Instructions } from "./components/Instructions";
import { Registers } from "./components/Registers";
import { CurrentInstruction, ExpectedState, InitialState, Status } from "./types/pvm";
Expand All @@ -17,13 +17,14 @@ import { Label } from "@/components/ui/label.tsx";
import { InstructionMode } from "@/components/Instructions/types.ts";
import { PvmSelect } from "@/components/PvmSelect";
import { NumeralSystemSwitch } from "@/components/NumeralSystemSwitch";
import { worker } from "./packages/web-worker";

import { Commands, PvmTypes, TargerOnMessageParams } from "./packages/web-worker/worker";
import { Commands, PvmTypes } from "./packages/web-worker/worker";
import { InitialLoadProgramCTA } from "@/components/InitialLoadProgramCTA";
import { MobileRegisters } from "./components/MobileRegisters";
import { MobileKnowledgeBase } from "./components/KnowledgeBase/Mobile";
import { virtualTrapInstruction } from "./utils/virtualTrapInstruction";
import { Store, StoreProvider } from "./AppProviders";
import { useMemoryFeature } from "./components/MemoryPreview/hooks/memoryFeature";
import { Assembly } from "./components/ProgramUpload/Assembly";

function App() {
Expand All @@ -44,12 +45,13 @@ function App() {
const [currentState, setCurrentState] = useState<ExpectedState>(initialState as ExpectedState);
const [previousState, setPreviousState] = useState<ExpectedState>(initialState as ExpectedState);
const [breakpointAddresses, setBreakpointAddresses] = useState<(number | undefined)[]>([]);
const [isRunMode, setIsRunMode] = useState(false);

const [isDebugFinished, setIsDebugFinished] = useState(false);
const [pvmInitialized, setPvmInitialized] = useState(false);

const mobileView = useRef<HTMLDivElement | null>(null);
const { worker, memory } = useContext(Store);
const { actions: memoryActions } = useMemoryFeature();

const setCurrentInstruction = useCallback((ins: CurrentInstruction | null) => {
if (ins === null) {
Expand All @@ -60,54 +62,71 @@ function App() {
setClickedInstruction(null);
}, []);

useEffect(() => {
worker.postMessage({ command: "load", payload: { type: "built-in" } });
}, []);
console.log("pvmInitialized", pvmInitialized);

useEffect(() => {
if (!worker) {
if (!worker.lastEvent) {
return;
}

worker.onmessage = (e: MessageEvent<TargerOnMessageParams>) => {
if (e.data.command === Commands.STEP) {
const { state, isFinished, isRunMode } = e.data.payload;
setCurrentState((prevState) => {
setPreviousState(prevState);
return state;
});
if (worker.lastEvent.command === Commands.STEP) {
const { state, isFinished, isRunMode } = worker.lastEvent.payload;
setCurrentState((prevState) => {
setPreviousState(prevState);
return state;
});
setCurrentInstruction(worker.lastEvent.payload.result);

if (e.data.command === Commands.STEP) {
setCurrentInstruction(e.data.payload.result);
}
if (isRunMode && !isFinished && !breakpointAddresses.includes(state.pc)) {
worker.worker.postMessage({ command: "step", payload: { program } });
}

if (isRunMode && !isFinished && !breakpointAddresses.includes(state.pc)) {
worker.postMessage({ command: "step", payload: { program } });
}
if (isRunMode && breakpointAddresses.includes(state.pc)) {
worker.worker.postMessage({ command: "stop", payload: { program } });
}

if (isRunMode && breakpointAddresses.includes(state.pc)) {
worker.postMessage({ command: "stop", payload: { program } });
setIsRunMode(false);
}
if (isFinished) {
setIsDebugFinished(true);
}

if (isFinished) {
setIsDebugFinished(true);
}
// Refresh page on each step
if (memory.page.state.pageNumber !== undefined) {
memoryActions.changePage(memory.page.state.pageNumber);
}
if (e.data.command === Commands.LOAD) {
restartProgram(initialState);
}
if (worker.lastEvent.command === Commands.LOAD) {
restartProgram(initialState);
}
if (worker.lastEvent.command === Commands.INIT) {
memoryActions.initSetPageSize();
}

if (worker.lastEvent.command === Commands.MEMORY_SIZE) {
memoryActions.setPageSize(worker.lastEvent.payload.memorySize);
}
if (worker.lastEvent.command === Commands.MEMORY_PAGE) {
if (memory.page.state.isLoading) {
memory.page.setState({
...memory.page.state,
data: worker.lastEvent.payload.memoryPage,
pageNumber: worker.lastEvent.payload.pageNumber,
isLoading: false,
});
}
};
} else if (worker.lastEvent.command === Commands.MEMORY_RANGE) {
const { start, end, memoryRange } = worker.lastEvent.payload;
memory.range.setState({
...memory.range.state,
data: [
...memory.range.state.data.map((el) =>
el.start === start && el.end === end ? { start, end, data: memoryRange } : el,
),
],
isLoading: false,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isRunMode,
breakpointAddresses,
currentState,
program,
setCurrentInstruction,
programPreviewResult,
initialState,
]);
}, [worker.lastEvent]);

const startProgram = useCallback(
(initialState: ExpectedState, newProgram: number[]) => {
Expand All @@ -117,14 +136,15 @@ function App() {
pc: 0,
regs: initialState.regs,
gas: initialState.gas,
pageMap: initialState.pageMap,
status: Status.OK,
};
setCurrentState(currentState);
setPreviousState(currentState);

setIsDebugFinished(false);

worker.postMessage({ command: "init", payload: { program: newProgram, initialState } });
worker.worker.postMessage({ command: "init", payload: { program: newProgram, initialState } });

try {
const result = disassemblify(new Uint8Array(newProgram));
Expand All @@ -136,7 +156,7 @@ function App() {
console.log("Error disassembling program", e);
}
},
[setCurrentInstruction],
[setCurrentInstruction, worker.worker],
);

const handleFileUpload = useCallback(
Expand All @@ -152,16 +172,14 @@ function App() {
);

const onNext = () => {
setIsRunMode(false);

if (!pvmInitialized) {
startProgram(initialState, program);
}

if (!currentInstruction) {
setCurrentState(initialState);
} else {
worker.postMessage({ command: "step", payload: { program } });
worker.worker.postMessage({ command: "step", payload: { program } });
}

setIsProgramEditMode(false);
Expand All @@ -171,9 +189,9 @@ function App() {
if (!pvmInitialized) {
startProgram(initialState, program);
}
setIsRunMode(true);
worker.postMessage({ command: "run", payload: { program } });
worker.postMessage({ command: "step", payload: { program } });

worker.worker.postMessage({ command: "run", payload: { program } });
worker.worker.postMessage({ command: "step", payload: { program } });
};

const restartProgram = useCallback(
Expand All @@ -182,9 +200,9 @@ function App() {
setCurrentState(state);
setPreviousState(state);
setCurrentInstruction(programPreviewResult?.[0]);
worker.postMessage({ command: "init", payload: { program, initialState: state } });
worker.worker.postMessage({ command: "init", payload: { program, initialState: state } });
},
[setCurrentInstruction, program, programPreviewResult],
[worker, setCurrentInstruction, program, programPreviewResult],
);

const handleBreakpointClick = (address: number) => {
Expand All @@ -206,11 +224,11 @@ function App() {
console.log("Selected PVM type", type, param);

if (type === PvmTypes.WASM_FILE) {
worker.postMessage({ command: "load", payload: { type, params: { file: param } } });
worker.worker.postMessage({ command: "load", payload: { type, params: { file: param } } });
} else if (type === PvmTypes.WASM_URL) {
worker.postMessage({ command: "load", payload: { type, params: { url: param } } });
worker.worker.postMessage({ command: "load", payload: { type, params: { url: param } } });
} else {
worker.postMessage({ command: "load", payload: { type } });
worker.worker.postMessage({ command: "load", payload: { type } });
}
};

Expand Down Expand Up @@ -364,5 +382,10 @@ function App() {
</>
);
}
const WrappedApp = () => (
<StoreProvider>
<App />
</StoreProvider>
);

export default App;
export default WrappedApp;
45 changes: 45 additions & 0 deletions src/AppProviders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { createContext, useEffect, useState } from "react";
import { initialMemoryState, useMemoryFeatureState } from "./components/MemoryPreview/hooks/memoryFeature";
import { worker } from "./packages/web-worker";
import { TargetOnMessageParams } from "./packages/web-worker/worker";

export const Store = createContext({
memory: initialMemoryState,
worker: {
worker,
lastEvent: null as MessageEvent<TargetOnMessageParams>["data"] | null,
},
});

const useWorkerState = () => {
const [lastEvent, setLastEvent] = useState<MessageEvent<TargetOnMessageParams>["data"] | null>(null);

useEffect(() => {
worker.onmessage = (e: MessageEvent<TargetOnMessageParams>) => {
console.log("Main thread received message:", e.data);
setLastEvent(e.data);
};
worker.postMessage({ command: "load", payload: { type: "built-in" } });
}, []);

return {
worker,
lastEvent,
};
};

export const StoreProvider = ({ children }: { children: React.ReactNode }) => {
const worker = useWorkerState();
const memory = useMemoryFeatureState();

return (
<Store.Provider
value={{
memory,
worker,
}}
>
{children}
</Store.Provider>
);
};
53 changes: 53 additions & 0 deletions src/components/MemoryPreview/PageMemory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Store } from "@/AppProviders";
import { Input } from "@/components/ui/input";
import { chunk } from "lodash";
import { useContext } from "react";

const SPLIT_STEP = 8 as const;
const toMemoryPageTabData = (memoryPage: Uint8Array | undefined, addressStart: number) => {
return chunk(memoryPage || [], SPLIT_STEP).map((chunk, index) => {
return {
address: (index * SPLIT_STEP + addressStart).toString().padStart(6, "0"),
bits: chunk.map((byte) => byte.toString().padStart(3, "0")),
};
});
};

export const MemoryTable = ({ data, addressStart }: { addressStart: number; data: Uint8Array | undefined }) => {
const tableData = toMemoryPageTabData(data, addressStart);

return (
<div className="mt-5 max-h-[calc(70vh-150px)] overflow-y-auto">
{tableData.map(({ address, bits }) => (
<div className="grid grid-cols-3" key={address}>
<div className="opacity-40">{address}</div>
<div className="col-span-2 font-semibold">{bits.join(" ")}</div>
</div>
))}
</div>
);
};

export const PageMemory = ({ onPageChange }: { onPageChange: (page: number) => void }) => {
const memory = useContext(Store).memory;

return (
<div>
<div className="grid grid-cols-3">
<div className="font-semibold flex items-center">Page</div>
<div className="col-span-2">
<Input
type="number"
onChange={(ev) => {
onPageChange(parseInt(ev.target.value || "-1"));
}}
/>
</div>
</div>
<MemoryTable
data={memory.page.state.data}
addressStart={(memory.page.state.pageNumber || 0) * (memory.meta.state.pageSize || 0)}
/>
</div>
);
};
Loading

0 comments on commit ae0a5cb

Please sign in to comment.