Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ff284a3
integrate conductor into frontend
tsammeow Feb 3, 2025
4667807
upgrade yarn(4.6.0)
tsammeow Feb 4, 2025
0ce4f9c
Merge branch 'master'
tsammeow Feb 4, 2025
bef90bb
fix lint issues
tsammeow Feb 4, 2025
c9165a4
Merge branch 'master' into master
RichDom2185 Feb 5, 2025
b085079
fix gl resolution problem
tsammeow Feb 5, 2025
711fd73
change github actions to use --immutable instead of --frozen-lockfile…
tsammeow Feb 5, 2025
309eb9e
add sa-conductor to list of ignored module paths in jest config
tsammeow Feb 5, 2025
2e3a25a
readd gl@^8.0.2 resolution to package.json
tsammeow Feb 5, 2025
b444a49
format with prettier
tsammeow Feb 5, 2025
2a10429
update caniuse-lite
tsammeow Feb 5, 2025
614ea2a
update sa-conductor(0.0.13)
tsammeow Feb 8, 2025
b575d9d
add ui to edit feature flags
tsammeow Feb 10, 2025
e788347
support repl, and use stop button to kill runner instead of timeout
tsammeow Feb 10, 2025
2c1a17f
extract actual feature selector to separate function and rename selec…
tsammeow Feb 10, 2025
4268f4d
stop replReducer setting isRunning to true when using conductor, as i…
tsammeow Feb 10, 2025
ce8c600
revert replReducer change as it causes an error when using the repl
tsammeow Feb 11, 2025
6b16a6f
hide eval button if not running when using conductor
tsammeow Feb 11, 2025
2a07968
Merge branch 'master' of https://github.com/source-academy/frontend i…
RichDom2185 Feb 15, 2025
1426f25
update lockfile after package.json change
tsammeow Feb 16, 2025
47f7864
upgrade sa-conductor(0.1.3) to use new file communication protocol
tsammeow Feb 16, 2025
5fc4dc6
upgrade sa-conductor(0.1.4)
tsammeow Feb 16, 2025
76d0f6c
Merge branch 'master' into master
martin-henz Feb 18, 2025
72ae405
update location of sa-languages repository
tsammeow Feb 18, 2025
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
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,19 @@ yarn-error.log
# emacs backup files

*~

# yarn files

.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Swap the comments on the following lines if you wish to use zero-installs
# In that case, don't forget to run `yarn config set enableGlobalCache false`!
# Documentation here: https://yarnpkg.com/features/caching#zero-installs

