|
1 | 1 | import * as acorn from 'acorn'; |
2 | 2 | import * as walk from 'acorn-walk'; |
3 | | -import escodegen from 'escodegen'; |
4 | 3 | import { hasNoProtect } from '../IDE/utils/loopProtection'; |
5 | 4 |
|
6 | 5 | const LOOP_TIMEOUT_MS = 100; |
@@ -31,81 +30,11 @@ function collectShaderFunctionNames(ast) { |
31 | 30 | } |
32 | 31 |
|
33 | 32 | function makeVarDecl(varName) { |
34 | | - return { |
35 | | - type: 'VariableDeclaration', |
36 | | - kind: 'var', |
37 | | - declarations: [ |
38 | | - { |
39 | | - type: 'VariableDeclarator', |
40 | | - id: { type: 'Identifier', name: varName }, |
41 | | - init: { |
42 | | - type: 'CallExpression', |
43 | | - callee: { |
44 | | - type: 'MemberExpression', |
45 | | - object: { type: 'Identifier', name: 'Date' }, |
46 | | - property: { type: 'Identifier', name: 'now' }, |
47 | | - computed: false |
48 | | - }, |
49 | | - arguments: [] |
50 | | - } |
51 | | - } |
52 | | - ] |
53 | | - }; |
| 33 | + return `var ${varName} = Date.now(); `; |
54 | 34 | } |
55 | 35 |
|
56 | 36 | function makeCheckStatement(varName, line) { |
57 | | - return { |
58 | | - type: 'IfStatement', |
59 | | - test: { |
60 | | - type: 'BinaryExpression', |
61 | | - operator: '>', |
62 | | - left: { |
63 | | - type: 'BinaryExpression', |
64 | | - operator: '-', |
65 | | - left: { |
66 | | - type: 'CallExpression', |
67 | | - callee: { |
68 | | - type: 'MemberExpression', |
69 | | - object: { type: 'Identifier', name: 'Date' }, |
70 | | - property: { type: 'Identifier', name: 'now' }, |
71 | | - computed: false |
72 | | - }, |
73 | | - arguments: [] |
74 | | - }, |
75 | | - right: { type: 'Identifier', name: varName } |
76 | | - }, |
77 | | - right: { |
78 | | - type: 'Literal', |
79 | | - value: LOOP_TIMEOUT_MS, |
80 | | - raw: String(LOOP_TIMEOUT_MS) |
81 | | - } |
82 | | - }, |
83 | | - consequent: { |
84 | | - type: 'BlockStatement', |
85 | | - body: [ |
86 | | - { |
87 | | - type: 'ExpressionStatement', |
88 | | - expression: { |
89 | | - type: 'CallExpression', |
90 | | - callee: { |
91 | | - type: 'MemberExpression', |
92 | | - object: { |
93 | | - type: 'MemberExpression', |
94 | | - object: { type: 'Identifier', name: 'window' }, |
95 | | - property: { type: 'Identifier', name: 'loopProtect' }, |
96 | | - computed: false |
97 | | - }, |
98 | | - property: { type: 'Identifier', name: 'hit' }, |
99 | | - computed: false |
100 | | - }, |
101 | | - arguments: [{ type: 'Literal', value: line, raw: String(line) }] |
102 | | - } |
103 | | - }, |
104 | | - { type: 'BreakStatement', label: null } |
105 | | - ] |
106 | | - }, |
107 | | - alternate: null |
108 | | - }; |
| 37 | + return `if (Date.now() - ${varName} > ${LOOP_TIMEOUT_MS}) { window.loopProtect.hit(${line}); break; }`; |
109 | 38 | } |
110 | 39 |
|
111 | 40 | function collectLoopsToProtect(ast, shaderNames) { |
@@ -167,26 +96,34 @@ function collectLoopsToProtect(ast, shaderNames) { |
167 | 96 | return loops; |
168 | 97 | } |
169 | 98 |
|
170 | | -function injectProtection(loops) { |
| 99 | +// Insert protection without changing the original line layout. |
| 100 | +function injectProtection(jsText, loops) { |
| 101 | + const edits = []; |
| 102 | + |
171 | 103 | loops.forEach(({ loop, parentBlock }, idx) => { |
172 | 104 | const varName = `_LP${idx}`; |
173 | 105 | const { line } = loop.loc.start; |
174 | 106 | const check = makeCheckStatement(varName, line); |
175 | 107 |
|
176 | 108 | if (loop.body.type === 'BlockStatement') { |
177 | | - loop.body.body.unshift(check); |
| 109 | + edits.push({ pos: loop.body.start + 1, rank: 1, text: ` ${check}` }); |
178 | 110 | } else { |
179 | | - loop.body = { type: 'BlockStatement', body: [check, loop.body] }; |
| 111 | + edits.push({ pos: loop.body.start, rank: 1, text: `{ ${check} ` }); |
| 112 | + edits.push({ pos: loop.body.end, rank: 0, text: ' }' }); |
180 | 113 | } |
181 | 114 |
|
182 | | - if (parentBlock) { |
183 | | - const varDecl = makeVarDecl(varName); |
184 | | - const nodeIdx = parentBlock.body.indexOf(loop); |
185 | | - if (nodeIdx !== -1) { |
186 | | - parentBlock.body.splice(nodeIdx, 0, varDecl); |
187 | | - } |
| 115 | + if (parentBlock && parentBlock.body.includes(loop)) { |
| 116 | + edits.push({ pos: loop.start, rank: 2, text: makeVarDecl(varName) }); |
188 | 117 | } |
189 | 118 | }); |
| 119 | + |
| 120 | + // Apply edits from the end so earlier offsets remain valid. |
| 121 | + edits.sort((a, b) => b.pos - a.pos || b.rank - a.rank); |
| 122 | + |
| 123 | + return edits.reduce( |
| 124 | + (result, { pos, text }) => result.slice(0, pos) + text + result.slice(pos), |
| 125 | + jsText |
| 126 | + ); |
190 | 127 | } |
191 | 128 |
|
192 | 129 | function parseJs(jsText) { |
@@ -215,7 +152,5 @@ export function jsPreprocess(jsText, indexSrc) { |
215 | 152 |
|
216 | 153 | if (loops.length === 0) return jsText; |
217 | 154 |
|
218 | | - injectProtection(loops); |
219 | | - |
220 | | - return escodegen.generate(ast); |
| 155 | + return injectProtection(jsText, loops); |
221 | 156 | } |
0 commit comments