JavaScriptWorker.swift 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import Foundation
  2. import JavaScriptCore
  3. final class JavaScriptWorker {
  4. private let processQueue = DispatchQueue(label: "DispatchQueue.JavaScriptWorker", attributes: .concurrent)
  5. private let virtualMachine: JSVirtualMachine
  6. private var contextPool: [JSContext] = []
  7. private let contextPoolLock = NSLock()
  8. init(poolSize: Int = 5) {
  9. virtualMachine = JSVirtualMachine()!
  10. // Pre-create a pool of JSContext instances
  11. for _ in 0 ..< poolSize {
  12. contextPool.append(createContext())
  13. }
  14. }
  15. private func createContext() -> JSContext {
  16. let context = JSContext(virtualMachine: virtualMachine)!
  17. context.exceptionHandler = { _, exception in
  18. if let error = exception?.toString() {
  19. warning(.openAPS, "JavaScript Error: \(error)")
  20. }
  21. }
  22. let consoleLog: @convention(block) (String) -> Void = { message in
  23. debug(.openAPS, "JavaScript log: \(message)")
  24. }
  25. context.setObject(consoleLog, forKeyedSubscript: "_consoleLog" as NSString)
  26. return context
  27. }
  28. private func getContext() -> JSContext {
  29. contextPoolLock.lock()
  30. let context = contextPool.popLast() ?? createContext()
  31. contextPoolLock.unlock()
  32. return context
  33. }
  34. private func returnContext(_ context: JSContext) {
  35. contextPoolLock.lock()
  36. contextPool.append(context)
  37. contextPoolLock.unlock()
  38. }
  39. @discardableResult func evaluate(script: Script) -> JSValue! {
  40. evaluate(string: script.body)
  41. }
  42. private func evaluate(string: String) -> JSValue! {
  43. let context = getContext()
  44. defer { returnContext(context) }
  45. return context.evaluateScript(string)
  46. }
  47. private func json(for string: String) -> RawJSON {
  48. evaluate(string: "JSON.stringify(\(string), null, 4);")!.toString()!
  49. }
  50. func call(function: String, with arguments: [JSON]) -> RawJSON {
  51. let joined = arguments.map(\.rawJSON).joined(separator: ",")
  52. return json(for: "\(function)(\(joined))")
  53. }
  54. func inCommonContext<Value>(execute: (JavaScriptWorker) -> Value) -> Value {
  55. let context = getContext()
  56. defer { returnContext(context) }
  57. return execute(self)
  58. }
  59. func evaluateBatch(scripts: [Script]) {
  60. let ctx = getContext()
  61. defer { returnContext(ctx) } // Ensure the context is returned to the pool
  62. scripts.forEach { script in
  63. ctx.evaluateScript(script.body)
  64. }
  65. }
  66. }