#!.yarn/cache
.pnp.*
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"private": true,
"name": "frontend",
"packageManager": "[email protected]",
"version": "1.4.3",
"scripts-info": {
"analyze": "Analyze bundle size breakdown",
Expand Down Expand Up @@ -91,6 +92,8 @@
"redux-mock-store": "^1.5.4",
"redux-saga": "^1.2.3",
"rehype-react": "^8.0.0",
"sa-conductor": "https://github.com/tsammeow/sa-conductor.git#0.0.11",
"sa-languages": "https://github.com/tsammeow/sa-languages.git",
"showdown": "^2.1.0",
"sourceror": "^0.8.5",
"unified": "^11.0.0",
Expand Down Expand Up @@ -138,7 +141,6 @@
"cross-env": "^7.0.3",
"eslint": "^9.9.0",
"eslint-plugin-react": "^7.35.0",
"//": "See: https://github.com/facebook/react/issues/28313#issuecomment-2076798972, https://github.com/t3-oss/create-t3-turbo/issues/984#issuecomment-2076413457",
"eslint-plugin-react-hooks": "5.1.0-canary-cb151849e1-20240424",
"eslint-plugin-react-refresh": "^0.4.9",
"eslint-plugin-simple-import-sort": "^12.1.1",
Expand All @@ -163,9 +165,6 @@
"url": "^0.11.1",
"webpack-bundle-analyzer": "^4.9.0"
},
"resolutions": {
"**/gl": "^8.0.2"
},
"browserslist": {
"production": [
"Firefox ESR",
Expand Down
3 changes: 3 additions & 0 deletions src/commons/application/ApplicationTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PlaybackStatus, RecordingStatus } from '../../features/sourceRecorder/S
import { StoriesEnvState, StoriesState } from '../../features/stories/StoriesTypes';
import { freshSortState } from '../../pages/academy/grading/subcomponents/GradingSubmissionsTable';
import { WORKSPACE_BASE_PATHS } from '../../pages/fileSystem/createInBrowserFileSystem';
import { defaultFeatureFlags, FeatureFlagsState } from '../featureFlags';
import { FileSystemState } from '../fileSystem/FileSystemTypes';
import { SideContentManagerState, SideContentState } from '../sideContent/SideContentTypes';
import Constants from '../utils/Constants';
Expand All @@ -29,6 +30,7 @@ export type OverallState = {
readonly stories: StoriesState;
readonly workspaces: WorkspaceManagerState;
readonly dashboard: DashboardState;
readonly featureFlags: FeatureFlagsState;
readonly fileSystem: FileSystemState;
readonly sideContent: SideContentManagerState;
};
Expand Down Expand Up @@ -612,6 +614,7 @@ export const defaultState: OverallState = {
session: defaultSession,
stories: defaultStories,
workspaces: defaultWorkspaceManager,
featureFlags: defaultFeatureFlags,
fileSystem: defaultFileSystem,
sideContent: defaultSideContentManager
};
2 changes: 2 additions & 0 deletions src/commons/application/reducers/RootReducer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { combineReducers, Reducer } from '@reduxjs/toolkit';
import { FeatureFlagsReducer as featureFlags } from 'src/commons/featureFlags';
import { SourceActionType } from 'src/commons/utils/ActionsHelper';

import { AchievementReducer as achievement } from '../../../features/achievement/AchievementReducer';
Expand All @@ -20,6 +21,7 @@ const rootReducer: Reducer<OverallState, SourceActionType> = combineReducers({
session,
stories,
workspaces,
featureFlags,
fileSystem,
sideContent
});
Expand Down
1 change: 1 addition & 0 deletions src/commons/featureFlags/FeatureFlag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type FeatureFlag<T> = readonly [string, T];
28 changes: 28 additions & 0 deletions src/commons/featureFlags/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createSlice } from "@reduxjs/toolkit";

import { FeatureFlag } from "./FeatureFlag";

export type FeatureFlagsState = {
modifiedFlags: Record<string, any>
};

export const defaultFeatureFlags = {
modifiedFlags: {}
};

const featureFlagsSlice = createSlice({
name: "featureFlags",
initialState: defaultFeatureFlags,
reducers: {
setFlag<T>(state: FeatureFlagsState, action: { payload: { featureFlag: FeatureFlag<T>, value: T } }) {
state.modifiedFlags[action.payload.featureFlag[0]] = action.payload.value;
},
resetFlag<T>(state: FeatureFlagsState, action: { payload: { featureFlag: FeatureFlag<T> } }) {
delete state.modifiedFlags[action.payload.featureFlag[0]];
}
}
});

export const FeatureFlagsActions = featureFlagsSlice.actions;

export const FeatureFlagsReducer = featureFlagsSlice.reducer;
7 changes: 7 additions & 0 deletions src/commons/featureFlags/publicFlags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { featureConductor } from "src/features/conductor/featureConductor";

import { FeatureFlag } from "./FeatureFlag";

export const publicFlags: FeatureFlag<any>[] = [
featureConductor
];
10 changes: 10 additions & 0 deletions src/commons/featureFlags/selectFeature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SagaIterator } from "redux-saga";
import { select } from "redux-saga/effects";

import { FeatureFlag } from "./FeatureFlag";

export function* selectFeature<T>(featureFlag: FeatureFlag<T>): SagaIterator<T> {
const [flag, defaultValue] = featureFlag;
const flagValue = yield select(state => state.featureFlags.modifiedFlags[flag]);
return flagValue ?? defaultValue;
}
8 changes: 8 additions & 0 deletions src/commons/featureFlags/useFeature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useTypedSelector } from "../utils/Hooks";
import { FeatureFlag } from "./FeatureFlag";

export function useFeature<T>(featureFlag: FeatureFlag<T>) {
const [flag, defaultValue] = featureFlag;
const flagValue = useTypedSelector(state => state.featureFlags.modifiedFlags[flag]);
return flagValue ?? defaultValue;
}
2 changes: 2 additions & 0 deletions src/commons/mocks/StoreMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
defaultWorkspaceManager,
OverallState
} from '../application/ApplicationTypes';
import { defaultFeatureFlags } from '../featureFlags';
import { SourceActionType } from '../utils/ActionsHelper';
import { DeepPartial } from '../utils/TypeHelper';

Expand All @@ -29,6 +30,7 @@ export function mockInitialStore(
workspaces: defaultWorkspaceManager,
session: defaultSession,
stories: defaultStories,
featureFlags: defaultFeatureFlags,
fileSystem: defaultFileSystem,
sideContent: defaultSideContentManager
};
Expand Down
57 changes: 55 additions & 2 deletions src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import { ACORN_PARSE_OPTIONS, TRY_AGAIN } from 'js-slang/dist/constants';
import { InterruptedError } from 'js-slang/dist/errors/errors';
import { manualToggleDebugger } from 'js-slang/dist/stdlib/inspector';
import { Chapter, ErrorSeverity, ErrorType, SourceError, Variant } from 'js-slang/dist/types';
import { SagaIterator } from 'redux-saga';
import { call, put, race, select, take } from 'redux-saga/effects';
import { eventChannel, SagaIterator } from 'redux-saga';
import { call, cancel, cancelled, delay, fork, put, race, select, take } from 'redux-saga/effects';
import { IConduit } from 'sa-conductor/dist/conduit';
import * as Sourceror from 'sourceror';
import InterpreterActions from 'src/commons/application/actions/InterpreterActions';
import { selectFeature } from 'src/commons/featureFlags/selectFeature';
import { makeCCompilerConfig, specialCReturnObject } from 'src/commons/utils/CToWasmHelper';
import { javaRun } from 'src/commons/utils/JavaHelper';
import { BrowserHostPlugin } from 'src/features/conductor/BrowserHostPlugin';
import { createConductor } from 'src/features/conductor/createConductor';
import { featureConductor } from 'src/features/conductor/featureConductor';
import StoriesActions from 'src/features/stories/StoriesActions';

import { EventType } from '../../../../features/achievement/AchievementTypes';
Expand Down Expand Up @@ -39,6 +44,9 @@ export function* evalCodeSaga(
actionType: string,
storyEnv?: string
): SagaIterator {
if (yield call(selectFeature, featureConductor)) {
return yield call(evalCodeConductorSaga, files, entrypointFilePath, context, execTime, workspaceLocation, actionType, storyEnv);
}
context.runtime.debuggerOn =
(actionType === WorkspaceActions.evalEditor.type ||
actionType === InterpreterActions.debuggerResume.type) &&
Expand Down Expand Up @@ -461,6 +469,51 @@ export function* evalCodeSaga(
}
}

function* handleStdout(hostPlugin: BrowserHostPlugin, workspaceLocation: WorkspaceLocation): SagaIterator {
const outputChan = eventChannel(emitter => {
hostPlugin.receiveOutput = emitter;
return () => {
if (hostPlugin.receiveOutput === emitter) delete hostPlugin.receiveOutput;
}
});
try {
while (true) {
const output = yield take(outputChan);
yield put(actions.handleConsoleLog(workspaceLocation, output));
}
} finally {
if (yield cancelled()) {
outputChan.close();
}
}
}

