Skip to content

Commit e030a27

Browse files
committed
finalize
1 parent 24c6bfe commit e030a27

File tree

5 files changed

+113
-146
lines changed

5 files changed

+113
-146
lines changed

src/main/kotlin/dev/echonine/kite/Kite.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Kite : JavaPlugin() {
1818
var INSTANCE: Kite? = null
1919
private set
2020
// Global instance of ImportsCache for ease of access.
21-
var CACHE: ImportsCache? = null
21+
var IMPORTS_CACHE: ImportsCache? = null
2222
}
2323

2424
object Structure {
@@ -42,12 +42,14 @@ class Kite : JavaPlugin() {
4242
return@lazy false
4343
}
4444
}
45+
// Bump this if cache is no longer compatible between releases.
46+
const val CACHE_VERSION = "1"
4547
}
4648

4749
override fun onEnable() {
4850
INSTANCE = this
4951
// Initializing ImportsCache.
50-
CACHE = ImportsCache()
52+
IMPORTS_CACHE = ImportsCache()
5153
// Initializing ScriptManager and loading all scripts.
5254
this.scriptManager = ScriptManager(this)
5355
this.scriptManager.loadAll()

src/main/kotlin/dev/echonine/kite/api/annotations/Annotations.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ annotation class Repository(val repository: String)
5858
@Repeatable
5959
@Target(AnnotationTarget.FILE)
6060
@Retention(AnnotationRetention.RUNTIME)
61-
annotation class Dependency(val dependency: String)
61+
annotation class Dependency(val dependency: String, val withTransitiveDependencies: Boolean = true)
6262

6363
/**
6464
* Configures a relocation rule to apply on a matching [Dependency].

src/main/kotlin/dev/echonine/kite/scripting/ScriptManager.kt

Lines changed: 67 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,43 @@
11
package dev.echonine.kite.scripting
22

33
import dev.echonine.kite.Kite
4+
import dev.echonine.kite.scripting.configuration.KiteCompilationConfiguration
5+
import dev.echonine.kite.scripting.configuration.KiteEvaluationConfiguration
46
import dev.echonine.kite.util.extensions.errorRich
57
import dev.echonine.kite.util.extensions.infoRich
68
import dev.echonine.kite.util.extensions.nameWithoutExtensions
79
import dev.echonine.kite.util.extensions.warnRich
8-
import dev.echonine.kite.scripting.configuration.KiteCompilationConfiguration
9-
import dev.echonine.kite.scripting.configuration.KiteEvaluationConfiguration
1010
import kotlinx.coroutines.CoroutineScope
1111
import kotlinx.coroutines.Dispatchers
12-
import kotlinx.coroutines.async
13-
import kotlinx.coroutines.awaitAll
1412
import kotlinx.coroutines.launch
1513
import kotlinx.coroutines.runBlocking
1614
import kotlinx.coroutines.suspendCancellableCoroutine
1715
import kotlinx.coroutines.withContext
1816
import net.kyori.adventure.text.logger.slf4j.ComponentLogger
1917
import org.jetbrains.annotations.Unmodifiable
20-
import java.util.concurrent.Executors
18+
import java.util.concurrent.atomic.AtomicBoolean
2119
import kotlin.coroutines.resume
2220
import kotlin.script.experimental.api.ResultWithDiagnostics
21+
import kotlin.script.experimental.api.ScriptCompilationConfiguration
2322
import kotlin.script.experimental.api.ScriptDiagnostic.Severity
23+
import kotlin.script.experimental.api.asSuccess
2424
import kotlin.script.experimental.api.displayName
2525
import kotlin.script.experimental.api.implicitReceivers
26+
import kotlin.script.experimental.api.importScripts
2627
import kotlin.script.experimental.api.providedProperties
28+
import kotlin.script.experimental.api.refineConfiguration
2729
import kotlin.script.experimental.api.with
30+
import kotlin.script.experimental.host.FileScriptSource
2831
import kotlin.script.experimental.host.toScriptSource
2932
import kotlin.script.experimental.jvm.baseClassLoader
3033
import kotlin.script.experimental.jvm.jvm
3134
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
35+
import kotlin.time.measureTime
3236
import kotlin.time.measureTimedValue
3337

3438
internal class ScriptManager(val plugin: Kite) {
35-
3639
private val logger = ComponentLogger.logger("Kite")
3740
private val loadedScripts = mutableMapOf<String, ScriptContext>()
38-
private val logExecutor = Executors.newSingleThreadExecutor()
3941
private val scriptingHost = BasicJvmScriptingHost()
4042

4143
/**
@@ -65,71 +67,59 @@ internal class ScriptManager(val plugin: Kite) {
6567
* Compiles and loads all available scripts.
6668
*/
6769
fun loadAll() {
68-
val scriptHolders = gatherAvailableScriptFiles()
70+
val scriptHolders = gatherAvailableScriptFiles().sortedBy { it.name }
6971
logger.infoRich("Compiling <yellow>${scriptHolders.size} <reset>script(s)...")
70-
// Compiling all available scripts in parallel.
71-
val compiledScripts = runBlocking {
72-
// primeScriptingEngine()
73-
scriptHolders.map { holder -> async(Dispatchers.Default) {
74-
try {
75-
compileScriptAsync(holder)
76-
} catch (e: Throwable) {
77-
// Logging error(s) to the console and returning null.
78-
logger.errorRich("Script <yellow>${holder.name}</yellow> reported error(s) during compilation.")
79-
logger.errorRich(" (1) ${e.javaClass.name}: ${e.message}")
80-
if (e.cause != null)
81-
logger.errorRich(" (2) ${e.cause!!.javaClass.name}: ${e.cause!!.message}")
82-
return@async null
83-
}
84-
}}.awaitAll().filterNotNull()
85-
}
86-
logExecutor.submit {
87-
logger.infoRich("Loading <yellow>${compiledScripts.size}<reset> out of <yellow>${scriptHolders.size}<reset> scripts...")
88-
}
89-
// Calling 'onLoad' for each compiled script and adding it to the list of loaded scripts.
90-
compiledScripts.forEach { script ->
91-
loadedScripts[script.name] = script
92-
script.runOnLoad()
93-
// Logging message(s) from a single-thread queue to keep correct order.
94-
logExecutor.submit {
95-
logger.infoRich("Script <yellow>${script.name} <reset>has been successfully loaded.")
72+
val (compiledScripts, elapsedCompilationTime) = measureTimedValue {
73+
runBlocking {
74+
scriptHolders.mapNotNull { compileScriptAsync(it) }
9675
}
9776
}
98-
// Logging message(s) from a single-thread queue to keep the correct order.
99-
logExecutor.submit {
100-
logger.infoRich("Successfully loaded <yellow>${loadedScripts.size} <reset>out of total <yellow>${scriptHolders.size}<reset> scripts.")
77+
logger.infoRich("Successfully compiled <yellow>${compiledScripts.size} <reset>out of total <yellow>${scriptHolders.size}<reset> scripts. Loading them now...")
78+
// Loading compiled scripts.
79+
val elapsedExecutionTime = measureTime {
80+
for (script in compiledScripts) try {
81+
loadedScripts[script.name] = script
82+
script.runOnLoad()
83+
} catch (e: Exception) {
84+
e.printStackTrace()
85+
}
10186
}
87+
logger.infoRich("Successfully loaded <yellow>${loadedScripts.size} <reset>out of total <yellow>${compiledScripts.size}<reset> scripts.")
88+
logger.infoRich("(Compilation: <yellow>${elapsedCompilationTime.inWholeMilliseconds}ms</yellow>, Execution: <yellow>${elapsedExecutionTime.inWholeMilliseconds}ms</yellow>)")
10289
}
10390

104-
// // Primes the scripting engine by compiling a simple hello world script.
105-
// private suspend fun primeScriptingEngine() = withContext(Dispatchers.IO) {
106-
// logger.infoRich("Priming scripting engine...")
107-
// val helloWorldScript = """
108-
// println("Hello, World!")
109-
// """.trimIndent()
110-
// val compilationConfiguration = KiteCompilationConfiguration.with {
111-
// displayName("primer")
112-
// }
113-
// val evaluationConfiguration = KiteEvaluationConfiguration.with {
114-
// jvm {
115-
// baseClassLoader(Kite::class.java.classLoader)
116-
// }
117-
// }
118-
// BasicJvmScriptingHost().eval(helloWorldScript.toScriptSource(), compilationConfiguration, evaluationConfiguration)
119-
// }
120-
121-
122-
12391
/**
12492
* Compiles script backed by the specified [ScriptHolder] instance and returns the result.
12593
* In case compilation fails, a `null` is returned instead.
12694
*/
12795
private suspend fun compileScriptAsync(holder: ScriptHolder): ScriptContext? = withContext(Dispatchers.IO) {
12896
// Creating a new instance of ScriptContext. Name of the script either file name with no extensions or script's folder name.
12997
val script = ScriptContext(holder.name, holder.entryPoint)
130-
// Creating compilation and evaluation configurations from Kite templates.
98+
val wasCacheInvalidated = AtomicBoolean(false)
99+
// Creating CompilationConfiguration based on KiteCompilationConfiguration template.
131100
val compilationConfiguration = KiteCompilationConfiguration.with {
132101
displayName(script.name)
102+
refineConfiguration {
103+
beforeCompiling { context ->
104+
return@beforeCompiling ScriptCompilationConfiguration(context.compilationConfiguration) {
105+
runBlocking {
106+
// Invalidating the cache. We need to make sure it will be triggered only once, for the main script.
107+
// Imports cache will then be populated with new entries in the next step.
108+
// Since this callback runs for *all* scripts - including @Import-ed ones - imports will be included recursively.
109+
if (wasCacheInvalidated.get() == false) {
110+
Kite.IMPORTS_CACHE!!.invalidate(script.name)
111+
wasCacheInvalidated.set(true)
112+
}
113+
// Getting all imported scripts added to the configuration via annotation processor.
114+
val imports = context.compilationConfiguration[ScriptCompilationConfiguration.importScripts]
115+
// Writing non-empty imported script paths to the imports cache.
116+
imports?.mapNotNull { (it as? FileScriptSource)?.file?.canonicalPath }?.also {
117+
Kite.IMPORTS_CACHE?.append(script.name, it)
118+
}
119+
}
120+
}.asSuccess()
121+
}
122+
}
133123
}
134124
// Getting the cache file and its last modified date before compilation. Used in a later step for determining whether cache was used or not.
135125
val cacheFile = Kite.Structure.CACHE_DIR.listFiles()?.firstOrNull { it.name.startsWith("${script.name}.") && it.name.endsWith(".cache.jar") }
@@ -145,33 +135,33 @@ internal class ScriptManager(val plugin: Kite) {
145135
"server" to plugin.server
146136
))
147137
}
148-
// Invalidating imports cache of the script before compilation.
149-
// It will then be populated with new entries by KiteCompilationConfiguration.
150-
Kite.CACHE?.invalidate(holder.name)
151-
// Compiling/loading the script. Time the operation to verify cache performance.
138+
// Compiling/loading the script. Measure operation time to verify cache performance.
152139
val (compiledScript, elapsedTime) = measureTimedValue {
153140
scriptingHost.eval(holder.entryPoint.toScriptSource(), compilationConfiguration, evaluationConfiguration)
154141
}
155-
// Logging diagnostics from a single-thread queue. Otherwise, messages can be displayed in wrong order due to parallel compilation.
156-
logExecutor.submit {
142+
// Logging message after *failed* compilation, but before diagnostics.
143+
if (compiledScript is ResultWithDiagnostics.Failure) {
144+
val errorCount = compiledScript.reports.count { it.severity == Severity.FATAL || it.severity == Severity.ERROR }
145+
logger.errorRich("Script <yellow>${holder.name}</yellow> reported <yellow>$errorCount</yellow> error(s) during compilation:")
146+
}
147+
// Logging diagnostics to the console.
148+
compiledScript.reports.forEach {
149+
val message = "<yellow>(${script.name}, ${it.sourcePath?.substringAfterLast("/")}, line ${it.location?.start?.line ?: "???"}, col ${it.location?.start?.col ?: "???"})</yellow> ${it.message}"
150+
when (it.severity) {
151+
Severity.INFO -> logger.infoRich(message)
152+
Severity.WARNING -> logger.warnRich(message)
153+
Severity.ERROR,
154+
Severity.FATAL -> logger.errorRich(message)
155+
else -> {}
156+
}
157+
}
158+
// Logging message after *successful* compilation, and after diagnostics.
159+
if (compiledScript is ResultWithDiagnostics.Success) {
157160
if (cacheFile != null && cacheFile.lastModified() == cacheLastModified) {
158161
logger.infoRich("Compiled <yellow>${holder.name}</yellow> from cache in <yellow>${elapsedTime.inWholeMilliseconds}ms</yellow>.")
159162
} else {
160163
logger.infoRich("Compiled <yellow>${holder.name}</yellow> in <yellow>${elapsedTime.inWholeMilliseconds}ms</yellow>.")
161164
}
162-
val errorCount = compiledScript.reports.count { it.severity == Severity.FATAL || it.severity == Severity.ERROR || it.severity == Severity.WARNING }
163-
if (compiledScript is ResultWithDiagnostics.Failure)
164-
logger.errorRich("Script <yellow>${holder.name}</yellow> reported <yellow>$errorCount</yellow> error(s) during compilation:")
165-
compiledScript.reports.forEach {
166-
val message = "<yellow>(${script.name}, ${it.sourcePath?.substringAfterLast("/")}, line ${it.location?.start?.line ?: "???"}, col ${it.location?.start?.col ?: "???"})</yellow> ${it.message}"
167-
when (it.severity) {
168-
Severity.INFO -> logger.infoRich(message)
169-
Severity.WARNING -> logger.warnRich(message)
170-
Severity.ERROR,
171-
Severity.FATAL -> logger.errorRich(message)
172-
else -> {}
173-
}
174-
}
175165
}
176166
// Returning ScriptContext instance if compilation was successful, or null otherwise.
177167
return@withContext if (compiledScript !is ResultWithDiagnostics.Failure) script else null
@@ -191,11 +181,10 @@ internal class ScriptManager(val plugin: Kite) {
191181
return load(holder)
192182
}
193183

194-
// Compiles and loads specified script file.
184+
// Compiles and loads specified script holder.
195185
private suspend fun load(holder: ScriptHolder): Boolean = suspendCancellableCoroutine { coroutine ->
196186
CoroutineScope(Dispatchers.Default).launch {
197187
try {
198-
// Compiling and loading specified script.
199188
compileScriptAsync(holder)?.let { script ->
200189
plugin.server.globalRegionScheduler.execute(plugin, {
201190
loadedScripts[script.name] = script

0 commit comments

Comments
 (0)