Created
February 13, 2023 09:32
-
-
Save muromec/d80219ef3ef6e307cf1d05bbfcfb5b0c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { computed, readonly, reactive, ref, watch } from 'vue' | |
function spawn(fn, pname, toParent = ()=> null) { | |
let current = null; | |
let state = reactive({}); | |
let buffer = []; | |
let resolveExit; | |
let exitPromise = new Promise(resolve => { | |
resolveExit = resolve; | |
}); | |
function send ({ to = pparent, ...msg }) { | |
let ret = to.next({ state, reply: toSelf, msg }); | |
tick(); | |
if (ret.done) { | |
resolveExit(); | |
} | |
} | |
function tick () { | |
let msg; | |
while(msg = buffer.pop()) { | |
toParent({pname, ...msg}); | |
} | |
} | |
function toSelf(msg) { | |
send({ to: current, ...msg}); | |
} | |
function fromChild(msg) { | |
toSelf(msg); | |
} | |
function toBuffer(msg) { | |
buffer.push(msg); | |
} | |
function fork (fn, pname) { | |
return spawn(fn, pname, fromChild); | |
} | |
function wait () { | |
return exitPromise; | |
} | |
return (...args) => { | |
const process = { | |
pname, | |
send: toSelf, | |
fork, | |
toParent: toBuffer, | |
id: Symbol(pname), | |
state: readonly(state), | |
wait, | |
}; | |
const task = watchExit(fn)(process, ...args); | |
current = task; | |
task.next(); | |
toSelf({ type: 'INIT' }); | |
return process; | |
} | |
} | |
function attach(supervisor, fn, pname) { | |
return (...args) => { | |
supervisor.send({ type: 'RUN', fn, args, pname}); | |
} | |
} | |
function watchExit(fn) { | |
return function* (process, ...args) { | |
yield* fn(process, ...args); | |
process.toParent({ type: 'EXIT', pid: process.id}); | |
} | |
} | |
function* runDispatch(name, fn) { | |
let state = null; | |
let msg; | |
while(true) { | |
({state, msg} = yield state); | |
//console.log('msg', name, ' <- ', msg); | |
let ret = fn(state, msg); | |
if (ret === 'STOPPED') { | |
break; | |
} | |
} | |
} | |
function* supervise({pname, toParent, fork}) { | |
yield* runDispatch(pname, (state, msg)=> { | |
if (msg.type === 'INIT') { | |
state.processes = []; | |
}; | |
if (msg.type === 'RUN') { | |
const newProcess = fork(msg.fn, msg.pname)(...msg.args); | |
state.processes.push(newProcess); | |
} | |
if (msg.type === 'ERROR' || msg.type === 'ABORT') { | |
state.processes.forEach(p=> p.send({ type: 'ABORT'})); | |
} | |
if (msg.type === 'EXIT') { | |
state.processes = state.processes.filter( | |
iter=> iter.id !== msg.pid | |
); | |
if (state.processes.length === 0) { | |
return 'STOPPED'; | |
} | |
} | |
if (msg.type === 'OK') { | |
toParent(msg); | |
} | |
}); | |
} | |
function* xfetch({ pname, toParent, send: toSelf }, { url }) { | |
const controller = new AbortController(); | |
const signal = controller.signal; | |
(async function do_request() { | |
try { | |
const res = await fetch(url.href, { signal }); | |
const text = await res.text(); | |
toSelf({ type: 'OK', text }); | |
} catch (e) { | |
const isAborted = (e instanceof DOMException && e.name === 'AbortError'); | |
if (isAborted) { | |
toSelf({ type: 'ABORTED', pname }); | |
} else { | |
//console.log('e', e); | |
toSelf({ type: 'ERROR', pname }); | |
} | |
} | |
toSelf({ type: 'DONE' }); | |
})(); | |
yield* runDispatch(pname, (state, msg)=> { | |
if (msg.type === 'INIT') { | |
state.code = 'pending'; | |
} | |
if (msg.type === 'ABORT') { | |
controller.abort(); | |
} | |
if (msg.type === 'DONE') { | |
return 'STOPPED'; | |
} | |
if (msg.type === 'ABORTED') { | |
toParent(msg); | |
state.code = 'aborted'; | |
} | |
if (msg.type === 'ERROR') { | |
toParent(msg); | |
state.code = 'failed'; | |
} | |
if (msg.type === 'OK') { | |
toParent(msg); | |
state.code = 'ok'; | |
} | |
}); | |
} | |
function* main({ pname, fork }) { | |
const s = fork(supervise, 'super')(); | |
const urls = [ | |
new URL('https://api.myip.com'), | |
new URL('https://x.myip.com'), | |
]; | |
for(let url of urls) { | |
attach(s, xfetch, `xfetch ${url.href}`)({ url }); | |
} | |
yield* runDispatch(pname, (state, msg)=> { | |
if (msg.type === 'INIT') { | |
state.s = s; | |
} | |
if (msg.type === 'ABORT') { | |
s.send({ type: 'ABORT'}); | |
} | |
if (msg.type === 'EXIT') { | |
state.s = null; | |
} | |
if (msg.type === 'OK') { | |
console.log('data', msg.text); | |
return 'STOPPED'; | |
} | |
}); | |
} | |
const m = spawn(main, 'main')(); | |
const states = computed(() => { | |
if (!m.state.s) { | |
return null; | |
} | |
return m.state.s.state.processes.map(p => p.state); | |
}); | |
watch(states, (value)=> { | |
console.log('s', value); | |
}, { immediate: true }); | |
//m.send({ type: 'ABORT', p: null}); | |
await m.wait(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment