Generalized Contexts for multiplatform

This commit is contained in:
Alexander Nozik 2019-01-11 07:36:02 +03:00
parent 83a56fa0cd
commit ec833dd13c
16 changed files with 242 additions and 59 deletions

View File

@ -1,8 +1,8 @@
buildscript {
extra["kotlinVersion"] = "1.3.11"
extra["ioVersion"] = "0.1.2-dev-2"
extra["ioVersion"] = "0.1.2"
extra["serializationVersion"] = "0.9.1"
extra["coroutinesVersion"] = "1.0.1"
extra["coroutinesVersion"] = "1.1.0"
val kotlinVersion: String by extra
val ioVersion: String by extra

View File

@ -9,7 +9,7 @@ repositories {
kotlin {
targets {
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 Linux, preset should be changed to e.g. presets.linuxX64
// For MacOS, preset should be changed to e.g. presets.macosX64
@ -21,11 +21,19 @@ kotlin {
api project(":dataforge-meta")
api "org.jetbrains.kotlin:kotlin-reflect"
api "io.github.microutils:kotlin-logging-common:1.6.10"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion"
}
}
jvmMain{
dependencies{
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"
}
}
}

View File

@ -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()
}

View File

@ -6,8 +6,11 @@ import hep.dataforge.names.toName
import hep.dataforge.provider.Provider
import hep.dataforge.provider.provideAll
import hep.dataforge.values.Value
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import mu.KLogger
import mu.KotlinLogging
import kotlin.coroutines.CoroutineContext
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.
* @author Alexander Nozik
*/
interface Context : Named, MetaRepr, Provider {
interface Context : Named, MetaRepr, Provider, CoroutineScope {
val parent: Context?
/**
* Context properties. Working as substitutes for environment variables
* Context properties. Working as substitute for environment variables
*/
val properties: Meta
@ -46,11 +49,6 @@ interface Context : Named, MetaRepr, Provider {
*/
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 fun provideTop(target: String, name: Name): Any? {
@ -79,10 +77,19 @@ interface Context : Named, MetaRepr, Provider {
*/
fun deactivate(activator: Any)
override val coroutineContext: CoroutineContext
get() = Dispatchers.Default
/**
* Detach all plugins and terminate context
*/
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
*/
expect object Global : Context
expect object Global : Context{
fun getContext(name: String): Context
}
/**
* The interface for something that encapsulated in context

View File

@ -46,7 +46,7 @@ interface Plugin : Named, Metoid, ContextAware, Provider, MetaRepr {
*
* @return
*/
fun dependsOn(): List<PluginTag>
fun dependsOn(): List<PluginTag> = emptyList()
/**
* Start this plugin and attach registration info to the context. This method

View File

@ -10,16 +10,17 @@ interface PluginFactory {
}
object PluginRepository {
expect object PluginRepository {
/**
* 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")

View File

@ -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()
}
}

View File

@ -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.
}
}

View File

@ -42,7 +42,6 @@ actual object Global : Context, JVMContext("GLOBAL", null, Thread.currentThread(
*
* @throws Exception
*/
@Throws(Exception::class)
override fun close() {
logger.info("Shutting down GLOBAL")
for (ctx in contextRegistry.values) {
@ -60,20 +59,7 @@ actual object Global : Context, JVMContext("GLOBAL", null, Thread.currentThread(
* @return
*/
@Synchronized
fun getContext(name: String): Context {
actual fun getContext(name: String): Context {
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)
}
}
}

View File

@ -16,6 +16,7 @@
package hep.dataforge.context
import hep.dataforge.meta.*
import kotlinx.coroutines.Dispatchers
import mu.KLogger
import mu.KotlinLogging
import java.lang.ref.WeakReference
@ -23,6 +24,7 @@ import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.collections.HashSet
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClass
import kotlin.reflect.full.cast
@ -72,23 +74,10 @@ open class JVMContext(
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) }
}
/**
* 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
*

View File

@ -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)
}

View File

@ -9,7 +9,7 @@ repositories {
kotlin {
targets {
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 Linux, preset should be changed to e.g. presets.linuxX64
// For MacOS, preset should be changed to e.g. presets.macosX64
@ -28,11 +28,5 @@ kotlin {
}
}
jsMain {
dependencies {
}
}
}
}

View File

@ -1,12 +1,27 @@
package hep.dataforge.io
import hep.dataforge.context.Context
import hep.dataforge.context.ContextAware
import hep.dataforge.meta.EmptyMeta
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.
*
* 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)
}
}

View File

@ -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)
}

View File

@ -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')
}
}
}

View File

@ -101,4 +101,6 @@ operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens)
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())