Skip to content

Commit e02bac7

Browse files
author
Oskar Widmark
committed
feat: parallelization for sorting networks
1 parent 089b1da commit e02bac7

File tree

8 files changed

+325
-127
lines changed

8 files changed

+325
-127
lines changed

TODO.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Features:
22

33
- [x] Sub 1ms draw times
4-
- [ ] Parallelization for algorithms
4+
- [x] Parallelization for algorithms
55
- [ ] Merge sort
66
- [x] 2D color matrix visualization
77
- [ ] Spiral visualization
@@ -14,3 +14,6 @@ Fixes:
1414
- [x] Improve Bully Sort
1515
- [x] Cleaner draw logic with no gaps
1616
- [ ] Handle resizing to smaller height correctly
17+
- [ ] Make parallel highlight work for recursive sorting networks
18+
- [ ] Separate highlight types for parallel highlighting to work properly
19+
- [ ] Optimize current 2D color matrix visualization

src/App.tsx

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,12 @@ class App extends React.Component<Props> {
189189
this.props.stopSounds();
190190
};
191191

192-
drawAndSwap = async (arr: SortValue[], i1: number, i2: number) => {
192+
drawAndSwap = async (
193+
arr: SortValue[],
194+
i1: number,
195+
i2: number,
196+
drawIteration?: number,
197+
) => {
193198
if (!this.state.isSorting) throw Error('isSorting is false!');
194199

195200
this.swap(arr, i1, i2);
@@ -205,7 +210,7 @@ class App extends React.Component<Props> {
205210
this.setState((prevState: AppState) => ({
206211
nbrOfSwaps: prevState.nbrOfSwaps + 1,
207212
}));
208-
this.canvasController.highlight(arr, [i1, i2]);
213+
this.canvasController.highlight(arr, [i1, i2], drawIteration);
209214
await sleep(this.state.settings.swapTime, this.swapCounter++);
210215
}
211216
};
@@ -215,12 +220,14 @@ class App extends React.Component<Props> {
215220
i1: number,
216221
operator: Operator,
217222
i2: number,
223+
drawIteration?: number,
218224
): Promise<boolean> => {
219225
return this._compare({
220226
arr,
221227
i1,
222228
operator,
223229
i2,
230+
drawIteration,
224231
});
225232
};
226233

@@ -229,22 +236,26 @@ class App extends React.Component<Props> {
229236
i: number,
230237
operator: Operator,
231238
value: number,
239+
drawIteration?: number,
232240
): Promise<boolean> => {
233241
return this._compare({
234242
arr,
235243
i1: i,
236244
operator,
237245
value,
246+
drawIteration,
238247
});
239248
};
240249

241250
async _compare(
242-
params: { arr: SortValue[]; i1: number; operator: Operator } & (
243-
| { value: number }
244-
| { i2: number }
245-
),
251+
params: {
252+
arr: SortValue[];
253+
i1: number;
254+
operator: Operator;
255+
drawIteration?: number;
256+
} & ({ value: number } | { i2: number }),
246257
) {
247-
const { arr, i1, operator } = params;
258+
const { arr, i1, operator, drawIteration } = params;
248259
if (!this.state.isSorting) throw Error('isSorting is false!');
249260
this.nbrOfComparisons++;
250261
if (this.state.settings.compareTime) {
@@ -257,7 +268,7 @@ class App extends React.Component<Props> {
257268
nbrOfComparisons: prevState.nbrOfComparisons + 1,
258269
}));
259270
const indexes = 'value' in params ? [i1] : [i1, params.i2];
260-
this.canvasController.highlight(arr, indexes);
271+
this.canvasController.highlight(arr, indexes, drawIteration);
261272
await sleep(this.state.settings.compareTime, this.comparisonCounter++);
262273
}
263274

@@ -285,7 +296,11 @@ class App extends React.Component<Props> {
285296
[arr[i1], arr[i2]] = [arr[i2], arr[i1]];
286297
}
287298

