Generalized Contexts for multiplatform
This commit is contained in:
parent
83a56fa0cd
commit
ec833dd13c
@ -1,8 +1,8 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
extra["kotlinVersion"] = "1.3.11"
|
extra["kotlinVersion"] = "1.3.11"
|
||||||
extra["ioVersion"] = "0.1.2-dev-2"
|
extra["ioVersion"] = "0.1.2"
|
||||||
extra["serializationVersion"] = "0.9.1"
|
extra["serializationVersion"] = "0.9.1"
|
||||||
extra["coroutinesVersion"] = "1.0.1"
|
extra["coroutinesVersion"] = "1.1.0"
|
||||||
|
|
||||||
val kotlinVersion: String by extra
|
val kotlinVersion: String by extra
|
||||||
val ioVersion: String by extra
|
val ioVersion: String by extra
|
||||||
|
@ -9,7 +9,7 @@ repositories {
|
|||||||
kotlin {
|
kotlin {
|
||||||
targets {
|
targets {
|
||||||
fromPreset(presets.jvm, 'jvm')
|
fromPreset(presets.jvm, 'jvm')
|
||||||
//fromPreset(presets.js, 'js')
|
fromPreset(presets.js, 'js')
|
||||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||||
@ -21,11 +21,19 @@ kotlin {
|
|||||||
api project(":dataforge-meta")
|
api project(":dataforge-meta")
|
||||||
api "org.jetbrains.kotlin:kotlin-reflect"
|
api "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
api "io.github.microutils:kotlin-logging-common:1.6.10"
|
api "io.github.microutils:kotlin-logging-common:1.6.10"
|
||||||
|
api "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain{
|
jvmMain{
|
||||||
dependencies{
|
dependencies{
|
||||||
api "io.github.microutils:kotlin-logging:1.6.10"
|
api "io.github.microutils:kotlin-logging:1.6.10"
|
||||||
|
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsMain{
|
||||||
|
dependencies{
|
||||||
|
api "io.github.microutils:kotlin-logging-js:1.6.10"
|
||||||
|
api "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package hep.dataforge.context
|
||||||
|
|
||||||
|
import hep.dataforge.meta.EmptyMeta
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
|
||||||
|
abstract class AbstractPlugin(override val meta: Meta = EmptyMeta) : Plugin {
|
||||||
|
|
||||||
|
private var _context: Context? = null
|
||||||
|
|
||||||
|
override val context: Context
|
||||||
|
get() = _context ?: error("Plugin $tag is not attached")
|
||||||
|
|
||||||
|
override fun attach(context: Context) {
|
||||||
|
this._context = context
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun detach() {
|
||||||
|
this._context = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun provideTop(target: String, name: Name): Any? = null
|
||||||
|
|
||||||
|
override fun listTop(target: String): Sequence<Name> = emptySequence()
|
||||||
|
}
|
@ -6,8 +6,11 @@ import hep.dataforge.names.toName
|
|||||||
import hep.dataforge.provider.Provider
|
import hep.dataforge.provider.Provider
|
||||||
import hep.dataforge.provider.provideAll
|
import hep.dataforge.provider.provideAll
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import mu.KLogger
|
import mu.KLogger
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,12 +25,12 @@ import kotlin.reflect.KClass
|
|||||||
* Since plugins could contain mutable state, context has two states: active and inactive. No changes are allowed to active context.
|
* Since plugins could contain mutable state, context has two states: active and inactive. No changes are allowed to active context.
|
||||||
* @author Alexander Nozik
|
* @author Alexander Nozik
|
||||||
*/
|
*/
|
||||||
interface Context : Named, MetaRepr, Provider {
|
interface Context : Named, MetaRepr, Provider, CoroutineScope {
|
||||||
|
|
||||||
val parent: Context?
|
val parent: Context?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context properties. Working as substitutes for environment variables
|
* Context properties. Working as substitute for environment variables
|
||||||
*/
|
*/
|
||||||
val properties: Meta
|
val properties: Meta
|
||||||
|
|
||||||
@ -46,11 +49,6 @@ interface Context : Named, MetaRepr, Provider {
|
|||||||
*/
|
*/
|
||||||
val isActive: Boolean
|
val isActive: Boolean
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide services for given type
|
|
||||||
*/
|
|
||||||
fun <T : Any> services(type: KClass<T>): Sequence<T>
|
|
||||||
|
|
||||||
override val defaultTarget: String get() = Plugin.PLUGIN_TARGET
|
override val defaultTarget: String get() = Plugin.PLUGIN_TARGET
|
||||||
|
|
||||||
override fun provideTop(target: String, name: Name): Any? {
|
override fun provideTop(target: String, name: Name): Any? {
|
||||||
@ -79,10 +77,19 @@ interface Context : Named, MetaRepr, Provider {
|
|||||||
*/
|
*/
|
||||||
fun deactivate(activator: Any)
|
fun deactivate(activator: Any)
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = Dispatchers.Default
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detach all plugins and terminate context
|
* Detach all plugins and terminate context
|
||||||
*/
|
*/
|
||||||
fun close()
|
fun close()
|
||||||
|
|
||||||
|
override fun toMeta(): Meta = buildMeta {
|
||||||
|
"parent" to parent?.name
|
||||||
|
"properties" to properties.seal()
|
||||||
|
"plugins" to plugins.map { it.toMeta() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,7 +102,11 @@ inline fun <reified T : Any> Context.list(target: String): Sequence<T> {
|
|||||||
/**
|
/**
|
||||||
* A global root context
|
* A global root context
|
||||||
*/
|
*/
|
||||||
expect object Global : Context
|
expect object Global : Context{
|
||||||
|
fun getContext(name: String): Context
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The interface for something that encapsulated in context
|
* The interface for something that encapsulated in context
|
||||||
|
@ -46,7 +46,7 @@ interface Plugin : Named, Metoid, ContextAware, Provider, MetaRepr {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun dependsOn(): List<PluginTag>
|
fun dependsOn(): List<PluginTag> = emptyList()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start this plugin and attach registration info to the context. This method
|
* Start this plugin and attach registration info to the context. This method
|
||||||
|
@ -10,16 +10,17 @@ interface PluginFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
object PluginRepository {
|
expect object PluginRepository {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List plugins available in the repository
|
* List plugins available in the repository
|
||||||
*/
|
*/
|
||||||
fun list(): Sequence<PluginFactory> = Global.services(PluginFactory::class)
|
fun list(): Sequence<PluginFactory>
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch specific plugin and instantiate it with given meta
|
|
||||||
*/
|
|
||||||
fun fetch(tag: PluginTag, meta: Meta): Plugin = PluginRepository.list().find { it.tag.matches(tag) }?.build(meta)
|
|
||||||
?: error("Plugin with tag $tag not found in the repository")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch specific plugin and instantiate it with given meta
|
||||||
|
*/
|
||||||
|
fun PluginRepository.fetch(tag: PluginTag, meta: Meta): Plugin = PluginRepository.list().find { it.tag.matches(tag) }?.build(meta)
|
||||||
|
?: error("Plugin with tag $tag not found in the repository")
|
@ -0,0 +1,83 @@
|
|||||||
|
package hep.dataforge.context
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import mu.KLogger
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import kotlin.jvm.Synchronized
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
actual object Global: Context, JSContext("GLOBAL", null){
|
||||||
|
/**
|
||||||
|
* Closing all contexts
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
override fun close() {
|
||||||
|
logger.info{"Shutting down GLOBAL"}
|
||||||
|
for (ctx in contextRegistry.values) {
|
||||||
|
ctx.close()
|
||||||
|
}
|
||||||
|
super.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val contextRegistry = HashMap<String, Context>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get previously builder context o builder a new one
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
actual fun getContext(name: String): Context {
|
||||||
|
return contextRegistry.getOrPut(name) { JSContext(name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class JSContext(
|
||||||
|
final override val name: String,
|
||||||
|
final override val parent: JSContext? = Global,
|
||||||
|
properties: Meta = EmptyMeta
|
||||||
|
): Context {
|
||||||
|
|
||||||
|
private val _properties = Config().apply { update(properties) }
|
||||||
|
override val properties: Meta
|
||||||
|
get() = if (parent == null) {
|
||||||
|
_properties
|
||||||
|
} else {
|
||||||
|
Laminate(_properties, parent.properties)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val plugins: PluginManager by lazy { PluginManager(this) }
|
||||||
|
override val logger: KLogger = KotlinLogging.logger(name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property showing that dispatch thread is started in the context
|
||||||
|
*/
|
||||||
|
private var started = false
|
||||||
|
|
||||||
|
override fun <T : Any> services(type: KClass<T>): Sequence<T> = TODO("Not implemented")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free up resources associated with this context
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
override fun close() {
|
||||||
|
if (isActive) error("Can't close active context")
|
||||||
|
//detach all plugins
|
||||||
|
plugins.forEach { it.detach() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val activators = HashSet<Any>()
|
||||||
|
|
||||||
|
override val isActive: Boolean = !activators.isEmpty()
|
||||||
|
|
||||||
|
override fun activate(activator: Any) {
|
||||||
|
activators.add(activator)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deactivate(activator: Any) {
|
||||||
|
activators.clear()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package hep.dataforge.context
|
||||||
|
|
||||||
|
actual object PluginRepository {
|
||||||
|
/**
|
||||||
|
* List plugins available in the repository
|
||||||
|
*/
|
||||||
|
actual fun list(): Sequence<PluginFactory> {
|
||||||
|
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -42,7 +42,6 @@ actual object Global : Context, JVMContext("GLOBAL", null, Thread.currentThread(
|
|||||||
*
|
*
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
@Throws(Exception::class)
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
logger.info("Shutting down GLOBAL")
|
logger.info("Shutting down GLOBAL")
|
||||||
for (ctx in contextRegistry.values) {
|
for (ctx in contextRegistry.values) {
|
||||||
@ -60,20 +59,7 @@ actual object Global : Context, JVMContext("GLOBAL", null, Thread.currentThread(
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getContext(name: String): Context {
|
actual fun getContext(name: String): Context {
|
||||||
return contextRegistry.getOrPut(name) { JVMContext(name) }
|
return contextRegistry.getOrPut(name) { JVMContext(name) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Close all contexts and terminate framework
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
fun terminate() {
|
|
||||||
try {
|
|
||||||
close()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logger.error("Exception while terminating DataForge framework", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package hep.dataforge.context
|
package hep.dataforge.context
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import mu.KLogger
|
import mu.KLogger
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
@ -23,6 +24,7 @@ import java.util.*
|
|||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import kotlin.collections.HashSet
|
import kotlin.collections.HashSet
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.full.cast
|
import kotlin.reflect.full.cast
|
||||||
|
|
||||||
@ -72,23 +74,10 @@ open class JVMContext(
|
|||||||
|
|
||||||
private val serviceCache: MutableMap<Class<*>, ServiceLoader<*>> = HashMap()
|
private val serviceCache: MutableMap<Class<*>, ServiceLoader<*>> = HashMap()
|
||||||
|
|
||||||
override fun <T : Any> services(type: KClass<T>): Sequence<T> {
|
fun <T : Any> services(type: KClass<T>): Sequence<T> {
|
||||||
return serviceCache.getOrPut(type.java) { ServiceLoader.load(type.java, classLoader) }.asSequence().map { type.cast(it) }
|
return serviceCache.getOrPut(type.java) { ServiceLoader.load(type.java, classLoader) }.asSequence().map { type.cast(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get identity for this context
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
override fun toMeta(): Meta {
|
|
||||||
return buildMeta {
|
|
||||||
"parent" to parent?.name
|
|
||||||
"properties" to properties.seal()
|
|
||||||
"plugins" to plugins.map { it.toMeta() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free up resources associated with this context
|
* Free up resources associated with this context
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package hep.dataforge.context
|
||||||
|
|
||||||
|
actual object PluginRepository {
|
||||||
|
/**
|
||||||
|
* List plugins available in the repository
|
||||||
|
*/
|
||||||
|
actual fun list(): Sequence<PluginFactory> = Global.services(PluginFactory::class)
|
||||||
|
|
||||||
|
}
|
@ -9,7 +9,7 @@ repositories {
|
|||||||
kotlin {
|
kotlin {
|
||||||
targets {
|
targets {
|
||||||
fromPreset(presets.jvm, 'jvm')
|
fromPreset(presets.jvm, 'jvm')
|
||||||
fromPreset(presets.js, 'js')
|
//fromPreset(presets.js, 'js')
|
||||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||||
@ -28,11 +28,5 @@ kotlin {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jsMain {
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,12 +1,27 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.context.ContextAware
|
import hep.dataforge.context.ContextAware
|
||||||
import hep.dataforge.meta.EmptyMeta
|
import hep.dataforge.meta.EmptyMeta
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic way to render any object in the output.
|
* A generic way to render any object in the output.
|
||||||
|
*
|
||||||
|
* An object could be rendered either in append or overlay mode. The mode is decided by the [Output]
|
||||||
|
* based on its configuration and provided meta
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
interface Output<in T: Any> : ContextAware {
|
interface Output<in T : Any> : ContextAware {
|
||||||
|
/**
|
||||||
|
* Render specific object with configuration.
|
||||||
|
*
|
||||||
|
* By convention actual render is called in asynchronous mode, so this method should never
|
||||||
|
* block execution
|
||||||
|
*/
|
||||||
fun render(obj: T, meta: Meta = EmptyMeta)
|
fun render(obj: T, meta: Meta = EmptyMeta)
|
||||||
}
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import hep.dataforge.context.Plugin
|
||||||
|
import hep.dataforge.meta.EmptyMeta
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.names.EmptyName
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A manager for outputs
|
||||||
|
*/
|
||||||
|
interface OutputManager : Plugin {
|
||||||
|
/**
|
||||||
|
* Provide an output for given name and stage.
|
||||||
|
*
|
||||||
|
* @param stage represents the node or directory for the output. Empty means root node.
|
||||||
|
* @param name represents the name inside the node.
|
||||||
|
* @param meta configuration for [Output] (not for rendered object)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
operator fun get(name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<Any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an output specialized for giver ntype
|
||||||
|
*/
|
||||||
|
fun <T : Any> typed(type: KClass<T>, name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<T>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Any> OutputManager.typed(name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<T> {
|
||||||
|
return typed(T::class, name, stage, meta)
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import hep.dataforge.context.Context
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
class TextOutput(override val context: Context, private val output: kotlinx.io.core.Output) : Output<Any> {
|
||||||
|
override fun render(obj: Any, meta: Meta) {
|
||||||
|
context.launch(Dispatchers.IO) {
|
||||||
|
output.append(obj.toString())
|
||||||
|
output.append('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -102,3 +102,5 @@ operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens)
|
|||||||
operator fun Name.plus(other: String): Name = this + other.toName()
|
operator fun Name.plus(other: String): Name = this + other.toName()
|
||||||
|
|
||||||
fun NameToken.toName() = Name(listOf(this))
|
fun NameToken.toName() = Name(listOf(this))
|
||||||
|
|
||||||
|
val EmptyName = Name(emptyList())
|
Loading…
Reference in New Issue
Block a user