Add OliveProgram scriptless replacement
This commit is contained in:
@@ -14,7 +14,7 @@ buildscript {
|
|||||||
apply plugin: 'org.jetbrains.dokka'
|
apply plugin: 'org.jetbrains.dokka'
|
||||||
|
|
||||||
project.ext {
|
project.ext {
|
||||||
openrndrVersion = "0.3.42-rc.2"
|
openrndrVersion = "0.3.42-rc.3"
|
||||||
kotlinVersion = "1.3.72"
|
kotlinVersion = "1.3.72"
|
||||||
spekVersion = "2.0.10"
|
spekVersion = "2.0.10"
|
||||||
libfreenectVersion = "0.5.7-1.5.3"
|
libfreenectVersion = "0.5.7-1.5.3"
|
||||||
@@ -57,6 +57,8 @@ allprojects {
|
|||||||
|
|
||||||
group 'org.openrndr.extra'
|
group 'org.openrndr.extra'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.openrndr.extra.kotlinparser
|
|||||||
import org.antlr.v4.runtime.CharStreams
|
import org.antlr.v4.runtime.CharStreams
|
||||||
import org.antlr.v4.runtime.CommonTokenStream
|
import org.antlr.v4.runtime.CommonTokenStream
|
||||||
import org.antlr.v4.runtime.ParserRuleContext
|
import org.antlr.v4.runtime.ParserRuleContext
|
||||||
|
import org.antlr.v4.runtime.RuleContext
|
||||||
import org.antlr.v4.runtime.misc.Interval
|
import org.antlr.v4.runtime.misc.Interval
|
||||||
import org.antlr.v4.runtime.tree.ParseTreeWalker
|
import org.antlr.v4.runtime.tree.ParseTreeWalker
|
||||||
import org.openrndr.extra.kotlin.antlr.KotlinLexer
|
import org.openrndr.extra.kotlin.antlr.KotlinLexer
|
||||||
@@ -10,14 +11,14 @@ import org.openrndr.extra.kotlin.antlr.KotlinParser
|
|||||||
import org.openrndr.extra.kotlin.antlr.KotlinParserBaseListener
|
import org.openrndr.extra.kotlin.antlr.KotlinParserBaseListener
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
fun ParserRuleContext.verbatimText(marginLeft:Int = 0, marginRight:Int = 0): String {
|
fun ParserRuleContext.verbatimText(marginLeft: Int = 0, marginRight: Int = 0): String {
|
||||||
val startIndex = start.startIndex + marginLeft
|
val startIndex = start.startIndex + marginLeft
|
||||||
val stopIndex = stop.stopIndex - marginRight
|
val stopIndex = stop.stopIndex - marginRight
|
||||||
val interval = Interval(startIndex, stopIndex)
|
val interval = Interval(startIndex, stopIndex)
|
||||||
return start.inputStream.getText(interval)
|
return start.inputStream.getText(interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImportsExtractor : KotlinParserBaseListener() {
|
class ImportsExtractor(val ruleNames: List<String>) : KotlinParserBaseListener() {
|
||||||
var result: String? = null
|
var result: String? = null
|
||||||
|
|
||||||
override fun enterImportList(ctx: KotlinParser.ImportListContext) {
|
override fun enterImportList(ctx: KotlinParser.ImportListContext) {
|
||||||
@@ -25,29 +26,34 @@ class ImportsExtractor : KotlinParserBaseListener() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LambdaExtractor(val lambdaName: String) : KotlinParserBaseListener() {
|
class LambdaExtractor(val ruleNames: List<String>, val lambdaName: String) : KotlinParserBaseListener() {
|
||||||
|
fun RuleContext.named(): String {
|
||||||
|
return ruleNames[this.ruleIndex]
|
||||||
|
}
|
||||||
var result: String? = null
|
var result: String? = null
|
||||||
override fun enterAnnotatedLambda(ctx: KotlinParser.AnnotatedLambdaContext?) {
|
override fun enterAnnotatedLambda(ctx: KotlinParser.AnnotatedLambdaContext?) {
|
||||||
val puec = ctx!!.parent!!.parent!!.parent!! as KotlinParser.PostfixUnaryExpressionContext
|
val puec = ctx!!.parent!!.parent!!.parent!! as KotlinParser.PostfixUnaryExpressionContext
|
||||||
val identifier = puec.primaryExpression().simpleIdentifier().Identifier().text
|
val identifier = puec.primaryExpression().simpleIdentifier().Identifier().text
|
||||||
if (identifier == lambdaName) {
|
if (identifier == lambdaName) {
|
||||||
result = ctx.verbatimText(1, 1)
|
if (result == null) {
|
||||||
|
result = ctx.verbatimText(1, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProgramSource(val imports: String, val programLambda: String)
|
class ProgramSource(val imports: String, val programLambda: String)
|
||||||
|
|
||||||
|
fun extractProgram(source: String, programIdentifier: String = "program"): ProgramSource {
|
||||||
|
|
||||||
fun extractProgram(source: String) : ProgramSource {
|
|
||||||
val parser = KotlinParser(CommonTokenStream(KotlinLexer(CharStreams.fromString(source))))
|
val parser = KotlinParser(CommonTokenStream(KotlinLexer(CharStreams.fromString(source))))
|
||||||
val root = parser.kotlinFile()
|
val root = parser.kotlinFile()
|
||||||
|
// val rules = parser.ruleNames.toList()
|
||||||
val importsExtractor = ImportsExtractor()
|
// val pt = TreeUtils.toPrettyTree(root, rules)
|
||||||
|
val ruleNames = parser.ruleNames.toList()
|
||||||
|
val importsExtractor = ImportsExtractor(ruleNames)
|
||||||
ParseTreeWalker.DEFAULT.walk(importsExtractor, root)
|
ParseTreeWalker.DEFAULT.walk(importsExtractor, root)
|
||||||
|
|
||||||
val lambdaExtractor = LambdaExtractor("program")
|
val lambdaExtractor = LambdaExtractor(ruleNames, programIdentifier)
|
||||||
ParseTreeWalker.DEFAULT.walk(lambdaExtractor, root)
|
ParseTreeWalker.DEFAULT.walk(lambdaExtractor, root)
|
||||||
|
|
||||||
return ProgramSource(importsExtractor.result!!, lambdaExtractor.result!!)
|
return ProgramSource(importsExtractor.result!!, lambdaExtractor.result!!)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
package org.openrndr.extra.kotlinparser
|
||||||
|
|
||||||
import org.antlr.v4.runtime.misc.Utils
|
import org.antlr.v4.runtime.misc.Utils
|
||||||
import org.antlr.v4.runtime.tree.Tree
|
import org.antlr.v4.runtime.tree.Tree
|
||||||
import org.antlr.v4.runtime.tree.Trees
|
import org.antlr.v4.runtime.tree.Trees
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ fun main() = application {
|
|||||||
|
|
||||||
extend(Olive<Program>()) {
|
extend(Olive<Program>()) {
|
||||||
script = "orx-olive/src/demo/kotlin/demo-olive-01.kts"
|
script = "orx-olive/src/demo/kotlin/demo-olive-01.kts"
|
||||||
|
|
||||||
// -- this block is for automation purposes only
|
// -- this block is for automation purposes only
|
||||||
if (System.getProperty("takeScreenshot") == "true") {
|
if (System.getProperty("takeScreenshot") == "true") {
|
||||||
scriptLoaded.listen {
|
scriptLoaded.listen {
|
||||||
|
|||||||
@@ -1,28 +1,36 @@
|
|||||||
import org.openrndr.Extension
|
import org.openrndr.Extension
|
||||||
import org.openrndr.Program
|
|
||||||
import org.openrndr.application
|
import org.openrndr.application
|
||||||
import org.openrndr.color.ColorRGBa
|
import org.openrndr.color.ColorRGBa
|
||||||
import org.openrndr.extensions.SingleScreenshot
|
import org.openrndr.extensions.SingleScreenshot
|
||||||
import org.openrndr.extra.olive.Olive
|
import org.openrndr.extra.olive.oliveProgram
|
||||||
import org.openrndr.math.IntVector2
|
import kotlin.math.cos
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
position = IntVector2(3440 / 2 + 200, 360)
|
|
||||||
width = 1280
|
width = 1280
|
||||||
height = 720
|
height = 720
|
||||||
}
|
}
|
||||||
program {
|
oliveProgram {
|
||||||
extend(Olive<Program>()) {
|
extend {
|
||||||
// -- this block is for automation purposes only
|
drawer.clear(ColorRGBa.GRAY)
|
||||||
if (System.getProperty("takeScreenshot") == "true") {
|
drawer.fill = ColorRGBa.WHITE
|
||||||
scriptLoaded.listen {
|
for (i in 0 until 100) {
|
||||||
|
drawer.circle(
|
||||||
|
width / 2.0 + cos(seconds + i) * 320.0,
|
||||||
|
i * 7.2,
|
||||||
|
cos(i + seconds * 0.5) * 20.0 + 20.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -- this is only needed for the automated screenshots
|
||||||
|
.olive.scriptLoaded.listen {
|
||||||
|
if (System.getProperty("takeScreenshot") == "true") {
|
||||||
// -- this is a bit of hack, we need to push the screenshot extension in front of the olive one
|
// -- this is a bit of hack, we need to push the screenshot extension in front of the olive one
|
||||||
fun <T : Extension> Program.extendHead(extension: T, configure: T.() -> Unit): T {
|
fun <T : Extension> extendHead(extension: T, configure: T.() -> Unit): T {
|
||||||
extensions.add(0, extension)
|
program.extensions.add(0, extension)
|
||||||
extension.configure()
|
extension.configure()
|
||||||
extension.setup(this)
|
extension.setup(program)
|
||||||
return extension
|
return extension
|
||||||
}
|
}
|
||||||
extendHead(SingleScreenshot()) {
|
extendHead(SingleScreenshot()) {
|
||||||
@@ -30,17 +38,5 @@ fun main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
script = "orx-olive/src/demo/kotlin/DemoOliveScriptless01.kt"
|
|
||||||
program {
|
|
||||||
extend {
|
|
||||||
drawer.background(ColorRGBa.PINK)
|
|
||||||
drawer.circle(width /2.0, height / 2.0, 300.0 + Math.cos(seconds)*100.0)
|
|
||||||
|
|
||||||
drawer.fill = ColorRGBa.RED
|
|
||||||
drawer.circle(mouse.position, 100.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import org.openrndr.draw.*
|
|||||||
{ program: Program ->
|
{ program: Program ->
|
||||||
program.apply {
|
program.apply {
|
||||||
extend {
|
extend {
|
||||||
drawer.background(ColorRGBa.GRAY)
|
drawer.clear(ColorRGBa.GRAY)
|
||||||
drawer.fill = ColorRGBa.PINK
|
drawer.fill = ColorRGBa.PINK
|
||||||
drawer.circle(width/2.0, height/2.0 ,200.0)
|
drawer.circle(width/2.0, height/2.0 ,200.0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ enum class OliveScriptHost {
|
|||||||
|
|
||||||
data class ScriptLoadedEvent(val scriptFile: String)
|
data class ScriptLoadedEvent(val scriptFile: String)
|
||||||
|
|
||||||
class Olive<P : Program>(val resources: Resources? = null, private var scriptless: Boolean = false) : Extension {
|
enum class ScriptMode {
|
||||||
|
KOTLIN_SCRIPT,
|
||||||
|
OLIVE_PROGRAM
|
||||||
|
}
|
||||||
|
|
||||||
|
class Olive<P : Program>(val resources: Resources? = null, private var scriptMode: ScriptMode = ScriptMode.KOTLIN_SCRIPT) : Extension {
|
||||||
override var enabled: Boolean = true
|
override var enabled: Boolean = true
|
||||||
var session: Session? = null
|
var session: Session? = null
|
||||||
var scriptHost = OliveScriptHost.JSR223_REUSE
|
var scriptHost = OliveScriptHost.JSR223_REUSE
|
||||||
@@ -44,12 +49,16 @@ class Olive<P : Program>(val resources: Resources? = null, private var scriptles
|
|||||||
|
|
||||||
internal var scriptChange: (String) -> Unit = {}
|
internal var scriptChange: (String) -> Unit = {}
|
||||||
|
|
||||||
var script = if (!scriptless) "src/main/kotlin/${stackRootClassName().split(".").last()}.kts"
|
var script = when (scriptMode) {
|
||||||
else "src/main/kotlin/${stackRootClassName().split(".").last()}.kt"
|
ScriptMode.KOTLIN_SCRIPT -> "src/main/kotlin/${stackRootClassName().split(".").last()}.kts"
|
||||||
|
else -> "src/main/kotlin/${stackRootClassName().split(".").last()}.kt"
|
||||||
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
|
// require(scriptMode == ScriptMode.KOTLIN_SCRIPT) {
|
||||||
|
// "can only change the script in KOTLIN_SCRIPT mode"
|
||||||
|
// }
|
||||||
field = value
|
field = value
|
||||||
scriptChange(value)
|
scriptChange(value)
|
||||||
scriptless = value.endsWith(".kt")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,12 +124,13 @@ class Olive<P : Program>(val resources: Resources? = null, private var scriptles
|
|||||||
try {
|
try {
|
||||||
logger.info("change detected, reloading script")
|
logger.info("change detected, reloading script")
|
||||||
|
|
||||||
val scriptContents = if (!scriptless) {
|
val scriptContents = when(scriptMode) {
|
||||||
it.readText()
|
ScriptMode.KOTLIN_SCRIPT -> it.readText()
|
||||||
} else {
|
ScriptMode.OLIVE_PROGRAM -> {
|
||||||
val source = it.readText()
|
val source = it.readText()
|
||||||
val programSource = extractProgram(source)
|
val programSource = extractProgram(source, programIdentifier = "oliveProgram")
|
||||||
generateScript(programSource)
|
generateScript<OliveProgram>(programSource)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val futureFunc = GlobalScope.async {
|
val futureFunc = GlobalScope.async {
|
||||||
@@ -181,10 +191,5 @@ class Olive<P : Program>(val resources: Resources? = null, private var scriptles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun program(f: Program.() -> Unit) {
|
|
||||||
require(script.endsWith(".kt")) {
|
|
||||||
"""program bodies are only allowed in 'scriptless' mode"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
43
orx-olive/src/main/kotlin/OliveProgram.kt
Normal file
43
orx-olive/src/main/kotlin/OliveProgram.kt
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package org.openrndr.extra.olive
|
||||||
|
|
||||||
|
import org.openrndr.ApplicationBuilder
|
||||||
|
import org.openrndr.Program
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.streams.toList
|
||||||
|
|
||||||
|
open class OliveProgram(private val sourceLocation: String) : Program() {
|
||||||
|
val olive = extend(Olive<OliveProgram>(scriptMode = ScriptMode.OLIVE_PROGRAM)) {
|
||||||
|
script = sourceLocation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stackRootClassName(thread: Thread = Thread.currentThread(), sanitize: Boolean = true): String {
|
||||||
|
val root = Thread.currentThread().stackTrace.last()
|
||||||
|
val rootClass = root.className
|
||||||
|
return if (sanitize) rootClass.replace(Regex("Kt$"), "") else rootClass
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ApplicationBuilder.oliveProgram(init: OliveProgram.() -> Unit): OliveProgram {
|
||||||
|
val rootClassName = stackRootClassName(sanitize = true)
|
||||||
|
|
||||||
|
var sourceLocation = "src/main/kotlin/$rootClassName.kt"
|
||||||
|
val candidateFile = File(sourceLocation)
|
||||||
|
if (!candidateFile.exists()) {
|
||||||
|
val otherCandidates = Files.walk(Paths.get("."))
|
||||||
|
.filter { Files.isRegularFile(it) && it.toString().endsWith("$rootClassName.kt") }.toList()
|
||||||
|
if (otherCandidates.size == 1) {
|
||||||
|
sourceLocation = otherCandidates.first().toString()
|
||||||
|
} else {
|
||||||
|
error("multiple source candidates found: $otherCandidates")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
program = object : OliveProgram(sourceLocation) {
|
||||||
|
override fun setup() {
|
||||||
|
super.setup()
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return program as OliveProgram
|
||||||
|
}
|
||||||
@@ -2,16 +2,19 @@ package org.openrndr.extra.olive
|
|||||||
|
|
||||||
import org.openrndr.extra.kotlinparser.ProgramSource
|
import org.openrndr.extra.kotlinparser.ProgramSource
|
||||||
|
|
||||||
fun generateScript(programSource: ProgramSource): String {
|
inline fun <reified T> generateScript(programSource: ProgramSource): String {
|
||||||
return """
|
val script = """
|
||||||
@file:Suppress("UNUSED_LAMBDA_EXPRESSION")
|
@file:Suppress("UNUSED_LAMBDA_EXPRESSION")
|
||||||
|
|
||||||
|
import org.openrndr.extra.olive.OliveProgram
|
||||||
${programSource.imports}
|
${programSource.imports}
|
||||||
|
|
||||||
{ program: Program ->
|
{ program: ${T::class.simpleName} ->
|
||||||
program.apply {
|
program.apply {
|
||||||
${programSource.programLambda}
|
${programSource.programLambda}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
println(script)
|
||||||
|
return script
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@ class ScriptObjectLoader(classLoader: ClassLoader? = Thread.currentThread().cont
|
|||||||
fun <R> safeEval(evaluation: () -> R?) = try {
|
fun <R> safeEval(evaluation: () -> R?) = try {
|
||||||
evaluation()
|
evaluation()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
throw LoadException("Cannot load script", e)
|
throw LoadException("Cannot load script", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user