288-
registerAuxWrite = async (arr: SortValue[], i: number) => {
299+
registerAuxWrite = async (
300+
arr: SortValue[],
301+
i: number,
302+
drawIteration?: number,
303+
) => {
289304
if (!this.state.isSorting) throw Error('isSorting is false!');
290305

291306
this.nbrOfAuxWrites++;
@@ -298,7 +313,7 @@ class App extends React.Component<Props> {
298313
this.setState((prevState: AppState) => ({
299314
nbrOfAuxWrites: prevState.nbrOfAuxWrites + 1,
300315
}));
301-
this.canvasController.highlight(arr, [i]);
316+
this.canvasController.highlight(arr, [i], drawIteration);
302317
await sleep(this.state.settings.auxWriteTime, this.auxWriteCounter++);
303318
}
304319
};

src/Options.tsx

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ import {
77
} from '@mui/material';
88
import { AlgorithmOptions, SortName } from './types';
99
import { useCallback, useState } from 'react';
10+
import { LabeledCheckbox } from './components/LabeledCheckbox';
1011

1112
const getAlgorithmOptionFields = (
1213
sortName: SortName,
1314
): (keyof AlgorithmOptions)[] => {
1415
switch (sortName) {
1516
case SortName.BitonicSort:
1617
case SortName.OddEvenMergesort:
17-
return ['type'];
18+
return ['type', 'parallel'];
19+
case SortName.OddEvenSort:
20+
return ['parallel'];
1821
case SortName.RadixSortLSD:
1922
case SortName.RadixSortMSD:
2023
return ['base'];
@@ -46,6 +49,7 @@ const ALGORITHM_OPTION_LABELS: Record<keyof AlgorithmOptions, string> = {
4649
base: 'Base',
4750
shrinkFactor: 'Shrink Factor',
4851
heapType: 'Heap Type',
52+
parallel: 'Run in parallel',
4953
};
5054

5155
const ALGORITHM_OPTION_TEXT_FIELD_TYPES: Record<
@@ -56,20 +60,22 @@ const ALGORITHM_OPTION_TEXT_FIELD_TYPES: Record<
5660
base: 'number',
5761
shrinkFactor: 'number',
5862
heapType: 'select',
63+
parallel: 'checkbox',
5964
};
6065

6166
const ALGORITHM_OPTION_VALUES: Record<
6267
keyof AlgorithmOptions,
63-
AlgorithmOptions[keyof AlgorithmOptions][]
68+
AlgorithmOptions['type' | 'heapType'][]
6469
> = {
6570
type: ['iterative', 'recursive'],
6671
base: [],
6772
shrinkFactor: [],
6873
heapType: ['max', 'min'],
74+
parallel: [],
6975
};
7076

7177
const ALGORITHM_OPTION_VALUE_LABELS: Record<
72-
AlgorithmOptions[keyof AlgorithmOptions],
78+
AlgorithmOptions['type' | 'heapType'],
7379
string
7480
> = {
7581
iterative: 'Iterative',
@@ -124,41 +130,64 @@ export function Options({
124130
>
125131
Options
126132
</Typography>
127-
<Grid2 container spacing={2}>
133+
<Grid2 container>
128134
{algorithmOptionFields.map((field) => (
129-
<FormControl component="fieldset">
130-
<TextField
131-
key={field}
132-
select={ALGORITHM_OPTION_TEXT_FIELD_TYPES[field] === 'select'}
133-
label={ALGORITHM_OPTION_LABELS[field]}
134-
value={nonValidatedOptions[field]}
135-
onChange={(event) =>
136-
handleOptionChange(field, event.target.value)
137-
}
138-
error={!isValidOption(field, nonValidatedOptions[field])}
139-
size="small"
140-
sx={{ width: 120 }}
141-
type={
142-
ALGORITHM_OPTION_TEXT_FIELD_TYPES[field] === 'select'
143-
? undefined
144-
: ALGORITHM_OPTION_TEXT_FIELD_TYPES[field]
145-
}
146-
>
147-
{Object.values(ALGORITHM_OPTION_VALUES[field]).map((v) => (
148-
<MenuItem key={v} value={v}>
149-
<Typography
150-
align="left"
151-
variant="body2"
152-
color="textSecondary"
153-
>
154-
{ALGORITHM_OPTION_VALUE_LABELS[v] ?? v}
155-
</Typography>
156-
</MenuItem>
157-
))}
158-
</TextField>
159-
</FormControl>
135+
<OptionField
136+
key={field}
137+
field={field}
138+
nonValidatedOptions={nonValidatedOptions}
139+
handleOptionChange={handleOptionChange}
140+
/>
160141
))}
161142
</Grid2>
162143
</div>
163144
);
164145
}
146+
147+
const OptionField = (props: {
148+
field: keyof AlgorithmOptions;
149+
nonValidatedOptions: AlgorithmOptions;
150+
handleOptionChange: (field: keyof AlgorithmOptions, value: unknown) => void;
151+
}) => {
152+
const { field, nonValidatedOptions, handleOptionChange } = props;
153+
switch (ALGORITHM_OPTION_TEXT_FIELD_TYPES[field]) {
154+
case 'checkbox':
155+
return (
156+
<FormControl key={field} component="fieldset">
157+
<LabeledCheckbox
158+
label={ALGORITHM_OPTION_LABELS[field]}
159+
checked={Boolean(nonValidatedOptions[field])}
160+
onChecked={(checked) => handleOptionChange(field, checked)}
161+
/>
162+
</FormControl>
163+
);
164+
default:
165+
return (
166+
<FormControl component="fieldset">
167+
<TextField
168+
key={field}
169+
select={ALGORITHM_OPTION_TEXT_FIELD_TYPES[field] === 'select'}
170+
label={ALGORITHM_OPTION_LABELS[field]}
171+
value={nonValidatedOptions[field]}
172+
onChange={(event) => handleOptionChange(field, event.target.value)}
173+
error={!isValidOption(field, nonValidatedOptions[field])}
174+
size="small"
175+
sx={{ width: 120 }}
176+
type={
177+
ALGORITHM_OPTION_TEXT_FIELD_TYPES[field] === 'select'
178+
? undefined
179+
: ALGORITHM_OPTION_TEXT_FIELD_TYPES[field]
180+
}
181+
>
182+
{Object.values(ALGORITHM_OPTION_VALUES[field]).map((v) => (
183+
<MenuItem key={v} value={v}>
184+
<Typography align="left" variant="body2" color="textSecondary">
185+
{ALGORITHM_OPTION_VALUE_LABELS[v] ?? v}
186+
</Typography>
187+
</MenuItem>
188+
))}
189+
</TextField>
190+
</FormControl>
191+
);
192+
}
193+
};

src/canvas-controller.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
import { hsvToRgbHex, rgbHexToHsv } from './utils';
1010

1111
export class CanvasController {
12-
private prevHighlightIndices: number[] | null = null;
12+
private highlightIndices: number[] = [];
13+
private currentDrawIteration: number | null = null;
1314
prevDrawIndex: number | null = null;
1415
prevDrawHeight: number | null = null;
1516
isDrawing: boolean = false;
@@ -172,18 +173,22 @@ export class CanvasController {
172173
this.drawAll(arr);
173174
};
174175

175-
highlight = (arr: SortValue[], indices: number[]) => {
176-
if (this.prevHighlightIndices) {
177-
for (const idx of this.prevHighlightIndices) {
178-
if (this.context.visualizationType === VisualizationType.Matrix) {
179-
this.redrawCellRow(arr, idx);
180-
this.redrawCellColumn(arr, idx);
181-
continue;
176+
highlight = (arr: SortValue[], indices: number[], drawIteration?: number) => {
177+
if (drawIteration == null || drawIteration !== this.currentDrawIteration) {
178+
if (this.highlightIndices.length) {
179+
for (const idx of this.highlightIndices) {
180+
if (this.context.visualizationType === VisualizationType.Matrix) {
181+
this.redrawCellRow(arr, idx);
182+
this.redrawCellColumn(arr, idx);
183+
continue;
184+
}
185+
this.redrawColumn(arr, idx);
182186
}
183-
this.redrawColumn(arr, idx);
184187
}
188+
this.currentDrawIteration = drawIteration ?? null;
189+
this.highlightIndices = [];
185190
}
186-
this.prevHighlightIndices = indices;
191+
this.highlightIndices.push(...indices);
187192

188193
for (const idx of indices) {
189194
if (this.context.visualizationType === VisualizationType.Matrix) {
@@ -266,14 +271,14 @@ export class CanvasController {
266271
};
267272

268273
stopSorting = (arr: SortValue[]) => {
269-
if (this.prevHighlightIndices) {
274+
if (this.highlightIndices) {
270275
this.removeHighlight(arr);
271276
}
272-
this.prevHighlightIndices = null;
277+
this.highlightIndices = [];
273278
};
274279

275280
private removeHighlight = (arr: SortValue[]) => {
276-
this.highlight(arr, []);
281+
this.highlight(arr, [], -1);
277282
};
278283

279284
private redrawColumn = (arr: SortValue[], i: number, color?: string) => {

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const INIT_SETTINGS: Settings = {
3535
base: 4,
3636
shrinkFactor: 1.3,
3737
heapType: 'max',
38+
parallel: false,
3839
},
3940
colorPreset: ColorPreset.Rainbow,
4041
columnColor1: '#ffffff',

0 commit comments

Comments
 (0)