rough-native
Version:
Create graphics using HTML Canvas or SVG with a hand-drawn, sketchy, appearance. Features comprehensive React hooks, memory management, and React 18 concurrent rendering support.
255 lines (203 loc) โข 7.36 kB
Markdown
# React 18 Concurrent Rendering Support
This document describes the React 18 concurrent rendering safety features implemented in rough-native's React hooks.
## ๐ Overview
React 18 introduced concurrent rendering features that can cause **tearing** and **inconsistent state** in hooks that manage external state. Our implementation uses `useSyncExternalStore` to ensure visual consistency during concurrent updates.
## โก Key Features
### Concurrent-Safe Instance Management
```typescript
// โ
Concurrent-safe rough instance creation
const rough = useRough(config); // No tearing during config updates
```
### Cached Shape Generation
```typescript
// โ
Concurrent-safe shape caching
const shape = useRoughShape('polygon', points, options); // Consistent snapshots
```
### Batch Processing Safety
```typescript
// โ
Concurrent-safe batch rendering
const shapes = useRoughShapes(shapeDefinitions); // Atomic batch updates
```
## ๐๏ธ Architecture
### RoughInstanceStore
Manages RoughReactNativeSVG instances with concurrent safety:
- **Immutable snapshots** prevent tearing
- **Subscriber pattern** for consistent updates
- **Automatic cleanup** prevents memory leaks
```typescript
class RoughInstanceStore {
private instances = new Map<string, RoughReactNativeSVG>();
private listeners = new Set<() => void>();
getSnapshot(): Map<string, RoughReactNativeSVG> {
// Return immutable snapshot for concurrent safety
return new Map(this.instances);
}
subscribe(callback: () => void): () => void {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
}
```
### ShapeGenerationStore
Manages shape generation with caching and concurrent safety:
- **Cached results** prevent duplicate work
- **Pending generation tracking** avoids race conditions
- **Error caching** prevents retry storms
```typescript
class ShapeGenerationStore {
private cache = new Map<string, any>();
private pendingGenerations = new Set<string>();
generateShape(key: string, generator: () => any): any {
// Check cache first
if (this.cache.has(key)) {
return this.cache.get(key);
}
// Prevent duplicate generation
if (this.pendingGenerations.has(key)) {
return null; // Still generating
}
// Generate and cache result
this.pendingGenerations.add(key);
const result = generator();
this.cache.set(key, result);
this.pendingGenerations.delete(key);
return result;
}
}
```
## ๐ง Concurrent Hook Implementation
### useRough with useSyncExternalStore
```typescript
export function useRough(config?: Config): RoughReactNativeSVG {
const stableConfig = useDeepMemo(config, [config]);
// Concurrent-safe external store subscription
const storeSnapshot = useSyncExternalStore(
roughStore.subscribe.bind(roughStore),
roughStore.getSnapshot.bind(roughStore),
roughStore.getSnapshot.bind(roughStore) // SSR snapshot
);
const rough = useMemo(() => {
if (!instanceIdRef.current) {
instanceIdRef.current = roughStore.createInstance(stableConfig);
} else {
roughStore.updateInstance(instanceIdRef.current, stableConfig);
}
return roughStore.getInstance(instanceIdRef.current);
}, [stableConfig, storeSnapshot]);
return rough;
}
```
### useRoughShape with Concurrent Caching
```typescript
export function useRoughShape<T extends keyof ShapeParams>(
shapeType: T,
params: ShapeParams[T],
options?: Options
) {
const rough = useRough();
const shapeKey = useMemo(() =>
`${shapeType}-${JSON.stringify(params)}-${JSON.stringify(options)}`
, [shapeType, params, options]);
// Concurrent-safe shape generation
const storeSnapshot = useSyncExternalStore(
shapeStore.subscribe.bind(shapeStore),
shapeStore.getSnapshot.bind(shapeStore),
shapeStore.getSnapshot.bind(shapeStore)
);
const shape = useMemo(() => {
return shapeStore.generateShape(shapeKey, () => {
// Shape generation logic
return rough[shapeType](params, options);
});
}, [shapeKey, rough, storeSnapshot]);
return shape || { props: {}, children: [] };
}
```
## ๐ฏ Benefits
### Visual Consistency
- **No tearing** during concurrent updates
- **Consistent snapshots** across component tree
- **Atomic updates** for batch operations
### Performance
- **Cached results** prevent duplicate work
- **Efficient subscriptions** minimize re-renders
- **Smart caching** with automatic cleanup
### Developer Experience
- **Transparent usage** - works like regular hooks
- **Debug utilities** for monitoring performance
- **Error handling** with graceful degradation
## ๐ Debugging Tools
### Performance Monitoring
```typescript
import { debugUtils } from 'rough-native';
// Get cache statistics
const deepEqualStats = debugUtils.getDeepEqualStats();
console.log(`Hit rate: ${deepEqualStats.hitRate * 100}%`);
// Monitor instance count
const instanceCount = debugUtils.getRoughInstanceCount();
console.log(`Active instances: ${instanceCount}`);
// Check shape cache size
const cacheSize = debugUtils.getShapeCacheSize();
console.log(`Cached shapes: ${cacheSize}`);
```
### Cache Management
```typescript
// Clear all caches for testing
debugUtils.clearAllCaches();
// Clear specific caches
debugUtils.clearRoughInstances();
debugUtils.clearShapeCache();
debugUtils.clearDeepEqualCache();
```
## ๐งช Usage Examples
### Concurrent Updates with useTransition
```tsx
import React, { useTransition, startTransition } from 'react';
import { useRoughShape } from 'rough-native';
function ConcurrentShapeDemo() {
const [isPending, startTransition] = useTransition();
const [complexity, setComplexity] = useState(5);
// โ
Concurrent-safe shape generation
const shape = useRoughShape('polygon', generatePoints(complexity));
const handleComplexityChange = () => {
startTransition(() => {
setComplexity(c => c * 2); // Large update marked as transition
});
};
return (
<View>
<Button title="Increase Complexity" onPress={handleComplexityChange} />
{isPending ? <Text>Updating...</Text> : null}
<Svg>{/* Render shape without tearing */}</Svg>
</View>
);
}
```
### Batch Concurrent Processing
```tsx
function ConcurrentBatchDemo() {
const [shapes, setShapes] = useState(initialShapes);
// โ
Concurrent-safe batch processing
const renderedShapes = useRoughShapes(shapes);
const handleBatchUpdate = () => {
startTransition(() => {
setShapes(generateComplexShapes()); // Large batch update
});
};
return <Svg>{/* All shapes render consistently */}</Svg>;
}
```
## ๐ Concurrent Safety Guarantees
### Tearing Prevention
- **Immutable snapshots** ensure visual consistency
- **Synchronized updates** across component tree
- **Atomic state changes** prevent partial updates
### Race Condition Protection
- **Pending generation tracking** prevents duplicate work
- **Cache consistency** during concurrent access
- **Error isolation** prevents cascade failures
### Memory Safety
- **WeakMap usage** allows garbage collection
- **Automatic cleanup** on component unmount
- **Bounded caches** prevent memory bloat
The concurrent rendering implementation ensures that rough-native hooks work seamlessly with React 18's concurrent features while maintaining excellent performance and visual consistency.