From a2c8cd6d4b867597032d01b096934937e6a86214 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 7 Oct 2017 15:36:22 -0400 Subject: [PATCH] Cache compile script data --- src/main/kotlin/net/shadowfacts/ekt/EKT.kt | 78 +++++++++++++++------ src/test/kotlin/net/shadowfacts/ekt/Test.kt | 36 ++++++++-- 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/src/main/kotlin/net/shadowfacts/ekt/EKT.kt b/src/main/kotlin/net/shadowfacts/ekt/EKT.kt index 51ee215..5028361 100644 --- a/src/main/kotlin/net/shadowfacts/ekt/EKT.kt +++ b/src/main/kotlin/net/shadowfacts/ekt/EKT.kt @@ -1,10 +1,9 @@ package net.shadowfacts.ekt -import java.io.File -import javax.script.ScriptContext -import javax.script.ScriptEngine -import javax.script.ScriptEngineManager -import javax.script.SimpleScriptContext +import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase +import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult +import java.io.* +import javax.script.* import kotlin.concurrent.getOrSet /** @@ -44,11 +43,11 @@ fun include(include: String, init: net.shadowfacts.ekt.EKT.DataProvider.() -> Un _result.toString() """ - private val engine = ThreadLocal() + private val engine = ThreadLocal() fun render(env: TemplateEnvironment, template: String = env.template): String { - if (env.cacheDir != null && env.cacheFile.exists()) { - return eval(env.cacheFile.readText(Charsets.UTF_8), env) + if (env.cacheDir != null && env.cacheScript.exists()) { + return eval(env.cacheScript.readText(Charsets.UTF_8), env) } @Suppress("NAME_SHADOWING") @@ -79,7 +78,7 @@ _result.toString() val script = imports + scriptPrefix + template + scriptSuffix if (env.cacheDir != null) { - env.cacheFile.apply { + env.cacheScript.apply { if (!parentFile.exists()) parentFile.mkdirs() if (!exists()) createNewFile() writeText(script, Charsets.UTF_8) @@ -121,21 +120,58 @@ _result.toString() return renderClasspath(name, path, "$path/includes", cacheDir, init) } - internal fun eval(script: String, env: TemplateEnvironment): String { - val engine = engine.getOrSet { ScriptEngineManager().getEngineByExtension("kts") } - engine.context = SimpleScriptContext() - val bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE) - bindings.putAll(env.data) - bindings.put("_env", env) + private fun eval(script: String, env: TemplateEnvironment): String { + val engine = engine.getOrSet { ScriptEngineManager().getEngineByExtension("kts") as KotlinJsr223JvmScriptEngineBase } + + val context = createContext(engine, env) + + if (env.cacheDir != null) { + val cacheCompiled = env.cacheCompiled + val compiled = if (cacheCompiled.exists()) { + val fis = FileInputStream(cacheCompiled) + val ois = ObjectInputStream(fis) + val data = ois.readObject() as ReplCompileResult.CompiledClasses + ois.close() + fis.close() + KotlinJsr223JvmScriptEngineBase.CompiledKotlinScript(engine, engine.nextCodeLine(context, env.cacheScript.readText(Charsets.UTF_8)), data) + } else { + val compiled = engine.compile(script, context) as KotlinJsr223JvmScriptEngineBase.CompiledKotlinScript + val data = compiled.compiledData + val fos = FileOutputStream(cacheCompiled) + val oos = ObjectOutputStream(fos) + oos.writeObject(data) + oos.close() + fos.close() + compiled + } + return engine.eval(compiled, context) as String + } else { + return engine.eval(script, context) as String + } + } + + private fun eval(script: KotlinJsr223JvmScriptEngineBase.CompiledKotlinScript, env: TemplateEnvironment): String { + return script.engine.eval(script, createContext(script.engine, env)) as String + } + + private fun createContext(engine: ScriptEngine, env: TemplateEnvironment): ScriptContext { + val bindings = engine.createBindings().apply { + putAll(env.data) + put("_env", env) + } + + val context = SimpleScriptContext().apply { + setBindings(bindings, ScriptContext.ENGINE_SCOPE) + } // Hack to allow data to be accessed by name from template instead of via bindings map - val unwrapBindings = env.data.keys.map { + val unwrapBindings = env.data.keys.joinToString("\n") { val type = env.data[it]!!.type "val $it = (bindings[\"$it\"] as net.shadowfacts.ekt.EKT.TypedValue).value as $type" - }.joinToString("\n") - engine.eval(unwrapBindings) + } + engine.eval(unwrapBindings, context) - return engine.eval(script) as String + return context } interface TemplateEnvironment { @@ -146,8 +182,10 @@ _result.toString() val template: String val include: String - val cacheFile: File + val cacheScript: File get() = File(cacheDir!!, "$name.kts") + val cacheCompiled: File + get() = File(cacheDir!!, "$name.kts.compiled") fun createChild(name: String, data: Map? = null): TemplateEnvironment diff --git a/src/test/kotlin/net/shadowfacts/ekt/Test.kt b/src/test/kotlin/net/shadowfacts/ekt/Test.kt index 9757c82..a5e385f 100644 --- a/src/test/kotlin/net/shadowfacts/ekt/Test.kt +++ b/src/test/kotlin/net/shadowfacts/ekt/Test.kt @@ -1,22 +1,46 @@ package net.shadowfacts.ekt import java.io.File +import java.lang.management.ManagementFactory import kotlin.concurrent.thread /** * @author shadowfacts */ fun main(args: Array) { - for (i in 0..2) { - thread { - println("Calling from: $i") - render() - } + println(ManagementFactory.getRuntimeMXBean().name) + + for (i in 0..99) { + println("Rendering $i") + render() } + + println("done") + +// thread { +// for (i in 0..30) { +// println("Thread 1 iteration $i") +// render() +// } +// } +// +// thread { +// for (i in 0..30) { +// println("Thread 2 iteration $i") +// render() +// } +// } +// +// thread { +// for (i in 0..30) { +// println("Thread 3 iteration $i") +// render() +// } +// } } fun render(): String { - return EKT.renderClasspath("template", "/templates") { + return EKT.renderClasspath("template", "/templates", cacheDir = File("cache")) { "list" to (listOf(1, 2, 3) asType "List") } } \ No newline at end of file