886 lines
23 KiB
Dart
886 lines
23 KiB
Dart
/**
|
||
* 渲染优化方案 - 实现代码
|
||
*
|
||
* 优化策略:
|
||
* 1. CustomPainter 性能调优
|
||
* 2. 分层渲染(静态/动态分离)
|
||
* 3. GPU 加速利用
|
||
*
|
||
* @version 1.0.0
|
||
* @date 2026-03-07
|
||
* @author 性能优化专家
|
||
*/
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'dart:ui' as ui;
|
||
import '../domain/models/core_models.dart';
|
||
import '../domain/managers/selection_manager.dart';
|
||
|
||
// ============================================================================
|
||
// 1. CustomPainter 性能调优
|
||
// ============================================================================
|
||
|
||
/// 渲染层类型
|
||
enum RenderLayerType {
|
||
staticLayer, // 静态层(网格、背景)
|
||
semiStatic, // 半静态层(元件,不常变化)
|
||
dynamicLayer, // 动态层(拖拽、连线、选择框)
|
||
}
|
||
|
||
/// 渲染层配置
|
||
class RenderLayerConfig {
|
||
final RenderLayerType type;
|
||
final bool shouldCache;
|
||
final Duration cacheExpiry;
|
||
final int maxCacheSize;
|
||
|
||
const RenderLayerConfig({
|
||
required this.type,
|
||
this.shouldCache = true,
|
||
this.cacheExpiry = const Duration(seconds: 5),
|
||
this.maxCacheSize = 3,
|
||
});
|
||
|
||
factory RenderLayerConfig.static() => const RenderLayerConfig(
|
||
type: RenderLayerType.staticLayer,
|
||
shouldCache: true,
|
||
);
|
||
|
||
factory RenderLayerConfig.semiStatic() => const RenderLayerConfig(
|
||
type: RenderLayerType.semiStatic,
|
||
shouldCache: true,
|
||
cacheExpiry: Duration(seconds: 2),
|
||
);
|
||
|
||
factory RenderLayerConfig.dynamic() => const RenderLayerConfig(
|
||
type: RenderLayerType.dynamicLayer,
|
||
shouldCache: false,
|
||
);
|
||
}
|
||
|
||
/// 优化的 CustomPainter 基类
|
||
abstract class OptimizedCustomPainter extends CustomPainter {
|
||
bool _isCached = false;
|
||
ui.Picture? _cachedPicture;
|
||
Size? _cachedSize;
|
||
DateTime? _cachedAt;
|
||
final RenderLayerConfig _config;
|
||
|
||
OptimizedCustomPainter({RenderLayerConfig? config})
|
||
: _config = config ?? const RenderLayerConfig.dynamic();
|
||
|
||
@override
|
||
void paint(Canvas canvas, Size size) {
|
||
if (_config.shouldCache && _isCached && _cachedPicture != null) {
|
||
// 检查缓存是否过期
|
||
if (_cachedAt != null &&
|
||
DateTime.now().difference(_cachedAt!) > _config.cacheExpiry) {
|
||
_invalidateCache();
|
||
}
|
||
// 检查尺寸是否变化
|
||
else if (_cachedSize == size) {
|
||
canvas.drawPicture(_cachedPicture!);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 录制绘制命令
|
||
final recorder = ui.PictureRecorder();
|
||
final recordingCanvas = Canvas(recorder);
|
||
|
||
paintContent(recordingCanvas, size);
|
||
|
||
if (_config.shouldCache) {
|
||
_cachedPicture = recorder.endRecording();
|
||
_cachedSize = size;
|
||
_cachedAt = DateTime.now();
|
||
_isCached = true;
|
||
}
|
||
|
||
// 绘制到实际画布
|
||
if (_cachedPicture != null) {
|
||
canvas.drawPicture(_cachedPicture!);
|
||
}
|
||
}
|
||
|
||
/// 子类实现绘制内容
|
||
void paintContent(Canvas canvas, Size size);
|
||
|
||
/// 使缓存失效
|
||
void _invalidateCache() {
|
||
_cachedPicture?.dispose();
|
||
_cachedPicture = null;
|
||
_isCached = false;
|
||
_cachedSize = null;
|
||
_cachedAt = null;
|
||
}
|
||
|
||
/// 公开的重绘方法
|
||
void markNeedsRepaint() {
|
||
_invalidateCache();
|
||
notifyListeners();
|
||
}
|
||
|
||
@override
|
||
bool shouldRepaint(covariant OptimizedCustomPainter oldDelegate) {
|
||
// 默认实现:总是重绘
|
||
return true;
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_cachedPicture?.dispose();
|
||
_cachedPicture = null;
|
||
super.dispose();
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 2. 分层渲染(静态/动态分离)
|
||
// ============================================================================
|
||
|
||
/// 分层渲染器
|
||
class LayeredRenderer extends StatefulWidget {
|
||
final Design design;
|
||
final double zoomLevel;
|
||
final Offset offset;
|
||
final SelectionManager selectionManager;
|
||
final WiringState? wiringState;
|
||
final Rect? selectionRect;
|
||
final Component? hoveredComponent;
|
||
final PinReference? hoveredPin;
|
||
|
||
const LayeredRenderer({
|
||
super.key,
|
||
required this.design,
|
||
required this.zoomLevel,
|
||
required this.offset,
|
||
required this.selectionManager,
|
||
this.wiringState,
|
||
this.selectionRect,
|
||
this.hoveredComponent,
|
||
this.hoveredPin,
|
||
});
|
||
|
||
@override
|
||
State<LayeredRenderer> createState() => _LayeredRendererState();
|
||
}
|
||
|
||
class _LayeredRendererState extends State<LayeredRenderer> {
|
||
// 静态层控制器
|
||
late StaticLayerPainter _staticLayer;
|
||
|
||
// 半静态层控制器
|
||
late SemiStaticLayerPainter _semiStaticLayer;
|
||
|
||
// 动态层控制器
|
||
late DynamicLayerPainter _dynamicLayer;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_staticLayer = StaticLayerPainter();
|
||
_semiStaticLayer = SemiStaticLayerPainter();
|
||
_dynamicLayer = DynamicLayerPainter();
|
||
|
||
// 添加监听器
|
||
_staticLayer.addListener(_onLayerChanged);
|
||
_semiStaticLayer.addListener(_onLayerChanged);
|
||
_dynamicLayer.addListener(_onLayerChanged);
|
||
}
|
||
|
||
@override
|
||
void didUpdateWidget(LayeredRenderer oldWidget) {
|
||
super.didUpdateWidget(oldWidget);
|
||
|
||
// 检查哪些层需要更新
|
||
bool staticChanged = false;
|
||
bool semiStaticChanged = false;
|
||
bool dynamicChanged = false;
|
||
|
||
// 网格、背景等静态元素通常不变
|
||
// 这里可以检查是否需要更新
|
||
|
||
// 元件变化 → 半静态层
|
||
if (widget.design.components != oldWidget.design.components ||
|
||
widget.design.nets != oldWidget.design.nets) {
|
||
semiStaticChanged = true;
|
||
}
|
||
|
||
// 交互状态变化 → 动态层
|
||
if (widget.wiringState != oldWidget.wiringState ||
|
||
widget.selectionRect != oldWidget.selectionRect ||
|
||
widget.hoveredComponent != oldWidget.hoveredComponent ||
|
||
widget.hoveredPin != oldWidget.hoveredPin ||
|
||
widget.selectionManager.selectedObjects !=
|
||
oldWidget.selectionManager.selectedObjects) {
|
||
dynamicChanged = true;
|
||
}
|
||
|
||
// 更新各层数据
|
||
if (semiStaticChanged) {
|
||
_semiStaticLayer.updateData(
|
||
design: widget.design,
|
||
zoomLevel: widget.zoomLevel,
|
||
offset: widget.offset,
|
||
);
|
||
}
|
||
|
||
if (dynamicChanged) {
|
||
_dynamicLayer.updateData(
|
||
selectionManager: widget.selectionManager,
|
||
wiringState: widget.wiringState,
|
||
selectionRect: widget.selectionRect,
|
||
hoveredComponent: widget.hoveredComponent,
|
||
hoveredPin: widget.hoveredPin,
|
||
);
|
||
}
|
||
|
||
// 标记需要重绘的层
|
||
if (semiStaticChanged) _semiStaticLayer.markNeedsRepaint();
|
||
if (dynamicChanged) _dynamicLayer.markNeedsRepaint();
|
||
}
|
||
|
||
void _onLayerChanged() {
|
||
setState(() {
|
||
// 触发重建
|
||
});
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Stack(
|
||
children: [
|
||
// 静态层(最底层)
|
||
CustomPaint(
|
||
painter: _staticLayer,
|
||
size: Size.infinite,
|
||
),
|
||
|
||
// 半静态层(中间层)
|
||
CustomPaint(
|
||
painter: _semiStaticLayer,
|
||
size: Size.infinite,
|
||
),
|
||
|
||
// 动态层(最上层)
|
||
CustomPaint(
|
||
painter: _dynamicLayer,
|
||
size: Size.infinite,
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_staticLayer.removeListener(_onLayerChanged);
|
||
_semiStaticLayer.removeListener(_onLayerChanged);
|
||
_dynamicLayer.removeListener(_onLayerChanged);
|
||
|
||
_staticLayer.dispose();
|
||
_semiStaticLayer.dispose();
|
||
_dynamicLayer.dispose();
|
||
|
||
super.dispose();
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 2.1 静态层绘制器(网格、背景)
|
||
// ============================================================================
|
||
|
||
class StaticLayerPainter extends OptimizedCustomPainter {
|
||
double zoomLevel = 1.0;
|
||
Offset offset = Offset.zero;
|
||
static const double gridSize = 20.0;
|
||
|
||
StaticLayerPainter()
|
||
: super(config: const RenderLayerConfig.static());
|
||
|
||
void updateData({required double zoom, required Offset offset}) {
|
||
zoomLevel = zoom;
|
||
this.offset = offset;
|
||
}
|
||
|
||
@override
|
||
void paintContent(Canvas canvas, Size size) {
|
||
_drawBackground(canvas, size);
|
||
_drawGrid(canvas, size);
|
||
}
|
||
|
||
void _drawBackground(Canvas canvas, Size size) {
|
||
final paint = Paint()..color = const Color(0xFFFAFAFA);
|
||
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
|
||
}
|
||
|
||
void _drawGrid(Canvas canvas, Size size) {
|
||
final paint = Paint()
|
||
..color = const Color(0xFFE0E0E0)
|
||
..strokeWidth = 0.5;
|
||
|
||
final startX = (-offset.dx / zoomLevel);
|
||
final startY = (-offset.dy / zoomLevel);
|
||
final endX = startX + size.width / zoomLevel;
|
||
final endY = startY + size.height / zoomLevel;
|
||
|
||
// 批量绘制垂直线
|
||
final verticalPoints = <Offset>[];
|
||
for (double x = (startX / gridSize).floor() * gridSize; x < endX; x += gridSize) {
|
||
verticalPoints.add(Offset(x, startY));
|
||
verticalPoints.add(Offset(x, endY));
|
||
}
|
||
if (verticalPoints.isNotEmpty) {
|
||
canvas.drawLines(
|
||
ui.PointsMode.pairs,
|
||
verticalPoints,
|
||
paint,
|
||
);
|
||
}
|
||
|
||
// 批量绘制水平线
|
||
final horizontalPoints = <Offset>[];
|
||
for (double y = (startY / gridSize).floor() * gridSize; y < endY; y += gridSize) {
|
||
horizontalPoints.add(Offset(startX, y));
|
||
horizontalPoints.add(Offset(endX, y));
|
||
}
|
||
if (horizontalPoints.isNotEmpty) {
|
||
canvas.drawLines(
|
||
ui.PointsMode.pairs,
|
||
horizontalPoints,
|
||
paint,
|
||
);
|
||
}
|
||
}
|
||
|
||
@override
|
||
bool shouldRepaint(covariant StaticLayerPainter oldDelegate) {
|
||
return oldDelegate.zoomLevel != zoomLevel || oldDelegate.offset != offset;
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 2.2 半静态层绘制器(元件、网络)
|
||
// ============================================================================
|
||
|
||
class SemiStaticLayerPainter extends OptimizedCustomPainter {
|
||
Design? design;
|
||
double zoomLevel = 1.0;
|
||
Offset offset = Offset.zero;
|
||
|
||
// 预计算的绘制数据
|
||
final List<_PrecomputedComponent> _precomputedComponents = [];
|
||
final List<_PrecomputedNet> _precomputedNets = [];
|
||
|
||
SemiStaticLayerPainter()
|
||
: super(config: const RenderLayerConfig.semiStatic());
|
||
|
||
void updateData({
|
||
required Design design,
|
||
required double zoomLevel,
|
||
required Offset offset,
|
||
}) {
|
||
this.design = design;
|
||
this.zoomLevel = zoomLevel;
|
||
this.offset = offset;
|
||
|
||
// 预计算元件数据
|
||
_precomputeComponents();
|
||
_precomputeNets();
|
||
}
|
||
|
||
void _precomputeComponents() {
|
||
_precomputedComponents.clear();
|
||
if (design == null) return;
|
||
|
||
for (final component in design!.components.values) {
|
||
_precomputedComponents.add(_PrecomputedComponent(
|
||
id: component.id,
|
||
position: Offset(
|
||
component.position.x.toDouble(),
|
||
component.position.y.toDouble(),
|
||
),
|
||
rotation: component.rotation,
|
||
name: component.name,
|
||
pins: component.pins
|
||
.map((pin) => Offset(
|
||
(component.position.x + pin.x).toDouble(),
|
||
(component.position.y + pin.y).toDouble(),
|
||
))
|
||
.toList(),
|
||
));
|
||
}
|
||
}
|
||
|
||
void _precomputeNets() {
|
||
_precomputedNets.clear();
|
||
if (design == null) return;
|
||
|
||
for (final net in design!.nets.values) {
|
||
if (net.connections.length < 2) continue;
|
||
|
||
final points = <Offset>[];
|
||
for (final conn in net.connections) {
|
||
if (conn.position != null) {
|
||
points.add(Offset(
|
||
conn.position!.x.toDouble(),
|
||
conn.position!.y.toDouble(),
|
||
));
|
||
}
|
||
}
|
||
|
||
if (points.length >= 2) {
|
||
_precomputedNets.add(_PrecomputedNet(
|
||
id: net.id,
|
||
points: points,
|
||
));
|
||
}
|
||
}
|
||
}
|
||
|
||
@override
|
||
void paintContent(Canvas canvas, Size size) {
|
||
// 应用变换
|
||
canvas.save();
|
||
canvas.translate(offset.dx, offset.dy);
|
||
canvas.scale(zoomLevel);
|
||
|
||
// 绘制网络
|
||
_drawNets(canvas);
|
||
|
||
// 绘制元件
|
||
_drawComponents(canvas);
|
||
|
||
canvas.restore();
|
||
}
|
||
|
||
void _drawComponents(Canvas canvas) {
|
||
final bodyPaint = Paint()
|
||
..color = const Color(0xFF333333)
|
||
..style = PaintingStyle.stroke
|
||
..strokeWidth = 2.0;
|
||
|
||
final fillPaint = Paint()
|
||
..color = const Color(0xFFFFFFFF).withOpacity(0.1)
|
||
..style = PaintingStyle.fill;
|
||
|
||
const size = 40.0;
|
||
|
||
for (final comp in _precomputedComponents) {
|
||
// 绘制元件主体
|
||
final rect = Rect.fromLTWH(
|
||
comp.position.dx - size / 2,
|
||
comp.position.dy - size / 2,
|
||
size,
|
||
size,
|
||
);
|
||
|
||
canvas.drawRect(rect, fillPaint);
|
||
canvas.drawRect(rect, bodyPaint);
|
||
|
||
// 绘制引脚
|
||
final pinPaint = Paint()
|
||
..color = const Color(0xFF333333)
|
||
..style = PaintingStyle.fill;
|
||
|
||
for (final pinPos in comp.pins) {
|
||
canvas.drawCircle(pinPos, 4.0, pinPaint);
|
||
}
|
||
}
|
||
}
|
||
|
||
void _drawNets(Canvas canvas) {
|
||
final paint = Paint()
|
||
..color = const Color(0xFF4CAF50)
|
||
..strokeWidth = 2.0
|
||
..style = PaintingStyle.stroke;
|
||
|
||
for (final net in _precomputedNets) {
|
||
for (int i = 0; i < net.points.length - 1; i++) {
|
||
canvas.drawLine(net.points[i], net.points[i + 1], paint);
|
||
}
|
||
}
|
||
}
|
||
|
||
@override
|
||
bool shouldRepaint(covariant SemiStaticLayerPainter oldDelegate) {
|
||
return oldDelegate.design != design ||
|
||
oldDelegate.zoomLevel != zoomLevel ||
|
||
oldDelegate.offset != offset;
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 2.3 动态层绘制器(交互元素)
|
||
// ============================================================================
|
||
|
||
class DynamicLayerPainter extends OptimizedCustomPainter {
|
||
SelectionManager? selectionManager;
|
||
WiringState? wiringState;
|
||
Rect? selectionRect;
|
||
Component? hoveredComponent;
|
||
PinReference? hoveredPin;
|
||
|
||
DynamicLayerPainter()
|
||
: super(config: const RenderLayerConfig.dynamic());
|
||
|
||
void updateData({
|
||
required SelectionManager selectionManager,
|
||
required WiringState? wiringState,
|
||
required Rect? selectionRect,
|
||
required Component? hoveredComponent,
|
||
required PinReference? hoveredPin,
|
||
}) {
|
||
this.selectionManager = selectionManager;
|
||
this.wiringState = wiringState;
|
||
this.selectionRect = selectionRect;
|
||
this.hoveredComponent = hoveredComponent;
|
||
this.hoveredPin = hoveredPin;
|
||
}
|
||
|
||
@override
|
||
void paintContent(Canvas canvas, Size size) {
|
||
// 绘制悬停高亮
|
||
if (hoveredComponent != null) {
|
||
_drawHoverHighlight(canvas, hoveredComponent!);
|
||
}
|
||
|
||
// 绘制选中框
|
||
if (selectionRect != null) {
|
||
_drawSelectionRect(canvas);
|
||
}
|
||
|
||
// 绘制连线
|
||
if (wiringState != null) {
|
||
_drawWiringLine(canvas);
|
||
}
|
||
}
|
||
|
||
void _drawHoverHighlight(Canvas canvas, Component component) {
|
||
final paint = Paint()
|
||
..color = const Color(0xFF64B5F6)
|
||
..style = PaintingStyle.stroke
|
||
..strokeWidth = 3.0;
|
||
|
||
const size = 40.0;
|
||
final rect = Rect.fromLTWH(
|
||
component.position.x.toDouble() - size / 2,
|
||
component.position.y.toDouble() - size / 2,
|
||
size,
|
||
size,
|
||
);
|
||
|
||
canvas.drawRect(rect, paint);
|
||
}
|
||
|
||
void _drawSelectionRect(Canvas canvas) {
|
||
if (selectionRect == null) return;
|
||
|
||
final fillPaint = Paint()
|
||
..color = const Color(0xFF2196F3).withOpacity(0.2)
|
||
..style = PaintingStyle.fill;
|
||
|
||
final borderPaint = Paint()
|
||
..color = const Color(0xFF2196F3)
|
||
..strokeWidth = 1.0
|
||
..style = PaintingStyle.stroke;
|
||
|
||
canvas.drawRect(selectionRect!, fillPaint);
|
||
canvas.drawRect(selectionRect!, borderPaint);
|
||
}
|
||
|
||
void _drawWiringLine(Canvas canvas) {
|
||
if (wiringState == null) return;
|
||
|
||
final paint = Paint()
|
||
..color = const Color(0xFFFF9800)
|
||
..strokeWidth = 2.0
|
||
..style = PaintingStyle.stroke;
|
||
|
||
final start = wiringState!.startPoint;
|
||
if (start.position != null) {
|
||
canvas.drawLine(
|
||
Offset(start.position!.x.toDouble(), start.position!.y.toDouble()),
|
||
wiringState!.currentPoint,
|
||
paint,
|
||
);
|
||
}
|
||
}
|
||
|
||
@override
|
||
bool shouldRepaint(covariant DynamicLayerPainter oldDelegate) {
|
||
return oldDelegate.wiringState != wiringState ||
|
||
oldDelegate.selectionRect != selectionRect ||
|
||
oldDelegate.hoveredComponent != hoveredComponent;
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 3. GPU 加速利用
|
||
// ============================================================================
|
||
|
||
/// GPU 加速的元件渲染器
|
||
class GPUAcceleratedRenderer {
|
||
/// 使用 PictureRecorder 批量录制绘制命令
|
||
static ui.Picture batchRenderComponents(
|
||
List<Component> components,
|
||
Size canvasSize,
|
||
) {
|
||
final recorder = ui.PictureRecorder();
|
||
final canvas = Canvas(recorder);
|
||
|
||
final bodyPaint = Paint()
|
||
..color = const Color(0xFF333333)
|
||
..style = PaintingStyle.stroke
|
||
..strokeWidth = 2.0;
|
||
|
||
const size = 40.0;
|
||
|
||
// 批量绘制所有元件
|
||
for (final component in components) {
|
||
final rect = Rect.fromLTWH(
|
||
component.position.x.toDouble() - size / 2,
|
||
component.position.y.toDouble() - size / 2,
|
||
size,
|
||
size,
|
||
);
|
||
|
||
canvas.drawRect(rect, bodyPaint);
|
||
|
||
// 批量绘制引脚
|
||
for (final pin in component.pins) {
|
||
final pinX = (component.position.x + pin.x).toDouble();
|
||
final pinY = (component.position.y + pin.y).toDouble();
|
||
canvas.drawCircle(Offset(pinX, pinY), 4.0, bodyPaint);
|
||
}
|
||
}
|
||
|
||
return recorder.endRecording();
|
||
}
|
||
|
||
/// 使用 Shader 实现渐变效果
|
||
static Shader createGradientShader(Offset start, Offset end) {
|
||
return ui.Gradient.linear(
|
||
start,
|
||
end,
|
||
[
|
||
const Color(0xFF2196F3),
|
||
const Color(0xFF64B5F6),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 使用 ImageFilter 实现模糊效果
|
||
static ui.ImageFilter createBlurFilter(double sigma) {
|
||
return ui.ImageFilter.blur(sigmaX: sigma, sigmaY: sigma);
|
||
}
|
||
|
||
/// 使用 ColorFilter 实现色调调整
|
||
static ui.ColorFilter createColorFilter() {
|
||
return ui.ColorFilter.matrix([
|
||
1, 0, 0, 0, 0,
|
||
0, 1, 0, 0, 0,
|
||
0, 0, 1, 0, 0,
|
||
0, 0, 0, 1, 0,
|
||
]);
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 4. 辅助类
|
||
// ============================================================================
|
||
|
||
/// 预计算的元件数据
|
||
class _PrecomputedComponent {
|
||
final ID id;
|
||
final Offset position;
|
||
final int rotation;
|
||
final String name;
|
||
final List<Offset> pins;
|
||
|
||
_PrecomputedComponent({
|
||
required this.id,
|
||
required this.position,
|
||
required this.rotation,
|
||
required this.name,
|
||
required this.pins,
|
||
});
|
||
}
|
||
|
||
/// 预计算的网络数据
|
||
class _PrecomputedNet {
|
||
final ID id;
|
||
final List<Offset> points;
|
||
|
||
_PrecomputedNet({
|
||
required this.id,
|
||
required this.points,
|
||
});
|
||
}
|
||
|
||
/// 连线状态(与 editable_canvas.dart 保持一致)
|
||
class WiringState {
|
||
final ConnectionPoint startPoint;
|
||
final Offset currentPoint;
|
||
final NetType netType;
|
||
|
||
const WiringState({
|
||
required this.startPoint,
|
||
required this.currentPoint,
|
||
this.netType = NetType.signal,
|
||
});
|
||
}
|
||
|
||
// ============================================================================
|
||
// 5. 性能监控
|
||
// ============================================================================
|
||
|
||
/// 渲染性能监控器
|
||
class RenderPerformanceMonitor {
|
||
final List<int> _frameTimes = [];
|
||
final List<int> _paintTimes = [];
|
||
DateTime? _lastFrameTime;
|
||
|
||
/// 标记帧开始
|
||
void markFrameStart() {
|
||
final now = DateTime.now();
|
||
if (_lastFrameTime != null) {
|
||
final frameTime = now.difference(_lastFrameTime!).inMilliseconds;
|
||
_frameTimes.add(frameTime);
|
||
if (_frameTimes.length > 300) {
|
||
_frameTimes.removeAt(0);
|
||
}
|
||
}
|
||
_lastFrameTime = now;
|
||
}
|
||
|
||
/// 标记绘制时间
|
||
void markPaintTime(int milliseconds) {
|
||
_paintTimes.add(milliseconds);
|
||
if (_paintTimes.length > 100) {
|
||
_paintTimes.removeAt(0);
|
||
}
|
||
}
|
||
|
||
/// 获取平均 FPS
|
||
double get averageFPS {
|
||
if (_frameTimes.isEmpty) return 0.0;
|
||
final avgFrameTime =
|
||
_frameTimes.reduce((a, b) => a + b) / _frameTimes.length;
|
||
return avgFrameTime > 0 ? 1000.0 / avgFrameTime : 0.0;
|
||
}
|
||
|
||
/// 获取平均绘制时间
|
||
int get averagePaintTime {
|
||
if (_paintTimes.isEmpty) return 0;
|
||
return _paintTimes.reduce((a, b) => a + b) ~/ _paintTimes.length;
|
||
}
|
||
|
||
/// 获取性能报告
|
||
PerformanceReport get report => PerformanceReport(
|
||
averageFPS: averageFPS,
|
||
averagePaintTime: averagePaintTime,
|
||
frameTimeP50: _percentile(_frameTimes, 50),
|
||
frameTimeP95: _percentile(_frameTimes, 95),
|
||
frameTimeP99: _percentile(_frameTimes, 99),
|
||
);
|
||
|
||
int _percentile(List<int> sortedData, int percentile) {
|
||
if (sortedData.isEmpty) return 0;
|
||
final sorted = List<int>.from(sortedData)..sort();
|
||
final index = (sorted.length * percentile / 100).floor();
|
||
return sorted[index.clamp(0, sorted.length - 1)];
|
||
}
|
||
|
||
void reset() {
|
||
_frameTimes.clear();
|
||
_paintTimes.clear();
|
||
_lastFrameTime = null;
|
||
}
|
||
}
|
||
|
||
/// 性能报告
|
||
class PerformanceReport {
|
||
final double averageFPS;
|
||
final int averagePaintTime;
|
||
final int frameTimeP50;
|
||
final int frameTimeP95;
|
||
final int frameTimeP99;
|
||
|
||
PerformanceReport({
|
||
required this.averageFPS,
|
||
required this.averagePaintTime,
|
||
required this.frameTimeP50,
|
||
required this.frameTimeP95,
|
||
required this.frameTimeP99,
|
||
});
|
||
|
||
@override
|
||
String toString() {
|
||
return '''
|
||
性能报告:
|
||
平均 FPS: ${averageFPS.toStringAsFixed(1)}
|
||
平均绘制时间:${averagePaintTime}ms
|
||
帧时间 P50: ${frameTimeP50}ms
|
||
帧时间 P95: ${frameTimeP95}ms
|
||
帧时间 P99: ${frameTimeP99}ms
|
||
''';
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 6. 使用示例
|
||
// ============================================================================
|
||
|
||
/*
|
||
/// 在 EditableCanvas 中使用分层渲染
|
||
class OptimizedEditableCanvas extends StatefulWidget {
|
||
final Design design;
|
||
final Function(Design) onDesignChanged;
|
||
final SelectionManager selectionManager;
|
||
|
||
const OptimizedEditableCanvas({
|
||
required this.design,
|
||
required this.onDesignChanged,
|
||
required this.selectionManager,
|
||
});
|
||
|
||
@override
|
||
State<OptimizedEditableCanvas> createState() => _OptimizedEditableCanvasState();
|
||
}
|
||
|
||
class _OptimizedEditableCanvasState extends State<OptimizedEditableCanvas> {
|
||
final RenderPerformanceMonitor _performanceMonitor = RenderPerformanceMonitor();
|
||
double _zoomLevel = 1.0;
|
||
Offset _offset = Offset.zero;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
_performanceMonitor.markFrameStart();
|
||
|
||
return GestureDetector(
|
||
onScaleUpdate: _handleScaleUpdate,
|
||
onPanUpdate: _handlePanUpdate,
|
||
child: LayeredRenderer(
|
||
design: widget.design,
|
||
zoomLevel: _zoomLevel,
|
||
offset: _offset,
|
||
selectionManager: widget.selectionManager,
|
||
),
|
||
);
|
||
}
|
||
|
||
void _handleScaleUpdate(ScaleUpdateDetails details) {
|
||
setState(() {
|
||
_zoomLevel = (_zoomLevel * details.scale).clamp(0.1, 10.0);
|
||
_offset += details.focalPoint - _lastPanPosition!;
|
||
});
|
||
|
||
// 打印性能数据
|
||
if (_performanceMonitor.averageFPS < 30) {
|
||
debugPrint('⚠️ 性能警告:FPS = ${_performanceMonitor.averageFPS.toStringAsFixed(1)}');
|
||
}
|
||
}
|
||
}
|
||
*/
|