export function* evalCodeConductorSaga(
files: Record<string, string>,
entrypointFilePath: string,
context: Context,
execTime: number,
workspaceLocation: WorkspaceLocation,
actionType: string,
storyEnv?: string
): SagaIterator {
const evaluatorResponse: Response = yield call(fetch, "https://fyp.tsammeow.dev/evaluator/worker.js"); // temporary evaluator
if (!evaluatorResponse.ok) throw Error("can't get evaluator");
const evaluatorBlob: Blob = yield call([evaluatorResponse, "blob"]);
const url: string = yield call(URL.createObjectURL, evaluatorBlob);
const { hostPlugin, conduit }: { hostPlugin: BrowserHostPlugin, conduit: IConduit } =
yield call(createConductor, url, async (fileName: string) => files[fileName]);
const stdoutTask = yield fork(handleStdout, hostPlugin, workspaceLocation);
yield call([hostPlugin, "runEvaluator"], entrypointFilePath);
yield delay(execTime);
yield call([conduit, "terminate"]);
yield cancel(stdoutTask);
//yield put(actions.debuggerReset(workspaceLocation));
yield put(actions.endInterruptExecution(workspaceLocation));
//console.log("killed");
//yield call(URL.revokeObjectURL, url);
}

// Special module errors
const specialErrors = ['source_academy_interrupt'] as const;
type SpecialError = (typeof specialErrors)[number];
Expand Down
4 changes: 3 additions & 1 deletion src/commons/utils/ActionsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import SourcecastActions from '../../features/sourceRecorder/sourcecast/Sourceca
import SourceRecorderActions from '../../features/sourceRecorder/SourceRecorderActions';
import SourcereelActions from '../../features/sourceRecorder/sourcereel/SourcereelActions';
import StoriesActions from '../../features/stories/StoriesActions';
import { FeatureFlagsActions } from '../featureFlags';
import { ActionType } from './TypeHelper';

export const actions = {
Expand All @@ -38,7 +39,8 @@ export const actions = {
...RemoteExecutionActions,
...FileSystemActions,
...StoriesActions,
...SideContentActions
...SideContentActions,
...FeatureFlagsActions
};

export type SourceActionType = ActionType<typeof actions>;
16 changes: 16 additions & 0 deletions src/features/conductor/BrowserHostPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BasicHostPlugin } from "sa-conductor/dist/conductor/host";
import { IConduit } from "sa-conductor/dist/conduit";

export class BrowserHostPlugin extends BasicHostPlugin {
requestFile: (fileName: string) => Promise<string | undefined>;

init(conduit: IConduit, channels: any): void {
super.init(conduit, channels);
console.log("browser-inited");
}

constructor(onRequestFile: (fileName: string) => Promise<string | undefined>) {
super();
this.requestFile = onRequestFile;
}
}
11 changes: 11 additions & 0 deletions src/features/conductor/createConductor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Conduit, IConduit } from "sa-conductor/dist/conduit";

import { BrowserHostPlugin } from "./BrowserHostPlugin";

export function createConductor(evaluatorPath: string, onRequestFile: (fileName: string) => Promise<string | undefined>): { hostPlugin: BrowserHostPlugin, conduit: IConduit } {
const worker = new Worker(evaluatorPath);
const hostPlugin = new BrowserHostPlugin(onRequestFile);
const conduit = new Conduit(worker, true);
conduit.registerPlugin(hostPlugin);
return { hostPlugin, conduit };
}
3 changes: 3 additions & 0 deletions src/features/conductor/featureConductor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { FeatureFlag } from "src/commons/featureFlags/FeatureFlag";

export const featureConductor: FeatureFlag<boolean> = ["conductor.enable", false] as const;
Loading
Loading