|
|
@@ -105,6 +105,11 @@ export default {
|
|
|
animFrameId: null,
|
|
|
isSimulating: false,
|
|
|
|
|
|
+ // 动能收敛检测(布局收敛后自动停止,避免永续占用主线程)
|
|
|
+ stillFrames: 0, // 连续低动能帧计数
|
|
|
+ energyThreshold: 2, // 总动能阈值(各节点速度平方和)
|
|
|
+ stillFrameLimit: 30, // 连续 30 帧低动能(约 0.5s)→ 停止
|
|
|
+
|
|
|
// 主题色
|
|
|
themeColors: {
|
|
|
body: '#8D6E63',
|
|
|
@@ -281,6 +286,7 @@ export default {
|
|
|
startSimulation: function() {
|
|
|
if (this.isSimulating) return
|
|
|
this.isSimulating = true
|
|
|
+ this.stillFrames = 0
|
|
|
this.simulationTick()
|
|
|
},
|
|
|
|
|
|
@@ -370,6 +376,7 @@ simulationTick: function() {
|
|
|
}
|
|
|
|
|
|
// 5. 更新速度 + 位置
|
|
|
+ var totalEnergy = 0
|
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
|
var node = nodes[i]
|
|
|
if (node.fixed) continue
|
|
|
@@ -410,6 +417,9 @@ simulationTick: function() {
|
|
|
node.x += node.vx
|
|
|
node.y += node.vy
|
|
|
|
|
|
+ // 累计动能(用于收敛检测)
|
|
|
+ totalEnergy += node.vx * node.vx + node.vy * node.vy
|
|
|
+
|
|
|
// 边界约束
|
|
|
var r = node.radius + 4
|
|
|
if (node.x < r) { node.x = r; node.vx *= -0.5 }
|
|
|
@@ -418,6 +428,17 @@ simulationTick: function() {
|
|
|
if (node.y > this.canvasHeight - r) { node.y = this.canvasHeight - r; node.vy *= -0.5 }
|
|
|
}
|
|
|
|
|
|
+ // 5.5 动能收敛检测:布局基本稳定后自动停止,避免永续占用主线程(模拟器会判定长时间无响应)
|
|
|
+ if (totalEnergy < this.energyThreshold) {
|
|
|
+ this.stillFrames++
|
|
|
+ if (this.stillFrames >= this.stillFrameLimit) {
|
|
|
+ this.stopSimulation()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.stillFrames = 0
|
|
|
+ }
|
|
|
+
|
|
|
// 6. 渲染
|
|
|
this.render()
|
|
|
|
|
|
@@ -766,7 +787,10 @@ simulationTick: function() {
|
|
|
// 自身节点 → 显示 + 号
|
|
|
self.touchingSelf = true
|
|
|
} else {
|
|
|
- // 其他节点 → 拖拽
|
|
|
+ // 其他节点 → 拖拽(若模拟已因收敛停止,则唤醒)
|
|
|
+ if (!self.isSimulating) {
|
|
|
+ self.startSimulation()
|
|
|
+ }
|
|
|
self.dragging = {
|
|
|
node: node,
|
|
|
offsetX: x - node.x,
|