mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 10:23:41 +00:00
Merge pull request #50 from jhbruhn/fix/32-optimize-stitch-rendering
fix: Optimize stitch rendering performance for large patterns
This commit is contained in:
commit
48918194a1
1 changed files with 143 additions and 40 deletions
|
|
@ -157,6 +157,77 @@ export const Stitches = memo(
|
||||||
currentStitchIndex,
|
currentStitchIndex,
|
||||||
showProgress = false,
|
showProgress = false,
|
||||||
}: StitchesProps) => {
|
}: StitchesProps) => {
|
||||||
|
// PERFORMANCE OPTIMIZATION:
|
||||||
|
// Separate static group structure (doesn't change during sewing)
|
||||||
|
// from dynamic completion status (changes with currentStitchIndex).
|
||||||
|
// This prevents recalculating all groups on every progress update.
|
||||||
|
//
|
||||||
|
// For very large patterns (>100k stitches), consider:
|
||||||
|
// - Virtualization: render only visible stitches based on viewport
|
||||||
|
// - LOD (Level of Detail): reduce stitch density when zoomed out
|
||||||
|
// - Web Workers: offload grouping calculations to background thread
|
||||||
|
|
||||||
|
interface StaticStitchGroup {
|
||||||
|
color: string;
|
||||||
|
points: number[];
|
||||||
|
isJump: boolean;
|
||||||
|
startIndex: number; // First stitch index in this group
|
||||||
|
endIndex: number; // Last stitch index in this group
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static grouping - only recalculates when stitches or colors change
|
||||||
|
const staticGroups = useMemo(() => {
|
||||||
|
const groups: StaticStitchGroup[] = [];
|
||||||
|
let currentGroup: StaticStitchGroup | null = null;
|
||||||
|
|
||||||
|
let prevX = 0;
|
||||||
|
let prevY = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < stitches.length; i++) {
|
||||||
|
const stitch = stitches[i];
|
||||||
|
const [x, y, cmd, colorIndex] = stitch;
|
||||||
|
const isJump = (cmd & MOVE) !== 0;
|
||||||
|
const color = getThreadColor(pesData, colorIndex);
|
||||||
|
|
||||||
|
// Start new group if color or type changes (NOT completion status)
|
||||||
|
if (
|
||||||
|
!currentGroup ||
|
||||||
|
currentGroup.color !== color ||
|
||||||
|
currentGroup.isJump !== isJump
|
||||||
|
) {
|
||||||
|
// For jump stitches, include previous position
|
||||||
|
if (isJump && i > 0) {
|
||||||
|
currentGroup = {
|
||||||
|
color,
|
||||||
|
points: [prevX, prevY, x, y],
|
||||||
|
isJump,
|
||||||
|
startIndex: i,
|
||||||
|
endIndex: i,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
currentGroup = {
|
||||||
|
color,
|
||||||
|
points: [x, y],
|
||||||
|
isJump,
|
||||||
|
startIndex: i,
|
||||||
|
endIndex: i,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
groups.push(currentGroup);
|
||||||
|
} else {
|
||||||
|
currentGroup.points.push(x, y);
|
||||||
|
currentGroup.endIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevX = x;
|
||||||
|
prevY = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}, [stitches, pesData]);
|
||||||
|
|
||||||
|
// Dynamic grouping - adds completion status based on currentStitchIndex
|
||||||
|
// Only needs to check group boundaries, not rebuild everything
|
||||||
const stitchGroups = useMemo(() => {
|
const stitchGroups = useMemo(() => {
|
||||||
interface StitchGroup {
|
interface StitchGroup {
|
||||||
color: string;
|
color: string;
|
||||||
|
|
@ -166,53 +237,75 @@ export const Stitches = memo(
|
||||||
}
|
}
|
||||||
|
|
||||||
const groups: StitchGroup[] = [];
|
const groups: StitchGroup[] = [];
|
||||||
let currentGroup: StitchGroup | null = null;
|
|
||||||
|
|
||||||
let prevX = 0;
|
for (const staticGroup of staticGroups) {
|
||||||
let prevY = 0;
|
// Check if this group needs to be split based on completion
|
||||||
|
|
||||||
for (let i = 0; i < stitches.length; i++) {
|
|
||||||
const stitch = stitches[i];
|
|
||||||
const [x, y, cmd, colorIndex] = stitch;
|
|
||||||
const isCompleted = i < currentStitchIndex;
|
|
||||||
const isJump = (cmd & MOVE) !== 0;
|
|
||||||
const color = getThreadColor(pesData, colorIndex);
|
|
||||||
|
|
||||||
// Start new group if color/status/type changes
|
|
||||||
if (
|
if (
|
||||||
!currentGroup ||
|
currentStitchIndex > staticGroup.startIndex &&
|
||||||
currentGroup.color !== color ||
|
currentStitchIndex <= staticGroup.endIndex
|
||||||
currentGroup.completed !== isCompleted ||
|
|
||||||
currentGroup.isJump !== isJump
|
|
||||||
) {
|
) {
|
||||||
// For jump stitches, we need to create a line from previous position to current position
|
// Group is partially completed - need to split
|
||||||
// So we include both the previous point and current point
|
// This is rare during sewing (only happens when crossing group boundaries)
|
||||||
if (isJump && i > 0) {
|
|
||||||
currentGroup = {
|
|
||||||
color,
|
|
||||||
points: [prevX, prevY, x, y],
|
|
||||||
completed: isCompleted,
|
|
||||||
isJump,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
currentGroup = {
|
|
||||||
color,
|
|
||||||
points: [x, y],
|
|
||||||
completed: isCompleted,
|
|
||||||
isJump,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
groups.push(currentGroup);
|
|
||||||
} else {
|
|
||||||
currentGroup.points.push(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
prevX = x;
|
// Rebuild this group with completion split
|
||||||
prevY = y;
|
let currentSubGroup: StitchGroup | null = null;
|
||||||
|
const groupStitches = stitches.slice(
|
||||||
|
staticGroup.startIndex,
|
||||||
|
staticGroup.endIndex + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
let prevX =
|
||||||
|
staticGroup.startIndex > 0
|
||||||
|
? stitches[staticGroup.startIndex - 1][0]
|
||||||
|
: 0;
|
||||||
|
let prevY =
|
||||||
|
staticGroup.startIndex > 0
|
||||||
|
? stitches[staticGroup.startIndex - 1][1]
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < groupStitches.length; i++) {
|
||||||
|
const absoluteIndex = staticGroup.startIndex + i;
|
||||||
|
const stitch = groupStitches[i];
|
||||||
|
const [x, y] = stitch;
|
||||||
|
const isCompleted = absoluteIndex < currentStitchIndex;
|
||||||
|
|
||||||
|
if (!currentSubGroup || currentSubGroup.completed !== isCompleted) {
|
||||||
|
if (staticGroup.isJump && i > 0) {
|
||||||
|
currentSubGroup = {
|
||||||
|
color: staticGroup.color,
|
||||||
|
points: [prevX, prevY, x, y],
|
||||||
|
completed: isCompleted,
|
||||||
|
isJump: staticGroup.isJump,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
currentSubGroup = {
|
||||||
|
color: staticGroup.color,
|
||||||
|
points: [x, y],
|
||||||
|
completed: isCompleted,
|
||||||
|
isJump: staticGroup.isJump,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
groups.push(currentSubGroup);
|
||||||
|
} else {
|
||||||
|
currentSubGroup.points.push(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
prevX = x;
|
||||||
|
prevY = y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Group is fully completed or fully incomplete
|
||||||
|
groups.push({
|
||||||
|
color: staticGroup.color,
|
||||||
|
points: staticGroup.points,
|
||||||
|
completed: currentStitchIndex > staticGroup.endIndex,
|
||||||
|
isJump: staticGroup.isJump,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
}, [stitches, pesData, currentStitchIndex]);
|
}, [staticGroups, currentStitchIndex, stitches]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group name="stitches">
|
<Group name="stitches">
|
||||||
|
|
@ -239,6 +332,16 @@ export const Stitches = memo(
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
// Custom comparison to prevent unnecessary re-renders
|
||||||
|
(prevProps, nextProps) => {
|
||||||
|
// Re-render only if these values actually changed
|
||||||
|
return (
|
||||||
|
prevProps.stitches === nextProps.stitches &&
|
||||||
|
prevProps.pesData === nextProps.pesData &&
|
||||||
|
prevProps.currentStitchIndex === nextProps.currentStitchIndex &&
|
||||||
|
prevProps.showProgress === nextProps.showProgress
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Stitches.displayName = "Stitches";
|
Stitches.displayName = "Stitches";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue