Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions client/src/Services/socketClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ class SocketClient {

private setupDefaultEvents() {
this.socket.on('connect', () => {
console.log('Socket handler Built / Connected!');
console.log('Socket Connected!');
});
this.socket.on('disconnect', () => {
console.log('Socket handler Removed / Disconnected!');

this.socket.off('disconnect', () => {
console.log('Socket Disconnected!');
});

// TODO: This section leaves for debugging purpose
Expand All @@ -37,6 +38,7 @@ class SocketClient {
this.socket = io(URL, { path: '/dapi' });
this.setupDefaultEvents();
this.socket.connect();
console.log('created a new socket client!');
}

setupEventHandlers(handlers: ServerToClientEvent) {
Expand Down Expand Up @@ -82,11 +84,27 @@ class SocketClient {
}

interface SocketStore {
socketClient: SocketClient;
socketClient: SocketClient | null;
initialise: () => void;
disconnect: () => void;
}

const useSocketClientStore = create<SocketStore>(() => ({
socketClient: new SocketClient(),
const useSocketClientStore = create<SocketStore>((set, get) => ({
socketClient: null,
initialise: () => {
if (!get().socketClient) {
console.log('intialising socket client');
set({ socketClient: new SocketClient() });
}
},
disconnect: () => {
const client = get().socketClient;
if (client) {
client.socket.close();
console.log('closing socket client ');
set({ socketClient: null });
}
},
}));

export default useSocketClientStore;
133 changes: 81 additions & 52 deletions client/src/Services/useSocketCommunication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,107 +21,136 @@ export const useSocketCommunication = () => {
resetConsoleChunks,
appendConsoleChunks,
} = useGlobalStore();

const { setActive, clearFrontendState } = useFrontendStateStore();
const { socketClient } = useSocketClientStore();
const { setToastMessage: setMessage } = useToastStateStore();
const { setToastMessage } = useToastStateStore();
const queue = useQueue();

// Setup socket event handlers on mount
useEffect(() => {
const eventHandler = buildSocketEventHandler({
console.log('usesocketcom is mounted');

if (!socketClient) {
return;
}

const handlers = buildSocketEventHandler({
setActive,
updateNextFrame,
updateTypeDeclaration,
appendConsoleChunks,
updateCurrFocusedTab,
setMessage,
setMessage: setToastMessage,
});

socketClient.setupEventHandlers(eventHandler);
}, []);
socketClient.setupEventHandlers(handlers);
}, [socketClient]);

// reset entire debuggin session
const resetDebugSession = useCallback(() => {
updateNextFrame(INITIAL_BACKEND_STATE);
clearFrontendState();
setActive(false);
clearTypeDeclarations();
clearUserAnnotation();
resetConsoleChunks();
}, []);
}, [
updateNextFrame,
clearFrontendState,
setActive,
clearTypeDeclarations,
clearUserAnnotation,
resetConsoleChunks,
]);

// error handler for sending code
const handleSendCodeError = () => {
setToastMessage({
content: 'No file being selected',
colorTheme: 'warning',
durationMs: DEFAULT_MESSAGE_DURATION,
});
};

// send code to backend to start debugging session
const sendCode = useCallback(() => {
if (!socketClient) return;

resetDebugSession();

const { fileSystem, currFocusFilePath } = useUserFsStateStore.getState();
const file = fileSystem.getFileFromPath(currFocusFilePath);

if (!file || file.path === 'root') {
setMessage({
content: 'No file being selected.',
colorTheme: 'warning',
durationMs: DEFAULT_MESSAGE_DURATION,
});
handleSendCodeError();
return;
}

socketClient.serverAction.initializeDebugSession(file.data);
}, [socketClient]);
}, [socketClient, resetDebugSession]);

const executeNextWithRetry = useCallback(() => {
const addEventListenerWithTimeout = (
listener: (state: BackendState | null) => void,
timeout: number
) => {
let resolved = false;

const wrappedListener = (state: BackendState) => {
if (!resolved) {
resolved = true;
listener(state);
socketClient.socket.off('sendBackendStateToUser', wrappedListener);
}
};

socketClient.socket.on('sendBackendStateToUser', wrappedListener);

setTimeout(() => {
if (!resolved) {
resolved = true;
listener(null);
socketClient.socket.off('sendBackendStateToUser', wrappedListener);
}
}, timeout);
// add event listener with timeout
const addEventListenerWithTimeout = (
listener: (state: BackendState | null) => void,
timeout: number
) => {
if (!socketClient) return;

let resolved = false;

const wrappedListener = (state: BackendState) => {
if (!resolved) {
resolved = true;
listener(state);
socketClient.socket.off('sendBackendStateToUser', wrappedListener);
}
};

socketClient.socket.on('sendBackendStateToUser', wrappedListener);

setTimeout(() => {
if (!resolved) {
resolved = true;
listener(null);
socketClient.socket.off('sendBackendStateToUser', wrappedListener);
}
}, timeout);
};

// step debugger and wait for backend to respond
const executeNextWithRetry = useCallback(() => {
if (!socketClient) return Promise.resolve(false);

return queue(() => {
return new Promise<boolean>((resolve) => {
const handleBackendState = (state: BackendState | null) => {
if (state) {
resolve(true); // Resolve as success
} else {
resolve(false); // Resolve as failure due to timeout
}
resolve(!!state);
};

// Add the event listener with a timeout
addEventListenerWithTimeout(handleBackendState, 5000);
socketClient.serverAction.executeNext();
});
});
}, [socketClient]);
}, [socketClient, queue]);

// to call multiple next states in bulk
const bulkSendNextStates = useCallback(
async (count: number) => {
const results = await Promise.all(Array.from({ length: count }, executeNextWithRetry));
const successfulCount = results.filter((result) => result).length;
return successfulCount;
const results = await Promise.all(
Array.from({ length: count }, () => executeNextWithRetry())
);
return results.filter(Boolean).length;
},
[executeNextWithRetry]
);

return {
resetConsoleChunks,
appendConsoleChunks,
sendCode,
getNextState: executeNextWithRetry,
bulkSendNextStates,
resetDebugSession,
resetConsoleChunks, // Clear console logs
appendConsoleChunks, // Append logs
sendCode, // Start session with file
getNextState: executeNextWithRetry, // Step once
bulkSendNextStates, // Step multiple times
resetDebugSession, // Full reset
};
};
7 changes: 4 additions & 3 deletions client/src/visualiser-debugger/Component/Console/useCursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ function useCursor(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isCompiled: boolean
) {
const socket = useSocketClientStore((state) => state.socketClient);
const socketClient = useSocketClientStore((state) => state.socketClient); // i dont understand whats happening and need to clarify
const appendConsoleChunk = useGlobalStore((state) => state.appendConsoleChunks);
const setActive = useFrontendStateStore((state) => state.setActive);
const { socketClient } = useSocketClientStore();

const [shifts, setShifts] = useState(0);
const [paused, setPaused] = useState(true);
Expand Down Expand Up @@ -65,7 +64,8 @@ function useCursor(
// TODO: Ensure that it's okay to send the PREFIX to the backend
// because if we remove the PREFIX, we can't tell which command
// is input while the program is running and while the program is not running
socket.serverAction.sendStdin(content);
if (!socketClient) return;
socketClient.serverAction.sendStdin(content);
appendConsoleChunk(`${content}\n`);
clearInput();
scrollToBottom();
Expand All @@ -82,6 +82,7 @@ function useCursor(
default:
break;
}
if (!socketClient) return;

if (isCtrlPressed && key === 'd') {
socketClient.serverAction.sendEOF();
Expand Down
17 changes: 17 additions & 0 deletions client/src/visualiser-debugger/DevelopmentMode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Tabs, Tab } from 'components/Tabs';
import Console from 'visualiser-debugger/Component/Console/Console';
import Joyride from 'react-joyride';
import DynamicTabs from 'components/TabResize/DynamicTabs';
import useSocketClientStore from 'Services/socketClient';
import { ThemeProvider as MuiThemeProvider } from '@mui/material';
import DevelopmentModeNavbar from '../components/Navbars/DevelopmentModeNavbar';
import Configuration from './Component/Configuration/Configuration';
Expand Down Expand Up @@ -35,6 +36,9 @@ const DevelopmentModeContent = () => {
}
};

const initialise = useSocketClientStore((state) => state.initialise);
const disconnect = useSocketClientStore((state) => state.disconnect);

// Onboarding Code
useEffect(() => {
if (onboardingCurrFile) {
Expand All @@ -45,6 +49,19 @@ const DevelopmentModeContent = () => {
}
}, [onboardingCurrFile]);

// initialising socket cycle
useEffect(() => {
// intialise socket Connection
console.log('DevelopmentMode is mounted');
initialise();

// disconnect socket connection
return () => {
console.log('DevelopmentMode is unmounted');
disconnect();
};
}, [initialise, disconnect]);

const handleClickStart = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault();
resetRootPaths();
Expand Down
Loading