@rip-lang/swarm 1.0.0 → 1.0.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 (2) hide show
  1. package/package.json +1 -1
  2. package/swarm.rip +42 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/swarm",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Parallel job runner with worker threads — setup once, swarm many",
5
5
  "type": "module",
6
6
  "main": "swarm.rip",
package/swarm.rip CHANGED
@@ -214,6 +214,7 @@ export swarm = (opts = {}) ->
214
214
  info = {}
215
215
  taskIdx = 0
216
216
  inflight = {} # slot → taskPath (track in-flight tasks for crash recovery)
217
+ lastTask = {} # slot → last completed task name (for display)
217
218
 
218
219
  # signal handlers
219
220
  process.on 'SIGINT', ->
@@ -239,7 +240,7 @@ export swarm = (opts = {}) ->
239
240
  if not finished and done + died >= jobs
240
241
  finished = true
241
242
  for wk in allWorkers
242
- try wk.postMessage { type: 'shutdown' }
243
+ try wk.unref()
243
244
  catch then null
244
245
  resolveAll()
245
246
 
@@ -265,10 +266,13 @@ export swarm = (opts = {}) ->
265
266
 
266
267
  w.on 'message', (msg) ->
267
268
  switch msg.type
269
+ when 'error'
270
+ writeFileSync('.swarm/errors.log', "worker #{slot} startup: #{msg.error}\n", { flag: 'a' }) if existsSync(_dir)
268
271
  when 'ready'
269
272
  dispatchNext(w, slot)
270
273
  when 'done'
271
274
  move(msg.taskPath, _done)
275
+ lastTask[slot] = msg.taskPath.split('/').pop()
272
276
  inflight[slot] = null
273
277
  live--
274
278
  done++
@@ -277,6 +281,8 @@ export swarm = (opts = {}) ->
277
281
  dispatchNext(w, slot)
278
282
  when 'failed'
279
283
  move(msg.taskPath, _died)
284
+ lastTask[slot] = msg.taskPath.split('/').pop()
285
+ writeFileSync('.swarm/errors.log', "#{msg.taskPath.split('/').pop()}: #{msg.error or 'unknown'}\n", { flag: 'a' }) if existsSync(_dir)
280
286
  inflight[slot] = null
281
287
  live--
282
288
  died++
@@ -285,9 +291,10 @@ export swarm = (opts = {}) ->
285
291
  dispatchNext(w, slot)
286
292
 
287
293
  w.on 'error', (err) ->
288
- console.error "\nworker #{slot} error: #{err.message}"
294
+ writeFileSync('.swarm/errors.log', "worker #{slot} error: #{err.message}\n", { flag: 'a' }) if existsSync(_dir)
289
295
 
290
296
  w.on 'exit', (code) ->
297
+ writeFileSync('.swarm/errors.log', "worker #{slot} exited with code #{code}, inflight: #{inflight[slot]}\n", { flag: 'a' }) if existsSync(_dir)
291
298
  # if worker crashed mid-task, count the in-flight task as died
292
299
  if inflight[slot]
293
300
  move(inflight[slot], _died)
@@ -307,9 +314,17 @@ export swarm = (opts = {}) ->
307
314
  for slot in [1..count]
308
315
  spawnWorker(slot)
309
316
 
317
+ # final redraw — fill all worker bars and show per-worker stats
318
+ secs = (Date.now() - startTime) / 1000
319
+ for slot of info
320
+ s = parseInt(slot)
321
+ n = info[slot]
322
+ rate = if secs > 0 then (n / secs).toFixed(1) else '—'
323
+ write go(s + 1, _len + 5) + bg("5383ec") + _char.repeat(_wide) + bg() + " │ #{n} jobs @ #{rate}/sec" + clear(true)
324
+ draw({ live: 0, done, died, jobs, workers, info })
325
+
310
326
  # summary
311
327
  cursor(true)
312
- secs = (Date.now() - startTime) / 1000
313
328
  write go(workers + 5, 1)
314
329
  write "#{secs.toFixed(2)} secs"
315
330
  write " for #{jobs} jobs"
@@ -321,6 +336,11 @@ export swarm = (opts = {}) ->
321
336
  # CLI helpers
322
337
  # ==============================================================================
323
338
 
339
+ # flags that swarm consumes (with value)
340
+ _flagsWithValue = ['-w', '--workers', '-b', '--bar', '-c', '--char']
341
+ # flags that swarm consumes (standalone)
342
+ _flagsAlone = ['-r', '--reset', '-h', '--help', '-v', '--version']
343
+
324
344
  findArg = (args, short, long) ->
325
345
  for arg, i in args
326
346
  if arg is short or arg is long
@@ -330,3 +350,22 @@ findArg = (args, short, long) ->
330
350
  if arg.startsWith("#{short}=")
331
351
  return arg.split('=')[1]
332
352
  null
353
+
354
+ # return process.argv with swarm's flags stripped — only your args remain
355
+ export args = ->
356
+ result = []
357
+ list = process.argv.slice(2)
358
+ skip = false
359
+ for arg in list
360
+ if skip
361
+ skip = false
362
+ continue
363
+ if arg in _flagsWithValue
364
+ skip = true
365
+ continue
366
+ if arg in _flagsAlone
367
+ continue
368
+ if _flagsWithValue.some((f) -> arg.startsWith("#{f}="))
369
+ continue
370
+ result.push(arg)
371
+ result