moonscratch 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/chunk-DQk6qfdC.mjs +18 -0
  2. package/dist/index.d.mts +1173 -0
  3. package/dist/index.mjs +27135 -0
  4. package/package.json +6 -1
  5. package/.agents/skills/moonbit-agent-guide/LICENSE +0 -202
  6. package/.agents/skills/moonbit-agent-guide/SKILL.mbt.md +0 -1126
  7. package/.agents/skills/moonbit-agent-guide/SKILL.md +0 -1126
  8. package/.agents/skills/moonbit-agent-guide/ide.md +0 -116
  9. package/.agents/skills/moonbit-agent-guide/references/advanced-moonbit-build.md +0 -106
  10. package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.mbt.md +0 -422
  11. package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.md +0 -422
  12. package/.agents/skills/moonbit-practice/SKILL.md +0 -258
  13. package/.agents/skills/moonbit-practice/assets/ci.yaml +0 -25
  14. package/.agents/skills/moonbit-practice/reference/agents.md +0 -1469
  15. package/.agents/skills/moonbit-practice/reference/configuration.md +0 -228
  16. package/.agents/skills/moonbit-practice/reference/ffi.md +0 -229
  17. package/.agents/skills/moonbit-practice/reference/ide.md +0 -189
  18. package/.agents/skills/moonbit-practice/reference/performance.md +0 -217
  19. package/.agents/skills/moonbit-practice/reference/refactor.md +0 -154
  20. package/.agents/skills/moonbit-practice/reference/stdlib.md +0 -351
  21. package/.agents/skills/moonbit-practice/reference/testing.md +0 -228
  22. package/.agents/skills/moonbit-refactoring/LICENSE +0 -21
  23. package/.agents/skills/moonbit-refactoring/SKILL.md +0 -323
  24. package/.githooks/README.md +0 -23
  25. package/.githooks/pre-commit +0 -3
  26. package/.github/workflows/copilot-setup-steps.yml +0 -40
  27. package/AGENTS.md +0 -91
  28. package/PLAN.md +0 -64
  29. package/TODO.md +0 -120
  30. package/benchmarks/calc.bench.ts +0 -144
  31. package/benchmarks/draw.bench.ts +0 -215
  32. package/benchmarks/load.bench.ts +0 -28
  33. package/benchmarks/render.bench.ts +0 -53
  34. package/benchmarks/run.bench.ts +0 -8
  35. package/benchmarks/types.d.ts +0 -15
  36. package/docs/scratch-vm-specs/eventloop.md +0 -103
  37. package/docs/scratch-vm-specs/moonscratch-time-separation.md +0 -50
  38. package/index.html +0 -91
  39. package/js/AGENTS.md +0 -5
  40. package/js/a.ts +0 -52
  41. package/js/assets/AGENTS.md +0 -5
  42. package/js/assets/base64.test.ts +0 -14
  43. package/js/assets/base64.ts +0 -21
  44. package/js/assets/build-asset.test.ts +0 -26
  45. package/js/assets/build-asset.ts +0 -28
  46. package/js/assets/create.test.ts +0 -142
  47. package/js/assets/create.ts +0 -122
  48. package/js/assets/index.test.ts +0 -15
  49. package/js/assets/index.ts +0 -2
  50. package/js/assets/types.ts +0 -26
  51. package/js/assets/validation.test.ts +0 -34
  52. package/js/assets/validation.ts +0 -25
  53. package/js/assets.test.ts +0 -14
  54. package/js/assets.ts +0 -1
  55. package/js/index.test.ts +0 -26
  56. package/js/index.ts +0 -3
  57. package/js/render/index.test.ts +0 -65
  58. package/js/render/index.ts +0 -13
  59. package/js/render/sharp.ts +0 -87
  60. package/js/render/svg.ts +0 -68
  61. package/js/render/types.ts +0 -35
  62. package/js/render/utils.ts +0 -108
  63. package/js/render/webgl.ts +0 -274
  64. package/js/sharp-optional.d.ts +0 -16
  65. package/js/test/helpers.ts +0 -116
  66. package/js/test/hikkaku-sample.test.ts +0 -37
  67. package/js/test/rubik-components.input-motion.test.ts +0 -60
  68. package/js/test/rubik-components.lists.test.ts +0 -49
  69. package/js/test/rubik-components.operators.test.ts +0 -104
  70. package/js/test/rubik-components.pen.test.ts +0 -112
  71. package/js/test/rubik-components.procedures-loops.test.ts +0 -72
  72. package/js/test/rubik-components.variables-branches.test.ts +0 -57
  73. package/js/test/rubik-components.visibility-entry.test.ts +0 -31
  74. package/js/test/test-projects.ts +0 -598
  75. package/js/test/variable.ts +0 -200
  76. package/js/test/warp.test.ts +0 -59
  77. package/js/vm/AGENTS.md +0 -6
  78. package/js/vm/README.md +0 -183
  79. package/js/vm/bindings.test.ts +0 -13
  80. package/js/vm/bindings.ts +0 -5
  81. package/js/vm/compare-operators.test.ts +0 -145
  82. package/js/vm/constants.test.ts +0 -11
  83. package/js/vm/constants.ts +0 -4
  84. package/js/vm/effect-guards.test.ts +0 -68
  85. package/js/vm/effect-guards.ts +0 -44
  86. package/js/vm/factory.test.ts +0 -486
  87. package/js/vm/factory.ts +0 -615
  88. package/js/vm/headless-vm.test.ts +0 -131
  89. package/js/vm/headless-vm.ts +0 -342
  90. package/js/vm/index.test.ts +0 -28
  91. package/js/vm/index.ts +0 -5
  92. package/js/vm/internal-types.ts +0 -32
  93. package/js/vm/json.test.ts +0 -40
  94. package/js/vm/json.ts +0 -273
  95. package/js/vm/normalize.test.ts +0 -48
  96. package/js/vm/normalize.ts +0 -65
  97. package/js/vm/options.test.ts +0 -30
  98. package/js/vm/options.ts +0 -55
  99. package/js/vm/pen-transparency.test.ts +0 -115
  100. package/js/vm/program-wasm.ts +0 -322
  101. package/js/vm/scheduler-render.test.ts +0 -401
  102. package/js/vm/scratch-assets.test.ts +0 -136
  103. package/js/vm/scratch-assets.ts +0 -202
  104. package/js/vm/types.ts +0 -358
  105. package/js/vm/value-guards.test.ts +0 -25
  106. package/js/vm/value-guards.ts +0 -18
  107. package/moon.mod.json +0 -10
  108. package/scripts/preinstall.ts +0 -4
  109. package/src/AGENTS.md +0 -6
  110. package/src/api.mbt +0 -161
  111. package/src/api_aot_commands.mbt +0 -184
  112. package/src/api_effects_json.mbt +0 -72
  113. package/src/api_options.mbt +0 -60
  114. package/src/api_program_wasm.mbt +0 -1647
  115. package/src/api_program_wat.mbt +0 -2206
  116. package/src/api_snapshot_json.mbt +0 -44
  117. package/src/cmd/AGENTS.md +0 -5
  118. package/src/cmd/main/AGENTS.md +0 -5
  119. package/src/cmd/main/main.mbt +0 -29
  120. package/src/cmd/main/moon.pkg +0 -7
  121. package/src/cmd/main/pkg.generated.mbti +0 -13
  122. package/src/json_helpers.mbt +0 -176
  123. package/src/moon.pkg +0 -65
  124. package/src/moonscratch.mbt +0 -3
  125. package/src/moonscratch_wbtest.mbt +0 -40
  126. package/src/parser_sb3.mbt +0 -890
  127. package/src/pkg.generated.mbti +0 -479
  128. package/src/runtime_eval.mbt +0 -2844
  129. package/src/runtime_exec.mbt +0 -3850
  130. package/src/runtime_render.mbt +0 -2550
  131. package/src/runtime_state.mbt +0 -870
  132. package/src/test/AGENTS.md +0 -3
  133. package/src/test/projects/AGENTS.md +0 -6
  134. package/src/test/projects/moon.pkg +0 -4
  135. package/src/test/projects/moonscratch_compat_test.mbt +0 -642
  136. package/src/test/projects/moonscratch_core_test.mbt +0 -1332
  137. package/src/test/projects/moonscratch_runtime_test.mbt +0 -1087
  138. package/src/test/projects/pkg.generated.mbti +0 -13
  139. package/src/test/projects/test_support.mbt +0 -35
  140. package/src/types_effects.mbt +0 -20
  141. package/src/types_error.mbt +0 -4
  142. package/src/types_options.mbt +0 -31
  143. package/src/types_runtime_structs.mbt +0 -254
  144. package/src/types_vm.mbt +0 -109
  145. package/tsconfig.json +0 -29
  146. package/viewer/index.ts +0 -399
  147. package/viewer/vite.d.ts +0 -1
  148. package/viewer/worker.ts +0 -161
  149. package/vite.config.ts +0 -61
package/viewer/index.ts DELETED
@@ -1,399 +0,0 @@
1
- /// <reference types="vite-plus/client" />
2
-
3
- import type { RenderFrame, VMInputEvent } from '../js/index.ts'
4
- import VMWorker from './worker.ts?worker'
5
-
6
- type ExampleProject = {
7
- id: string
8
- label: string
9
- projectJson: string
10
- }
11
-
12
- type ProjectModule = {
13
- default?: unknown
14
- }
15
-
16
- type ViewerWorkerRequest =
17
- | {
18
- type: 'load'
19
- projectJson: string
20
- }
21
- | {
22
- type: 'input'
23
- input: VMInputEvent
24
- }
25
-
26
- type ViewerWorkerResponse =
27
- | {
28
- type: 'frame'
29
- frame: RenderFrame
30
- workerFps: number
31
- workerOpsPerSecond: number
32
- }
33
- | {
34
- type: 'finished'
35
- }
36
- | {
37
- type: 'warp-exit'
38
- isInWarp: boolean
39
- }
40
- | {
41
- type: 'error'
42
- message: string
43
- }
44
-
45
- const formatProjectId = (path: string): string => {
46
- const match = path.match(/([^/]+)\/dist\/project\.json$/)
47
- return match?.[1] ?? path
48
- }
49
-
50
- const toProjectJson = (_path: string, raw: unknown): string | null => {
51
- const rawModule = raw as ProjectModule | string
52
- const extracted =
53
- typeof rawModule === 'object' &&
54
- rawModule !== null &&
55
- 'default' in rawModule
56
- ? rawModule.default
57
- : rawModule
58
- if (typeof extracted === 'string') {
59
- return extracted
60
- }
61
- if (extracted === undefined || extracted === null) {
62
- return null
63
- }
64
- try {
65
- return JSON.stringify(extracted)
66
- } catch {
67
- return null
68
- }
69
- }
70
-
71
- const loadedModules = import.meta.glob(
72
- '../../../examples/*/dist/project.json',
73
- {
74
- eager: true,
75
- import: 'default',
76
- },
77
- )
78
-
79
- const projects = Object.entries(loadedModules)
80
- .map(([path, raw]) => {
81
- const projectJson = toProjectJson(path, raw)
82
- if (!projectJson) {
83
- return null
84
- }
85
-
86
- const id = formatProjectId(path)
87
- return {
88
- id,
89
- label: id,
90
- projectJson,
91
- }
92
- })
93
- .filter(
94
- (entry): entry is ExampleProject =>
95
- entry !== null && entry.id.trim().length > 0,
96
- )
97
- .sort((left, right) => left.label.localeCompare(right.label))
98
-
99
- const appElement = document.querySelector<HTMLElement>('#app') ?? document.body
100
-
101
- const controlPanel = document.createElement('div')
102
- controlPanel.className = 'controls'
103
-
104
- const selectLabel = document.createElement('label')
105
- selectLabel.textContent = 'Project'
106
- selectLabel.htmlFor = 'example-project-select'
107
-
108
- const projectSelect = document.createElement('select')
109
- projectSelect.id = 'example-project-select'
110
- projectSelect.setAttribute('aria-label', 'Select a project')
111
-
112
- const status = document.createElement('p')
113
- status.className = 'status'
114
-
115
- const fpsLabel = document.createElement('small')
116
- fpsLabel.style.color = '#facc15'
117
-
118
- const canvas = document.createElement('canvas')
119
- canvas.width = 480
120
- canvas.height = 360
121
- canvas.tabIndex = 0
122
-
123
- const statusContainer = document.createElement('div')
124
- statusContainer.append(status, fpsLabel)
125
-
126
- if (projects.length === 0) {
127
- const heading = document.createElement('h1')
128
- heading.textContent = 'MoonScratch Viewer'
129
- const errorMessage = document.createElement('p')
130
- errorMessage.textContent =
131
- 'examples/*/dist/project.json を検出できませんでした。'
132
- appElement.append(heading, errorMessage)
133
- } else {
134
- projects.forEach((project) => {
135
- const option = document.createElement('option')
136
- option.value = project.id
137
- option.textContent = project.label
138
- projectSelect.append(option)
139
- })
140
-
141
- const heading = document.createElement('h1')
142
- heading.textContent = 'MoonScratch Viewer'
143
- controlPanel.append(selectLabel, projectSelect)
144
- appElement.append(heading, controlPanel, statusContainer, canvas)
145
- }
146
-
147
- document.body.append(appElement)
148
-
149
- const context = canvas.getContext('2d')
150
- if (!context) {
151
- throw new Error('canvas 2D context is unavailable')
152
- }
153
-
154
- let worker: Worker | null = null
155
- let fpsFrames = 0
156
- let fpsStartedAt = 0
157
- let playbackToken = 0
158
- let isPointerDown = false
159
- const keysDown = new Set<string>()
160
- let workerFps = 0
161
- let workerOpsPerSecond = 0
162
-
163
- const postInput = (input: VMInputEvent): void => {
164
- if (!worker) {
165
- return
166
- }
167
- const message: ViewerWorkerRequest = {
168
- type: 'input',
169
- input,
170
- }
171
- worker.postMessage(message)
172
- }
173
-
174
- const normalizeDomKey = (key: string): string => {
175
- if (key === ' ') {
176
- return 'space'
177
- }
178
- return key.toLowerCase()
179
- }
180
-
181
- const syncKeysDown = (): void => {
182
- postInput({
183
- type: 'keys_down',
184
- keys: Array.from(keysDown),
185
- })
186
- }
187
-
188
- const toStageMousePoint = (
189
- event: Pick<MouseEvent, 'clientX' | 'clientY'>,
190
- ): { x: number; y: number } => {
191
- const rect = canvas.getBoundingClientRect()
192
- if (rect.width <= 0 || rect.height <= 0) {
193
- return { x: 0, y: 0 }
194
- }
195
- const offsetX = (event.clientX - rect.left) / rect.width
196
- const offsetY = (event.clientY - rect.top) / rect.height
197
- const rawX = offsetX * canvas.width - canvas.width / 2
198
- const rawY = canvas.height / 2 - offsetY * canvas.height
199
- const x = Math.max(-canvas.width / 2, Math.min(canvas.width / 2, rawX))
200
- const y = Math.max(-canvas.height / 2, Math.min(canvas.height / 2, rawY))
201
- return {
202
- x,
203
- y,
204
- }
205
- }
206
-
207
- const postMouseState = (
208
- event: Pick<MouseEvent, 'clientX' | 'clientY'>,
209
- isDown: boolean,
210
- ): void => {
211
- const point = toStageMousePoint(event)
212
- postInput({
213
- type: 'mouse',
214
- x: point.x,
215
- y: point.y,
216
- isDown,
217
- })
218
- }
219
-
220
- const postMouseTargets = (): void => {
221
- postInput({
222
- type: 'mouse_targets',
223
- stage: true,
224
- targets: [],
225
- })
226
- }
227
-
228
- const updateFps = (now = performance.now()): void => {
229
- if (fpsStartedAt === 0) {
230
- fpsStartedAt = now
231
- }
232
- fpsFrames += 1
233
- const elapsedMs = now - fpsStartedAt
234
- if (elapsedMs >= 500) {
235
- const renderFps = (fpsFrames * 1000) / elapsedMs
236
- fpsLabel.textContent = `${renderFps.toFixed(1)} FPS, worker ${workerFps.toFixed(1)} FPS, ${workerOpsPerSecond.toFixed(0)} ops/s`
237
- fpsFrames = 0
238
- fpsStartedAt = now
239
- }
240
- }
241
-
242
- const renderFrameToCanvas = (frame: RenderFrame): void => {
243
- const { width, height, pixels } = frame
244
- if (width <= 0 || height <= 0) {
245
- status.textContent = '空のフレームです'
246
- return
247
- }
248
-
249
- if (canvas.width !== width || canvas.height !== height) {
250
- canvas.width = width
251
- canvas.height = height
252
- }
253
- const clamped = Uint8ClampedArray.from(pixels)
254
- const imageData = new ImageData(clamped, width, height)
255
- context.putImageData(imageData, 0, 0)
256
- updateFps()
257
- }
258
-
259
- const stopPlayback = (): void => {
260
- if (worker) {
261
- worker.terminate()
262
- worker = null
263
- }
264
- keysDown.clear()
265
- isPointerDown = false
266
- fpsFrames = 0
267
- fpsStartedAt = 0
268
- workerFps = 0
269
- workerOpsPerSecond = 0
270
- }
271
-
272
- const startPlayback = async (projectId: string) => {
273
- const selected = projects.find((project) => project.id === projectId)
274
- if (!selected) {
275
- return
276
- }
277
-
278
- stopPlayback()
279
-
280
- status.textContent = `${selected.label} を起動中...`
281
- fpsLabel.textContent = ''
282
- try {
283
- const token = playbackToken + 1
284
- playbackToken = token
285
- status.textContent = `${selected.label} を再生中`
286
- worker = new VMWorker()
287
- const startMessage: ViewerWorkerRequest = {
288
- type: 'load',
289
- projectJson: selected.projectJson,
290
- }
291
- worker.postMessage(startMessage)
292
- worker.onmessage = (event) => {
293
- if (token !== playbackToken) {
294
- return
295
- }
296
- const payload = event.data as ViewerWorkerResponse
297
- if (payload.type === 'frame') {
298
- workerFps = payload.workerFps
299
- workerOpsPerSecond = payload.workerOpsPerSecond
300
- renderFrameToCanvas(payload.frame)
301
- return
302
- }
303
- if (payload.type === 'finished') {
304
- status.textContent = `${selected.label} の再生が完了`
305
- return
306
- }
307
- if (payload.type === 'warp-exit') {
308
- status.textContent = `${selected.label}: warp-exit (isInWarp=${payload.isInWarp})`
309
- return
310
- }
311
- if (payload.type === 'error') {
312
- status.textContent = `読み込みに失敗しました: ${payload.message}`
313
- stopPlayback()
314
- }
315
- }
316
- worker.onerror = (event) => {
317
- if (token !== playbackToken) {
318
- return
319
- }
320
- status.textContent = `読み込みに失敗しました: ${event.message}`
321
- stopPlayback()
322
- }
323
- } catch (error) {
324
- status.textContent = `読み込みに失敗しました: ${
325
- error instanceof Error ? error.message : String(error)
326
- }`
327
- stopPlayback()
328
- }
329
- }
330
-
331
- projectSelect.addEventListener('change', () => {
332
- startPlayback(projectSelect.value)
333
- })
334
-
335
- canvas.addEventListener('pointerdown', (event) => {
336
- if (!worker) {
337
- return
338
- }
339
- isPointerDown = true
340
- postMouseState(event, true)
341
- postMouseTargets()
342
- canvas.focus()
343
- })
344
-
345
- canvas.addEventListener('pointermove', (event) => {
346
- if (!worker) {
347
- return
348
- }
349
- postMouseState(event, isPointerDown || event.buttons > 0)
350
- postMouseTargets()
351
- })
352
-
353
- canvas.addEventListener('pointerup', (event) => {
354
- if (!worker) {
355
- return
356
- }
357
- isPointerDown = false
358
- postMouseState(event, false)
359
- postMouseTargets()
360
- })
361
-
362
- canvas.addEventListener('pointercancel', () => {
363
- isPointerDown = false
364
- })
365
-
366
- canvas.addEventListener('keydown', (event) => {
367
- if (!worker) {
368
- return
369
- }
370
- const key = normalizeDomKey(event.key)
371
- if (event.repeat || keysDown.has(key)) {
372
- return
373
- }
374
- keysDown.add(key)
375
- syncKeysDown()
376
- })
377
-
378
- canvas.addEventListener('keyup', (event) => {
379
- if (!worker) {
380
- return
381
- }
382
- const key = normalizeDomKey(event.key)
383
- if (!keysDown.delete(key)) {
384
- return
385
- }
386
- syncKeysDown()
387
- })
388
-
389
- canvas.addEventListener('blur', () => {
390
- if (!worker || keysDown.size === 0) {
391
- return
392
- }
393
- keysDown.clear()
394
- syncKeysDown()
395
- })
396
-
397
- if (projects.length > 0) {
398
- startPlayback('rubiks-cube')
399
- }
package/viewer/vite.d.ts DELETED
@@ -1 +0,0 @@
1
- /// <reference types="vite/client" />
package/viewer/worker.ts DELETED
@@ -1,161 +0,0 @@
1
- import {
2
- createHeadlessVM,
3
- createProgramModuleFromProject,
4
- type VMInputEvent,
5
- } from '../js'
6
-
7
- const FRAME_FORCE_TIMEOUT_OUT_OF_WARP = 1000 / 30 // 30 FPS
8
- const FRAME_FORCE_TIMEOUT_IN_WARP = 1000 / 5 // 5 FPS
9
- const TICKS_TIMEOUT = 1
10
- const WORKER_METRICS_WINDOW_MS = 1000
11
-
12
- type ViewerWorkerRequest =
13
- | {
14
- type: 'load'
15
- projectJson: string
16
- }
17
- | {
18
- type: 'input'
19
- input: VMInputEvent
20
- }
21
-
22
- let vm: ReturnType<typeof createHeadlessVM> | null = null
23
- let runToken = 0
24
- const pendingInputs: VMInputEvent[] = []
25
-
26
- const flushPendingInputs = (): void => {
27
- if (!vm || pendingInputs.length === 0) {
28
- return
29
- }
30
- for (const input of pendingInputs) {
31
- vm.dispatchInputEvent(input)
32
- }
33
- pendingInputs.length = 0
34
- }
35
-
36
- const waitForNextFrame = async (): Promise<void> => {
37
- await new Promise<void>((resolve) => {
38
- if (typeof requestAnimationFrame === 'function') {
39
- requestAnimationFrame(() => resolve())
40
- return
41
- }
42
- setTimeout(() => resolve(), FRAME_FORCE_TIMEOUT_OUT_OF_WARP)
43
- })
44
- }
45
-
46
- const playbackLoop = async (token: number): Promise<void> => {
47
- let workerFpsFrames = 0
48
- let workerOps = 0
49
- let metricsStartedAt = 0
50
- let currentWorkerFps = 0
51
- let currentWorkerOpsPerSecond = 0
52
- while (true) {
53
- if (!vm || token !== runToken) {
54
- return
55
- }
56
- flushPendingInputs()
57
-
58
- const frameStart = performance.now()
59
- vm.setTime(frameStart)
60
- let frameOps = 0
61
- let shouldRender = false
62
- let isFinished = false
63
- while (true) {
64
- const frameInfo = vm.stepFrame()
65
- frameOps += frameInfo.ops
66
- if (frameInfo.stopReason === 'finished') {
67
- shouldRender = frameInfo.shouldRender
68
- isFinished = true
69
- break
70
- }
71
- if (frameInfo.shouldRender) {
72
- shouldRender = true
73
- break
74
- }
75
-
76
- const frameBudget = frameInfo.isInWarp
77
- ? FRAME_FORCE_TIMEOUT_IN_WARP
78
- : FRAME_FORCE_TIMEOUT_OUT_OF_WARP
79
- if (performance.now() - frameStart > frameBudget) {
80
- break
81
- }
82
- }
83
-
84
- workerFpsFrames += 1
85
- workerOps += frameOps
86
- const now = performance.now()
87
- if (metricsStartedAt === 0) {
88
- metricsStartedAt = now
89
- } else {
90
- const elapsedMs = now - metricsStartedAt
91
- if (elapsedMs >= WORKER_METRICS_WINDOW_MS) {
92
- currentWorkerFps = (workerFpsFrames * 1000) / elapsedMs
93
- currentWorkerOpsPerSecond = (workerOps * 1000) / elapsedMs
94
- workerFpsFrames = 0
95
- workerOps = 0
96
- metricsStartedAt = now
97
- }
98
- }
99
-
100
- if (shouldRender) {
101
- const frame = vm.renderFrame()
102
- postMessage({
103
- type: 'frame',
104
- frame,
105
- workerFps: currentWorkerFps,
106
- workerOpsPerSecond: currentWorkerOpsPerSecond,
107
- })
108
- }
109
-
110
- if (isFinished) {
111
- postMessage({ type: 'finished' })
112
- return
113
- }
114
-
115
- await waitForNextFrame()
116
- }
117
- }
118
-
119
- const toErrorMessage = (error: unknown): string =>
120
- error instanceof Error ? error.message : String(error)
121
-
122
- globalThis.onmessage = (event) => {
123
- const data = event.data as ViewerWorkerRequest
124
- if (!data || typeof data !== 'object') {
125
- return
126
- }
127
- if (data.type === 'input') {
128
- if (!vm) {
129
- return
130
- }
131
- pendingInputs.push(data.input)
132
- return
133
- }
134
- if (data.type !== 'load') {
135
- return
136
- }
137
-
138
- pendingInputs.length = 0
139
- runToken += 1
140
- const token = runToken
141
- try {
142
- const program = createProgramModuleFromProject({
143
- projectJson: data.projectJson,
144
- })
145
- vm = createHeadlessVM({
146
- program,
147
- options: {
148
- stepTimeoutTicks: TICKS_TIMEOUT,
149
- },
150
- })
151
- vm.start()
152
- vm.greenFlag()
153
- void playbackLoop(token)
154
- } catch (error) {
155
- vm = null
156
- postMessage({
157
- type: 'error',
158
- message: toErrorMessage(error),
159
- })
160
- }
161
- }
package/vite.config.ts DELETED
@@ -1,61 +0,0 @@
1
- import { existsSync } from 'node:fs'
2
- import { dirname, resolve } from 'node:path'
3
- import { defineConfig } from 'vite-plus'
4
-
5
- const RELEASE_IMPORTER_PATH = '../../_build/js/release/build/moonscratch.js'
6
- const RELEASE_BINDING_PATH = '../../_build/js/release/build/moonscratch.js'
7
- const DEBUG_BINDING_PATH = '../../_build/js/debug/build/moonscratch.js'
8
- const DEBUG_BUILD_PATH = './_build/js/debug/build/moonscratch.js'
9
-
10
- const toAbsolutePath = (
11
- importer: string | null | undefined,
12
- importPath: string,
13
- ) => {
14
- const baseDir = importer ? dirname(importer) : process.cwd()
15
- const normalizedCandidate = importPath.replace(/^\.\.\/\.\.\//, './')
16
- const candidates = [
17
- resolve(baseDir, importPath),
18
- resolve(process.cwd(), importPath),
19
- resolve(process.cwd(), normalizedCandidate),
20
- ]
21
- for (const candidate of candidates) {
22
- if (existsSync(candidate)) {
23
- return candidate
24
- }
25
- }
26
- return undefined
27
- }
28
-
29
- const resolveBindingPath = (importer: string | null | undefined) => {
30
- const releasePath = toAbsolutePath(importer, RELEASE_IMPORTER_PATH)
31
- if (releasePath) {
32
- return releasePath
33
- }
34
- if (existsSync(resolve(process.cwd(), DEBUG_BUILD_PATH))) {
35
- return resolve(process.cwd(), DEBUG_BUILD_PATH)
36
- }
37
- const debugPath = toAbsolutePath(importer, DEBUG_BINDING_PATH)
38
- return debugPath
39
- }
40
-
41
- export default defineConfig({
42
- pack: {
43
- entry: 'js/index.ts',
44
- dts: true,
45
- inlineOnly: ['sb3-types'],
46
- external: ['sharp', 'detect-libc', 'semver', '@img/colour'],
47
- plugins: [
48
- {
49
- name: 'mbt-binding',
50
- resolveId(id, importer) {
51
- if (id === RELEASE_BINDING_PATH) {
52
- return resolveBindingPath(importer)
53
- }
54
- },
55
- },
56
- ],
57
- },
58
- test: {
59
- include: ['./js/**/*.test.ts'],
60
- },
61
- })