diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..b1a2f83 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,7 @@ +export default { + testEnvironment: 'node', + testMatch: ['**/*.test.(ts|tsx)'], + transform: { + '^.+.tsx?$': ['ts-jest', {}], + }, +}; diff --git a/package.json b/package.json index 23faaa4..29f90e7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,10 @@ "prepare": "husky", "commit": "cz", "e2e": "playwright test", - "e2e:ui": "playwright test --ui" + "e2e:ui": "playwright test --ui", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" }, "dependencies": { "@commitlint/cli": "^19.4.0", @@ -25,8 +28,11 @@ "@eslint/js": "^9.9.1", "@mui/icons-material": "^6.0.1", "@mui/material": "^5.16.7", + "@types/eslint-scope": "^3.7.7", "ace-builds": "^1.36.0", "acorn": "^8.12.1", + "acorn-walk": "^8.3.4", + "eslint-scope": "^8.2.0", "globals": "^15.9.0", "react": "^18.3.1", "react-ace": "^12.0.0", @@ -38,6 +44,7 @@ "@playwright/test": "^1.48.2", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", + "@types/jest": "^29.5.14", "@types/node": "^22.9.0", "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", @@ -52,9 +59,11 @@ "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.11", "husky": "^9.1.5", + "jest": "^29.7.0", "lint-staged": "^15.2.9", "prettier": "^3.3.3", "semantic-release": "^24.1.0", + "ts-jest": "^29.2.5", "typescript": "^5.5.4", "vite": "^5.4.2" }, diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index 5252c7e..82d5526 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -4,12 +4,12 @@ import { StyledBox } from './Modal.styled.ts'; function InfoModal({ children, - isOpen, + isOpened, onClose, -}: PropsWithChildren<{ isOpen: boolean; onClose: () => void }>) { +}: PropsWithChildren<{ isOpened: boolean; onClose: () => void }>) { return ( state.status); - - useEffect(() => { - const eventLoopTitleDomNode = document.getElementById(EVENT_LOOP_ID); - if (isMobile() && status === 'running' && eventLoopTitleDomNode) { - eventLoopTitleDomNode.scrollIntoView({ - behavior: 'smooth', - block: 'start', - }); - } - }, [status]); - return ( diff --git a/src/pages/home/sections/Callstack/Callstack.modal.tsx b/src/pages/home/sections/Callstack/Callstack.modal.tsx new file mode 100644 index 0000000..f184ced --- /dev/null +++ b/src/pages/home/sections/Callstack/Callstack.modal.tsx @@ -0,0 +1,44 @@ +import * as Styled from './Callstack.styled.ts'; +import InfoModal from 'components/Modal/Modal.tsx'; + +function CallStackModal({ + isOpened, + toggle, +}: { + isOpened: boolean; + toggle: () => void; +}) { + return ( + +

Call stack

+ +

+ A call stack is a mechanism for an interpreter to keep track of its + place in a script that calls multiple functions — what function is + currently being run and what functions are called from within that + function, etc. +

+
    +
  • + When a script calls a function, the interpreter adds it to the call + stack and then starts carrying out the function. +
  • +
  • + Any functions that are called by that function are added to the call + stack further up, and run where their calls are reached. +
  • +
  • + When the current function is finished, the interpreter takes it off + the stack and resumes execution where it left off in the last code + listing. +
  • +
  • + If the stack takes up more space than it was assigned, a "stack + overflow" error is thrown. +
  • +
+
+ ); +} + +export default CallStackModal; diff --git a/src/pages/home/sections/Callstack/Callstack.styled.ts b/src/pages/home/sections/Callstack/Callstack.styled.ts index 0c71134..b95592e 100644 --- a/src/pages/home/sections/Callstack/Callstack.styled.ts +++ b/src/pages/home/sections/Callstack/Callstack.styled.ts @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; import { css } from '@emotion/react'; -import InfoClosed from '../../../../components/CloseIcon/InfoIcon.tsx'; +import InfoClosed from 'components/CloseIcon/InfoIcon.tsx'; export const Callstack = styled.div` flex: 1; diff --git a/src/pages/home/sections/Callstack/Callstack.tsx b/src/pages/home/sections/Callstack/Callstack.tsx index 847158f..a60ece1 100644 --- a/src/pages/home/sections/Callstack/Callstack.tsx +++ b/src/pages/home/sections/Callstack/Callstack.tsx @@ -1,54 +1,26 @@ -import { useEventLists } from '../../../../store/store.ts'; import * as Styled from './Callstack.styled.ts'; -import InfoIcon from '../../../../components/InfoIcon/InfoIcon.tsx'; -import InfoModal from '../../../../components/Modal/Modal.tsx'; -import useBoolean from '../../../../utils/useBoolean.tsx'; +import InfoIcon from 'components/InfoIcon/InfoIcon.tsx'; +import useBoolean from 'utils/hooks/useBoolean.ts'; import { Zoom } from '@mui/material'; import { List } from '../../Home.styled.ts'; +import CallStackModal from './Callstack.modal.tsx'; +import { useQueueManagerStore } from 'store/store.ts'; function CallStack({ className }: { className?: string }) { - const tasks = useEventLists((state) => state.callstack); - const [open, setOpen, setClose] = useBoolean(false); + const tasks = useQueueManagerStore((state) => state.callstack); + const [isOpened, toggle] = useBoolean(false); return ( CallStack - - {tasks.map(({ display: stack }) => ( - - {stack} + + {tasks.map((task) => ( + + {task} ))} - -

Call stack

- -

- A call stack is a mechanism for an interpreter to keep track of its - place in a script that calls multiple functions — what function is - currently being run and what functions are called from within that - function, etc. -

-
    -
  • - When a script calls a function, the interpreter adds it to the - call stack and then starts carrying out the function. -
  • -
  • - Any functions that are called by that function are added to the - call stack further up, and run where their calls are reached. -
  • -
  • - When the current function is finished, the interpreter takes it - off the stack and resumes execution where it left off in the last - code listing. -
  • -
  • - If the stack takes up more space than it was assigned, a "stack - overflow" error is thrown. -
  • -
-
+
); diff --git a/src/pages/home/sections/Configurator/Configurator.data.tsx b/src/pages/home/sections/Configurator/Configurator.data.tsx index c3428ac..e8a1376 100644 --- a/src/pages/home/sections/Configurator/Configurator.data.tsx +++ b/src/pages/home/sections/Configurator/Configurator.data.tsx @@ -29,21 +29,21 @@ foo1();`, }, { title: 'microtasks', - code: `console.log(1); -setTimeout(() => console.log(2), 0); -queueMicrotask(() => console.log(3)); + code: `console.log(1); +setTimeout(() => console.log(2), 0); +queueMicrotask(() => console.log(3)); Promise.resolve().then(() => { console.log(4); -}); -setTimeout(() => console.log(5), 500); +}); +setTimeout(() => console.log(5), 500); console.log(6);`, }, { title: 'requestAnimationFrame', - code: `console.log(1); -queueMicrotask(() => console.log(2)); -requestAnimationFrame(() => console.log(3)); -requestAnimationFrame(() => console.log(4)); + code: `console.log(1); +queueMicrotask(() => console.log(2)); +requestAnimationFrame(() => console.log(3)); +requestAnimationFrame(() => console.log(4)); console.log(5);`, }, { @@ -60,24 +60,20 @@ function foo3() { setTimeout(() => { foo4(); console.log('foo3:1'); - }, 0); - queueMicrotask(() => console.log('foo3:2')); + }, 0); + queueMicrotask(() => console.log('foo3:2')); Promise.resolve().then(() => { console.log('foo3:3') - }); - setTimeout(() => console.log('foo3:4'), 500); + }); + setTimeout(() => console.log('foo3:4'), 500); console.log('foo3:5'); } function foo4() { console.log('foo4'); } -function foo5(param1) { - console.log(param1); -} console.log('global'); -setTimeout(() => console.log('global:1'), 500); -foo1(); -foo5('foo5:1');`, +setTimeout(() => console.log('global:1'), 500); +foo1();`, }, ]; diff --git a/src/pages/home/sections/Configurator/Configurator.tsx b/src/pages/home/sections/Configurator/Configurator.tsx index ec3533f..f7f51a2 100644 --- a/src/pages/home/sections/Configurator/Configurator.tsx +++ b/src/pages/home/sections/Configurator/Configurator.tsx @@ -1,5 +1,3 @@ -import 'ace-builds/src-noconflict/mode-javascript'; -import 'ace-builds/src-noconflict/theme-solarized_dark'; import * as Styled from './Configurator.styled.ts'; import { BaseLayoutElement } from '../../Home.styled.ts'; import Controls from './Controls/Controls.tsx'; diff --git a/src/pages/home/sections/Configurator/Controls/Controls.tsx b/src/pages/home/sections/Configurator/Controls/Controls.tsx index 07f1fb3..4592db6 100644 --- a/src/pages/home/sections/Configurator/Controls/Controls.tsx +++ b/src/pages/home/sections/Configurator/Controls/Controls.tsx @@ -9,19 +9,17 @@ import { SelectChangeEvent, Slider, } from '@mui/material'; -import 'ace-builds/src-noconflict/mode-javascript'; -import 'ace-builds/src-noconflict/theme-solarized_dark'; import { useState } from 'react'; import { - useEditor, - useEventLists, - useEventLoopAnimation, - useSpeedFactor, -} from '../../../../../store/store.ts'; -import { parse } from '../../../../../utils/parse.ts'; + useQueueManagerStore, + useSimulatorStore, + useWheelStore, + useEditorStore, +} from 'store/store.ts'; import { codeExamples } from '../Configurator.data.tsx'; import * as Styled from './Controls.styled.ts'; import { getCodeExampleByTitle } from './Controls.utils.tsx'; +import { start } from 'utils/start.ts'; export default function Controls({ text, @@ -30,15 +28,16 @@ export default function Controls({ text: string; setText: (key: string) => void; }) { - const eventListsStateSet = useEventLists((state) => state.set); - const eventListsStateClear = useEventLists((state) => state.clear); - const clearAnimationState = useEventLoopAnimation((state) => state.clear); - const setAnimationState = useEventLoopAnimation((state) => state.setState); - const status = useEventLoopAnimation((state) => state.status); + const status = useSimulatorStore((state) => state.status); + const setStatus = useSimulatorStore((state) => state.setStatus); const [exampleTitle, setExampleTitle] = useState(codeExamples[3].title); - const speedFactorState = useSpeedFactor((state) => state); - const setSourceCode = useEditor((state) => state.setSource); - const clearEditor = useEditor((state) => state.clearEditor); + const simulatorStore = useSimulatorStore((state) => state); + const setEditorSource = useEditorStore((state) => state.setSource); + + const clearWheel = useWheelStore((state) => state.clear); + const clearQueueManager = useQueueManagerStore((state) => state.clear); + const clearSimulator = useSimulatorStore((state) => state.clear); + const clearEditor = useEditorStore((state) => state.clearEditor); const onExampleSelect = (e: SelectChangeEvent) => { const example = e.target.value; @@ -47,47 +46,43 @@ export default function Controls({ setExampleTitle(example); }; - const onStop = () => { - clearAnimationState(); - eventListsStateClear(); + const onClear = () => { + clearWheel(); + clearQueueManager(); + clearSimulator(); clearEditor(); }; - const onPause = () => { - setAnimationState('paused', 'status'); + const onStop = () => { + setStatus('idle'); + onClear(); }; - const onResume = () => { - setAnimationState('running', 'status'); - }; + const onPause = () => setStatus('paused'); + + const onResume = () => setStatus('running'); const onRun = () => { - clearAnimationState(); - eventListsStateClear(); - const script = parse(text); - setSourceCode(text); - eventListsStateSet({ - list: 'task_queue', - type: 'push', - value: script, + onClear(); + setEditorSource(text); + setStatus('running'); + start(text, () => setStatus('idle')); + window.scrollTo({ + top: document.body.scrollHeight, + behavior: 'smooth', }); - setAnimationState('running', 'status'); }; const onSpeedChange = (_: Event, value: number | number[]) => { const num = Array.isArray(value) ? value[0] : value; - const res = num >= 0 ? num + 1 : 1 / (1 - num); - speedFactorState.setSpeed(res); + simulatorStore.setSpeed(Math.pow(2, num)); }; - const speed = - speedFactorState.speed >= 1 - ? speedFactorState.speed - 1 - : 1 - 1 / speedFactorState.speed; + const speed = Math.log2(simulatorStore.speed); return ( - {status === 'disabled' && ( + {status === 'idle' && ( <> @@ -124,22 +119,22 @@ export default function Controls({ )} - {status !== 'disabled' && ( + {status !== 'idle' && ( <>
- speed: {Math.round(speedFactorState.speed * 100)}% + speed: x{simulatorStore.speed}
diff --git a/src/pages/home/sections/Configurator/Controls/Controls.utils.tsx b/src/pages/home/sections/Configurator/Controls/Controls.utils.tsx index d581c66..f1996e2 100644 --- a/src/pages/home/sections/Configurator/Controls/Controls.utils.tsx +++ b/src/pages/home/sections/Configurator/Controls/Controls.utils.tsx @@ -1,5 +1,3 @@ -import 'ace-builds/src-noconflict/mode-javascript'; -import 'ace-builds/src-noconflict/theme-solarized_dark'; import { codeExamples } from '../Configurator.data.tsx'; export const getCodeExampleByTitle = codeExamples.reduce( diff --git a/src/pages/home/sections/Configurator/Editor/Editor.styled.ts b/src/pages/home/sections/Configurator/Editor/Editor.styled.ts index f977c63..8463a35 100644 --- a/src/pages/home/sections/Configurator/Editor/Editor.styled.ts +++ b/src/pages/home/sections/Configurator/Editor/Editor.styled.ts @@ -11,7 +11,7 @@ export const EditorWrapper = styled.div( .selected_lines { position: absolute; - background: ${theme.custom.colors.wheel.task.disabled}; + background: ${theme.custom.colors.wheel.macrotask.disabled}; } .ace_gutter-active-line { diff --git a/src/pages/home/sections/Configurator/Editor/Editor.tsx b/src/pages/home/sections/Configurator/Editor/Editor.tsx index e4ab425..a372752 100644 --- a/src/pages/home/sections/Configurator/Editor/Editor.tsx +++ b/src/pages/home/sections/Configurator/Editor/Editor.tsx @@ -1,11 +1,8 @@ +import AceEditor from 'react-ace'; import 'ace-builds/src-noconflict/mode-javascript'; import 'ace-builds/src-noconflict/theme-solarized_dark'; import { useEffect, useRef } from 'react'; -import AceEditor from 'react-ace'; -import { - useEditor, - useEventLoopAnimation, -} from '../../../../../store/store.ts'; +import { useEditorStore, useSimulatorStore } from 'store/store.ts'; import * as Styled from './Editor.styled.ts'; export default function Editor({ @@ -15,8 +12,8 @@ export default function Editor({ text: string; setText: (key: string) => void; }) { - const status = useEventLoopAnimation((state) => state.status); - const setEditorRef = useEditor((state) => state.setRef); + const status = useSimulatorStore((state) => state.status); + const setEditorRef = useEditorStore((state) => state.setRef); const editorRef = useRef(null); useEffect(() => { @@ -36,7 +33,7 @@ export default function Editor({ theme="solarized_dark" setOptions={{ useWorker: false, - readOnly: status !== 'disabled', + readOnly: status !== 'idle', }} showPrintMargin={false} fontSize={14} diff --git a/src/pages/home/sections/Console/Console.tsx b/src/pages/home/sections/Console/Console.tsx index 6d1117a..fd54af8 100644 --- a/src/pages/home/sections/Console/Console.tsx +++ b/src/pages/home/sections/Console/Console.tsx @@ -1,10 +1,10 @@ -import { useEventLists } from '../../../../store/store.ts'; +import { useQueueManagerStore } from 'store/store.ts'; import * as Styled from './Console.styled.ts'; import { Zoom } from '@mui/material'; import { List } from '../../Home.styled.ts'; function Console({ className }: { className?: string }) { - const tasks = useEventLists((state) => state.console); + const tasks = useQueueManagerStore((state) => state.console); return ( diff --git a/src/pages/home/sections/EventLoop/EventLoop.tsx b/src/pages/home/sections/EventLoop/EventLoop.tsx deleted file mode 100644 index 66f49fe..0000000 --- a/src/pages/home/sections/EventLoop/EventLoop.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import Pointer from './Pointer/Pointer.tsx'; -import { useEventLoopAnimation } from '../../../../store/store.ts'; -import CircleLabels from './CircleLabels/CircleLabels.tsx'; -import { events } from './EventLoop.data.ts'; -import * as Styled from './EventLoop.styled.ts'; -import { useTheme } from '@emotion/react'; -import InfoIcon from '../../../../components/InfoIcon/InfoIcon.tsx'; -import InfoModal from '../../../../components/Modal/Modal.tsx'; -import useBoolean from '../../../../utils/useBoolean.tsx'; -import { BaseLayoutElement } from '../../Home.styled.ts'; -import { EVENT_LOOP_ID } from '../../../../utils/constants.ts'; - -function EventLoop({ className }: { className?: string }) { - const animation = useEventLoopAnimation((state) => state); - const theme = useTheme(); - const [open, setOpen, setClose] = useBoolean(false); - - return ( - -

Event Loop

- - - - - {events.map(({ degree, type }) => { - const enabled = animation[type] ? 'enabled' : 'disabled'; - const background = theme.custom.colors.wheel[type][enabled]; - return ( - - ); - })} - - - - - -

Event Loop

- -

- The event loop is a fundamental concept in browser that manages the - execution of code, handling of events, and updating of the user - interface. -

-

- The event loop continuously checks for and processes events and - queued tasks in a specific order: -

-
    -
  1. Execute all synchronous code in the call stack
  2. -
  3. - Check the microtask queue (e.g., Promise callbacks) and execute - all tasks. -
  4. -
  5. - Check the macrotask queue (e.g., setTimeout, DOM events) and - execute one task. -
  6. -
  7. Update the rendering if necessary.
  8. -
  9. Repeat the process.
  10. -
-
-
-
- ); -} - -export default EventLoop; diff --git a/src/pages/home/sections/EventLoop/Pointer/Pointer.tsx b/src/pages/home/sections/EventLoop/Pointer/Pointer.tsx deleted file mode 100644 index b4dc489..0000000 --- a/src/pages/home/sections/EventLoop/Pointer/Pointer.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { useEffect, useRef } from 'react'; -import { - useEventLoopAnimation, - useEventLoopTime, -} from '../../../../../store/store.ts'; -import { events } from '../EventLoop.data.ts'; -import { EventInterface } from '../EventLoop.types.ts'; -import { useProcessEvent } from '../useProcessEvent.ts'; -import useRefState from '../../../../../utils/useRefState.tsx'; -import * as Styled from './Pointer.styled.ts'; - -const RENDER_DELAY_MS = 720; -let timeFromLastRender = 0; - -const stops = new Set(events.map((event) => event.degree)); -const typeByStop = events.reduce( - (acc, event) => { - acc[event.degree] = event.type; - return acc; - }, - {} as Record -); - -function Pointer() { - const setState = useEventLoopAnimation((state) => state.setState); - const animationStatus = useEventLoopAnimation((state) => state.status); - const animationRef = useRefState(useEventLoopAnimation, (state) => state); - const incrementTime = useEventLoopTime((state) => state.increment); - const processEvent = useProcessEvent(); - const angleRef = useRef(100 - 10); - - const sectorInnerRef = useRef(null); - const sectorOuterRef = useRef(null); - - const animationNotIdle = animationStatus !== 'disabled'; - - useEffect(() => { - const animate = async () => { - if (animationRef.current.status === 'disabled') { - angleRef.current = 100 - 10; - timeFromLastRender = 0; - if (sectorInnerRef.current && sectorOuterRef.current) { - sectorInnerRef.current.style.transform = `rotate(${360 - angleRef.current + 10}deg)`; - sectorOuterRef.current.style.transform = `rotate(${360 - angleRef.current + 10}deg)`; - } - return; - } - while (animationRef.current.status === 'paused') { - await new Promise((resolve) => setTimeout(resolve, 200)); - } - if (sectorInnerRef.current && sectorOuterRef.current) { - const angleWithOffset = angleRef.current + 1; - if (stops.has(angleWithOffset)) { - const type = typeByStop[angleWithOffset]; - if (animationRef.current[type]) { - await processEvent(type); - } - } - sectorInnerRef.current.style.transform = `rotate(${360 - angleRef.current + 10}deg)`; - sectorOuterRef.current.style.transform = `rotate(${360 - angleRef.current + 10}deg)`; - angleRef.current = angleRef.current - 1; - timeFromLastRender += 1; - incrementTime(); - if (angleWithOffset < 0) { - angleRef.current = angleRef.current + 360; - } - if (timeFromLastRender >= RENDER_DELAY_MS) { - setState(true, 'render'); - timeFromLastRender = 0; - } - } - requestAnimationFrame(animate); - }; - - animate(); - }, [animationNotIdle]); - - return ( - <> - - - - ); -} - -export default Pointer; diff --git a/src/pages/home/sections/EventLoop/useProcessEvent.ts b/src/pages/home/sections/EventLoop/useProcessEvent.ts deleted file mode 100644 index edf1555..0000000 --- a/src/pages/home/sections/EventLoop/useProcessEvent.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { EventInterface } from './EventLoop.types.ts'; -import { - useEditor, - useEventLists, - useEventLoopAnimation, - useSpeedFactor, -} from '../../../../store/store.ts'; -import { MutableRefObject, useCallback } from 'react'; -import { nodeFactory } from '../../../../utils/nodes/factory.ts'; -import { ArrowFunctionExpression } from 'acorn'; -import { - ActionInterface, - CallStackValue, - EventListsInterface, - EventLoopAnimationInterface, - SpeedFactorInterface, -} from '../../../../store/store.types.ts'; -import useRefState from '../../../../utils/useRefState.tsx'; - -const DELAY_BETWEEN_ACTIONS_MS = 1000; - -interface ProcessProps { - eventListRef: MutableRefObject; - animationRef: MutableRefObject; - speedFactorRef: MutableRefObject; -} - -const processTask = async ({ - eventListRef, - animationRef, - speedFactorRef, -}: ProcessProps) => { - const node = eventListRef.current.task_queue[0]; - eventListRef.current.set({ list: 'task_queue', type: 'shift' }); - - if (node.node.type !== 'ArrowFunctionExpression') { - // manage script - node.context.actions = []; - node.traverse(); - const { actions } = node.context; - - for (const step of actions) { - await handleStep({ eventListRef, step, speedFactorRef, animationRef }); - } - } else { - // manage callbacks - const expression = nodeFactory({ - node: (node.node as ArrowFunctionExpression).body, - context: { - ...node.context, - actions: [], - }, - params: node.params, - }); - - expression.traverse(); - const { actions } = expression.context; - - for (const step of actions) { - await handleStep({ eventListRef, step, speedFactorRef, animationRef }); - } - } - - if (eventListRef.current.task_queue.length === 0) - animationRef.current.setState(false, 'task'); -}; - -const processMicroTask = async ({ - eventListRef, - animationRef, - speedFactorRef, -}: ProcessProps) => { - while (eventListRef.current.microtask_queue.length) { - const node = eventListRef.current.microtask_queue[0]; - eventListRef.current.set({ list: 'microtask_queue', type: 'shift' }); - - const expression = nodeFactory({ - node: (node.node as ArrowFunctionExpression).body, - context: { - ...node.context, - actions: [], - }, - params: node.params, - }); - - expression.traverse(); - const { actions } = expression.context; - - for (const step of actions) { - await handleStep({ eventListRef, step, speedFactorRef, animationRef }); - } - } - animationRef.current.setState(false, 'microtask'); -}; - -const processRender = async ({ - eventListRef, - animationRef, - speedFactorRef, -}: ProcessProps) => { - if (eventListRef.current.render_callbacks.length === 0) { - await new Promise((resolve) => - setTimeout(resolve, DELAY_BETWEEN_ACTIONS_MS / speedFactorRef.current) - ); - } - - while (eventListRef.current.render_callbacks.length) { - const node = eventListRef.current.render_callbacks[0]; - if (!node) { - await new Promise((resolve) => - setTimeout(resolve, DELAY_BETWEEN_ACTIONS_MS / speedFactorRef.current) - ); - animationRef.current.setState(false, 'render'); - return; - } - eventListRef.current.set({ list: 'render_callbacks', type: 'shift' }); - - const expression = nodeFactory({ - node: (node.node as ArrowFunctionExpression).body, - context: { - ...node.context, - actions: [], - }, - params: node.params, - }); - expression.traverse(); - const { actions } = expression.context; - - for (const step of actions) { - await handleStep({ eventListRef, step, speedFactorRef, animationRef }); - } - } - animationRef.current.setState(false, 'render'); -}; - -export const useProcessEvent = () => { - const eventListRef = useRefState(useEventLists, (state) => state); - const animationRef = useRefState(useEventLoopAnimation, (state) => state); - const speedFactorRef = useRefState(useSpeedFactor, (state) => state.speed); - - return useCallback( - async (type: EventInterface['type']) => { - if (type === 'task') { - await processTask({ eventListRef, animationRef, speedFactorRef }); - } else if (type === 'microtask') { - await processMicroTask({ eventListRef, animationRef, speedFactorRef }); - } else if (type === 'render') { - await processRender({ eventListRef, animationRef, speedFactorRef }); - } - }, - [eventListRef, animationRef, speedFactorRef] - ); -}; - -export const handleStep = async ({ - eventListRef, - step, - speedFactorRef, - animationRef, -}: { - eventListRef: MutableRefObject; - speedFactorRef: MutableRefObject; - animationRef: MutableRefObject; - step: ActionInterface; -}) => { - while (animationRef.current.status === 'paused') { - await new Promise((resolve) => setTimeout(resolve, 200)); - } - if (animationRef.current.status === 'disabled') return; - - if (step.type === 'push' && step.list === 'callstack') { - const range = (step.value as CallStackValue).range; - useEditor.getState().pushMarker([range.start, range.end]); - } - - if (step.type === 'pop' && step.list === 'callstack') { - useEditor.getState().popMarker(); - } - - eventListRef.current.set({ - list: step.list, - type: step.type, - value: step.value, - }); - await new Promise((resolve) => - setTimeout(resolve, DELAY_BETWEEN_ACTIONS_MS / speedFactorRef.current) - ); -}; diff --git a/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.modal.tsx b/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.modal.tsx new file mode 100644 index 0000000..681d7c3 --- /dev/null +++ b/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.modal.tsx @@ -0,0 +1,31 @@ +import * as Styled from './MicroTasksQueue.styled.ts'; +import InfoModal from 'components/Modal/Modal.tsx'; + +function MicroTasksQueueModal({ + isOpened, + toggle, +}: { + isOpened: boolean; + toggle: () => void; +}) { + return ( + +

Microtasks

+ +

+ A microtask is a short function which is executed after the function or + program which created it exits and only if the JavaScript execution + stack is empty, but before returning control to the event loop being + used by the user agent to drive the script's execution environment. +

+

Events that can trigger new microtasks:

+
    +
  • Promise resolution (.then(), .catch(), .finally())
  • +
  • Occurrence of observed DOM changes
  • +
  • queueMicrotask() method
  • +
+
+ ); +} + +export default MicroTasksQueueModal; diff --git a/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.styled.ts b/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.styled.ts index ed8c91c..b5cc03c 100644 --- a/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.styled.ts +++ b/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.styled.ts @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; import { css } from '@emotion/react'; -import InfoClosed from '../../../../components/CloseIcon/InfoIcon.tsx'; +import InfoClosed from 'components/CloseIcon/InfoIcon.tsx'; export const MicroTasksQueue = styled.div` flex: 1; diff --git a/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.tsx b/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.tsx index 024305b..d126fc4 100644 --- a/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.tsx +++ b/src/pages/home/sections/MicroTasksQueue/MicroTasksQueue.tsx @@ -1,48 +1,26 @@ -import { useEventLists } from '../../../../store/store.ts'; +import { useQueueManagerStore } from 'store/store.ts'; import * as Styled from './MicroTasksQueue.styled.ts'; -import InfoIcon from '../../../../components/InfoIcon/InfoIcon.tsx'; -import InfoModal from '../../../../components/Modal/Modal.tsx'; -import useBoolean from '../../../../utils/useBoolean.tsx'; +import InfoIcon from 'components/InfoIcon/InfoIcon.tsx'; +import useBoolean from 'utils/hooks/useBoolean.ts'; import { Zoom } from '@mui/material'; import { List } from '../../Home.styled.ts'; +import MicroTasksQueueModal from './MicroTasksQueue.modal.tsx'; function MicroTasksQueue({ className }: { className?: string }) { - const tasks = useEventLists((state) => state.microtask_queue); - const [open, setOpen, setClose] = useBoolean(false); + const tasks = useQueueManagerStore((state) => state.microtask); + const [isOpened, toggle] = useBoolean(false); return ( Microtasks Queue - - {tasks.map((task) => { - const serialized = task.serialize(); - const key = serialized + task.node.start; - return ( - - {serialized} - - ); - })} - -

Microtasks

- -

- A microtask is a short function which is executed after the function - or program which created it exits and only if the JavaScript - execution stack is empty, but before returning control to the event - loop being used by the user agent to drive the script's execution - environment. -

-

- Events that can trigger new microtasks: -

-
    -
  • Promise resolution (.then(), .catch(), .finally())
  • -
  • Occurrence of observed DOM changes
  • -
  • queueMicrotask() method
  • -
-
+ + {tasks.map((task) => ( + + {task} + + ))} +
); diff --git a/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.modal.tsx b/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.modal.tsx new file mode 100644 index 0000000..c1cff25 --- /dev/null +++ b/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.modal.tsx @@ -0,0 +1,35 @@ +import * as Styled from './RequestAnimationFrameQueue.styled.ts'; +import InfoModal from 'components/Modal/Modal.tsx'; + +function RequestAnimationFrameQueueModal({ + isOpened, + toggle, +}: { + isOpened: boolean; + toggle: () => void; +}) { + return ( + +

RequestAnimationFrame

+ +

+ The window.requestAnimationFrame() method tells the browser you wish to + perform an animation. It requests the browser to call a user-supplied + callback function before the next repaint. +

+

+ The frequency of calls to the callback function will generally match the + display refresh rate. The most common refresh rate is 60hz, (60 + cycles/frames per second), though 75hz, 120hz, and 144hz are also widely + used. +

+

+ requestAnimationFrame() calls are paused in most browsers when running + in background tabs or hidden iframes, in order to improve performance + and battery life. +

+
+ ); +} + +export default RequestAnimationFrameQueueModal; diff --git a/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.styled.ts b/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.styled.ts index 7c1a21a..b29c861 100644 --- a/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.styled.ts +++ b/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.styled.ts @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; import { css } from '@mui/material'; -import InfoClosed from '../../../../components/CloseIcon/InfoIcon.tsx'; +import InfoClosed from 'components/CloseIcon/InfoIcon.tsx'; export const CallbacksQueue = styled.div` flex: 1; diff --git a/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.tsx b/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.tsx index 4fd712b..dda4c59 100644 --- a/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.tsx +++ b/src/pages/home/sections/RequestAnimationFrameQueue/RequestAnimationFrameQueue.tsx @@ -1,49 +1,26 @@ -import { useEventLists } from '../../../../store/store.ts'; import * as Styled from './RequestAnimationFrameQueue.styled.ts'; -import InfoIcon from '../../../../components/InfoIcon/InfoIcon.tsx'; -import InfoModal from '../../../../components/Modal/Modal.tsx'; -import useBoolean from '../../../../utils/useBoolean.tsx'; +import InfoIcon from 'components/InfoIcon/InfoIcon.tsx'; +import useBoolean from 'utils/hooks/useBoolean.ts'; import { Zoom } from '@mui/material'; import { List } from '../../Home.styled.ts'; +import RequestAnimationFrameQueueModal from './RequestAnimationFrameQueue.modal.tsx'; +import { useQueueManagerStore } from 'store/store.ts'; function RequestAnimationFrameQueue({ className }: { className?: string }) { - const callbacks = useEventLists((state) => state.render_callbacks); - const [open, setOpen, setClose] = useBoolean(false); + const callbacks = useQueueManagerStore((state) => state.rafCallback); + const [isOpened, toggle] = useBoolean(false); return ( RequestAnimationFrame callbacks - - {callbacks.map((callback) => { - const serialized = callback.serialize(); - const key = serialized + callback.node.start; - return ( - - {serialized} - - ); - })} - -

RequestAnimationFrame

- -

- The window.requestAnimationFrame() method tells the browser you wish - to perform an animation. It requests the browser to call a - user-supplied callback function before the next repaint. -

-

- The frequency of calls to the callback function will generally match - the display refresh rate. The most common refresh rate is 60hz, (60 - cycles/frames per second), though 75hz, 120hz, and 144hz are also - widely used. -

-

- requestAnimationFrame() calls are paused in most browsers when - running in background tabs or hidden iframes, in order to improve - performance and battery life. -

-
+ + {callbacks.map((callback) => ( + + {callback} + + ))} +
); diff --git a/src/pages/home/sections/TasksQueue/TasksQueue.modal.tsx b/src/pages/home/sections/TasksQueue/TasksQueue.modal.tsx new file mode 100644 index 0000000..919b6db --- /dev/null +++ b/src/pages/home/sections/TasksQueue/TasksQueue.modal.tsx @@ -0,0 +1,43 @@ +import * as Styled from './TasksQueue.styled.ts'; +import InfoModal from 'components/Modal/Modal.tsx'; + +function TasksQueueModal({ + isOpened, + toggle, +}: { + isOpened: boolean; + toggle: () => void; +}) { + return ( + +

Tasks

+ +

+ A task is anything which is scheduled to be run by the standard + mechanisms such as initially starting to run a program, an event being + dispatched asynchronously, or an interval or timeout being fired. These + all get scheduled on the task queue. +

+

+ For example, tasks get added to the task queue when: +

+
    +
  • + A new JavaScript program or subprogram is executed (such as from a + console, or by running the code in a {'