execPyodide.mjs

import get from 'lodash-es/get.js'
import each from 'lodash-es/each.js'
import isarr from './isarr.mjs'
import loadPyodide from './_loadPyodide.js'
import getGlobal from './getGlobal.mjs'
import haskey from './haskey.mjs'
import waitFun from './waitFun.mjs'
import queue from './queue.mjs'
// console.log('loadPyodide', loadPyodide)


//pyodide於VM上之node下執行有問題,主要是請求request無法設定head,待日後確認是否有新版並修復


function getLoadPyodide() {
    let g = getGlobal()
    let x = loadPyodide || g.loadPyodide
    // console.log('getLoadPyodide loadPyodide', x)
    return x
}


let state = ''
let pyodide = null
let micropip = null
async function iniPyodide() {

    //state
    if (state === 'done') {
        return
    }
    else if (state === 'loading') {
        await waitFun(() => {
            return pyodide !== null
        })
        return
    }
    state = 'loading'

    //getLoadPyodide
    let lpd = getLoadPyodide()
    // console.log('lpd', lpd)

    //lpd
    let _pyodide = await lpd()
    // console.log('_pyodide',_pyodide)

    await _pyodide.loadPackage('micropip')
    let _micropip = _pyodide.pyimport('micropip')
    // console.log('_micropip',_micropip)

    //save
    pyodide = _pyodide
    micropip = _micropip

    //state
    state = 'done'

}


let q = null
let kp = {}
if (true) {

    //queue
    let takeLimit = 1
    q = queue(takeLimit)

    //message
    q.on('message', function(qs) {
        // console.log('message', JSON.stringify(qs))

        //get
        let pkg = q.get()
        if (!pkg) {
            return
        }
        // console.log('get', pkg)

        async function core() {

            //loading
            if (!haskey(kp, pkg)) {
                kp[pkg] = 'loading'
            }

            //install and loadPackage
            await micropip.install(pkg)
            await pyodide.loadPackage(pkg)
            // console.log('inst-load pkg',pkg)

            //done
            kp[pkg] = 'done'

            //cb
            q.cb()

            // console.log('finish',pkg)
        }
        core()
            .catch((err) => {
                console.log(err)
            })

    })

}


function loadPkg(pkg) {
    q.push(pkg)
}


async function checkPkgs(pkgs) {
    await waitFun(() => {
        // console.log('check pkgs',pkgs)
        let b = true
        each(pkgs, (pkg) => {
            let c = get(kp, pkg)
            if (c !== 'done') {
                b = false
                return false //跳出
            }
        })
        return b
    })
}


async function loadPkgs(pkgs) {

    //loadPkg
    each(pkgs, (pkg) => {
        loadPkg(pkg)
    })

    //checkPkgs
    await checkPkgs(pkgs)

}


/**
 * 通過Pyodide執行Python程式碼
 *
 * Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/execPyodide.test.mjs Github}
 * @memberOf wsemi
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {Array} [opt.pkgs=[]] 輸入Python引用已安裝套件字串陣列,例如為'scipy'、'numpy'等,預設[]
 * @param {Array} [opt.imps=[]] 輸入Python引入(import)的字串陣列,例如為['import scipy','import numpy']等,預設[]
 * @param {Array} [opt.inps=[]] 輸入輸入數據陣列,例如為[123,'abc']等,預設[]
 * @param {String} [opt.content=''] 輸入執行程式核心字串,若要使用inps則使用'rIn數字',數字由1至inps的長度n,回傳使用'ret'接收,例如ret = fun(rIn1,rIn2,...rInn),預設''
 * @returns {Number|String|Boolean|Array|Object} 回傳運算結果資料
 * @example
 *
 * async function test() {
 *
 *     let pkgs = [
 *         'scipy',
 *     ]
 *     let imps = [
 *         'from scipy.interpolate import griddata',
 *     ]
 *     let psSrc = [
 *         [-0.1, -0.1, -0.1, 0],
 *         [1, 0, 0, 0],
 *         [1, 1, 0, 0],
 *         [0, 0, 1, 0],
 *         [1, 0, 1, 0],
 *         [1, 1, 1, 10],
 *     ]
 *     let psLocs = []
 *     let psValus = []
 *     for (let k = 0; k < psSrc.length; k++) {
 *         let v = psSrc[k]
 *         psLocs.push([v[0], v[1], v[2]])
 *         psValus.push(v[3])
 *     }
 *     let psTar = [
 *         0.1, 0.1, 0.95
 *     ]
 *     let inps = [
 *         psLocs,
 *         psValus,
 *         psTar,
 *     ]
 *     let content = `
 * ret = griddata(rIn1, rIn2, rIn3, method='linear')
 *     `
 *     let rs = await execPyodide({
 *         pkgs,
 *         imps,
 *         inps,
 *         content,
 *     })
 *     console.log('rs', rs)
 *
 * }
 *
 * test()
 *     .catch((err) => {
 *         console.log(err)
 *     })
 * // Loading micropip, packaging
 * // Loaded packaging, micropip
 * // Loading scipy, numpy, clapack
 * // Loaded clapack, numpy, scipy
 * // scipy already loaded from default channel
 * // No new packages to load
 * // rs Float64Array(1) [ 0.49999999999999933 ]
 *
 */
async function execPyodide(opt = {}) {

    //pkgs
    let pkgs = get(opt, 'pkgs', [])
    if (!isarr(pkgs)) {
        pkgs = []
    }

    //imps
    let imps = get(opt, 'imps', [])
    if (!isarr(imps)) {
        imps = []
    }

    //inps
    let inps = get(opt, 'inps', [])
    if (!isarr(inps)) {
        inps = []
    }

    //content, 執行Python程式碼, 例如 ret = griddata(rIn1, rIn2, rIn3, method='linear')
    let content = get(opt, 'content', '')

    //iniPyodide
    await iniPyodide()

    //loadPkgs
    await loadPkgs(pkgs)

    //cimps
    let cimps = ''
    each(imps, (imp) => {
        cimps += imp + '\n'
    })
    // console.log('cimps', cimps)

    //cins, 待之後與pyodide.globals.set變數提高效能, 可能有轉型問題, 待確認
    let cins = ''
    each(inps, (v, k) => {
        cins += `rIn${k + 1} = ${JSON.stringify(v)}` + '\n'
    })
    // console.log('cins', cins)

    //scp
    let scp = `
${cimps}

${cins}

ret = ''

${content}

# print(ret)
    `

    //runPython
    await pyodide.runPython(scp)

    //r
    let r = pyodide.globals.get('ret').toJs()
    // console.log('r', r)

    // //cdbl
    // rs = map(rs, (v) => {
    //     return cdbl(v)
    // })

    return r
}


export default execPyodide